@kerebron/extension-yjs 0.5.2 → 0.5.4
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/README.md +3 -89
- package/esm/ExtensionYjs.d.ts +10 -1
- package/esm/ExtensionYjs.d.ts.map +1 -1
- package/esm/ExtensionYjs.js +47 -6
- package/esm/ExtensionYjs.js.map +1 -1
- package/esm/MarkYChange.d.ts +7 -0
- package/esm/MarkYChange.d.ts.map +1 -0
- package/esm/MarkYChange.js +21 -0
- package/esm/MarkYChange.js.map +1 -0
- package/esm/ProsemirrorBinding.d.ts +60 -0
- package/esm/ProsemirrorBinding.d.ts.map +1 -0
- package/esm/ProsemirrorBinding.js +405 -0
- package/esm/ProsemirrorBinding.js.map +1 -0
- package/esm/createNodeFromYElement.d.ts +10 -0
- package/esm/createNodeFromYElement.d.ts.map +1 -0
- package/esm/createNodeFromYElement.js +123 -0
- package/esm/createNodeFromYElement.js.map +1 -0
- package/esm/debug.d.ts +13 -0
- package/esm/debug.d.ts.map +1 -0
- package/esm/debug.js +147 -0
- package/esm/debug.js.map +1 -0
- package/esm/keys.d.ts +5 -8
- package/esm/keys.d.ts.map +1 -1
- package/esm/keys.js +1 -6
- package/esm/keys.js.map +1 -1
- package/esm/lib.d.ts +1 -2
- package/esm/lib.d.ts.map +1 -1
- package/esm/lib.js +12 -2
- package/esm/lib.js.map +1 -1
- package/esm/updateYFragment.d.ts +17 -0
- package/esm/updateYFragment.d.ts.map +1 -0
- package/esm/updateYFragment.js +333 -0
- package/esm/updateYFragment.js.map +1 -0
- package/esm/utils.d.ts +2 -0
- package/esm/utils.d.ts.map +1 -1
- package/esm/utils.js +4 -0
- package/esm/utils.js.map +1 -1
- package/esm/yPositionPlugin.d.ts +12 -4
- package/esm/yPositionPlugin.d.ts.map +1 -1
- package/esm/yPositionPlugin.js +114 -61
- package/esm/yPositionPlugin.js.map +1 -1
- package/esm/ySyncPlugin.d.ts +16 -78
- package/esm/ySyncPlugin.d.ts.map +1 -1
- package/esm/ySyncPlugin.js +81 -848
- package/esm/ySyncPlugin.js.map +1 -1
- package/esm/yUndoPlugin.d.ts +1 -1
- package/esm/yUndoPlugin.d.ts.map +1 -1
- package/esm/yUndoPlugin.js +1 -1
- package/esm/yUndoPlugin.js.map +1 -1
- package/package.json +9 -3
- package/src/ExtensionYjs.ts +65 -9
- package/src/MarkYChange.ts +23 -0
- package/src/ProsemirrorBinding.ts +607 -0
- package/src/createNodeFromYElement.ts +175 -0
- package/src/debug.ts +218 -0
- package/src/keys.ts +9 -9
- package/src/lib.ts +11 -3
- package/src/updateYFragment.ts +439 -0
- package/src/utils.ts +6 -0
- package/src/yPositionPlugin.ts +167 -92
- package/src/ySyncPlugin.ts +135 -1193
- package/src/yUndoPlugin.ts +1 -1
- package/esm/convertUtils.d.ts +0 -59
- package/esm/convertUtils.d.ts.map +0 -1
- package/esm/convertUtils.js +0 -89
- package/esm/convertUtils.js.map +0 -1
- package/src/convertUtils.ts +0 -143
package/src/ySyncPlugin.ts
CHANGED
|
@@ -1,61 +1,17 @@
|
|
|
1
|
-
// deno-lint-ignore-file no-window
|
|
2
1
|
import * as Y from 'yjs';
|
|
3
|
-
import {
|
|
4
|
-
import * as PModel from 'prosemirror-model';
|
|
5
|
-
import {
|
|
6
|
-
AllSelection,
|
|
7
|
-
NodeSelection,
|
|
8
|
-
Plugin,
|
|
9
|
-
Selection,
|
|
10
|
-
TextSelection,
|
|
11
|
-
Transaction,
|
|
12
|
-
} from 'prosemirror-state';
|
|
13
|
-
import { Mark, MarkType, Node, Schema } from 'prosemirror-model';
|
|
14
|
-
import { EditorState } from 'prosemirror-state';
|
|
15
|
-
import { EditorView } from 'prosemirror-view';
|
|
16
|
-
import * as math from 'lib0/math';
|
|
17
|
-
import * as object from 'lib0/object';
|
|
18
|
-
import * as set from 'lib0/set';
|
|
19
|
-
import { simpleDiff } from 'lib0/diff';
|
|
20
|
-
import * as error from 'lib0/error';
|
|
21
|
-
import * as random from 'lib0/random';
|
|
22
|
-
import * as environment from 'lib0/environment';
|
|
23
|
-
import * as dom from 'lib0/dom';
|
|
24
|
-
import * as eventloop from 'lib0/eventloop';
|
|
25
|
-
import * as map from 'lib0/map';
|
|
26
|
-
|
|
27
|
-
import { remoteSelectionPluginKey } from '@kerebron/extension-basic-editor/ExtensionRemoteSelection';
|
|
2
|
+
import { Plugin } from 'prosemirror-state';
|
|
28
3
|
|
|
29
4
|
import { ySyncPluginKey, yUndoPluginKey } from './keys.js';
|
|
30
|
-
import
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
relativePositionToAbsolutePosition,
|
|
34
|
-
} from './lib.js';
|
|
5
|
+
import { defaultColors, ProsemirrorBinding } from './ProsemirrorBinding.js';
|
|
6
|
+
import { Schema } from 'prosemirror-model';
|
|
7
|
+
import type { CreateWsProvider, YjsProvider } from './ExtensionYjs.js';
|
|
35
8
|
|
|
36
9
|
export type TransactFunc<T> = (
|
|
37
10
|
f: (arg0?: Y.Transaction) => T,
|
|
38
11
|
origin?: any,
|
|
39
12
|
) => T;
|
|
40
13
|
|
|
41
|
-
export interface
|
|
42
|
-
mapping: ProsemirrorMapping;
|
|
43
|
-
isOMark: Map<MarkType, boolean>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export const isVisible = (item: Y.Item, snapshot: Y.Snapshot) =>
|
|
47
|
-
snapshot === undefined
|
|
48
|
-
? !item.deleted
|
|
49
|
-
: (snapshot.sv.has(item.id.client) && /** @type {number} */
|
|
50
|
-
(snapshot.sv.get(item.id.client)) > item.id.clock &&
|
|
51
|
-
!Y.isDeleted(snapshot.ds, item.id));
|
|
52
|
-
|
|
53
|
-
type ProsemirrorMapping = Map<
|
|
54
|
-
Y.AbstractType<any>,
|
|
55
|
-
PModel.Node | Array<PModel.Node>
|
|
56
|
-
>;
|
|
57
|
-
|
|
58
|
-
interface ColorDef {
|
|
14
|
+
export interface ColorDef {
|
|
59
15
|
light: string;
|
|
60
16
|
dark: string;
|
|
61
17
|
}
|
|
@@ -63,97 +19,142 @@ interface ColorDef {
|
|
|
63
19
|
interface YSyncOpts {
|
|
64
20
|
colors?: Array<ColorDef>;
|
|
65
21
|
colorMapping?: Map<string, ColorDef>;
|
|
66
|
-
permanentUserData?: Y.PermanentUserData
|
|
67
|
-
mapping?: ProsemirrorMapping;
|
|
22
|
+
permanentUserData?: Y.PermanentUserData;
|
|
68
23
|
onFirstRender?: () => void;
|
|
24
|
+
roomId?: string;
|
|
69
25
|
}
|
|
70
26
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
27
|
+
export interface YSyncPluginState {
|
|
28
|
+
roomId: string;
|
|
29
|
+
provider?: YjsProvider;
|
|
30
|
+
type: Y.XmlFragment;
|
|
31
|
+
ydoc: Y.Doc;
|
|
75
32
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
colorMapping.set(user, random.oneOf(colors));
|
|
89
|
-
}
|
|
90
|
-
return colorMapping.get(user) || defaultColors[0];
|
|
91
|
-
};
|
|
33
|
+
binding: ProsemirrorBinding;
|
|
34
|
+
addToHistory: boolean;
|
|
35
|
+
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>;
|
|
42
|
+
permanentUserData?: Y.PermanentUserData;
|
|
43
|
+
}
|
|
92
44
|
|
|
93
45
|
/**
|
|
94
46
|
* This plugin listens to changes in prosemirror view and keeps yXmlState and view in sync.
|
|
95
47
|
*
|
|
96
48
|
* This plugin also keeps references to the type and the shared document so other plugins can access it.
|
|
97
49
|
*/
|
|
98
|
-
export const ySyncPlugin = (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
50
|
+
export const ySyncPlugin = (
|
|
51
|
+
schema: Schema,
|
|
52
|
+
createWsProvider: CreateWsProvider,
|
|
53
|
+
{
|
|
54
|
+
colors = defaultColors,
|
|
55
|
+
colorMapping = new Map(),
|
|
56
|
+
onFirstRender = () => {
|
|
57
|
+
},
|
|
58
|
+
}: YSyncOpts = {},
|
|
59
|
+
): any => {
|
|
106
60
|
let initialContentChanged = false;
|
|
107
|
-
const
|
|
108
|
-
const plugin = new Plugin({
|
|
61
|
+
const plugin: Plugin<YSyncPluginState> = new Plugin<YSyncPluginState>({
|
|
109
62
|
props: {
|
|
110
|
-
editable: (state) => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
},
|
|
63
|
+
// editable: (state) => {
|
|
64
|
+
// const syncState = ySyncPluginKey.getState(state)!;
|
|
65
|
+
// return syncState.snapshot && syncState.prevSnapshot;
|
|
66
|
+
// },
|
|
114
67
|
},
|
|
115
68
|
key: ySyncPluginKey,
|
|
116
69
|
state: {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
70
|
+
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
|
+
|
|
121
75
|
return {
|
|
122
|
-
|
|
123
|
-
|
|
76
|
+
roomId: '',
|
|
77
|
+
provider: undefined,
|
|
124
78
|
binding,
|
|
125
|
-
|
|
126
|
-
|
|
79
|
+
type: yXmlFragment,
|
|
80
|
+
ydoc: ydoc,
|
|
81
|
+
snapshot: undefined,
|
|
82
|
+
prevSnapshot: undefined,
|
|
127
83
|
isChangeOrigin: false,
|
|
128
84
|
isUndoRedoOperation: false,
|
|
129
85
|
addToHistory: true,
|
|
86
|
+
restore: undefined,
|
|
130
87
|
colors,
|
|
131
88
|
colorMapping,
|
|
132
|
-
permanentUserData,
|
|
89
|
+
permanentUserData: undefined,
|
|
133
90
|
};
|
|
134
91
|
},
|
|
135
|
-
apply: (tr, pluginState:
|
|
136
|
-
const change = tr.getMeta(ySyncPluginKey);
|
|
92
|
+
apply: (tr, pluginState: YSyncPluginState) => {
|
|
93
|
+
const change: Partial<YSyncPluginState> = tr.getMeta(ySyncPluginKey);
|
|
137
94
|
if (change !== undefined) {
|
|
138
|
-
pluginState =
|
|
139
|
-
|
|
140
|
-
|
|
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
|
+
}
|
|
119
|
+
} 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;
|
|
126
|
+
}
|
|
127
|
+
pluginState.snapshot = undefined;
|
|
128
|
+
pluginState.prevSnapshot = undefined;
|
|
129
|
+
pluginState.isChangeOrigin = false;
|
|
130
|
+
pluginState.isUndoRedoOperation = false;
|
|
131
|
+
pluginState.addToHistory = true;
|
|
132
|
+
pluginState.restore = undefined;
|
|
133
|
+
|
|
134
|
+
initialContentChanged = false;
|
|
135
|
+
|
|
136
|
+
pluginState.binding.changeRoom(pluginState.type);
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
pluginState.binding._forceRerender();
|
|
139
|
+
}, 0);
|
|
140
|
+
|
|
141
|
+
return pluginState;
|
|
141
142
|
}
|
|
142
143
|
}
|
|
144
|
+
|
|
143
145
|
pluginState.addToHistory = tr.getMeta('addToHistory') !== false;
|
|
144
146
|
// always set isChangeOrigin. If undefined, this is not change origin.
|
|
145
|
-
pluginState.isChangeOrigin = change
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
) {
|
|
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) {
|
|
154
155
|
// snapshot changed, rerender next
|
|
155
156
|
setTimeout(() => {
|
|
156
|
-
if (binding.prosemirrorView
|
|
157
|
+
if (!binding.prosemirrorView) {
|
|
157
158
|
return;
|
|
158
159
|
}
|
|
159
160
|
if (change.restore == null) {
|
|
@@ -172,8 +173,12 @@ export const ySyncPlugin = (yXmlFragment: Y.XmlFragment, {
|
|
|
172
173
|
delete pluginState.restore;
|
|
173
174
|
delete pluginState.snapshot;
|
|
174
175
|
delete pluginState.prevSnapshot;
|
|
176
|
+
initialContentChanged = false;
|
|
175
177
|
binding.mux(() => {
|
|
176
|
-
binding.
|
|
178
|
+
if (!binding.prosemirrorView) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
binding.prosemirrorChanged(
|
|
177
182
|
binding.prosemirrorView.state.doc,
|
|
178
183
|
);
|
|
179
184
|
});
|
|
@@ -185,17 +190,29 @@ export const ySyncPlugin = (yXmlFragment: Y.XmlFragment, {
|
|
|
185
190
|
},
|
|
186
191
|
},
|
|
187
192
|
view: (view) => {
|
|
193
|
+
const pluginState: YSyncPluginState = ySyncPluginKey.getState(
|
|
194
|
+
view.state,
|
|
195
|
+
)!;
|
|
196
|
+
const binding = pluginState.binding;
|
|
197
|
+
|
|
188
198
|
binding.initView(view);
|
|
189
|
-
if (mapping
|
|
199
|
+
if (binding.mapping.size === 0) {
|
|
190
200
|
// force rerender to update the bindings mapping
|
|
191
201
|
binding._forceRerender();
|
|
192
202
|
}
|
|
193
203
|
onFirstRender();
|
|
194
204
|
return {
|
|
195
205
|
update: () => {
|
|
196
|
-
const pluginState = plugin.getState(
|
|
206
|
+
const pluginState: YSyncPluginState | undefined = plugin.getState(
|
|
207
|
+
view.state,
|
|
208
|
+
);
|
|
209
|
+
if (!pluginState) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const binding = pluginState.binding;
|
|
197
214
|
if (
|
|
198
|
-
pluginState.snapshot
|
|
215
|
+
!pluginState.snapshot && !pluginState.prevSnapshot
|
|
199
216
|
) {
|
|
200
217
|
if (
|
|
201
218
|
// If the content doesn't change initially, we don't render anything to Yjs
|
|
@@ -217,9 +234,12 @@ export const ySyncPlugin = (yXmlFragment: Y.XmlFragment, {
|
|
|
217
234
|
}
|
|
218
235
|
}
|
|
219
236
|
binding.mux(() => {
|
|
220
|
-
pluginState.
|
|
237
|
+
if (!pluginState.ydoc) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
pluginState.ydoc.transact((tr) => {
|
|
221
241
|
tr.meta.set('addToHistory', pluginState.addToHistory);
|
|
222
|
-
binding.
|
|
242
|
+
binding.prosemirrorChanged(view.state.doc);
|
|
223
243
|
}, ySyncPluginKey);
|
|
224
244
|
});
|
|
225
245
|
}
|
|
@@ -233,1081 +253,3 @@ export const ySyncPlugin = (yXmlFragment: Y.XmlFragment, {
|
|
|
233
253
|
});
|
|
234
254
|
return plugin;
|
|
235
255
|
};
|
|
236
|
-
|
|
237
|
-
const restoreRelativeSelection = (
|
|
238
|
-
tr: Transaction,
|
|
239
|
-
relSel: ReturnType<typeof getRelativeSelection>,
|
|
240
|
-
binding: ProsemirrorBinding,
|
|
241
|
-
) => {
|
|
242
|
-
if (relSel !== null && relSel.anchor !== null && relSel.head !== null) {
|
|
243
|
-
if (relSel.type === 'all') {
|
|
244
|
-
tr.setSelection(new AllSelection(tr.doc));
|
|
245
|
-
} else if (relSel.type === 'node') {
|
|
246
|
-
const anchor = relativePositionToAbsolutePosition(
|
|
247
|
-
binding.ydoc,
|
|
248
|
-
binding.type,
|
|
249
|
-
relSel.anchor,
|
|
250
|
-
binding.mapping,
|
|
251
|
-
);
|
|
252
|
-
tr.setSelection(NodeSelection.create(tr.doc, anchor));
|
|
253
|
-
} else {
|
|
254
|
-
const anchor = relativePositionToAbsolutePosition(
|
|
255
|
-
binding.ydoc,
|
|
256
|
-
binding.type,
|
|
257
|
-
relSel.anchor,
|
|
258
|
-
binding.mapping,
|
|
259
|
-
);
|
|
260
|
-
const head = relativePositionToAbsolutePosition(
|
|
261
|
-
binding.ydoc,
|
|
262
|
-
binding.type,
|
|
263
|
-
relSel.head,
|
|
264
|
-
binding.mapping,
|
|
265
|
-
);
|
|
266
|
-
if (anchor !== null && head !== null) {
|
|
267
|
-
const sel = TextSelection.between(
|
|
268
|
-
tr.doc.resolve(anchor),
|
|
269
|
-
tr.doc.resolve(head),
|
|
270
|
-
);
|
|
271
|
-
tr.setSelection(sel);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
interface TransactionSelection {
|
|
278
|
-
type: string;
|
|
279
|
-
anchor: Y.RelativePosition;
|
|
280
|
-
head: Y.RelativePosition;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function getSelectionType(selection: Selection) {
|
|
284
|
-
if (selection instanceof TextSelection) {
|
|
285
|
-
return 'text';
|
|
286
|
-
}
|
|
287
|
-
if (selection instanceof AllSelection) {
|
|
288
|
-
return 'all';
|
|
289
|
-
}
|
|
290
|
-
if (selection instanceof NodeSelection) {
|
|
291
|
-
return 'node';
|
|
292
|
-
}
|
|
293
|
-
return 'other_selection';
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export const getRelativeSelection = (
|
|
297
|
-
pmbinding: ProsemirrorBinding,
|
|
298
|
-
state: EditorState,
|
|
299
|
-
): TransactionSelection => ({
|
|
300
|
-
type: getSelectionType(state.selection),
|
|
301
|
-
anchor: absolutePositionToRelativePosition(
|
|
302
|
-
state.selection.anchor,
|
|
303
|
-
pmbinding.type,
|
|
304
|
-
pmbinding.mapping,
|
|
305
|
-
),
|
|
306
|
-
head: absolutePositionToRelativePosition(
|
|
307
|
-
state.selection.head,
|
|
308
|
-
pmbinding.type,
|
|
309
|
-
pmbinding.mapping,
|
|
310
|
-
),
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
interface PluginState {
|
|
314
|
-
addToHistory: boolean;
|
|
315
|
-
isChangeOrigin: boolean;
|
|
316
|
-
restore: any;
|
|
317
|
-
snapshot?: Y.Snapshot;
|
|
318
|
-
prevSnapshot?: Y.Snapshot;
|
|
319
|
-
isUndoRedoOperation: boolean;
|
|
320
|
-
colors: Array<ColorDef>;
|
|
321
|
-
colorMapping: Map<string, ColorDef>;
|
|
322
|
-
permanentUserData: Y.PermanentUserData;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Binding for prosemirror.
|
|
327
|
-
*
|
|
328
|
-
* @protected
|
|
329
|
-
*/
|
|
330
|
-
export class ProsemirrorBinding implements BindingMetadata {
|
|
331
|
-
public ydoc: Y.Doc;
|
|
332
|
-
public isOMark: Map<MarkType, boolean>;
|
|
333
|
-
public type: Y.XmlFragment;
|
|
334
|
-
private mux: any;
|
|
335
|
-
public prosemirrorView: EditorView | null;
|
|
336
|
-
private _beforeTransactionSelection: TransactionSelection | null;
|
|
337
|
-
|
|
338
|
-
private beforeAllTransactions: () => void;
|
|
339
|
-
private afterAllTransactions: () => void;
|
|
340
|
-
private _observeFunction: (event: any, transaction: any) => void;
|
|
341
|
-
private _domSelectionInView: boolean = false;
|
|
342
|
-
get beforeTransactionSelection(): TransactionSelection {
|
|
343
|
-
return this._beforeTransactionSelection;
|
|
344
|
-
}
|
|
345
|
-
set beforeTransactionSelection(value: TransactionSelection) {
|
|
346
|
-
this._beforeTransactionSelection = value;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
constructor(
|
|
350
|
-
yXmlFragment: Y.XmlFragment,
|
|
351
|
-
public mapping: ProsemirrorMapping = new Map(),
|
|
352
|
-
) {
|
|
353
|
-
this.type = yXmlFragment;
|
|
354
|
-
this.prosemirrorView = null;
|
|
355
|
-
this.mux = createMutex();
|
|
356
|
-
/**
|
|
357
|
-
* Is overlapping mark - i.e. mark does not exclude itself.
|
|
358
|
-
*/
|
|
359
|
-
this.isOMark = new Map();
|
|
360
|
-
this._observeFunction = (event, transaction) =>
|
|
361
|
-
this.yXmlChanged(event, transaction);
|
|
362
|
-
this.ydoc = yXmlFragment.doc!;
|
|
363
|
-
/**
|
|
364
|
-
* current selection as relative positions in the Yjs model
|
|
365
|
-
*/
|
|
366
|
-
this._beforeTransactionSelection = null;
|
|
367
|
-
this.beforeAllTransactions = () => {
|
|
368
|
-
if (
|
|
369
|
-
this._beforeTransactionSelection === null &&
|
|
370
|
-
this.prosemirrorView != null
|
|
371
|
-
) {
|
|
372
|
-
this._beforeTransactionSelection = getRelativeSelection(
|
|
373
|
-
this,
|
|
374
|
-
this.prosemirrorView.state,
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
};
|
|
378
|
-
this.afterAllTransactions = () => {
|
|
379
|
-
this._beforeTransactionSelection = null;
|
|
380
|
-
};
|
|
381
|
-
this._domSelectionInView = false;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
debug(msg = 'ydoc.prosemirror') {
|
|
385
|
-
console.log(msg, this.type.toString());
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
_isLocalCursorInView(): boolean {
|
|
389
|
-
if (!this.prosemirrorView?.hasFocus()) return false;
|
|
390
|
-
if (environment.isBrowser && this._domSelectionInView === false) {
|
|
391
|
-
// Calculate the domSelectionInView and clear by next tick after all events are finished
|
|
392
|
-
eventloop.timeout(0, () => {
|
|
393
|
-
this._domSelectionInView = false;
|
|
394
|
-
});
|
|
395
|
-
this._domSelectionInView = this._isDomSelectionInView();
|
|
396
|
-
}
|
|
397
|
-
return this._domSelectionInView;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
_isDomSelectionInView(): boolean {
|
|
401
|
-
const selection = this.prosemirrorView?.root?.getSelection(); // https://stackoverflow.com/questions/62054839/shadowroot-getselection
|
|
402
|
-
|
|
403
|
-
if (!selection || selection.anchorNode == null) return false;
|
|
404
|
-
|
|
405
|
-
const range = dom.doc.createRange(); // https://github.com/yjs/y-prosemirror/pull/193
|
|
406
|
-
range.setStart(selection.anchorNode, selection.anchorOffset);
|
|
407
|
-
range.setEnd(selection.focusNode, selection.focusOffset);
|
|
408
|
-
|
|
409
|
-
// This is a workaround for an edgecase where getBoundingClientRect will
|
|
410
|
-
// return zero values if the selection is collapsed at the start of a newline
|
|
411
|
-
// see reference here: https://stackoverflow.com/a/59780954
|
|
412
|
-
const rects = range.getClientRects();
|
|
413
|
-
if (rects.length === 0) {
|
|
414
|
-
// probably buggy newline behavior, explicitly select the node contents
|
|
415
|
-
if (range.startContainer && range.collapsed) {
|
|
416
|
-
range.selectNodeContents(range.startContainer);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const bounding = range.getBoundingClientRect();
|
|
421
|
-
const documentElement = dom.doc.documentElement;
|
|
422
|
-
|
|
423
|
-
return bounding.bottom >= 0 && bounding.right >= 0 &&
|
|
424
|
-
bounding.left <=
|
|
425
|
-
(globalThis.innerWidth || documentElement.clientWidth || 0) &&
|
|
426
|
-
bounding.top <= (globalThis.innerHeight || documentElement.clientHeight || 0);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
renderSnapshot(snapshot: Y.Snapshot, prevSnapshot: Y.Snapshot) {
|
|
430
|
-
if (!prevSnapshot) {
|
|
431
|
-
prevSnapshot = Y.createSnapshot(Y.createDeleteSet(), new Map());
|
|
432
|
-
}
|
|
433
|
-
if (this.prosemirrorView) {
|
|
434
|
-
const _tr = this.prosemirrorView.state.tr.setMeta('addToHistory', false);
|
|
435
|
-
this.prosemirrorView.dispatch(
|
|
436
|
-
_tr.setMeta(ySyncPluginKey, { snapshot, prevSnapshot }),
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
unrenderSnapshot() {
|
|
442
|
-
this.mapping.clear();
|
|
443
|
-
this.mux(() => {
|
|
444
|
-
if (!this.prosemirrorView) {
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
const state = this.prosemirrorView.state;
|
|
448
|
-
const fragmentContent = this.type.toArray().map((t: Y.XmlElement) =>
|
|
449
|
-
createNodeFromYElement(
|
|
450
|
-
t,
|
|
451
|
-
state.schema,
|
|
452
|
-
this,
|
|
453
|
-
)
|
|
454
|
-
).filter((n) => n !== null);
|
|
455
|
-
const _tr = state.tr.setMeta('addToHistory', false);
|
|
456
|
-
const tr = _tr.replace(
|
|
457
|
-
0,
|
|
458
|
-
state.doc.content.size,
|
|
459
|
-
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0),
|
|
460
|
-
);
|
|
461
|
-
tr.setMeta(ySyncPluginKey, { snapshot: null, prevSnapshot: null });
|
|
462
|
-
this.prosemirrorView.dispatch(tr);
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
_forceRerender() {
|
|
467
|
-
this.mapping.clear();
|
|
468
|
-
this.mux(() => {
|
|
469
|
-
if (!this.prosemirrorView) {
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
const state = this.prosemirrorView.state;
|
|
473
|
-
// If this is a forced rerender, this might neither happen as a pm change nor within a Yjs
|
|
474
|
-
// transaction. Then the "before selection" doesn't exist. In this case, we need to create a
|
|
475
|
-
// relative position before replacing content. Fixes #126
|
|
476
|
-
const sel = this._beforeTransactionSelection !== null
|
|
477
|
-
? null
|
|
478
|
-
: state.selection;
|
|
479
|
-
const fragmentContent = this.type.toArray().map((t) =>
|
|
480
|
-
createNodeFromYElement(
|
|
481
|
-
/** @type {Y.XmlElement} */ (t),
|
|
482
|
-
state.schema,
|
|
483
|
-
this,
|
|
484
|
-
)
|
|
485
|
-
).filter((n) => n !== null);
|
|
486
|
-
const _tr = state.tr.setMeta('addToHistory', false);
|
|
487
|
-
const tr = _tr.replace(
|
|
488
|
-
0,
|
|
489
|
-
state.doc.content.size,
|
|
490
|
-
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0),
|
|
491
|
-
);
|
|
492
|
-
if (sel) {
|
|
493
|
-
/**
|
|
494
|
-
* If the Prosemirror document we just created from this.type is
|
|
495
|
-
* smaller than the previous document, the selection might be
|
|
496
|
-
* out of bound, which would make Prosemirror throw an error.
|
|
497
|
-
*/
|
|
498
|
-
const clampedAnchor = math.min(
|
|
499
|
-
math.max(sel.anchor, 0),
|
|
500
|
-
tr.doc.content.size,
|
|
501
|
-
);
|
|
502
|
-
const clampedHead = math.min(
|
|
503
|
-
math.max(sel.head, 0),
|
|
504
|
-
tr.doc.content.size,
|
|
505
|
-
);
|
|
506
|
-
|
|
507
|
-
tr.setSelection(
|
|
508
|
-
TextSelection.create(tr.doc, clampedAnchor, clampedHead),
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
this.prosemirrorView.dispatch(
|
|
512
|
-
tr.setMeta(ySyncPluginKey, { isChangeOrigin: true, binding: this })
|
|
513
|
-
.setMeta(remoteSelectionPluginKey, { isChangeOrigin: true }),
|
|
514
|
-
);
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
_renderSnapshot(
|
|
519
|
-
snapshot: Y.Snapshot | Uint8Array,
|
|
520
|
-
prevSnapshot: Y.Snapshot | Uint8Array,
|
|
521
|
-
pluginState: PluginState,
|
|
522
|
-
) {
|
|
523
|
-
/**
|
|
524
|
-
* The document that contains the full history of this document.
|
|
525
|
-
*/
|
|
526
|
-
let historyDoc = this.ydoc;
|
|
527
|
-
let historyType = this.type;
|
|
528
|
-
if (!snapshot) {
|
|
529
|
-
snapshot = Y.snapshot(this.ydoc);
|
|
530
|
-
}
|
|
531
|
-
if (snapshot instanceof Uint8Array || prevSnapshot instanceof Uint8Array) {
|
|
532
|
-
if (
|
|
533
|
-
!(snapshot instanceof Uint8Array) ||
|
|
534
|
-
!(prevSnapshot instanceof Uint8Array)
|
|
535
|
-
) {
|
|
536
|
-
// expected both snapshots to be v2 updates
|
|
537
|
-
error.unexpectedCase();
|
|
538
|
-
}
|
|
539
|
-
historyDoc = new Y.Doc({ gc: false });
|
|
540
|
-
Y.applyUpdateV2(historyDoc, prevSnapshot);
|
|
541
|
-
prevSnapshot = Y.snapshot(historyDoc);
|
|
542
|
-
Y.applyUpdateV2(historyDoc, snapshot);
|
|
543
|
-
snapshot = Y.snapshot(historyDoc);
|
|
544
|
-
if (historyType._item === null) {
|
|
545
|
-
/**
|
|
546
|
-
* If is a root type, we need to find the root key in the initial document
|
|
547
|
-
* and use it to get the history type.
|
|
548
|
-
*/
|
|
549
|
-
const rootKey = Array.from(this.ydoc.share.keys()).find(
|
|
550
|
-
(key) => this.ydoc.share.get(key) === this.type,
|
|
551
|
-
);
|
|
552
|
-
historyType = historyDoc.getXmlFragment(rootKey);
|
|
553
|
-
} else {
|
|
554
|
-
/**
|
|
555
|
-
* If it is a sub type, we use the item id to find the history type.
|
|
556
|
-
*/
|
|
557
|
-
const historyStructs =
|
|
558
|
-
historyDoc.store.clients.get(historyType._item.id.client) ?? [];
|
|
559
|
-
const itemIndex = Y.findIndexSS(
|
|
560
|
-
historyStructs,
|
|
561
|
-
historyType._item.id.clock,
|
|
562
|
-
);
|
|
563
|
-
const item = /** @type {Y.Item} */ (historyStructs[itemIndex]);
|
|
564
|
-
const content = /** @type {Y.ContentType} */ (item.content);
|
|
565
|
-
historyType = /** @type {Y.XmlFragment} */ (content.type);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
// clear mapping because we are going to rerender
|
|
569
|
-
this.mapping.clear();
|
|
570
|
-
this.mux(() => {
|
|
571
|
-
historyDoc.transact((transaction) => {
|
|
572
|
-
// before rendering, we are going to sanitize ops and split deleted ops
|
|
573
|
-
// if they were deleted by seperate users.
|
|
574
|
-
/**
|
|
575
|
-
* @type {Y.PermanentUserData}
|
|
576
|
-
*/
|
|
577
|
-
const pud = pluginState.permanentUserData;
|
|
578
|
-
if (pud) {
|
|
579
|
-
pud.dss.forEach((ds) => {
|
|
580
|
-
Y.iterateDeletedStructs(transaction, ds, (_item) => {});
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
const computeYChange = (type: 'removed' | 'added', id: Y.ID) => {
|
|
584
|
-
const user = type === 'added'
|
|
585
|
-
? pud.getUserByClientId(id.client)
|
|
586
|
-
: pud.getUserByDeletedId(id);
|
|
587
|
-
return {
|
|
588
|
-
user,
|
|
589
|
-
type,
|
|
590
|
-
color: getUserColor(
|
|
591
|
-
pluginState.colorMapping,
|
|
592
|
-
pluginState.colors,
|
|
593
|
-
user,
|
|
594
|
-
),
|
|
595
|
-
};
|
|
596
|
-
};
|
|
597
|
-
// Create document fragment and render
|
|
598
|
-
const fragmentContent = Y.typeListToArraySnapshot(
|
|
599
|
-
historyType,
|
|
600
|
-
new Y.Snapshot(prevSnapshot.ds, snapshot.sv),
|
|
601
|
-
).map((t) => {
|
|
602
|
-
if (!this.prosemirrorView) {
|
|
603
|
-
return null;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
if (
|
|
607
|
-
!t._item.deleted || isVisible(t._item, snapshot) ||
|
|
608
|
-
isVisible(t._item, prevSnapshot)
|
|
609
|
-
) {
|
|
610
|
-
return createNodeFromYElement(
|
|
611
|
-
t,
|
|
612
|
-
this.prosemirrorView.state.schema,
|
|
613
|
-
{ mapping: new Map(), isOMark: new Map() },
|
|
614
|
-
snapshot,
|
|
615
|
-
prevSnapshot,
|
|
616
|
-
computeYChange,
|
|
617
|
-
);
|
|
618
|
-
} else {
|
|
619
|
-
// No need to render elements that are not visible by either snapshot.
|
|
620
|
-
// If a client adds and deletes content in the same snapshot the element is not visible by either snapshot.
|
|
621
|
-
return null;
|
|
622
|
-
}
|
|
623
|
-
}).filter((n) => n !== null);
|
|
624
|
-
if (this.prosemirrorView) {
|
|
625
|
-
const _tr = this.prosemirrorView.state.tr.setMeta(
|
|
626
|
-
'addToHistory',
|
|
627
|
-
false,
|
|
628
|
-
);
|
|
629
|
-
const tr = _tr.replace(
|
|
630
|
-
0,
|
|
631
|
-
this.prosemirrorView.state.doc.content.size,
|
|
632
|
-
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0),
|
|
633
|
-
);
|
|
634
|
-
this.prosemirrorView.dispatch(
|
|
635
|
-
tr.setMeta(ySyncPluginKey, { isChangeOrigin: true }),
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
}, ySyncPluginKey);
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
yXmlChanged(events: Array<Y.YEvent<any>>, transaction: Y.Transaction) {
|
|
643
|
-
if (this.prosemirrorView == null) return;
|
|
644
|
-
const syncState = ySyncPluginKey.getState(this.prosemirrorView.state);
|
|
645
|
-
if (
|
|
646
|
-
events.length === 0 || syncState.snapshot != null ||
|
|
647
|
-
syncState.prevSnapshot != null
|
|
648
|
-
) {
|
|
649
|
-
// drop out if snapshot is active
|
|
650
|
-
this.renderSnapshot(syncState.snapshot, syncState.prevSnapshot);
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
this.mux(() => {
|
|
654
|
-
const delType = (_, type: Y.AbstractType<any>) =>
|
|
655
|
-
this.mapping.delete(type);
|
|
656
|
-
Y.iterateDeletedStructs(
|
|
657
|
-
transaction,
|
|
658
|
-
transaction.deleteSet,
|
|
659
|
-
(struct) => {
|
|
660
|
-
if (struct.constructor === Y.Item) {
|
|
661
|
-
const type =
|
|
662
|
-
/** @type {Y.ContentType} */ (/** @type {Y.Item} */ (struct)
|
|
663
|
-
.content).type;
|
|
664
|
-
type && this.mapping.delete(type);
|
|
665
|
-
}
|
|
666
|
-
},
|
|
667
|
-
);
|
|
668
|
-
transaction.changed.forEach(delType);
|
|
669
|
-
transaction.changedParentTypes.forEach(delType);
|
|
670
|
-
|
|
671
|
-
if (!this.prosemirrorView) {
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
const state = this.prosemirrorView.state;
|
|
675
|
-
|
|
676
|
-
const fragmentContent = this.type.toArray().map((t) =>
|
|
677
|
-
createNodeIfNotExists(
|
|
678
|
-
/** @type {Y.XmlElement | Y.XmlHook} */ (t),
|
|
679
|
-
state.schema,
|
|
680
|
-
this,
|
|
681
|
-
)
|
|
682
|
-
).filter((n) => n !== null);
|
|
683
|
-
const _tr = state.tr.setMeta('addToHistory', false);
|
|
684
|
-
let tr = _tr.replace(
|
|
685
|
-
0,
|
|
686
|
-
state.doc.content.size,
|
|
687
|
-
new PModel.Slice(PModel.Fragment.from(fragmentContent), 0, 0),
|
|
688
|
-
);
|
|
689
|
-
try {
|
|
690
|
-
restoreRelativeSelection(tr, this._beforeTransactionSelection, this);
|
|
691
|
-
} catch (err) {
|
|
692
|
-
console.warn(err);
|
|
693
|
-
}
|
|
694
|
-
tr = tr.setMeta(ySyncPluginKey, {
|
|
695
|
-
isChangeOrigin: true,
|
|
696
|
-
isUndoRedoOperation: transaction.origin instanceof Y.UndoManager,
|
|
697
|
-
});
|
|
698
|
-
if (
|
|
699
|
-
this._beforeTransactionSelection !== null && this._isLocalCursorInView()
|
|
700
|
-
) {
|
|
701
|
-
tr.scrollIntoView();
|
|
702
|
-
}
|
|
703
|
-
this.prosemirrorView.dispatch(tr);
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
_prosemirrorChanged(doc: Node) {
|
|
708
|
-
this.ydoc.transact(() => {
|
|
709
|
-
updateYFragment(this.ydoc, this.type, doc, this);
|
|
710
|
-
this._beforeTransactionSelection = getRelativeSelection(
|
|
711
|
-
this,
|
|
712
|
-
this.prosemirrorView.state,
|
|
713
|
-
);
|
|
714
|
-
}, ySyncPluginKey);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* View is ready to listen to changes. Register observers.
|
|
719
|
-
*/
|
|
720
|
-
initView(prosemirrorView: any) {
|
|
721
|
-
if (this.prosemirrorView != null) this.destroy();
|
|
722
|
-
this.prosemirrorView = prosemirrorView;
|
|
723
|
-
this.ydoc.on('beforeAllTransactions', this.beforeAllTransactions);
|
|
724
|
-
this.ydoc.on('afterAllTransactions', this.afterAllTransactions);
|
|
725
|
-
this.type.observeDeep(this._observeFunction);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
destroy() {
|
|
729
|
-
if (this.prosemirrorView == null) return;
|
|
730
|
-
this.prosemirrorView = null;
|
|
731
|
-
this.type.unobserveDeep(this._observeFunction);
|
|
732
|
-
this.ydoc.off('beforeAllTransactions', this.beforeAllTransactions);
|
|
733
|
-
this.ydoc.off('afterAllTransactions', this.afterAllTransactions);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
const createNodeIfNotExists = (
|
|
738
|
-
el: Y.XmlElement | Y.XmlHook,
|
|
739
|
-
schema: PModel.Schema,
|
|
740
|
-
meta: BindingMetadata,
|
|
741
|
-
snapshot?: Y.Snapshot,
|
|
742
|
-
prevSnapshot?: Y.Snapshot,
|
|
743
|
-
computeYChange?: (arg0: 'removed' | 'added', arg1: Y.ID) => any,
|
|
744
|
-
): PModel.Node | null => {
|
|
745
|
-
const node: PModel.Node = meta.mapping.get(el);
|
|
746
|
-
if (node === undefined) {
|
|
747
|
-
if (el instanceof Y.XmlElement) {
|
|
748
|
-
return createNodeFromYElement(
|
|
749
|
-
el,
|
|
750
|
-
schema,
|
|
751
|
-
meta,
|
|
752
|
-
snapshot,
|
|
753
|
-
prevSnapshot,
|
|
754
|
-
computeYChange,
|
|
755
|
-
);
|
|
756
|
-
} else {
|
|
757
|
-
throw error.methodUnimplemented(); // we are currently not handling hooks
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
return node;
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
export const createNodeFromYElement = (
|
|
764
|
-
el: Y.XmlElement,
|
|
765
|
-
schema: any,
|
|
766
|
-
meta: BindingMetadata,
|
|
767
|
-
snapshot?: Y.Snapshot,
|
|
768
|
-
prevSnapshot?: Y.Snapshot,
|
|
769
|
-
computeYChange?: (arg0: 'removed' | 'added', arg1: Y.ID) => any,
|
|
770
|
-
): PModel.Node | null => {
|
|
771
|
-
const children: PModel.Node[] = [];
|
|
772
|
-
const createChildren = (type: Y.XmlElement | Y.XmlText) => {
|
|
773
|
-
if (type instanceof Y.XmlElement) {
|
|
774
|
-
const n = createNodeIfNotExists(
|
|
775
|
-
type,
|
|
776
|
-
schema,
|
|
777
|
-
meta,
|
|
778
|
-
snapshot,
|
|
779
|
-
prevSnapshot,
|
|
780
|
-
computeYChange,
|
|
781
|
-
);
|
|
782
|
-
if (n !== null) {
|
|
783
|
-
children.push(n);
|
|
784
|
-
}
|
|
785
|
-
} else {
|
|
786
|
-
// If the next ytext exists and was created by us, move the content to the current ytext.
|
|
787
|
-
// This is a fix for #160 -- duplication of characters when two Y.Text exist next to each
|
|
788
|
-
// other.
|
|
789
|
-
const nextytext = /** @type {Y.ContentType} */ (type._item.right?.content)
|
|
790
|
-
?.type;
|
|
791
|
-
if (
|
|
792
|
-
nextytext instanceof Y.Text && !nextytext._item.deleted &&
|
|
793
|
-
nextytext._item.id.client === nextytext.doc.clientID
|
|
794
|
-
) {
|
|
795
|
-
type.applyDelta([
|
|
796
|
-
{ retain: type.length },
|
|
797
|
-
...nextytext.toDelta(),
|
|
798
|
-
]);
|
|
799
|
-
nextytext.doc.transact((tr) => {
|
|
800
|
-
nextytext._item.delete(tr);
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
// now create the prosemirror text nodes
|
|
804
|
-
const ns = createTextNodesFromYText(
|
|
805
|
-
type,
|
|
806
|
-
schema,
|
|
807
|
-
meta,
|
|
808
|
-
snapshot,
|
|
809
|
-
prevSnapshot,
|
|
810
|
-
computeYChange,
|
|
811
|
-
);
|
|
812
|
-
if (ns !== null) {
|
|
813
|
-
ns.forEach((textchild) => {
|
|
814
|
-
if (textchild !== null) {
|
|
815
|
-
children.push(textchild);
|
|
816
|
-
}
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
};
|
|
821
|
-
if (snapshot === undefined || prevSnapshot === undefined) {
|
|
822
|
-
el.toArray().forEach(createChildren);
|
|
823
|
-
} else {
|
|
824
|
-
Y.typeListToArraySnapshot(el, new Y.Snapshot(prevSnapshot.ds, snapshot.sv))
|
|
825
|
-
.forEach(createChildren);
|
|
826
|
-
}
|
|
827
|
-
try {
|
|
828
|
-
const attrs = el.getAttributes(snapshot);
|
|
829
|
-
if (snapshot !== undefined) {
|
|
830
|
-
if (!isVisible(/** @type {Y.Item} */ (el._item), snapshot)) {
|
|
831
|
-
attrs.ychange = computeYChange
|
|
832
|
-
? computeYChange('removed', /** @type {Y.Item} */ (el._item).id)
|
|
833
|
-
: { type: 'removed' };
|
|
834
|
-
} else if (!isVisible(/** @type {Y.Item} */ (el._item), prevSnapshot)) {
|
|
835
|
-
attrs.ychange = computeYChange
|
|
836
|
-
? computeYChange('added', /** @type {Y.Item} */ (el._item).id)
|
|
837
|
-
: { type: 'added' };
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
const node = schema.node(el.nodeName, attrs, children);
|
|
841
|
-
meta.mapping.set(el, node);
|
|
842
|
-
return node;
|
|
843
|
-
} catch (e) {
|
|
844
|
-
// an error occured while creating the node. This is probably a result of a concurrent action.
|
|
845
|
-
/** @type {Y.Doc} */ (el.doc).transact((transaction) => {
|
|
846
|
-
/** @type {Y.Item} */ (el._item).delete(transaction);
|
|
847
|
-
}, ySyncPluginKey);
|
|
848
|
-
meta.mapping.delete(el);
|
|
849
|
-
return null;
|
|
850
|
-
}
|
|
851
|
-
};
|
|
852
|
-
|
|
853
|
-
/**
|
|
854
|
-
* @private
|
|
855
|
-
*/
|
|
856
|
-
const createTextNodesFromYText = (
|
|
857
|
-
text: Y.XmlText,
|
|
858
|
-
schema: import('prosemirror-model').Schema,
|
|
859
|
-
_meta: BindingMetadata,
|
|
860
|
-
snapshot: Y.Snapshot,
|
|
861
|
-
prevSnapshot: Y.Snapshot,
|
|
862
|
-
computeYChange: (arg0: 'removed' | 'added', arg1: Y.ID) => any,
|
|
863
|
-
): Array<PModel.Node> | null => {
|
|
864
|
-
const nodes = [];
|
|
865
|
-
const deltas = text.toDelta(snapshot, prevSnapshot, computeYChange);
|
|
866
|
-
try {
|
|
867
|
-
for (let i = 0; i < deltas.length; i++) {
|
|
868
|
-
const delta = deltas[i];
|
|
869
|
-
nodes.push(
|
|
870
|
-
schema.text(delta.insert, attributesToMarks(delta.attributes, schema)),
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
} catch (e) {
|
|
874
|
-
// an error occured while creating the node. This is probably a result of a concurrent action.
|
|
875
|
-
/** @type {Y.Doc} */ (text.doc).transact((transaction) => {
|
|
876
|
-
/** @type {Y.Item} */ (text._item).delete(transaction);
|
|
877
|
-
}, ySyncPluginKey);
|
|
878
|
-
return null;
|
|
879
|
-
}
|
|
880
|
-
return nodes;
|
|
881
|
-
};
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* @private
|
|
885
|
-
*/
|
|
886
|
-
const createTypeFromTextNodes = (
|
|
887
|
-
nodes: Array<any>,
|
|
888
|
-
meta: BindingMetadata,
|
|
889
|
-
): Y.XmlText => {
|
|
890
|
-
const type = new Y.XmlText();
|
|
891
|
-
const delta = nodes.map((node) => ({
|
|
892
|
-
insert: node.text,
|
|
893
|
-
attributes: marksToAttributes(node.marks, meta),
|
|
894
|
-
}));
|
|
895
|
-
type.applyDelta(delta);
|
|
896
|
-
meta.mapping.set(type, nodes);
|
|
897
|
-
return type;
|
|
898
|
-
};
|
|
899
|
-
|
|
900
|
-
/**
|
|
901
|
-
* @private
|
|
902
|
-
*/
|
|
903
|
-
const createTypeFromElementNode = (
|
|
904
|
-
node: any,
|
|
905
|
-
meta: BindingMetadata,
|
|
906
|
-
): Y.XmlElement => {
|
|
907
|
-
const type = new Y.XmlElement(node.type.name);
|
|
908
|
-
for (const key in node.attrs) {
|
|
909
|
-
const val = node.attrs[key];
|
|
910
|
-
if (val !== null && key !== 'ychange') {
|
|
911
|
-
type.setAttribute(key, val);
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
type.insert(
|
|
915
|
-
0,
|
|
916
|
-
normalizePNodeContent(node).map((n) =>
|
|
917
|
-
createTypeFromTextOrElementNode(n, meta)
|
|
918
|
-
),
|
|
919
|
-
);
|
|
920
|
-
meta.mapping.set(type, node);
|
|
921
|
-
return type;
|
|
922
|
-
};
|
|
923
|
-
|
|
924
|
-
/**
|
|
925
|
-
* @private
|
|
926
|
-
*/
|
|
927
|
-
const createTypeFromTextOrElementNode = (
|
|
928
|
-
node: PModel.Node | Array<PModel.Node>,
|
|
929
|
-
meta: BindingMetadata,
|
|
930
|
-
): Y.XmlElement | Y.XmlText =>
|
|
931
|
-
node instanceof Array
|
|
932
|
-
? createTypeFromTextNodes(node, meta)
|
|
933
|
-
: createTypeFromElementNode(node, meta);
|
|
934
|
-
|
|
935
|
-
const isObject = (val: any) => typeof val === 'object' && val !== null;
|
|
936
|
-
|
|
937
|
-
const equalAttrs = (pattrs: any, yattrs: any) => {
|
|
938
|
-
const keys = Object.keys(pattrs).filter((key) => pattrs[key] !== null);
|
|
939
|
-
let eq = keys.length ===
|
|
940
|
-
(yattrs == null
|
|
941
|
-
? 0
|
|
942
|
-
: Object.keys(yattrs).filter((key) => yattrs[key] !== null).length);
|
|
943
|
-
for (let i = 0; i < keys.length && eq; i++) {
|
|
944
|
-
const key = keys[i];
|
|
945
|
-
const l = pattrs[key];
|
|
946
|
-
const r = yattrs[key];
|
|
947
|
-
eq = key === 'ychange' || l === r ||
|
|
948
|
-
(isObject(l) && isObject(r) && equalAttrs(l, r));
|
|
949
|
-
}
|
|
950
|
-
return eq;
|
|
951
|
-
};
|
|
952
|
-
|
|
953
|
-
type NormalizedPNodeContent = Array<Array<PModel.Node> | PModel.Node>;
|
|
954
|
-
|
|
955
|
-
const normalizePNodeContent = (pnode: any): NormalizedPNodeContent => {
|
|
956
|
-
const c = pnode.content.content;
|
|
957
|
-
const res = [];
|
|
958
|
-
for (let i = 0; i < c.length; i++) {
|
|
959
|
-
const n = c[i];
|
|
960
|
-
if (n.isText) {
|
|
961
|
-
const textNodes = [];
|
|
962
|
-
for (let tnode = c[i]; i < c.length && tnode.isText; tnode = c[++i]) {
|
|
963
|
-
textNodes.push(tnode);
|
|
964
|
-
}
|
|
965
|
-
i--;
|
|
966
|
-
res.push(textNodes);
|
|
967
|
-
} else {
|
|
968
|
-
res.push(n);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
return res;
|
|
972
|
-
};
|
|
973
|
-
|
|
974
|
-
const equalYTextPText = (ytext: Y.XmlText, ptexts: Array<any>) => {
|
|
975
|
-
const delta = ytext.toDelta();
|
|
976
|
-
return delta.length === ptexts.length &&
|
|
977
|
-
delta.every((d: any, i: number): boolean =>
|
|
978
|
-
d.insert === /** @type {any} */ (ptexts[i]).text &&
|
|
979
|
-
object.keys(d.attributes || {}).length === ptexts[i].marks.length &&
|
|
980
|
-
object.every(d.attributes, (attr, yattrname) => {
|
|
981
|
-
const markname = yattr2markname(yattrname);
|
|
982
|
-
const pmarks = ptexts[i].marks;
|
|
983
|
-
return equalAttrs(
|
|
984
|
-
attr,
|
|
985
|
-
pmarks.find((mark: Mark) => mark.type.name === markname)?.attrs,
|
|
986
|
-
);
|
|
987
|
-
})
|
|
988
|
-
);
|
|
989
|
-
};
|
|
990
|
-
|
|
991
|
-
const equalYTypePNode = (
|
|
992
|
-
ytype: Y.XmlElement | Y.XmlText | Y.XmlHook,
|
|
993
|
-
pnode: any | Array<any>,
|
|
994
|
-
) => {
|
|
995
|
-
if (
|
|
996
|
-
ytype instanceof Y.XmlElement && !(pnode instanceof Array) &&
|
|
997
|
-
matchNodeName(ytype, pnode)
|
|
998
|
-
) {
|
|
999
|
-
const normalizedContent = normalizePNodeContent(pnode);
|
|
1000
|
-
return ytype._length === normalizedContent.length &&
|
|
1001
|
-
equalAttrs(ytype.getAttributes(), pnode.attrs) &&
|
|
1002
|
-
ytype.toArray().every((ychild, i) =>
|
|
1003
|
-
equalYTypePNode(ychild, normalizedContent[i])
|
|
1004
|
-
);
|
|
1005
|
-
}
|
|
1006
|
-
return ytype instanceof Y.XmlText && pnode instanceof Array &&
|
|
1007
|
-
equalYTextPText(ytype, pnode);
|
|
1008
|
-
};
|
|
1009
|
-
|
|
1010
|
-
const mappedIdentity = (
|
|
1011
|
-
mapped: PModel.Node | Array<PModel.Node> | undefined,
|
|
1012
|
-
pcontent: PModel.Node | Array<PModel.Node>,
|
|
1013
|
-
) =>
|
|
1014
|
-
mapped === pcontent ||
|
|
1015
|
-
(mapped instanceof Array && pcontent instanceof Array &&
|
|
1016
|
-
mapped.length === pcontent.length &&
|
|
1017
|
-
mapped.every((a, i) => pcontent[i] === a));
|
|
1018
|
-
|
|
1019
|
-
const computeChildEqualityFactor = (
|
|
1020
|
-
ytype: Y.XmlElement,
|
|
1021
|
-
pnode: PModel.Node,
|
|
1022
|
-
meta: BindingMetadata,
|
|
1023
|
-
): { foundMappedChild: boolean; equalityFactor: number } => {
|
|
1024
|
-
const yChildren = ytype.toArray();
|
|
1025
|
-
const pChildren = normalizePNodeContent(pnode);
|
|
1026
|
-
const pChildCnt = pChildren.length;
|
|
1027
|
-
const yChildCnt = yChildren.length;
|
|
1028
|
-
const minCnt = math.min(yChildCnt, pChildCnt);
|
|
1029
|
-
let left = 0;
|
|
1030
|
-
let right = 0;
|
|
1031
|
-
let foundMappedChild = false;
|
|
1032
|
-
for (; left < minCnt; left++) {
|
|
1033
|
-
const leftY = yChildren[left];
|
|
1034
|
-
const leftP = pChildren[left];
|
|
1035
|
-
if (mappedIdentity(meta.mapping.get(leftY), leftP)) {
|
|
1036
|
-
foundMappedChild = true; // definite (good) match!
|
|
1037
|
-
} else if (!equalYTypePNode(leftY, leftP)) {
|
|
1038
|
-
break;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
for (; left + right < minCnt; right++) {
|
|
1042
|
-
const rightY = yChildren[yChildCnt - right - 1];
|
|
1043
|
-
const rightP = pChildren[pChildCnt - right - 1];
|
|
1044
|
-
if (mappedIdentity(meta.mapping.get(rightY), rightP)) {
|
|
1045
|
-
foundMappedChild = true;
|
|
1046
|
-
} else if (!equalYTypePNode(rightY, rightP)) {
|
|
1047
|
-
break;
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
return {
|
|
1051
|
-
equalityFactor: left + right,
|
|
1052
|
-
foundMappedChild,
|
|
1053
|
-
};
|
|
1054
|
-
};
|
|
1055
|
-
|
|
1056
|
-
const ytextTrans = (ytext: Y.Text) => {
|
|
1057
|
-
let str = '';
|
|
1058
|
-
let n: Y.Item | null = ytext._start;
|
|
1059
|
-
const nAttrs: Record<string, null> = {};
|
|
1060
|
-
while (n !== null) {
|
|
1061
|
-
if (!n.deleted) {
|
|
1062
|
-
if (n.countable && n.content instanceof Y.ContentString) {
|
|
1063
|
-
str += n.content.str;
|
|
1064
|
-
} else if (n.content instanceof Y.ContentFormat) {
|
|
1065
|
-
nAttrs[n.content.key] = null;
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
n = n.right;
|
|
1069
|
-
}
|
|
1070
|
-
return {
|
|
1071
|
-
str,
|
|
1072
|
-
nAttrs,
|
|
1073
|
-
};
|
|
1074
|
-
};
|
|
1075
|
-
|
|
1076
|
-
/**
|
|
1077
|
-
* @todo test this more
|
|
1078
|
-
*/
|
|
1079
|
-
const updateYText = (
|
|
1080
|
-
ytext: Y.Text,
|
|
1081
|
-
ptexts: Array<any>,
|
|
1082
|
-
meta: BindingMetadata,
|
|
1083
|
-
) => {
|
|
1084
|
-
meta.mapping.set(ytext, ptexts);
|
|
1085
|
-
const { nAttrs, str } = ytextTrans(ytext);
|
|
1086
|
-
const content = ptexts.map((p) => ({
|
|
1087
|
-
insert: /** @type {any} */ (p).text,
|
|
1088
|
-
attributes: Object.assign({}, nAttrs, marksToAttributes(p.marks, meta)),
|
|
1089
|
-
}));
|
|
1090
|
-
const { insert, remove, index } = simpleDiff(
|
|
1091
|
-
str,
|
|
1092
|
-
content.map((c) => c.insert).join(''),
|
|
1093
|
-
);
|
|
1094
|
-
ytext.delete(index, remove);
|
|
1095
|
-
ytext.insert(index, insert);
|
|
1096
|
-
ytext.applyDelta(
|
|
1097
|
-
content.map((c) => ({ retain: c.insert.length, attributes: c.attributes })),
|
|
1098
|
-
);
|
|
1099
|
-
};
|
|
1100
|
-
|
|
1101
|
-
const hashedMarkNameRegex = /(.*)(--[a-zA-Z0-9+/=]{8})$/;
|
|
1102
|
-
export const yattr2markname = (attrName: string) =>
|
|
1103
|
-
hashedMarkNameRegex.exec(attrName)?.[1] ?? attrName;
|
|
1104
|
-
|
|
1105
|
-
/**
|
|
1106
|
-
* @todo move this to markstoattributes
|
|
1107
|
-
*/
|
|
1108
|
-
export const attributesToMarks = (
|
|
1109
|
-
attrs: { [s: string]: any },
|
|
1110
|
-
schema: Schema,
|
|
1111
|
-
) => {
|
|
1112
|
-
const marks: Array<Mark> = [];
|
|
1113
|
-
for (const markName in attrs) {
|
|
1114
|
-
// remove hashes if necessary
|
|
1115
|
-
marks.push(schema.mark(yattr2markname(markName), attrs[markName]));
|
|
1116
|
-
}
|
|
1117
|
-
return marks;
|
|
1118
|
-
};
|
|
1119
|
-
|
|
1120
|
-
const marksToAttributes = (
|
|
1121
|
-
marks: Array<Mark>,
|
|
1122
|
-
meta: BindingMetadata,
|
|
1123
|
-
): Record<string, PModel.Attrs> => {
|
|
1124
|
-
const pattrs: Record<string, PModel.Attrs> = {};
|
|
1125
|
-
marks.forEach((mark) => {
|
|
1126
|
-
if (mark.type.name !== 'ychange') {
|
|
1127
|
-
const isOverlapping = map.setIfUndefined(
|
|
1128
|
-
meta.isOMark,
|
|
1129
|
-
mark.type,
|
|
1130
|
-
() => !mark.type.excludes(mark.type),
|
|
1131
|
-
);
|
|
1132
|
-
pattrs[
|
|
1133
|
-
isOverlapping
|
|
1134
|
-
? `${mark.type.name}--${utils.hashOfJSON(mark.toJSON())}`
|
|
1135
|
-
: mark.type.name
|
|
1136
|
-
] = mark.attrs;
|
|
1137
|
-
}
|
|
1138
|
-
});
|
|
1139
|
-
return pattrs;
|
|
1140
|
-
};
|
|
1141
|
-
|
|
1142
|
-
/**
|
|
1143
|
-
* Update a yDom node by syncing the current content of the prosemirror node.
|
|
1144
|
-
*
|
|
1145
|
-
* This is a y-prosemirror internal feature that you can use at your own risk.
|
|
1146
|
-
*
|
|
1147
|
-
* @private
|
|
1148
|
-
* @unstable
|
|
1149
|
-
*/
|
|
1150
|
-
export const updateYFragment = (
|
|
1151
|
-
y: { transact: TransactFunc<void> },
|
|
1152
|
-
yDomFragment: Y.XmlFragment,
|
|
1153
|
-
pNode: Node,
|
|
1154
|
-
meta: BindingMetadata,
|
|
1155
|
-
) => {
|
|
1156
|
-
if (
|
|
1157
|
-
yDomFragment instanceof Y.XmlElement &&
|
|
1158
|
-
yDomFragment.nodeName !== pNode.type.name
|
|
1159
|
-
) {
|
|
1160
|
-
throw new Error('node name mismatch!');
|
|
1161
|
-
}
|
|
1162
|
-
meta.mapping.set(yDomFragment, pNode);
|
|
1163
|
-
// update attributes
|
|
1164
|
-
if (yDomFragment instanceof Y.XmlElement) {
|
|
1165
|
-
const yDomAttrs = yDomFragment.getAttributes();
|
|
1166
|
-
const pAttrs = pNode.attrs;
|
|
1167
|
-
for (const key in pAttrs) {
|
|
1168
|
-
if (pAttrs[key] !== null) {
|
|
1169
|
-
if (yDomAttrs[key] !== pAttrs[key] && key !== 'ychange') {
|
|
1170
|
-
yDomFragment.setAttribute(key, pAttrs[key]);
|
|
1171
|
-
}
|
|
1172
|
-
} else {
|
|
1173
|
-
yDomFragment.removeAttribute(key);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
// remove all keys that are no longer in pAttrs
|
|
1177
|
-
for (const key in yDomAttrs) {
|
|
1178
|
-
if (pAttrs[key] === undefined) {
|
|
1179
|
-
yDomFragment.removeAttribute(key);
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
// update children
|
|
1184
|
-
const pChildren = normalizePNodeContent(pNode);
|
|
1185
|
-
const pChildCnt = pChildren.length;
|
|
1186
|
-
const yChildren = yDomFragment.toArray();
|
|
1187
|
-
const yChildCnt = yChildren.length;
|
|
1188
|
-
const minCnt = math.min(pChildCnt, yChildCnt);
|
|
1189
|
-
let left = 0;
|
|
1190
|
-
let right = 0;
|
|
1191
|
-
// find number of matching elements from left
|
|
1192
|
-
for (; left < minCnt; left++) {
|
|
1193
|
-
const leftY = yChildren[left];
|
|
1194
|
-
const leftP = pChildren[left];
|
|
1195
|
-
if (!mappedIdentity(meta.mapping.get(leftY), leftP)) {
|
|
1196
|
-
if (equalYTypePNode(leftY, leftP)) {
|
|
1197
|
-
// update mapping
|
|
1198
|
-
meta.mapping.set(leftY, leftP);
|
|
1199
|
-
} else {
|
|
1200
|
-
break;
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
// find number of matching elements from right
|
|
1205
|
-
for (; right + left < minCnt; right++) {
|
|
1206
|
-
const rightY = yChildren[yChildCnt - right - 1];
|
|
1207
|
-
const rightP = pChildren[pChildCnt - right - 1];
|
|
1208
|
-
if (!mappedIdentity(meta.mapping.get(rightY), rightP)) {
|
|
1209
|
-
if (equalYTypePNode(rightY, rightP)) {
|
|
1210
|
-
// update mapping
|
|
1211
|
-
meta.mapping.set(rightY, rightP);
|
|
1212
|
-
} else {
|
|
1213
|
-
break;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
y.transact(() => {
|
|
1218
|
-
// try to compare and update
|
|
1219
|
-
while (yChildCnt - left - right > 0 && pChildCnt - left - right > 0) {
|
|
1220
|
-
const leftY: Y.XmlElement = yChildren[left];
|
|
1221
|
-
const leftP: PModel.Node = pChildren[left];
|
|
1222
|
-
const rightY: Y.XmlElement = yChildren[yChildCnt - right - 1];
|
|
1223
|
-
const rightP: PModel.Node = pChildren[pChildCnt - right - 1];
|
|
1224
|
-
if (leftY instanceof Y.XmlText && leftP instanceof Array) {
|
|
1225
|
-
if (!equalYTextPText(leftY, leftP)) {
|
|
1226
|
-
updateYText(leftY, leftP, meta);
|
|
1227
|
-
}
|
|
1228
|
-
left += 1;
|
|
1229
|
-
} else {
|
|
1230
|
-
let updateLeft = leftY instanceof Y.XmlElement &&
|
|
1231
|
-
matchNodeName(leftY, leftP);
|
|
1232
|
-
let updateRight = rightY instanceof Y.XmlElement &&
|
|
1233
|
-
matchNodeName(rightY, rightP);
|
|
1234
|
-
if (updateLeft && updateRight) {
|
|
1235
|
-
// decide which element to update
|
|
1236
|
-
const equalityLeft = computeChildEqualityFactor(
|
|
1237
|
-
leftY,
|
|
1238
|
-
leftP,
|
|
1239
|
-
meta,
|
|
1240
|
-
);
|
|
1241
|
-
const equalityRight = computeChildEqualityFactor(
|
|
1242
|
-
rightY,
|
|
1243
|
-
rightP,
|
|
1244
|
-
meta,
|
|
1245
|
-
);
|
|
1246
|
-
if (
|
|
1247
|
-
equalityLeft.foundMappedChild && !equalityRight.foundMappedChild
|
|
1248
|
-
) {
|
|
1249
|
-
updateRight = false;
|
|
1250
|
-
} else if (
|
|
1251
|
-
!equalityLeft.foundMappedChild && equalityRight.foundMappedChild
|
|
1252
|
-
) {
|
|
1253
|
-
updateLeft = false;
|
|
1254
|
-
} else if (
|
|
1255
|
-
equalityLeft.equalityFactor < equalityRight.equalityFactor
|
|
1256
|
-
) {
|
|
1257
|
-
updateLeft = false;
|
|
1258
|
-
} else {
|
|
1259
|
-
updateRight = false;
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
if (updateLeft) {
|
|
1263
|
-
updateYFragment(
|
|
1264
|
-
y,
|
|
1265
|
-
/** @type {Y.XmlFragment} */ (leftY),
|
|
1266
|
-
/** @type {PModel.Node} */ (leftP),
|
|
1267
|
-
meta,
|
|
1268
|
-
);
|
|
1269
|
-
left += 1;
|
|
1270
|
-
} else if (updateRight) {
|
|
1271
|
-
updateYFragment(
|
|
1272
|
-
y,
|
|
1273
|
-
/** @type {Y.XmlFragment} */ (rightY),
|
|
1274
|
-
/** @type {PModel.Node} */ (rightP),
|
|
1275
|
-
meta,
|
|
1276
|
-
);
|
|
1277
|
-
right += 1;
|
|
1278
|
-
} else {
|
|
1279
|
-
meta.mapping.delete(yDomFragment.get(left));
|
|
1280
|
-
yDomFragment.delete(left, 1);
|
|
1281
|
-
yDomFragment.insert(left, [
|
|
1282
|
-
createTypeFromTextOrElementNode(leftP, meta),
|
|
1283
|
-
]);
|
|
1284
|
-
left += 1;
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
const yDelLen = yChildCnt - left - right;
|
|
1289
|
-
if (
|
|
1290
|
-
yChildCnt === 1 && pChildCnt === 0 && yChildren[0] instanceof Y.XmlText
|
|
1291
|
-
) {
|
|
1292
|
-
meta.mapping.delete(yChildren[0]);
|
|
1293
|
-
// Edge case handling https://github.com/yjs/y-prosemirror/issues/108
|
|
1294
|
-
// Only delete the content of the Y.Text to retain remote changes on the same Y.Text object
|
|
1295
|
-
yChildren[0].delete(0, yChildren[0].length);
|
|
1296
|
-
} else if (yDelLen > 0) {
|
|
1297
|
-
yDomFragment.slice(left, left + yDelLen).forEach((type) =>
|
|
1298
|
-
meta.mapping.delete(type)
|
|
1299
|
-
);
|
|
1300
|
-
yDomFragment.delete(left, yDelLen);
|
|
1301
|
-
}
|
|
1302
|
-
if (left + right < pChildCnt) {
|
|
1303
|
-
const ins = [];
|
|
1304
|
-
for (let i = left; i < pChildCnt - right; i++) {
|
|
1305
|
-
ins.push(createTypeFromTextOrElementNode(pChildren[i], meta));
|
|
1306
|
-
}
|
|
1307
|
-
yDomFragment.insert(left, ins);
|
|
1308
|
-
}
|
|
1309
|
-
}, ySyncPluginKey);
|
|
1310
|
-
};
|
|
1311
|
-
|
|
1312
|
-
const matchNodeName = (yElement: Y.XmlElement, pNode: any) =>
|
|
1313
|
-
!(pNode instanceof Array) && yElement.nodeName === pNode.type.name;
|