@kerebron/extension-yjs 0.6.7 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/esm/ExtensionYjs.d.ts +3 -11
  2. package/esm/ExtensionYjs.d.ts.map +1 -1
  3. package/esm/ExtensionYjs.js +71 -45
  4. package/esm/ExtensionYjs.js.map +1 -1
  5. package/esm/WebsocketProvider.d.ts +70 -0
  6. package/esm/WebsocketProvider.d.ts.map +1 -0
  7. package/esm/WebsocketProvider.js +377 -0
  8. package/esm/WebsocketProvider.js.map +1 -0
  9. package/esm/YjsProvider.d.ts +48 -0
  10. package/esm/YjsProvider.d.ts.map +1 -0
  11. package/esm/YjsProvider.js +12 -0
  12. package/esm/YjsProvider.js.map +1 -0
  13. package/esm/_dnt.shims.d.ts +2 -0
  14. package/esm/_dnt.shims.d.ts.map +1 -0
  15. package/esm/_dnt.shims.js +58 -0
  16. package/esm/_dnt.shims.js.map +1 -0
  17. package/esm/binding/BindingMetadata.d.ts +6 -0
  18. package/esm/binding/BindingMetadata.d.ts.map +1 -0
  19. package/esm/binding/BindingMetadata.js +2 -0
  20. package/esm/binding/BindingMetadata.js.map +1 -0
  21. package/esm/binding/DiffViewer.d.ts +17 -0
  22. package/esm/binding/DiffViewer.d.ts.map +1 -0
  23. package/esm/binding/DiffViewer.js +96 -0
  24. package/esm/binding/DiffViewer.js.map +1 -0
  25. package/esm/binding/PmYjsBinding.d.ts +45 -0
  26. package/esm/binding/PmYjsBinding.d.ts.map +1 -0
  27. package/esm/binding/PmYjsBinding.js +230 -0
  28. package/esm/binding/PmYjsBinding.js.map +1 -0
  29. package/esm/binding/convertUtils.d.ts +48 -0
  30. package/esm/binding/convertUtils.d.ts.map +1 -0
  31. package/esm/binding/convertUtils.js +80 -0
  32. package/esm/binding/convertUtils.js.map +1 -0
  33. package/esm/{createNodeFromYElement.d.ts → binding/createNodeFromYElement.d.ts} +2 -2
  34. package/esm/binding/createNodeFromYElement.d.ts.map +1 -0
  35. package/esm/{createNodeFromYElement.js → binding/createNodeFromYElement.js} +2 -2
  36. package/esm/binding/createNodeFromYElement.js.map +1 -0
  37. package/esm/{updateYFragment.d.ts → binding/updateYFragment.d.ts} +3 -3
  38. package/esm/binding/updateYFragment.d.ts.map +1 -0
  39. package/esm/{updateYFragment.js → binding/updateYFragment.js} +10 -7
  40. package/esm/binding/updateYFragment.js.map +1 -0
  41. package/esm/debug.d.ts.map +1 -1
  42. package/esm/debug.js +11 -0
  43. package/esm/debug.js.map +1 -1
  44. package/esm/lib.d.ts +1 -7
  45. package/esm/lib.d.ts.map +1 -1
  46. package/esm/lib.js +1 -200
  47. package/esm/lib.js.map +1 -1
  48. package/esm/position.d.ts +8 -0
  49. package/esm/position.d.ts.map +1 -0
  50. package/esm/position.js +165 -0
  51. package/esm/position.js.map +1 -0
  52. package/esm/ui/selection.d.ts +29 -0
  53. package/esm/ui/selection.d.ts.map +1 -0
  54. package/esm/ui/selection.js +129 -0
  55. package/esm/ui/selection.js.map +1 -0
  56. package/esm/utils.d.ts +1 -1
  57. package/esm/utils.d.ts.map +1 -1
  58. package/esm/utils.js.map +1 -1
  59. package/esm/yPositionPlugin.d.ts +6 -1
  60. package/esm/yPositionPlugin.d.ts.map +1 -1
  61. package/esm/yPositionPlugin.js +91 -50
  62. package/esm/yPositionPlugin.js.map +1 -1
  63. package/esm/ySyncPlugin.d.ts +5 -22
  64. package/esm/ySyncPlugin.d.ts.map +1 -1
  65. package/esm/ySyncPlugin.js +70 -101
  66. package/esm/ySyncPlugin.js.map +1 -1
  67. package/esm/yUndoPlugin.d.ts +11 -10
  68. package/esm/yUndoPlugin.d.ts.map +1 -1
  69. package/esm/yUndoPlugin.js +90 -52
  70. package/esm/yUndoPlugin.js.map +1 -1
  71. package/package.json +9 -6
  72. package/src/ExtensionYjs.ts +98 -67
  73. package/src/WebsocketProvider.ts +528 -0
  74. package/src/YjsProvider.ts +75 -0
  75. package/src/_dnt.shims.ts +60 -0
  76. package/src/binding/BindingMetadata.ts +6 -0
  77. package/src/binding/DiffViewer.ts +138 -0
  78. package/src/binding/PmYjsBinding.ts +360 -0
  79. package/src/binding/convertUtils.ts +124 -0
  80. package/src/{createNodeFromYElement.ts → binding/createNodeFromYElement.ts} +4 -4
  81. package/src/{updateYFragment.ts → binding/updateYFragment.ts} +15 -8
  82. package/src/debug.ts +21 -0
  83. package/src/lib.ts +4 -230
  84. package/src/position.ts +191 -0
  85. package/src/ui/selection.ts +218 -0
  86. package/src/utils.ts +1 -1
  87. package/src/yPositionPlugin.ts +122 -74
  88. package/src/ySyncPlugin.ts +111 -155
  89. package/src/yUndoPlugin.ts +113 -62
  90. package/esm/ProsemirrorBinding.d.ts +0 -60
  91. package/esm/ProsemirrorBinding.d.ts.map +0 -1
  92. package/esm/ProsemirrorBinding.js +0 -405
  93. package/esm/ProsemirrorBinding.js.map +0 -1
  94. package/esm/createNodeFromYElement.d.ts.map +0 -1
  95. package/esm/createNodeFromYElement.js.map +0 -1
  96. package/esm/updateYFragment.d.ts.map +0 -1
  97. package/esm/updateYFragment.js.map +0 -1
  98. package/esm/userColors.d.ts +0 -5
  99. package/esm/userColors.d.ts.map +0 -1
  100. package/esm/userColors.js +0 -11
  101. package/esm/userColors.js.map +0 -1
  102. package/src/ProsemirrorBinding.ts +0 -607
  103. package/src/userColors.ts +0 -10
