@@ -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