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