Skip to content
Merged
59 changes: 31 additions & 28 deletions packages/web-pkg/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,34 +54,35 @@
"@opencloud-eu/design-system": "workspace:^",
"@opencloud-eu/web-client": "workspace:^",
"@sentry/vue": "^10.46.0",
"@tiptap/core": "^3.28.0",
"@tiptap/extension-bubble-menu": "^3.29.2",
"@tiptap/extension-collaboration": "^3.28.0",
"@tiptap/extension-document": "^3.28.0",
"@tiptap/extension-drag-handle": "^3.28.0",
"@tiptap/extension-drag-handle-vue-3": "^3.28.0",
"@tiptap/extension-file-handler": "^3.28.0",
"@tiptap/extension-find-and-replace": "^3.29.2",
"@tiptap/extension-hard-break": "^3.28.0",
"@tiptap/extension-image": "^3.28.0",
"@tiptap/extension-link": "^3.28.0",
"@tiptap/extension-node-range": "^3.28.0",
"@tiptap/extension-paragraph": "^3.28.0",
"@tiptap/extension-placeholder": "^3.28.0",
"@tiptap/extension-subscript": "^3.29.2",
"@tiptap/extension-superscript": "^3.29.2",
"@tiptap/extension-table": "^3.28.0",
"@tiptap/extension-task-item": "^3.28.0",
"@tiptap/extension-task-list": "^3.28.0",
"@tiptap/extension-text": "^3.28.0",
"@tiptap/extension-text-align": "^3.28.0",
"@tiptap/extension-text-style": "^3.28.0",
"@tiptap/extension-underline": "^3.28.0",
"@tiptap/markdown": "^3.28.0",
"@tiptap/pm": "^3.28.0",
"@tiptap/starter-kit": "^3.28.0",
"@tiptap/suggestion": "^3.28.0",
"@tiptap/vue-3": "^3.28.0",
"@tiptap/core": "^3.30.2",
"@tiptap/extension-bubble-menu": "^3.30.2",
"@tiptap/extension-code-block-lowlight": "^3.30.2",
"@tiptap/extension-collaboration": "^3.30.2",
"@tiptap/extension-document": "^3.30.2",
"@tiptap/extension-drag-handle": "^3.30.2",
"@tiptap/extension-drag-handle-vue-3": "^3.30.2",
"@tiptap/extension-file-handler": "^3.30.2",
"@tiptap/extension-find-and-replace": "^3.30.2",
"@tiptap/extension-hard-break": "^3.30.2",
"@tiptap/extension-image": "^3.30.2",
"@tiptap/extension-link": "^3.30.2",
"@tiptap/extension-node-range": "^3.30.2",
"@tiptap/extension-paragraph": "^3.30.2",
"@tiptap/extension-placeholder": "^3.30.2",
"@tiptap/extension-subscript": "^3.30.2",
"@tiptap/extension-superscript": "^3.30.2",
"@tiptap/extension-table": "^3.30.2",
"@tiptap/extension-task-item": "^3.30.2",
"@tiptap/extension-task-list": "^3.30.2",
"@tiptap/extension-text": "^3.30.2",
"@tiptap/extension-text-align": "^3.30.2",
"@tiptap/extension-text-style": "^3.30.2",
"@tiptap/extension-underline": "^3.30.2",
"@tiptap/markdown": "^3.30.2",
"@tiptap/pm": "^3.30.2",
"@tiptap/starter-kit": "^3.30.2",
"@tiptap/suggestion": "^3.30.2",
"@tiptap/vue-3": "^3.30.2",
"@tiptap/y-tiptap": "^3.0.0",
"@uppy/core": "^5.2.0",
"@uppy/tus": "^5.1.1",
Expand All @@ -95,8 +96,10 @@
"dompurify": "^3.3.3",
"filesize": "^11.0.14",
"fuse.js": "^7.1.0",
"highlight.js": "^11.11.1",
"lodash-es": "^4.17.23",
"luxon": "^3.7.2",
"lowlight": "^3.3.0",
"mark.js": "^8.11.1",
"oidc-client-ts": "^3.5.0",
"p-queue": "^9.1.0",
Expand Down
80 changes: 80 additions & 0 deletions packages/web-pkg/src/editor/components/CodeBlockComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<template>
<node-view-wrapper class="code-block">
<div class="text-editor-code-block-language" contenteditable="false">
<oc-select
class="text-editor-code-block-select"
:model-value="selectedLanguageOption"
:options="languageOptions"
:disabled="isReadonly"
option-label="label"
:clearable="false"
:searchable="true"
:label="$gettext('Code language')"
:label-hidden="true"
@update:model-value="updateSelectedLanguage"
/>
</div>
<pre><code><node-view-content /></code></pre>
</node-view-wrapper>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { NodeViewContent, nodeViewProps, NodeViewWrapper } from '@tiptap/vue-3'
import { useGettext } from 'vue3-gettext'

