@lexical/yjs 0.36.3-nightly.20251006.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/LexicalYjs.dev.js CHANGED
@@ -35,30 +35,62 @@ function formatDevErrorMessage(message) {
35
35
  *
36
36
  */
37
37
 
38
- class CollabLineBreakNode {
39
- _map;
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(map, parent) {
68
+ constructor(xmlElem, parent, type) {
44
69
  this._key = '';
45
- this._map = map;
70
+ this._xmlElem = xmlElem;
46
71
  this._parent = parent;
47
- this._type = 'linebreak';
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.$isLineBreakNode(node) ? node : null;
52
- }
53
- getKey() {
54
- return this._key;
83
+ return lexical.$isDecoratorNode(node) ? node : null;
55
84
  }
56
85
  getSharedType() {
57
- return this._map;
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 $createCollabLineBreakNode(map, parent) {
77
- const collabNode = new CollabLineBreakNode(map, parent);
78
- map._collabNode = collabNode;
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
- function simpleDiffWithCursor(a, b, cursor) {
91
- const aLength = a.length;
92
- const bLength = b.length;
93
- let left = 0; // number of same characters counting from left
94
- let right = 0; // number of same characters counting from right
95
- // Iterate left to the right until we find a changed character
96
- // First iteration considers the current cursor position
97
- while (left < aLength && left < bLength && a[left] === b[left] && left < cursor) {
98
- left++;
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
- // Iterate right to the left until we find a changed character
101
- while (right + left < aLength && right + left < bLength && a[aLength - right - 1] === b[bLength - right - 1]) {
102
- right++;
146
+ getNode() {
147
+ const node = lexical.$getNodeByKey(this._key);
148
+ return lexical.$isLineBreakNode(node) ? node : null;
103
149
  }
104
- // Try to iterate left further to the right without caring about the current cursor position
105
- while (right + left < aLength && right + left < bLength && a[left] === b[left]) {
106
- left++;
150
+ getKey() {
151
+ return this._key;
107
152
  }
108
- return {
109
- index: left,
110
- insert: b.slice(left, bLength - right),
111
- remove: aLength - left - right
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
- const baseExcludedProperties = new Set(['__key', '__parent', '__next', '__prev', '__state']);
218
- const elementExcludedProperties = new Set(['__first', '__last', '__size']);
219
- const rootExcludedProperties = new Set(['__cachedText']);
220
- const textExcludedProperties = new Set(['__text']);
221
- function isExcludedProperty(name, node, binding) {
222
- if (baseExcludedProperties.has(name) || typeof node[name] === 'function') {
223
- return true;
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
- if (lexical.$isTextNode(node)) {
226
- if (textExcludedProperties.has(name)) {
227
- return true;
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
- const nodeKlass = node.constructor;
235
- const excludedProperties = binding.excludedProperties.get(nodeKlass);
236
- return excludedProperties != null && excludedProperties.has(name);
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
- return properties;
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
- collabNode._key = lexicalNode.__key;
294
- return collabNode;
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
- return type;
302
- }
303
- function $getOrInitCollabNodeFromSharedType(binding, sharedType, parent) {
304
- const collabNode = sharedType._collabNode;
305
- if (collabNode === undefined) {
306
- const registeredNodes = binding.editor._nodes;
307
- const type = getNodeTypeFromSharedType(sharedType);
308
- if (!(typeof type === 'string')) {
309
- formatDevErrorMessage(`Expected shared type to include type attribute`);
310
- }
311
- const nodeInfo = registeredNodes.get(type);
312
- if (!(nodeInfo !== undefined)) {
313
- formatDevErrorMessage(`Node ${type} is not registered`);
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 createBindingV2__EXPERIMENTAL(editor, id, doc, docMap, options = {}) {
1209
- if (!(doc !== undefined && doc !== null)) {
1210
- formatDevErrorMessage(`createBinding: doc is null or undefined`);
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 isBindingV1(binding) {
1223
- return Object.hasOwn(binding, 'collabNodeMap');
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
- function createRelativePosition(point, binding) {
1227
- const collabNodeMap = binding.collabNodeMap;
1228
- const collabNode = collabNodeMap.get(point.key);
1229
- if (collabNode === undefined) {
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
- let offset = point.offset;
1233
- let sharedType = collabNode.getSharedType();
1234
- if (collabNode instanceof CollabTextNode) {
1235
- sharedType = collabNode._parent._xmlText;
1236
- const currentOffset = collabNode.getOffset();
1237
- if (currentOffset === -1) {
1238
- return null;
1239
- }
1240
- offset = currentOffset + 1 + offset;
1241
- } else if (collabNode instanceof CollabElementNode && point.type === 'element') {
1242
- const parent = point.getNode();
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
- if (point.type === 'text') {
1274
- if (!lexical.$isTextNode(node)) {
1275
- formatDevErrorMessage(`Text point must be a text node`);
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
- let i = 0;
1289
- let child = node.getFirstChild();
1290
- while (child !== null && i < offset) {
1291
- if (lexical.$isTextNode(child)) {
1292
- let nextSibling = child.getNextSibling();
1293
- while (lexical.$isTextNode(nextSibling)) {
1294
- nextSibling = nextSibling.getNextSibling();
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
- i++;
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 createAbsolutePosition(relativePosition, binding) {
1305
- return yjs.createAbsolutePositionFromRelativePosition(relativePosition, binding.doc);
1127
+ function spliceString(str, index, delCount, newText) {
1128
+ return str.slice(0, index) + newText + str.slice(index + delCount);
1306
1129
  }
1307
- function shouldUpdatePosition(currentPos, pos) {
1308
- if (currentPos == null) {
1309
- if (pos != null) {
1310
- return true;
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
- color: color,
1320
- name: name,
1321
- selection: null
1171
+ length: 0,
1172
+ node: null,
1173
+ nodeIndex: 0,
1174
+ offset: 0
1322
1175
  };
1323
1176
  }
1324
- function destroySelection(binding, selection) {
1325
- const cursorsContainer = binding.cursorsContainer;
1326
- if (cursorsContainer !== null) {
1327
- const selections = selection.selections;
1328
- const selectionsLength = selections.length;
1329
- for (let i = 0; i < selectionsLength; i++) {
1330
- cursorsContainer.removeChild(selections[i]);
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
- function destroyCursor(binding, cursor) {
1335
- const selection = cursor.selection;
1336
- if (selection !== null) {
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
- function createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset) {
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
- const cursorsContainerOffsetParent = cursorsContainer.offsetParent;
1371
- if (cursorsContainerOffsetParent === null) {
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
- const containerRect = cursorsContainerOffsetParent.getBoundingClientRect();
1375
- const prevSelection = cursor.selection;
1376
- if (nextSelection === null) {
1377
- if (prevSelection === null) {
1378
- return;
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
- cursor.selection = null;
1381
- destroySelection(binding, prevSelection);
1382
- return;
1276
+ yjs.typeListToArraySnapshot(el, new yjs.Snapshot(prevSnapshot.ds, snapshot.sv)).forEach($createChildren);
1383
1277
  }
1384
- } else {
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
- // In the case of a collapsed selection on a linebreak, we need
1402
- // to improvise as the browser will return nothing here as <br>
1403
- // apparently take up no visual space :/
1404
- // This won't work in all cases, but it's better than just showing
1405
- // nothing all the time.
1406
- if (anchorNode === focusNode && lexical.$isLineBreakNode(anchorNode)) {
1407
- const brRect = editor.getElementByKey(anchorKey).getBoundingClientRect();
1408
- selectionRects = [brRect];
1409
- } else {
1410
- const range = selection.createDOMRange(editor, anchorNode, anchor.offset, focusNode, focus.offset);
1411
- if (range === null) {
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 selectionsLength = selections.length;
1417
- const selectionRectsLength = selectionRects.length;
1418
- for (let i = 0; i < selectionRectsLength; i++) {
1419
- const selectionRect = selectionRects[i];
1420
- let selection = selections[i];
1421
- if (selection === undefined) {
1422
- selection = document.createElement('span');
1423
- selections[i] = selection;
1424
- const selectionBg = document.createElement('span');
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
- const top = selectionRect.top - containerRect.top;
1429
- const left = selectionRect.left - containerRect.left;
1430
- const style = `position:absolute;top:${top}px;left:${left}px;height:${selectionRect.height}px;width:${selectionRect.width}px;pointer-events:none;z-index:5;`;
1431
- selection.style.cssText = style;
1432
- selection.firstChild.style.cssText = `${style}left:0;top:0;background-color:${color};opacity:0.3;`;
1433
- if (i === selectionRectsLength - 1) {
1434
- if (caret.parentNode !== selection) {
1435
- selection.appendChild(caret);
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
- for (let i = selectionsLength - 1; i >= selectionRectsLength; i--) {
1440
- const selection = selections[i];
1441
- cursorsContainer.removeChild(selection);
1442
- selections.pop();
1443
- }
1444
- }
1445
- /**
1446
- * @deprecated Use `$getAnchorAndFocusForUserState` instead.
1447
- */
1448
- function getAnchorAndFocusCollabNodesForUserState(binding, userState) {
1449
- const {
1450
- anchorPos,
1451
- focusPos
1452
- } = userState;
1453
- let anchorCollabNode = null;
1454
- let anchorOffset = 0;
1455
- let focusCollabNode = null;
1456
- let focusOffset = 0;
1457
- if (anchorPos !== null && focusPos !== null) {
1458
- const anchorAbsPos = createAbsolutePosition(anchorPos, binding);
1459
- const focusAbsPos = createAbsolutePosition(focusPos, binding);
1460
- if (anchorAbsPos !== null && focusAbsPos !== null) {
1461
- [anchorCollabNode, anchorOffset] = getCollabNodeAndOffset(anchorAbsPos.type, anchorAbsPos.index);
1462
- [focusCollabNode, focusOffset] = getCollabNodeAndOffset(focusAbsPos.type, focusAbsPos.index);
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
- return {
1466
- anchorCollabNode,
1467
- anchorOffset,
1468
- focusCollabNode,
1469
- focusOffset
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
- let [anchorNode, anchorOffset] = $getNodeAndOffsetV2(binding.mapping, anchorAbsPos);
1498
- let [focusNode, focusOffset] = $getNodeAndOffsetV2(binding.mapping, focusAbsPos);
1499
- // For a non-collapsed selection, if the start of the selection is as the end of a text node,
1500
- // move it to the beginning of the next text node (if one exists).
1501
- if (focusNode && anchorNode && (focusNode !== anchorNode || focusOffset !== anchorOffset)) {
1502
- const isBackwards = focusNode.isBefore(anchorNode);
1503
- const startNode = isBackwards ? focusNode : anchorNode;
1504
- const startOffset = isBackwards ? focusOffset : anchorOffset;
1505
- if (lexical.$isTextNode(startNode) && lexical.$isTextNode(startNode.getNextSibling()) && startOffset === startNode.getTextContentSize()) {
1506
- if (isBackwards) {
1507
- focusNode = startNode.getNextSibling();
1508
- focusOffset = 0;
1509
- } else {
1510
- anchorNode = startNode.getNextSibling();
1511
- anchorOffset = 0;
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
- return {
1516
- anchorKey: anchorNode !== null ? anchorNode.getKey() : null,
1517
- anchorOffset,
1518
- focusKey: focusNode !== null ? focusNode.getKey() : null,
1519
- focusOffset
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
- function $syncLocalCursorPosition(binding, provider) {
1523
- const awareness = provider.awareness;
1524
- const localState = awareness.getLocalState();
1525
- if (localState === null) {
1526
- return;
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
- function $setPoint(point, key, offset) {
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
- function getCollabNodeAndOffset(
1556
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1557
- sharedType, offset) {
1558
- const collabNode = sharedType._collabNode;
1559
- if (collabNode === undefined) {
1560
- return [null, 0];
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
- if (collabNode instanceof CollabElementNode) {
1563
- const {
1564
- node,
1565
- offset: collabNodeOffset
1566
- } = getPositionFromElementAndOffset(collabNode, offset, true);
1567
- if (node === null) {
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 [null, 0];
1574
- }
1575
- function $getNodeAndOffsetV2(mapping, absolutePosition) {
1576
- const yType = absolutePosition.type;
1577
- const yOffset = absolutePosition.index;
1578
- if (yType instanceof yjs.XmlElement) {
1579
- const node = mapping.get(yType);
1580
- if (node === undefined) {
1581
- return [null, 0];
1582
- }
1583
- if (!lexical.$isElementNode(node)) {
1584
- return [node, yOffset];
1585
- }
1586
- let remainingYOffset = yOffset;
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
- return [node, lexicalOffset];
1600
- } else {
1601
- const nodes = mapping.get(yType);
1602
- if (nodes === undefined) {
1603
- return [null, 0];
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
- let i = 0;
1606
- let adjustedOffset = yOffset;
1607
- while (adjustedOffset > nodes[i].getTextContentSize() && i + 1 < nodes.length) {
1608
- adjustedOffset -= nodes[i].getTextContentSize();
1609
- i++;
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
- function getAwarenessStatesDefault(_binding, provider) {
1616
- return provider.awareness.getStates();
1617
- }
1618
- function syncCursorPositions(binding, provider, options) {
1619
- const {
1620
- getAwarenessStates = getAwarenessStatesDefault
1621
- } = options ?? {};
1622
- const awarenessStates = Array.from(getAwarenessStates(binding, provider));
1623
- const localClientID = binding.clientID;
1624
- const cursors = binding.cursors;
1625
- const editor = binding.editor;
1626
- const nodeMap = editor._editorState._nodeMap;
1627
- const visitedClientIDs = new Set();
1628
- for (let i = 0; i < awarenessStates.length; i++) {
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
- const allClientIDs = Array.from(cursors.keys());
1669
- for (let i = 0; i < allClientIDs.length; i++) {
1670
- const clientID = allClientIDs[i];
1671
- if (!visitedClientIDs.has(clientID)) {
1672
- const cursor = cursors.get(clientID);
1673
- if (cursor !== undefined) {
1674
- destroyCursor(binding, cursor);
1675
- cursors.delete(clientID);
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
- anchorPos: currentAnchorPos,
1688
- focusPos: currentFocusPos,
1689
- name,
1690
- color,
1691
- focusing,
1692
- awarenessData
1693
- } = localState;
1694
- let anchorPos = null;
1695
- let focusPos = null;
1696
- if (nextSelection === null || currentAnchorPos !== null && !nextSelection.is(prevSelection)) {
1697
- if (prevSelection === null) {
1698
- return;
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
- if (lexical.$isRangeSelection(nextSelection)) {
1702
- if (isBindingV1(binding)) {
1703
- anchorPos = createRelativePosition(nextSelection.anchor, binding);
1704
- focusPos = createRelativePosition(nextSelection.focus, binding);
1705
- } else {
1706
- anchorPos = createRelativePositionV2(nextSelection.anchor, binding);
1707
- focusPos = createRelativePositionV2(nextSelection.focus, binding);
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
- if (shouldUpdatePosition(currentAnchorPos, anchorPos) || shouldUpdatePosition(currentFocusPos, focusPos)) {
1711
- awareness.setLocalState({
1712
- ...localState,
1713
- anchorPos,
1714
- awarenessData,
1715
- color,
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 type = isRootElement(el) ? lexical.RootNode.getType() : el.nodeName;
1741
- const registeredNodes = binding.editor._nodes;
1742
- const nodeInfo = registeredNodes.get(type);
1743
- if (nodeInfo === undefined) {
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
- if (!node) {
1747
- node = new nodeInfo.klass();
1748
- keysChanged = null;
1749
- childListChanged = true;
1654
+ for (const [stateConfig, v] of known) {
1655
+ attrs[stateKeyToAttrKey(stateConfig.key)] = stateConfig.unparse(v);
1750
1656
  }
1751
- if (childListChanged && node instanceof lexical.ElementNode) {
1752
- const children = [];
1753
- const $createChildren = childType => {
1754
- if (childType instanceof yjs.XmlElement) {
1755
- const n = $createOrUpdateNodeFromYElement(childType, binding, new Set(), false, snapshot, prevSnapshot, computeYChange);
1756
- if (n !== null) {
1757
- children.push(n);
1758
- }
1759
- } else if (childType instanceof yjs.XmlText) {
1760
- const ns = $createOrUpdateTextNodesFromYText(childType, binding, snapshot, prevSnapshot, computeYChange);
1761
- if (ns !== null) {
1762
- ns.forEach(textchild => {
1763
- if (textchild !== null) {
1764
- children.push(textchild);
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
- formatDevErrorMessage(`XmlHook is not supported`);
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
- const attrs = el.getAttributes(snapshot);
1780
- // TODO(collab-v2): support for ychange
1781
- /*
1782
- if (snapshot !== undefined) {
1783
- if (!isVisible(el._item!, snapshot)) {
1784
- attrs.ychange = computeYChange
1785
- ? computeYChange('removed', el._item!.id)
1786
- : {type: 'removed'};
1787
- } else if (!isVisible(el._item!, prevSnapshot)) {
1788
- attrs.ychange = computeYChange
1789
- ? computeYChange('added', el._item!.id)
1790
- : {type: 'added'};
1791
- }
1792
- }
1793
- */
1794
- const properties = {
1795
- ...getDefaultNodeProperties(node, binding)
1796
- };
1797
- const state = {};
1798
- for (const k in attrs) {
1799
- if (k.startsWith(STATE_KEY_PREFIX)) {
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
- properties[k] = attrs[k];
1711
+ break;
1803
1712
  }
1804
1713
  }
1805
- $syncPropertiesFromYjs(binding, properties, node, keysChanged);
1806
- if (!keysChanged) {
1807
- lexical.$getWritableNodeState(node).updateFromJSON(state);
1808
- } else {
1809
- const stateKeysChanged = Object.keys(state).filter(k => keysChanged.has(stateKeyToAttrKey(k)));
1810
- if (stateKeysChanged.length > 0) {
1811
- const writableState = lexical.$getWritableNodeState(node);
1812
- for (const k of stateKeysChanged) {
1813
- writableState.updateFromUnknown(k, state[k]);
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
- const latestNode = node.getLatest();
1818
- binding.mapping.set(el, latestNode);
1819
- return latestNode;
1820
- };
1821
- const $spliceChildren = (node, nextChildren) => {
1822
- const prevChildren = node.getChildren();
1823
- const prevChildrenKeySet = new Set(prevChildren.map(child => child.getKey()));
1824
- const nextChildrenKeySet = new Set(nextChildren.map(child => child.getKey()));
1825
- const prevEndIndex = prevChildren.length - 1;
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
- // Remove
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
- node.splice(nextIndex, 0, [nextChildNode]);
1860
- nextIndex++;
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 appendNewChildren = prevIndex > prevEndIndex;
1864
- const removeOldChildren = nextIndex > nextEndIndex;
1865
- if (appendNewChildren && !removeOldChildren) {
1866
- node.append(...nextChildren.slice(nextIndex));
1867
- } else if (removeOldChildren && !appendNewChildren) {
1868
- node.splice(nextChildren.length, node.getChildrenSize() - nextChildren.length, []);
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 $createOrUpdateTextNodesFromYText = (text, binding, snapshot, prevSnapshot, computeYChange) => {
1872
- const deltas = toDelta(text, snapshot, prevSnapshot, computeYChange);
1791
+ const matchNodeName = (yElement, lnode) => !(lnode instanceof Array) && yElement.nodeName === lnode.getType();
1873
1792
 
1874
- // Use existing text nodes if the count and types all align, otherwise throw out the existing
1875
- // nodes and create new ones.
1876
- let nodes = binding.mapping.get(text) ?? [];
1877
- const nodeTypes = deltas.map(delta => delta.attributes.t ?? lexical.TextNode.getType());
1878
- const canReuseNodes = nodes.length === nodeTypes.length && nodes.every((node, i) => node.getType() === nodeTypes[i]);
1879
- if (!canReuseNodes) {
1880
- const registeredNodes = binding.editor._nodes;
1881
- nodes = nodeTypes.map(type => {
1882
- const nodeInfo = registeredNodes.get(type);
1883
- if (nodeInfo === undefined) {
1884
- throw new Error(`$createTextNodesFromYText: Node ${type} is not registered`);
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
- const node = new nodeInfo.klass();
1887
- if (!lexical.$isTextNode(node)) {
1888
- throw new Error(`$createTextNodesFromYText: Node ${type} is not a TextNode`);
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
- return node;
1891
- });
1919
+ i++;
1920
+ child = child.getNextSibling();
1921
+ }
1922
+ return yjs.createRelativePositionFromTypeIndex(yType, i);
1892
1923
  }
1893
-
1894
- // Sync text, properties and state to the text nodes.
1895
- for (let i = 0; i < deltas.length; i++) {
1896
- const node = nodes[i];
1897
- const delta = deltas[i];
1898
- const {
1899
- attributes,
1900
- insert
1901
- } = delta;
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
- const properties = {
1906
- ...getDefaultNodeProperties(node, binding),
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
- const latestNodes = nodes.map(node => node.getLatest());
1914
- binding.mapping.set(text, latestNodes);
1915
- return latestNodes;
1916
- };
1917
- const $createTypeFromTextNodes = (nodes, binding) => {
1918
- const type = new yjs.XmlText();
1919
- $updateYText(type, nodes, binding);
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
- for (const key in attrs) {
1929
- const val = attrs[key];
1930
- if (val !== null) {
1931
- // TODO(collab-v2): typing for XmlElement generic
1932
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1933
- type.setAttribute(key, val);
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
- if (!(node instanceof lexical.ElementNode)) {
1937
- return type;
1938
- }
1939
- type.insert(0, normalizeNodeContent(node).map(n => $createTypeFromTextOrElementNode(n, binding)));
1940
- binding.mapping.set(type, node);
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
- let eq = keys.length === Object.keys(yattrs).filter(key => yattrs[key] !== null).length;
1951
- for (let i = 0; i < keys.length && eq; i++) {
1952
- const key = keys[i];
1953
- const l = pattrs[key];
1954
- const r = yattrs[key];
1955
- eq = key === 'ychange' || l === r || isObject(l) && isObject(r) && equalAttrs(l, r);
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
- return eq;
1958
- };
1959
- const normalizeNodeContent = node => {
1960
- if (!(node instanceof lexical.ElementNode)) {
1961
- return [];
1992
+ const cursorsContainerOffsetParent = cursorsContainer.offsetParent;
1993
+ if (cursorsContainerOffsetParent === null) {
1994
+ return;
1962
1995
  }
1963
- const c = node.getChildren();
1964
- const res = [];
1965
- for (let i = 0; i < c.length; i++) {
1966
- const n = c[i];
1967
- if (lexical.$isTextNode(n)) {
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
- res.push(n);
2002
+ cursor.selection = null;
2003
+ destroySelection(binding, prevSelection);
2004
+ return;
1976
2005
  }
2006
+ } else {
2007
+ cursor.selection = nextSelection;
1977
2008
  }
1978
- return res;
1979
- };
1980
- const equalYTextLText = (ytext, ltexts, binding) => {
1981
- const deltas = toDelta(ytext);
1982
- return deltas.length === ltexts.length && deltas.every((d, i) => {
1983
- const ltext = ltexts[i];
1984
- const type = d.attributes.t ?? lexical.TextNode.getType();
1985
- const propertyAttrs = d.attributes.p ?? {};
1986
- const stateAttrs = Object.fromEntries(Object.entries(d.attributes).filter(([k]) => k.startsWith(STATE_KEY_PREFIX)));
1987
- return d.insert === ltext.getTextContent() && type === ltext.getType() && equalAttrs(propertyAttrs, propertiesToAttributes(ltext, binding)) && equalAttrs(stateAttrs, stateToAttributes(ltext));
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
- for (; left + right < minCnt; right++) {
2022
- const rightY = yChildren[yChildCnt - right - 1];
2023
- const rightP = pChildren[pChildCnt - right - 1];
2024
- if (rightY instanceof yjs.XmlHook) {
2025
- break;
2026
- } else if (mappedIdentity(binding.mapping.get(rightY), rightP)) {
2027
- foundMappedChild = true;
2028
- } else if (!equalYTypePNode(rightY, rightP, binding)) {
2029
- break;
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
- return {
2033
- equalityFactor: left + right,
2034
- foundMappedChild
2035
- };
2036
- };
2037
- const ytextTrans = ytext => {
2038
- let str = '';
2039
- let n = ytext._start;
2040
- const nAttrs = {};
2041
- while (n !== null) {
2042
- if (!n.deleted) {
2043
- if (n.countable && n.content instanceof yjs.ContentString) {
2044
- str += n.content.str;
2045
- } else if (n.content instanceof yjs.ContentFormat) {
2046
- nAttrs[n.content.key] = null;
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
- n = n.right;
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
- nAttrs,
2053
- str
2088
+ anchorCollabNode,
2089
+ anchorOffset,
2090
+ focusCollabNode,
2091
+ focusOffset
2054
2092
  };
2055
- };
2056
- const $updateYText = (ytext, ltexts, binding) => {
2057
- binding.mapping.set(ytext, ltexts);
2093
+ }
2094
+ function $getAnchorAndFocusForUserState(binding, userState) {
2058
2095
  const {
2059
- nAttrs,
2060
- str
2061
- } = ytextTrans(ytext);
2062
- const content = ltexts.map((node, i) => {
2063
- const nodeType = node.getType();
2064
- let p = propertiesToAttributes(node, binding);
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
- attributes: Object.assign({}, nAttrs, {
2070
- ...(nodeType !== lexical.TextNode.getType() && {
2071
- t: nodeType
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
- const nextText = content.map(c => c.insert).join('');
2084
- const selection = lexical.$getSelection();
2085
- let cursorOffset;
2086
- if (lexical.$isRangeSelection(selection) && selection.isCollapsed()) {
2087
- cursorOffset = 0;
2088
- for (const c of content) {
2089
- if (c.nodeKey === selection.anchor.key) {
2090
- cursorOffset += selection.anchor.offset;
2091
- break;
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
- } else {
2096
- cursorOffset = nextText.length;
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
- insert,
2100
- remove,
2101
- index
2102
- } = simpleDiffWithCursor(str, nextText, cursorOffset);
2103
- ytext.delete(index, remove);
2104
- ytext.insert(index, insert);
2105
- ytext.applyDelta(content.map(c => ({
2106
- attributes: c.attributes,
2107
- retain: c.insert.length
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
- return attrs;
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
- binding.mapping.set(yDomFragment, node);
2156
- // update attributes
2157
- if (yDomFragment instanceof yjs.XmlElement) {
2158
- const yDomAttrs = yDomFragment.getAttributes();
2159
- const lexicalAttrs = {
2160
- ...propertiesToAttributes(node, binding),
2161
- ...stateToAttributes(node)
2162
- };
2163
- for (const key in lexicalAttrs) {
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
- // update children
2182
- const lChildren = normalizeNodeContent(node);
2183
- const lChildCnt = lChildren.length;
2184
- const yChildren = yDomFragment.toArray();
2185
- const yChildCnt = yChildren.length;
2186
- const minCnt = Math.min(lChildCnt, yChildCnt);
2187
- let left = 0;
2188
- let right = 0;
2189
- // find number of matching elements from left
2190
- for (; left < minCnt; left++) {
2191
- const leftY = yChildren[left];
2192
- const leftL = lChildren[left];
2193
- if (leftY instanceof yjs.XmlHook) {
2194
- break;
2195
- } else if (mappedIdentity(binding.mapping.get(leftY), leftL)) {
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
- break;
2192
+ return [node, collabNodeOffset];
2204
2193
  }
2205
2194
  }
2206
- // find number of matching elements from right
2207
- for (; right + left < minCnt; right++) {
2208
- const rightY = yChildren[yChildCnt - right - 1];
2209
- const rightL = lChildren[lChildCnt - right - 1];
2210
- if (rightY instanceof yjs.XmlHook) {
2211
- break;
2212
- } else if (mappedIdentity(binding.mapping.get(rightY), rightL)) {
2213
- if (rightL instanceof lexical.ElementNode && dirtyElements.has(rightL.getKey())) {
2214
- $updateYFragment(y, rightY, rightL, binding, dirtyElements);
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
- // try to compare and update
2224
- while (yChildCnt - left - right > 0 && lChildCnt - left - right > 0) {
2225
- const leftY = yChildren[left];
2226
- const leftL = lChildren[left];
2227
- const rightY = yChildren[yChildCnt - right - 1];
2228
- const rightL = lChildren[lChildCnt - right - 1];
2229
- if (leftY instanceof yjs.XmlText && leftL instanceof Array) {
2230
- if (!equalYTextLText(leftY, leftL, binding)) {
2231
- $updateYText(leftY, leftL, binding);
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
- left += 1;
2234
- } else {
2235
- let updateLeft = leftY instanceof yjs.XmlElement && matchNodeName(leftY, leftL);
2236
- let updateRight = rightY instanceof yjs.XmlElement && matchNodeName(rightY, rightL);
2237
- if (updateLeft && updateRight) {
2238
- // decide which which element to update
2239
- const equalityLeft = computeChildEqualityFactor(leftY, leftL, binding);
2240
- const equalityRight = computeChildEqualityFactor(rightY, rightL, binding);
2241
- if (equalityLeft.foundMappedChild && !equalityRight.foundMappedChild) {
2242
- updateRight = false;
2243
- } else if (!equalityLeft.foundMappedChild && equalityRight.foundMappedChild) {
2244
- updateLeft = false;
2245
- } else if (equalityLeft.equalityFactor < equalityRight.equalityFactor) {
2246
- updateLeft = false;
2247
- } else {
2248
- updateRight = false;
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
- if (updateLeft) {
2252
- $updateYFragment(y, leftY, leftL, binding, dirtyElements);
2253
- left += 1;
2254
- } else if (updateRight) {
2255
- $updateYFragment(y, rightY, rightL, binding, dirtyElements);
2256
- right += 1;
2257
- } else {
2258
- binding.mapping.delete(yDomFragment.get(left));
2259
- yDomFragment.delete(left, 1);
2260
- yDomFragment.insert(left, [$createTypeFromTextOrElementNode(leftL, binding)]);
2261
- left += 1;
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
- const yDelLen = yChildCnt - left - right;
2266
- if (yChildCnt === 1 && lChildCnt === 0 && yChildren[0] instanceof yjs.XmlText) {
2267
- binding.mapping.delete(yChildren[0]);
2268
- // Edge case handling https://github.com/yjs/y-prosemirror/issues/108
2269
- // Only delete the content of the Y.Text to retain remote changes on the same Y.Text object
2270
- yChildren[0].delete(0, yChildren[0].length);
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
- if (left + right < lChildCnt) {
2276
- const ins = [];
2277
- for (let i = left; i < lChildCnt - right; i++) {
2278
- ins.push($createTypeFromTextOrElementNode(lChildren[i], binding));
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
- const matchNodeName = (yElement, lnode) => !(lnode instanceof Array) && yElement.nodeName === lnode.getType();
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;