Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f0a01f0
[lexical-yjs][lexical-react] Refactor: split out useYjsCollaborationI…
james-atticus Jun 11, 2025
8d91369
[lexical-yjs][lexical-react] Feature: initial implementation of colla…
james-atticus Jun 12, 2025
e59f560
[lexical-yjs] Feature: sync cursors in collab v2
james-atticus Jun 17, 2025
7160838
Merge remote-tracking branch 'origin/main' into yjs-v2-boilerplate
james-atticus Aug 20, 2025
c68770e
Merge remote-tracking branch 'origin/main' into yjs-v2-boilerplate
james-atticus Sep 2, 2025
64bd3ad
Merge remote-tracking branch 'origin/main' into yjs-v2-boilerplate
james-atticus Sep 4, 2025
169c242
Merge remote-tracking branch 'origin/main' into yjs-v2-boilerplate
james-atticus Sep 8, 2025
0f63303
Merge remote-tracking branch 'origin/main' into yjs-v2-boilerplate
james-atticus Sep 9, 2025
7bcf36a
[*] Feature: more v2 sync code, all e2e and unit tests passing
james-atticus Sep 10, 2025
28235aa
Merge remote-tracking branch 'origin/main' into yjs-v2-boilerplate
james-atticus Sep 10, 2025
21aa07e
rewrite lexicalmapping to collabv2mapping without bimultimap
james-atticus Sep 10, 2025
4e7a3ac
self-review
james-atticus Sep 11, 2025
2208ab0
self-review pt2
james-atticus Sep 11, 2025
faee909
simpleDiff
james-atticus Sep 11, 2025
b60c886
attrs
james-atticus Sep 11, 2025
1080cf0
node state
james-atticus Sep 11, 2025
586b6af
more targeted updates of properties/state
james-atticus Sep 11, 2025
d7b6d2d
pull out toDelta with default attributes
james-atticus Sep 12, 2025
9fffb0c
little bit more cleanup
james-atticus Sep 12, 2025
2892aed
pass in doc+provider in v2 plugin
james-atticus Sep 16, 2025
13a41c3
__shouldBootstrapUnsafe, getEditorState(), remove === false
james-atticus Sep 19, 2025
1c5f8bd
switch to interface, introduce AnyBinding
james-atticus Sep 19, 2025
2b951ea
AnyBinding for cursor fn
james-atticus Sep 19, 2025
fb16e9a
Merge remote-tracking branch 'origin/main' into yjs-v2-boilerplate
james-atticus Sep 30, 2025
5d8b4ec
allow for selected class on datetime component
james-atticus Sep 30, 2025
edd7129
fixup! Merge remote-tracking branch 'origin/main' into yjs-v2-boilerp…
james-atticus Sep 30, 2025
e13bd4b
remove unused import
james-atticus Sep 30, 2025
a9d7f44
more targeted state update when syncing from yjs
james-atticus Sep 30, 2025
cd975d3
Merge branch 'main' into yjs-v2-boilerplate
etrepum Sep 30, 2025
0d9fc70
Merge branch 'main' into yjs-v2-boilerplate
etrepum Oct 2, 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
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
"test-e2e-collab-chromium": "cross-env E2E_BROWSER=chromium E2E_EDITOR_MODE=rich-text-with-collab playwright test --project=\"chromium\"",
"test-e2e-collab-firefox": "cross-env E2E_BROWSER=firefox E2E_EDITOR_MODE=rich-text-with-collab playwright test --project=\"firefox\"",
"test-e2e-collab-webkit": "cross-env E2E_BROWSER=webkit E2E_EDITOR_MODE=rich-text-with-collab playwright test --project=\"webkit\"",
"test-e2e-collab-v2-chromium": "cross-env E2E_BROWSER=chromium E2E_EDITOR_MODE=rich-text-with-collab-v2 playwright test --project=\"chromium\"",
"test-e2e-collab-v2-firefox": "cross-env E2E_BROWSER=firefox E2E_EDITOR_MODE=rich-text-with-collab-v2 playwright test --project=\"firefox\"",
"test-e2e-collab-v2-webkit": "cross-env E2E_BROWSER=webkit E2E_EDITOR_MODE=rich-text-with-collab-v2 playwright test --project=\"webkit\"",
"test-e2e-prod-chromium": "cross-env E2E_BROWSER=chromium E2E_PORT=4000 playwright test --project=\"chromium\"",
"test-e2e-collab-prod-chromium": "cross-env E2E_BROWSER=chromium E2E_PORT=4000 E2E_EDITOR_MODE=rich-text-with-collab playwright test --project=\"chromium\"",
"test-e2e-ci-chromium": "npm run prepare-ci && cross-env E2E_PORT=4000 npm run test-e2e-chromium",
Expand Down
2 changes: 1 addition & 1 deletion packages/lexical-code-shiki/src/CodeHighlighterShiki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ export function registerCodeHighlighting(
editor.registerMutationListener(
CodeNode,
(mutations) => {
editor.update(() => {
editor.getEditorState().read(() => {
for (const [key, type] of mutations) {
if (type !== 'destroyed') {
const node = $getNodeByKey(key);
Expand Down
2 changes: 1 addition & 1 deletion packages/lexical-code/src/CodeHighlighterPrism.ts
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ export function registerCodeHighlighting(
editor.registerMutationListener(
CodeNode,
(mutations) => {
editor.update(() => {
editor.getEditorState().read(() => {
for (const [key, type] of mutations) {
if (type !== 'destroyed') {
const node = $getNodeByKey(key);
Expand Down
86 changes: 59 additions & 27 deletions packages/lexical-playground/__tests__/e2e/Collaboration.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,21 @@ test.describe('Collaboration', () => {
</p>
`,
);
await assertSelection(page, {
anchorOffset: 5,
anchorPath: [0, 0, 0],
focusOffset: 5,
focusPath: [0, 0, 0],
});
if (isCollab === 1) {
await assertSelection(page, {
anchorOffset: 5,
anchorPath: [0, 0, 0],
focusOffset: 5,
focusPath: [0, 0, 0],
});
} else {
await assertSelection(page, {
anchorOffset: 1,
anchorPath: [0],
focusOffset: 1,
focusPath: [0],
});
}
Comment on lines +163 to +177
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same overall outcome. This was the driver for #7794.


await page.keyboard.press('ArrowDown');
await page.keyboard.type('Some bold text');
Expand Down Expand Up @@ -314,7 +323,6 @@ test.describe('Collaboration', () => {
</p>
`,
);
const boldSleep = sleep(1050);

// Right collaborator types at the end of the paragraph.
await page
Expand All @@ -339,7 +347,7 @@ test.describe('Collaboration', () => {
);

// Left collaborator undoes their bold text.
await boldSleep;
await sleep(1050);
await page.frameLocator('iframe[name="left"]').getByLabel('Undo').click();

// The undo also removed bold the text node from YJS.
Expand Down Expand Up @@ -428,15 +436,27 @@ test.describe('Collaboration', () => {
// Left collaborator undoes their bold text.
await page.frameLocator('iframe[name="left"]').getByLabel('Undo').click();

// The undo causes the text to be appended to the original string, like in the above test.
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph" dir="auto">
<span data-lexical-text="true">normal boldBOLD</span>
</p>
`,
);
if (isCollab === 1) {
// The undo causes the text to be appended to the original string, like in the above test.
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph" dir="auto">
<span data-lexical-text="true">normal boldBOLD</span>
</p>
`,
);
} else {
// In v2, the text is not moved.
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph" dir="auto">
<span data-lexical-text="true">normal boBOLDld</span>
</p>
`,
);
}

// Left collaborator redoes the bold text.
await page.frameLocator('iframe[name="left"]').getByLabel('Redo').click();
Expand Down Expand Up @@ -549,15 +569,27 @@ test.describe('Collaboration', () => {
// Left collaborator undoes the link.
await page.frameLocator('iframe[name="left"]').getByLabel('Undo').click();

// The undo causes the text to be appended to the original string, like in the above test.
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph" dir="auto">
<span data-lexical-text="true">Check out the website! now</span>
</p>
`,
);
if (isCollab === 1) {
// The undo causes the text to be appended to the original string, like in the above test.
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph" dir="auto">
<span data-lexical-text="true">Check out the website! now</span>
</p>
`,
);
} else {
// The undo causes the YText node to be removed.
await assertHTML(
page,
html`
<p class="PlaygroundEditorTheme__paragraph" dir="auto">
<span data-lexical-text="true">Check out the website!</span>
</p>
`,
);
}

// Left collaborator redoes the link.
await page.frameLocator('iframe[name="left"]').getByLabel('Redo').click();
Expand Down Expand Up @@ -636,7 +668,7 @@ test.describe('Collaboration', () => {
`,
);

// Right collaborator deletes A, left deletes B.
// Left collaborator deletes A, right deletes B.
await sleep(1050);
await page.keyboard.press('Delete');
await sleep(50);
Expand Down
4 changes: 3 additions & 1 deletion packages/lexical-playground/__tests__/e2e/File.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
html,
initialize,
insertUploadImage,
IS_COLLAB_V2,
sleep,
test,
waitForSelector,
Expand All @@ -24,7 +25,8 @@ test.describe('File', () => {
test.beforeEach(({isCollab, page}) => initialize({isCollab, page}));

test(`Can import/export`, async ({page, isPlainText}) => {
test.skip(isPlainText);
// TODO(collab-v2): nested editors are not supported yet
test.skip(isPlainText || IS_COLLAB_V2);
await focusEditor(page);
await toggleBold(page);
await page.keyboard.type('Hello');
Expand Down
4 changes: 4 additions & 0 deletions packages/lexical-playground/__tests__/e2e/Images.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
insertSampleImage,
insertUploadImage,
insertUrlImage,
IS_COLLAB_V2,
IS_WINDOWS,
LEGACY_EVENTS,
SAMPLE_IMAGE_URL,
Expand All @@ -33,6 +34,9 @@ import {
} from '../utils/index.mjs';

test.describe('Images', () => {
// TODO(collab-v2): nested editors are not supported yet
test.skip(IS_COLLAB_V2);

test.beforeEach(({isCollab, page}) => initialize({isCollab, page}));
test(`Can create a decorator and move selection around it`, async ({
page,
Expand Down
19 changes: 4 additions & 15 deletions packages/lexical-playground/__tests__/e2e/List.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ import {
click,
copyToClipboard,
focusEditor,
getExpectedDateTimeHtml,
html,
initialize,
insertSampleImage,
insertDateTime,
pasteFromClipboard,
repeat,
SAMPLE_IMAGE_URL,
selectFromAlignDropdown,
selectFromColorPicker,
selectFromFormatDropdown,
Expand Down Expand Up @@ -229,7 +229,7 @@ test.describe.parallel('Nested List', () => {
await focusEditor(page);
await toggleBulletList(page);

await insertSampleImage(page);
await insertDateTime(page);
await page.keyboard.type('x');
await moveLeft(page, 1);

Expand All @@ -244,18 +244,7 @@ test.describe.parallel('Nested List', () => {
value="1">
<ul class="PlaygroundEditorTheme__ul">
<li class="PlaygroundEditorTheme__listItem" value="1">
<span
class="editor-image"
contenteditable="false"
data-lexical-decorator="true">
<div draggable="false">
<img
alt="Yellow flower in tilt shift lens"
draggable="false"
src="${SAMPLE_IMAGE_URL}"
style="height: inherit; max-width: 500px; width: inherit" />
</div>
</span>
${getExpectedDateTimeHtml()}
<span data-lexical-text="true">x</span>
</li>
</ul>
Expand Down
11 changes: 9 additions & 2 deletions packages/lexical-playground/__tests__/e2e/Markdown.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
getHTML,
html,
initialize,
IS_COLLAB_V2,
LEGACY_EVENTS,
pasteFromClipboard,
pressToggleBold,
Expand Down Expand Up @@ -906,6 +907,9 @@ test.describe.parallel('Markdown', () => {
});

test('can import single decorator node (#2604)', async ({page}) => {
// TODO(collab-v2): nested editors are not supported yet
test.skip(IS_COLLAB_V2);

await focusEditor(page);
await page.keyboard.type(
'```markdown ![Yellow flower in tilt shift lens](' +
Expand Down Expand Up @@ -939,6 +943,9 @@ test.describe.parallel('Markdown', () => {
test('can import several text match transformers in a same line (#5385)', async ({
page,
}) => {
// TODO(collab-v2): nested editors are not supported yet
test.skip(IS_COLLAB_V2);

await focusEditor(page);
await page.keyboard.type(
'```markdown [link](https://lexical.dev)[link](https://lexical.dev)![Yellow flower in tilt shift lens](' +
Expand Down Expand Up @@ -1053,7 +1060,7 @@ test.describe.parallel('Markdown', () => {

const TYPED_MARKDOWN = `# Markdown Shortcuts
This is *italic*, _italic_, **bold**, __bold__, ~~strikethrough~~ text
This is *__~~bold italic strikethrough~~__* text, ___~~this one too~~___
This is ~~*__bold italic strikethrough__*~~ text, ___~~this one too~~___
It ~~___works [with links](https://lexical.io) too___~~
*Nested **stars tags** are handled too*
# Title
Expand Down Expand Up @@ -1101,7 +1108,7 @@ const TYPED_MARKDOWN_HTML = html`
<p class="PlaygroundEditorTheme__paragraph" dir="auto">
<span data-lexical-text="true">This is</span>
<strong
class="PlaygroundEditorTheme__textBold PlaygroundEditorTheme__textStrikethrough PlaygroundEditorTheme__textItalic"
class="PlaygroundEditorTheme__textBold PlaygroundEditorTheme__textItalic PlaygroundEditorTheme__textStrikethrough"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was seeing class names be applied in a different order between left and right clients. Didn't get to the bottom of why, by changing the order of the markdown modifiers above then the test here seemed to fix it.

data-lexical-text="true">
bold italic strikethrough
</strong>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
insertSampleImage,
insertTable,
insertYouTubeEmbed,
IS_COLLAB_V2,
IS_LINUX,
IS_MAC,
IS_WINDOWS,
Expand Down Expand Up @@ -86,7 +87,8 @@ test.describe.parallel('Selection', () => {
isPlainText,
browserName,
}) => {
test.skip(isPlainText);
// TODO(collab-v2): nested editors are not supported yet
test.skip(isPlainText || IS_COLLAB_V2);
const hasSelection = async (parentSelector) =>
await evaluate(
page,
Expand Down
27 changes: 8 additions & 19 deletions packages/lexical-playground/__tests__/e2e/Tables.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ import {
dragMouse,
expect,
focusEditor,
getExpectedDateTimeHtml,
getPageOrFrame,
html,
initialize,
insertCollapsible,
insertDateTime,
insertHorizontalRule,
insertSampleImage,
insertTable,
insertTableColumnBefore,
insertTableRowAbove,
Expand All @@ -46,7 +47,6 @@ import {
LEGACY_EVENTS,
mergeTableCells,
pasteFromClipboard,
SAMPLE_IMAGE_URL,
selectCellFromTableCoord,
selectCellsFromTableCords,
selectFromAdditionalStylesDropdown,
Expand Down Expand Up @@ -1356,7 +1356,7 @@ test.describe.parallel('Tables', () => {
await focusEditor(page);
await page.keyboard.type('Text before');
await page.keyboard.press('Enter');
await insertSampleImage(page);
await insertDateTime(page);
await page.keyboard.press('Enter');
await page.keyboard.type('Text after');
await insertTable(page, 2, 3);
Expand Down Expand Up @@ -1448,7 +1448,7 @@ test.describe.parallel('Tables', () => {
});

test(
'Table selection: can select multiple cells and insert an image',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's nothing special about images here from what I can tell, so switched it to another decorator node so that the test can still run on v2.

'Table selection: can select multiple cells and insert a decorator',
{
tag: '@flaky',
},
Expand All @@ -1468,11 +1468,9 @@ test.describe.parallel('Tables', () => {
await page.keyboard.press('ArrowDown');
await page.keyboard.up('Shift');

await insertSampleImage(page);
await insertDateTime(page);
await page.keyboard.type(' <- it works!');

await waitForSelector(page, '.editor-image img');

await assertHTML(
page,
html`
Expand Down Expand Up @@ -1501,18 +1499,7 @@ test.describe.parallel('Tables', () => {
</th>
<td class="PlaygroundEditorTheme__tableCell">
<p class="PlaygroundEditorTheme__paragraph">
<span
class="editor-image"
contenteditable="false"
data-lexical-decorator="true">
<div draggable="false">
<img
alt="Yellow flower in tilt shift lens"
draggable="false"
src="${SAMPLE_IMAGE_URL}"
style="height: inherit; max-width: 500px; width: inherit" />
</div>
</span>
${getExpectedDateTimeHtml()}
<span data-lexical-text="true">&lt;- it works!</span>
</p>
</td>
Expand Down Expand Up @@ -6703,6 +6690,8 @@ test.describe.parallel('Tables', () => {
// undo is used so we need to wait for history
await sleep(1050);

await sleep(1050);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

To ensure that undo on v2 doesn't delete the whole table.


await withExclusiveClipboardAccess(async () => {
const clipboard = await copyToClipboard(page);

Expand Down
Loading
Loading