diff --git a/packages/core/src/editor/BlockNoteEditor.test.ts b/packages/core/src/editor/BlockNoteEditor.test.ts index 0f73ac2ea3..db62dafb76 100644 --- a/packages/core/src/editor/BlockNoteEditor.test.ts +++ b/packages/core/src/editor/BlockNoteEditor.test.ts @@ -5,6 +5,7 @@ import { } from "../api/getBlockInfoFromPos.js"; import { BlockNoteEditor } from "./BlockNoteEditor.js"; import { BlockNoteExtension } from "./BlockNoteExtension.js"; +import * as Y from "yjs"; /** * @vitest-environment jsdom @@ -146,3 +147,67 @@ it("onCreate event", () => { }); expect(created).toBe(true); }); + +it("sets an initial block id when using Y.js", async () => { + const doc = new Y.Doc(); + const fragment = doc.getXmlFragment("doc"); + let transactionCount = 0; + const editor = BlockNoteEditor.create({ + collaboration: { + fragment, + user: { name: "Hello", color: "#FFFFFF" }, + provider: null, + }, + _tiptapOptions: { + onTransaction: () => { + transactionCount++; + }, + }, + }); + + editor.mount(document.createElement("div")); + + expect(editor.prosemirrorState.doc.toJSON()).toMatchInlineSnapshot(` + { + "content": [ + { + "content": [ + { + "attrs": { + "id": "initialBlockId", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "doc", + } + `); + expect(transactionCount).toBe(1); + // The fragment should not be modified yet, since the editor's content is only the initial content + expect(fragment.toJSON()).toMatchInlineSnapshot(`""`); + + editor.replaceBlocks(editor.document, [ + { + type: "paragraph", + content: [{ text: "Hello", styles: {}, type: "text" }], + }, + ]); + expect(transactionCount).toBe(2); + // Only after a real modification is made, will the fragment be updated + expect(fragment.toJSON()).toMatchInlineSnapshot( + `"Hello"`, + ); +}); diff --git a/packages/core/src/editor/BlockNoteEditor.ts b/packages/core/src/editor/BlockNoteEditor.ts index 9a5451763e..2292cf2fb9 100644 --- a/packages/core/src/editor/BlockNoteEditor.ts +++ b/packages/core/src/editor/BlockNoteEditor.ts @@ -917,6 +917,25 @@ export class BlockNoteEditor< ); } + // When y-prosemirror creates an empty document, the `blockContainer` node is created with an `id` of `null`. + // This causes the unique id extension to generate a new id for the initial block, which is not what we want + // Since it will be randomly generated & cause there to be more updates to the ydoc + // This is a hack to make it so that anytime `schema.doc.createAndFill` is called, the initial block id is already set to "initialBlockId" + let cache: Node | undefined = undefined; + const oldCreateAndFill = this.pmSchema.nodes.doc.createAndFill; + this.pmSchema.nodes.doc.createAndFill = (...args: any) => { + if (cache) { + return cache; + } + const ret = oldCreateAndFill.apply(this.pmSchema.nodes.doc, args)!; + + // create a copy that we can mutate (otherwise, assigning attrs is not safe and corrupts the pm state) + const jsonNode = JSON.parse(JSON.stringify(ret.toJSON())); + jsonNode.content[0].content[0].attrs.id = "initialBlockId"; + + cache = Node.fromJSON(this.pmSchema, jsonNode); + return cache; + }; this.pmSchema.cached.blockNoteEditor = this; // Initialize managers diff --git a/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json b/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json index 704076c85a..786e727b7e 100644 --- a/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json +++ b/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor-forked.json @@ -8,7 +8,7 @@ "type": "text", }, ], - "id": "3", + "id": "2", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -19,7 +19,7 @@ { "children": [], "content": [], - "id": "4", + "id": "3", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json b/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json index dd12eb46bd..e7580c5b7b 100644 --- a/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json +++ b/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-editor.json @@ -8,7 +8,7 @@ "type": "text", }, ], - "id": "1", + "id": "0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -19,7 +19,7 @@ { "children": [], "content": [], - "id": "2", + "id": "1", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html b/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html index 6442ffd900..8957bbb259 100644 --- a/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html +++ b/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap-forked.html @@ -1 +1 @@ -Hello World \ No newline at end of file +Hello World \ No newline at end of file diff --git a/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html b/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html index b9adef68d0..063ddebeac 100644 --- a/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html +++ b/packages/core/src/extensions/Collaboration/__snapshots__/fork-yjs-snap.html @@ -1 +1 @@ -Hello \ No newline at end of file +Hello \ No newline at end of file