Skip to content

Commit 3e8af82

Browse files
committed
refactor: cleanup & naming
1 parent 61330b7 commit 3e8af82

File tree

2 files changed

+71
-31
lines changed

2 files changed

+71
-31
lines changed

examples/07-collaboration/01-partykit/App.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { BlockNoteView } from "@blocknote/mantine";
44
import "@blocknote/mantine/style.css";
55
import YPartyKitProvider from "y-partykit/provider";
66
import * as Y from "yjs";
7+
import { useEffect } from "react";
8+
import { useState } from "react";
79

810
// Sets up Yjs document and PartyKit Yjs provider.
911
const doc = new Y.Doc();
@@ -28,13 +30,17 @@ export default function App() {
2830
},
2931
},
3032
});
33+
const [forked, setForked] = useState(false);
34+
useEffect(() => {
35+
editor.on("forked", setForked);
36+
}, [editor]);
3137

3238
// Renders the editor instance.
3339
return (
3440
<>
3541
<button
3642
onClick={() => {
37-
editor.pauseYjsSync();
43+
editor.forkYjsSync();
3844
}}>
3945
Pause syncing
4046
</button>
@@ -50,6 +56,9 @@ export default function App() {
5056
}}>
5157
Play (reject changes)
5258
</button>
59+
<div>
60+
<p>Forked: {forked ? "Yes" : "No"}</p>
61+
</div>
5362
<BlockNoteView editor={editor} />
5463
</>
5564
);

packages/core/src/editor/BlockNoteEditor.ts

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ import {
106106
redoCommand,
107107
undoCommand,
108108
yCursorPluginKey,
109-
ySyncPlugin,
110109
ySyncPluginKey,
111110
yUndoPluginKey,
112111
} from "y-prosemirror";
@@ -404,6 +403,7 @@ export class BlockNoteEditor<
404403
SSchema extends StyleSchema = DefaultStyleSchema,
405404
> extends EventEmitter<{
406405
create: void;
406+
forked: boolean;
407407
}> {
408408
/**
409409
* The underlying prosemirror schema
@@ -976,60 +976,91 @@ export class BlockNoteEditor<
976976
}
977977
}
978978

979-
public get isRemoteSyncing() {
980-
return this.yjsState !== undefined;
979+
/**
980+
* Whether the editor is editing a forked document,
981+
* preserving a reference to the original document and the forked document.
982+
*/
983+
public get isForkedFromRemote() {
984+
return this.forkedState !== undefined;
981985
}
982986

983-
private yjsState:
987+
/**
988+
* Stores whether the editor is editing a forked document,
989+
* preserving a reference to the original document and the forked document.
990+
*/
991+
private forkedState:
984992
| {
985-
prevFragment: Y.XmlFragment;
986-
nextFragment: Y.XmlFragment;
993+
originalFragment: Y.XmlFragment;
994+
forkedFragment: Y.XmlFragment;
987995
}
988996
| undefined;
989997

990-
public pauseYjsSync() {
991-
if (this.isRemoteSyncing) {
998+
/**
999+
* Fork the Y.js document from syncing to the remote,
1000+
* allowing modifications to the document without affecting the remote.
1001+
* These changes can later be rolled back or applied to the remote.
1002+
*/
1003+
public forkYjsSync() {
1004+
if (this.forkedState) {
9921005
return;
9931006
}
9941007

995-
const prevFragment = this.options.collaboration?.fragment;
1008+
const originalFragment = this.options.collaboration?.fragment;
9961009

997-
if (!prevFragment) {
1010+
if (!originalFragment) {
9981011
throw new Error("No Yjs document found");
9991012
}
10001013

1001-
const update = Y.encodeStateAsUpdate(prevFragment.doc!);
1002-
10031014
const doc = new Y.Doc();
1004-
Y.applyUpdate(doc, update);
1015+
// Copy the original document to a new Yjs document
1016+
Y.applyUpdate(doc, Y.encodeStateAsUpdate(originalFragment.doc!));
10051017

1006-
const nextFragment = this.findTypeInOtherYdoc(prevFragment, doc);
1018+
// Find the forked fragment in the new Yjs document
1019+
const forkedFragment = this.findTypeInOtherYdoc(originalFragment, doc);
10071020

1008-
this.yjsState = {
1009-
prevFragment,
1010-
nextFragment,
1021+
this.forkedState = {
1022+
originalFragment,
1023+
forkedFragment,
10111024
};
1012-
this._tiptapEditor.unregisterPlugin(yCursorPluginKey);
1013-
this._tiptapEditor.unregisterPlugin(yUndoPluginKey);
1014-
this._tiptapEditor.unregisterPlugin(ySyncPluginKey);
1015-
this._tiptapEditor.registerPlugin(ySyncPlugin(nextFragment));
1025+
1026+
// Need to reset all the yjs plugins
1027+
[yCursorPluginKey, yUndoPluginKey, ySyncPluginKey].forEach((key) => {
1028+
this._tiptapEditor.unregisterPlugin(key);
1029+
});
1030+
// Register them again, based on the new forked fragment
1031+
this._tiptapEditor.registerPlugin(new SyncPlugin(forkedFragment).plugin);
1032+
this._tiptapEditor.registerPlugin(new UndoPlugin().plugin);
1033+
// No need to register the cursor plugin again, it's a local fork
1034+
this.emit("forked", true);
10161035
}
10171036

1018-
public resumeYjsSync(mergeChanges = false) {
1019-
if (!this.yjsState) {
1020-
throw new Error("No Yjs document found");
1037+
/**
1038+
* Resume syncing the Y.js document to the remote
1039+
* If `keepChanges` is true, any changes that have been made to the forked document will be applied to the original document.
1040+
* Otherwise, the original document will be restored and the changes will be discarded.
1041+
*/
1042+
public resumeYjsSync(keepChanges = false) {
1043+
if (!this.forkedState) {
1044+
return;
10211045
}
1046+
// Remove the forked fragment's plugins
10221047
this._tiptapEditor.unregisterPlugin(ySyncPluginKey);
1023-
const fragment = this.yjsState.prevFragment;
1024-
if (mergeChanges) {
1025-
const update = Y.encodeStateAsUpdate(this.yjsState.nextFragment.doc!);
1026-
Y.applyUpdate(fragment.doc!, update);
1048+
this._tiptapEditor.unregisterPlugin(yUndoPluginKey);
1049+
1050+
const { originalFragment, forkedFragment } = this.forkedState!;
1051+
if (keepChanges) {
1052+
// Apply any changes that have been made to the fork, onto the original doc
1053+
const update = Y.encodeStateAsUpdate(forkedFragment.doc!);
1054+
Y.applyUpdate(originalFragment.doc!, update);
10271055
}
1028-
this._tiptapEditor.registerPlugin(new SyncPlugin(fragment).plugin);
1056+
// Register the plugins again, based on the original fragment
1057+
this._tiptapEditor.registerPlugin(new SyncPlugin(originalFragment).plugin);
10291058
this.cursorPlugin = new CursorPlugin(this.options.collaboration!);
10301059
this._tiptapEditor.registerPlugin(this.cursorPlugin.plugin);
10311060
this._tiptapEditor.registerPlugin(new UndoPlugin().plugin);
1032-
this.yjsState = undefined;
1061+
// Reset the forked state
1062+
this.forkedState = undefined;
1063+
this.emit("forked", false);
10331064
}
10341065

10351066
/**

0 commit comments

Comments
 (0)