@kerebron/extension-yjs 0.7.0 → 0.7.2
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.
- package/esm/ExtensionYjs.d.ts.map +1 -1
- package/esm/ExtensionYjs.js +33 -0
- package/esm/ExtensionYjs.js.map +1 -1
- package/esm/WebsocketProvider.d.ts +1 -0
- package/esm/WebsocketProvider.d.ts.map +1 -1
- package/esm/WebsocketProvider.js +64 -41
- package/esm/WebsocketProvider.js.map +1 -1
- package/esm/binding/DiffViewer.d.ts +17 -0
- package/esm/binding/DiffViewer.d.ts.map +1 -0
- package/esm/binding/DiffViewer.js +96 -0
- package/esm/binding/DiffViewer.js.map +1 -0
- package/esm/binding/PmYjsBinding.d.ts +4 -0
- package/esm/binding/PmYjsBinding.d.ts.map +1 -1
- package/esm/binding/PmYjsBinding.js +49 -9
- package/esm/binding/PmYjsBinding.js.map +1 -1
- package/esm/binding/createNodeFromYElement.d.ts +1 -1
- package/esm/binding/createNodeFromYElement.d.ts.map +1 -1
- package/esm/binding/createNodeFromYElement.js.map +1 -1
- package/esm/debug.d.ts.map +1 -1
- package/esm/debug.js +11 -0
- package/esm/debug.js.map +1 -1
- package/esm/ui/selection.d.ts.map +1 -1
- package/esm/ui/selection.js +3 -3
- package/esm/ui/selection.js.map +1 -1
- package/esm/utils.d.ts +1 -1
- package/esm/utils.d.ts.map +1 -1
- package/esm/utils.js.map +1 -1
- package/esm/ySyncPlugin.d.ts.map +1 -1
- package/esm/ySyncPlugin.js +45 -14
- package/esm/ySyncPlugin.js.map +1 -1
- package/package.json +3 -3
- package/src/ExtensionYjs.ts +43 -0
- package/src/WebsocketProvider.ts +73 -61
- package/src/binding/DiffViewer.ts +138 -0
- package/src/binding/PmYjsBinding.ts +70 -10
- package/src/binding/createNodeFromYElement.ts +1 -1
- package/src/debug.ts +21 -0
- package/src/ui/selection.ts +5 -3
- package/src/utils.ts +1 -1
- package/src/ySyncPlugin.ts +58 -19
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
|
|
3
|
+
import { Node } from 'prosemirror-model';
|
|
4
|
+
import { EditorState } from 'prosemirror-state';
|
|
5
|
+
|
|
6
|
+
import { isVisible } from '../utils.js';
|
|
7
|
+
import { YjsData } from './PmYjsBinding.js';
|
|
8
|
+
import { createNodeFromYElement } from './createNodeFromYElement.js';
|
|
9
|
+
|
|
10
|
+
export class DiffViewer {
|
|
11
|
+
prevSnapshot?: Y.Snapshot;
|
|
12
|
+
snapshot?: Y.Snapshot;
|
|
13
|
+
|
|
14
|
+
private historyYXmlFragment: Y.XmlFragment | undefined;
|
|
15
|
+
private historyDoc: Y.Doc | undefined;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
isActive() {
|
|
21
|
+
return !!this.snapshot || !!this.prevSnapshot;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setSnapshot(yjs: YjsData, uSnapshot: Uint8Array, uPrevSnapshot?: Uint8Array) {
|
|
25
|
+
this.prevSnapshot = undefined;
|
|
26
|
+
if (uPrevSnapshot) {
|
|
27
|
+
this.prevSnapshot = Y.decodeSnapshotV2(uPrevSnapshot);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.snapshot = Y.decodeSnapshotV2(uSnapshot);
|
|
31
|
+
|
|
32
|
+
this.historyDoc = Y.createDocFromSnapshot(
|
|
33
|
+
yjs.ydoc,
|
|
34
|
+
this.snapshot,
|
|
35
|
+
new Y.Doc({ gc: false }),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (yjs.xmlFragment._item === null) {
|
|
39
|
+
/**
|
|
40
|
+
* If is a root type, we need to find the root key in the initial document
|
|
41
|
+
* and use it to get the history type.
|
|
42
|
+
*/
|
|
43
|
+
const share: Map<string, Y.AbstractType<Y.YEvent<any>>> = yjs.ydoc.share;
|
|
44
|
+
const rootKey = Array.from(share.keys()).find(
|
|
45
|
+
(key) => share.get(key) === yjs.xmlFragment as Y.AbstractType<any>,
|
|
46
|
+
);
|
|
47
|
+
this.historyYXmlFragment = this.historyDoc.getXmlFragment(rootKey);
|
|
48
|
+
} else {
|
|
49
|
+
/**
|
|
50
|
+
* If it is a sub type, we use the item id to find the history type.
|
|
51
|
+
*/
|
|
52
|
+
const historyStructs =
|
|
53
|
+
this.historyDoc.store.clients.get(yjs.xmlFragment._item.id.client) ??
|
|
54
|
+
[];
|
|
55
|
+
const itemIndex = Y.findIndexSS(
|
|
56
|
+
historyStructs,
|
|
57
|
+
yjs.xmlFragment._item.id.clock,
|
|
58
|
+
);
|
|
59
|
+
if (historyStructs[itemIndex] instanceof Y.GC) {
|
|
60
|
+
throw new Error('Incorrect type Y.GC');
|
|
61
|
+
}
|
|
62
|
+
const item: Y.Item = historyStructs[itemIndex];
|
|
63
|
+
const content: Y.ContentType = item.content;
|
|
64
|
+
this.historyYXmlFragment = content.type as Y.XmlFragment;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
reset() {
|
|
69
|
+
this.snapshot = undefined;
|
|
70
|
+
this.prevSnapshot = undefined;
|
|
71
|
+
this.historyDoc = undefined;
|
|
72
|
+
this.historyYXmlFragment = undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getFragmentContent(
|
|
76
|
+
state: EditorState,
|
|
77
|
+
ytr: Y.Transaction,
|
|
78
|
+
): Node[] | undefined {
|
|
79
|
+
if (!this.historyYXmlFragment || !this.snapshot) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// before rendering, we are going to sanitize ops and split deleted ops
|
|
84
|
+
// if they were deleted by seperate users.
|
|
85
|
+
const pud: Y.PermanentUserData | undefined = undefined;
|
|
86
|
+
// pluginState.permanentUserData;
|
|
87
|
+
if (pud) {
|
|
88
|
+
pud.dss.forEach((ds) => {
|
|
89
|
+
Y.iterateDeletedStructs(ytr, ds, (_item) => {});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const computeYChange = (type: 'removed' | 'added', id: Y.ID) => {
|
|
93
|
+
const user = pud
|
|
94
|
+
? (type === 'added'
|
|
95
|
+
? pud.getUserByClientId(id.client)
|
|
96
|
+
: pud.getUserByDeletedId(id))
|
|
97
|
+
: undefined;
|
|
98
|
+
return {
|
|
99
|
+
user,
|
|
100
|
+
type,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const deleteSet = this.prevSnapshot
|
|
105
|
+
? this.prevSnapshot.ds
|
|
106
|
+
: Y.emptySnapshot.ds;
|
|
107
|
+
|
|
108
|
+
// Create document fragment and render
|
|
109
|
+
const fragmentContent = Y.typeListToArraySnapshot(
|
|
110
|
+
this.historyYXmlFragment,
|
|
111
|
+
new Y.Snapshot(deleteSet, this.snapshot.sv),
|
|
112
|
+
).map((t) => {
|
|
113
|
+
if (
|
|
114
|
+
!t._item.deleted || isVisible(t._item, this.snapshot) ||
|
|
115
|
+
isVisible(t._item, this.prevSnapshot)
|
|
116
|
+
) {
|
|
117
|
+
return createNodeFromYElement(
|
|
118
|
+
t,
|
|
119
|
+
state.schema,
|
|
120
|
+
{ mapping: new Map(), isOverlappingMark: new Map() },
|
|
121
|
+
this.snapshot,
|
|
122
|
+
this.prevSnapshot,
|
|
123
|
+
computeYChange,
|
|
124
|
+
);
|
|
125
|
+
} else {
|
|
126
|
+
// No need to render elements that are not visible by either snapshot.
|
|
127
|
+
// If a client adds and deletes content in the same snapshot the element is not visible by either snapshot.
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}).filter((n) => n !== null);
|
|
131
|
+
|
|
132
|
+
return fragmentContent;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getHistoryDoc(): Y.Doc | undefined {
|
|
136
|
+
return this.historyDoc;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -15,6 +15,7 @@ import { yXmlFragmentToProseMirrorRootNode } from './convertUtils.js';
|
|
|
15
15
|
import { updateYFragment } from './updateYFragment.js';
|
|
16
16
|
import { BindingMetadata } from './BindingMetadata.js';
|
|
17
17
|
import { createNodeIfNotExists } from './createNodeFromYElement.js';
|
|
18
|
+
import { DiffViewer } from './DiffViewer.js';
|
|
18
19
|
|
|
19
20
|
type Mutex = (f: () => void, g?: () => void) => void;
|
|
20
21
|
|
|
@@ -44,6 +45,7 @@ export class PmYjsBinding extends EventTarget {
|
|
|
44
45
|
|
|
45
46
|
private yjs: YjsData | undefined;
|
|
46
47
|
private selectionStash: SelectionStash | undefined;
|
|
48
|
+
public readonly diffViewer: DiffViewer;
|
|
47
49
|
|
|
48
50
|
public addToYjsHistory = true;
|
|
49
51
|
private hasImported = false;
|
|
@@ -63,6 +65,7 @@ export class PmYjsBinding extends EventTarget {
|
|
|
63
65
|
constructor(private readonly editor: CoreEditor) {
|
|
64
66
|
super();
|
|
65
67
|
|
|
68
|
+
this.diffViewer = new DiffViewer();
|
|
66
69
|
this.bindingMetadata = { mapping: new Map(), isOverlappingMark: new Map() };
|
|
67
70
|
|
|
68
71
|
this.mux = createMutex();
|
|
@@ -90,6 +93,8 @@ export class PmYjsBinding extends EventTarget {
|
|
|
90
93
|
const tr = this.editor.state.tr;
|
|
91
94
|
this.leaveRoom(tr);
|
|
92
95
|
this.editor.dispatchTransaction(tr);
|
|
96
|
+
this.diffViewer.reset();
|
|
97
|
+
this.selectionStash?.destroy();
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
changeUser(user: User) {
|
|
@@ -104,23 +109,26 @@ export class PmYjsBinding extends EventTarget {
|
|
|
104
109
|
createYjsProvider: CreateYjsProvider,
|
|
105
110
|
tr: Transaction,
|
|
106
111
|
) {
|
|
112
|
+
this.connectionState = 'joining';
|
|
113
|
+
this.hasImported = false;
|
|
114
|
+
|
|
107
115
|
if (this.provider) {
|
|
108
116
|
this.provider.removeEventListener('synced', this.syncedHandler);
|
|
109
117
|
this.provider.destroy();
|
|
110
|
-
this.hasImported = false;
|
|
111
118
|
this.provider = undefined;
|
|
112
119
|
}
|
|
113
120
|
|
|
114
|
-
this.addToYjsHistory = true;
|
|
115
|
-
|
|
116
|
-
this.connectionState = 'joining';
|
|
117
121
|
const [provider, ydoc] = createYjsProvider(
|
|
118
122
|
roomId,
|
|
119
123
|
);
|
|
124
|
+
|
|
125
|
+
this.addToYjsHistory = true;
|
|
126
|
+
this.provider = provider;
|
|
127
|
+
|
|
120
128
|
this.bindingMetadata.mapping.clear();
|
|
121
129
|
this.bindingMetadata.isOverlappingMark.clear();
|
|
130
|
+
this.diffViewer.reset();
|
|
122
131
|
|
|
123
|
-
this.provider = provider;
|
|
124
132
|
const fieldName = 'kerebron:' + this.editor.schema.topNodeType.name;
|
|
125
133
|
this.yjs = { ydoc, xmlFragment: ydoc.getXmlFragment(fieldName) };
|
|
126
134
|
this.selectionStash = new SelectionStash(
|
|
@@ -146,6 +154,10 @@ export class PmYjsBinding extends EventTarget {
|
|
|
146
154
|
this.addToYjsHistory = true;
|
|
147
155
|
this.provider = undefined;
|
|
148
156
|
|
|
157
|
+
this.bindingMetadata.mapping.clear();
|
|
158
|
+
this.bindingMetadata.isOverlappingMark.clear();
|
|
159
|
+
this.diffViewer.reset();
|
|
160
|
+
|
|
149
161
|
this.selectionStash?.destroy();
|
|
150
162
|
this.selectionStash = undefined;
|
|
151
163
|
this.yjs?.xmlFragment.unobserveDeep(this._observeFunction);
|
|
@@ -181,12 +193,14 @@ export class PmYjsBinding extends EventTarget {
|
|
|
181
193
|
return;
|
|
182
194
|
}
|
|
183
195
|
|
|
196
|
+
if (this.diffViewer.isActive()) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
184
200
|
const state = this.editor.state;
|
|
185
201
|
|
|
186
202
|
const { xmlFragment } = this.yjs;
|
|
187
203
|
|
|
188
|
-
// TODO handleSnapShot
|
|
189
|
-
|
|
190
204
|
const mapping = this.bindingMetadata.mapping;
|
|
191
205
|
const delType = (_: any, type: Y.AbstractType<any>) =>
|
|
192
206
|
mapping.delete(type);
|
|
@@ -212,9 +226,6 @@ export class PmYjsBinding extends EventTarget {
|
|
|
212
226
|
).filter((n) => n !== null);
|
|
213
227
|
|
|
214
228
|
const tr = state.tr;
|
|
215
|
-
if (ytr.origin instanceof Y.UndoManager) {
|
|
216
|
-
} else {
|
|
217
|
-
}
|
|
218
229
|
tr.setMeta('addToYjsHistory', false);
|
|
219
230
|
tr.replace(
|
|
220
231
|
0,
|
|
@@ -236,6 +247,10 @@ export class PmYjsBinding extends EventTarget {
|
|
|
236
247
|
return;
|
|
237
248
|
}
|
|
238
249
|
|
|
250
|
+
if (this.diffViewer.isActive()) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
239
254
|
const doc = this.editor.state.doc;
|
|
240
255
|
const { ydoc, xmlFragment } = this.yjs;
|
|
241
256
|
|
|
@@ -297,4 +312,49 @@ export class PmYjsBinding extends EventTarget {
|
|
|
297
312
|
getSelectionStash(): SelectionStash | undefined {
|
|
298
313
|
return this.selectionStash;
|
|
299
314
|
}
|
|
315
|
+
|
|
316
|
+
isEditable() {
|
|
317
|
+
return !this.diffViewer.isActive();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
setSnapshot(
|
|
321
|
+
snapshot: Uint8Array<ArrayBufferLike>,
|
|
322
|
+
prevSnapshot: Uint8Array<ArrayBufferLike> | undefined,
|
|
323
|
+
) {
|
|
324
|
+
if (!this.yjs) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
this.diffViewer.setSnapshot(this.yjs, snapshot, prevSnapshot);
|
|
328
|
+
|
|
329
|
+
// clear mapping because we are going to rerender
|
|
330
|
+
this.bindingMetadata.mapping.clear();
|
|
331
|
+
this.bindingMetadata.isOverlappingMark.clear();
|
|
332
|
+
|
|
333
|
+
this.mux(() => {
|
|
334
|
+
const historyDoc = this.diffViewer.getHistoryDoc();
|
|
335
|
+
if (!historyDoc) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
historyDoc.transact((ytr) => {
|
|
340
|
+
const state = this.editor.state;
|
|
341
|
+
const fragmentContent = this.diffViewer.getFragmentContent(state, ytr);
|
|
342
|
+
|
|
343
|
+
if (!fragmentContent) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const tr = state.tr;
|
|
348
|
+
tr.replace(
|
|
349
|
+
0,
|
|
350
|
+
state.doc.content.size,
|
|
351
|
+
new Slice(Fragment.from(fragmentContent), 0, 0),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
tr.setMeta('addToHistory', false);
|
|
355
|
+
tr.setMeta(ySyncPluginKey, { isChangeOrigin: true }),
|
|
356
|
+
this.editor.dispatchTransaction(tr);
|
|
357
|
+
}, ySyncPluginKey);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
300
360
|
}
|
|
@@ -51,7 +51,7 @@ export const createNodeFromYElement = (
|
|
|
51
51
|
meta: BindingMetadata,
|
|
52
52
|
snapshot?: Y.Snapshot,
|
|
53
53
|
prevSnapshot?: Y.Snapshot,
|
|
54
|
-
computeYChange?: (
|
|
54
|
+
computeYChange?: (changeType: 'removed' | 'added', yid: Y.ID) => any,
|
|
55
55
|
): PModel.Node | null => {
|
|
56
56
|
const children: PModel.Node[] = [];
|
|
57
57
|
const createChildren = (type: Y.XmlElement | Y.XmlText) => {
|
package/src/debug.ts
CHANGED
|
@@ -177,6 +177,27 @@ export function debugYNode(
|
|
|
177
177
|
return retVal;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
if (node instanceof Y.AbstractType) {
|
|
181
|
+
if (!config.isVisible(node._item)) {
|
|
182
|
+
return '';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const atype: Y.AbstractType<any> = node;
|
|
186
|
+
|
|
187
|
+
retVal += config.renderDeleted(
|
|
188
|
+
`Y.AbstractType(${atype._length})`,
|
|
189
|
+
node._item,
|
|
190
|
+
);
|
|
191
|
+
retVal += yDebugClient(node._item);
|
|
192
|
+
retVal += '\n';
|
|
193
|
+
|
|
194
|
+
retVal += indentText(
|
|
195
|
+
JSON.stringify(atype._item),
|
|
196
|
+
1,
|
|
197
|
+
);
|
|
198
|
+
return retVal;
|
|
199
|
+
}
|
|
200
|
+
|
|
180
201
|
if ('object' !== typeof node) {
|
|
181
202
|
retVal += '' + node;
|
|
182
203
|
return retVal;
|
package/src/ui/selection.ts
CHANGED
|
@@ -186,11 +186,13 @@ export class SelectionStash {
|
|
|
186
186
|
if (!('root' in view)) {
|
|
187
187
|
return false;
|
|
188
188
|
}
|
|
189
|
-
const selection =
|
|
189
|
+
const selection = document.getSelection();
|
|
190
190
|
|
|
191
|
-
if (
|
|
191
|
+
if (
|
|
192
|
+
!selection || selection.anchorNode == null || selection.focusNode == null
|
|
193
|
+
) return false;
|
|
192
194
|
|
|
193
|
-
const range = document.createRange();
|
|
195
|
+
const range = document.createRange();
|
|
194
196
|
range.setStart(selection.anchorNode, selection.anchorOffset);
|
|
195
197
|
range.setEnd(selection.focusNode, selection.focusOffset);
|
|
196
198
|
|
package/src/utils.ts
CHANGED
|
@@ -13,7 +13,7 @@ const _convolute = (digest: Uint8Array) => {
|
|
|
13
13
|
export const hashOfJSON = (json: any) =>
|
|
14
14
|
buf.toBase64(_convolute(sha256.digest(buf.encodeAny(json))));
|
|
15
15
|
|
|
16
|
-
export const isVisible = (item: Y.Item, snapshot
|
|
16
|
+
export const isVisible = (item: Y.Item, snapshot?: Y.Snapshot) =>
|
|
17
17
|
snapshot === undefined ? !item.deleted : (snapshot.sv.has(item.id.client) &&
|
|
18
18
|
(snapshot.sv.get(item.id.client)!) > item.id.clock &&
|
|
19
19
|
!Y.isDeleted(snapshot.ds, item.id));
|
package/src/ySyncPlugin.ts
CHANGED
|
@@ -30,7 +30,15 @@ interface YSyncMeta {
|
|
|
30
30
|
leaveRoom?: boolean;
|
|
31
31
|
isChangeOrigin?: boolean;
|
|
32
32
|
isUndoRedoOperation?: boolean;
|
|
33
|
-
|
|
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;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
/**
|
|
@@ -50,7 +58,8 @@ export const ySyncPlugin = (
|
|
|
50
58
|
const plugin: Plugin<YSyncPluginState> = new Plugin<YSyncPluginState>({
|
|
51
59
|
props: {
|
|
52
60
|
editable: (state) => {
|
|
53
|
-
|
|
61
|
+
const syncState = ySyncPluginKey.getState(state)!;
|
|
62
|
+
return syncState.binding.isEditable();
|
|
54
63
|
},
|
|
55
64
|
},
|
|
56
65
|
key: ySyncPluginKey,
|
|
@@ -119,6 +128,34 @@ export const ySyncPlugin = (
|
|
|
119
128
|
pluginState.isUndoRedoOperation = !!pluginMeta?.isChangeOrigin &&
|
|
120
129
|
!!pluginMeta?.isUndoRedoOperation;
|
|
121
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
|
+
}
|
|
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;
|
|
157
|
+
}
|
|
158
|
+
|
|
122
159
|
return pluginState;
|
|
123
160
|
},
|
|
124
161
|
},
|
|
@@ -139,27 +176,29 @@ export const ySyncPlugin = (
|
|
|
139
176
|
}
|
|
140
177
|
|
|
141
178
|
const binding = pluginState.binding;
|
|
142
|
-
if (
|
|
143
|
-
// If the content doesn't change initially, we don't render anything to Yjs
|
|
144
|
-
// If the content was cleared by a user action, we want to catch the change and
|
|
145
|
-
// represent it in Yjs
|
|
146
|
-
initialContentChanged ||
|
|
147
|
-
view.state.doc.content.findDiffStart(
|
|
148
|
-
view.state.doc.type.createAndFill()!.content,
|
|
149
|
-
) !== null
|
|
150
|
-
) {
|
|
151
|
-
initialContentChanged = true;
|
|
179
|
+
if (binding.isEditable()) {
|
|
152
180
|
if (
|
|
153
|
-
|
|
154
|
-
|
|
181
|
+
// If the content doesn't change initially, we don't render anything to Yjs
|
|
182
|
+
// If the content was cleared by a user action, we want to catch the change and
|
|
183
|
+
// represent it in Yjs
|
|
184
|
+
initialContentChanged ||
|
|
185
|
+
view.state.doc.content.findDiffStart(
|
|
186
|
+
view.state.doc.type.createAndFill()!.content,
|
|
187
|
+
) !== null
|
|
155
188
|
) {
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
|
|
189
|
+
initialContentChanged = true;
|
|
190
|
+
if (
|
|
191
|
+
pluginState.binding.addToYjsHistory === false &&
|
|
192
|
+
!pluginState.isChangeOrigin
|
|
193
|
+
) {
|
|
194
|
+
const yUndoPluginState = yUndoPluginKey.getState(view.state);
|
|
195
|
+
if (yUndoPluginState?.undoManager) {
|
|
196
|
+
yUndoPluginState.undoManager.stopCapturing();
|
|
197
|
+
}
|
|
159
198
|
}
|
|
160
|
-
}
|
|
161
199
|
|
|
162
|
-
|
|
200
|
+
binding.pmChanged();
|
|
201
|
+
}
|
|
163
202
|
}
|
|
164
203
|
},
|
|
165
204
|
destroy: () => {
|