Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Next Next commit
fix: Create idempotent y.js doc for initial content
Signed-off-by: Julius Härtl <[email protected]>
  • Loading branch information
juliusknorr authored and backportbot[bot] committed Apr 2, 2024
commit ffb0430e32012196b80025f31e3f861823c0addc
3 changes: 1 addition & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"vue-click-outside": "^1.1.0",
"vue-material-design-icons": "^5.3.0",
"vuex": "^3.6.2",
"y-prosemirror": "^1.0.20",
"y-protocols": "^1.0.6",
"y-websocket": "^2.0.1",
"yjs": "^13.6.14"
Expand Down
2 changes: 1 addition & 1 deletion src/EditorFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const loadSyntaxHighlight = async (language) => {
}
}

const createEditor = ({ language, onCreate, onUpdate = () => {}, extensions, enableRichEditing, session, relativePath, isEmbedded = false }) => {
const createEditor = ({ language, onCreate = () => {}, onUpdate = () => {}, extensions, enableRichEditing, session, relativePath, isEmbedded = false }) => {
let defaultExtensions
if (enableRichEditing) {
defaultExtensions = [
Expand Down
8 changes: 2 additions & 6 deletions src/components/Editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,8 @@ export default {
logger.debug('onLoaded: Pushing local changes to server')
this.$queue.push(updateMessage)
}
} else {
this.setInitialYjsState(documentSource, { isRich: this.isRichEditor })
}

this.hasConnectionIssue = false
Expand Down Expand Up @@ -542,12 +544,6 @@ export default {
isEmbedded: this.isEmbedded,
})
this.hasEditor = true
if (!documentState && documentSource) {
this.setContent(documentSource, {
isRich: this.isRichEditor,
addToHistory: false,
})
}
this.listenEditorEvents()
} else {
// $editor already existed. So this is a reconnect.
Expand Down
38 changes: 38 additions & 0 deletions src/mixins/setContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@

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 { createEditor } from '../EditorFactory.js'

export default {
methods: {
Expand All @@ -38,5 +43,38 @@ export default {
.run()
},

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

const editor = createEditor({
enableRichEditing: isRich,
})
const json = generateJSON(html, editor.extensionManager.extensions)

const doc = Node.fromJSON(editor.schema, json)
const getBaseDoc = (doc) => {
const ydoc = 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
// 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
ydoc.clientID = 0
const type = /** @type {XmlFragment} */ (ydoc.get('default', XmlFragment))
if (!type.doc) {
prosemirrorToYXmlFragment(doc, ydoc)
return ydoc
}

prosemirrorToYXmlFragment(doc, type)
return ydoc
}

const baseUpdate = encodeStateAsUpdate(getBaseDoc(doc))
applyUpdate(this.$ydoc, baseUpdate)
},
},
}