const props = defineProps(nodeViewProps)
const { $gettext } = useGettext()

type LanguageOption = {
label: string
value: string | null
}

const languages = computed<string[]>(() => {
return props.extension.options.lowlight.listLanguages() as string[]
})

const selectedLanguage = computed<string | null>({
get() {
return (props.node.attrs.language as string | null) ?? null
},
set(language) {
props.updateAttributes({ language })
}
})

const languageOptions = computed<LanguageOption[]>(() => {
return [
{ label: 'auto', value: null },
...languages.value.map((language) => ({ label: language, value: language }))
]
})

const selectedLanguageOption = computed<LanguageOption>(() => {
const current = selectedLanguage.value
return languageOptions.value.find(({ value }) => value === current) ?? languageOptions.value[0]
})

const isReadonly = computed(() => props.editor.isEditable === false)

function updateSelectedLanguage(option: LanguageOption | null) {
selectedLanguage.value = option?.value ?? null
}
</script>

<style scoped>
.text-editor-code-block-language {
position: absolute;
right: 12px;
top: 4px;
min-width: 130px;
max-width: calc(100% - 24px);
}

.text-editor-code-block-select :deep(.vs__dropdown-toggle) {
min-height: 30px !important;
padding-top: 0 !important;
padding-bottom: 0 !important;
}
</style>
48 changes: 47 additions & 1 deletion packages/web-pkg/src/editor/components/TextEditorContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,26 +46,44 @@
</template>

<script setup lang="ts">
import { computed, inject, nextTick, ref, unref, useTemplateRef, watch } from 'vue'
import {
computed,
inject,
nextTick,
onMounted,
onUnmounted,
ref,
unref,
useTemplateRef,
watch
} from 'vue'
import { EditorContent } from '@tiptap/vue-3'
import { DragHandle } from '@tiptap/extension-drag-handle-vue-3'
import { useGettext } from 'vue3-gettext'
import { storeToRefs } from 'pinia'
import TextEditorTableBubbleMenu from './TextEditorTableBubbleMenu.vue'
import TextEditorLinkBubbleMenu from './TextEditorLinkBubbleMenu.vue'
import type { TextEditorInstance } from '../types'
import { useIsMobile } from '@opencloud-eu/design-system/composables'
import { useThemeStore } from '../../composables'
import atomOneDarkThemeUrl from 'highlight.js/styles/atom-one-dark.css?url'
import atomOneLightThemeUrl from 'highlight.js/styles/atom-one-light.css?url'

const { editor = undefined } = defineProps<{
editor?: TextEditorInstance
}>()

const { $gettext } = useGettext()
const { isMobile } = useIsMobile()
const themeStore = useThemeStore()
const { currentTheme } = storeToRefs(themeStore)

const textEditor = editor || inject<TextEditorInstance>('textEditor')!
const sourceContent = ref('')
const sourceModeTextareaRef = useTemplateRef<HTMLTextAreaElement>('sourceModeTextarea')
const currentDragHandleNodePos = ref<number | null>(null)
const isDarkTheme = computed(() => unref(currentTheme)?.isDark)
const hljsThemeStyleElement = ref<HTMLLinkElement | null>(null)

