Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
881ce5b
chore(migrate): useEditorMixin to useEditor composable
max-nextcloud Jun 16, 2025
8d998f0
chore(migrate): setContent mixin...
max-nextcloud Jun 17, 2025
13a5ce1
chore(migrate): to useEditorFlags composable
max-nextcloud Jun 18, 2025
d958a4d
chore(cleanup): fix small review remarks
max-nextcloud Jun 18, 2025
b0790dc
chore(migrate): use.find instead of deprecated .contains
max-nextcloud Jun 19, 2025
8b4e635
enh(editor): store session in separate extension
max-nextcloud Jun 20, 2025
9d3cc87
chore(refactor): configure mention in rich text extension
max-nextcloud Jun 20, 2025
c4f863f
chore(types): collaborationCursor extension to typescript
max-nextcloud Jun 20, 2025
354ad41
chore(simplify): rely on updateUser command
max-nextcloud Jun 20, 2025
022e483
chore(simplify): replace computed fileExtension with temp
max-nextcloud Jun 20, 2025
0809c8e
enh(code): start to load syntax highlighting during setup
max-nextcloud Jun 20, 2025
b326875
chore(refactor): load editor in mounted
max-nextcloud Jun 20, 2025
3e5cfd8
chore(refactor): create editor in created instead of mounted
max-nextcloud Jun 26, 2025
ab26032
chore(refactor): create ydoc in setup
max-nextcloud Jun 26, 2025
2831462
fix(character-count): always provide the current editors doc
max-nextcloud Jun 26, 2025
d7df4c0
fix(character-count): use the NcActionTexts name prop
max-nextcloud Jun 26, 2025
e24d90a
fix(loading): only show main container when content loaded
max-nextcloud Jun 26, 2025
0d5f4f7
fix(mention): use shallowRef for connection
max-nextcloud Jun 27, 2025
057a682
fix(load): create initial YjsState with dir
max-nextcloud Jun 27, 2025
fae62fc
chore(refactor): extract useEditor into its own file
max-nextcloud Jun 28, 2025
17f0d62
chore(refactor): extract useEditorFlags into its own file
max-nextcloud Jun 28, 2025
90b1d84
test(RichTextReader): basic test
max-nextcloud Jun 28, 2025
fabc79e
test(RichTextReader): update content
max-nextcloud Jun 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore(migrate): setContent mixin...
to useEditorMethods composable
and setInitialYjsState helper method.

Signed-off-by: Max <[email protected]>
  • Loading branch information
max-nextcloud committed Jun 28, 2025
commit 8d998f059f2748c78863c0a6b048beebe724b8ca
8 changes: 5 additions & 3 deletions src/components/BaseReader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
useOutlineActions,
} from './Editor/Wrapper.provider.js'
import EditorOutline from './Editor/EditorOutline.vue'
import { useEditorMethods } from '../composables/useEditorMethods.ts'

