@kerebron/extension-yjs 0.6.7 → 0.7.0

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 (90) hide show
  1. package/esm/ExtensionYjs.d.ts +3 -11
  2. package/esm/ExtensionYjs.d.ts.map +1 -1
  3. package/esm/ExtensionYjs.js +38 -45
  4. package/esm/ExtensionYjs.js.map +1 -1
  5. package/esm/WebsocketProvider.d.ts +69 -0
  6. package/esm/WebsocketProvider.d.ts.map +1 -0
  7. package/esm/WebsocketProvider.js +354 -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/PmYjsBinding.d.ts +41 -0
  22. package/esm/binding/PmYjsBinding.d.ts.map +1 -0
  23. package/esm/binding/PmYjsBinding.js +190 -0
  24. package/esm/binding/PmYjsBinding.js.map +1 -0
  25. package/esm/binding/convertUtils.d.ts +48 -0
  26. package/esm/binding/convertUtils.d.ts.map +1 -0
  27. package/esm/binding/convertUtils.js +80 -0
  28. package/esm/binding/convertUtils.js.map +1 -0
  29. package/esm/{createNodeFromYElement.d.ts → binding/createNodeFromYElement.d.ts} +1 -1
  30. package/esm/binding/createNodeFromYElement.d.ts.map +1 -0
  31. package/esm/{createNodeFromYElement.js → binding/createNodeFromYElement.js} +2 -2
  32. package/esm/binding/createNodeFromYElement.js.map +1 -0
  33. package/esm/{updateYFragment.d.ts → binding/updateYFragment.d.ts} +3 -3
  34. package/esm/binding/updateYFragment.d.ts.map +1 -0
  35. package/esm/{updateYFragment.js → binding/updateYFragment.js} +10 -7
  36. package/esm/binding/updateYFragment.js.map +1 -0
  37. package/esm/lib.d.ts +1 -7
  38. package/esm/lib.d.ts.map +1 -1
  39. package/esm/lib.js +1 -200
  40. package/esm/lib.js.map +1 -1
  41. package/esm/position.d.ts +8 -0
  42. package/esm/position.d.ts.map +1 -0
  43. package/esm/position.js +165 -0
  44. package/esm/position.js.map +1 -0
  45. package/esm/ui/selection.d.ts +29 -0
  46. package/esm/ui/selection.d.ts.map +1 -0
  47. package/esm/ui/selection.js +129 -0
  48. package/esm/ui/selection.js.map +1 -0
  49. package/esm/yPositionPlugin.d.ts +6 -1
  50. package/esm/yPositionPlugin.d.ts.map +1 -1
  51. package/esm/yPositionPlugin.js +91 -50
  52. package/esm/yPositionPlugin.js.map +1 -1
  53. package/esm/ySyncPlugin.d.ts +5 -22
  54. package/esm/ySyncPlugin.d.ts.map +1 -1
  55. package/esm/ySyncPlugin.js +54 -116
  56. package/esm/ySyncPlugin.js.map +1 -1
  57. package/esm/yUndoPlugin.d.ts +11 -10
  58. package/esm/yUndoPlugin.d.ts.map +1 -1
  59. package/esm/yUndoPlugin.js +90 -52
  60. package/esm/yUndoPlugin.js.map +1 -1
  61. package/package.json +9 -6
  62. package/src/ExtensionYjs.ts +55 -67
  63. package/src/WebsocketProvider.ts +516 -0
  64. package/src/YjsProvider.ts +75 -0
  65. package/src/_dnt.shims.ts +60 -0
  66. package/src/binding/BindingMetadata.ts +6 -0
  67. package/src/binding/PmYjsBinding.ts +300 -0
  68. package/src/binding/convertUtils.ts +124 -0
  69. package/src/{createNodeFromYElement.ts → binding/createNodeFromYElement.ts} +3 -3
  70. package/src/{updateYFragment.ts → binding/updateYFragment.ts} +15 -8
  71. package/src/lib.ts +4 -230
  72. package/src/position.ts +191 -0
  73. package/src/ui/selection.ts +216 -0
  74. package/src/yPositionPlugin.ts +122 -74
  75. package/src/ySyncPlugin.ts +87 -170
  76. package/src/yUndoPlugin.ts +113 -62
  77. package/esm/ProsemirrorBinding.d.ts +0 -60
  78. package/esm/ProsemirrorBinding.d.ts.map +0 -1
  79. package/esm/ProsemirrorBinding.js +0 -405
  80. package/esm/ProsemirrorBinding.js.map +0 -1
  81. package/esm/createNodeFromYElement.d.ts.map +0 -1
  82. package/esm/createNodeFromYElement.js.map +0 -1
  83. package/esm/updateYFragment.d.ts.map +0 -1
  84. package/esm/updateYFragment.js.map +0 -1
  85. package/esm/userColors.d.ts +0 -5
  86. package/esm/userColors.d.ts.map +0 -1
  87. package/esm/userColors.js +0 -11
  88. package/esm/userColors.js.map +0 -1
  89. package/src/ProsemirrorBinding.ts +0 -607
  90. package/src/userColors.ts +0 -10