const isSourceMode = computed(() => unref(textEditor.state.sourceMode))
const zoomFactor = computed(() => {
Expand Down Expand Up @@ -137,6 +155,34 @@ const openSlashMenu = () => {
}
}

function applyHljsThemeCss() {
if (!hljsThemeStyleElement.value) {
return
}

hljsThemeStyleElement.value.href = unref(isDarkTheme) ? atomOneDarkThemeUrl : atomOneLightThemeUrl
}

watch(isDarkTheme, applyHljsThemeCss)

onMounted(() => {
if (typeof document === 'undefined') {
return
}

const styleElement = document.createElement('link')
styleElement.rel = 'stylesheet'
styleElement.setAttribute('data-oc-text-editor-hljs-theme', 'true')
document.head.appendChild(styleElement)
hljsThemeStyleElement.value = styleElement
applyHljsThemeCss()
})

onUnmounted(() => {
hljsThemeStyleElement.value?.remove()
hljsThemeStyleElement.value = null
})

watch(isSourceMode, async () => {
if (unref(isSourceMode)) {
sourceContent.value = textEditor.getContent()
Expand Down
14 changes: 11 additions & 3 deletions packages/web-pkg/src/editor/composables/strategies/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ import {
} from '@tiptap/extension-text-style'
import { EditorActionGroup, useEditorActions } from '../useEditorActions'
import { TextEditorState } from '../../types'
import { createLinkExtension } from '../../extensions'
import { imageFileHandlerExtension } from './imageFileHandler'
import {
createCodeBlockLowlight,
createLinkExtension,
imageFileHandlerExtension
} from '../../extensions'

export const useStrategyHtml = (editorState: TextEditorState): ContentTypeStrategy => {
const { $gettext } = useGettext()
Expand All @@ -44,7 +47,12 @@ export const useStrategyHtml = (editorState: TextEditorState): ContentTypeStrate

const extensions = (options?: ExtensionsOptions): Extension[] => {
return [
StarterKit.configure({ link: false, undoRedo: options?.yjs ? false : undefined }),
StarterKit.configure({
link: false,
codeBlock: false,
undoRedo: options?.yjs ? false : undefined
}),
createCodeBlockLowlight(),
createLinkExtension(),
Image.configure({
inline: false,
Expand Down
14 changes: 11 additions & 3 deletions packages/web-pkg/src/editor/composables/strategies/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ import TaskList from '@tiptap/extension-task-list'
import TaskItem from '@tiptap/extension-task-item'
import { useGettext } from 'vue3-gettext'
import { TextEditorState } from '../../types'
import { createLinkExtension } from '../../extensions'
import { imageFileHandlerExtension } from './imageFileHandler'
import {
createCodeBlockLowlight,
createLinkExtension,
imageFileHandlerExtension
} from '../../extensions'

export const useStrategyMarkdown = (editorState: TextEditorState): ContentTypeStrategy => {
const { $gettext } = useGettext()
Expand Down Expand Up @@ -70,7 +73,12 @@ export const useStrategyMarkdown = (editorState: TextEditorState): ContentTypeSt
})

return [
StarterKit.configure({ link: false, undoRedo: options?.yjs ? false : undefined }),
StarterKit.configure({
link: false,
codeBlock: false,
undoRedo: options?.yjs ? false : undefined
}),
createCodeBlockLowlight(),
Markdown,
createLinkExtension(),
Table.configure({ resizable: false }),
Expand Down
14 changes: 11 additions & 3 deletions packages/web-pkg/src/editor/composables/strategies/tiptapJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ import {
TextStyle
} from '@tiptap/extension-text-style'
import { TextEditorState } from '../../types'
import { createLinkExtension } from '../../extensions'
import { imageFileHandlerExtension } from './imageFileHandler'
import {
createCodeBlockLowlight,
createLinkExtension,
imageFileHandlerExtension
} from '../../extensions'

export const useStrategyTiptapJson = (editorState: TextEditorState): ContentTypeStrategy => {
const { $gettext } = useGettext()
Expand All @@ -42,7 +45,12 @@ export const useStrategyTiptapJson = (editorState: TextEditorState): ContentType

const extensions = (options?: ExtensionsOptions): Extension[] => {
return [
StarterKit.configure({ link: false, undoRedo: options?.yjs ? false : undefined }),
StarterKit.configure({
link: false,
codeBlock: false,
undoRedo: options?.yjs ? false : undefined
}),
createCodeBlockLowlight(),
createLinkExtension(),
Image.configure({
inline: false,
Expand Down
14 changes: 14 additions & 0 deletions packages/web-pkg/src/editor/extensions/codeBlockLowlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import { VueNodeViewRenderer } from '@tiptap/vue-3'
import { common, createLowlight } from 'lowlight'
import CodeBlockComponent from '../components/CodeBlockComponent.vue'

const lowlight = createLowlight(common)

export function createCodeBlockLowlight() {
return CodeBlockLowlight.extend({
addNodeView() {
return VueNodeViewRenderer(CodeBlockComponent)
}
}).configure({ lowlight })
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good that you moved it 👍

File renamed without changes.
2 changes: 2 additions & 0 deletions packages/web-pkg/src/editor/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './codeBlockLowlight'
export * from './imageFileHandler'
export * from './link'
export * from './slashCommands'
Loading