export default {
name: 'BaseReader',
Expand All @@ -52,7 +53,8 @@ export default {
},

setup() {
const { editor, setEditable } = provideEditor()
const { editor } = provideEditor()
const { setEditable } = useEditorMethods(editor)
return { editor, setEditable }
},

Expand All @@ -77,7 +79,7 @@ export default {
},

beforeDestroy() {
this.editor.destroy()
this.editor?.destroy()
},

methods: {
Expand All @@ -89,7 +91,7 @@ export default {
},

updateContent() {
this.editor.commands.setContent(this.htmlContent, true)
this.editor?.commands.setContent(this.htmlContent, true)
},
},
}
Expand Down
9 changes: 5 additions & 4 deletions src/components/CollisionResolveDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ import {
useSyncServiceMixin,
} from './Editor.provider.ts'
import NcButton from '@nextcloud/vue/components/NcButton'
import setContent from './../mixins/setContent.js'
import { useEditorMethods } from '../composables/useEditorMethods.ts'
export default {
name: 'CollisionResolveDialog',
components: {
NcButton,
},
mixins: [useIsRichEditorMixin, setContent, useSyncServiceMixin],
mixins: [useIsRichEditorMixin, useSyncServiceMixin],
props: {
syncError: {
type: Object,
Expand All @@ -50,8 +50,9 @@ export default {
},
setup() {
// editor is needed for the setContent mixin
const { editor, setEditable } = useEditor()
return { editor, setEditable }
const { editor } = useEditor()
const { setContent, setEditable } = useEditorMethods(editor)
return { editor, setContent, setEditable }
},
data() {
return {
Expand Down
14 changes: 2 additions & 12 deletions src/components/Editor.provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,11 @@ export const editorKey = Symbol('tiptap:editor') as InjectionKey<
export const provideEditor = () => {
const editor: ShallowRef<Editor | undefined> = shallowRef(undefined)
provide(editorKey, editor)
const setEditable = (val: boolean) => {
if (editor.value && editor.value.isEditable !== val) {
editor.value.setEditable(val)
}
}
return { editor, setEditable }
return { editor }
}
export const useEditor = () => {
const editor = inject(editorKey, shallowRef(undefined))
const setEditable = (val: boolean) => {
if (editor.value && editor.value.isEditable !== val) {
editor.value.setEditable(val)
}
}
return { editor, setEditable }
return { editor }
}

export const FILE = Symbol('editor:file')
Expand Down
10 changes: 6 additions & 4 deletions src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ import { exposeForDebugging, removeFromDebugging } from '../helpers/debug.js'
import { CollaborationCursor } from '../extensions/index.js'
import DocumentStatus from './Editor/DocumentStatus.vue'
import isMobile from './../mixins/isMobile.js'
import setContent from './../mixins/setContent.js'
import { setInitialYjsState } from '../helpers/setInitialYjsState.js'
import MenuBar from './Menu/MenuBar.vue'
import ContentContainer from './Editor/ContentContainer.vue'
import Status from './Editor/Status.vue'
Expand All @@ -137,6 +137,7 @@ import { generateRemoteUrl } from '@nextcloud/router'
import { fetchNode } from '../services/WebdavClient.ts'
import SuggestionsBar from './SuggestionsBar.vue'
import { useDelayedFlag } from './Editor/useDelayedFlag.ts'
import { useEditorMethods } from '../composables/useEditorMethods.ts'

export default {
name: 'Editor',
Expand All @@ -155,7 +156,7 @@ export default {
Translate,
SuggestionsBar,
},
mixins: [isMobile, setContent],
mixins: [isMobile],

provide() {
const val = {}
Expand Down Expand Up @@ -249,7 +250,8 @@ export default {
})
const hasConnectionIssue = ref(false)
const { delayed: requireReconnect } = useDelayedFlag(hasConnectionIssue)
const { editor, setEditable } = provideEditor()
const { editor } = provideEditor()
const { setEditable } = useEditorMethods(editor)
return {
el,
width,
Expand Down Expand Up @@ -589,7 +591,7 @@ export default {

onLoaded({ document, documentSource, documentState }) {
if (!documentState) {
this.setInitialYjsState(documentSource, {
setInitialYjsState(this.$ydoc, documentSource, {
isRichEditor: this.isRichEditor,
})
}
Expand Down
6 changes: 4 additions & 2 deletions src/components/Editor/MarkdownContentEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import markdownit from '../../markdownit/index.js'
import { RichText, FocusTrap } from '../../extensions/index.js'
import ReadonlyBar from '../Menu/ReadonlyBar.vue'
import ContentContainer from './ContentContainer.vue'
import { useEditorMethods } from '../../composables/useEditorMethods.ts'

export default {
name: 'MarkdownContentEditor',
Expand Down Expand Up @@ -86,7 +87,8 @@ export default {
emits: ['update:content'],

setup() {
const { editor, setEditable } = provideEditor()
const { editor } = provideEditor()
const { setEditable } = useEditorMethods(editor)
return { editor, setEditable }
},

Expand Down Expand Up @@ -120,7 +122,7 @@ export default {
},

beforeDestroy() {
this.editor.destroy()
this.editor?.destroy()
},

methods: {
Expand Down
37 changes: 37 additions & 0 deletions src/composables/useEditorMethods.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { Editor } from '@tiptap/core'
import escapeHtml from 'escape-html'
import { unref, type ShallowRef } from 'vue'
import markdownit from '../markdownit/index.js'

export const useEditorMethods = (editor: ShallowRef<Editor | undefined>) => {
const setEditable = (val: boolean) => {
const ed = unref(editor)
if (ed && ed.isEditable !== val) {
ed.setEditable(val)
}
}

const setContent: (
content: string,
options: { isRichEditor?: boolean; addToHistory?: boolean },
) => void = (content, { isRichEditor, addToHistory = true } = {}) => {
const html = isRichEditor
? markdownit.render(content) + '<p/>'
: `<pre>${escapeHtml(content)}</pre>`
editor.value
?.chain()
.setContent(html, addToHistory)
.command(({ tr }) => {
tr.setMeta('addToHistory', addToHistory)
return true
})
.run()
}

return { setContent, setEditable }
}
46 changes: 46 additions & 0 deletions src/helpers/setInitialYjsState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import escapeHtml from 'escape-html'
import markdownit from '../markdownit/index.js'
import { Doc, encodeStateAsUpdate, XmlFragment, applyUpdate } from 'yjs'
import { generateJSON } from '@tiptap/core'
import { prosemirrorToYXmlFragment } from 'y-prosemirror'
import { Node } from '@tiptap/pm/model'
import { createRichEditor, createPlainEditor } from '../EditorFactory.js'

export const setInitialYjsState = (ydoc, content, { isRichEditor }) => {
const html = isRichEditor
? markdownit.render(content) + '<p/>'
: `<pre>${escapeHtml(content)}</pre>`

const editor = isRichEditor ? createRichEditor() : createPlainEditor()

const json = generateJSON(html, editor.options.extensions)

const node = Node.fromJSON(editor.schema, json)
const getBaseDoc = (node) => {
const baseDoc = new Doc()
// In order to make the initial document state idempotent, we need to reset the clientID
// While this is not recommended, we cannot avoid it here as we lack another mechanism
// to generate the initial state on the server side
// The only other option to avoid this could be to generate the initial state once and push
// it to the server immediately, however this would require read only sessions to be able
// to still push a state
baseDoc.clientID = 0
const type = /** @type {XmlFragment} */ (baseDoc.get('default', XmlFragment))
if (!type.doc) {
// This should not happen but is aligned with the upstream implementation
// https://github.com/yjs/y-prosemirror/blob/8db24263770c2baaccb08e08ea9ef92dbcf8a9da/src/lib.js#L209
return baseDoc
}

prosemirrorToYXmlFragment(node, type)
return baseDoc
}

const baseUpdate = encodeStateAsUpdate(getBaseDoc(node))
applyUpdate(ydoc, baseUpdate)
}
66 changes: 0 additions & 66 deletions src/mixins/setContent.js

This file was deleted.