Skip to content

Commit e323cda

Browse files
committed
refactor: cleanup & naming
1 parent c13df25 commit e323cda

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
@@ -977,60 +977,91 @@ export class BlockNoteEditor<
977977
}
978978
}
979979

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

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

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

996-
const prevFragment = this.options.collaboration?.fragment;
1009+
const originalFragment = this.options.collaboration?.fragment;
9971010

998-
if (!prevFragment) {
1011+
if (!originalFragment) {
9991012
throw new Error("No Yjs document found");
10001013
}
10011014

1002-
const update = Y.encodeStateAsUpdate(prevFragment.doc!);
1003-
10041015
const doc = new Y.Doc();
1005-
Y.applyUpdate(doc, update);
1016+
// Copy the original document to a new Yjs document
1017+
Y.applyUpdate(doc, Y.encodeStateAsUpdate(originalFragment.doc!));
10061018

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

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

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

10361067
/**

0 commit comments

Comments
 (0)