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