@@ -1,24 +1,50 @@
1
- import * as Y from 'yjs';
2
1
  import { Awareness } from 'y-protocols/awareness';
2
+ import * as Y from 'yjs';
3
3
 
4
4
  import { EditorState, Plugin, Transaction } from 'prosemirror-state';
5
5
  import { EditorView } from 'prosemirror-view';
6
6
 
7
7
  import type { CoreEditor } from '@kerebron/editor';
8
- import type {
9
- ExtensionRemoteSelection,
10
- SelectionState,
11
- } from '@kerebron/extension-basic-editor/ExtensionRemoteSelection';
12
- import { remoteSelectionPluginKey } from '@kerebron/extension-basic-editor/ExtensionRemoteSelection';
8
+ import {
9
+ type ColorMapper,
10
+ defaultColorMapper,
11
+ generateBlankUser,
12
+ type User,
13
+ } from '@kerebron/editor/user';
14
+ import type { SelectionState } from '@kerebron/extension-basic-editor/ExtensionRemoteSelection';
13
15
 
16
+ import { yPositionPluginKey, ySyncPluginKey } from './keys.js';
14
17
  import {
15
18
  absolutePositionToRelativePosition,
16
19
  relativePositionToAbsolutePosition,
17
- setMeta,
18
- } from './lib.js';
19
- import { yPositionPluginKey, ySyncPluginKey } from './keys.js';
20
+ } from './position.js';
20
21
  import type { YSyncPluginState } from './ySyncPlugin.js';
21
22
 