@@ -0,0 +1,75 @@
1
+ // y-websocket is forked because we need better state connection handling
2
+
3
+ import * as Y from 'yjs';
4
+ import * as awarenessProtocol from 'y-protocols/awareness';
5
+ import * as encoding from 'lib0/encoding';
6
+ import * as decoding from 'lib0/decoding';
7
+
8
+ export const messageSync = 0;
9
+ export const messageQueryAwareness = 3;
10
+ export const messageAwareness = 1;
11
+ export const messageAuth = 2;
12
+
13
+ export type CreateYjsProvider = (roomId: string) => [YjsProvider, Y.Doc];
14
+
15
+ export const MessageType = {
16
+ Sync: 0,
17
+ Awareness: 1,
18
+ Auth: 2,
19
+ QueryAwareness: 3,
20
+ } as const;
21
+
22
+ export type MessageType = typeof MessageType[keyof typeof MessageType];
23
+
24
+ export type MessageHandler<T extends YjsProvider> = (
25
+ encoder: encoding.Encoder,
26
+ decoder: decoding.Decoder,
27
+ provider: T,
28
+ emitSynced: boolean,
29
+ messageType: MessageType,
30
+ ) => void;
31
+
32
+ export interface YjsProviderEventMap {
33
+ status: CustomEvent<{ status: 'connected' | 'disconnected' | 'connecting' }>;
34
+ sync: CustomEvent<{ state: boolean }>;
35
+ synced: CustomEvent<{ state: boolean }>;
36
+ 'connection-error': CustomEvent<{ event: Event; provider: YjsProvider }>;
37
+ 'connection-close': CustomEvent<
38
+ { event: CloseEvent | null; provider: YjsProvider }
39
+ >;
40
+ }
41
+
42
+ export interface YjsProvider extends EventTarget {
43
+ awareness: awarenessProtocol.Awareness;
44
+ doc: Y.Doc;
45
+ synced: boolean;
46
+
47
+ destroy();
48
+
49
+ addEventListener<K extends keyof YjsProviderEventMap>(
50
+ type: K,
51
+ listener: (event: YjsProviderEventMap[K]) => void,
52
+ options?: boolean | AddEventListenerOptions,
53
+ ): void;
54
+
55
+ removeEventListener<K extends keyof YjsProviderEventMap>(
56
+ type: K,
57
+ listener: (event: YjsProviderEventMap[K]) => void,
58
+ options?: boolean | EventListenerOptions,
59
+ ): void;
60
+
61
+ dispatchEvent(event: Event): boolean;
62
+
63
+ // fallback DOM signature (required)
64
+ addEventListener(
65
+ type: string,
66
+ listener: EventListenerOrEventListenerObject | null,
67
+ options?: boolean | AddEventListenerOptions,
68
+ ): void;
69
+
70
+ removeEventListener(
71
+ type: string,
72
+ listener: EventListenerOrEventListenerObject | null,
73
+ options?: boolean | EventListenerOptions,
74
+ ): void;
75
+ }
@@ -0,0 +1,60 @@
1
+ const dntGlobals = {
2
+ };
3
+ export const dntGlobalThis = createMergeProxy(globalThis, dntGlobals);
4
+
5
+ function createMergeProxy<T extends object, U extends object>(
6
+ baseObj: T,
7
+ extObj: U,
8
+ ): Omit<T, keyof U> & U {
9
+ return new Proxy(baseObj, {
10
+ get(_target, prop, _receiver) {
11
+ if (prop in extObj) {
12
+ return (extObj as any)[prop];
13
+ } else {
14
+ return (baseObj as any)[prop];
15
+ }
16
+ },
17
+ set(_target, prop, value) {
18
+ if (prop in extObj) {
19
+ delete (extObj as any)[prop];
20
+ }
21
+ (baseObj as any)[prop] = value;
22
+ return true;
23
+ },
24
+ deleteProperty(_target, prop) {
25
+ let success = false;
26
+ if (prop in extObj) {
27
+ delete (extObj as any)[prop];
28
+ success = true;
29
+ }
30
+ if (prop in baseObj) {
31
+ delete (baseObj as any)[prop];
32
+ success = true;
33
+ }
34
+ return success;
35
+ },
36
+ ownKeys(_target) {
37
+ const baseKeys = Reflect.ownKeys(baseObj);
38
+ const extKeys = Reflect.ownKeys(extObj);
39
+ const extKeysSet = new Set(extKeys);
40
+ return [...baseKeys.filter((k) => !extKeysSet.has(k)), ...extKeys];
41
+ },
42
+ defineProperty(_target, prop, desc) {
43
+ if (prop in extObj) {
44
+ delete (extObj as any)[prop];
45
+ }
46
+ Reflect.defineProperty(baseObj, prop, desc);
47
+ return true;
48
+ },
49
+ getOwnPropertyDescriptor(_target, prop) {
50
+ if (prop in extObj) {
51
+ return Reflect.getOwnPropertyDescriptor(extObj, prop);
52
+ } else {
53
+ return Reflect.getOwnPropertyDescriptor(baseObj, prop);
54
+ }
55
+ },
56
+ has(_target, prop) {
57
+ return prop in extObj || prop in baseObj;
58
+ },
59
+ }) as any;
60
+ }
@@ -0,0 +1,6 @@
1
+ import { ProsemirrorMapping } from '../lib.js';
2
+
3
+ export interface BindingMetadata {
4
+ mapping: ProsemirrorMapping;
5
+ isOverlappingMark: Map<string, boolean>;
6
+ }
@@ -0,0 +1,300 @@
1
+ import * as Y from 'yjs';
2
+
3
+ import { Fragment, Slice } from 'prosemirror-model';
4
+ import { Transaction } from 'prosemirror-state';
5
+
6
+ import { CoreEditor } from '@kerebron/editor';
7
+ import { User } from '@kerebron/editor/user';
8
+
9
+ import { CreateYjsProvider, YjsProvider } from '../YjsProvider.js';
10
+ import { ProsemirrorMapping } from '../lib.js';
11
+ import { SelectionStash } from '../ui/selection.js';
12
+ import { ySyncPluginKey } from '../keys.js';
13
+
14
+ import { yXmlFragmentToProseMirrorRootNode } from './convertUtils.js';
15
+ import { updateYFragment } from './updateYFragment.js';
16
+ import { BindingMetadata } from './BindingMetadata.js';
17
+ import { createNodeIfNotExists } from './createNodeFromYElement.js';
18
+
19
+ type Mutex = (f: () => void, g?: () => void) => void;
20
+
21
+ export const createMutex = (): Mutex => {
22
+ let token = true;
23
+ return (f: () => void, g?: () => void) => {
24
+ if (token) {
25
+ token = false;
26
+ try {
27
+ f();
28
+ } finally {
29
+ token = true;
30
+ }
31
+ } else if (g !== undefined) {
32
+ g();
33
+ }
34
+ };
35
+ };
36
+
37
+ type ConnectionState = 'idle' | 'joining' | 'synced' | 'leaving' | 'error';
38
+
39
+ export type YjsData = { ydoc: Y.Doc; xmlFragment: Y.XmlFragment };
40
+
41
+ export class PmYjsBinding extends EventTarget {
42
+ private provider: YjsProvider | undefined;
43
+ private connectionState: ConnectionState = 'idle';
44
+
45
+ private yjs: YjsData | undefined;
46
+ private selectionStash: SelectionStash | undefined;
47
+
48
+ public addToYjsHistory = true;
49
+ private hasImported = false;
50
+ private mux: Mutex;
51
+ private bindingMetadata: BindingMetadata;
52
+
53
+ private syncedHandler: (
54
+ event: CustomEvent<{
55
+ state: boolean;
56
+ }>,
57
+ ) => void;
58
+ private _observeFunction: (
59
+ events: Array<Y.YEvent<any>>,
60
+ transaction: Y.Transaction,
61
+ ) => void;
62
+
63
+ constructor(private readonly editor: CoreEditor) {
64
+ super();
65
+
66
+ this.bindingMetadata = { mapping: new Map(), isOverlappingMark: new Map() };
67
+
68
+ this.mux = createMutex();
69
+
70
+ this.syncedHandler = (
71
+ event: CustomEvent<{
72
+ state: boolean;
73
+ }>,
74
+ ) => {
75
+ const synced = event.detail.state;
76
+ if (synced && !this.hasImported) {
77
+ this.importRemoteYdoc();
78
+ } else {
79
+ // this.ydocChanged();
80
+ }
81
+ };
82
+
83
+ this._observeFunction = (events, transaction) => {
84
+ this.yXmlChanged(events, transaction);
85
+ };
86
+ }
87
+
88
+ destroy() {
89
+ this.connectionState = 'idle';
90
+ const tr = this.editor.state.tr;
91
+ this.leaveRoom(tr);
92
+ this.editor.dispatchTransaction(tr);
93
+ }
94
+
95
+ changeUser(user: User) {
96
+ if (!this.provider) {
97
+ return;
98
+ }
99
+ this.provider.awareness.setLocalStateField('kerebron:user', user);
100
+ }
101
+
102
+ changeRoom(
103
+ roomId: string,
104
+ createYjsProvider: CreateYjsProvider,
105
+ tr: Transaction,
106
+ ) {
107
+ if (this.provider) {
108
+ this.provider.removeEventListener('synced', this.syncedHandler);
109
+ this.provider.destroy();
110
+ this.hasImported = false;
111
+ this.provider = undefined;
112
+ }
113
+
114
+ this.addToYjsHistory = true;
115
+
116
+ this.connectionState = 'joining';
117
+ const [provider, ydoc] = createYjsProvider(
118
+ roomId,
119
+ );
120
+ this.bindingMetadata.mapping.clear();
121
+ this.bindingMetadata.isOverlappingMark.clear();
122
+
123
+ this.provider = provider;
124
+ const fieldName = 'kerebron:' + this.editor.schema.topNodeType.name;
125
+ this.yjs = { ydoc, xmlFragment: ydoc.getXmlFragment(fieldName) };
126
+ this.selectionStash = new SelectionStash(
127
+ this.yjs,
128
+ this.getMapping(),
129
+ this.editor,
130
+ );
131
+ this.yjs.xmlFragment.observeDeep(this._observeFunction);
132
+
133
+ this.provider.addEventListener('synced', this.syncedHandler);
134
+
135
+ tr.setMeta('yjs:setAwareness', provider.awareness);
136
+ tr.setMeta('setYjs', this.yjs);
137
+ }
138
+
139
+ leaveRoom(tr: Transaction) {
140
+ this.connectionState = 'leaving';
141
+ if (this.provider) {
142
+ this.provider.removeEventListener('synced', this.syncedHandler);
143
+ this.provider.destroy();
144
+ }
145
+
146
+ this.addToYjsHistory = true;
147
+ this.provider = undefined;
148
+
149
+ this.selectionStash?.destroy();
150
+ this.selectionStash = undefined;
151
+ this.yjs?.xmlFragment.unobserveDeep(this._observeFunction);
152
+ this.yjs = undefined;
153
+
154
+ this.hasImported = false;
155
+
156
+ tr.setMeta('addToYjsHistory', false);
157
+ tr.setMeta('yjs:removeAwareness', true);
158
+ tr.setMeta('clearYjs', true);
159
+ }
160
+
161
+ importRemoteYdoc() {
162
+ if (!this.yjs) {
163
+ return;
164
+ }
165
+
166
+ this.addToYjsHistory = this.editor.state.doc.content.size > 2; // Non empty para
167
+
168
+ if (this.yjs.xmlFragment.length === 0) {
169
+ this.createFromProseMirror(this.yjs);
170
+ } else {
171
+ this.overwriteProseMirror(this.yjs.xmlFragment);
172
+ }
173
+
174
+ this.connectionState = 'synced';
175
+ this.hasImported = true;
176
+ }
177
+
178
+ yXmlChanged(events: Array<Y.YEvent<any>>, ytr: Y.Transaction) {
179
+ this.mux(() => {
180
+ if (!this.yjs) {
181
+ return;
182
+ }
183
+
184
+ const state = this.editor.state;
185
+
186
+ const { xmlFragment } = this.yjs;
187
+
188
+ // TODO handleSnapShot
189
+
190
+ const mapping = this.bindingMetadata.mapping;
191
+ const delType = (_: any, type: Y.AbstractType<any>) =>
192
+ mapping.delete(type);
193
+ Y.iterateDeletedStructs(
194
+ ytr,
195
+ ytr.deleteSet,
196
+ (struct) => {
197
+ if (struct.constructor === Y.Item) {
198
+ const type = (struct as Y.Item).content.type;
199
+ type && mapping.delete(type);
200
+ }
201
+ },
202
+ );
203
+ ytr.changed.forEach(delType);
204
+ ytr.changedParentTypes.forEach(delType);
205
+
206
+ const fragmentContent = xmlFragment.toArray().map((t) =>
207
+ createNodeIfNotExists(
208
+ t as Y.XmlElement,
209
+ state.schema,
210
+ this.bindingMetadata,
211
+ )
212
+ ).filter((n) => n !== null);
213
+
214
+ const tr = state.tr;
215
+ if (ytr.origin instanceof Y.UndoManager) {
216
+ } else {
217
+ }
218
+ tr.setMeta('addToYjsHistory', false);
219
+ tr.replace(
220
+ 0,
221
+ state.doc.content.size,
222
+ new Slice(Fragment.from(fragmentContent), 0, 0),
223
+ );
224
+
225
+ this.selectionStash?.restore(tr);
226
+ this.editor.dispatchTransaction(tr);
227
+ });
228
+ }
229
+
230
+ pmChanged() {
231
+ if (!this.hasImported) {
232
+ return;
233
+ }
234
+
235
+ if (!this.yjs) {
236
+ return;
237
+ }
238
+
239
+ const doc = this.editor.state.doc;
240
+ const { ydoc, xmlFragment } = this.yjs;
241
+
242
+ this.mux(() => {
243
+ const origin = ySyncPluginKey;
244
+
245
+ ydoc.transact((ytr) => {
246
+ ytr.meta.set('addToYjsHistory', this.addToYjsHistory);
247
+ updateYFragment(
248
+ ydoc,
249
+ xmlFragment,
250
+ doc,
251
+ this.bindingMetadata,
252
+ this.addToYjsHistory,
253
+ );
254
+ this.selectionStash?.store();
255
+ }, origin);
256
+ });
257
+ }
258
+
259
+ createFromProseMirror(yjs: YjsData) {
260
+ const doc = this.editor.state.doc;
261
+ const { ydoc, xmlFragment } = yjs;
262
+ updateYFragment(
263
+ ydoc,
264
+ xmlFragment,
265
+ doc,
266
+ this.bindingMetadata,
267
+ this.addToYjsHistory,
268
+ );
269
+ }
270
+
271
+ overwriteProseMirror(yXmlFragment: Y.XmlFragment) {
272
+ const state = this.editor.state;
273
+ const newRoot = yXmlFragmentToProseMirrorRootNode(
274
+ yXmlFragment,
275
+ state.schema,
276
+ );
277
+
278
+ const tr = state.tr;
279
+ tr.setMeta('addToYjsHistory', false);
280
+ tr.replaceWith(
281
+ 0,
282
+ state.doc.content.size,
283
+ newRoot,
284
+ );
285
+
286
+ this.editor.dispatchTransaction(tr);
287
+ }
288
+
289
+ getYjs(): YjsData | undefined {
290
+ return this.yjs;
291
+ }
292
+
293
+ getMapping(): ProsemirrorMapping {
294
+ return this.bindingMetadata.mapping;
295
+ }
296
+
297
+ getSelectionStash(): SelectionStash | undefined {
298
+ return this.selectionStash;
299
+ }
300
+ }
@@ -0,0 +1,124 @@
1
+ import * as Y from 'yjs';
2
+
3
+ import { Fragment, Node, type Schema } from 'prosemirror-model';
4
+
5
+ import { TransactFunc } from '../lib.js';
6
+
7
+ import { updateYFragment } from './updateYFragment.js';
8
+ import { createNodeFromYElement } from './createNodeFromYElement.js';
9
+ import { BindingMetadata } from './BindingMetadata.js';
10
+
11
+ export const createEmptyMeta = (): BindingMetadata => ({
12
+ mapping: new Map(),
13
+ isOverlappingMark: new Map(),
14
+ });
15
+
16
+ /**
17
+ * Utility function for converting an Y.Fragment to a ProseMirror fragment.
18
+ */
19
+ export const yXmlFragmentToProseMirrorFragment = (
20
+ yXmlFragment: Y.XmlFragment,
21
+ schema: Schema,
22
+ ) => {
23
+ const elements: Y.XmlElement[] = yXmlFragment.toArray().filter((i) =>
24
+ i instanceof Y.XmlElement
25
+ );
26
+ const fragmentContent = elements.map((t) =>
27
+ createNodeFromYElement(
28
+ t,
29
+ schema,
30
+ createEmptyMeta(),
31
+ )
32
+ ).filter((n) => n !== null);
33
+ return Fragment.fromArray(fragmentContent);
34
+ };
35
+
36
+ /**
37
+ * Utility function for converting an Y.Fragment to a ProseMirror node.
38
+ */
39
+ export const yXmlFragmentToProseMirrorRootNode = (
40
+ yXmlFragment: Y.XmlFragment,
41
+ schema: Schema,
42
+ ) =>
43
+ schema.topNodeType.create(
44
+ null,
45
+ yXmlFragmentToProseMirrorFragment(yXmlFragment, schema),
46
+ );
47
+
48
+ /**
49
+ * Utility method to convert a Prosemirror Doc Node into a Y.Doc.
50
+ *
51
+ * This can be used when importing existing content to Y.Doc for the first time,
52
+ * note that this should not be used to rehydrate a Y.Doc from a database once
53
+ * collaboration has begun as all history will be lost
54
+ */
55
+ export function prosemirrorToYDoc(
56
+ doc: Node,
57
+ xmlFragment: string = 'prosemirror',
58
+ ): Y.Doc {
59
+ const ydoc = new Y.Doc();
60
+ const type: Y.XmlFragment = ydoc.get(xmlFragment, Y.XmlFragment);
61
+ if (!type.doc) {
62
+ return ydoc;
63
+ }
64
+
65
+ prosemirrorToYXmlFragment(doc, type);
66
+ return type.doc;
67
+ }
68
+
69
+ /**
70
+ * Utility method to update an empty Y.XmlFragment with content from a Prosemirror Doc Node.
71
+ *
72
+ * This can be used when importing existing content to Y.Doc for the first time,
73
+ * note that this should not be used to rehydrate a Y.Doc from a database once
74
+ * collaboration has begun as all history will be lost
75
+ *
76
+ * Note: The Y.XmlFragment does not need to be part of a Y.Doc document at the time that this
77
+ * method is called, but it must be added before any other operations are performed on it.
78
+ */
79
+ export function prosemirrorToYXmlFragment(
80
+ doc: Node,
81
+ xmlFragment: Y.XmlFragment,
82
+ ): Y.XmlFragment {
83
+ const type = xmlFragment || new Y.XmlFragment();
84
+ const ydoc: { transact: TransactFunc<void> } = type.doc
85
+ ? type.doc
86
+ : { transact: (transaction) => transaction(undefined) };
87
+ updateYFragment(ydoc, type, doc, {
88
+ mapping: new Map(),
89
+ isOverlappingMark: new Map(),
90
+ });
91
+ return type;
92
+ }
93
+
94
+ /**
95
+ * Utility method to convert Prosemirror compatible JSON into a Y.Doc.
96
+ *
97
+ * This can be used when importing existing content to Y.Doc for the first time,
98
+ * note that this should not be used to rehydrate a Y.Doc from a database once
99
+ * collaboration has begun as all history will be lost
100
+ */
101
+ export function prosemirrorJSONToYDoc(
102
+ schema: Schema,
103
+ state: any,
104
+ xmlFragment: string = 'prosemirror',
105
+ ): Y.Doc {
106
+ const doc = Node.fromJSON(schema, state);
107
+ return prosemirrorToYDoc(doc, xmlFragment);
108
+ }
109
+
110
+ /**
111
+ * Utility method to convert Prosemirror compatible JSON to a Y.XmlFragment
112
+ *
113
+ * This can be used when importing existing content to Y.Doc for the first time,
114
+ * note that this should not be used to rehydrate a Y.Doc from a database once
115
+ * collaboration has begun as all history will be lost
116
+ */
117
+ export function prosemirrorJSONToYXmlFragment(
118
+ schema: Schema,
119
+ state: any,
120
+ xmlFragment: Y.XmlFragment,
121
+ ): Y.XmlFragment {
122
+ const doc = Node.fromJSON(schema, state);
123
+ return prosemirrorToYXmlFragment(doc, xmlFragment);
124
+ }
@@ -2,9 +2,9 @@ import * as PModel from 'prosemirror-model';
2
2
  import { Mark, Schema } from 'prosemirror-model';
