diff --git a/packages/lexical-playground/__tests__/e2e/Collaboration.spec.mjs b/packages/lexical-playground/__tests__/e2e/Collaboration.spec.mjs index df5414b355f..f83590e5cc4 100644 --- a/packages/lexical-playground/__tests__/e2e/Collaboration.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Collaboration.spec.mjs @@ -231,7 +231,7 @@ test.describe('Collaboration', () => { }); }); - test('Undo with two collaborators editing same paragraph', async ({ + test('Remove dangling text from YJS when there is no preceding text node', async ({ isRichText, page, isCollab, @@ -273,11 +273,12 @@ test.describe('Collaboration', () => { `, ); - // Left collaborator undoes their second paragraph - await sleep(1050); + // Left collaborator undoes their text in the second paragraph. + await sleep(50); await page.frameLocator('iframe[name="left"]').getByLabel('Undo').click(); - // Only left collaborator's text should have been undone + // The undo also removed the text node from YJS. + // Check that the dangling text from right user was also removed. await assertHTML( page, html` @@ -286,11 +287,7 @@ test.describe('Collaboration', () => { dir="ltr"> Line 1
-- Word -
++ normal + + boldBOLD + +
+ `, + ); + + // Left collaborator undoes their bold text. + await sleep(50); + await page.frameLocator('iframe[name="left"]').getByLabel('Undo').click(); + + // The undo also removed bold the text node from YJS. + // Check that the dangling text from right user was merged into the preceding text node. + await assertHTML( + page, + html` ++ normalBOLD +
+ `, + ); + + // Left collaborator refreshes their page + await page.evaluate(() => { + document + .querySelector('iframe[name="left"]') + .contentDocument.location.reload(); + }); + + // Page content should be the same as before the refresh + await assertHTML( + page, + html`- Word + normalBOLD
`, ); diff --git a/packages/lexical-yjs/src/CollabElementNode.ts b/packages/lexical-yjs/src/CollabElementNode.ts index f4c3f124c55..c38171af0e8 100644 --- a/packages/lexical-yjs/src/CollabElementNode.ts +++ b/packages/lexical-yjs/src/CollabElementNode.ts @@ -157,21 +157,25 @@ export class CollabElementNode { nodeIndex !== 0 ? children[nodeIndex - 1] : null; const nodeSize = node.getSize(); - if ( - offset === 0 && - delCount === 1 && - nodeIndex > 0 && - prevCollabNode instanceof CollabTextNode && - length === nodeSize && - // If the node has no keys, it's been deleted - Array.from(node._map.keys()).length === 0 - ) { - // Merge the text node with previous. - prevCollabNode._text += node._text; - children.splice(nodeIndex, 1); - } else if (offset === 0 && delCount === nodeSize) { - // The entire thing needs removing + if (offset === 0 && length === nodeSize) { + // Text node has been deleted. children.splice(nodeIndex, 1); + // If this was caused by an undo from YJS, there could be dangling text. + const danglingText = spliceString( + node._text, + offset, + delCount - 1, + '', + ); + if (danglingText.length > 0) { + if (prevCollabNode instanceof CollabTextNode) { + // Merge the text node with previous. + prevCollabNode._text += danglingText; + } else { + // No previous text node to merge into, just delete the text. + this._xmlText.delete(offset, danglingText.length); + } + } } else { node._text = spliceString(node._text, offset, delCount, ''); }