@lexical/yjs 0.36.3-nightly.20251002.0 → 0.36.3-nightly.20251003.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/Bindings.d.ts +22 -6
- package/CollabV2Mapping.d.ts +24 -0
- package/LexicalYjs.dev.js +993 -60
- package/LexicalYjs.dev.mjs +993 -63
- package/LexicalYjs.js.flow +1 -1
- package/LexicalYjs.mjs +4 -1
- package/LexicalYjs.node.mjs +4 -1
- package/LexicalYjs.prod.js +1 -1
- package/LexicalYjs.prod.mjs +1 -1
- package/SyncCursors.d.ts +15 -6
- package/SyncEditorStates.d.ts +5 -1
- package/SyncV2.d.ts +14 -0
- package/Utils.d.ts +7 -3
- package/index.d.ts +6 -6
- package/package.json +4 -4
package/LexicalYjs.dev.mjs
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { $getNodeByKey, $isLineBreakNode, $isTextNode, $getSelection, $isRangeSelection, createEditor, $isElementNode, $isDecoratorNode, $isRootNode, $getWritableNodeState, $getRoot, $getNodeByKeyOrThrow, removeFromParent,
|
|
10
|
-
import { XmlText, Map as Map$1, XmlElement, Doc, createAbsolutePositionFromRelativePosition, createRelativePositionFromTypeIndex, compareRelativePositions, YMapEvent, YTextEvent, YXmlEvent, UndoManager } from 'yjs';
|
|
9
|
+
import { $getNodeByKey, $isLineBreakNode, $isTextNode, $getSelection, $isRangeSelection, createEditor, $isElementNode, $isDecoratorNode, $isRootNode, $getWritableNodeState, $getRoot, $getNodeByKeyOrThrow, removeFromParent, RootNode, ElementNode, TextNode, COLLABORATION_TAG, HISTORIC_TAG, $addUpdateTag, SKIP_SCROLL_INTO_VIEW_TAG, $createParagraphNode, createCommand } from 'lexical';
|
|
10
|
+
import { XmlText, Map as Map$1, XmlElement, Doc, createAbsolutePositionFromRelativePosition, createRelativePositionFromTypeIndex, compareRelativePositions, XmlHook, ContentString, ContentFormat, iterateDeletedStructs, Item, YMapEvent, YTextEvent, YXmlEvent, UndoManager } from 'yjs';
|
|
11
11
|
import { $createChildrenArray } from '@lexical/offset';
|
|
12
12
|
import { createDOMRange, createRectsFromDOMRange } from '@lexical/selection';
|
|
13
13
|
|
|
@@ -233,6 +233,35 @@ function isExcludedProperty(name, node, binding) {
|
|
|
233
233
|
const excludedProperties = binding.excludedProperties.get(nodeKlass);
|
|
234
234
|
return excludedProperties != null && excludedProperties.has(name);
|
|
235
235
|
}
|
|
236
|
+
function initializeNodeProperties(binding) {
|
|
237
|
+
const {
|
|
238
|
+
editor,
|
|
239
|
+
nodeProperties
|
|
240
|
+
} = binding;
|
|
241
|
+
editor.update(() => {
|
|
242
|
+
editor._nodes.forEach(nodeInfo => {
|
|
243
|
+
const node = new nodeInfo.klass();
|
|
244
|
+
const defaultProperties = {};
|
|
245
|
+
for (const [property, value] of Object.entries(node)) {
|
|
246
|
+
if (!isExcludedProperty(property, node, binding)) {
|
|
247
|
+
defaultProperties[property] = value;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
nodeProperties.set(node.__type, Object.freeze(defaultProperties));
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function getDefaultNodeProperties(node, binding) {
|
|
255
|
+
const type = node.__type;
|
|
256
|
+
const {
|
|
257
|
+
nodeProperties
|
|
258
|
+
} = binding;
|
|
259
|
+
const properties = nodeProperties.get(type);
|
|
260
|
+
if (!(properties !== undefined)) {
|
|
261
|
+
formatDevErrorMessage(`Node properties for ${type} not initialized for sync`);
|
|
262
|
+
}
|
|
263
|
+
return properties;
|
|
264
|
+
}
|
|
236
265
|
function $createCollabNodeFromLexicalNode(binding, lexicalNode, parent) {
|
|
237
266
|
const nodeType = lexicalNode.__type;
|
|
238
267
|
let collabNode;
|
|
@@ -323,16 +352,16 @@ function createLexicalNodeFromCollabNode(binding, collabNode, parentKey) {
|
|
|
323
352
|
return lexicalNode;
|
|
324
353
|
}
|
|
325
354
|
function $syncPropertiesFromYjs(binding, sharedType, lexicalNode, keysChanged) {
|
|
326
|
-
const properties = keysChanged === null ? sharedType instanceof Map$1 ? Array.from(sharedType.keys()) : Object.keys(sharedType.getAttributes()) : Array.from(keysChanged);
|
|
355
|
+
const properties = keysChanged === null ? sharedType instanceof Map$1 ? Array.from(sharedType.keys()) : sharedType instanceof XmlText || sharedType instanceof XmlElement ? Object.keys(sharedType.getAttributes()) : Object.keys(sharedType) : Array.from(keysChanged);
|
|
327
356
|
let writableNode;
|
|
328
357
|
for (let i = 0; i < properties.length; i++) {
|
|
329
358
|
const property = properties[i];
|
|
330
359
|
if (isExcludedProperty(property, lexicalNode, binding)) {
|
|
331
|
-
if (property === '__state') {
|
|
360
|
+
if (property === '__state' && isBindingV1(binding)) {
|
|
332
361
|
if (!writableNode) {
|
|
333
362
|
writableNode = lexicalNode.getWritable();
|
|
334
363
|
}
|
|
335
|
-
$syncNodeStateToLexical(
|
|
364
|
+
$syncNodeStateToLexical(sharedType, writableNode);
|
|
336
365
|
}
|
|
337
366
|
continue;
|
|
338
367
|
}
|
|
@@ -363,8 +392,10 @@ function $syncPropertiesFromYjs(binding, sharedType, lexicalNode, keysChanged) {
|
|
|
363
392
|
function sharedTypeGet(sharedType, property) {
|
|
364
393
|
if (sharedType instanceof Map$1) {
|
|
365
394
|
return sharedType.get(property);
|
|
366
|
-
} else {
|
|
395
|
+
} else if (sharedType instanceof XmlText || sharedType instanceof XmlElement) {
|
|
367
396
|
return sharedType.getAttribute(property);
|
|
397
|
+
} else {
|
|
398
|
+
return sharedType[property];
|
|
368
399
|
}
|
|
369
400
|
}
|
|
370
401
|
function sharedTypeSet(sharedType, property, nextValue) {
|
|
@@ -374,7 +405,7 @@ function sharedTypeSet(sharedType, property, nextValue) {
|
|
|
374
405
|
sharedType.setAttribute(property, nextValue);
|
|
375
406
|
}
|
|
376
407
|
}
|
|
377
|
-
function $syncNodeStateToLexical(
|
|
408
|
+
function $syncNodeStateToLexical(sharedType, lexicalNode) {
|
|
378
409
|
const existingState = sharedTypeGet(sharedType, '__state');
|
|
379
410
|
if (!(existingState instanceof Map$1)) {
|
|
380
411
|
return;
|
|
@@ -414,15 +445,7 @@ function syncNodeStateFromLexical(binding, sharedType, prevLexicalNode, nextLexi
|
|
|
414
445
|
}
|
|
415
446
|
}
|
|
416
447
|
function syncPropertiesFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode) {
|
|
417
|
-
const
|
|
418
|
-
const nodeProperties = binding.nodeProperties;
|
|
419
|
-
let properties = nodeProperties.get(type);
|
|
420
|
-
if (properties === undefined) {
|
|
421
|
-
properties = Object.keys(nextLexicalNode).filter(property => {
|
|
422
|
-
return !isExcludedProperty(property, nextLexicalNode, binding);
|
|
423
|
-
});
|
|
424
|
-
nodeProperties.set(type, properties);
|
|
425
|
-
}
|
|
448
|
+
const properties = Object.keys(getDefaultNodeProperties(nextLexicalNode, binding));
|
|
426
449
|
const EditorClass = binding.editor.constructor;
|
|
427
450
|
syncNodeStateFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode);
|
|
428
451
|
for (let i = 0; i < properties.length; i++) {
|
|
@@ -1063,16 +1086,98 @@ function $createCollabElementNode(xmlText, parent, type) {
|
|
|
1063
1086
|
return collabNode;
|
|
1064
1087
|
}
|
|
1065
1088
|
|
|
1066
|
-
|
|
1089
|
+
// Stores mappings between Yjs shared types and the Lexical nodes they were last associated with.
|
|
1090
|
+
class CollabV2Mapping {
|
|
1091
|
+
_nodeMap = new Map();
|
|
1092
|
+
_sharedTypeToNodeKeys = new Map();
|
|
1093
|
+
_nodeKeyToSharedType = new Map();
|
|
1094
|
+
set(sharedType, node) {
|
|
1095
|
+
const isArray = node instanceof Array;
|
|
1096
|
+
|
|
1097
|
+
// Clear all existing associations for this key.
|
|
1098
|
+
this.delete(sharedType);
|
|
1099
|
+
|
|
1100
|
+
// If nodes were associated with other shared types, remove those associations.
|
|
1101
|
+
const nodes = isArray ? node : [node];
|
|
1102
|
+
for (const n of nodes) {
|
|
1103
|
+
const key = n.getKey();
|
|
1104
|
+
if (this._nodeKeyToSharedType.has(key)) {
|
|
1105
|
+
const otherSharedType = this._nodeKeyToSharedType.get(key);
|
|
1106
|
+
const keyIndex = this._sharedTypeToNodeKeys.get(otherSharedType).indexOf(key);
|
|
1107
|
+
if (keyIndex !== -1) {
|
|
1108
|
+
this._sharedTypeToNodeKeys.get(otherSharedType).splice(keyIndex, 1);
|
|
1109
|
+
}
|
|
1110
|
+
this._nodeKeyToSharedType.delete(key);
|
|
1111
|
+
this._nodeMap.delete(key);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (sharedType instanceof XmlText) {
|
|
1115
|
+
if (!isArray) {
|
|
1116
|
+
formatDevErrorMessage(`Text nodes must be mapped as an array`);
|
|
1117
|
+
}
|
|
1118
|
+
if (node.length === 0) {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
this._sharedTypeToNodeKeys.set(sharedType, node.map(n => n.getKey()));
|
|
1122
|
+
for (const n of node) {
|
|
1123
|
+
this._nodeMap.set(n.getKey(), n);
|
|
1124
|
+
this._nodeKeyToSharedType.set(n.getKey(), sharedType);
|
|
1125
|
+
}
|
|
1126
|
+
} else {
|
|
1127
|
+
if (!!isArray) {
|
|
1128
|
+
formatDevErrorMessage(`Element nodes must be mapped as a single node`);
|
|
1129
|
+
}
|
|
1130
|
+
if (!!$isTextNode(node)) {
|
|
1131
|
+
formatDevErrorMessage(`Text nodes must be mapped to XmlText`);
|
|
1132
|
+
}
|
|
1133
|
+
this._sharedTypeToNodeKeys.set(sharedType, [node.getKey()]);
|
|
1134
|
+
this._nodeMap.set(node.getKey(), node);
|
|
1135
|
+
this._nodeKeyToSharedType.set(node.getKey(), sharedType);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
get(sharedType) {
|
|
1139
|
+
const nodes = this._sharedTypeToNodeKeys.get(sharedType);
|
|
1140
|
+
if (nodes === undefined) {
|
|
1141
|
+
return undefined;
|
|
1142
|
+
}
|
|
1143
|
+
if (sharedType instanceof XmlText) {
|
|
1144
|
+
const arr = Array.from(nodes.map(nodeKey => this._nodeMap.get(nodeKey)));
|
|
1145
|
+
return arr.length > 0 ? arr : undefined;
|
|
1146
|
+
}
|
|
1147
|
+
return this._nodeMap.get(nodes[0]);
|
|
1148
|
+
}
|
|
1149
|
+
getSharedType(node) {
|
|
1150
|
+
return this._nodeKeyToSharedType.get(node.getKey());
|
|
1151
|
+
}
|
|
1152
|
+
delete(sharedType) {
|
|
1153
|
+
const nodeKeys = this._sharedTypeToNodeKeys.get(sharedType);
|
|
1154
|
+
if (nodeKeys === undefined) {
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
for (const nodeKey of nodeKeys) {
|
|
1158
|
+
this._nodeMap.delete(nodeKey);
|
|
1159
|
+
this._nodeKeyToSharedType.delete(nodeKey);
|
|
1160
|
+
}
|
|
1161
|
+
this._sharedTypeToNodeKeys.delete(sharedType);
|
|
1162
|
+
}
|
|
1163
|
+
deleteNode(nodeKey) {
|
|
1164
|
+
const sharedType = this._nodeKeyToSharedType.get(nodeKey);
|
|
1165
|
+
if (sharedType) {
|
|
1166
|
+
this.delete(sharedType);
|
|
1167
|
+
}
|
|
1168
|
+
this._nodeMap.delete(nodeKey);
|
|
1169
|
+
}
|
|
1170
|
+
has(sharedType) {
|
|
1171
|
+
return this._sharedTypeToNodeKeys.has(sharedType);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function createBaseBinding(editor, id, doc, docMap, excludedProperties) {
|
|
1067
1176
|
if (!(doc !== undefined && doc !== null)) {
|
|
1068
1177
|
formatDevErrorMessage(`createBinding: doc is null or undefined`);
|
|
1069
1178
|
}
|
|
1070
|
-
const
|
|
1071
|
-
const root = $createCollabElementNode(rootXmlText, null, 'root');
|
|
1072
|
-
root._key = 'root';
|
|
1073
|
-
return {
|
|
1179
|
+
const binding = {
|
|
1074
1180
|
clientID: doc.clientID,
|
|
1075
|
-
collabNodeMap: new Map(),
|
|
1076
1181
|
cursors: new Map(),
|
|
1077
1182
|
cursorsContainer: null,
|
|
1078
1183
|
doc,
|
|
@@ -1080,10 +1185,41 @@ function createBinding(editor, provider, id, doc, docMap, excludedProperties) {
|
|
|
1080
1185
|
editor,
|
|
1081
1186
|
excludedProperties: excludedProperties || new Map(),
|
|
1082
1187
|
id,
|
|
1083
|
-
nodeProperties: new Map()
|
|
1188
|
+
nodeProperties: new Map()
|
|
1189
|
+
};
|
|
1190
|
+
initializeNodeProperties(binding);
|
|
1191
|
+
return binding;
|
|
1192
|
+
}
|
|
1193
|
+
function createBinding(editor, provider, id, doc, docMap, excludedProperties) {
|
|
1194
|
+
if (!(doc !== undefined && doc !== null)) {
|
|
1195
|
+
formatDevErrorMessage(`createBinding: doc is null or undefined`);
|
|
1196
|
+
}
|
|
1197
|
+
const rootXmlText = doc.get('root', XmlText);
|
|
1198
|
+
const root = $createCollabElementNode(rootXmlText, null, 'root');
|
|
1199
|
+
root._key = 'root';
|
|
1200
|
+
return {
|
|
1201
|
+
...createBaseBinding(editor, id, doc, docMap, excludedProperties),
|
|
1202
|
+
collabNodeMap: new Map(),
|
|
1084
1203
|
root
|
|
1085
1204
|
};
|
|
1086
1205
|
}
|
|
1206
|
+
function createBindingV2__EXPERIMENTAL(editor, id, doc, docMap, options = {}) {
|
|
1207
|
+
if (!(doc !== undefined && doc !== null)) {
|
|
1208
|
+
formatDevErrorMessage(`createBinding: doc is null or undefined`);
|
|
1209
|
+
}
|
|
1210
|
+
const {
|
|
1211
|
+
excludedProperties,
|
|
1212
|
+
rootName = 'root-v2'
|
|
1213
|
+
} = options;
|
|
1214
|
+
return {
|
|
1215
|
+
...createBaseBinding(editor, id, doc, docMap, excludedProperties),
|
|
1216
|
+
mapping: new CollabV2Mapping(),
|
|
1217
|
+
root: doc.get(rootName, XmlElement)
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function isBindingV1(binding) {
|
|
1221
|
+
return Object.hasOwn(binding, 'collabNodeMap');
|
|
1222
|
+
}
|
|
1087
1223
|
|
|
1088
1224
|
function createRelativePosition(point, binding) {
|
|
1089
1225
|
const collabNodeMap = binding.collabNodeMap;
|
|
@@ -1120,6 +1256,49 @@ function createRelativePosition(point, binding) {
|
|
|
1120
1256
|
}
|
|
1121
1257
|
return createRelativePositionFromTypeIndex(sharedType, offset);
|
|
1122
1258
|
}
|
|
1259
|
+
function createRelativePositionV2(point, binding) {
|
|
1260
|
+
const {
|
|
1261
|
+
mapping
|
|
1262
|
+
} = binding;
|
|
1263
|
+
const {
|
|
1264
|
+
offset
|
|
1265
|
+
} = point;
|
|
1266
|
+
const node = point.getNode();
|
|
1267
|
+
const yType = mapping.getSharedType(node);
|
|
1268
|
+
if (yType === undefined) {
|
|
1269
|
+
return null;
|
|
1270
|
+
}
|
|
1271
|
+
if (point.type === 'text') {
|
|
1272
|
+
if (!$isTextNode(node)) {
|
|
1273
|
+
formatDevErrorMessage(`Text point must be a text node`);
|
|
1274
|
+
}
|
|
1275
|
+
let prevSibling = node.getPreviousSibling();
|
|
1276
|
+
let adjustedOffset = offset;
|
|
1277
|
+
while ($isTextNode(prevSibling)) {
|
|
1278
|
+
adjustedOffset += prevSibling.getTextContentSize();
|
|
1279
|
+
prevSibling = prevSibling.getPreviousSibling();
|
|
1280
|
+
}
|
|
1281
|
+
return createRelativePositionFromTypeIndex(yType, adjustedOffset);
|
|
1282
|
+
} else if (point.type === 'element') {
|
|
1283
|
+
if (!$isElementNode(node)) {
|
|
1284
|
+
formatDevErrorMessage(`Element point must be an element node`);
|
|
1285
|
+
}
|
|
1286
|
+
let i = 0;
|
|
1287
|
+
let child = node.getFirstChild();
|
|
1288
|
+
while (child !== null && i < offset) {
|
|
1289
|
+
if ($isTextNode(child)) {
|
|
1290
|
+
let nextSibling = child.getNextSibling();
|
|
1291
|
+
while ($isTextNode(nextSibling)) {
|
|
1292
|
+
nextSibling = nextSibling.getNextSibling();
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
i++;
|
|
1296
|
+
child = child.getNextSibling();
|
|
1297
|
+
}
|
|
1298
|
+
return createRelativePositionFromTypeIndex(yType, i);
|
|
1299
|
+
}
|
|
1300
|
+
return null;
|
|
1301
|
+
}
|
|
1123
1302
|
function createAbsolutePosition(relativePosition, binding) {
|
|
1124
1303
|
return createAbsolutePositionFromRelativePosition(relativePosition, binding.doc);
|
|
1125
1304
|
}
|
|
@@ -1261,6 +1440,9 @@ function updateCursor(binding, cursor, nextSelection, nodeMap) {
|
|
|
1261
1440
|
selections.pop();
|
|
1262
1441
|
}
|
|
1263
1442
|
}
|
|
1443
|
+
/**
|
|
1444
|
+
* @deprecated Use `$getAnchorAndFocusForUserState` instead.
|
|
1445
|
+
*/
|
|
1264
1446
|
function getAnchorAndFocusCollabNodesForUserState(binding, userState) {
|
|
1265
1447
|
const {
|
|
1266
1448
|
anchorPos,
|
|
@@ -1285,6 +1467,56 @@ function getAnchorAndFocusCollabNodesForUserState(binding, userState) {
|
|
|
1285
1467
|
focusOffset
|
|
1286
1468
|
};
|
|
1287
1469
|
}
|
|
1470
|
+
function $getAnchorAndFocusForUserState(binding, userState) {
|
|
1471
|
+
const {
|
|
1472
|
+
anchorPos,
|
|
1473
|
+
focusPos
|
|
1474
|
+
} = userState;
|
|
1475
|
+
const anchorAbsPos = anchorPos ? createAbsolutePosition(anchorPos, binding) : null;
|
|
1476
|
+
const focusAbsPos = focusPos ? createAbsolutePosition(focusPos, binding) : null;
|
|
1477
|
+
if (anchorAbsPos === null || focusAbsPos === null) {
|
|
1478
|
+
return {
|
|
1479
|
+
anchorKey: null,
|
|
1480
|
+
anchorOffset: 0,
|
|
1481
|
+
focusKey: null,
|
|
1482
|
+
focusOffset: 0
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
if (isBindingV1(binding)) {
|
|
1486
|
+
const [anchorCollabNode, anchorOffset] = getCollabNodeAndOffset(anchorAbsPos.type, anchorAbsPos.index);
|
|
1487
|
+
const [focusCollabNode, focusOffset] = getCollabNodeAndOffset(focusAbsPos.type, focusAbsPos.index);
|
|
1488
|
+
return {
|
|
1489
|
+
anchorKey: anchorCollabNode !== null ? anchorCollabNode.getKey() : null,
|
|
1490
|
+
anchorOffset,
|
|
1491
|
+
focusKey: focusCollabNode !== null ? focusCollabNode.getKey() : null,
|
|
1492
|
+
focusOffset
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
let [anchorNode, anchorOffset] = $getNodeAndOffsetV2(binding.mapping, anchorAbsPos);
|
|
1496
|
+
let [focusNode, focusOffset] = $getNodeAndOffsetV2(binding.mapping, focusAbsPos);
|
|
1497
|
+
// For a non-collapsed selection, if the start of the selection is as the end of a text node,
|
|
1498
|
+
// move it to the beginning of the next text node (if one exists).
|
|
1499
|
+
if (focusNode && anchorNode && (focusNode !== anchorNode || focusOffset !== anchorOffset)) {
|
|
1500
|
+
const isBackwards = focusNode.isBefore(anchorNode);
|
|
1501
|
+
const startNode = isBackwards ? focusNode : anchorNode;
|
|
1502
|
+
const startOffset = isBackwards ? focusOffset : anchorOffset;
|
|
1503
|
+
if ($isTextNode(startNode) && $isTextNode(startNode.getNextSibling()) && startOffset === startNode.getTextContentSize()) {
|
|
1504
|
+
if (isBackwards) {
|
|
1505
|
+
focusNode = startNode.getNextSibling();
|
|
1506
|
+
focusOffset = 0;
|
|
1507
|
+
} else {
|
|
1508
|
+
anchorNode = startNode.getNextSibling();
|
|
1509
|
+
anchorOffset = 0;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
return {
|
|
1514
|
+
anchorKey: anchorNode !== null ? anchorNode.getKey() : null,
|
|
1515
|
+
anchorOffset,
|
|
1516
|
+
focusKey: focusNode !== null ? focusNode.getKey() : null,
|
|
1517
|
+
focusOffset
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1288
1520
|
function $syncLocalCursorPosition(binding, provider) {
|
|
1289
1521
|
const awareness = provider.awareness;
|
|
1290
1522
|
const localState = awareness.getLocalState();
|
|
@@ -1292,14 +1524,12 @@ function $syncLocalCursorPosition(binding, provider) {
|
|
|
1292
1524
|
return;
|
|
1293
1525
|
}
|
|
1294
1526
|
const {
|
|
1295
|
-
|
|
1527
|
+
anchorKey,
|
|
1296
1528
|
anchorOffset,
|
|
1297
|
-
|
|
1529
|
+
focusKey,
|
|
1298
1530
|
focusOffset
|
|
1299
|
-
} =
|
|
1300
|
-
if (
|
|
1301
|
-
const anchorKey = anchorCollabNode.getKey();
|
|
1302
|
-
const focusKey = focusCollabNode.getKey();
|
|
1531
|
+
} = $getAnchorAndFocusForUserState(binding, localState);
|
|
1532
|
+
if (anchorKey !== null && focusKey !== null) {
|
|
1303
1533
|
const selection = $getSelection();
|
|
1304
1534
|
if (!$isRangeSelection(selection)) {
|
|
1305
1535
|
return;
|
|
@@ -1340,6 +1570,46 @@ sharedType, offset) {
|
|
|
1340
1570
|
}
|
|
1341
1571
|
return [null, 0];
|
|
1342
1572
|
}
|
|
1573
|
+
function $getNodeAndOffsetV2(mapping, absolutePosition) {
|
|
1574
|
+
const yType = absolutePosition.type;
|
|
1575
|
+
const yOffset = absolutePosition.index;
|
|
1576
|
+
if (yType instanceof XmlElement) {
|
|
1577
|
+
const node = mapping.get(yType);
|
|
1578
|
+
if (node === undefined) {
|
|
1579
|
+
return [null, 0];
|
|
1580
|
+
}
|
|
1581
|
+
if (!$isElementNode(node)) {
|
|
1582
|
+
return [node, yOffset];
|
|
1583
|
+
}
|
|
1584
|
+
let remainingYOffset = yOffset;
|
|
1585
|
+
let lexicalOffset = 0;
|
|
1586
|
+
const children = node.getChildren();
|
|
1587
|
+
while (remainingYOffset > 0 && lexicalOffset < children.length) {
|
|
1588
|
+
const child = children[lexicalOffset];
|
|
1589
|
+
remainingYOffset -= 1;
|
|
1590
|
+
lexicalOffset += 1;
|
|
1591
|
+
if ($isTextNode(child)) {
|
|
1592
|
+
while (lexicalOffset < children.length && $isTextNode(children[lexicalOffset])) {
|
|
1593
|
+
lexicalOffset += 1;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
return [node, lexicalOffset];
|
|
1598
|
+
} else {
|
|
1599
|
+
const nodes = mapping.get(yType);
|
|
1600
|
+
if (nodes === undefined) {
|
|
1601
|
+
return [null, 0];
|
|
1602
|
+
}
|
|
1603
|
+
let i = 0;
|
|
1604
|
+
let adjustedOffset = yOffset;
|
|
1605
|
+
while (adjustedOffset > nodes[i].getTextContentSize() && i + 1 < nodes.length) {
|
|
1606
|
+
adjustedOffset -= nodes[i].getTextContentSize();
|
|
1607
|
+
i++;
|
|
1608
|
+
}
|
|
1609
|
+
const textNode = nodes[i];
|
|
1610
|
+
return [textNode, Math.min(adjustedOffset, textNode.getTextContentSize())];
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1343
1613
|
function getAwarenessStatesDefault(_binding, provider) {
|
|
1344
1614
|
return provider.awareness.getStates();
|
|
1345
1615
|
}
|
|
@@ -1371,14 +1641,12 @@ function syncCursorPositions(binding, provider, options) {
|
|
|
1371
1641
|
}
|
|
1372
1642
|
if (focusing) {
|
|
1373
1643
|
const {
|
|
1374
|
-
|
|
1644
|
+
anchorKey,
|
|
1375
1645
|
anchorOffset,
|
|
1376
|
-
|
|
1646
|
+
focusKey,
|
|
1377
1647
|
focusOffset
|
|
1378
|
-
} =
|
|
1379
|
-
if (
|
|
1380
|
-
const anchorKey = anchorCollabNode.getKey();
|
|
1381
|
-
const focusKey = focusCollabNode.getKey();
|
|
1648
|
+
} = editor.read(() => $getAnchorAndFocusForUserState(binding, awareness));
|
|
1649
|
+
if (anchorKey !== null && focusKey !== null) {
|
|
1382
1650
|
selection = cursor.selection;
|
|
1383
1651
|
if (selection === null) {
|
|
1384
1652
|
selection = createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset);
|
|
@@ -1429,8 +1697,13 @@ function syncLexicalSelectionToYjs(binding, provider, prevSelection, nextSelecti
|
|
|
1429
1697
|
}
|
|
1430
1698
|
}
|
|
1431
1699
|
if ($isRangeSelection(nextSelection)) {
|
|
1432
|
-
|
|
1433
|
-
|
|
1700
|
+
if (isBindingV1(binding)) {
|
|
1701
|
+
anchorPos = createRelativePosition(nextSelection.anchor, binding);
|
|
1702
|
+
focusPos = createRelativePosition(nextSelection.focus, binding);
|
|
1703
|
+
} else {
|
|
1704
|
+
anchorPos = createRelativePositionV2(nextSelection.anchor, binding);
|
|
1705
|
+
focusPos = createRelativePositionV2(nextSelection.focus, binding);
|
|
1706
|
+
}
|
|
1434
1707
|
}
|
|
1435
1708
|
if (shouldUpdatePosition(currentAnchorPos, anchorPos) || shouldUpdatePosition(currentFocusPos, focusPos)) {
|
|
1436
1709
|
awareness.setLocalState({
|
|
@@ -1445,6 +1718,568 @@ function syncLexicalSelectionToYjs(binding, provider, prevSelection, nextSelecti
|
|
|
1445
1718
|
}
|
|
1446
1719
|
}
|
|
1447
1720
|
|
|
1721
|
+
/*
|
|
1722
|
+
const isVisible = (item: Item, snapshot?: Snapshot): boolean =>
|
|
1723
|
+
snapshot === undefined
|
|
1724
|
+
? !item.deleted
|
|
1725
|
+
: snapshot.sv.has(item.id.client) &&
|
|
1726
|
+
snapshot.sv.get(item.id.client)! > item.id.clock &&
|
|
1727
|
+
!isDeleted(snapshot.ds, item.id);
|
|
1728
|
+
*/
|
|
1729
|
+
|
|
1730
|
+
// https://docs.yjs.dev/api/shared-types/y.xmlelement
|
|
1731
|
+
// "Define a top-level type; Note that the nodeName is always "undefined""
|
|
1732
|
+
const isRootElement = el => el.nodeName === 'UNDEFINED';
|
|
1733
|
+
const $createOrUpdateNodeFromYElement = (el, binding, keysChanged, childListChanged, snapshot, prevSnapshot, computeYChange) => {
|
|
1734
|
+
let node = binding.mapping.get(el);
|
|
1735
|
+
if (node && keysChanged && keysChanged.size === 0 && !childListChanged) {
|
|
1736
|
+
return node;
|
|
1737
|
+
}
|
|
1738
|
+
const type = isRootElement(el) ? RootNode.getType() : el.nodeName;
|
|
1739
|
+
const registeredNodes = binding.editor._nodes;
|
|
1740
|
+
const nodeInfo = registeredNodes.get(type);
|
|
1741
|
+
if (nodeInfo === undefined) {
|
|
1742
|
+
throw new Error(`$createOrUpdateNodeFromYElement: Node ${type} is not registered`);
|
|
1743
|
+
}
|
|
1744
|
+
if (!node) {
|
|
1745
|
+
node = new nodeInfo.klass();
|
|
1746
|
+
keysChanged = null;
|
|
1747
|
+
childListChanged = true;
|
|
1748
|
+
}
|
|
1749
|
+
if (childListChanged && node instanceof ElementNode) {
|
|
1750
|
+
const children = [];
|
|
1751
|
+
const $createChildren = childType => {
|
|
1752
|
+
if (childType instanceof XmlElement) {
|
|
1753
|
+
const n = $createOrUpdateNodeFromYElement(childType, binding, new Set(), false, snapshot, prevSnapshot, computeYChange);
|
|
1754
|
+
if (n !== null) {
|
|
1755
|
+
children.push(n);
|
|
1756
|
+
}
|
|
1757
|
+
} else if (childType instanceof XmlText) {
|
|
1758
|
+
const ns = $createOrUpdateTextNodesFromYText(childType, binding, snapshot, prevSnapshot, computeYChange);
|
|
1759
|
+
if (ns !== null) {
|
|
1760
|
+
ns.forEach(textchild => {
|
|
1761
|
+
if (textchild !== null) {
|
|
1762
|
+
children.push(textchild);
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
} else {
|
|
1767
|
+
{
|
|
1768
|
+
formatDevErrorMessage(`XmlHook is not supported`);
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
{
|
|
1773
|
+
el.toArray().forEach($createChildren);
|
|
1774
|
+
}
|
|
1775
|
+
$spliceChildren(node, children);
|
|
1776
|
+
}
|
|
1777
|
+
const attrs = el.getAttributes(snapshot);
|
|
1778
|
+
// TODO(collab-v2): support for ychange
|
|
1779
|
+
/*
|
|
1780
|
+
if (snapshot !== undefined) {
|
|
1781
|
+
if (!isVisible(el._item!, snapshot)) {
|
|
1782
|
+
attrs.ychange = computeYChange
|
|
1783
|
+
? computeYChange('removed', el._item!.id)
|
|
1784
|
+
: {type: 'removed'};
|
|
1785
|
+
} else if (!isVisible(el._item!, prevSnapshot)) {
|
|
1786
|
+
attrs.ychange = computeYChange
|
|
1787
|
+
? computeYChange('added', el._item!.id)
|
|
1788
|
+
: {type: 'added'};
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
*/
|
|
1792
|
+
const properties = {
|
|
1793
|
+
...getDefaultNodeProperties(node, binding)
|
|
1794
|
+
};
|
|
1795
|
+
const state = {};
|
|
1796
|
+
for (const k in attrs) {
|
|
1797
|
+
if (k.startsWith(STATE_KEY_PREFIX)) {
|
|
1798
|
+
state[attrKeyToStateKey(k)] = attrs[k];
|
|
1799
|
+
} else {
|
|
1800
|
+
properties[k] = attrs[k];
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
$syncPropertiesFromYjs(binding, properties, node, keysChanged);
|
|
1804
|
+
if (!keysChanged) {
|
|
1805
|
+
$getWritableNodeState(node).updateFromJSON(state);
|
|
1806
|
+
} else {
|
|
1807
|
+
const stateKeysChanged = Object.keys(state).filter(k => keysChanged.has(stateKeyToAttrKey(k)));
|
|
1808
|
+
if (stateKeysChanged.length > 0) {
|
|
1809
|
+
const writableState = $getWritableNodeState(node);
|
|
1810
|
+
for (const k of stateKeysChanged) {
|
|
1811
|
+
writableState.updateFromUnknown(k, state[k]);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
const latestNode = node.getLatest();
|
|
1816
|
+
binding.mapping.set(el, latestNode);
|
|
1817
|
+
return latestNode;
|
|
1818
|
+
};
|
|
1819
|
+
const $spliceChildren = (node, nextChildren) => {
|
|
1820
|
+
const prevChildren = node.getChildren();
|
|
1821
|
+
const prevChildrenKeySet = new Set(prevChildren.map(child => child.getKey()));
|
|
1822
|
+
const nextChildrenKeySet = new Set(nextChildren.map(child => child.getKey()));
|
|
1823
|
+
const prevEndIndex = prevChildren.length - 1;
|
|
1824
|
+
const nextEndIndex = nextChildren.length - 1;
|
|
1825
|
+
let prevIndex = 0;
|
|
1826
|
+
let nextIndex = 0;
|
|
1827
|
+
while (prevIndex <= prevEndIndex && nextIndex <= nextEndIndex) {
|
|
1828
|
+
const prevKey = prevChildren[prevIndex].getKey();
|
|
1829
|
+
const nextKey = nextChildren[nextIndex].getKey();
|
|
1830
|
+
if (prevKey === nextKey) {
|
|
1831
|
+
prevIndex++;
|
|
1832
|
+
nextIndex++;
|
|
1833
|
+
continue;
|
|
1834
|
+
}
|
|
1835
|
+
const nextHasPrevKey = nextChildrenKeySet.has(prevKey);
|
|
1836
|
+
const prevHasNextKey = prevChildrenKeySet.has(nextKey);
|
|
1837
|
+
if (!nextHasPrevKey) {
|
|
1838
|
+
// If removing the last node, insert remaining new nodes immediately, otherwise if the node
|
|
1839
|
+
// cannot be empty, it will remove itself from its parent.
|
|
1840
|
+
if (nextIndex === 0 && node.getChildrenSize() === 1) {
|
|
1841
|
+
node.splice(nextIndex, 1, nextChildren.slice(nextIndex));
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
// Remove
|
|
1845
|
+
node.splice(nextIndex, 1, []);
|
|
1846
|
+
prevIndex++;
|
|
1847
|
+
continue;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// Create or replace
|
|
1851
|
+
const nextChildNode = nextChildren[nextIndex];
|
|
1852
|
+
if (prevHasNextKey) {
|
|
1853
|
+
node.splice(nextIndex, 1, [nextChildNode]);
|
|
1854
|
+
prevIndex++;
|
|
1855
|
+
nextIndex++;
|
|
1856
|
+
} else {
|
|
1857
|
+
node.splice(nextIndex, 0, [nextChildNode]);
|
|
1858
|
+
nextIndex++;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
const appendNewChildren = prevIndex > prevEndIndex;
|
|
1862
|
+
const removeOldChildren = nextIndex > nextEndIndex;
|
|
1863
|
+
if (appendNewChildren && !removeOldChildren) {
|
|
1864
|
+
node.append(...nextChildren.slice(nextIndex));
|
|
1865
|
+
} else if (removeOldChildren && !appendNewChildren) {
|
|
1866
|
+
node.splice(nextChildren.length, node.getChildrenSize() - nextChildren.length, []);
|
|
1867
|
+
}
|
|
1868
|
+
};
|
|
1869
|
+
const $createOrUpdateTextNodesFromYText = (text, binding, snapshot, prevSnapshot, computeYChange) => {
|
|
1870
|
+
const deltas = toDelta(text, snapshot, prevSnapshot, computeYChange);
|
|
1871
|
+
|
|
1872
|
+
// Use existing text nodes if the count and types all align, otherwise throw out the existing
|
|
1873
|
+
// nodes and create new ones.
|
|
1874
|
+
let nodes = binding.mapping.get(text) ?? [];
|
|
1875
|
+
const nodeTypes = deltas.map(delta => delta.attributes.t ?? TextNode.getType());
|
|
1876
|
+
const canReuseNodes = nodes.length === nodeTypes.length && nodes.every((node, i) => node.getType() === nodeTypes[i]);
|
|
1877
|
+
if (!canReuseNodes) {
|
|
1878
|
+
const registeredNodes = binding.editor._nodes;
|
|
1879
|
+
nodes = nodeTypes.map(type => {
|
|
1880
|
+
const nodeInfo = registeredNodes.get(type);
|
|
1881
|
+
if (nodeInfo === undefined) {
|
|
1882
|
+
throw new Error(`$createTextNodesFromYText: Node ${type} is not registered`);
|
|
1883
|
+
}
|
|
1884
|
+
const node = new nodeInfo.klass();
|
|
1885
|
+
if (!$isTextNode(node)) {
|
|
1886
|
+
throw new Error(`$createTextNodesFromYText: Node ${type} is not a TextNode`);
|
|
1887
|
+
}
|
|
1888
|
+
return node;
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// Sync text, properties and state to the text nodes.
|
|
1893
|
+
for (let i = 0; i < deltas.length; i++) {
|
|
1894
|
+
const node = nodes[i];
|
|
1895
|
+
const delta = deltas[i];
|
|
1896
|
+
const {
|
|
1897
|
+
attributes,
|
|
1898
|
+
insert
|
|
1899
|
+
} = delta;
|
|
1900
|
+
if (node.__text !== insert) {
|
|
1901
|
+
node.setTextContent(insert);
|
|
1902
|
+
}
|
|
1903
|
+
const properties = {
|
|
1904
|
+
...getDefaultNodeProperties(node, binding),
|
|
1905
|
+
...attributes.p
|
|
1906
|
+
};
|
|
1907
|
+
const state = Object.fromEntries(Object.entries(attributes).filter(([k]) => k.startsWith(STATE_KEY_PREFIX)).map(([k, v]) => [attrKeyToStateKey(k), v]));
|
|
1908
|
+
$syncPropertiesFromYjs(binding, properties, node, null);
|
|
1909
|
+
$getWritableNodeState(node).updateFromJSON(state);
|
|
1910
|
+
}
|
|
1911
|
+
const latestNodes = nodes.map(node => node.getLatest());
|
|
1912
|
+
binding.mapping.set(text, latestNodes);
|
|
1913
|
+
return latestNodes;
|
|
1914
|
+
};
|
|
1915
|
+
const $createTypeFromTextNodes = (nodes, binding) => {
|
|
1916
|
+
const type = new XmlText();
|
|
1917
|
+
$updateYText(type, nodes, binding);
|
|
1918
|
+
return type;
|
|
1919
|
+
};
|
|
1920
|
+
const createTypeFromElementNode = (node, binding) => {
|
|
1921
|
+
const type = new XmlElement(node.getType());
|
|
1922
|
+
const attrs = {
|
|
1923
|
+
...propertiesToAttributes(node, binding),
|
|
1924
|
+
...stateToAttributes(node)
|
|
1925
|
+
};
|
|
1926
|
+
for (const key in attrs) {
|
|
1927
|
+
const val = attrs[key];
|
|
1928
|
+
if (val !== null) {
|
|
1929
|
+
// TODO(collab-v2): typing for XmlElement generic
|
|
1930
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1931
|
+
type.setAttribute(key, val);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
if (!(node instanceof ElementNode)) {
|
|
1935
|
+
return type;
|
|
1936
|
+
}
|
|
1937
|
+
type.insert(0, normalizeNodeContent(node).map(n => $createTypeFromTextOrElementNode(n, binding)));
|
|
1938
|
+
binding.mapping.set(type, node);
|
|
1939
|
+
return type;
|
|
1940
|
+
};
|
|
1941
|
+
const $createTypeFromTextOrElementNode = (node, meta) => node instanceof Array ? $createTypeFromTextNodes(node, meta) : createTypeFromElementNode(node, meta);
|
|
1942
|
+
const isObject = val => typeof val === 'object' && val != null;
|
|
1943
|
+
const equalAttrs = (pattrs, yattrs) => {
|
|
1944
|
+
const keys = Object.keys(pattrs).filter(key => pattrs[key] !== null);
|
|
1945
|
+
if (yattrs == null) {
|
|
1946
|
+
return keys.length === 0;
|
|
1947
|
+
}
|
|
1948
|
+
let eq = keys.length === Object.keys(yattrs).filter(key => yattrs[key] !== null).length;
|
|
1949
|
+
for (let i = 0; i < keys.length && eq; i++) {
|
|
1950
|
+
const key = keys[i];
|
|
1951
|
+
const l = pattrs[key];
|
|
1952
|
+
const r = yattrs[key];
|
|
1953
|
+
eq = key === 'ychange' || l === r || isObject(l) && isObject(r) && equalAttrs(l, r);
|
|
1954
|
+
}
|
|
1955
|
+
return eq;
|
|
1956
|
+
};
|
|
1957
|
+
const normalizeNodeContent = node => {
|
|
1958
|
+
if (!(node instanceof ElementNode)) {
|
|
1959
|
+
return [];
|
|
1960
|
+
}
|
|
1961
|
+
const c = node.getChildren();
|
|
1962
|
+
const res = [];
|
|
1963
|
+
for (let i = 0; i < c.length; i++) {
|
|
1964
|
+
const n = c[i];
|
|
1965
|
+
if ($isTextNode(n)) {
|
|
1966
|
+
const textNodes = [];
|
|
1967
|
+
for (let maybeTextNode = c[i]; i < c.length && $isTextNode(maybeTextNode); maybeTextNode = c[++i]) {
|
|
1968
|
+
textNodes.push(maybeTextNode);
|
|
1969
|
+
}
|
|
1970
|
+
i--;
|
|
1971
|
+
res.push(textNodes);
|
|
1972
|
+
} else {
|
|
1973
|
+
res.push(n);
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
return res;
|
|
1977
|
+
};
|
|
1978
|
+
const equalYTextLText = (ytext, ltexts, binding) => {
|
|
1979
|
+
const deltas = toDelta(ytext);
|
|
1980
|
+
return deltas.length === ltexts.length && deltas.every((d, i) => {
|
|
1981
|
+
const ltext = ltexts[i];
|
|
1982
|
+
const type = d.attributes.t ?? TextNode.getType();
|
|
1983
|
+
const propertyAttrs = d.attributes.p ?? {};
|
|
1984
|
+
const stateAttrs = Object.fromEntries(Object.entries(d.attributes).filter(([k]) => k.startsWith(STATE_KEY_PREFIX)));
|
|
1985
|
+
return d.insert === ltext.getTextContent() && type === ltext.getType() && equalAttrs(propertyAttrs, propertiesToAttributes(ltext, binding)) && equalAttrs(stateAttrs, stateToAttributes(ltext));
|
|
1986
|
+
});
|
|
1987
|
+
};
|
|
1988
|
+
const equalYTypePNode = (ytype, lnode, binding) => {
|
|
1989
|
+
if (ytype instanceof XmlElement && !(lnode instanceof Array) && matchNodeName(ytype, lnode)) {
|
|
1990
|
+
const normalizedContent = normalizeNodeContent(lnode);
|
|
1991
|
+
return ytype._length === normalizedContent.length && equalAttrs(ytype.getAttributes(), {
|
|
1992
|
+
...propertiesToAttributes(lnode, binding),
|
|
1993
|
+
...stateToAttributes(lnode)
|
|
1994
|
+
}) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, normalizedContent[i], binding));
|
|
1995
|
+
}
|
|
1996
|
+
return ytype instanceof XmlText && lnode instanceof Array && equalYTextLText(ytype, lnode, binding);
|
|
1997
|
+
};
|
|
1998
|
+
const mappedIdentity = (mapped, lcontent) => mapped === lcontent || mapped instanceof Array && lcontent instanceof Array && mapped.length === lcontent.length && mapped.every((a, i) => lcontent[i] === a);
|
|
1999
|
+
const computeChildEqualityFactor = (ytype, lnode, binding) => {
|
|
2000
|
+
const yChildren = ytype.toArray();
|
|
2001
|
+
const pChildren = normalizeNodeContent(lnode);
|
|
2002
|
+
const pChildCnt = pChildren.length;
|
|
2003
|
+
const yChildCnt = yChildren.length;
|
|
2004
|
+
const minCnt = Math.min(yChildCnt, pChildCnt);
|
|
2005
|
+
let left = 0;
|
|
2006
|
+
let right = 0;
|
|
2007
|
+
let foundMappedChild = false;
|
|
2008
|
+
for (; left < minCnt; left++) {
|
|
2009
|
+
const leftY = yChildren[left];
|
|
2010
|
+
const leftP = pChildren[left];
|
|
2011
|
+
if (leftY instanceof XmlHook) {
|
|
2012
|
+
break;
|
|
2013
|
+
} else if (mappedIdentity(binding.mapping.get(leftY), leftP)) {
|
|
2014
|
+
foundMappedChild = true; // definite (good) match!
|
|
2015
|
+
} else if (!equalYTypePNode(leftY, leftP, binding)) {
|
|
2016
|
+
break;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
for (; left + right < minCnt; right++) {
|
|
2020
|
+
const rightY = yChildren[yChildCnt - right - 1];
|
|
2021
|
+
const rightP = pChildren[pChildCnt - right - 1];
|
|
2022
|
+
if (rightY instanceof XmlHook) {
|
|
2023
|
+
break;
|
|
2024
|
+
} else if (mappedIdentity(binding.mapping.get(rightY), rightP)) {
|
|
2025
|
+
foundMappedChild = true;
|
|
2026
|
+
} else if (!equalYTypePNode(rightY, rightP, binding)) {
|
|
2027
|
+
break;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
return {
|
|
2031
|
+
equalityFactor: left + right,
|
|
2032
|
+
foundMappedChild
|
|
2033
|
+
};
|
|
2034
|
+
};
|
|
2035
|
+
const ytextTrans = ytext => {
|
|
2036
|
+
let str = '';
|
|
2037
|
+
let n = ytext._start;
|
|
2038
|
+
const nAttrs = {};
|
|
2039
|
+
while (n !== null) {
|
|
2040
|
+
if (!n.deleted) {
|
|
2041
|
+
if (n.countable && n.content instanceof ContentString) {
|
|
2042
|
+
str += n.content.str;
|
|
2043
|
+
} else if (n.content instanceof ContentFormat) {
|
|
2044
|
+
nAttrs[n.content.key] = null;
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
n = n.right;
|
|
2048
|
+
}
|
|
2049
|
+
return {
|
|
2050
|
+
nAttrs,
|
|
2051
|
+
str
|
|
2052
|
+
};
|
|
2053
|
+
};
|
|
2054
|
+
const $updateYText = (ytext, ltexts, binding) => {
|
|
2055
|
+
binding.mapping.set(ytext, ltexts);
|
|
2056
|
+
const {
|
|
2057
|
+
nAttrs,
|
|
2058
|
+
str
|
|
2059
|
+
} = ytextTrans(ytext);
|
|
2060
|
+
const content = ltexts.map((node, i) => {
|
|
2061
|
+
const nodeType = node.getType();
|
|
2062
|
+
let p = propertiesToAttributes(node, binding);
|
|
2063
|
+
if (Object.keys(p).length === 0) {
|
|
2064
|
+
p = null;
|
|
2065
|
+
}
|
|
2066
|
+
return {
|
|
2067
|
+
attributes: Object.assign({}, nAttrs, {
|
|
2068
|
+
...(nodeType !== TextNode.getType() && {
|
|
2069
|
+
t: nodeType
|
|
2070
|
+
}),
|
|
2071
|
+
p,
|
|
2072
|
+
...stateToAttributes(node),
|
|
2073
|
+
...(i > 0 && {
|
|
2074
|
+
i
|
|
2075
|
+
}) // Prevent Yjs from merging text nodes itself.
|
|
2076
|
+
}),
|
|
2077
|
+
insert: node.getTextContent(),
|
|
2078
|
+
nodeKey: node.getKey()
|
|
2079
|
+
};
|
|
2080
|
+
});
|
|
2081
|
+
const nextText = content.map(c => c.insert).join('');
|
|
2082
|
+
const selection = $getSelection();
|
|
2083
|
+
let cursorOffset;
|
|
2084
|
+
if ($isRangeSelection(selection) && selection.isCollapsed()) {
|
|
2085
|
+
cursorOffset = 0;
|
|
2086
|
+
for (const c of content) {
|
|
2087
|
+
if (c.nodeKey === selection.anchor.key) {
|
|
2088
|
+
cursorOffset += selection.anchor.offset;
|
|
2089
|
+
break;
|
|
2090
|
+
}
|
|
2091
|
+
cursorOffset += c.insert.length;
|
|
2092
|
+
}
|
|
2093
|
+
} else {
|
|
2094
|
+
cursorOffset = nextText.length;
|
|
2095
|
+
}
|
|
2096
|
+
const {
|
|
2097
|
+
insert,
|
|
2098
|
+
remove,
|
|
2099
|
+
index
|
|
2100
|
+
} = simpleDiffWithCursor(str, nextText, cursorOffset);
|
|
2101
|
+
ytext.delete(index, remove);
|
|
2102
|
+
ytext.insert(index, insert);
|
|
2103
|
+
ytext.applyDelta(content.map(c => ({
|
|
2104
|
+
attributes: c.attributes,
|
|
2105
|
+
retain: c.insert.length
|
|
2106
|
+
})));
|
|
2107
|
+
};
|
|
2108
|
+
const toDelta = (ytext, snapshot, prevSnapshot, computeYChange) => {
|
|
2109
|
+
return ytext.toDelta(snapshot, prevSnapshot, computeYChange).map(delta => ({
|
|
2110
|
+
...delta,
|
|
2111
|
+
attributes: delta.attributes ?? {}
|
|
2112
|
+
}));
|
|
2113
|
+
};
|
|
2114
|
+
const propertiesToAttributes = (node, meta) => {
|
|
2115
|
+
const defaultProperties = getDefaultNodeProperties(node, meta);
|
|
2116
|
+
const attrs = {};
|
|
2117
|
+
Object.entries(defaultProperties).forEach(([property, defaultValue]) => {
|
|
2118
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2119
|
+
const value = node[property];
|
|
2120
|
+
if (value !== defaultValue) {
|
|
2121
|
+
attrs[property] = value;
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
return attrs;
|
|
2125
|
+
};
|
|
2126
|
+
const STATE_KEY_PREFIX = 's_';
|
|
2127
|
+
const stateKeyToAttrKey = key => STATE_KEY_PREFIX + key;
|
|
2128
|
+
const attrKeyToStateKey = key => {
|
|
2129
|
+
if (!key.startsWith(STATE_KEY_PREFIX)) {
|
|
2130
|
+
throw new Error(`Invalid state key: ${key}`);
|
|
2131
|
+
}
|
|
2132
|
+
return key.slice(STATE_KEY_PREFIX.length);
|
|
2133
|
+
};
|
|
2134
|
+
const stateToAttributes = node => {
|
|
2135
|
+
const state = node.__state;
|
|
2136
|
+
if (!state) {
|
|
2137
|
+
return {};
|
|
2138
|
+
}
|
|
2139
|
+
const [unknown = {}, known] = state.getInternalState();
|
|
2140
|
+
const attrs = {};
|
|
2141
|
+
for (const [k, v] of Object.entries(unknown)) {
|
|
2142
|
+
attrs[stateKeyToAttrKey(k)] = v;
|
|
2143
|
+
}
|
|
2144
|
+
for (const [stateConfig, v] of known) {
|
|
2145
|
+
attrs[stateKeyToAttrKey(stateConfig.key)] = stateConfig.unparse(v);
|
|
2146
|
+
}
|
|
2147
|
+
return attrs;
|
|
2148
|
+
};
|
|
2149
|
+
const $updateYFragment = (y, yDomFragment, node, binding, dirtyElements) => {
|
|
2150
|
+
if (yDomFragment instanceof XmlElement && yDomFragment.nodeName !== node.getType() && !(isRootElement(yDomFragment) && node.getType() === RootNode.getType())) {
|
|
2151
|
+
throw new Error('node name mismatch!');
|
|
2152
|
+
}
|
|
2153
|
+
binding.mapping.set(yDomFragment, node);
|
|
2154
|
+
// update attributes
|
|
2155
|
+
if (yDomFragment instanceof XmlElement) {
|
|
2156
|
+
const yDomAttrs = yDomFragment.getAttributes();
|
|
2157
|
+
const lexicalAttrs = {
|
|
2158
|
+
...propertiesToAttributes(node, binding),
|
|
2159
|
+
...stateToAttributes(node)
|
|
2160
|
+
};
|
|
2161
|
+
for (const key in lexicalAttrs) {
|
|
2162
|
+
if (lexicalAttrs[key] != null) {
|
|
2163
|
+
if (yDomAttrs[key] !== lexicalAttrs[key] && key !== 'ychange') {
|
|
2164
|
+
// TODO(collab-v2): typing for XmlElement generic
|
|
2165
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2166
|
+
yDomFragment.setAttribute(key, lexicalAttrs[key]);
|
|
2167
|
+
}
|
|
2168
|
+
} else {
|
|
2169
|
+
yDomFragment.removeAttribute(key);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
// remove all keys that are no longer in pAttrs
|
|
2173
|
+
for (const key in yDomAttrs) {
|
|
2174
|
+
if (lexicalAttrs[key] === undefined) {
|
|
2175
|
+
yDomFragment.removeAttribute(key);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
// update children
|
|
2180
|
+
const lChildren = normalizeNodeContent(node);
|
|
2181
|
+
const lChildCnt = lChildren.length;
|
|
2182
|
+
const yChildren = yDomFragment.toArray();
|
|
2183
|
+
const yChildCnt = yChildren.length;
|
|
2184
|
+
const minCnt = Math.min(lChildCnt, yChildCnt);
|
|
2185
|
+
let left = 0;
|
|
2186
|
+
let right = 0;
|
|
2187
|
+
// find number of matching elements from left
|
|
2188
|
+
for (; left < minCnt; left++) {
|
|
2189
|
+
const leftY = yChildren[left];
|
|
2190
|
+
const leftL = lChildren[left];
|
|
2191
|
+
if (leftY instanceof XmlHook) {
|
|
2192
|
+
break;
|
|
2193
|
+
} else if (mappedIdentity(binding.mapping.get(leftY), leftL)) {
|
|
2194
|
+
if (leftL instanceof ElementNode && dirtyElements.has(leftL.getKey())) {
|
|
2195
|
+
$updateYFragment(y, leftY, leftL, binding, dirtyElements);
|
|
2196
|
+
}
|
|
2197
|
+
} else if (equalYTypePNode(leftY, leftL, binding)) {
|
|
2198
|
+
// update mapping
|
|
2199
|
+
binding.mapping.set(leftY, leftL);
|
|
2200
|
+
} else {
|
|
2201
|
+
break;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
// find number of matching elements from right
|
|
2205
|
+
for (; right + left < minCnt; right++) {
|
|
2206
|
+
const rightY = yChildren[yChildCnt - right - 1];
|
|
2207
|
+
const rightL = lChildren[lChildCnt - right - 1];
|
|
2208
|
+
if (rightY instanceof XmlHook) {
|
|
2209
|
+
break;
|
|
2210
|
+
} else if (mappedIdentity(binding.mapping.get(rightY), rightL)) {
|
|
2211
|
+
if (rightL instanceof ElementNode && dirtyElements.has(rightL.getKey())) {
|
|
2212
|
+
$updateYFragment(y, rightY, rightL, binding, dirtyElements);
|
|
2213
|
+
}
|
|
2214
|
+
} else if (equalYTypePNode(rightY, rightL, binding)) {
|
|
2215
|
+
// update mapping
|
|
2216
|
+
binding.mapping.set(rightY, rightL);
|
|
2217
|
+
} else {
|
|
2218
|
+
break;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
// try to compare and update
|
|
2222
|
+
while (yChildCnt - left - right > 0 && lChildCnt - left - right > 0) {
|
|
2223
|
+
const leftY = yChildren[left];
|
|
2224
|
+
const leftL = lChildren[left];
|
|
2225
|
+
const rightY = yChildren[yChildCnt - right - 1];
|
|
2226
|
+
const rightL = lChildren[lChildCnt - right - 1];
|
|
2227
|
+
if (leftY instanceof XmlText && leftL instanceof Array) {
|
|
2228
|
+
if (!equalYTextLText(leftY, leftL, binding)) {
|
|
2229
|
+
$updateYText(leftY, leftL, binding);
|
|
2230
|
+
}
|
|
2231
|
+
left += 1;
|
|
2232
|
+
} else {
|
|
2233
|
+
let updateLeft = leftY instanceof XmlElement && matchNodeName(leftY, leftL);
|
|
2234
|
+
let updateRight = rightY instanceof XmlElement && matchNodeName(rightY, rightL);
|
|
2235
|
+
if (updateLeft && updateRight) {
|
|
2236
|
+
// decide which which element to update
|
|
2237
|
+
const equalityLeft = computeChildEqualityFactor(leftY, leftL, binding);
|
|
2238
|
+
const equalityRight = computeChildEqualityFactor(rightY, rightL, binding);
|
|
2239
|
+
if (equalityLeft.foundMappedChild && !equalityRight.foundMappedChild) {
|
|
2240
|
+
updateRight = false;
|
|
2241
|
+
} else if (!equalityLeft.foundMappedChild && equalityRight.foundMappedChild) {
|
|
2242
|
+
updateLeft = false;
|
|
2243
|
+
} else if (equalityLeft.equalityFactor < equalityRight.equalityFactor) {
|
|
2244
|
+
updateLeft = false;
|
|
2245
|
+
} else {
|
|
2246
|
+
updateRight = false;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
if (updateLeft) {
|
|
2250
|
+
$updateYFragment(y, leftY, leftL, binding, dirtyElements);
|
|
2251
|
+
left += 1;
|
|
2252
|
+
} else if (updateRight) {
|
|
2253
|
+
$updateYFragment(y, rightY, rightL, binding, dirtyElements);
|
|
2254
|
+
right += 1;
|
|
2255
|
+
} else {
|
|
2256
|
+
binding.mapping.delete(yDomFragment.get(left));
|
|
2257
|
+
yDomFragment.delete(left, 1);
|
|
2258
|
+
yDomFragment.insert(left, [$createTypeFromTextOrElementNode(leftL, binding)]);
|
|
2259
|
+
left += 1;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
const yDelLen = yChildCnt - left - right;
|
|
2264
|
+
if (yChildCnt === 1 && lChildCnt === 0 && yChildren[0] instanceof XmlText) {
|
|
2265
|
+
binding.mapping.delete(yChildren[0]);
|
|
2266
|
+
// Edge case handling https://github.com/yjs/y-prosemirror/issues/108
|
|
2267
|
+
// Only delete the content of the Y.Text to retain remote changes on the same Y.Text object
|
|
2268
|
+
yChildren[0].delete(0, yChildren[0].length);
|
|
2269
|
+
} else if (yDelLen > 0) {
|
|
2270
|
+
yDomFragment.slice(left, left + yDelLen).forEach(type => binding.mapping.delete(type));
|
|
2271
|
+
yDomFragment.delete(left, yDelLen);
|
|
2272
|
+
}
|
|
2273
|
+
if (left + right < lChildCnt) {
|
|
2274
|
+
const ins = [];
|
|
2275
|
+
for (let i = left; i < lChildCnt - right; i++) {
|
|
2276
|
+
ins.push($createTypeFromTextOrElementNode(lChildren[i], binding));
|
|
2277
|
+
}
|
|
2278
|
+
yDomFragment.insert(left, ins);
|
|
2279
|
+
}
|
|
2280
|
+
};
|
|
2281
|
+
const matchNodeName = (yElement, lnode) => !(lnode instanceof Array) && yElement.nodeName === lnode.getType();
|
|
2282
|
+
|
|
1448
2283
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1449
2284
|
function $syncStateEvent(binding, event) {
|
|
1450
2285
|
const {
|
|
@@ -1531,23 +2366,7 @@ function syncYjsChangesToLexical(binding, provider, events, isFromUndoManger, sy
|
|
|
1531
2366
|
const event = events[i];
|
|
1532
2367
|
$syncEvent(binding, event);
|
|
1533
2368
|
}
|
|
1534
|
-
|
|
1535
|
-
if ($isRangeSelection(selection)) {
|
|
1536
|
-
if (doesSelectionNeedRecovering(selection)) {
|
|
1537
|
-
const prevSelection = currentEditorState._selection;
|
|
1538
|
-
if ($isRangeSelection(prevSelection)) {
|
|
1539
|
-
$syncLocalCursorPosition(binding, provider);
|
|
1540
|
-
if (doesSelectionNeedRecovering(selection)) {
|
|
1541
|
-
// If the selected node is deleted, move the selection to the previous or parent node.
|
|
1542
|
-
const anchorNodeKey = selection.anchor.key;
|
|
1543
|
-
$moveSelectionToPreviousNode(anchorNodeKey, currentEditorState);
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
syncLexicalSelectionToYjs(binding, provider, prevSelection, $getSelection());
|
|
1547
|
-
} else {
|
|
1548
|
-
$syncLocalCursorPosition(binding, provider);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
2369
|
+
$syncCursorFromYjs(currentEditorState, binding, provider);
|
|
1551
2370
|
if (!isFromUndoManger) {
|
|
1552
2371
|
// If it is an external change, we don't want the current scroll position to get changed
|
|
1553
2372
|
// since the user might've intentionally scrolled somewhere else in the document.
|
|
@@ -1556,19 +2375,31 @@ function syncYjsChangesToLexical(binding, provider, events, isFromUndoManger, sy
|
|
|
1556
2375
|
}, {
|
|
1557
2376
|
onUpdate: () => {
|
|
1558
2377
|
syncCursorPositionsFn(binding, provider);
|
|
1559
|
-
|
|
1560
|
-
// we need to re-add a paragraph. To ensure this insertion properly syncs with other clients,
|
|
1561
|
-
// it must be placed outside of the update block above that has tags 'collaboration' or 'historic'.
|
|
1562
|
-
editor.update(() => {
|
|
1563
|
-
if ($getRoot().getChildrenSize() === 0) {
|
|
1564
|
-
$getRoot().append($createParagraphNode());
|
|
1565
|
-
}
|
|
1566
|
-
});
|
|
2378
|
+
editor.update(() => $ensureEditorNotEmpty());
|
|
1567
2379
|
},
|
|
1568
2380
|
skipTransforms: true,
|
|
1569
2381
|
tag: isFromUndoManger ? HISTORIC_TAG : COLLABORATION_TAG
|
|
1570
2382
|
});
|
|
1571
2383
|
}
|
|
2384
|
+
function $syncCursorFromYjs(editorState, binding, provider) {
|
|
2385
|
+
const selection = $getSelection();
|
|
2386
|
+
if ($isRangeSelection(selection)) {
|
|
2387
|
+
if (doesSelectionNeedRecovering(selection)) {
|
|
2388
|
+
const prevSelection = editorState._selection;
|
|
2389
|
+
if ($isRangeSelection(prevSelection)) {
|
|
2390
|
+
$syncLocalCursorPosition(binding, provider);
|
|
2391
|
+
if (doesSelectionNeedRecovering(selection)) {
|
|
2392
|
+
// If the selected node is deleted, move the selection to the previous or parent node.
|
|
2393
|
+
const anchorNodeKey = selection.anchor.key;
|
|
2394
|
+
$moveSelectionToPreviousNode(anchorNodeKey, editorState);
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
syncLexicalSelectionToYjs(binding, provider, prevSelection, $getSelection());
|
|
2398
|
+
} else {
|
|
2399
|
+
$syncLocalCursorPosition(binding, provider);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
1572
2403
|
function $handleNormalizationMergeConflicts(binding, normalizedNodes) {
|
|
1573
2404
|
// We handle the merge operations here
|
|
1574
2405
|
const normalizedNodesKeys = Array.from(normalizedNodes);
|
|
@@ -1609,6 +2440,15 @@ function $handleNormalizationMergeConflicts(binding, normalizedNodes) {
|
|
|
1609
2440
|
collabNode._text = text;
|
|
1610
2441
|
}
|
|
1611
2442
|
}
|
|
2443
|
+
|
|
2444
|
+
// If there was a collision on the top level paragraph
|
|
2445
|
+
// we need to re-add a paragraph. To ensure this insertion properly syncs with other clients,
|
|
2446
|
+
// it must be placed outside of the update block above that has tags 'collaboration' or 'historic'.
|
|
2447
|
+
function $ensureEditorNotEmpty() {
|
|
2448
|
+
if ($getRoot().getChildrenSize() === 0) {
|
|
2449
|
+
$getRoot().append($createParagraphNode());
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
1612
2452
|
function syncLexicalUpdateToYjs(binding, provider, prevEditorState, currEditorState, dirtyElements, dirtyLeaves, normalizedNodes, tags) {
|
|
1613
2453
|
syncWithTransaction(binding, () => {
|
|
1614
2454
|
currEditorState.read(() => {
|
|
@@ -1638,6 +2478,96 @@ function syncLexicalUpdateToYjs(binding, provider, prevEditorState, currEditorSt
|
|
|
1638
2478
|
});
|
|
1639
2479
|
});
|
|
1640
2480
|
}
|
|
2481
|
+
function $syncEventV2(binding, event) {
|
|
2482
|
+
const {
|
|
2483
|
+
target
|
|
2484
|
+
} = event;
|
|
2485
|
+
if (target instanceof XmlElement && event instanceof YXmlEvent) {
|
|
2486
|
+
$createOrUpdateNodeFromYElement(target, binding, event.attributesChanged,
|
|
2487
|
+
// @ts-expect-error childListChanged is private
|
|
2488
|
+
event.childListChanged);
|
|
2489
|
+
} else if (target instanceof XmlText && event instanceof YTextEvent) {
|
|
2490
|
+
const parent = target.parent;
|
|
2491
|
+
if (parent instanceof XmlElement) {
|
|
2492
|
+
// Need to sync via parent element in order to attach new next nodes.
|
|
2493
|
+
$createOrUpdateNodeFromYElement(parent, binding, new Set(), true);
|
|
2494
|
+
} else {
|
|
2495
|
+
{
|
|
2496
|
+
formatDevErrorMessage(`Expected XmlElement parent for XmlText`);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
} else {
|
|
2500
|
+
{
|
|
2501
|
+
formatDevErrorMessage(`Expected xml or text event`);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
function syncYjsChangesToLexicalV2__EXPERIMENTAL(binding, provider, events, transaction, isFromUndoManger) {
|
|
2506
|
+
const editor = binding.editor;
|
|
2507
|
+
const editorState = editor._editorState;
|
|
2508
|
+
|
|
2509
|
+
// Remove deleted nodes from the mapping
|
|
2510
|
+
iterateDeletedStructs(transaction, transaction.deleteSet, struct => {
|
|
2511
|
+
if (struct.constructor === Item) {
|
|
2512
|
+
const content = struct.content;
|
|
2513
|
+
const type = content.type;
|
|
2514
|
+
if (type) {
|
|
2515
|
+
binding.mapping.delete(type);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2520
|
+
// This line precompute the delta before editor update. The reason is
|
|
2521
|
+
// delta is computed when it is accessed. Note that this can only be
|
|
2522
|
+
// safely computed during the event call. If it is accessed after event
|
|
2523
|
+
// call it might result in unexpected behavior.
|
|
2524
|
+
// https://github.com/yjs/yjs/blob/00ef472d68545cb260abd35c2de4b3b78719c9e4/src/utils/YEvent.js#L132
|
|
2525
|
+
events.forEach(event => event.delta);
|
|
2526
|
+
editor.update(() => {
|
|
2527
|
+
for (let i = 0; i < events.length; i++) {
|
|
2528
|
+
const event = events[i];
|
|
2529
|
+
$syncEventV2(binding, event);
|
|
2530
|
+
}
|
|
2531
|
+
$syncCursorFromYjs(editorState, binding, provider);
|
|
2532
|
+
if (!isFromUndoManger) {
|
|
2533
|
+
// If it is an external change, we don't want the current scroll position to get changed
|
|
2534
|
+
// since the user might've intentionally scrolled somewhere else in the document.
|
|
2535
|
+
$addUpdateTag(SKIP_SCROLL_INTO_VIEW_TAG);
|
|
2536
|
+
}
|
|
2537
|
+
}, {
|
|
2538
|
+
// Need any text node normalization to be synchronously updated back to Yjs, otherwise the
|
|
2539
|
+
// binding.mapping will get out of sync.
|
|
2540
|
+
discrete: true,
|
|
2541
|
+
onUpdate: () => {
|
|
2542
|
+
syncCursorPositions(binding, provider);
|
|
2543
|
+
editor.update(() => $ensureEditorNotEmpty());
|
|
2544
|
+
},
|
|
2545
|
+
skipTransforms: true,
|
|
2546
|
+
tag: isFromUndoManger ? HISTORIC_TAG : COLLABORATION_TAG
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
function syncLexicalUpdateToYjsV2__EXPERIMENTAL(binding, provider, prevEditorState, currEditorState, dirtyElements, normalizedNodes, tags) {
|
|
2550
|
+
const isFromYjs = tags.has(COLLABORATION_TAG) || tags.has(HISTORIC_TAG);
|
|
2551
|
+
if (isFromYjs && normalizedNodes.size === 0) {
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
// Nodes are normalized synchronously (`discrete: true` above), so the mapping may now be
|
|
2556
|
+
// incorrect for these nodes, as they point to `getLatest` which is mutable within an update.
|
|
2557
|
+
normalizedNodes.forEach(nodeKey => {
|
|
2558
|
+
binding.mapping.deleteNode(nodeKey);
|
|
2559
|
+
});
|
|
2560
|
+
syncWithTransaction(binding, () => {
|
|
2561
|
+
currEditorState.read(() => {
|
|
2562
|
+
if (dirtyElements.has('root')) {
|
|
2563
|
+
$updateYFragment(binding.doc, binding.root, $getRoot(), binding, new Set(dirtyElements.keys()));
|
|
2564
|
+
}
|
|
2565
|
+
const selection = $getSelection();
|
|
2566
|
+
const prevSelection = prevEditorState._selection;
|
|
2567
|
+
syncLexicalSelectionToYjs(binding, provider, prevSelection, selection);
|
|
2568
|
+
});
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
1641
2571
|
|
|
1642
2572
|
/**
|
|
1643
2573
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -1683,4 +2613,4 @@ function setLocalStateFocus(provider, name, color, focusing, awarenessData) {
|
|
|
1683
2613
|
awareness.setLocalState(localState);
|
|
1684
2614
|
}
|
|
1685
2615
|
|
|
1686
|
-
export { CONNECTED_COMMAND, TOGGLE_CONNECT_COMMAND, createBinding, createUndoManager, getAnchorAndFocusCollabNodesForUserState, initLocalState, setLocalStateFocus, syncCursorPositions, syncLexicalUpdateToYjs, syncYjsChangesToLexical };
|
|
2616
|
+
export { CONNECTED_COMMAND, TOGGLE_CONNECT_COMMAND, createBinding, createBindingV2__EXPERIMENTAL, createUndoManager, getAnchorAndFocusCollabNodesForUserState, initLocalState, setLocalStateFocus, syncCursorPositions, syncLexicalUpdateToYjs, syncLexicalUpdateToYjsV2__EXPERIMENTAL, syncYjsChangesToLexical, syncYjsChangesToLexicalV2__EXPERIMENTAL };
|