23
+ /**
24
+ * Is null if no timeout is in progress.
25
+ * Is defined if a timeout is in progress.
26
+ * Maps from view
27
+ */
28
+ let viewsToUpdate: Map<EditorView, Map<any, any>> | null = null;
29
+
30
+ const updateMetas = () => {
31
+ const ups: Map<EditorView, Map<any, any>> | null = viewsToUpdate;
32
+ viewsToUpdate = null;
33
+ if (!ups) {
34
+ return;
35
+ }
36
+ ups.forEach((metas, view) => {
37
+ const tr = view.state.tr;
38
+ const syncState = ySyncPluginKey.getState(view.state);
39
+ if (syncState && syncState.binding) { // && !syncState.binding.isDestroyed
40
+ metas.forEach((val, key) => {
41
+ tr.setMeta(key, val);
42
+ });
43
+ view.dispatch(tr);
44
+ }
45
+ });
46
+ };
47
+
22
48
  type AwarenessListener = (
23
49
  { added, updated, removed }: {
24
50
  added: number[];
@@ -36,20 +62,24 @@ interface PositionPluginConfig {
36
62
  export interface YPositionPluginState {
37
63
  awareness?: Awareness;
38
64
  awarenessListener?: AwarenessListener;
65
+ cursorStateField: string;
66
+ userStateField: string;
67
+ me: User;
68
+ colorMapper: ColorMapper;
39
69
  }
40
70
 
41
71
  function destroyAwareness(
42
- state: YPositionPluginState,
43
- cursorStateField: string,
72
+ pluginState: YPositionPluginState,
44
73
  ) {
45
- if (!state.awareness) {
74
+ if (!pluginState.awareness) {
46
75
  return;
47
76
  }
48
- const awareness = state.awareness;
49
- if (state.awarenessListener) {
50
- awareness.off('change', state.awarenessListener);
77
+ const awareness = pluginState.awareness;
78
+ if (pluginState.awarenessListener) {
79
+ awareness.off('change', pluginState.awarenessListener);
51
80
  }
52
- awareness.setLocalStateField(cursorStateField, null);
81
+ awareness.setLocalStateField(pluginState.cursorStateField, null);
82
+ pluginState.awareness = undefined;
53
83
  }
54
84
 
55
85
  function initAwareness(state: YPositionPluginState, editor: CoreEditor) {
@@ -63,10 +93,14 @@ function initAwareness(state: YPositionPluginState, editor: CoreEditor) {
63
93
  { added, updated, removed },
64
94
  ) => {
65
95
  const ystate: YSyncPluginState = ySyncPluginKey.getState(view.state)!;
66
- if (!ystate.provider) {
96
+ if (!ystate.binding) {
67
97
  return;
68
98
  }
69
- const awareness = ystate.provider.awareness;
99
+ const yjs = ystate.binding.getYjs();
100
+ if (!yjs) {
101
+ return;
102
+ }
103
+
70
104
  const clients = added.concat(updated).concat(removed);
71
105
  if (
72
106
  clients.findIndex((id: number) => id !== awareness.doc.clientID) ===
@@ -75,46 +109,37 @@ function initAwareness(state: YPositionPluginState, editor: CoreEditor) {
75
109
  return;
76
110
  }
77
111
 
78
- if (view.docView) {
79
- setMeta(view, remoteSelectionPluginKey, {
80
- remotePositionUpdated: true,
81
- });
82
- }
83
-
112
+ const { ydoc, xmlFragment } = yjs;
84
113
  const remoteStates: SelectionState[] = [];
85
-
86
- const ydoc = ystate.ydoc;
87
-
88
114
  awareness.getStates().forEach((aw, clientId) => {
89
115
  if (!defaultAwarenessStateFilter(ydoc.clientID, clientId, aw)) {
90
116
  return;
91
117
  }
92
118
 
93
- if (!aw.cursor) {
119
+ const cursor = aw[state.cursorStateField];
120
+ const user: User | undefined = aw[state.userStateField];
121
+
122
+ if (!cursor || !user) {
94
123
  return;
95
124
  }
96
125
 
97
126
  const anchor = relativePositionToAbsolutePosition(
98
127
  ydoc,
99
- ystate.type,
100
- Y.createRelativePositionFromJSON(aw.cursor.anchor),
101
- ystate.binding.mapping,
128
+ xmlFragment,
129
+ Y.createRelativePositionFromJSON(cursor.anchor),
130
+ ystate.binding.getMapping(),
102
131
  );
103
132
  const head = relativePositionToAbsolutePosition(
104
133
  ydoc,
105
- ystate.type,
106
- Y.createRelativePositionFromJSON(aw.cursor.head),
107
- ystate.binding.mapping,
134
+ xmlFragment,
135
+ Y.createRelativePositionFromJSON(cursor.head),
136
+ ystate.binding.getMapping(),
108
137
  );
109
138
 
110
139
  if (anchor !== null && head !== null) {
111
140
  remoteStates.push({
112
141
  clientId,
113
- user: {
114
- name: aw.user?.name,
115
- color: aw.user?.color,
116
- colorLight: aw.user?.colorLight,
117
- },
142
+ user: user,
118
143
  cursor: {
119
144
  anchor,
120
145
  head,
@@ -122,12 +147,10 @@ function initAwareness(state: YPositionPluginState, editor: CoreEditor) {
122
147
  });
123
148
  }
124
149
  });
125
- const extension: ExtensionRemoteSelection = editor.getExtension(
126
- 'remote-selection',
127
- )!;
128
150
 
129
- extension.setRemoteStates(remoteStates);
130
- // view.dispatch({ annotations: [yRemoteSelectionsAnnotation.of([])] });
151
+ const tr = editor.state.tr;
152
+ tr.setMeta('remoteSelectionChange', { remoteStates });
153
+ editor.dispatchTransaction(tr);
131
154
  };
132
155
 
133
156
  awareness.on('change', state.awarenessListener);
@@ -147,40 +170,49 @@ export const yPositionPlugin = (
147
170
  {
148
171
  getSelection = (state: EditorState) => state.selection,
149
172
  }: PositionPluginConfig = {},
150
- cursorStateField: string = 'cursor',
151
173
  ) => {
152
174
  return new Plugin<YPositionPluginState>({
153
175
  key: yPositionPluginKey,
154
176
  state: {
155
- init: (_initargs, state): YPositionPluginState => {
177
+ init: (): YPositionPluginState => {
156
178
  return {
157
179
  awareness: undefined,
180
+ cursorStateField: 'kerebron:cursor',
181
+ userStateField: 'kerebron:user',
182
+ me: generateBlankUser(),
183
+ colorMapper: defaultColorMapper,
158
184
  };
159
185
  },
160
186
  apply: (tr: Transaction, pluginState: YPositionPluginState) => {
161
- const awareness = tr.getMeta('yjs:awareness');
187
+ const changeUser = tr.getMeta('changeUser');
188
+ if (changeUser) {
189
+ pluginState.me = { ...changeUser.user };
190
+ }
191
+ const setColorMapper = tr.getMeta('setColorMapper');
192
+ if (setColorMapper) {
193
+ pluginState.colorMapper = setColorMapper.colorMapper;
194
+ }
195
+
196
+ const awareness = tr.getMeta('yjs:setAwareness');
162
197
  if (awareness) {
163
198
  if (pluginState.awareness) {
164
- destroyAwareness(pluginState, cursorStateField);
199
+ destroyAwareness(pluginState);
165
200
  }
166
201
  pluginState.awareness = awareness;
167
202
  if (pluginState.awareness) {
168
203
  initAwareness(pluginState, editor);
169
204
  }
170
205
  }
206
+
207
+ if (tr.getMeta('yjs:removeAwareness')) {
208
+ if (pluginState.awareness) {
209
+ destroyAwareness(pluginState);
210
+ }
211
+ }
171
212
  return pluginState;
172
213
  },
173
214
  },
174
215
  view: (view: EditorView) => {
175
- // const ystate: YSyncPluginState = ySyncPluginKey.getState(view.state)!;
176
- // if (
177
- // ystate.snapshot != null || ystate.prevSnapshot != null ||
178
- // ystate.binding.mapping.size === 0
179
- // ) {
180
- // // do not render cursors while snapshot is active
181
- // return DecorationSet.empty;
182
- // }
183
-
184
216
  const updateAwareness = (
185
217
  selectionAnchor: number,
186
218
  selectionHead: number,
@@ -195,30 +227,37 @@ export const yPositionPlugin = (
195
227
  const current = awareness.getLocalState() || {};
196
228
 
197
229
  const ystate: YSyncPluginState = ySyncPluginKey.getState(view.state)!;
230
+ const yjs = ystate.binding.getYjs();
231
+ if (!yjs) {
232
+ return;
233
+ }
234
+ const { xmlFragment } = yjs;
198
235
 
199
236
  const anchor: Y.RelativePosition = absolutePositionToRelativePosition(
200
237
  selectionAnchor,
201
- ystate.type,
202
- ystate.binding.mapping,
238
+ xmlFragment,
239
+ ystate.binding.getMapping(),
203
240
  );
204
241
  const head: Y.RelativePosition = absolutePositionToRelativePosition(
205
242
  selectionHead,
206
- ystate.type,
207
- ystate.binding.mapping,
243
+ xmlFragment,
244
+ ystate.binding.getMapping(),
208
245
  );
209
246
 
247
+ const cursor = current[state.cursorStateField];
248
+
210
249
  if (
211
- current.cursor == null ||
250
+ cursor == null ||
212
251
  !Y.compareRelativePositions(
213
- Y.createRelativePositionFromJSON(current.cursor.anchor),
252
+ Y.createRelativePositionFromJSON(cursor.anchor),
214
253
  anchor,
215
254
  ) ||
216
255
  !Y.compareRelativePositions(
217
- Y.createRelativePositionFromJSON(current.cursor.head),
256
+ Y.createRelativePositionFromJSON(cursor.head),
218
257
  head,
219
258
  )
220
259
  ) {
221
- awareness.setLocalStateField(cursorStateField, {
260
+ awareness.setLocalStateField(state.cursorStateField, {
222
261
  anchor,
223
262
  head,
224
263
  });
@@ -232,22 +271,31 @@ export const yPositionPlugin = (
232
271
  if (!state.awareness) {
233
272
  return;
234
273
  }
235
- const awareness = state.awareness;
236
274
 
237
- const ystate = ySyncPluginKey.getState(view.state)!;
275
+ const ystate: YSyncPluginState = ySyncPluginKey.getState(view.state)!;
276
+ const yjs = ystate.binding.getYjs();
277
+ if (!yjs) {
278
+ return;
279
+ }
280
+
281
+ const awareness = state.awareness;
238
282
  const current = awareness.getLocalState() || {};
239
283
 
284
+ const { ydoc, xmlFragment } = yjs;
285
+
286
+ const cursor = current[state.cursorStateField];
287
+
240
288
  if (
241
- current.cursor != null &&
289
+ cursor &&
242
290
  relativePositionToAbsolutePosition(
243
- ystate.ydoc,
244
- ystate.type,
245
- Y.createRelativePositionFromJSON(current.cursor.anchor),
246
- ystate.binding.mapping,
291
+ ydoc,
292
+ xmlFragment,
293
+ Y.createRelativePositionFromJSON(cursor.anchor),
294
+ ystate.binding.getMapping(),
247
295
  ) !== null
248
296
  ) {
249
297
  // delete cursor information if current cursor information is owned by this editor binding
250
- awareness.setLocalStateField(cursorStateField, null);
298
+ awareness.setLocalStateField(state.cursorStateField, null);
251
299
  }
252
300
  };
253
301
 
@@ -286,7 +334,7 @@ export const yPositionPlugin = (
286
334
  view.state,
287
335
  );
288
336
  if (pluginState) {
289
- destroyAwareness(pluginState, cursorStateField);
337
+ destroyAwareness(pluginState);
290
338
  }
291
339
 
292
340
  editor.removeEventListener(
@@ -2,57 +2,54 @@ import * as Y from 'yjs';
2
2
  import { Plugin } from 'prosemirror-state';
3
3
 
4
4
  import { ySyncPluginKey, yUndoPluginKey } from './keys.js';
5
- import { defaultColors, ProsemirrorBinding } from './ProsemirrorBinding.js';
6
- import { Schema } from 'prosemirror-model';
7
- import type { CreateWsProvider, YjsProvider } from './ExtensionYjs.js';
8
-
9
- export type TransactFunc<T> = (
10
- f: (arg0?: Y.Transaction) => T,
11
- origin?: any,
12
- ) => T;
13
-
14
- export interface ColorDef {
15
- light: string;
16
- dark: string;
17
- }
5
+ import type { CreateYjsProvider } from './YjsProvider.js';
6
+ import { PmYjsBinding } from './binding/PmYjsBinding.js';
7
+ import { CoreEditor } from '@kerebron/editor';
18
8
 
19
9
  interface YSyncOpts {
20
- colors?: Array<ColorDef>;
21
- colorMapping?: Map<string, ColorDef>;
22
10
  permanentUserData?: Y.PermanentUserData;
23
11
  onFirstRender?: () => void;
24
12
  roomId?: string;
25
13
  }
26
14
 
27
15
  export interface YSyncPluginState {
28
- roomId: string;
29
- provider?: YjsProvider;
30
- type: Y.XmlFragment;
31
- ydoc: Y.Doc;
32
-
33
- binding: ProsemirrorBinding;
34
- addToHistory: boolean;
16
+ binding: PmYjsBinding;
35
17
  isChangeOrigin: boolean;
36
- restore: any;
37
- snapshot?: Y.Snapshot;
38
- prevSnapshot?: Y.Snapshot;
39
- isUndoRedoOperation: boolean;
40
- colors: Array<ColorDef>;
41
- colorMapping: Map<string, ColorDef>;
18
+ isUndoRedoOperation: boolean; // Used in y-history.test.ts
42
19
  permanentUserData?: Y.PermanentUserData;
43
20
  }
44
21
 
22
+ interface YSyncMeta {
23
+ getYDoc?: {
24
+ resolve: (doc: Y.Doc) => void;
25
+ reject: (reason: any) => void;
26
+ };
27
+ changeRoom?: {
28
+ roomId: string;
29
+ };
30
+ leaveRoom?: boolean;
31
+ isChangeOrigin?: boolean;
32
+ isUndoRedoOperation?: boolean;
33
+ getYSnapshot?: {
34
+ resolve: (snapshot: Uint8Array) => void;
35
+ reject: (reason: any) => void;
36
+ };
37
+ setYSnapshot?: {
38
+ prevSnapshot?: Uint8Array;
39
+ snapshot: Uint8Array;
40
+ };
41
+ resetYSnapshot?: boolean;
42
+ }
43
+
45
44
  /**
46
45
  * This plugin listens to changes in prosemirror view and keeps yXmlState and view in sync.
47
46
  *
48
47
  * This plugin also keeps references to the type and the shared document so other plugins can access it.
49
48
  */
50
49
  export const ySyncPlugin = (
51
- schema: Schema,
52
- createWsProvider: CreateWsProvider,
50
+ editor: CoreEditor,
51
+ createYjsProvider: CreateYjsProvider,
53
52
  {
54
- colors = defaultColors,
55
- colorMapping = new Map(),
56
53
  onFirstRender = () => {
57
54
  },
58
55
  }: YSyncOpts = {},
@@ -60,132 +57,105 @@ export const ySyncPlugin = (
60
57
  let initialContentChanged = false;
61
58
  const plugin: Plugin<YSyncPluginState> = new Plugin<YSyncPluginState>({
62
59
  props: {
63
- // editable: (state) => {
64
- // const syncState = ySyncPluginKey.getState(state)!;
65
- // return syncState.snapshot && syncState.prevSnapshot;
66
- // },
60
+ editable: (state) => {
61
+ const syncState = ySyncPluginKey.getState(state)!;
62
+ return syncState.binding.isEditable();
63
+ },
67
64
  },
68
65
  key: ySyncPluginKey,
69
66
  state: {
70
67
  init: (_initargs, state): YSyncPluginState => {
71
- const ydoc = new Y.Doc();
72
- const yXmlFragment: Y.XmlFragment = ydoc.getXmlFragment('prosemirror');
73
- const binding = new ProsemirrorBinding(yXmlFragment, new Map());
74
-
75
68
  return {
76
- roomId: '',
77
- provider: undefined,
78
- binding,
79
- type: yXmlFragment,
80
- ydoc: ydoc,
81
- snapshot: undefined,
82
- prevSnapshot: undefined,
69
+ binding: new PmYjsBinding(editor),
83
70
  isChangeOrigin: false,
84
71
  isUndoRedoOperation: false,
85
- addToHistory: true,
86
- restore: undefined,
87
- colors,
88
- colorMapping,
89
72
  permanentUserData: undefined,
90
73
  };
91
74
  },
92
75
  apply: (tr, pluginState: YSyncPluginState) => {
93
- const change: Partial<YSyncPluginState> = tr.getMeta(ySyncPluginKey);
94
- if (change !== undefined) {
95
- pluginState = {
96
- ...pluginState,
97
- ...change,
98
- };
99
-
100
- if ('roomId' in change) {
101
- if (change.roomId) {
102
- const [provider, ydoc] = createWsProvider(change.roomId);
103
- pluginState.provider = provider;
104
- const yXmlFragment: Y.XmlFragment = ydoc.getXmlFragment(
105
- 'prosemirror',
106
- );
107
- pluginState.type = yXmlFragment, pluginState.ydoc = ydoc;
108
-
109
- if (pluginState.provider) {
110
- if (pluginState.binding.prosemirrorView) {
111
- const view = pluginState.binding.prosemirrorView;
112
- const tr = view.state.tr.setMeta(
113
- 'yjs:awareness',
114
- pluginState.provider.awareness,
115
- );
116
- view.dispatch(tr);
117
- }
118
- }
76
+ const pluginMeta: Partial<YSyncMeta> = tr.getMeta(ySyncPluginKey);
77
+
78
+ const changeUser = tr.getMeta('changeUser');
79
+ if (changeUser) {
80
+ pluginState.binding.changeUser(changeUser.user);
81
+ }
82
+
83
+ if (pluginMeta?.getYDoc) {
84
+ const yjs = pluginState.binding.getYjs();
85
+ if (yjs) {
86
+ pluginMeta.getYDoc.resolve(yjs.ydoc);
87
+ } else {
88
+ if (pluginMeta.getYDoc.reject) {
89
+ pluginMeta.getYDoc.reject(new Error('No yjs'));
119
90
  } else {
120
- const ydoc = new Y.Doc();
121
- const yXmlFragment: Y.XmlFragment = ydoc.getXmlFragment(
122
- 'prosemirror',
123
- );
124
- pluginState.type = yXmlFragment, pluginState.ydoc = ydoc;
125
- pluginState.provider = undefined;
91
+ throw new Error('No yjs');
126
92
  }
127
- pluginState.snapshot = undefined;
128
- pluginState.prevSnapshot = undefined;
129
- pluginState.isChangeOrigin = false;
130
- pluginState.isUndoRedoOperation = false;
131
- pluginState.addToHistory = true;
132
- pluginState.restore = undefined;
93
+ }
94
+ return pluginState;
95
+ }
133
96
 
134
- initialContentChanged = false;
97
+ if (pluginMeta?.leaveRoom) {
98
+ pluginState.isChangeOrigin = false;
99
+ pluginState.isUndoRedoOperation = false;
135
100
 
136
- pluginState.binding.changeRoom(pluginState.type);
137
- setTimeout(() => {
138
- pluginState.binding._forceRerender();
139
- }, 0);
101
+ initialContentChanged = false;
140
102
 
141
- return pluginState;
142
- }
103
+ pluginState.binding.leaveRoom(tr);
104
+
105
+ return pluginState;
106
+ }
107
+
108
+ if (pluginMeta?.changeRoom) {
109
+ pluginState.isChangeOrigin = false;
110
+ pluginState.isUndoRedoOperation = false;
111
+
112
+ initialContentChanged = false;
113
+
114
+ const roomId = pluginMeta.changeRoom.roomId;
115
+ pluginState.binding.changeRoom(
116
+ roomId,
117
+ createYjsProvider,
118
+ tr,
119
+ );
120
+
121
+ return pluginState;
143
122
  }
144
123
 
145
- pluginState.addToHistory = tr.getMeta('addToHistory') !== false;
124
+ pluginState.binding.addToYjsHistory =
125
+ tr.getMeta('addToYjsHistory') !== false;
146
126
  // always set isChangeOrigin. If undefined, this is not change origin.
147
- pluginState.isChangeOrigin = !!change?.isChangeOrigin;
148
- pluginState.isUndoRedoOperation = !!change?.isChangeOrigin &&
149
- !!change?.isUndoRedoOperation;
150
-
151
- const binding = pluginState.binding;
152
-
153
- if (binding?.prosemirrorView) {
154
- if (change?.snapshot || change?.prevSnapshot) {
155
- // snapshot changed, rerender next
156
- setTimeout(() => {
157
- if (!binding.prosemirrorView) {
158
- return;
159
- }
160
- if (change.restore == null) {
161
- binding._renderSnapshot(
162
- change.snapshot,
163
- change.prevSnapshot,
164
- pluginState,
165
- );
166
- } else {
167
- binding._renderSnapshot(
168
- change.snapshot,
169
- change.snapshot,
170
- pluginState,
171
- );
172
- // reset to current prosemirror state
173
- delete pluginState.restore;
174
- delete pluginState.snapshot;
175
- delete pluginState.prevSnapshot;
176
- initialContentChanged = false;
177
- binding.mux(() => {
178
- if (!binding.prosemirrorView) {
179
- return;
180
- }
181
- binding.prosemirrorChanged(
182
- binding.prosemirrorView.state.doc,
183
- );
184
- });
185
- }
186
- }, 0);
127
+ pluginState.isChangeOrigin = !!pluginMeta?.isChangeOrigin;
128
+ pluginState.isUndoRedoOperation = !!pluginMeta?.isChangeOrigin &&
129
+ !!pluginMeta?.isUndoRedoOperation;
130
+
131
+ if (pluginMeta?.getYSnapshot) {
132
+ const yjs = pluginState.binding.getYjs();
133
+ if (yjs) {
134
+ const snapshot = Y.snapshot(yjs.ydoc);
135
+ pluginMeta.getYSnapshot.resolve(Y.encodeSnapshotV2(snapshot));
136
+ } else {
137
+ if (pluginMeta.getYSnapshot.reject) {
138
+ pluginMeta.getYSnapshot.reject(new Error('No yjs'));
139
+ } else {
140
+ throw new Error('No yjs');
141
+ }
187
142
  }
143
+ return pluginState;
144
+ }
145
+
146
+ if (pluginMeta?.resetYSnapshot) {
147
+ pluginState.binding.diffViewer.reset();
148
+ return pluginState;
149
+ }
150
+
151
+ if (pluginMeta?.setYSnapshot) {
152
+ const { prevSnapshot, snapshot } = pluginMeta?.setYSnapshot;
153
+ setTimeout(() => { // Prevent from snapshot being overwritten by current tr
154
+ pluginState.binding.setSnapshot(snapshot, prevSnapshot);
155
+ }, 0);
156
+ return pluginState;
188
157
  }
158
+
189
159
  return pluginState;
190
160
  },
191
161
  },
@@ -195,11 +165,6 @@ export const ySyncPlugin = (
195
165
  )!;
196
166
  const binding = pluginState.binding;
197
167
 
198
- binding.initView(view);
199
- if (binding.mapping.size === 0) {
200
- // force rerender to update the bindings mapping
201
- binding._forceRerender();
202
- }
203
168
  onFirstRender();
204
169
  return {
205
170
  update: () => {
@@ -211,9 +176,7 @@ export const ySyncPlugin = (
211
176
  }
212
177
 
213
178
  const binding = pluginState.binding;
214
- if (
215
- !pluginState.snapshot && !pluginState.prevSnapshot
216
- ) {
179
+ if (binding.isEditable()) {
217
180
  if (
218
181
  // If the content doesn't change initially, we don't render anything to Yjs
219
182
  // If the content was cleared by a user action, we want to catch the change and
@@ -225,7 +188,7 @@ export const ySyncPlugin = (
225
188
  ) {
226
189
  initialContentChanged = true;
227
190
  if (
228
- pluginState.addToHistory === false &&
191
+ pluginState.binding.addToYjsHistory === false &&
229
192
  !pluginState.isChangeOrigin
230
193
  ) {
231
194
  const yUndoPluginState = yUndoPluginKey.getState(view.state);
@@ -233,15 +196,8 @@ export const ySyncPlugin = (
233
196
  yUndoPluginState.undoManager.stopCapturing();
234
197
  }
235
198
  }
236
- binding.mux(() => {
237
- if (!pluginState.ydoc) {
238
- return;
239
- }
240
- pluginState.ydoc.transact((tr) => {
241
- tr.meta.set('addToHistory', pluginState.addToHistory);
242
- binding.prosemirrorChanged(view.state.doc);
243
- }, ySyncPluginKey);
244
- });
199
+
200
+ binding.pmChanged();
245
201
  }
246
202
  }
247
203
  },