@lexical/yjs 0.36.3-nightly.20251003.0 → 0.36.3-nightly.20251007.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.
- package/CollabV2Mapping.d.ts +1 -0
- package/LexicalYjs.dev.js +1544 -1458
- package/LexicalYjs.dev.mjs +1542 -1461
- package/LexicalYjs.mjs +6 -1
- package/LexicalYjs.node.mjs +6 -1
- package/LexicalYjs.prod.js +1 -1
- package/LexicalYjs.prod.mjs +1 -1
- package/RenderSnapshot.d.ts +25 -0
- package/SyncEditorStates.d.ts +1 -0
- package/index.d.ts +8 -2
- package/package.json +4 -4
package/LexicalYjs.dev.mjs
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { $getNodeByKey, $isLineBreakNode, $isTextNode, $getSelection, $isRangeSelection,
|
|
10
|
-
import { XmlText, Map as Map$1,
|
|
9
|
+
import { $isDecoratorNode, $getNodeByKey, $isLineBreakNode, $isTextNode, $getSelection, $isRangeSelection, $isElementNode, $getNodeByKeyOrThrow, removeFromParent, createEditor, $isRootNode, $getWritableNodeState, $getRoot, RootNode, ElementNode, TextNode, $getState, createState, COLLABORATION_TAG, HISTORIC_TAG, $addUpdateTag, SKIP_SCROLL_INTO_VIEW_TAG, $createParagraphNode, createCommand } from 'lexical';
|
|
10
|
+
import { XmlText, XmlElement, Map as Map$1, Doc, typeListToArraySnapshot, Snapshot, isDeleted, XmlHook, ContentString, ContentFormat, snapshot, emptySnapshot, PermanentUserData, iterateDeletedStructs, createAbsolutePositionFromRelativePosition, createRelativePositionFromTypeIndex, compareRelativePositions, Item, YMapEvent, YTextEvent, YXmlEvent, UndoManager } from 'yjs';
|
|
11
11
|
import { $createChildrenArray } from '@lexical/offset';
|
|
12
12
|
import { createDOMRange, createRectsFromDOMRange } from '@lexical/selection';
|
|
13
13
|
|
|
@@ -33,30 +33,62 @@ function formatDevErrorMessage(message) {
|
|
|
33
33
|
*
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
function simpleDiffWithCursor(a, b, cursor) {
|
|
37
|
+
const aLength = a.length;
|
|
38
|
+
const bLength = b.length;
|
|
39
|
+
let left = 0; // number of same characters counting from left
|
|
40
|
+
let right = 0; // number of same characters counting from right
|
|
41
|
+
// Iterate left to the right until we find a changed character
|
|
42
|
+
// First iteration considers the current cursor position
|
|
43
|
+
while (left < aLength && left < bLength && a[left] === b[left] && left < cursor) {
|
|
44
|
+
left++;
|
|
45
|
+
}
|
|
46
|
+
// Iterate right to the left until we find a changed character
|
|
47
|
+
while (right + left < aLength && right + left < bLength && a[aLength - right - 1] === b[bLength - right - 1]) {
|
|
48
|
+
right++;
|
|
49
|
+
}
|
|
50
|
+
// Try to iterate left further to the right without caring about the current cursor position
|
|
51
|
+
while (right + left < aLength && right + left < bLength && a[left] === b[left]) {
|
|
52
|
+
left++;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
index: left,
|
|
56
|
+
insert: b.slice(left, bLength - right),
|
|
57
|
+
remove: aLength - left - right
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class CollabDecoratorNode {
|
|
62
|
+
_xmlElem;
|
|
38
63
|
_key;
|
|
39
64
|
_parent;
|
|
40
65
|
_type;
|
|
41
|
-
constructor(
|
|
66
|
+
constructor(xmlElem, parent, type) {
|
|
42
67
|
this._key = '';
|
|
43
|
-
this.
|
|
68
|
+
this._xmlElem = xmlElem;
|
|
44
69
|
this._parent = parent;
|
|
45
|
-
this._type =
|
|
70
|
+
this._type = type;
|
|
71
|
+
}
|
|
72
|
+
getPrevNode(nodeMap) {
|
|
73
|
+
if (nodeMap === null) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const node = nodeMap.get(this._key);
|
|
77
|
+
return $isDecoratorNode(node) ? node : null;
|
|
46
78
|
}
|
|
47
79
|
getNode() {
|
|
48
80
|
const node = $getNodeByKey(this._key);
|
|
49
|
-
return $
|
|
50
|
-
}
|
|
51
|
-
getKey() {
|
|
52
|
-
return this._key;
|
|
81
|
+
return $isDecoratorNode(node) ? node : null;
|
|
53
82
|
}
|
|
54
83
|
getSharedType() {
|
|
55
|
-
return this.
|
|
84
|
+
return this._xmlElem;
|
|
56
85
|
}
|
|
57
86
|
getType() {
|
|
58
87
|
return this._type;
|
|
59
88
|
}
|
|
89
|
+
getKey() {
|
|
90
|
+
return this._key;
|
|
91
|
+
}
|
|
60
92
|
getSize() {
|
|
61
93
|
return 1;
|
|
62
94
|
}
|
|
@@ -64,6 +96,19 @@ class CollabLineBreakNode {
|
|
|
64
96
|
const collabElementNode = this._parent;
|
|
65
97
|
return collabElementNode.getChildOffset(this);
|
|
66
98
|
}
|
|
99
|
+
syncPropertiesFromLexical(binding, nextLexicalNode, prevNodeMap) {
|
|
100
|
+
const prevLexicalNode = this.getPrevNode(prevNodeMap);
|
|
101
|
+
const xmlElem = this._xmlElem;
|
|
102
|
+
syncPropertiesFromLexical(binding, xmlElem, prevLexicalNode, nextLexicalNode);
|
|
103
|
+
}
|
|
104
|
+
syncPropertiesFromYjs(binding, keysChanged) {
|
|
105
|
+
const lexicalNode = this.getNode();
|
|
106
|
+
if (!(lexicalNode !== null)) {
|
|
107
|
+
formatDevErrorMessage(`syncPropertiesFromYjs: could not find decorator node`);
|
|
108
|
+
}
|
|
109
|
+
const xmlElem = this._xmlElem;
|
|
110
|
+
$syncPropertiesFromYjs(binding, xmlElem, lexicalNode, keysChanged);
|
|
111
|
+
}
|
|
67
112
|
destroy(binding) {
|
|
68
113
|
const collabNodeMap = binding.collabNodeMap;
|
|
69
114
|
if (collabNodeMap.get(this._key) === this) {
|
|
@@ -71,9 +116,9 @@ class CollabLineBreakNode {
|
|
|
71
116
|
}
|
|
72
117
|
}
|
|
73
118
|
}
|
|
74
|
-
function $
|
|
75
|
-
const collabNode = new
|
|
76
|
-
|
|
119
|
+
function $createCollabDecoratorNode(xmlElem, parent, type) {
|
|
120
|
+
const collabNode = new CollabDecoratorNode(xmlElem, parent, type);
|
|
121
|
+
xmlElem._collabNode = collabNode;
|
|
77
122
|
return collabNode;
|
|
78
123
|
}
|
|
79
124
|
|
|
@@ -85,29 +130,48 @@ function $createCollabLineBreakNode(map, parent) {
|
|
|
85
130
|
*
|
|
86
131
|
*/
|
|
87
132
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
133
|
+
class CollabLineBreakNode {
|
|
134
|
+
_map;
|
|
135
|
+
_key;
|
|
136
|
+
_parent;
|
|
137
|
+
_type;
|
|
138
|
+
constructor(map, parent) {
|
|
139
|
+
this._key = '';
|
|
140
|
+
this._map = map;
|
|
141
|
+
this._parent = parent;
|
|
142
|
+
this._type = 'linebreak';
|
|
97
143
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
144
|
+
getNode() {
|
|
145
|
+
const node = $getNodeByKey(this._key);
|
|
146
|
+
return $isLineBreakNode(node) ? node : null;
|
|
101
147
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
left++;
|
|
148
|
+
getKey() {
|
|
149
|
+
return this._key;
|
|
105
150
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
151
|
+
getSharedType() {
|
|
152
|
+
return this._map;
|
|
153
|
+
}
|
|
154
|
+
getType() {
|
|
155
|
+
return this._type;
|
|
156
|
+
}
|
|
157
|
+
getSize() {
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
getOffset() {
|
|
161
|
+
const collabElementNode = this._parent;
|
|
162
|
+
return collabElementNode.getChildOffset(this);
|
|
163
|
+
}
|
|
164
|
+
destroy(binding) {
|
|
165
|
+
const collabNodeMap = binding.collabNodeMap;
|
|
166
|
+
if (collabNodeMap.get(this._key) === this) {
|
|
167
|
+
collabNodeMap.delete(this._key);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function $createCollabLineBreakNode(map, parent) {
|
|
172
|
+
const collabNode = new CollabLineBreakNode(map, parent);
|
|
173
|
+
map._collabNode = collabNode;
|
|
174
|
+
return collabNode;
|
|
111
175
|
}
|
|
112
176
|
|
|
113
177
|
function $diffTextContentAndApplyDelta(collabNode, key, prevText, nextText) {
|
|
@@ -212,485 +276,49 @@ function $createCollabTextNode(map, text, parent, type) {
|
|
|
212
276
|
return collabNode;
|
|
213
277
|
}
|
|
214
278
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
279
|
+
class CollabElementNode {
|
|
280
|
+
_key;
|
|
281
|
+
_children;
|
|
282
|
+
_xmlText;
|
|
283
|
+
_type;
|
|
284
|
+
_parent;
|
|
285
|
+
constructor(xmlText, parent, type) {
|
|
286
|
+
this._key = '';
|
|
287
|
+
this._children = [];
|
|
288
|
+
this._xmlText = xmlText;
|
|
289
|
+
this._type = type;
|
|
290
|
+
this._parent = parent;
|
|
222
291
|
}
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
return
|
|
226
|
-
}
|
|
227
|
-
} else if ($isElementNode(node)) {
|
|
228
|
-
if (elementExcludedProperties.has(name) || $isRootNode(node) && rootExcludedProperties.has(name)) {
|
|
229
|
-
return true;
|
|
292
|
+
getPrevNode(nodeMap) {
|
|
293
|
+
if (nodeMap === null) {
|
|
294
|
+
return null;
|
|
230
295
|
}
|
|
296
|
+
const node = nodeMap.get(this._key);
|
|
297
|
+
return $isElementNode(node) ? node : null;
|
|
231
298
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
function initializeNodeProperties(binding) {
|
|
237
|
-
const {
|
|
238
|
-
editor,
|
|
239
|
-
nodeProperties
|
|
240
|
-
} = binding;
|
|
241
|
-
editor.update(() => {
|
|
242
|
-
editor._nodes.forEach(nodeInfo => {
|
|
243
|
-
const node = new nodeInfo.klass();
|
|
244
|
-
const defaultProperties = {};
|
|
245
|
-
for (const [property, value] of Object.entries(node)) {
|
|
246
|
-
if (!isExcludedProperty(property, node, binding)) {
|
|
247
|
-
defaultProperties[property] = value;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
nodeProperties.set(node.__type, Object.freeze(defaultProperties));
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
function getDefaultNodeProperties(node, binding) {
|
|
255
|
-
const type = node.__type;
|
|
256
|
-
const {
|
|
257
|
-
nodeProperties
|
|
258
|
-
} = binding;
|
|
259
|
-
const properties = nodeProperties.get(type);
|
|
260
|
-
if (!(properties !== undefined)) {
|
|
261
|
-
formatDevErrorMessage(`Node properties for ${type} not initialized for sync`);
|
|
299
|
+
getNode() {
|
|
300
|
+
const node = $getNodeByKey(this._key);
|
|
301
|
+
return $isElementNode(node) ? node : null;
|
|
262
302
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
function $createCollabNodeFromLexicalNode(binding, lexicalNode, parent) {
|
|
266
|
-
const nodeType = lexicalNode.__type;
|
|
267
|
-
let collabNode;
|
|
268
|
-
if ($isElementNode(lexicalNode)) {
|
|
269
|
-
const xmlText = new XmlText();
|
|
270
|
-
collabNode = $createCollabElementNode(xmlText, parent, nodeType);
|
|
271
|
-
collabNode.syncPropertiesFromLexical(binding, lexicalNode, null);
|
|
272
|
-
collabNode.syncChildrenFromLexical(binding, lexicalNode, null, null, null);
|
|
273
|
-
} else if ($isTextNode(lexicalNode)) {
|
|
274
|
-
// TODO create a token text node for token, segmented nodes.
|
|
275
|
-
const map = new Map$1();
|
|
276
|
-
collabNode = $createCollabTextNode(map, lexicalNode.__text, parent, nodeType);
|
|
277
|
-
collabNode.syncPropertiesAndTextFromLexical(binding, lexicalNode, null);
|
|
278
|
-
} else if ($isLineBreakNode(lexicalNode)) {
|
|
279
|
-
const map = new Map$1();
|
|
280
|
-
map.set('__type', 'linebreak');
|
|
281
|
-
collabNode = $createCollabLineBreakNode(map, parent);
|
|
282
|
-
} else if ($isDecoratorNode(lexicalNode)) {
|
|
283
|
-
const xmlElem = new XmlElement();
|
|
284
|
-
collabNode = $createCollabDecoratorNode(xmlElem, parent, nodeType);
|
|
285
|
-
collabNode.syncPropertiesFromLexical(binding, lexicalNode, null);
|
|
286
|
-
} else {
|
|
287
|
-
{
|
|
288
|
-
formatDevErrorMessage(`Expected text, element, decorator, or linebreak node`);
|
|
289
|
-
}
|
|
303
|
+
getSharedType() {
|
|
304
|
+
return this._xmlText;
|
|
290
305
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
function getNodeTypeFromSharedType(sharedType) {
|
|
295
|
-
const type = sharedTypeGet(sharedType, '__type');
|
|
296
|
-
if (!(typeof type === 'string' || typeof type === 'undefined')) {
|
|
297
|
-
formatDevErrorMessage(`Expected shared type to include type attribute`);
|
|
306
|
+
getType() {
|
|
307
|
+
return this._type;
|
|
298
308
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const
|
|
310
|
-
if (!(
|
|
311
|
-
formatDevErrorMessage(`
|
|
312
|
-
}
|
|
313
|
-
const sharedParent = sharedType.parent;
|
|
314
|
-
const targetParent = parent === undefined && sharedParent !== null ? $getOrInitCollabNodeFromSharedType(binding, sharedParent) : parent || null;
|
|
315
|
-
if (!(targetParent instanceof CollabElementNode)) {
|
|
316
|
-
formatDevErrorMessage(`Expected parent to be a collab element node`);
|
|
317
|
-
}
|
|
318
|
-
if (sharedType instanceof XmlText) {
|
|
319
|
-
return $createCollabElementNode(sharedType, targetParent, type);
|
|
320
|
-
} else if (sharedType instanceof Map$1) {
|
|
321
|
-
if (type === 'linebreak') {
|
|
322
|
-
return $createCollabLineBreakNode(sharedType, targetParent);
|
|
323
|
-
}
|
|
324
|
-
return $createCollabTextNode(sharedType, '', targetParent, type);
|
|
325
|
-
} else if (sharedType instanceof XmlElement) {
|
|
326
|
-
return $createCollabDecoratorNode(sharedType, targetParent, type);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
return collabNode;
|
|
330
|
-
}
|
|
331
|
-
function createLexicalNodeFromCollabNode(binding, collabNode, parentKey) {
|
|
332
|
-
const type = collabNode.getType();
|
|
333
|
-
const registeredNodes = binding.editor._nodes;
|
|
334
|
-
const nodeInfo = registeredNodes.get(type);
|
|
335
|
-
if (!(nodeInfo !== undefined)) {
|
|
336
|
-
formatDevErrorMessage(`Node ${type} is not registered`);
|
|
337
|
-
}
|
|
338
|
-
const lexicalNode = new nodeInfo.klass();
|
|
339
|
-
lexicalNode.__parent = parentKey;
|
|
340
|
-
collabNode._key = lexicalNode.__key;
|
|
341
|
-
if (collabNode instanceof CollabElementNode) {
|
|
342
|
-
const xmlText = collabNode._xmlText;
|
|
343
|
-
collabNode.syncPropertiesFromYjs(binding, null);
|
|
344
|
-
collabNode.applyChildrenYjsDelta(binding, xmlText.toDelta());
|
|
345
|
-
collabNode.syncChildrenFromYjs(binding);
|
|
346
|
-
} else if (collabNode instanceof CollabTextNode) {
|
|
347
|
-
collabNode.syncPropertiesAndTextFromYjs(binding, null);
|
|
348
|
-
} else if (collabNode instanceof CollabDecoratorNode) {
|
|
349
|
-
collabNode.syncPropertiesFromYjs(binding, null);
|
|
350
|
-
}
|
|
351
|
-
binding.collabNodeMap.set(lexicalNode.__key, collabNode);
|
|
352
|
-
return lexicalNode;
|
|
353
|
-
}
|
|
354
|
-
function $syncPropertiesFromYjs(binding, sharedType, lexicalNode, keysChanged) {
|
|
355
|
-
const properties = keysChanged === null ? sharedType instanceof Map$1 ? Array.from(sharedType.keys()) : sharedType instanceof XmlText || sharedType instanceof XmlElement ? Object.keys(sharedType.getAttributes()) : Object.keys(sharedType) : Array.from(keysChanged);
|
|
356
|
-
let writableNode;
|
|
357
|
-
for (let i = 0; i < properties.length; i++) {
|
|
358
|
-
const property = properties[i];
|
|
359
|
-
if (isExcludedProperty(property, lexicalNode, binding)) {
|
|
360
|
-
if (property === '__state' && isBindingV1(binding)) {
|
|
361
|
-
if (!writableNode) {
|
|
362
|
-
writableNode = lexicalNode.getWritable();
|
|
363
|
-
}
|
|
364
|
-
$syncNodeStateToLexical(sharedType, writableNode);
|
|
365
|
-
}
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
369
|
-
const prevValue = lexicalNode[property];
|
|
370
|
-
let nextValue = sharedTypeGet(sharedType, property);
|
|
371
|
-
if (prevValue !== nextValue) {
|
|
372
|
-
if (nextValue instanceof Doc) {
|
|
373
|
-
const yjsDocMap = binding.docMap;
|
|
374
|
-
if (prevValue instanceof Doc) {
|
|
375
|
-
yjsDocMap.delete(prevValue.guid);
|
|
376
|
-
}
|
|
377
|
-
const nestedEditor = createEditor();
|
|
378
|
-
const key = nextValue.guid;
|
|
379
|
-
nestedEditor._key = key;
|
|
380
|
-
yjsDocMap.set(key, nextValue);
|
|
381
|
-
nextValue = nestedEditor;
|
|
382
|
-
}
|
|
383
|
-
if (writableNode === undefined) {
|
|
384
|
-
writableNode = lexicalNode.getWritable();
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
388
|
-
writableNode[property] = nextValue;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
function sharedTypeGet(sharedType, property) {
|
|
393
|
-
if (sharedType instanceof Map$1) {
|
|
394
|
-
return sharedType.get(property);
|
|
395
|
-
} else if (sharedType instanceof XmlText || sharedType instanceof XmlElement) {
|
|
396
|
-
return sharedType.getAttribute(property);
|
|
397
|
-
} else {
|
|
398
|
-
return sharedType[property];
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
function sharedTypeSet(sharedType, property, nextValue) {
|
|
402
|
-
if (sharedType instanceof Map$1) {
|
|
403
|
-
sharedType.set(property, nextValue);
|
|
404
|
-
} else {
|
|
405
|
-
sharedType.setAttribute(property, nextValue);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
function $syncNodeStateToLexical(sharedType, lexicalNode) {
|
|
409
|
-
const existingState = sharedTypeGet(sharedType, '__state');
|
|
410
|
-
if (!(existingState instanceof Map$1)) {
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
// This should only called when creating the node initially,
|
|
414
|
-
// incremental updates to state come in through YMapEvent
|
|
415
|
-
// with the __state as the target.
|
|
416
|
-
$getWritableNodeState(lexicalNode).updateFromJSON(existingState.toJSON());
|
|
417
|
-
}
|
|
418
|
-
function syncNodeStateFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode) {
|
|
419
|
-
const nextState = nextLexicalNode.__state;
|
|
420
|
-
const existingState = sharedTypeGet(sharedType, '__state');
|
|
421
|
-
if (!nextState) {
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
const [unknown, known] = nextState.getInternalState();
|
|
425
|
-
const prevState = prevLexicalNode && prevLexicalNode.__state;
|
|
426
|
-
const stateMap = existingState instanceof Map$1 ? existingState : new Map$1();
|
|
427
|
-
if (prevState === nextState) {
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
const [prevUnknown, prevKnown] = prevState && stateMap.doc ? prevState.getInternalState() : [undefined, new Map()];
|
|
431
|
-
if (unknown) {
|
|
432
|
-
for (const [k, v] of Object.entries(unknown)) {
|
|
433
|
-
if (prevUnknown && v !== prevUnknown[k]) {
|
|
434
|
-
stateMap.set(k, v);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
for (const [stateConfig, v] of known) {
|
|
439
|
-
if (prevKnown.get(stateConfig) !== v) {
|
|
440
|
-
stateMap.set(stateConfig.key, stateConfig.unparse(v));
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
if (!existingState) {
|
|
444
|
-
sharedTypeSet(sharedType, '__state', stateMap);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
function syncPropertiesFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode) {
|
|
448
|
-
const properties = Object.keys(getDefaultNodeProperties(nextLexicalNode, binding));
|
|
449
|
-
const EditorClass = binding.editor.constructor;
|
|
450
|
-
syncNodeStateFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode);
|
|
451
|
-
for (let i = 0; i < properties.length; i++) {
|
|
452
|
-
const property = properties[i];
|
|
453
|
-
const prevValue =
|
|
454
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
455
|
-
prevLexicalNode === null ? undefined : prevLexicalNode[property];
|
|
456
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
457
|
-
let nextValue = nextLexicalNode[property];
|
|
458
|
-
if (prevValue !== nextValue) {
|
|
459
|
-
if (nextValue instanceof EditorClass) {
|
|
460
|
-
const yjsDocMap = binding.docMap;
|
|
461
|
-
let prevDoc;
|
|
462
|
-
if (prevValue instanceof EditorClass) {
|
|
463
|
-
const prevKey = prevValue._key;
|
|
464
|
-
prevDoc = yjsDocMap.get(prevKey);
|
|
465
|
-
yjsDocMap.delete(prevKey);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// If we already have a document, use it.
|
|
469
|
-
const doc = prevDoc || new Doc();
|
|
470
|
-
const key = doc.guid;
|
|
471
|
-
nextValue._key = key;
|
|
472
|
-
yjsDocMap.set(key, doc);
|
|
473
|
-
nextValue = doc;
|
|
474
|
-
// Mark the node dirty as we've assigned a new key to it
|
|
475
|
-
binding.editor.update(() => {
|
|
476
|
-
nextLexicalNode.markDirty();
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
sharedTypeSet(sharedType, property, nextValue);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
function spliceString(str, index, delCount, newText) {
|
|
484
|
-
return str.slice(0, index) + newText + str.slice(index + delCount);
|
|
485
|
-
}
|
|
486
|
-
function getPositionFromElementAndOffset(node, offset, boundaryIsEdge) {
|
|
487
|
-
let index = 0;
|
|
488
|
-
let i = 0;
|
|
489
|
-
const children = node._children;
|
|
490
|
-
const childrenLength = children.length;
|
|
491
|
-
for (; i < childrenLength; i++) {
|
|
492
|
-
const child = children[i];
|
|
493
|
-
const childOffset = index;
|
|
494
|
-
const size = child.getSize();
|
|
495
|
-
index += size;
|
|
496
|
-
const exceedsBoundary = boundaryIsEdge ? index >= offset : index > offset;
|
|
497
|
-
if (exceedsBoundary && child instanceof CollabTextNode) {
|
|
498
|
-
let textOffset = offset - childOffset - 1;
|
|
499
|
-
if (textOffset < 0) {
|
|
500
|
-
textOffset = 0;
|
|
501
|
-
}
|
|
502
|
-
const diffLength = index - offset;
|
|
503
|
-
return {
|
|
504
|
-
length: diffLength,
|
|
505
|
-
node: child,
|
|
506
|
-
nodeIndex: i,
|
|
507
|
-
offset: textOffset
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
if (index > offset) {
|
|
511
|
-
return {
|
|
512
|
-
length: 0,
|
|
513
|
-
node: child,
|
|
514
|
-
nodeIndex: i,
|
|
515
|
-
offset: childOffset
|
|
516
|
-
};
|
|
517
|
-
} else if (i === childrenLength - 1) {
|
|
518
|
-
return {
|
|
519
|
-
length: 0,
|
|
520
|
-
node: null,
|
|
521
|
-
nodeIndex: i + 1,
|
|
522
|
-
offset: childOffset + 1
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
return {
|
|
527
|
-
length: 0,
|
|
528
|
-
node: null,
|
|
529
|
-
nodeIndex: 0,
|
|
530
|
-
offset: 0
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
function doesSelectionNeedRecovering(selection) {
|
|
534
|
-
const anchor = selection.anchor;
|
|
535
|
-
const focus = selection.focus;
|
|
536
|
-
let recoveryNeeded = false;
|
|
537
|
-
try {
|
|
538
|
-
const anchorNode = anchor.getNode();
|
|
539
|
-
const focusNode = focus.getNode();
|
|
540
|
-
if (
|
|
541
|
-
// We might have removed a node that no longer exists
|
|
542
|
-
!anchorNode.isAttached() || !focusNode.isAttached() ||
|
|
543
|
-
// If we've split a node, then the offset might not be right
|
|
544
|
-
$isTextNode(anchorNode) && anchor.offset > anchorNode.getTextContentSize() || $isTextNode(focusNode) && focus.offset > focusNode.getTextContentSize()) {
|
|
545
|
-
recoveryNeeded = true;
|
|
546
|
-
}
|
|
547
|
-
} catch (_e) {
|
|
548
|
-
// Sometimes checking nor a node via getNode might trigger
|
|
549
|
-
// an error, so we need recovery then too.
|
|
550
|
-
recoveryNeeded = true;
|
|
551
|
-
}
|
|
552
|
-
return recoveryNeeded;
|
|
553
|
-
}
|
|
554
|
-
function syncWithTransaction(binding, fn) {
|
|
555
|
-
binding.doc.transact(fn, binding);
|
|
556
|
-
}
|
|
557
|
-
function $moveSelectionToPreviousNode(anchorNodeKey, currentEditorState) {
|
|
558
|
-
const anchorNode = currentEditorState._nodeMap.get(anchorNodeKey);
|
|
559
|
-
if (!anchorNode) {
|
|
560
|
-
$getRoot().selectStart();
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
// Get previous node
|
|
564
|
-
const prevNodeKey = anchorNode.__prev;
|
|
565
|
-
let prevNode = null;
|
|
566
|
-
if (prevNodeKey) {
|
|
567
|
-
prevNode = $getNodeByKey(prevNodeKey);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// If previous node not found, get parent node
|
|
571
|
-
if (prevNode === null && anchorNode.__parent !== null) {
|
|
572
|
-
prevNode = $getNodeByKey(anchorNode.__parent);
|
|
573
|
-
}
|
|
574
|
-
if (prevNode === null) {
|
|
575
|
-
$getRoot().selectStart();
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
if (prevNode !== null && prevNode.isAttached()) {
|
|
579
|
-
prevNode.selectEnd();
|
|
580
|
-
return;
|
|
581
|
-
} else {
|
|
582
|
-
// If the found node is also deleted, select the next one
|
|
583
|
-
$moveSelectionToPreviousNode(prevNode.__key, currentEditorState);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
class CollabDecoratorNode {
|
|
588
|
-
_xmlElem;
|
|
589
|
-
_key;
|
|
590
|
-
_parent;
|
|
591
|
-
_type;
|
|
592
|
-
constructor(xmlElem, parent, type) {
|
|
593
|
-
this._key = '';
|
|
594
|
-
this._xmlElem = xmlElem;
|
|
595
|
-
this._parent = parent;
|
|
596
|
-
this._type = type;
|
|
597
|
-
}
|
|
598
|
-
getPrevNode(nodeMap) {
|
|
599
|
-
if (nodeMap === null) {
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
602
|
-
const node = nodeMap.get(this._key);
|
|
603
|
-
return $isDecoratorNode(node) ? node : null;
|
|
604
|
-
}
|
|
605
|
-
getNode() {
|
|
606
|
-
const node = $getNodeByKey(this._key);
|
|
607
|
-
return $isDecoratorNode(node) ? node : null;
|
|
608
|
-
}
|
|
609
|
-
getSharedType() {
|
|
610
|
-
return this._xmlElem;
|
|
611
|
-
}
|
|
612
|
-
getType() {
|
|
613
|
-
return this._type;
|
|
614
|
-
}
|
|
615
|
-
getKey() {
|
|
616
|
-
return this._key;
|
|
617
|
-
}
|
|
618
|
-
getSize() {
|
|
619
|
-
return 1;
|
|
620
|
-
}
|
|
621
|
-
getOffset() {
|
|
622
|
-
const collabElementNode = this._parent;
|
|
623
|
-
return collabElementNode.getChildOffset(this);
|
|
624
|
-
}
|
|
625
|
-
syncPropertiesFromLexical(binding, nextLexicalNode, prevNodeMap) {
|
|
626
|
-
const prevLexicalNode = this.getPrevNode(prevNodeMap);
|
|
627
|
-
const xmlElem = this._xmlElem;
|
|
628
|
-
syncPropertiesFromLexical(binding, xmlElem, prevLexicalNode, nextLexicalNode);
|
|
629
|
-
}
|
|
630
|
-
syncPropertiesFromYjs(binding, keysChanged) {
|
|
631
|
-
const lexicalNode = this.getNode();
|
|
632
|
-
if (!(lexicalNode !== null)) {
|
|
633
|
-
formatDevErrorMessage(`syncPropertiesFromYjs: could not find decorator node`);
|
|
634
|
-
}
|
|
635
|
-
const xmlElem = this._xmlElem;
|
|
636
|
-
$syncPropertiesFromYjs(binding, xmlElem, lexicalNode, keysChanged);
|
|
637
|
-
}
|
|
638
|
-
destroy(binding) {
|
|
639
|
-
const collabNodeMap = binding.collabNodeMap;
|
|
640
|
-
if (collabNodeMap.get(this._key) === this) {
|
|
641
|
-
collabNodeMap.delete(this._key);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
function $createCollabDecoratorNode(xmlElem, parent, type) {
|
|
646
|
-
const collabNode = new CollabDecoratorNode(xmlElem, parent, type);
|
|
647
|
-
xmlElem._collabNode = collabNode;
|
|
648
|
-
return collabNode;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
class CollabElementNode {
|
|
652
|
-
_key;
|
|
653
|
-
_children;
|
|
654
|
-
_xmlText;
|
|
655
|
-
_type;
|
|
656
|
-
_parent;
|
|
657
|
-
constructor(xmlText, parent, type) {
|
|
658
|
-
this._key = '';
|
|
659
|
-
this._children = [];
|
|
660
|
-
this._xmlText = xmlText;
|
|
661
|
-
this._type = type;
|
|
662
|
-
this._parent = parent;
|
|
663
|
-
}
|
|
664
|
-
getPrevNode(nodeMap) {
|
|
665
|
-
if (nodeMap === null) {
|
|
666
|
-
return null;
|
|
667
|
-
}
|
|
668
|
-
const node = nodeMap.get(this._key);
|
|
669
|
-
return $isElementNode(node) ? node : null;
|
|
670
|
-
}
|
|
671
|
-
getNode() {
|
|
672
|
-
const node = $getNodeByKey(this._key);
|
|
673
|
-
return $isElementNode(node) ? node : null;
|
|
674
|
-
}
|
|
675
|
-
getSharedType() {
|
|
676
|
-
return this._xmlText;
|
|
677
|
-
}
|
|
678
|
-
getType() {
|
|
679
|
-
return this._type;
|
|
680
|
-
}
|
|
681
|
-
getKey() {
|
|
682
|
-
return this._key;
|
|
683
|
-
}
|
|
684
|
-
isEmpty() {
|
|
685
|
-
return this._children.length === 0;
|
|
686
|
-
}
|
|
687
|
-
getSize() {
|
|
688
|
-
return 1;
|
|
689
|
-
}
|
|
690
|
-
getOffset() {
|
|
691
|
-
const collabElementNode = this._parent;
|
|
692
|
-
if (!(collabElementNode !== null)) {
|
|
693
|
-
formatDevErrorMessage(`getOffset: could not find collab element node`);
|
|
309
|
+
getKey() {
|
|
310
|
+
return this._key;
|
|
311
|
+
}
|
|
312
|
+
isEmpty() {
|
|
313
|
+
return this._children.length === 0;
|
|
314
|
+
}
|
|
315
|
+
getSize() {
|
|
316
|
+
return 1;
|
|
317
|
+
}
|
|
318
|
+
getOffset() {
|
|
319
|
+
const collabElementNode = this._parent;
|
|
320
|
+
if (!(collabElementNode !== null)) {
|
|
321
|
+
formatDevErrorMessage(`getOffset: could not find collab element node`);
|
|
694
322
|
}
|
|
695
323
|
return collabElementNode.getChildOffset(this);
|
|
696
324
|
}
|
|
@@ -1170,6 +798,11 @@ class CollabV2Mapping {
|
|
|
1170
798
|
has(sharedType) {
|
|
1171
799
|
return this._sharedTypeToNodeKeys.has(sharedType);
|
|
1172
800
|
}
|
|
801
|
+
clear() {
|
|
802
|
+
this._nodeMap.clear();
|
|
803
|
+
this._sharedTypeToNodeKeys.clear();
|
|
804
|
+
this._nodeKeyToSharedType.clear();
|
|
805
|
+
}
|
|
1173
806
|
}
|
|
1174
807
|
|
|
1175
808
|
function createBaseBinding(editor, id, doc, docMap, excludedProperties) {
|
|
@@ -1194,1091 +827,1518 @@ function createBinding(editor, provider, id, doc, docMap, excludedProperties) {
|
|
|
1194
827
|
if (!(doc !== undefined && doc !== null)) {
|
|
1195
828
|
formatDevErrorMessage(`createBinding: doc is null or undefined`);
|
|
1196
829
|
}
|
|
1197
|
-
const rootXmlText = doc.get('root', XmlText);
|
|
1198
|
-
const root = $createCollabElementNode(rootXmlText, null, 'root');
|
|
1199
|
-
root._key = 'root';
|
|
1200
|
-
return {
|
|
1201
|
-
...createBaseBinding(editor, id, doc, docMap, excludedProperties),
|
|
1202
|
-
collabNodeMap: new Map(),
|
|
1203
|
-
root
|
|
1204
|
-
};
|
|
830
|
+
const rootXmlText = doc.get('root', XmlText);
|
|
831
|
+
const root = $createCollabElementNode(rootXmlText, null, 'root');
|
|
832
|
+
root._key = 'root';
|
|
833
|
+
return {
|
|
834
|
+
...createBaseBinding(editor, id, doc, docMap, excludedProperties),
|
|
835
|
+
collabNodeMap: new Map(),
|
|
836
|
+
root
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function createBindingV2__EXPERIMENTAL(editor, id, doc, docMap, options = {}) {
|
|
840
|
+
if (!(doc !== undefined && doc !== null)) {
|
|
841
|
+
formatDevErrorMessage(`createBinding: doc is null or undefined`);
|
|
842
|
+
}
|
|
843
|
+
const {
|
|
844
|
+
excludedProperties,
|
|
845
|
+
rootName = 'root-v2'
|
|
846
|
+
} = options;
|
|
847
|
+
return {
|
|
848
|
+
...createBaseBinding(editor, id, doc, docMap, excludedProperties),
|
|
849
|
+
mapping: new CollabV2Mapping(),
|
|
850
|
+
root: doc.get(rootName, XmlElement)
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
function isBindingV1(binding) {
|
|
854
|
+
return Object.hasOwn(binding, 'collabNodeMap');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const baseExcludedProperties = new Set(['__key', '__parent', '__next', '__prev', '__state']);
|
|
858
|
+
const elementExcludedProperties = new Set(['__first', '__last', '__size']);
|
|
859
|
+
const rootExcludedProperties = new Set(['__cachedText']);
|
|
860
|
+
const textExcludedProperties = new Set(['__text']);
|
|
861
|
+
function isExcludedProperty(name, node, binding) {
|
|
862
|
+
if (baseExcludedProperties.has(name) || typeof node[name] === 'function') {
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
if ($isTextNode(node)) {
|
|
866
|
+
if (textExcludedProperties.has(name)) {
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
} else if ($isElementNode(node)) {
|
|
870
|
+
if (elementExcludedProperties.has(name) || $isRootNode(node) && rootExcludedProperties.has(name)) {
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const nodeKlass = node.constructor;
|
|
875
|
+
const excludedProperties = binding.excludedProperties.get(nodeKlass);
|
|
876
|
+
return excludedProperties != null && excludedProperties.has(name);
|
|
877
|
+
}
|
|
878
|
+
function initializeNodeProperties(binding) {
|
|
879
|
+
const {
|
|
880
|
+
editor,
|
|
881
|
+
nodeProperties
|
|
882
|
+
} = binding;
|
|
883
|
+
editor.update(() => {
|
|
884
|
+
editor._nodes.forEach(nodeInfo => {
|
|
885
|
+
const node = new nodeInfo.klass();
|
|
886
|
+
const defaultProperties = {};
|
|
887
|
+
for (const [property, value] of Object.entries(node)) {
|
|
888
|
+
if (!isExcludedProperty(property, node, binding)) {
|
|
889
|
+
defaultProperties[property] = value;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
nodeProperties.set(node.__type, Object.freeze(defaultProperties));
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
function getDefaultNodeProperties(node, binding) {
|
|
897
|
+
const type = node.__type;
|
|
898
|
+
const {
|
|
899
|
+
nodeProperties
|
|
900
|
+
} = binding;
|
|
901
|
+
const properties = nodeProperties.get(type);
|
|
902
|
+
if (!(properties !== undefined)) {
|
|
903
|
+
formatDevErrorMessage(`Node properties for ${type} not initialized for sync`);
|
|
904
|
+
}
|
|
905
|
+
return properties;
|
|
906
|
+
}
|
|
907
|
+
function $createCollabNodeFromLexicalNode(binding, lexicalNode, parent) {
|
|
908
|
+
const nodeType = lexicalNode.__type;
|
|
909
|
+
let collabNode;
|
|
910
|
+
if ($isElementNode(lexicalNode)) {
|
|
911
|
+
const xmlText = new XmlText();
|
|
912
|
+
collabNode = $createCollabElementNode(xmlText, parent, nodeType);
|
|
913
|
+
collabNode.syncPropertiesFromLexical(binding, lexicalNode, null);
|
|
914
|
+
collabNode.syncChildrenFromLexical(binding, lexicalNode, null, null, null);
|
|
915
|
+
} else if ($isTextNode(lexicalNode)) {
|
|
916
|
+
// TODO create a token text node for token, segmented nodes.
|
|
917
|
+
const map = new Map$1();
|
|
918
|
+
collabNode = $createCollabTextNode(map, lexicalNode.__text, parent, nodeType);
|
|
919
|
+
collabNode.syncPropertiesAndTextFromLexical(binding, lexicalNode, null);
|
|
920
|
+
} else if ($isLineBreakNode(lexicalNode)) {
|
|
921
|
+
const map = new Map$1();
|
|
922
|
+
map.set('__type', 'linebreak');
|
|
923
|
+
collabNode = $createCollabLineBreakNode(map, parent);
|
|
924
|
+
} else if ($isDecoratorNode(lexicalNode)) {
|
|
925
|
+
const xmlElem = new XmlElement();
|
|
926
|
+
collabNode = $createCollabDecoratorNode(xmlElem, parent, nodeType);
|
|
927
|
+
collabNode.syncPropertiesFromLexical(binding, lexicalNode, null);
|
|
928
|
+
} else {
|
|
929
|
+
{
|
|
930
|
+
formatDevErrorMessage(`Expected text, element, decorator, or linebreak node`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
collabNode._key = lexicalNode.__key;
|
|
934
|
+
return collabNode;
|
|
935
|
+
}
|
|
936
|
+
function getNodeTypeFromSharedType(sharedType) {
|
|
937
|
+
const type = sharedTypeGet(sharedType, '__type');
|
|
938
|
+
if (!(typeof type === 'string' || typeof type === 'undefined')) {
|
|
939
|
+
formatDevErrorMessage(`Expected shared type to include type attribute`);
|
|
940
|
+
}
|
|
941
|
+
return type;
|
|
942
|
+
}
|
|
943
|
+
function $getOrInitCollabNodeFromSharedType(binding, sharedType, parent) {
|
|
944
|
+
const collabNode = sharedType._collabNode;
|
|
945
|
+
if (collabNode === undefined) {
|
|
946
|
+
const registeredNodes = binding.editor._nodes;
|
|
947
|
+
const type = getNodeTypeFromSharedType(sharedType);
|
|
948
|
+
if (!(typeof type === 'string')) {
|
|
949
|
+
formatDevErrorMessage(`Expected shared type to include type attribute`);
|
|
950
|
+
}
|
|
951
|
+
const nodeInfo = registeredNodes.get(type);
|
|
952
|
+
if (!(nodeInfo !== undefined)) {
|
|
953
|
+
formatDevErrorMessage(`Node ${type} is not registered`);
|
|
954
|
+
}
|
|
955
|
+
const sharedParent = sharedType.parent;
|
|
956
|
+
const targetParent = parent === undefined && sharedParent !== null ? $getOrInitCollabNodeFromSharedType(binding, sharedParent) : parent || null;
|
|
957
|
+
if (!(targetParent instanceof CollabElementNode)) {
|
|
958
|
+
formatDevErrorMessage(`Expected parent to be a collab element node`);
|
|
959
|
+
}
|
|
960
|
+
if (sharedType instanceof XmlText) {
|
|
961
|
+
return $createCollabElementNode(sharedType, targetParent, type);
|
|
962
|
+
} else if (sharedType instanceof Map$1) {
|
|
963
|
+
if (type === 'linebreak') {
|
|
964
|
+
return $createCollabLineBreakNode(sharedType, targetParent);
|
|
965
|
+
}
|
|
966
|
+
return $createCollabTextNode(sharedType, '', targetParent, type);
|
|
967
|
+
} else if (sharedType instanceof XmlElement) {
|
|
968
|
+
return $createCollabDecoratorNode(sharedType, targetParent, type);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return collabNode;
|
|
972
|
+
}
|
|
973
|
+
function createLexicalNodeFromCollabNode(binding, collabNode, parentKey) {
|
|
974
|
+
const type = collabNode.getType();
|
|
975
|
+
const registeredNodes = binding.editor._nodes;
|
|
976
|
+
const nodeInfo = registeredNodes.get(type);
|
|
977
|
+
if (!(nodeInfo !== undefined)) {
|
|
978
|
+
formatDevErrorMessage(`Node ${type} is not registered`);
|
|
979
|
+
}
|
|
980
|
+
const lexicalNode = new nodeInfo.klass();
|
|
981
|
+
lexicalNode.__parent = parentKey;
|
|
982
|
+
collabNode._key = lexicalNode.__key;
|
|
983
|
+
if (collabNode instanceof CollabElementNode) {
|
|
984
|
+
const xmlText = collabNode._xmlText;
|
|
985
|
+
collabNode.syncPropertiesFromYjs(binding, null);
|
|
986
|
+
collabNode.applyChildrenYjsDelta(binding, xmlText.toDelta());
|
|
987
|
+
collabNode.syncChildrenFromYjs(binding);
|
|
988
|
+
} else if (collabNode instanceof CollabTextNode) {
|
|
989
|
+
collabNode.syncPropertiesAndTextFromYjs(binding, null);
|
|
990
|
+
} else if (collabNode instanceof CollabDecoratorNode) {
|
|
991
|
+
collabNode.syncPropertiesFromYjs(binding, null);
|
|
992
|
+
}
|
|
993
|
+
binding.collabNodeMap.set(lexicalNode.__key, collabNode);
|
|
994
|
+
return lexicalNode;
|
|
995
|
+
}
|
|
996
|
+
function $syncPropertiesFromYjs(binding, sharedType, lexicalNode, keysChanged) {
|
|
997
|
+
const properties = keysChanged === null ? sharedType instanceof Map$1 ? Array.from(sharedType.keys()) : sharedType instanceof XmlText || sharedType instanceof XmlElement ? Object.keys(sharedType.getAttributes()) : Object.keys(sharedType) : Array.from(keysChanged);
|
|
998
|
+
let writableNode;
|
|
999
|
+
for (let i = 0; i < properties.length; i++) {
|
|
1000
|
+
const property = properties[i];
|
|
1001
|
+
if (isExcludedProperty(property, lexicalNode, binding)) {
|
|
1002
|
+
if (property === '__state' && isBindingV1(binding)) {
|
|
1003
|
+
if (!writableNode) {
|
|
1004
|
+
writableNode = lexicalNode.getWritable();
|
|
1005
|
+
}
|
|
1006
|
+
$syncNodeStateToLexical(sharedType, writableNode);
|
|
1007
|
+
}
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1011
|
+
const prevValue = lexicalNode[property];
|
|
1012
|
+
let nextValue = sharedTypeGet(sharedType, property);
|
|
1013
|
+
if (prevValue !== nextValue) {
|
|
1014
|
+
if (nextValue instanceof Doc) {
|
|
1015
|
+
const yjsDocMap = binding.docMap;
|
|
1016
|
+
if (prevValue instanceof Doc) {
|
|
1017
|
+
yjsDocMap.delete(prevValue.guid);
|
|
1018
|
+
}
|
|
1019
|
+
const nestedEditor = createEditor();
|
|
1020
|
+
const key = nextValue.guid;
|
|
1021
|
+
nestedEditor._key = key;
|
|
1022
|
+
yjsDocMap.set(key, nextValue);
|
|
1023
|
+
nextValue = nestedEditor;
|
|
1024
|
+
}
|
|
1025
|
+
if (writableNode === undefined) {
|
|
1026
|
+
writableNode = lexicalNode.getWritable();
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1030
|
+
writableNode[property] = nextValue;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
function sharedTypeGet(sharedType, property) {
|
|
1035
|
+
if (sharedType instanceof Map$1) {
|
|
1036
|
+
return sharedType.get(property);
|
|
1037
|
+
} else if (sharedType instanceof XmlText || sharedType instanceof XmlElement) {
|
|
1038
|
+
return sharedType.getAttribute(property);
|
|
1039
|
+
} else {
|
|
1040
|
+
return sharedType[property];
|
|
1041
|
+
}
|
|
1205
1042
|
}
|
|
1206
|
-
function
|
|
1207
|
-
if (
|
|
1208
|
-
|
|
1043
|
+
function sharedTypeSet(sharedType, property, nextValue) {
|
|
1044
|
+
if (sharedType instanceof Map$1) {
|
|
1045
|
+
sharedType.set(property, nextValue);
|
|
1046
|
+
} else {
|
|
1047
|
+
sharedType.setAttribute(property, nextValue);
|
|
1209
1048
|
}
|
|
1210
|
-
const {
|
|
1211
|
-
excludedProperties,
|
|
1212
|
-
rootName = 'root-v2'
|
|
1213
|
-
} = options;
|
|
1214
|
-
return {
|
|
1215
|
-
...createBaseBinding(editor, id, doc, docMap, excludedProperties),
|
|
1216
|
-
mapping: new CollabV2Mapping(),
|
|
1217
|
-
root: doc.get(rootName, XmlElement)
|
|
1218
|
-
};
|
|
1219
1049
|
}
|
|
1220
|
-
function
|
|
1221
|
-
|
|
1050
|
+
function $syncNodeStateToLexical(sharedType, lexicalNode) {
|
|
1051
|
+
const existingState = sharedTypeGet(sharedType, '__state');
|
|
1052
|
+
if (!(existingState instanceof Map$1)) {
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
// This should only called when creating the node initially,
|
|
1056
|
+
// incremental updates to state come in through YMapEvent
|
|
1057
|
+
// with the __state as the target.
|
|
1058
|
+
$getWritableNodeState(lexicalNode).updateFromJSON(existingState.toJSON());
|
|
1222
1059
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
return null;
|
|
1060
|
+
function syncNodeStateFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode) {
|
|
1061
|
+
const nextState = nextLexicalNode.__state;
|
|
1062
|
+
const existingState = sharedTypeGet(sharedType, '__state');
|
|
1063
|
+
if (!nextState) {
|
|
1064
|
+
return;
|
|
1229
1065
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
if (!$isElementNode(parent)) {
|
|
1242
|
-
formatDevErrorMessage(`Element point must be an element node`);
|
|
1243
|
-
}
|
|
1244
|
-
let accumulatedOffset = 0;
|
|
1245
|
-
let i = 0;
|
|
1246
|
-
let node = parent.getFirstChild();
|
|
1247
|
-
while (node !== null && i++ < offset) {
|
|
1248
|
-
if ($isTextNode(node)) {
|
|
1249
|
-
accumulatedOffset += node.getTextContentSize() + 1;
|
|
1250
|
-
} else {
|
|
1251
|
-
accumulatedOffset++;
|
|
1066
|
+
const [unknown, known] = nextState.getInternalState();
|
|
1067
|
+
const prevState = prevLexicalNode && prevLexicalNode.__state;
|
|
1068
|
+
const stateMap = existingState instanceof Map$1 ? existingState : new Map$1();
|
|
1069
|
+
if (prevState === nextState) {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const [prevUnknown, prevKnown] = prevState && stateMap.doc ? prevState.getInternalState() : [undefined, new Map()];
|
|
1073
|
+
if (unknown) {
|
|
1074
|
+
for (const [k, v] of Object.entries(unknown)) {
|
|
1075
|
+
if (prevUnknown && v !== prevUnknown[k]) {
|
|
1076
|
+
stateMap.set(k, v);
|
|
1252
1077
|
}
|
|
1253
|
-
node = node.getNextSibling();
|
|
1254
1078
|
}
|
|
1255
|
-
offset = accumulatedOffset;
|
|
1256
|
-
}
|
|
1257
|
-
return createRelativePositionFromTypeIndex(sharedType, offset);
|
|
1258
|
-
}
|
|
1259
|
-
function createRelativePositionV2(point, binding) {
|
|
1260
|
-
const {
|
|
1261
|
-
mapping
|
|
1262
|
-
} = binding;
|
|
1263
|
-
const {
|
|
1264
|
-
offset
|
|
1265
|
-
} = point;
|
|
1266
|
-
const node = point.getNode();
|
|
1267
|
-
const yType = mapping.getSharedType(node);
|
|
1268
|
-
if (yType === undefined) {
|
|
1269
|
-
return null;
|
|
1270
1079
|
}
|
|
1271
|
-
|
|
1272
|
-
if (
|
|
1273
|
-
|
|
1274
|
-
}
|
|
1275
|
-
let prevSibling = node.getPreviousSibling();
|
|
1276
|
-
let adjustedOffset = offset;
|
|
1277
|
-
while ($isTextNode(prevSibling)) {
|
|
1278
|
-
adjustedOffset += prevSibling.getTextContentSize();
|
|
1279
|
-
prevSibling = prevSibling.getPreviousSibling();
|
|
1280
|
-
}
|
|
1281
|
-
return createRelativePositionFromTypeIndex(yType, adjustedOffset);
|
|
1282
|
-
} else if (point.type === 'element') {
|
|
1283
|
-
if (!$isElementNode(node)) {
|
|
1284
|
-
formatDevErrorMessage(`Element point must be an element node`);
|
|
1080
|
+
for (const [stateConfig, v] of known) {
|
|
1081
|
+
if (prevKnown.get(stateConfig) !== v) {
|
|
1082
|
+
stateMap.set(stateConfig.key, stateConfig.unparse(v));
|
|
1285
1083
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1084
|
+
}
|
|
1085
|
+
if (!existingState) {
|
|
1086
|
+
sharedTypeSet(sharedType, '__state', stateMap);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
function syncPropertiesFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode) {
|
|
1090
|
+
const properties = Object.keys(getDefaultNodeProperties(nextLexicalNode, binding));
|
|
1091
|
+
const EditorClass = binding.editor.constructor;
|
|
1092
|
+
syncNodeStateFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode);
|
|
1093
|
+
for (let i = 0; i < properties.length; i++) {
|
|
1094
|
+
const property = properties[i];
|
|
1095
|
+
const prevValue =
|
|
1096
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1097
|
+
prevLexicalNode === null ? undefined : prevLexicalNode[property];
|
|
1098
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1099
|
+
let nextValue = nextLexicalNode[property];
|
|
1100
|
+
if (prevValue !== nextValue) {
|
|
1101
|
+
if (nextValue instanceof EditorClass) {
|
|
1102
|
+
const yjsDocMap = binding.docMap;
|
|
1103
|
+
let prevDoc;
|
|
1104
|
+
if (prevValue instanceof EditorClass) {
|
|
1105
|
+
const prevKey = prevValue._key;
|
|
1106
|
+
prevDoc = yjsDocMap.get(prevKey);
|
|
1107
|
+
yjsDocMap.delete(prevKey);
|
|
1293
1108
|
}
|
|
1109
|
+
|
|
1110
|
+
// If we already have a document, use it.
|
|
1111
|
+
const doc = prevDoc || new Doc();
|
|
1112
|
+
const key = doc.guid;
|
|
1113
|
+
nextValue._key = key;
|
|
1114
|
+
yjsDocMap.set(key, doc);
|
|
1115
|
+
nextValue = doc;
|
|
1116
|
+
// Mark the node dirty as we've assigned a new key to it
|
|
1117
|
+
binding.editor.update(() => {
|
|
1118
|
+
nextLexicalNode.markDirty();
|
|
1119
|
+
});
|
|
1294
1120
|
}
|
|
1295
|
-
|
|
1296
|
-
child = child.getNextSibling();
|
|
1121
|
+
sharedTypeSet(sharedType, property, nextValue);
|
|
1297
1122
|
}
|
|
1298
|
-
return createRelativePositionFromTypeIndex(yType, i);
|
|
1299
1123
|
}
|
|
1300
|
-
return null;
|
|
1301
1124
|
}
|
|
1302
|
-
function
|
|
1303
|
-
return
|
|
1125
|
+
function spliceString(str, index, delCount, newText) {
|
|
1126
|
+
return str.slice(0, index) + newText + str.slice(index + delCount);
|
|
1304
1127
|
}
|
|
1305
|
-
function
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1128
|
+
function getPositionFromElementAndOffset(node, offset, boundaryIsEdge) {
|
|
1129
|
+
let index = 0;
|
|
1130
|
+
let i = 0;
|
|
1131
|
+
const children = node._children;
|
|
1132
|
+
const childrenLength = children.length;
|
|
1133
|
+
for (; i < childrenLength; i++) {
|
|
1134
|
+
const child = children[i];
|
|
1135
|
+
const childOffset = index;
|
|
1136
|
+
const size = child.getSize();
|
|
1137
|
+
index += size;
|
|
1138
|
+
const exceedsBoundary = boundaryIsEdge ? index >= offset : index > offset;
|
|
1139
|
+
if (exceedsBoundary && child instanceof CollabTextNode) {
|
|
1140
|
+
let textOffset = offset - childOffset - 1;
|
|
1141
|
+
if (textOffset < 0) {
|
|
1142
|
+
textOffset = 0;
|
|
1143
|
+
}
|
|
1144
|
+
const diffLength = index - offset;
|
|
1145
|
+
return {
|
|
1146
|
+
length: diffLength,
|
|
1147
|
+
node: child,
|
|
1148
|
+
nodeIndex: i,
|
|
1149
|
+
offset: textOffset
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
if (index > offset) {
|
|
1153
|
+
return {
|
|
1154
|
+
length: 0,
|
|
1155
|
+
node: child,
|
|
1156
|
+
nodeIndex: i,
|
|
1157
|
+
offset: childOffset
|
|
1158
|
+
};
|
|
1159
|
+
} else if (i === childrenLength - 1) {
|
|
1160
|
+
return {
|
|
1161
|
+
length: 0,
|
|
1162
|
+
node: null,
|
|
1163
|
+
nodeIndex: i + 1,
|
|
1164
|
+
offset: childOffset + 1
|
|
1165
|
+
};
|
|
1309
1166
|
}
|
|
1310
|
-
} else if (pos == null || !compareRelativePositions(currentPos, pos)) {
|
|
1311
|
-
return true;
|
|
1312
1167
|
}
|
|
1313
|
-
return false;
|
|
1314
|
-
}
|
|
1315
|
-
function createCursor(name, color) {
|
|
1316
1168
|
return {
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1169
|
+
length: 0,
|
|
1170
|
+
node: null,
|
|
1171
|
+
nodeIndex: 0,
|
|
1172
|
+
offset: 0
|
|
1320
1173
|
};
|
|
1321
1174
|
}
|
|
1322
|
-
function
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1175
|
+
function doesSelectionNeedRecovering(selection) {
|
|
1176
|
+
const anchor = selection.anchor;
|
|
1177
|
+
const focus = selection.focus;
|
|
1178
|
+
let recoveryNeeded = false;
|
|
1179
|
+
try {
|
|
1180
|
+
const anchorNode = anchor.getNode();
|
|
1181
|
+
const focusNode = focus.getNode();
|
|
1182
|
+
if (
|
|
1183
|
+
// We might have removed a node that no longer exists
|
|
1184
|
+
!anchorNode.isAttached() || !focusNode.isAttached() ||
|
|
1185
|
+
// If we've split a node, then the offset might not be right
|
|
1186
|
+
$isTextNode(anchorNode) && anchor.offset > anchorNode.getTextContentSize() || $isTextNode(focusNode) && focus.offset > focusNode.getTextContentSize()) {
|
|
1187
|
+
recoveryNeeded = true;
|
|
1329
1188
|
}
|
|
1189
|
+
} catch (_e) {
|
|
1190
|
+
// Sometimes checking nor a node via getNode might trigger
|
|
1191
|
+
// an error, so we need recovery then too.
|
|
1192
|
+
recoveryNeeded = true;
|
|
1193
|
+
}
|
|
1194
|
+
return recoveryNeeded;
|
|
1195
|
+
}
|
|
1196
|
+
function syncWithTransaction(binding, fn) {
|
|
1197
|
+
binding.doc.transact(fn, binding);
|
|
1198
|
+
}
|
|
1199
|
+
function $moveSelectionToPreviousNode(anchorNodeKey, currentEditorState) {
|
|
1200
|
+
const anchorNode = currentEditorState._nodeMap.get(anchorNodeKey);
|
|
1201
|
+
if (!anchorNode) {
|
|
1202
|
+
$getRoot().selectStart();
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
// Get previous node
|
|
1206
|
+
const prevNodeKey = anchorNode.__prev;
|
|
1207
|
+
let prevNode = null;
|
|
1208
|
+
if (prevNodeKey) {
|
|
1209
|
+
prevNode = $getNodeByKey(prevNodeKey);
|
|
1330
1210
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
destroySelection(binding, selection);
|
|
1211
|
+
|
|
1212
|
+
// If previous node not found, get parent node
|
|
1213
|
+
if (prevNode === null && anchorNode.__parent !== null) {
|
|
1214
|
+
prevNode = $getNodeByKey(anchorNode.__parent);
|
|
1336
1215
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
const color = cursor.color;
|
|
1340
|
-
const caret = document.createElement('span');
|
|
1341
|
-
caret.style.cssText = `position:absolute;top:0;bottom:0;right:-1px;width:1px;background-color:${color};z-index:10;`;
|
|
1342
|
-
const name = document.createElement('span');
|
|
1343
|
-
name.textContent = cursor.name;
|
|
1344
|
-
name.style.cssText = `position:absolute;left:-2px;top:-16px;background-color:${color};color:#fff;line-height:12px;font-size:12px;padding:2px;font-family:Arial;font-weight:bold;white-space:nowrap;`;
|
|
1345
|
-
caret.appendChild(name);
|
|
1346
|
-
return {
|
|
1347
|
-
anchor: {
|
|
1348
|
-
key: anchorKey,
|
|
1349
|
-
offset: anchorOffset
|
|
1350
|
-
},
|
|
1351
|
-
caret,
|
|
1352
|
-
color,
|
|
1353
|
-
focus: {
|
|
1354
|
-
key: focusKey,
|
|
1355
|
-
offset: focusOffset
|
|
1356
|
-
},
|
|
1357
|
-
name,
|
|
1358
|
-
selections: []
|
|
1359
|
-
};
|
|
1360
|
-
}
|
|
1361
|
-
function updateCursor(binding, cursor, nextSelection, nodeMap) {
|
|
1362
|
-
const editor = binding.editor;
|
|
1363
|
-
const rootElement = editor.getRootElement();
|
|
1364
|
-
const cursorsContainer = binding.cursorsContainer;
|
|
1365
|
-
if (cursorsContainer === null || rootElement === null) {
|
|
1216
|
+
if (prevNode === null) {
|
|
1217
|
+
$getRoot().selectStart();
|
|
1366
1218
|
return;
|
|
1367
1219
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1220
|
+
if (prevNode !== null && prevNode.isAttached()) {
|
|
1221
|
+
prevNode.selectEnd();
|
|
1370
1222
|
return;
|
|
1223
|
+
} else {
|
|
1224
|
+
// If the found node is also deleted, select the next one
|
|
1225
|
+
$moveSelectionToPreviousNode(prevNode.__key, currentEditorState);
|
|
1371
1226
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// https://docs.yjs.dev/api/shared-types/y.xmlelement
|
|
1230
|
+
// "Define a top-level type; Note that the nodeName is always "undefined""
|
|
1231
|
+
const isRootElement = el => el.nodeName === 'UNDEFINED';
|
|
1232
|
+
const $createOrUpdateNodeFromYElement = (el, binding, keysChanged, childListChanged, snapshot, prevSnapshot, computeYChange) => {
|
|
1233
|
+
let node = binding.mapping.get(el);
|
|
1234
|
+
if (node && keysChanged && keysChanged.size === 0 && !childListChanged) {
|
|
1235
|
+
return node;
|
|
1236
|
+
}
|
|
1237
|
+
const type = isRootElement(el) ? RootNode.getType() : el.nodeName;
|
|
1238
|
+
const registeredNodes = binding.editor._nodes;
|
|
1239
|
+
const nodeInfo = registeredNodes.get(type);
|
|
1240
|
+
if (nodeInfo === undefined) {
|
|
1241
|
+
throw new Error(`$createOrUpdateNodeFromYElement: Node ${type} is not registered`);
|
|
1242
|
+
}
|
|
1243
|
+
if (!node) {
|
|
1244
|
+
node = new nodeInfo.klass();
|
|
1245
|
+
keysChanged = null;
|
|
1246
|
+
childListChanged = true;
|
|
1247
|
+
}
|
|
1248
|
+
if (childListChanged && node instanceof ElementNode) {
|
|
1249
|
+
const children = [];
|
|
1250
|
+
const $createChildren = childType => {
|
|
1251
|
+
if (childType instanceof XmlElement) {
|
|
1252
|
+
const n = $createOrUpdateNodeFromYElement(childType, binding, new Set(), false, snapshot, prevSnapshot, computeYChange);
|
|
1253
|
+
if (n !== null) {
|
|
1254
|
+
children.push(n);
|
|
1255
|
+
}
|
|
1256
|
+
} else if (childType instanceof XmlText) {
|
|
1257
|
+
const ns = $createOrUpdateTextNodesFromYText(childType, binding, snapshot, prevSnapshot, computeYChange);
|
|
1258
|
+
if (ns !== null) {
|
|
1259
|
+
ns.forEach(textchild => {
|
|
1260
|
+
if (textchild !== null) {
|
|
1261
|
+
children.push(textchild);
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
} else {
|
|
1266
|
+
{
|
|
1267
|
+
formatDevErrorMessage(`XmlHook is not supported`);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
if (snapshot === undefined || prevSnapshot === undefined) {
|
|
1272
|
+
el.toArray().forEach($createChildren);
|
|
1377
1273
|
} else {
|
|
1378
|
-
|
|
1379
|
-
destroySelection(binding, prevSelection);
|
|
1380
|
-
return;
|
|
1274
|
+
typeListToArraySnapshot(el, new Snapshot(prevSnapshot.ds, snapshot.sv)).forEach($createChildren);
|
|
1381
1275
|
}
|
|
1382
|
-
|
|
1383
|
-
cursor.selection = nextSelection;
|
|
1384
|
-
}
|
|
1385
|
-
const caret = nextSelection.caret;
|
|
1386
|
-
const color = nextSelection.color;
|
|
1387
|
-
const selections = nextSelection.selections;
|
|
1388
|
-
const anchor = nextSelection.anchor;
|
|
1389
|
-
const focus = nextSelection.focus;
|
|
1390
|
-
const anchorKey = anchor.key;
|
|
1391
|
-
const focusKey = focus.key;
|
|
1392
|
-
const anchorNode = nodeMap.get(anchorKey);
|
|
1393
|
-
const focusNode = nodeMap.get(focusKey);
|
|
1394
|
-
if (anchorNode == null || focusNode == null) {
|
|
1395
|
-
return;
|
|
1276
|
+
$spliceChildren(node, children);
|
|
1396
1277
|
}
|
|
1397
|
-
let selectionRects;
|
|
1398
1278
|
|
|
1399
|
-
//
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
return;
|
|
1279
|
+
// TODO(collab-v2): typing for XmlElement generic
|
|
1280
|
+
const attrs = el.getAttributes(snapshot);
|
|
1281
|
+
if (!isRootElement(el) && snapshot !== undefined) {
|
|
1282
|
+
if (!isItemVisible(el._item, snapshot)) {
|
|
1283
|
+
attrs[stateKeyToAttrKey('ychange')] = computeYChange ? computeYChange('removed', el._item.id) : {
|
|
1284
|
+
type: 'removed'
|
|
1285
|
+
};
|
|
1286
|
+
} else if (!isItemVisible(el._item, prevSnapshot)) {
|
|
1287
|
+
attrs[stateKeyToAttrKey('ychange')] = computeYChange ? computeYChange('added', el._item.id) : {
|
|
1288
|
+
type: 'added'
|
|
1289
|
+
};
|
|
1411
1290
|
}
|
|
1412
|
-
selectionRects = createRectsFromDOMRange(editor, range);
|
|
1413
1291
|
}
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
if (
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
selection.appendChild(selectionBg);
|
|
1424
|
-
cursorsContainer.appendChild(selection);
|
|
1292
|
+
const properties = {
|
|
1293
|
+
...getDefaultNodeProperties(node, binding)
|
|
1294
|
+
};
|
|
1295
|
+
const state = {};
|
|
1296
|
+
for (const k in attrs) {
|
|
1297
|
+
if (k.startsWith(STATE_KEY_PREFIX)) {
|
|
1298
|
+
state[attrKeyToStateKey(k)] = attrs[k];
|
|
1299
|
+
} else {
|
|
1300
|
+
properties[k] = attrs[k];
|
|
1425
1301
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1302
|
+
}
|
|
1303
|
+
$syncPropertiesFromYjs(binding, properties, node, keysChanged);
|
|
1304
|
+
if (!keysChanged) {
|
|
1305
|
+
$getWritableNodeState(node).updateFromJSON(state);
|
|
1306
|
+
} else {
|
|
1307
|
+
const stateKeysChanged = Object.keys(state).filter(k => keysChanged.has(stateKeyToAttrKey(k)));
|
|
1308
|
+
if (stateKeysChanged.length > 0) {
|
|
1309
|
+
const writableState = $getWritableNodeState(node);
|
|
1310
|
+
for (const k of stateKeysChanged) {
|
|
1311
|
+
writableState.updateFromUnknown(k, state[k]);
|
|
1434
1312
|
}
|
|
1435
1313
|
}
|
|
1436
1314
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
const
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1315
|
+
const latestNode = node.getLatest();
|
|
1316
|
+
binding.mapping.set(el, latestNode);
|
|
1317
|
+
return latestNode;
|
|
1318
|
+
};
|
|
1319
|
+
const $spliceChildren = (node, nextChildren) => {
|
|
1320
|
+
const prevChildren = node.getChildren();
|
|
1321
|
+
const prevChildrenKeySet = new Set(prevChildren.map(child => child.getKey()));
|
|
1322
|
+
const nextChildrenKeySet = new Set(nextChildren.map(child => child.getKey()));
|
|
1323
|
+
const prevEndIndex = prevChildren.length - 1;
|
|
1324
|
+
const nextEndIndex = nextChildren.length - 1;
|
|
1325
|
+
let prevIndex = 0;
|
|
1326
|
+
let nextIndex = 0;
|
|
1327
|
+
while (prevIndex <= prevEndIndex && nextIndex <= nextEndIndex) {
|
|
1328
|
+
const prevKey = prevChildren[prevIndex].getKey();
|
|
1329
|
+
const nextKey = nextChildren[nextIndex].getKey();
|
|
1330
|
+
if (prevKey === nextKey) {
|
|
1331
|
+
prevIndex++;
|
|
1332
|
+
nextIndex++;
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
const nextHasPrevKey = nextChildrenKeySet.has(prevKey);
|
|
1336
|
+
const prevHasNextKey = prevChildrenKeySet.has(nextKey);
|
|
1337
|
+
if (!nextHasPrevKey) {
|
|
1338
|
+
// If removing the last node, insert remaining new nodes immediately, otherwise if the node
|
|
1339
|
+
// cannot be empty, it will remove itself from its parent.
|
|
1340
|
+
if (nextIndex === 0 && node.getChildrenSize() === 1) {
|
|
1341
|
+
node.splice(nextIndex, 1, nextChildren.slice(nextIndex));
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
// Remove
|
|
1345
|
+
node.splice(nextIndex, 1, []);
|
|
1346
|
+
prevIndex++;
|
|
1347
|
+
continue;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Create or replace
|
|
1351
|
+
const nextChildNode = nextChildren[nextIndex];
|
|
1352
|
+
if (prevHasNextKey) {
|
|
1353
|
+
node.splice(nextIndex, 1, [nextChildNode]);
|
|
1354
|
+
prevIndex++;
|
|
1355
|
+
nextIndex++;
|
|
1356
|
+
} else {
|
|
1357
|
+
node.splice(nextIndex, 0, [nextChildNode]);
|
|
1358
|
+
nextIndex++;
|
|
1461
1359
|
}
|
|
1462
1360
|
}
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
}
|
|
1470
|
-
function $getAnchorAndFocusForUserState(binding, userState) {
|
|
1471
|
-
const {
|
|
1472
|
-
anchorPos,
|
|
1473
|
-
focusPos
|
|
1474
|
-
} = userState;
|
|
1475
|
-
const anchorAbsPos = anchorPos ? createAbsolutePosition(anchorPos, binding) : null;
|
|
1476
|
-
const focusAbsPos = focusPos ? createAbsolutePosition(focusPos, binding) : null;
|
|
1477
|
-
if (anchorAbsPos === null || focusAbsPos === null) {
|
|
1478
|
-
return {
|
|
1479
|
-
anchorKey: null,
|
|
1480
|
-
anchorOffset: 0,
|
|
1481
|
-
focusKey: null,
|
|
1482
|
-
focusOffset: 0
|
|
1483
|
-
};
|
|
1484
|
-
}
|
|
1485
|
-
if (isBindingV1(binding)) {
|
|
1486
|
-
const [anchorCollabNode, anchorOffset] = getCollabNodeAndOffset(anchorAbsPos.type, anchorAbsPos.index);
|
|
1487
|
-
const [focusCollabNode, focusOffset] = getCollabNodeAndOffset(focusAbsPos.type, focusAbsPos.index);
|
|
1488
|
-
return {
|
|
1489
|
-
anchorKey: anchorCollabNode !== null ? anchorCollabNode.getKey() : null,
|
|
1490
|
-
anchorOffset,
|
|
1491
|
-
focusKey: focusCollabNode !== null ? focusCollabNode.getKey() : null,
|
|
1492
|
-
focusOffset
|
|
1493
|
-
};
|
|
1361
|
+
const appendNewChildren = prevIndex > prevEndIndex;
|
|
1362
|
+
const removeOldChildren = nextIndex > nextEndIndex;
|
|
1363
|
+
if (appendNewChildren && !removeOldChildren) {
|
|
1364
|
+
node.append(...nextChildren.slice(nextIndex));
|
|
1365
|
+
} else if (removeOldChildren && !appendNewChildren) {
|
|
1366
|
+
node.splice(nextChildren.length, node.getChildrenSize() - nextChildren.length, []);
|
|
1494
1367
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1368
|
+
};
|
|
1369
|
+
const isItemVisible = (item, snapshot) => snapshot === undefined ? !item.deleted : snapshot.sv.has(item.id.client) && snapshot.sv.get(item.id.client) > item.id.clock && !isDeleted(snapshot.ds, item.id);
|
|
1370
|
+
const $createOrUpdateTextNodesFromYText = (text, binding, snapshot, prevSnapshot, computeYChange) => {
|
|
1371
|
+
const deltas = toDelta(text, snapshot, prevSnapshot, computeYChange);
|
|
1372
|
+
|
|
1373
|
+
// Use existing text nodes if the count and types all align, otherwise throw out the existing
|
|
1374
|
+
// nodes and create new ones.
|
|
1375
|
+
let nodes = binding.mapping.get(text) ?? [];
|
|
1376
|
+
const nodeTypes = deltas.map(delta => delta.attributes.t ?? TextNode.getType());
|
|
1377
|
+
const canReuseNodes = nodes.length === nodeTypes.length && nodes.every((node, i) => node.getType() === nodeTypes[i]);
|
|
1378
|
+
if (!canReuseNodes) {
|
|
1379
|
+
const registeredNodes = binding.editor._nodes;
|
|
1380
|
+
nodes = nodeTypes.map(type => {
|
|
1381
|
+
const nodeInfo = registeredNodes.get(type);
|
|
1382
|
+
if (nodeInfo === undefined) {
|
|
1383
|
+
throw new Error(`$createTextNodesFromYText: Node ${type} is not registered`);
|
|
1384
|
+
}
|
|
1385
|
+
const node = new nodeInfo.klass();
|
|
1386
|
+
if (!$isTextNode(node)) {
|
|
1387
|
+
throw new Error(`$createTextNodesFromYText: Node ${type} is not a TextNode`);
|
|
1510
1388
|
}
|
|
1389
|
+
return node;
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
// Sync text, properties and state to the text nodes.
|
|
1394
|
+
for (let i = 0; i < deltas.length; i++) {
|
|
1395
|
+
const node = nodes[i];
|
|
1396
|
+
const delta = deltas[i];
|
|
1397
|
+
const {
|
|
1398
|
+
attributes,
|
|
1399
|
+
insert
|
|
1400
|
+
} = delta;
|
|
1401
|
+
if (node.__text !== insert) {
|
|
1402
|
+
node.setTextContent(insert);
|
|
1511
1403
|
}
|
|
1404
|
+
const properties = {
|
|
1405
|
+
...getDefaultNodeProperties(node, binding),
|
|
1406
|
+
...attributes.p
|
|
1407
|
+
};
|
|
1408
|
+
const state = Object.fromEntries(Object.entries(attributes).filter(([k]) => k.startsWith(STATE_KEY_PREFIX)).map(([k, v]) => [attrKeyToStateKey(k), v]));
|
|
1409
|
+
$syncPropertiesFromYjs(binding, properties, node, null);
|
|
1410
|
+
$getWritableNodeState(node).updateFromJSON(state);
|
|
1512
1411
|
}
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1412
|
+
const latestNodes = nodes.map(node => node.getLatest());
|
|
1413
|
+
binding.mapping.set(text, latestNodes);
|
|
1414
|
+
return latestNodes;
|
|
1415
|
+
};
|
|
1416
|
+
const $createTypeFromTextNodes = (nodes, binding) => {
|
|
1417
|
+
const type = new XmlText();
|
|
1418
|
+
$updateYText(type, nodes, binding);
|
|
1419
|
+
return type;
|
|
1420
|
+
};
|
|
1421
|
+
const createTypeFromElementNode = (node, binding) => {
|
|
1422
|
+
const type = new XmlElement(node.getType());
|
|
1423
|
+
const attrs = {
|
|
1424
|
+
...propertiesToAttributes(node, binding),
|
|
1425
|
+
...stateToAttributes(node)
|
|
1518
1426
|
};
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
}
|
|
1526
|
-
const {
|
|
1527
|
-
anchorKey,
|
|
1528
|
-
anchorOffset,
|
|
1529
|
-
focusKey,
|
|
1530
|
-
focusOffset
|
|
1531
|
-
} = $getAnchorAndFocusForUserState(binding, localState);
|
|
1532
|
-
if (anchorKey !== null && focusKey !== null) {
|
|
1533
|
-
const selection = $getSelection();
|
|
1534
|
-
if (!$isRangeSelection(selection)) {
|
|
1535
|
-
return;
|
|
1427
|
+
for (const key in attrs) {
|
|
1428
|
+
const val = attrs[key];
|
|
1429
|
+
if (val !== null) {
|
|
1430
|
+
// TODO(collab-v2): typing for XmlElement generic
|
|
1431
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1432
|
+
type.setAttribute(key, val);
|
|
1536
1433
|
}
|
|
1537
|
-
$setPoint(selection.anchor, anchorKey, anchorOffset);
|
|
1538
|
-
$setPoint(selection.focus, focusKey, focusOffset);
|
|
1539
1434
|
}
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
if (point.key !== key || point.offset !== offset) {
|
|
1543
|
-
let anchorNode = $getNodeByKey(key);
|
|
1544
|
-
if (anchorNode !== null && !$isElementNode(anchorNode) && !$isTextNode(anchorNode)) {
|
|
1545
|
-
const parent = anchorNode.getParentOrThrow();
|
|
1546
|
-
key = parent.getKey();
|
|
1547
|
-
offset = anchorNode.getIndexWithinParent();
|
|
1548
|
-
anchorNode = parent;
|
|
1549
|
-
}
|
|
1550
|
-
point.set(key, offset, $isElementNode(anchorNode) ? 'element' : 'text');
|
|
1435
|
+
if (!(node instanceof ElementNode)) {
|
|
1436
|
+
return type;
|
|
1551
1437
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1438
|
+
type.insert(0, normalizeNodeContent(node).map(n => $createTypeFromTextOrElementNode(n, binding)));
|
|
1439
|
+
binding.mapping.set(type, node);
|
|
1440
|
+
return type;
|
|
1441
|
+
};
|
|
1442
|
+
const $createTypeFromTextOrElementNode = (node, meta) => node instanceof Array ? $createTypeFromTextNodes(node, meta) : createTypeFromElementNode(node, meta);
|
|
1443
|
+
const isObject = val => typeof val === 'object' && val != null;
|
|
1444
|
+
const equalAttrs = (pattrs, yattrs) => {
|
|
1445
|
+
const keys = Object.keys(pattrs).filter(key => pattrs[key] !== null);
|
|
1446
|
+
if (yattrs == null) {
|
|
1447
|
+
return keys.length === 0;
|
|
1559
1448
|
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
return [collabNode, 0];
|
|
1567
|
-
} else {
|
|
1568
|
-
return [node, collabNodeOffset];
|
|
1569
|
-
}
|
|
1449
|
+
let eq = keys.length === Object.keys(yattrs).filter(key => yattrs[key] !== null).length;
|
|
1450
|
+
for (let i = 0; i < keys.length && eq; i++) {
|
|
1451
|
+
const key = keys[i];
|
|
1452
|
+
const l = pattrs[key];
|
|
1453
|
+
const r = yattrs[key];
|
|
1454
|
+
eq = key === 'ychange' || l === r || isObject(l) && isObject(r) && equalAttrs(l, r);
|
|
1570
1455
|
}
|
|
1571
|
-
return
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
if (
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
let lexicalOffset = 0;
|
|
1586
|
-
const children = node.getChildren();
|
|
1587
|
-
while (remainingYOffset > 0 && lexicalOffset < children.length) {
|
|
1588
|
-
const child = children[lexicalOffset];
|
|
1589
|
-
remainingYOffset -= 1;
|
|
1590
|
-
lexicalOffset += 1;
|
|
1591
|
-
if ($isTextNode(child)) {
|
|
1592
|
-
while (lexicalOffset < children.length && $isTextNode(children[lexicalOffset])) {
|
|
1593
|
-
lexicalOffset += 1;
|
|
1594
|
-
}
|
|
1456
|
+
return eq;
|
|
1457
|
+
};
|
|
1458
|
+
const normalizeNodeContent = node => {
|
|
1459
|
+
if (!(node instanceof ElementNode)) {
|
|
1460
|
+
return [];
|
|
1461
|
+
}
|
|
1462
|
+
const c = node.getChildren();
|
|
1463
|
+
const res = [];
|
|
1464
|
+
for (let i = 0; i < c.length; i++) {
|
|
1465
|
+
const n = c[i];
|
|
1466
|
+
if ($isTextNode(n)) {
|
|
1467
|
+
const textNodes = [];
|
|
1468
|
+
for (let maybeTextNode = c[i]; i < c.length && $isTextNode(maybeTextNode); maybeTextNode = c[++i]) {
|
|
1469
|
+
textNodes.push(maybeTextNode);
|
|
1595
1470
|
}
|
|
1471
|
+
i--;
|
|
1472
|
+
res.push(textNodes);
|
|
1473
|
+
} else {
|
|
1474
|
+
res.push(n);
|
|
1596
1475
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1476
|
+
}
|
|
1477
|
+
return res;
|
|
1478
|
+
};
|
|
1479
|
+
const equalYTextLText = (ytext, ltexts, binding) => {
|
|
1480
|
+
const deltas = toDelta(ytext);
|
|
1481
|
+
return deltas.length === ltexts.length && deltas.every((d, i) => {
|
|
1482
|
+
const ltext = ltexts[i];
|
|
1483
|
+
const type = d.attributes.t ?? TextNode.getType();
|
|
1484
|
+
const propertyAttrs = d.attributes.p ?? {};
|
|
1485
|
+
const stateAttrs = Object.fromEntries(Object.entries(d.attributes).filter(([k]) => k.startsWith(STATE_KEY_PREFIX)));
|
|
1486
|
+
return d.insert === ltext.getTextContent() && type === ltext.getType() && equalAttrs(propertyAttrs, propertiesToAttributes(ltext, binding)) && equalAttrs(stateAttrs, stateToAttributes(ltext));
|
|
1487
|
+
});
|
|
1488
|
+
};
|
|
1489
|
+
const equalYTypePNode = (ytype, lnode, binding) => {
|
|
1490
|
+
if (ytype instanceof XmlElement && !(lnode instanceof Array) && matchNodeName(ytype, lnode)) {
|
|
1491
|
+
const normalizedContent = normalizeNodeContent(lnode);
|
|
1492
|
+
return ytype._length === normalizedContent.length && equalAttrs(ytype.getAttributes(), {
|
|
1493
|
+
...propertiesToAttributes(lnode, binding),
|
|
1494
|
+
...stateToAttributes(lnode)
|
|
1495
|
+
}) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, normalizedContent[i], binding));
|
|
1496
|
+
}
|
|
1497
|
+
return ytype instanceof XmlText && lnode instanceof Array && equalYTextLText(ytype, lnode, binding);
|
|
1498
|
+
};
|
|
1499
|
+
const mappedIdentity = (mapped, lcontent) => mapped === lcontent || mapped instanceof Array && lcontent instanceof Array && mapped.length === lcontent.length && mapped.every((a, i) => lcontent[i] === a);
|
|
1500
|
+
const computeChildEqualityFactor = (ytype, lnode, binding) => {
|
|
1501
|
+
const yChildren = ytype.toArray();
|
|
1502
|
+
const pChildren = normalizeNodeContent(lnode);
|
|
1503
|
+
const pChildCnt = pChildren.length;
|
|
1504
|
+
const yChildCnt = yChildren.length;
|
|
1505
|
+
const minCnt = Math.min(yChildCnt, pChildCnt);
|
|
1506
|
+
let left = 0;
|
|
1507
|
+
let right = 0;
|
|
1508
|
+
let foundMappedChild = false;
|
|
1509
|
+
for (; left < minCnt; left++) {
|
|
1510
|
+
const leftY = yChildren[left];
|
|
1511
|
+
const leftP = pChildren[left];
|
|
1512
|
+
if (leftY instanceof XmlHook) {
|
|
1513
|
+
break;
|
|
1514
|
+
} else if (mappedIdentity(binding.mapping.get(leftY), leftP)) {
|
|
1515
|
+
foundMappedChild = true; // definite (good) match!
|
|
1516
|
+
} else if (!equalYTypePNode(leftY, leftP, binding)) {
|
|
1517
|
+
break;
|
|
1602
1518
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1519
|
+
}
|
|
1520
|
+
for (; left + right < minCnt; right++) {
|
|
1521
|
+
const rightY = yChildren[yChildCnt - right - 1];
|
|
1522
|
+
const rightP = pChildren[pChildCnt - right - 1];
|
|
1523
|
+
if (rightY instanceof XmlHook) {
|
|
1524
|
+
break;
|
|
1525
|
+
} else if (mappedIdentity(binding.mapping.get(rightY), rightP)) {
|
|
1526
|
+
foundMappedChild = true;
|
|
1527
|
+
} else if (!equalYTypePNode(rightY, rightP, binding)) {
|
|
1528
|
+
break;
|
|
1608
1529
|
}
|
|
1609
|
-
const textNode = nodes[i];
|
|
1610
|
-
return [textNode, Math.min(adjustedOffset, textNode.getTextContentSize())];
|
|
1611
1530
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
const
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
const awarenessState = awarenessStates[i];
|
|
1628
|
-
const [clientID, awareness] = awarenessState;
|
|
1629
|
-
if (clientID !== localClientID) {
|
|
1630
|
-
visitedClientIDs.add(clientID);
|
|
1631
|
-
const {
|
|
1632
|
-
name,
|
|
1633
|
-
color,
|
|
1634
|
-
focusing
|
|
1635
|
-
} = awareness;
|
|
1636
|
-
let selection = null;
|
|
1637
|
-
let cursor = cursors.get(clientID);
|
|
1638
|
-
if (cursor === undefined) {
|
|
1639
|
-
cursor = createCursor(name, color);
|
|
1640
|
-
cursors.set(clientID, cursor);
|
|
1641
|
-
}
|
|
1642
|
-
if (focusing) {
|
|
1643
|
-
const {
|
|
1644
|
-
anchorKey,
|
|
1645
|
-
anchorOffset,
|
|
1646
|
-
focusKey,
|
|
1647
|
-
focusOffset
|
|
1648
|
-
} = editor.read(() => $getAnchorAndFocusForUserState(binding, awareness));
|
|
1649
|
-
if (anchorKey !== null && focusKey !== null) {
|
|
1650
|
-
selection = cursor.selection;
|
|
1651
|
-
if (selection === null) {
|
|
1652
|
-
selection = createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset);
|
|
1653
|
-
} else {
|
|
1654
|
-
const anchor = selection.anchor;
|
|
1655
|
-
const focus = selection.focus;
|
|
1656
|
-
anchor.key = anchorKey;
|
|
1657
|
-
anchor.offset = anchorOffset;
|
|
1658
|
-
focus.key = focusKey;
|
|
1659
|
-
focus.offset = focusOffset;
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1531
|
+
return {
|
|
1532
|
+
equalityFactor: left + right,
|
|
1533
|
+
foundMappedChild
|
|
1534
|
+
};
|
|
1535
|
+
};
|
|
1536
|
+
const ytextTrans = ytext => {
|
|
1537
|
+
let str = '';
|
|
1538
|
+
let n = ytext._start;
|
|
1539
|
+
const nAttrs = {};
|
|
1540
|
+
while (n !== null) {
|
|
1541
|
+
if (!n.deleted) {
|
|
1542
|
+
if (n.countable && n.content instanceof ContentString) {
|
|
1543
|
+
str += n.content.str;
|
|
1544
|
+
} else if (n.content instanceof ContentFormat) {
|
|
1545
|
+
nAttrs[n.content.key] = null;
|
|
1662
1546
|
}
|
|
1663
|
-
updateCursor(binding, cursor, selection, nodeMap);
|
|
1664
1547
|
}
|
|
1548
|
+
n = n.right;
|
|
1665
1549
|
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1550
|
+
return {
|
|
1551
|
+
nAttrs,
|
|
1552
|
+
str
|
|
1553
|
+
};
|
|
1554
|
+
};
|
|
1555
|
+
const $updateYText = (ytext, ltexts, binding) => {
|
|
1556
|
+
binding.mapping.set(ytext, ltexts);
|
|
1557
|
+
const {
|
|
1558
|
+
nAttrs,
|
|
1559
|
+
str
|
|
1560
|
+
} = ytextTrans(ytext);
|
|
1561
|
+
const content = ltexts.map((node, i) => {
|
|
1562
|
+
const nodeType = node.getType();
|
|
1563
|
+
let p = propertiesToAttributes(node, binding);
|
|
1564
|
+
if (Object.keys(p).length === 0) {
|
|
1565
|
+
p = null;
|
|
1566
|
+
}
|
|
1567
|
+
return {
|
|
1568
|
+
attributes: Object.assign({}, nAttrs, {
|
|
1569
|
+
...(nodeType !== TextNode.getType() && {
|
|
1570
|
+
t: nodeType
|
|
1571
|
+
}),
|
|
1572
|
+
p,
|
|
1573
|
+
...stateToAttributes(node),
|
|
1574
|
+
...(i > 0 && {
|
|
1575
|
+
i
|
|
1576
|
+
}) // Prevent Yjs from merging text nodes itself.
|
|
1577
|
+
}),
|
|
1578
|
+
insert: node.getTextContent(),
|
|
1579
|
+
nodeKey: node.getKey()
|
|
1580
|
+
};
|
|
1581
|
+
});
|
|
1582
|
+
const nextText = content.map(c => c.insert).join('');
|
|
1583
|
+
const selection = $getSelection();
|
|
1584
|
+
let cursorOffset;
|
|
1585
|
+
if ($isRangeSelection(selection) && selection.isCollapsed()) {
|
|
1586
|
+
cursorOffset = 0;
|
|
1587
|
+
for (const c of content) {
|
|
1588
|
+
if (c.nodeKey === selection.anchor.key) {
|
|
1589
|
+
cursorOffset += selection.anchor.offset;
|
|
1590
|
+
break;
|
|
1674
1591
|
}
|
|
1592
|
+
cursorOffset += c.insert.length;
|
|
1675
1593
|
}
|
|
1676
|
-
}
|
|
1677
|
-
|
|
1678
|
-
function syncLexicalSelectionToYjs(binding, provider, prevSelection, nextSelection) {
|
|
1679
|
-
const awareness = provider.awareness;
|
|
1680
|
-
const localState = awareness.getLocalState();
|
|
1681
|
-
if (localState === null) {
|
|
1682
|
-
return;
|
|
1594
|
+
} else {
|
|
1595
|
+
cursorOffset = nextText.length;
|
|
1683
1596
|
}
|
|
1684
1597
|
const {
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1598
|
+
insert,
|
|
1599
|
+
remove,
|
|
1600
|
+
index
|
|
1601
|
+
} = simpleDiffWithCursor(str, nextText, cursorOffset);
|
|
1602
|
+
ytext.delete(index, remove);
|
|
1603
|
+
ytext.insert(index, insert);
|
|
1604
|
+
ytext.applyDelta(content.map(c => ({
|
|
1605
|
+
attributes: c.attributes,
|
|
1606
|
+
retain: c.insert.length
|
|
1607
|
+
})));
|
|
1608
|
+
};
|
|
1609
|
+
const toDelta = (ytext, snapshot, prevSnapshot, computeYChange) => {
|
|
1610
|
+
return ytext.toDelta(snapshot, prevSnapshot, computeYChange).map(delta => {
|
|
1611
|
+
const attributes = delta.attributes ?? {};
|
|
1612
|
+
if ('ychange' in attributes) {
|
|
1613
|
+
attributes[stateKeyToAttrKey('ychange')] = attributes.ychange;
|
|
1614
|
+
delete attributes.ychange;
|
|
1697
1615
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1616
|
+
return {
|
|
1617
|
+
...delta,
|
|
1618
|
+
attributes
|
|
1619
|
+
};
|
|
1620
|
+
});
|
|
1621
|
+
};
|
|
1622
|
+
const propertiesToAttributes = (node, meta) => {
|
|
1623
|
+
const defaultProperties = getDefaultNodeProperties(node, meta);
|
|
1624
|
+
const attrs = {};
|
|
1625
|
+
Object.entries(defaultProperties).forEach(([property, defaultValue]) => {
|
|
1626
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1627
|
+
const value = node[property];
|
|
1628
|
+
if (value !== defaultValue) {
|
|
1629
|
+
attrs[property] = value;
|
|
1706
1630
|
}
|
|
1631
|
+
});
|
|
1632
|
+
return attrs;
|
|
1633
|
+
};
|
|
1634
|
+
const STATE_KEY_PREFIX = 's_';
|
|
1635
|
+
const stateKeyToAttrKey = key => `s_${key}`;
|
|
1636
|
+
const attrKeyToStateKey = key => {
|
|
1637
|
+
if (!key.startsWith(STATE_KEY_PREFIX)) {
|
|
1638
|
+
throw new Error(`Invalid state key: ${key}`);
|
|
1707
1639
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
focusPos,
|
|
1715
|
-
focusing,
|
|
1716
|
-
name
|
|
1717
|
-
});
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
/*
|
|
1722
|
-
const isVisible = (item: Item, snapshot?: Snapshot): boolean =>
|
|
1723
|
-
snapshot === undefined
|
|
1724
|
-
? !item.deleted
|
|
1725
|
-
: snapshot.sv.has(item.id.client) &&
|
|
1726
|
-
snapshot.sv.get(item.id.client)! > item.id.clock &&
|
|
1727
|
-
!isDeleted(snapshot.ds, item.id);
|
|
1728
|
-
*/
|
|
1729
|
-
|
|
1730
|
-
// https://docs.yjs.dev/api/shared-types/y.xmlelement
|
|
1731
|
-
// "Define a top-level type; Note that the nodeName is always "undefined""
|
|
1732
|
-
const isRootElement = el => el.nodeName === 'UNDEFINED';
|
|
1733
|
-
const $createOrUpdateNodeFromYElement = (el, binding, keysChanged, childListChanged, snapshot, prevSnapshot, computeYChange) => {
|
|
1734
|
-
let node = binding.mapping.get(el);
|
|
1735
|
-
if (node && keysChanged && keysChanged.size === 0 && !childListChanged) {
|
|
1736
|
-
return node;
|
|
1640
|
+
return key.slice(STATE_KEY_PREFIX.length);
|
|
1641
|
+
};
|
|
1642
|
+
const stateToAttributes = node => {
|
|
1643
|
+
const state = node.__state;
|
|
1644
|
+
if (!state) {
|
|
1645
|
+
return {};
|
|
1737
1646
|
}
|
|
1738
|
-
const
|
|
1739
|
-
const
|
|
1740
|
-
const
|
|
1741
|
-
|
|
1742
|
-
throw new Error(`$createOrUpdateNodeFromYElement: Node ${type} is not registered`);
|
|
1647
|
+
const [unknown = {}, known] = state.getInternalState();
|
|
1648
|
+
const attrs = {};
|
|
1649
|
+
for (const [k, v] of Object.entries(unknown)) {
|
|
1650
|
+
attrs[stateKeyToAttrKey(k)] = v;
|
|
1743
1651
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
keysChanged = null;
|
|
1747
|
-
childListChanged = true;
|
|
1652
|
+
for (const [stateConfig, v] of known) {
|
|
1653
|
+
attrs[stateKeyToAttrKey(stateConfig.key)] = stateConfig.unparse(v);
|
|
1748
1654
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1655
|
+
return attrs;
|
|
1656
|
+
};
|
|
1657
|
+
const $updateYFragment = (y, yDomFragment, node, binding, dirtyElements) => {
|
|
1658
|
+
if (yDomFragment instanceof XmlElement && yDomFragment.nodeName !== node.getType() && !(isRootElement(yDomFragment) && node.getType() === RootNode.getType())) {
|
|
1659
|
+
throw new Error('node name mismatch!');
|
|
1660
|
+
}
|
|
1661
|
+
binding.mapping.set(yDomFragment, node);
|
|
1662
|
+
// update attributes
|
|
1663
|
+
if (yDomFragment instanceof XmlElement) {
|
|
1664
|
+
const yDomAttrs = yDomFragment.getAttributes();
|
|
1665
|
+
const lexicalAttrs = {
|
|
1666
|
+
...propertiesToAttributes(node, binding),
|
|
1667
|
+
...stateToAttributes(node)
|
|
1668
|
+
};
|
|
1669
|
+
for (const key in lexicalAttrs) {
|
|
1670
|
+
if (lexicalAttrs[key] != null) {
|
|
1671
|
+
if (yDomAttrs[key] !== lexicalAttrs[key] && key !== 'ychange') {
|
|
1672
|
+
// TODO(collab-v2): typing for XmlElement generic
|
|
1673
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1674
|
+
yDomFragment.setAttribute(key, lexicalAttrs[key]);
|
|
1765
1675
|
}
|
|
1766
1676
|
} else {
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1677
|
+
yDomFragment.removeAttribute(key);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
// remove all keys that are no longer in pAttrs
|
|
1681
|
+
for (const key in yDomAttrs) {
|
|
1682
|
+
if (lexicalAttrs[key] === undefined) {
|
|
1683
|
+
yDomFragment.removeAttribute(key);
|
|
1770
1684
|
}
|
|
1771
|
-
};
|
|
1772
|
-
{
|
|
1773
|
-
el.toArray().forEach($createChildren);
|
|
1774
1685
|
}
|
|
1775
|
-
$spliceChildren(node, children);
|
|
1776
1686
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
state[attrKeyToStateKey(k)] = attrs[k];
|
|
1687
|
+
// update children
|
|
1688
|
+
const lChildren = normalizeNodeContent(node);
|
|
1689
|
+
const lChildCnt = lChildren.length;
|
|
1690
|
+
const yChildren = yDomFragment.toArray();
|
|
1691
|
+
const yChildCnt = yChildren.length;
|
|
1692
|
+
const minCnt = Math.min(lChildCnt, yChildCnt);
|
|
1693
|
+
let left = 0;
|
|
1694
|
+
let right = 0;
|
|
1695
|
+
// find number of matching elements from left
|
|
1696
|
+
for (; left < minCnt; left++) {
|
|
1697
|
+
const leftY = yChildren[left];
|
|
1698
|
+
const leftL = lChildren[left];
|
|
1699
|
+
if (leftY instanceof XmlHook) {
|
|
1700
|
+
break;
|
|
1701
|
+
} else if (mappedIdentity(binding.mapping.get(leftY), leftL)) {
|
|
1702
|
+
if (leftL instanceof ElementNode && dirtyElements.has(leftL.getKey())) {
|
|
1703
|
+
$updateYFragment(y, leftY, leftL, binding, dirtyElements);
|
|
1704
|
+
}
|
|
1705
|
+
} else if (equalYTypePNode(leftY, leftL, binding)) {
|
|
1706
|
+
// update mapping
|
|
1707
|
+
binding.mapping.set(leftY, leftL);
|
|
1799
1708
|
} else {
|
|
1800
|
-
|
|
1709
|
+
break;
|
|
1801
1710
|
}
|
|
1802
1711
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1712
|
+
// find number of matching elements from right
|
|
1713
|
+
for (; right + left < minCnt; right++) {
|
|
1714
|
+
const rightY = yChildren[yChildCnt - right - 1];
|
|
1715
|
+
const rightL = lChildren[lChildCnt - right - 1];
|
|
1716
|
+
if (rightY instanceof XmlHook) {
|
|
1717
|
+
break;
|
|
1718
|
+
} else if (mappedIdentity(binding.mapping.get(rightY), rightL)) {
|
|
1719
|
+
if (rightL instanceof ElementNode && dirtyElements.has(rightL.getKey())) {
|
|
1720
|
+
$updateYFragment(y, rightY, rightL, binding, dirtyElements);
|
|
1812
1721
|
}
|
|
1722
|
+
} else if (equalYTypePNode(rightY, rightL, binding)) {
|
|
1723
|
+
// update mapping
|
|
1724
|
+
binding.mapping.set(rightY, rightL);
|
|
1725
|
+
} else {
|
|
1726
|
+
break;
|
|
1813
1727
|
}
|
|
1814
1728
|
}
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
const
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
const nextEndIndex = nextChildren.length - 1;
|
|
1825
|
-
let prevIndex = 0;
|
|
1826
|
-
let nextIndex = 0;
|
|
1827
|
-
while (prevIndex <= prevEndIndex && nextIndex <= nextEndIndex) {
|
|
1828
|
-
const prevKey = prevChildren[prevIndex].getKey();
|
|
1829
|
-
const nextKey = nextChildren[nextIndex].getKey();
|
|
1830
|
-
if (prevKey === nextKey) {
|
|
1831
|
-
prevIndex++;
|
|
1832
|
-
nextIndex++;
|
|
1833
|
-
continue;
|
|
1834
|
-
}
|
|
1835
|
-
const nextHasPrevKey = nextChildrenKeySet.has(prevKey);
|
|
1836
|
-
const prevHasNextKey = prevChildrenKeySet.has(nextKey);
|
|
1837
|
-
if (!nextHasPrevKey) {
|
|
1838
|
-
// If removing the last node, insert remaining new nodes immediately, otherwise if the node
|
|
1839
|
-
// cannot be empty, it will remove itself from its parent.
|
|
1840
|
-
if (nextIndex === 0 && node.getChildrenSize() === 1) {
|
|
1841
|
-
node.splice(nextIndex, 1, nextChildren.slice(nextIndex));
|
|
1842
|
-
return;
|
|
1729
|
+
// try to compare and update
|
|
1730
|
+
while (yChildCnt - left - right > 0 && lChildCnt - left - right > 0) {
|
|
1731
|
+
const leftY = yChildren[left];
|
|
1732
|
+
const leftL = lChildren[left];
|
|
1733
|
+
const rightY = yChildren[yChildCnt - right - 1];
|
|
1734
|
+
const rightL = lChildren[lChildCnt - right - 1];
|
|
1735
|
+
if (leftY instanceof XmlText && leftL instanceof Array) {
|
|
1736
|
+
if (!equalYTextLText(leftY, leftL, binding)) {
|
|
1737
|
+
$updateYText(leftY, leftL, binding);
|
|
1843
1738
|
}
|
|
1844
|
-
|
|
1845
|
-
node.splice(nextIndex, 1, []);
|
|
1846
|
-
prevIndex++;
|
|
1847
|
-
continue;
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
// Create or replace
|
|
1851
|
-
const nextChildNode = nextChildren[nextIndex];
|
|
1852
|
-
if (prevHasNextKey) {
|
|
1853
|
-
node.splice(nextIndex, 1, [nextChildNode]);
|
|
1854
|
-
prevIndex++;
|
|
1855
|
-
nextIndex++;
|
|
1739
|
+
left += 1;
|
|
1856
1740
|
} else {
|
|
1857
|
-
|
|
1858
|
-
|
|
1741
|
+
let updateLeft = leftY instanceof XmlElement && matchNodeName(leftY, leftL);
|
|
1742
|
+
let updateRight = rightY instanceof XmlElement && matchNodeName(rightY, rightL);
|
|
1743
|
+
if (updateLeft && updateRight) {
|
|
1744
|
+
// decide which which element to update
|
|
1745
|
+
const equalityLeft = computeChildEqualityFactor(leftY, leftL, binding);
|
|
1746
|
+
const equalityRight = computeChildEqualityFactor(rightY, rightL, binding);
|
|
1747
|
+
if (equalityLeft.foundMappedChild && !equalityRight.foundMappedChild) {
|
|
1748
|
+
updateRight = false;
|
|
1749
|
+
} else if (!equalityLeft.foundMappedChild && equalityRight.foundMappedChild) {
|
|
1750
|
+
updateLeft = false;
|
|
1751
|
+
} else if (equalityLeft.equalityFactor < equalityRight.equalityFactor) {
|
|
1752
|
+
updateLeft = false;
|
|
1753
|
+
} else {
|
|
1754
|
+
updateRight = false;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
if (updateLeft) {
|
|
1758
|
+
$updateYFragment(y, leftY, leftL, binding, dirtyElements);
|
|
1759
|
+
left += 1;
|
|
1760
|
+
} else if (updateRight) {
|
|
1761
|
+
$updateYFragment(y, rightY, rightL, binding, dirtyElements);
|
|
1762
|
+
right += 1;
|
|
1763
|
+
} else {
|
|
1764
|
+
binding.mapping.delete(yDomFragment.get(left));
|
|
1765
|
+
yDomFragment.delete(left, 1);
|
|
1766
|
+
yDomFragment.insert(left, [$createTypeFromTextOrElementNode(leftL, binding)]);
|
|
1767
|
+
left += 1;
|
|
1768
|
+
}
|
|
1859
1769
|
}
|
|
1860
1770
|
}
|
|
1861
|
-
const
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1771
|
+
const yDelLen = yChildCnt - left - right;
|
|
1772
|
+
if (yChildCnt === 1 && lChildCnt === 0 && yChildren[0] instanceof XmlText) {
|
|
1773
|
+
binding.mapping.delete(yChildren[0]);
|
|
1774
|
+
// Edge case handling https://github.com/yjs/y-prosemirror/issues/108
|
|
1775
|
+
// Only delete the content of the Y.Text to retain remote changes on the same Y.Text object
|
|
1776
|
+
yChildren[0].delete(0, yChildren[0].length);
|
|
1777
|
+
} else if (yDelLen > 0) {
|
|
1778
|
+
yDomFragment.slice(left, left + yDelLen).forEach(type => binding.mapping.delete(type));
|
|
1779
|
+
yDomFragment.delete(left, yDelLen);
|
|
1780
|
+
}
|
|
1781
|
+
if (left + right < lChildCnt) {
|
|
1782
|
+
const ins = [];
|
|
1783
|
+
for (let i = left; i < lChildCnt - right; i++) {
|
|
1784
|
+
ins.push($createTypeFromTextOrElementNode(lChildren[i], binding));
|
|
1785
|
+
}
|
|
1786
|
+
yDomFragment.insert(left, ins);
|
|
1867
1787
|
}
|
|
1868
1788
|
};
|
|
1869
|
-
const
|
|
1870
|
-
const deltas = toDelta(text, snapshot, prevSnapshot, computeYChange);
|
|
1789
|
+
const matchNodeName = (yElement, lnode) => !(lnode instanceof Array) && yElement.nodeName === lnode.getType();
|
|
1871
1790
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1791
|
+
const STATE_KEY = 'ychange';
|
|
1792
|
+
|
|
1793
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1794
|
+
|
|
1795
|
+
const ychangeState = createState(STATE_KEY, {
|
|
1796
|
+
isEqual: (a, b) => a === b,
|
|
1797
|
+
parse: value => value ?? null
|
|
1798
|
+
});
|
|
1799
|
+
function $getYChangeState(node) {
|
|
1800
|
+
return $getState(node, ychangeState);
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// Not exposing $setState because it should only be created by SyncV2.ts.
|
|
1804
|
+
|
|
1805
|
+
/**
|
|
1806
|
+
* Replaces the editor content with a view that compares the state between two given snapshots.
|
|
1807
|
+
* Any added or removed nodes between the two snapshots will have {@link YChange} attached to them.
|
|
1808
|
+
*
|
|
1809
|
+
* @param binding Yjs binding
|
|
1810
|
+
* @param snapshot Ending snapshot state (default: current state of the Yjs document)
|
|
1811
|
+
* @param prevSnapshot Starting snapshot state (default: empty snapshot)
|
|
1812
|
+
*/
|
|
1813
|
+
const renderSnapshot__EXPERIMENTAL = (binding, snapshot$1 = snapshot(binding.doc), prevSnapshot = emptySnapshot) => {
|
|
1814
|
+
// The document that contains the full history of this document.
|
|
1815
|
+
const {
|
|
1816
|
+
doc
|
|
1817
|
+
} = binding;
|
|
1818
|
+
if (!!doc.gc) {
|
|
1819
|
+
formatDevErrorMessage(`GC must be disabled to render snapshot`);
|
|
1820
|
+
}
|
|
1821
|
+
doc.transact(transaction => {
|
|
1822
|
+
// Before rendering, we are going to sanitize ops and split deleted ops
|
|
1823
|
+
// if they were deleted by seperate users.
|
|
1824
|
+
const pud = new PermanentUserData(doc);
|
|
1825
|
+
if (pud) {
|
|
1826
|
+
pud.dss.forEach(ds => {
|
|
1827
|
+
iterateDeletedStructs(transaction, ds, _item => {});
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
const computeYChange = (type, id) => {
|
|
1831
|
+
const user = type === 'added' ? pud.getUserByClientId(id.client) : pud.getUserByDeletedId(id);
|
|
1832
|
+
return {
|
|
1833
|
+
id,
|
|
1834
|
+
type,
|
|
1835
|
+
user: user ?? null
|
|
1836
|
+
};
|
|
1837
|
+
};
|
|
1838
|
+
binding.mapping.clear();
|
|
1839
|
+
binding.editor.update(() => {
|
|
1840
|
+
$getRoot().clear();
|
|
1841
|
+
$createOrUpdateNodeFromYElement(binding.root, binding, null, true, snapshot$1, prevSnapshot, computeYChange);
|
|
1842
|
+
});
|
|
1843
|
+
}, binding);
|
|
1844
|
+
};
|
|
1845
|
+
|
|
1846
|
+
function createRelativePosition(point, binding) {
|
|
1847
|
+
const collabNodeMap = binding.collabNodeMap;
|
|
1848
|
+
const collabNode = collabNodeMap.get(point.key);
|
|
1849
|
+
if (collabNode === undefined) {
|
|
1850
|
+
return null;
|
|
1851
|
+
}
|
|
1852
|
+
let offset = point.offset;
|
|
1853
|
+
let sharedType = collabNode.getSharedType();
|
|
1854
|
+
if (collabNode instanceof CollabTextNode) {
|
|
1855
|
+
sharedType = collabNode._parent._xmlText;
|
|
1856
|
+
const currentOffset = collabNode.getOffset();
|
|
1857
|
+
if (currentOffset === -1) {
|
|
1858
|
+
return null;
|
|
1859
|
+
}
|
|
1860
|
+
offset = currentOffset + 1 + offset;
|
|
1861
|
+
} else if (collabNode instanceof CollabElementNode && point.type === 'element') {
|
|
1862
|
+
const parent = point.getNode();
|
|
1863
|
+
if (!$isElementNode(parent)) {
|
|
1864
|
+
formatDevErrorMessage(`Element point must be an element node`);
|
|
1865
|
+
}
|
|
1866
|
+
let accumulatedOffset = 0;
|
|
1867
|
+
let i = 0;
|
|
1868
|
+
let node = parent.getFirstChild();
|
|
1869
|
+
while (node !== null && i++ < offset) {
|
|
1870
|
+
if ($isTextNode(node)) {
|
|
1871
|
+
accumulatedOffset += node.getTextContentSize() + 1;
|
|
1872
|
+
} else {
|
|
1873
|
+
accumulatedOffset++;
|
|
1883
1874
|
}
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1875
|
+
node = node.getNextSibling();
|
|
1876
|
+
}
|
|
1877
|
+
offset = accumulatedOffset;
|
|
1878
|
+
}
|
|
1879
|
+
return createRelativePositionFromTypeIndex(sharedType, offset);
|
|
1880
|
+
}
|
|
1881
|
+
function createRelativePositionV2(point, binding) {
|
|
1882
|
+
const {
|
|
1883
|
+
mapping
|
|
1884
|
+
} = binding;
|
|
1885
|
+
const {
|
|
1886
|
+
offset
|
|
1887
|
+
} = point;
|
|
1888
|
+
const node = point.getNode();
|
|
1889
|
+
const yType = mapping.getSharedType(node);
|
|
1890
|
+
if (yType === undefined) {
|
|
1891
|
+
return null;
|
|
1892
|
+
}
|
|
1893
|
+
if (point.type === 'text') {
|
|
1894
|
+
if (!$isTextNode(node)) {
|
|
1895
|
+
formatDevErrorMessage(`Text point must be a text node`);
|
|
1896
|
+
}
|
|
1897
|
+
let prevSibling = node.getPreviousSibling();
|
|
1898
|
+
let adjustedOffset = offset;
|
|
1899
|
+
while ($isTextNode(prevSibling)) {
|
|
1900
|
+
adjustedOffset += prevSibling.getTextContentSize();
|
|
1901
|
+
prevSibling = prevSibling.getPreviousSibling();
|
|
1902
|
+
}
|
|
1903
|
+
return createRelativePositionFromTypeIndex(yType, adjustedOffset);
|
|
1904
|
+
} else if (point.type === 'element') {
|
|
1905
|
+
if (!$isElementNode(node)) {
|
|
1906
|
+
formatDevErrorMessage(`Element point must be an element node`);
|
|
1907
|
+
}
|
|
1908
|
+
let i = 0;
|
|
1909
|
+
let child = node.getFirstChild();
|
|
1910
|
+
while (child !== null && i < offset) {
|
|
1911
|
+
if ($isTextNode(child)) {
|
|
1912
|
+
let nextSibling = child.getNextSibling();
|
|
1913
|
+
while ($isTextNode(nextSibling)) {
|
|
1914
|
+
nextSibling = nextSibling.getNextSibling();
|
|
1915
|
+
}
|
|
1887
1916
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1917
|
+
i++;
|
|
1918
|
+
child = child.getNextSibling();
|
|
1919
|
+
}
|
|
1920
|
+
return createRelativePositionFromTypeIndex(yType, i);
|
|
1890
1921
|
}
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
if (node.__text !== insert) {
|
|
1901
|
-
node.setTextContent(insert);
|
|
1922
|
+
return null;
|
|
1923
|
+
}
|
|
1924
|
+
function createAbsolutePosition(relativePosition, binding) {
|
|
1925
|
+
return createAbsolutePositionFromRelativePosition(relativePosition, binding.doc);
|
|
1926
|
+
}
|
|
1927
|
+
function shouldUpdatePosition(currentPos, pos) {
|
|
1928
|
+
if (currentPos == null) {
|
|
1929
|
+
if (pos != null) {
|
|
1930
|
+
return true;
|
|
1902
1931
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
...attributes.p
|
|
1906
|
-
};
|
|
1907
|
-
const state = Object.fromEntries(Object.entries(attributes).filter(([k]) => k.startsWith(STATE_KEY_PREFIX)).map(([k, v]) => [attrKeyToStateKey(k), v]));
|
|
1908
|
-
$syncPropertiesFromYjs(binding, properties, node, null);
|
|
1909
|
-
$getWritableNodeState(node).updateFromJSON(state);
|
|
1932
|
+
} else if (pos == null || !compareRelativePositions(currentPos, pos)) {
|
|
1933
|
+
return true;
|
|
1910
1934
|
}
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
return type;
|
|
1919
|
-
};
|
|
1920
|
-
const createTypeFromElementNode = (node, binding) => {
|
|
1921
|
-
const type = new XmlElement(node.getType());
|
|
1922
|
-
const attrs = {
|
|
1923
|
-
...propertiesToAttributes(node, binding),
|
|
1924
|
-
...stateToAttributes(node)
|
|
1935
|
+
return false;
|
|
1936
|
+
}
|
|
1937
|
+
function createCursor(name, color) {
|
|
1938
|
+
return {
|
|
1939
|
+
color: color,
|
|
1940
|
+
name: name,
|
|
1941
|
+
selection: null
|
|
1925
1942
|
};
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1943
|
+
}
|
|
1944
|
+
function destroySelection(binding, selection) {
|
|
1945
|
+
const cursorsContainer = binding.cursorsContainer;
|
|
1946
|
+
if (cursorsContainer !== null) {
|
|
1947
|
+
const selections = selection.selections;
|
|
1948
|
+
const selectionsLength = selections.length;
|
|
1949
|
+
for (let i = 0; i < selectionsLength; i++) {
|
|
1950
|
+
cursorsContainer.removeChild(selections[i]);
|
|
1932
1951
|
}
|
|
1933
1952
|
}
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
return type;
|
|
1940
|
-
};
|
|
1941
|
-
const $createTypeFromTextOrElementNode = (node, meta) => node instanceof Array ? $createTypeFromTextNodes(node, meta) : createTypeFromElementNode(node, meta);
|
|
1942
|
-
const isObject = val => typeof val === 'object' && val != null;
|
|
1943
|
-
const equalAttrs = (pattrs, yattrs) => {
|
|
1944
|
-
const keys = Object.keys(pattrs).filter(key => pattrs[key] !== null);
|
|
1945
|
-
if (yattrs == null) {
|
|
1946
|
-
return keys.length === 0;
|
|
1953
|
+
}
|
|
1954
|
+
function destroyCursor(binding, cursor) {
|
|
1955
|
+
const selection = cursor.selection;
|
|
1956
|
+
if (selection !== null) {
|
|
1957
|
+
destroySelection(binding, selection);
|
|
1947
1958
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1959
|
+
}
|
|
1960
|
+
function createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset) {
|
|
1961
|
+
const color = cursor.color;
|
|
1962
|
+
const caret = document.createElement('span');
|
|
1963
|
+
caret.style.cssText = `position:absolute;top:0;bottom:0;right:-1px;width:1px;background-color:${color};z-index:10;`;
|
|
1964
|
+
const name = document.createElement('span');
|
|
1965
|
+
name.textContent = cursor.name;
|
|
1966
|
+
name.style.cssText = `position:absolute;left:-2px;top:-16px;background-color:${color};color:#fff;line-height:12px;font-size:12px;padding:2px;font-family:Arial;font-weight:bold;white-space:nowrap;`;
|
|
1967
|
+
caret.appendChild(name);
|
|
1968
|
+
return {
|
|
1969
|
+
anchor: {
|
|
1970
|
+
key: anchorKey,
|
|
1971
|
+
offset: anchorOffset
|
|
1972
|
+
},
|
|
1973
|
+
caret,
|
|
1974
|
+
color,
|
|
1975
|
+
focus: {
|
|
1976
|
+
key: focusKey,
|
|
1977
|
+
offset: focusOffset
|
|
1978
|
+
},
|
|
1979
|
+
name,
|
|
1980
|
+
selections: []
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
function updateCursor(binding, cursor, nextSelection, nodeMap) {
|
|
1984
|
+
const editor = binding.editor;
|
|
1985
|
+
const rootElement = editor.getRootElement();
|
|
1986
|
+
const cursorsContainer = binding.cursorsContainer;
|
|
1987
|
+
if (cursorsContainer === null || rootElement === null) {
|
|
1988
|
+
return;
|
|
1954
1989
|
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
if (!(node instanceof ElementNode)) {
|
|
1959
|
-
return [];
|
|
1990
|
+
const cursorsContainerOffsetParent = cursorsContainer.offsetParent;
|
|
1991
|
+
if (cursorsContainerOffsetParent === null) {
|
|
1992
|
+
return;
|
|
1960
1993
|
}
|
|
1961
|
-
const
|
|
1962
|
-
const
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
const textNodes = [];
|
|
1967
|
-
for (let maybeTextNode = c[i]; i < c.length && $isTextNode(maybeTextNode); maybeTextNode = c[++i]) {
|
|
1968
|
-
textNodes.push(maybeTextNode);
|
|
1969
|
-
}
|
|
1970
|
-
i--;
|
|
1971
|
-
res.push(textNodes);
|
|
1994
|
+
const containerRect = cursorsContainerOffsetParent.getBoundingClientRect();
|
|
1995
|
+
const prevSelection = cursor.selection;
|
|
1996
|
+
if (nextSelection === null) {
|
|
1997
|
+
if (prevSelection === null) {
|
|
1998
|
+
return;
|
|
1972
1999
|
} else {
|
|
1973
|
-
|
|
2000
|
+
cursor.selection = null;
|
|
2001
|
+
destroySelection(binding, prevSelection);
|
|
2002
|
+
return;
|
|
1974
2003
|
}
|
|
2004
|
+
} else {
|
|
2005
|
+
cursor.selection = nextSelection;
|
|
1975
2006
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
const
|
|
1979
|
-
const
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
};
|
|
1988
|
-
const equalYTypePNode = (ytype, lnode, binding) => {
|
|
1989
|
-
if (ytype instanceof XmlElement && !(lnode instanceof Array) && matchNodeName(ytype, lnode)) {
|
|
1990
|
-
const normalizedContent = normalizeNodeContent(lnode);
|
|
1991
|
-
return ytype._length === normalizedContent.length && equalAttrs(ytype.getAttributes(), {
|
|
1992
|
-
...propertiesToAttributes(lnode, binding),
|
|
1993
|
-
...stateToAttributes(lnode)
|
|
1994
|
-
}) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, normalizedContent[i], binding));
|
|
1995
|
-
}
|
|
1996
|
-
return ytype instanceof XmlText && lnode instanceof Array && equalYTextLText(ytype, lnode, binding);
|
|
1997
|
-
};
|
|
1998
|
-
const mappedIdentity = (mapped, lcontent) => mapped === lcontent || mapped instanceof Array && lcontent instanceof Array && mapped.length === lcontent.length && mapped.every((a, i) => lcontent[i] === a);
|
|
1999
|
-
const computeChildEqualityFactor = (ytype, lnode, binding) => {
|
|
2000
|
-
const yChildren = ytype.toArray();
|
|
2001
|
-
const pChildren = normalizeNodeContent(lnode);
|
|
2002
|
-
const pChildCnt = pChildren.length;
|
|
2003
|
-
const yChildCnt = yChildren.length;
|
|
2004
|
-
const minCnt = Math.min(yChildCnt, pChildCnt);
|
|
2005
|
-
let left = 0;
|
|
2006
|
-
let right = 0;
|
|
2007
|
-
let foundMappedChild = false;
|
|
2008
|
-
for (; left < minCnt; left++) {
|
|
2009
|
-
const leftY = yChildren[left];
|
|
2010
|
-
const leftP = pChildren[left];
|
|
2011
|
-
if (leftY instanceof XmlHook) {
|
|
2012
|
-
break;
|
|
2013
|
-
} else if (mappedIdentity(binding.mapping.get(leftY), leftP)) {
|
|
2014
|
-
foundMappedChild = true; // definite (good) match!
|
|
2015
|
-
} else if (!equalYTypePNode(leftY, leftP, binding)) {
|
|
2016
|
-
break;
|
|
2017
|
-
}
|
|
2007
|
+
const caret = nextSelection.caret;
|
|
2008
|
+
const color = nextSelection.color;
|
|
2009
|
+
const selections = nextSelection.selections;
|
|
2010
|
+
const anchor = nextSelection.anchor;
|
|
2011
|
+
const focus = nextSelection.focus;
|
|
2012
|
+
const anchorKey = anchor.key;
|
|
2013
|
+
const focusKey = focus.key;
|
|
2014
|
+
const anchorNode = nodeMap.get(anchorKey);
|
|
2015
|
+
const focusNode = nodeMap.get(focusKey);
|
|
2016
|
+
if (anchorNode == null || focusNode == null) {
|
|
2017
|
+
return;
|
|
2018
2018
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2019
|
+
let selectionRects;
|
|
2020
|
+
|
|
2021
|
+
// In the case of a collapsed selection on a linebreak, we need
|
|
2022
|
+
// to improvise as the browser will return nothing here as <br>
|
|
2023
|
+
// apparently take up no visual space :/
|
|
2024
|
+
// This won't work in all cases, but it's better than just showing
|
|
2025
|
+
// nothing all the time.
|
|
2026
|
+
if (anchorNode === focusNode && $isLineBreakNode(anchorNode)) {
|
|
2027
|
+
const brRect = editor.getElementByKey(anchorKey).getBoundingClientRect();
|
|
2028
|
+
selectionRects = [brRect];
|
|
2029
|
+
} else {
|
|
2030
|
+
const range = createDOMRange(editor, anchorNode, anchor.offset, focusNode, focus.offset);
|
|
2031
|
+
if (range === null) {
|
|
2032
|
+
return;
|
|
2028
2033
|
}
|
|
2034
|
+
selectionRects = createRectsFromDOMRange(editor, range);
|
|
2029
2035
|
}
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2036
|
+
const selectionsLength = selections.length;
|
|
2037
|
+
const selectionRectsLength = selectionRects.length;
|
|
2038
|
+
for (let i = 0; i < selectionRectsLength; i++) {
|
|
2039
|
+
const selectionRect = selectionRects[i];
|
|
2040
|
+
let selection = selections[i];
|
|
2041
|
+
if (selection === undefined) {
|
|
2042
|
+
selection = document.createElement('span');
|
|
2043
|
+
selections[i] = selection;
|
|
2044
|
+
const selectionBg = document.createElement('span');
|
|
2045
|
+
selection.appendChild(selectionBg);
|
|
2046
|
+
cursorsContainer.appendChild(selection);
|
|
2047
|
+
}
|
|
2048
|
+
const top = selectionRect.top - containerRect.top;
|
|
2049
|
+
const left = selectionRect.left - containerRect.left;
|
|
2050
|
+
const style = `position:absolute;top:${top}px;left:${left}px;height:${selectionRect.height}px;width:${selectionRect.width}px;pointer-events:none;z-index:5;`;
|
|
2051
|
+
selection.style.cssText = style;
|
|
2052
|
+
selection.firstChild.style.cssText = `${style}left:0;top:0;background-color:${color};opacity:0.3;`;
|
|
2053
|
+
if (i === selectionRectsLength - 1) {
|
|
2054
|
+
if (caret.parentNode !== selection) {
|
|
2055
|
+
selection.appendChild(caret);
|
|
2045
2056
|
}
|
|
2046
2057
|
}
|
|
2047
|
-
|
|
2058
|
+
}
|
|
2059
|
+
for (let i = selectionsLength - 1; i >= selectionRectsLength; i--) {
|
|
2060
|
+
const selection = selections[i];
|
|
2061
|
+
cursorsContainer.removeChild(selection);
|
|
2062
|
+
selections.pop();
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
/**
|
|
2066
|
+
* @deprecated Use `$getAnchorAndFocusForUserState` instead.
|
|
2067
|
+
*/
|
|
2068
|
+
function getAnchorAndFocusCollabNodesForUserState(binding, userState) {
|
|
2069
|
+
const {
|
|
2070
|
+
anchorPos,
|
|
2071
|
+
focusPos
|
|
2072
|
+
} = userState;
|
|
2073
|
+
let anchorCollabNode = null;
|
|
2074
|
+
let anchorOffset = 0;
|
|
2075
|
+
let focusCollabNode = null;
|
|
2076
|
+
let focusOffset = 0;
|
|
2077
|
+
if (anchorPos !== null && focusPos !== null) {
|
|
2078
|
+
const anchorAbsPos = createAbsolutePosition(anchorPos, binding);
|
|
2079
|
+
const focusAbsPos = createAbsolutePosition(focusPos, binding);
|
|
2080
|
+
if (anchorAbsPos !== null && focusAbsPos !== null) {
|
|
2081
|
+
[anchorCollabNode, anchorOffset] = getCollabNodeAndOffset(anchorAbsPos.type, anchorAbsPos.index);
|
|
2082
|
+
[focusCollabNode, focusOffset] = getCollabNodeAndOffset(focusAbsPos.type, focusAbsPos.index);
|
|
2083
|
+
}
|
|
2048
2084
|
}
|
|
2049
2085
|
return {
|
|
2050
|
-
|
|
2051
|
-
|
|
2086
|
+
anchorCollabNode,
|
|
2087
|
+
anchorOffset,
|
|
2088
|
+
focusCollabNode,
|
|
2089
|
+
focusOffset
|
|
2052
2090
|
};
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
binding.mapping.set(ytext, ltexts);
|
|
2091
|
+
}
|
|
2092
|
+
function $getAnchorAndFocusForUserState(binding, userState) {
|
|
2056
2093
|
const {
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
} =
|
|
2060
|
-
const
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
if (Object.keys(p).length === 0) {
|
|
2064
|
-
p = null;
|
|
2065
|
-
}
|
|
2094
|
+
anchorPos,
|
|
2095
|
+
focusPos
|
|
2096
|
+
} = userState;
|
|
2097
|
+
const anchorAbsPos = anchorPos ? createAbsolutePosition(anchorPos, binding) : null;
|
|
2098
|
+
const focusAbsPos = focusPos ? createAbsolutePosition(focusPos, binding) : null;
|
|
2099
|
+
if (anchorAbsPos === null || focusAbsPos === null) {
|
|
2066
2100
|
return {
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
p,
|
|
2072
|
-
...stateToAttributes(node),
|
|
2073
|
-
...(i > 0 && {
|
|
2074
|
-
i
|
|
2075
|
-
}) // Prevent Yjs from merging text nodes itself.
|
|
2076
|
-
}),
|
|
2077
|
-
insert: node.getTextContent(),
|
|
2078
|
-
nodeKey: node.getKey()
|
|
2101
|
+
anchorKey: null,
|
|
2102
|
+
anchorOffset: 0,
|
|
2103
|
+
focusKey: null,
|
|
2104
|
+
focusOffset: 0
|
|
2079
2105
|
};
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2106
|
+
}
|
|
2107
|
+
if (isBindingV1(binding)) {
|
|
2108
|
+
const [anchorCollabNode, anchorOffset] = getCollabNodeAndOffset(anchorAbsPos.type, anchorAbsPos.index);
|
|
2109
|
+
const [focusCollabNode, focusOffset] = getCollabNodeAndOffset(focusAbsPos.type, focusAbsPos.index);
|
|
2110
|
+
return {
|
|
2111
|
+
anchorKey: anchorCollabNode !== null ? anchorCollabNode.getKey() : null,
|
|
2112
|
+
anchorOffset,
|
|
2113
|
+
focusKey: focusCollabNode !== null ? focusCollabNode.getKey() : null,
|
|
2114
|
+
focusOffset
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
let [anchorNode, anchorOffset] = $getNodeAndOffsetV2(binding.mapping, anchorAbsPos);
|
|
2118
|
+
let [focusNode, focusOffset] = $getNodeAndOffsetV2(binding.mapping, focusAbsPos);
|
|
2119
|
+
// For a non-collapsed selection, if the start of the selection is as the end of a text node,
|
|
2120
|
+
// move it to the beginning of the next text node (if one exists).
|
|
2121
|
+
if (focusNode && anchorNode && (focusNode !== anchorNode || focusOffset !== anchorOffset)) {
|
|
2122
|
+
const isBackwards = focusNode.isBefore(anchorNode);
|
|
2123
|
+
const startNode = isBackwards ? focusNode : anchorNode;
|
|
2124
|
+
const startOffset = isBackwards ? focusOffset : anchorOffset;
|
|
2125
|
+
if ($isTextNode(startNode) && $isTextNode(startNode.getNextSibling()) && startOffset === startNode.getTextContentSize()) {
|
|
2126
|
+
if (isBackwards) {
|
|
2127
|
+
focusNode = startNode.getNextSibling();
|
|
2128
|
+
focusOffset = 0;
|
|
2129
|
+
} else {
|
|
2130
|
+
anchorNode = startNode.getNextSibling();
|
|
2131
|
+
anchorOffset = 0;
|
|
2090
2132
|
}
|
|
2091
|
-
cursorOffset += c.insert.length;
|
|
2092
2133
|
}
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2134
|
+
}
|
|
2135
|
+
return {
|
|
2136
|
+
anchorKey: anchorNode !== null ? anchorNode.getKey() : null,
|
|
2137
|
+
anchorOffset,
|
|
2138
|
+
focusKey: focusNode !== null ? focusNode.getKey() : null,
|
|
2139
|
+
focusOffset
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
function $syncLocalCursorPosition(binding, provider) {
|
|
2143
|
+
const awareness = provider.awareness;
|
|
2144
|
+
const localState = awareness.getLocalState();
|
|
2145
|
+
if (localState === null) {
|
|
2146
|
+
return;
|
|
2095
2147
|
}
|
|
2096
2148
|
const {
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
})));
|
|
2107
|
-
};
|
|
2108
|
-
const toDelta = (ytext, snapshot, prevSnapshot, computeYChange) => {
|
|
2109
|
-
return ytext.toDelta(snapshot, prevSnapshot, computeYChange).map(delta => ({
|
|
2110
|
-
...delta,
|
|
2111
|
-
attributes: delta.attributes ?? {}
|
|
2112
|
-
}));
|
|
2113
|
-
};
|
|
2114
|
-
const propertiesToAttributes = (node, meta) => {
|
|
2115
|
-
const defaultProperties = getDefaultNodeProperties(node, meta);
|
|
2116
|
-
const attrs = {};
|
|
2117
|
-
Object.entries(defaultProperties).forEach(([property, defaultValue]) => {
|
|
2118
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2119
|
-
const value = node[property];
|
|
2120
|
-
if (value !== defaultValue) {
|
|
2121
|
-
attrs[property] = value;
|
|
2149
|
+
anchorKey,
|
|
2150
|
+
anchorOffset,
|
|
2151
|
+
focusKey,
|
|
2152
|
+
focusOffset
|
|
2153
|
+
} = $getAnchorAndFocusForUserState(binding, localState);
|
|
2154
|
+
if (anchorKey !== null && focusKey !== null) {
|
|
2155
|
+
const selection = $getSelection();
|
|
2156
|
+
if (!$isRangeSelection(selection)) {
|
|
2157
|
+
return;
|
|
2122
2158
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
};
|
|
2126
|
-
const STATE_KEY_PREFIX = 's_';
|
|
2127
|
-
const stateKeyToAttrKey = key => STATE_KEY_PREFIX + key;
|
|
2128
|
-
const attrKeyToStateKey = key => {
|
|
2129
|
-
if (!key.startsWith(STATE_KEY_PREFIX)) {
|
|
2130
|
-
throw new Error(`Invalid state key: ${key}`);
|
|
2131
|
-
}
|
|
2132
|
-
return key.slice(STATE_KEY_PREFIX.length);
|
|
2133
|
-
};
|
|
2134
|
-
const stateToAttributes = node => {
|
|
2135
|
-
const state = node.__state;
|
|
2136
|
-
if (!state) {
|
|
2137
|
-
return {};
|
|
2138
|
-
}
|
|
2139
|
-
const [unknown = {}, known] = state.getInternalState();
|
|
2140
|
-
const attrs = {};
|
|
2141
|
-
for (const [k, v] of Object.entries(unknown)) {
|
|
2142
|
-
attrs[stateKeyToAttrKey(k)] = v;
|
|
2143
|
-
}
|
|
2144
|
-
for (const [stateConfig, v] of known) {
|
|
2145
|
-
attrs[stateKeyToAttrKey(stateConfig.key)] = stateConfig.unparse(v);
|
|
2146
|
-
}
|
|
2147
|
-
return attrs;
|
|
2148
|
-
};
|
|
2149
|
-
const $updateYFragment = (y, yDomFragment, node, binding, dirtyElements) => {
|
|
2150
|
-
if (yDomFragment instanceof XmlElement && yDomFragment.nodeName !== node.getType() && !(isRootElement(yDomFragment) && node.getType() === RootNode.getType())) {
|
|
2151
|
-
throw new Error('node name mismatch!');
|
|
2159
|
+
$setPoint(selection.anchor, anchorKey, anchorOffset);
|
|
2160
|
+
$setPoint(selection.focus, focusKey, focusOffset);
|
|
2152
2161
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
if (
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
if (lexicalAttrs[key] != null) {
|
|
2163
|
-
if (yDomAttrs[key] !== lexicalAttrs[key] && key !== 'ychange') {
|
|
2164
|
-
// TODO(collab-v2): typing for XmlElement generic
|
|
2165
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2166
|
-
yDomFragment.setAttribute(key, lexicalAttrs[key]);
|
|
2167
|
-
}
|
|
2168
|
-
} else {
|
|
2169
|
-
yDomFragment.removeAttribute(key);
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
// remove all keys that are no longer in pAttrs
|
|
2173
|
-
for (const key in yDomAttrs) {
|
|
2174
|
-
if (lexicalAttrs[key] === undefined) {
|
|
2175
|
-
yDomFragment.removeAttribute(key);
|
|
2176
|
-
}
|
|
2162
|
+
}
|
|
2163
|
+
function $setPoint(point, key, offset) {
|
|
2164
|
+
if (point.key !== key || point.offset !== offset) {
|
|
2165
|
+
let anchorNode = $getNodeByKey(key);
|
|
2166
|
+
if (anchorNode !== null && !$isElementNode(anchorNode) && !$isTextNode(anchorNode)) {
|
|
2167
|
+
const parent = anchorNode.getParentOrThrow();
|
|
2168
|
+
key = parent.getKey();
|
|
2169
|
+
offset = anchorNode.getIndexWithinParent();
|
|
2170
|
+
anchorNode = parent;
|
|
2177
2171
|
}
|
|
2172
|
+
point.set(key, offset, $isElementNode(anchorNode) ? 'element' : 'text');
|
|
2178
2173
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
const
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
if (leftL instanceof ElementNode && dirtyElements.has(leftL.getKey())) {
|
|
2195
|
-
$updateYFragment(y, leftY, leftL, binding, dirtyElements);
|
|
2196
|
-
}
|
|
2197
|
-
} else if (equalYTypePNode(leftY, leftL, binding)) {
|
|
2198
|
-
// update mapping
|
|
2199
|
-
binding.mapping.set(leftY, leftL);
|
|
2174
|
+
}
|
|
2175
|
+
function getCollabNodeAndOffset(
|
|
2176
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2177
|
+
sharedType, offset) {
|
|
2178
|
+
const collabNode = sharedType._collabNode;
|
|
2179
|
+
if (collabNode === undefined) {
|
|
2180
|
+
return [null, 0];
|
|
2181
|
+
}
|
|
2182
|
+
if (collabNode instanceof CollabElementNode) {
|
|
2183
|
+
const {
|
|
2184
|
+
node,
|
|
2185
|
+
offset: collabNodeOffset
|
|
2186
|
+
} = getPositionFromElementAndOffset(collabNode, offset, true);
|
|
2187
|
+
if (node === null) {
|
|
2188
|
+
return [collabNode, 0];
|
|
2200
2189
|
} else {
|
|
2201
|
-
|
|
2190
|
+
return [node, collabNodeOffset];
|
|
2202
2191
|
}
|
|
2203
2192
|
}
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2193
|
+
return [null, 0];
|
|
2194
|
+
}
|
|
2195
|
+
function $getNodeAndOffsetV2(mapping, absolutePosition) {
|
|
2196
|
+
const yType = absolutePosition.type;
|
|
2197
|
+
const yOffset = absolutePosition.index;
|
|
2198
|
+
if (yType instanceof XmlElement) {
|
|
2199
|
+
const node = mapping.get(yType);
|
|
2200
|
+
if (node === undefined) {
|
|
2201
|
+
return [null, 0];
|
|
2202
|
+
}
|
|
2203
|
+
if (!$isElementNode(node)) {
|
|
2204
|
+
return [node, yOffset];
|
|
2205
|
+
}
|
|
2206
|
+
let remainingYOffset = yOffset;
|
|
2207
|
+
let lexicalOffset = 0;
|
|
2208
|
+
const children = node.getChildren();
|
|
2209
|
+
while (remainingYOffset > 0 && lexicalOffset < children.length) {
|
|
2210
|
+
const child = children[lexicalOffset];
|
|
2211
|
+
remainingYOffset -= 1;
|
|
2212
|
+
lexicalOffset += 1;
|
|
2213
|
+
if ($isTextNode(child)) {
|
|
2214
|
+
while (lexicalOffset < children.length && $isTextNode(children[lexicalOffset])) {
|
|
2215
|
+
lexicalOffset += 1;
|
|
2216
|
+
}
|
|
2213
2217
|
}
|
|
2214
|
-
} else if (equalYTypePNode(rightY, rightL, binding)) {
|
|
2215
|
-
// update mapping
|
|
2216
|
-
binding.mapping.set(rightY, rightL);
|
|
2217
|
-
} else {
|
|
2218
|
-
break;
|
|
2219
2218
|
}
|
|
2219
|
+
return [node, lexicalOffset];
|
|
2220
|
+
} else {
|
|
2221
|
+
const nodes = mapping.get(yType);
|
|
2222
|
+
if (nodes === undefined) {
|
|
2223
|
+
return [null, 0];
|
|
2224
|
+
}
|
|
2225
|
+
let i = 0;
|
|
2226
|
+
let adjustedOffset = yOffset;
|
|
2227
|
+
while (adjustedOffset > nodes[i].getTextContentSize() && i + 1 < nodes.length) {
|
|
2228
|
+
adjustedOffset -= nodes[i].getTextContentSize();
|
|
2229
|
+
i++;
|
|
2230
|
+
}
|
|
2231
|
+
const textNode = nodes[i];
|
|
2232
|
+
return [textNode, Math.min(adjustedOffset, textNode.getTextContentSize())];
|
|
2220
2233
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2234
|
+
}
|
|
2235
|
+
function getAwarenessStatesDefault(_binding, provider) {
|
|
2236
|
+
return provider.awareness.getStates();
|
|
2237
|
+
}
|
|
2238
|
+
function syncCursorPositions(binding, provider, options) {
|
|
2239
|
+
const {
|
|
2240
|
+
getAwarenessStates = getAwarenessStatesDefault
|
|
2241
|
+
} = options ?? {};
|
|
2242
|
+
const awarenessStates = Array.from(getAwarenessStates(binding, provider));
|
|
2243
|
+
const localClientID = binding.clientID;
|
|
2244
|
+
const cursors = binding.cursors;
|
|
2245
|
+
const editor = binding.editor;
|
|
2246
|
+
const nodeMap = editor._editorState._nodeMap;
|
|
2247
|
+
const visitedClientIDs = new Set();
|
|
2248
|
+
for (let i = 0; i < awarenessStates.length; i++) {
|
|
2249
|
+
const awarenessState = awarenessStates[i];
|
|
2250
|
+
const [clientID, awareness] = awarenessState;
|
|
2251
|
+
if (clientID !== 0 && clientID !== localClientID) {
|
|
2252
|
+
visitedClientIDs.add(clientID);
|
|
2253
|
+
const {
|
|
2254
|
+
name,
|
|
2255
|
+
color,
|
|
2256
|
+
focusing
|
|
2257
|
+
} = awareness;
|
|
2258
|
+
let selection = null;
|
|
2259
|
+
let cursor = cursors.get(clientID);
|
|
2260
|
+
if (cursor === undefined) {
|
|
2261
|
+
cursor = createCursor(name, color);
|
|
2262
|
+
cursors.set(clientID, cursor);
|
|
2230
2263
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2264
|
+
if (focusing) {
|
|
2265
|
+
const {
|
|
2266
|
+
anchorKey,
|
|
2267
|
+
anchorOffset,
|
|
2268
|
+
focusKey,
|
|
2269
|
+
focusOffset
|
|
2270
|
+
} = editor.read(() => $getAnchorAndFocusForUserState(binding, awareness));
|
|
2271
|
+
if (anchorKey !== null && focusKey !== null) {
|
|
2272
|
+
selection = cursor.selection;
|
|
2273
|
+
if (selection === null) {
|
|
2274
|
+
selection = createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset);
|
|
2275
|
+
} else {
|
|
2276
|
+
const anchor = selection.anchor;
|
|
2277
|
+
const focus = selection.focus;
|
|
2278
|
+
anchor.key = anchorKey;
|
|
2279
|
+
anchor.offset = anchorOffset;
|
|
2280
|
+
focus.key = focusKey;
|
|
2281
|
+
focus.offset = focusOffset;
|
|
2282
|
+
}
|
|
2247
2283
|
}
|
|
2248
2284
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2285
|
+
updateCursor(binding, cursor, selection, nodeMap);
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
const allClientIDs = Array.from(cursors.keys());
|
|
2289
|
+
for (let i = 0; i < allClientIDs.length; i++) {
|
|
2290
|
+
const clientID = allClientIDs[i];
|
|
2291
|
+
if (!visitedClientIDs.has(clientID)) {
|
|
2292
|
+
const cursor = cursors.get(clientID);
|
|
2293
|
+
if (cursor !== undefined) {
|
|
2294
|
+
destroyCursor(binding, cursor);
|
|
2295
|
+
cursors.delete(clientID);
|
|
2260
2296
|
}
|
|
2261
2297
|
}
|
|
2262
2298
|
}
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
} else if (yDelLen > 0) {
|
|
2270
|
-
yDomFragment.slice(left, left + yDelLen).forEach(type => binding.mapping.delete(type));
|
|
2271
|
-
yDomFragment.delete(left, yDelLen);
|
|
2299
|
+
}
|
|
2300
|
+
function syncLexicalSelectionToYjs(binding, provider, prevSelection, nextSelection) {
|
|
2301
|
+
const awareness = provider.awareness;
|
|
2302
|
+
const localState = awareness.getLocalState();
|
|
2303
|
+
if (localState === null) {
|
|
2304
|
+
return;
|
|
2272
2305
|
}
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2306
|
+
const {
|
|
2307
|
+
anchorPos: currentAnchorPos,
|
|
2308
|
+
focusPos: currentFocusPos,
|
|
2309
|
+
name,
|
|
2310
|
+
color,
|
|
2311
|
+
focusing,
|
|
2312
|
+
awarenessData
|
|
2313
|
+
} = localState;
|
|
2314
|
+
let anchorPos = null;
|
|
2315
|
+
let focusPos = null;
|
|
2316
|
+
if (nextSelection === null || currentAnchorPos !== null && !nextSelection.is(prevSelection)) {
|
|
2317
|
+
if (prevSelection === null) {
|
|
2318
|
+
return;
|
|
2277
2319
|
}
|
|
2278
|
-
yDomFragment.insert(left, ins);
|
|
2279
2320
|
}
|
|
2280
|
-
|
|
2281
|
-
|
|
2321
|
+
if ($isRangeSelection(nextSelection)) {
|
|
2322
|
+
if (isBindingV1(binding)) {
|
|
2323
|
+
anchorPos = createRelativePosition(nextSelection.anchor, binding);
|
|
2324
|
+
focusPos = createRelativePosition(nextSelection.focus, binding);
|
|
2325
|
+
} else {
|
|
2326
|
+
anchorPos = createRelativePositionV2(nextSelection.anchor, binding);
|
|
2327
|
+
focusPos = createRelativePositionV2(nextSelection.focus, binding);
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
if (shouldUpdatePosition(currentAnchorPos, anchorPos) || shouldUpdatePosition(currentFocusPos, focusPos)) {
|
|
2331
|
+
awareness.setLocalState({
|
|
2332
|
+
...localState,
|
|
2333
|
+
anchorPos,
|
|
2334
|
+
awarenessData,
|
|
2335
|
+
color,
|
|
2336
|
+
focusPos,
|
|
2337
|
+
focusing,
|
|
2338
|
+
name
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2282
2342
|
|
|
2283
2343
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2284
2344
|
function $syncStateEvent(binding, event) {
|
|
@@ -2546,6 +2606,25 @@ function syncYjsChangesToLexicalV2__EXPERIMENTAL(binding, provider, events, tran
|
|
|
2546
2606
|
tag: isFromUndoManger ? HISTORIC_TAG : COLLABORATION_TAG
|
|
2547
2607
|
});
|
|
2548
2608
|
}
|
|
2609
|
+
function syncYjsStateToLexicalV2__EXPERIMENTAL(binding, provider) {
|
|
2610
|
+
binding.mapping.clear();
|
|
2611
|
+
const editor = binding.editor;
|
|
2612
|
+
editor.update(() => {
|
|
2613
|
+
$getRoot().clear();
|
|
2614
|
+
$createOrUpdateNodeFromYElement(binding.root, binding, null, true);
|
|
2615
|
+
$addUpdateTag(COLLABORATION_TAG);
|
|
2616
|
+
}, {
|
|
2617
|
+
// Need any text node normalization to be synchronously updated back to Yjs, otherwise the
|
|
2618
|
+
// binding.mapping will get out of sync.
|
|
2619
|
+
discrete: true,
|
|
2620
|
+
onUpdate: () => {
|
|
2621
|
+
syncCursorPositions(binding, provider);
|
|
2622
|
+
editor.update(() => $ensureEditorNotEmpty());
|
|
2623
|
+
},
|
|
2624
|
+
skipTransforms: true,
|
|
2625
|
+
tag: COLLABORATION_TAG
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2549
2628
|
function syncLexicalUpdateToYjsV2__EXPERIMENTAL(binding, provider, prevEditorState, currEditorState, dirtyElements, normalizedNodes, tags) {
|
|
2550
2629
|
const isFromYjs = tags.has(COLLABORATION_TAG) || tags.has(HISTORIC_TAG);
|
|
2551
2630
|
if (isFromYjs && normalizedNodes.size === 0) {
|
|
@@ -2579,6 +2658,8 @@ function syncLexicalUpdateToYjsV2__EXPERIMENTAL(binding, provider, prevEditorSta
|
|
|
2579
2658
|
|
|
2580
2659
|
const CONNECTED_COMMAND = createCommand('CONNECTED_COMMAND');
|
|
2581
2660
|
const TOGGLE_CONNECT_COMMAND = createCommand('TOGGLE_CONNECT_COMMAND');
|
|
2661
|
+
const DIFF_VERSIONS_COMMAND__EXPERIMENTAL = createCommand('DIFF_VERSIONS_COMMAND');
|
|
2662
|
+
const CLEAR_DIFF_VERSIONS_COMMAND__EXPERIMENTAL = createCommand('CLEAR_DIFF_VERSIONS_COMMAND');
|
|
2582
2663
|
function createUndoManager(binding, root) {
|
|
2583
2664
|
return new UndoManager(root, {
|
|
2584
2665
|
trackedOrigins: new Set([binding, null])
|
|
@@ -2613,4 +2694,4 @@ function setLocalStateFocus(provider, name, color, focusing, awarenessData) {
|
|
|
2613
2694
|
awareness.setLocalState(localState);
|
|
2614
2695
|
}
|
|
2615
2696
|
|
|
2616
|
-
export { CONNECTED_COMMAND, TOGGLE_CONNECT_COMMAND, createBinding, createBindingV2__EXPERIMENTAL, createUndoManager, getAnchorAndFocusCollabNodesForUserState, initLocalState, setLocalStateFocus, syncCursorPositions, syncLexicalUpdateToYjs, syncLexicalUpdateToYjsV2__EXPERIMENTAL, syncYjsChangesToLexical, syncYjsChangesToLexicalV2__EXPERIMENTAL };
|
|
2697
|
+
export { $getYChangeState, CLEAR_DIFF_VERSIONS_COMMAND__EXPERIMENTAL, CONNECTED_COMMAND, DIFF_VERSIONS_COMMAND__EXPERIMENTAL, TOGGLE_CONNECT_COMMAND, createBinding, createBindingV2__EXPERIMENTAL, createUndoManager, getAnchorAndFocusCollabNodesForUserState, initLocalState, renderSnapshot__EXPERIMENTAL, setLocalStateFocus, syncCursorPositions, syncLexicalUpdateToYjs, syncLexicalUpdateToYjsV2__EXPERIMENTAL, syncYjsChangesToLexical, syncYjsChangesToLexicalV2__EXPERIMENTAL, syncYjsStateToLexicalV2__EXPERIMENTAL };
|