3
3
  import * as Y from 'yjs';
4
4
 
5
- import type { BindingMetadata } from './ProsemirrorBinding.js';
6
- import { ySyncPluginKey } from './keys.js';
7
- import { isVisible } from './utils.js';
5
+ import type { BindingMetadata } from './BindingMetadata.js';
6
+ import { ySyncPluginKey } from '../keys.js';
7
+ import { isVisible } from '../utils.js';
8
8
  import { yattr2markname } from './updateYFragment.js';
9
9
 
10
10
  export const attributesToMarks = (
@@ -4,10 +4,10 @@ import { Mark, Node, Schema } from 'prosemirror-model';
4
4
 
5
5
  import { simpleDiff } from 'lib0/diff';
6
6
 
7
- import { ySyncPluginKey } from './keys.js';
8
- import * as utils from './utils.js';
9
- import type { BindingMetadata } from './ProsemirrorBinding.js';
10
- import { TransactFunc } from './ySyncPlugin.js';
7
+ import { ySyncPluginKey } from '../keys.js';
8
+ import * as utils from '../utils.js';
9
+ import type { BindingMetadata } from './BindingMetadata.js';
10
+ import { TransactFunc } from '../lib.js';
11
11
 
12
12
  const hashedMarkNameRegex = /(.*)(--[a-zA-Z0-9+/=]{8})$/;
13
13
  export const yattr2markname = (attrName: string) =>
@@ -21,8 +21,11 @@ const marksToAttributes = (
21
21
  marks.forEach((mark) => {
22
22
  if (mark.type.name !== 'ychange') {
23
23
  let isOverlapping = true;
24
- if (!meta.isOMark.has(mark.type.name)) {
25
- meta.isOMark.set(mark.type.name, !mark.type.excludes(mark.type));
24
+ if (!meta.isOverlappingMark.has(mark.type.name)) {
25
+ meta.isOverlappingMark.set(
26
+ mark.type.name,
27
+ !mark.type.excludes(mark.type),
28
+ );
26
29
  isOverlapping = false;
27
30
  }
28
31
 
@@ -278,7 +281,9 @@ export const updateYFragment = (
278
281
  yDomFragment: Y.XmlFragment,
279
282
  pNode: Node,
280
283
  meta: BindingMetadata,
284
+ addToYjsHistory?: boolean,
281
285
  ) => {
286
+ const origin = ySyncPluginKey;
282
287
  if (
283
288
  yDomFragment instanceof Y.XmlElement &&
284
289
  yDomFragment.nodeName !== pNode.type.name
@@ -342,7 +347,9 @@ export const updateYFragment = (
342
347
  }
343
348
  }
344
349
  }
345
- ydoc.transact(() => {
350
+ ydoc.transact((ytr) => {
351
+ ytr.meta.set('updateYFragment', 1);
352
+ ytr.meta.set('addToYjsHistory', addToYjsHistory);
346
353
  // try to compare and update
347
354
  while (yChildCnt - left - right > 0 && pChildCnt - left - right > 0) {
348
355
  const leftY: Y.XmlElement | Y.XmlText = yChildren[left];
@@ -435,5 +442,5 @@ export const updateYFragment = (
435
442
  }
436
443
  yDomFragment.insert(left, ins);
437
444
  }
438
- }, ySyncPluginKey);
445
+ }, origin);
439
446
  };