Skip to content

Commit

Permalink
Fix duplication issue on concurrent insertion into empty nodes - closes
Browse files Browse the repository at this point in the history
  • Loading branch information
dmonad committed Aug 5, 2024
1 parent b96bdb9 commit 627b6b2
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 2 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"test": "npm run lint && rollup -c && node dist/test.cjs",
"lint": "standard && tsc",
"watch": "rollup -wc",
"debug": "concurrently 'http-server -o test.html' 'npm run watch'",
"debug": "concurrently '0serve -o test.html' 'npm run watch'",
"preversion": "npm run lint && npm run dist && npm run test",
"start": "concurrently 'http-server -o demo/prosemirror.html' 'npm run watch'"
"start": "concurrently '0serve -o demo/prosemirror.html' 'npm run watch'"
},
"exports": {
".": {
Expand Down
14 changes: 14 additions & 0 deletions src/plugins/sync-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,20 @@ export const createNodeFromYElement = (
children.push(n)
}
} else {
// If the next ytext exists and was created by us, move the content to the current ytext.
// This is a fix for #160 -- duplication of characters when two Y.Text exist next to each
// other.
const nextytext = type._item.right?.content.type
if (nextytext != null && !nextytext._item.deleted && nextytext._item.id.client === nextytext.doc.clientID) {
type.applyDelta([
{ retain: type.length },
...nextytext.toDelta()
])
nextytext.doc.transact(tr => {
nextytext._item.delete(tr)
})
}
// now create the prosemirror text nodes
const ns = createTextNodesFromYText(
type,
schema,
Expand Down
49 changes: 49 additions & 0 deletions tests/y-prosemirror.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,55 @@ export const testEmptyParagraph = (_tc) => {
)
}

/**
* Test duplication issue https://github.com/yjs/y-prosemirror/issues/161
*
* @param {t.TestCase} tc
*/
export const testInsertDuplication = (_tc) => {
const ydoc1 = new Y.Doc()
ydoc1.clientID = 1
const ydoc2 = new Y.Doc()
ydoc2.clientID = 2
const view1 = createNewProsemirrorView(ydoc1)
const view2 = createNewProsemirrorView(ydoc2)
const yxml1 = ydoc1.getXmlFragment('prosemirror')
const yxml2 = ydoc2.getXmlFragment('prosemirror')
yxml1.observeDeep(events => {
events.forEach(event => {
console.log('yxml1: ', JSON.stringify(event.changes.delta))
})
})
yxml2.observeDeep(events => {
events.forEach(event => {
console.log('yxml2: ', JSON.stringify(event.changes.delta))
})
})
view1.dispatch(
view1.state.tr.insert(
0,
/** @type {any} */ (schema.node(
'paragraph'
))
)
)
const sync = () => {
Y.applyUpdate(ydoc2, Y.encodeStateAsUpdate(ydoc1))
Y.applyUpdate(ydoc1, Y.encodeStateAsUpdate(ydoc2))
Y.applyUpdate(ydoc2, Y.encodeStateAsUpdate(ydoc1))
Y.applyUpdate(ydoc1, Y.encodeStateAsUpdate(ydoc2))
}
sync()
view1.dispatch(view1.state.tr.insertText('1', 1, 1))
view2.dispatch(view2.state.tr.insertText('2', 1, 1))
sync()
view1.dispatch(view1.state.tr.insertText('1', 2, 2))
view2.dispatch(view2.state.tr.insertText('2', 3, 3))
sync()
checkResult({ testObjects: [view1, view2] })
t.assert(yxml1.toString() === '<paragraph>1122</paragraph><paragraph></paragraph>')
}

export const testAddToHistory = (_tc) => {
const ydoc = new Y.Doc()
const view = createNewProsemirrorViewWithUndoManager(ydoc)
Expand Down

0 comments on commit 627b6b2

Please sign in to comment.