@@ -106,7 +106,6 @@ import {
106
106
redoCommand ,
107
107
undoCommand ,
108
108
yCursorPluginKey ,
109
- ySyncPlugin ,
110
109
ySyncPluginKey ,
111
110
yUndoPluginKey ,
112
111
} from "y-prosemirror" ;
@@ -404,6 +403,7 @@ export class BlockNoteEditor<
404
403
SSchema extends StyleSchema = DefaultStyleSchema
405
404
> extends EventEmitter < {
406
405
create : void ;
406
+ forked : boolean ;
407
407
} > {
408
408
/**
409
409
* The underlying prosemirror schema
@@ -977,60 +977,91 @@ export class BlockNoteEditor<
977
977
}
978
978
}
979
979
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 ;
982
986
}
983
987
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 :
985
993
| {
986
- prevFragment : Y . XmlFragment ;
987
- nextFragment : Y . XmlFragment ;
994
+ originalFragment : Y . XmlFragment ;
995
+ forkedFragment : Y . XmlFragment ;
988
996
}
989
997
| undefined ;
990
998
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 ) {
993
1006
return ;
994
1007
}
995
1008
996
- const prevFragment = this . options . collaboration ?. fragment ;
1009
+ const originalFragment = this . options . collaboration ?. fragment ;
997
1010
998
- if ( ! prevFragment ) {
1011
+ if ( ! originalFragment ) {
999
1012
throw new Error ( "No Yjs document found" ) ;
1000
1013
}
1001
1014
1002
- const update = Y . encodeStateAsUpdate ( prevFragment . doc ! ) ;
1003
-
1004
1015
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 ! ) ) ;
1006
1018
1007
- const nextFragment = this . findTypeInOtherYdoc ( prevFragment , doc ) ;
1019
+ // Find the forked fragment in the new Yjs document
1020
+ const forkedFragment = this . findTypeInOtherYdoc ( originalFragment , doc ) ;
1008
1021
1009
- this . yjsState = {
1010
- prevFragment ,
1011
- nextFragment ,
1022
+ this . forkedState = {
1023
+ originalFragment ,
1024
+ forkedFragment ,
1012
1025
} ;
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 ) ;
1017
1036
}
1018
1037
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 ;
1022
1046
}
1047
+ // Remove the forked fragment's plugins
1023
1048
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 ) ;
1028
1056
}
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 ) ;
1030
1059
this . cursorPlugin = new CursorPlugin ( this . options . collaboration ! ) ;
1031
1060
this . _tiptapEditor . registerPlugin ( this . cursorPlugin . plugin ) ;
1032
1061
this . _tiptapEditor . registerPlugin ( new UndoPlugin ( ) . plugin ) ;
1033
- this . yjsState = undefined ;
1062
+ // Reset the forked state
1063
+ this . forkedState = undefined ;
1064
+ this . emit ( "forked" , false ) ;
1034
1065
}
1035
1066
1036
1067
/**
0 commit comments