@lexical/yjs 0.36.2 → 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.js
CHANGED
|
@@ -235,6 +235,35 @@ function isExcludedProperty(name, node, binding) {
|
|
|
235
235
|
const excludedProperties = binding.excludedProperties.get(nodeKlass);
|
|
236
236
|
return excludedProperties != null && excludedProperties.has(name);
|
|
237
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`);
|
|
264
|
+
}
|
|
265
|
+
return properties;
|
|
266
|
+
}
|
|
238
267
|
function $createCollabNodeFromLexicalNode(binding, lexicalNode, parent) {
|
|
239
268
|
const nodeType = lexicalNode.__type;
|
|
240
269
|
let collabNode;
|
|
@@ -325,16 +354,16 @@ function createLexicalNodeFromCollabNode(binding, collabNode, parentKey) {
|
|
|
325
354
|
return lexicalNode;
|
|
326
355
|
}
|
|
327
356
|
function $syncPropertiesFromYjs(binding, sharedType, lexicalNode, keysChanged) {
|
|
328
|
-
const properties = keysChanged === null ? sharedType instanceof yjs.Map ? Array.from(sharedType.keys()) : Object.keys(sharedType.getAttributes()) : Array.from(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);
|
|
329
358
|
let writableNode;
|
|
330
359
|
for (let i = 0; i < properties.length; i++) {
|
|
331
360
|
const property = properties[i];
|
|
332
361
|
if (isExcludedProperty(property, lexicalNode, binding)) {
|
|
333
|
-
if (property === '__state') {
|
|
362
|
+
if (property === '__state' && isBindingV1(binding)) {
|
|
334
363
|
if (!writableNode) {
|
|
335
364
|
writableNode = lexicalNode.getWritable();
|
|
336
365
|
}
|
|
337
|
-
$syncNodeStateToLexical(
|
|
366
|
+
$syncNodeStateToLexical(sharedType, writableNode);
|
|
338
367
|
}
|
|
339
368
|
continue;
|
|
340
369
|
}
|
|
@@ -365,8 +394,10 @@ function $syncPropertiesFromYjs(binding, sharedType, lexicalNode, keysChanged) {
|
|
|
365
394
|
function sharedTypeGet(sharedType, property) {
|
|
366
395
|
if (sharedType instanceof yjs.Map) {
|
|
367
396
|
return sharedType.get(property);
|
|
368
|
-
} else {
|
|
397
|
+
} else if (sharedType instanceof yjs.XmlText || sharedType instanceof yjs.XmlElement) {
|
|
369
398
|
return sharedType.getAttribute(property);
|
|
399
|
+
} else {
|
|
400
|
+
return sharedType[property];
|
|
370
401
|
}
|
|
371
402
|
}
|
|
372
403
|
function sharedTypeSet(sharedType, property, nextValue) {
|
|
@@ -376,7 +407,7 @@ function sharedTypeSet(sharedType, property, nextValue) {
|
|
|
376
407
|
sharedType.setAttribute(property, nextValue);
|
|
377
408
|
}
|
|
378
409
|
}
|
|
379
|
-
function $syncNodeStateToLexical(
|
|
410
|
+
function $syncNodeStateToLexical(sharedType, lexicalNode) {
|
|
380
411
|
const existingState = sharedTypeGet(sharedType, '__state');
|
|
381
412
|
if (!(existingState instanceof yjs.Map)) {
|
|
382
413
|
return;
|
|
@@ -416,15 +447,7 @@ function syncNodeStateFromLexical(binding, sharedType, prevLexicalNode, nextLexi
|
|
|
416
447
|
}
|
|
417
448
|
}
|
|
418
449
|
function syncPropertiesFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode) {
|
|
419
|
-
const
|
|
420
|
-
const nodeProperties = binding.nodeProperties;
|
|
421
|
-
let properties = nodeProperties.get(type);
|
|
422
|
-
if (properties === undefined) {
|
|
423
|
-
properties = Object.keys(nextLexicalNode).filter(property => {
|
|
424
|
-
return !isExcludedProperty(property, nextLexicalNode, binding);
|
|
425
|
-
});
|
|
426
|
-
nodeProperties.set(type, properties);
|
|
427
|
-
}
|
|
450
|
+
const properties = Object.keys(getDefaultNodeProperties(nextLexicalNode, binding));
|
|
428
451
|
const EditorClass = binding.editor.constructor;
|
|
429
452
|
syncNodeStateFromLexical(binding, sharedType, prevLexicalNode, nextLexicalNode);
|
|
430
453
|
for (let i = 0; i < properties.length; i++) {
|
|
@@ -1065,16 +1088,98 @@ function $createCollabElementNode(xmlText, parent, type) {
|
|
|
1065
1088
|
return collabNode;
|
|
1066
1089
|
}
|
|
1067
1090
|
|
|
1068
|
-
|
|
1091
|
+
// Stores mappings between Yjs shared types and the Lexical nodes they were last associated with.
|
|
1092
|
+
class CollabV2Mapping {
|
|
1093
|
+
_nodeMap = new Map();
|
|
1094
|
+
_sharedTypeToNodeKeys = new Map();
|
|
1095
|
+
_nodeKeyToSharedType = new Map();
|
|
1096
|
+
set(sharedType, node) {
|
|
1097
|
+
const isArray = node instanceof Array;
|
|
1098
|
+
|
|
1099
|
+
// Clear all existing associations for this key.
|
|
1100
|
+
this.delete(sharedType);
|
|
1101
|
+
|
|
1102
|
+
// If nodes were associated with other shared types, remove those associations.
|
|
1103
|
+
const nodes = isArray ? node : [node];
|
|
1104
|
+
for (const n of nodes) {
|
|
1105
|
+
const key = n.getKey();
|
|
1106
|
+
if (this._nodeKeyToSharedType.has(key)) {
|
|
1107
|
+
const otherSharedType = this._nodeKeyToSharedType.get(key);
|
|
1108
|
+
const keyIndex = this._sharedTypeToNodeKeys.get(otherSharedType).indexOf(key);
|
|
1109
|
+
if (keyIndex !== -1) {
|
|
1110
|
+
this._sharedTypeToNodeKeys.get(otherSharedType).splice(keyIndex, 1);
|
|
1111
|
+
}
|
|
1112
|
+
this._nodeKeyToSharedType.delete(key);
|
|
1113
|
+
this._nodeMap.delete(key);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (sharedType instanceof yjs.XmlText) {
|
|
1117
|
+
if (!isArray) {
|
|
1118
|
+
formatDevErrorMessage(`Text nodes must be mapped as an array`);
|
|
1119
|
+
}
|
|
1120
|
+
if (node.length === 0) {
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
this._sharedTypeToNodeKeys.set(sharedType, node.map(n => n.getKey()));
|
|
1124
|
+
for (const n of node) {
|
|
1125
|
+
this._nodeMap.set(n.getKey(), n);
|
|
1126
|
+
this._nodeKeyToSharedType.set(n.getKey(), sharedType);
|
|
1127
|
+
}
|
|
1128
|
+
} else {
|
|
1129
|
+
if (!!isArray) {
|
|
1130
|
+
formatDevErrorMessage(`Element nodes must be mapped as a single node`);
|
|
1131
|
+
}
|
|
1132
|
+
if (!!lexical.$isTextNode(node)) {
|
|
1133
|
+
formatDevErrorMessage(`Text nodes must be mapped to XmlText`);
|
|
1134
|
+
}
|
|
1135
|
+
this._sharedTypeToNodeKeys.set(sharedType, [node.getKey()]);
|
|
1136
|
+
this._nodeMap.set(node.getKey(), node);
|
|
1137
|
+
this._nodeKeyToSharedType.set(node.getKey(), sharedType);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
get(sharedType) {
|
|
1141
|
+
const nodes = this._sharedTypeToNodeKeys.get(sharedType);
|
|
1142
|
+
if (nodes === undefined) {
|
|
1143
|
+
return undefined;
|
|
1144
|
+
}
|
|
1145
|
+
if (sharedType instanceof yjs.XmlText) {
|
|
1146
|
+
const arr = Array.from(nodes.map(nodeKey => this._nodeMap.get(nodeKey)));
|
|
1147
|
+
return arr.length > 0 ? arr : undefined;
|
|
1148
|
+
}
|
|
1149
|
+
return this._nodeMap.get(nodes[0]);
|
|
1150
|
+
}
|
|
1151
|
+
getSharedType(node) {
|
|
1152
|
+
return this._nodeKeyToSharedType.get(node.getKey());
|
|
1153
|
+
}
|
|
1154
|
+
delete(sharedType) {
|
|
1155
|
+
const nodeKeys = this._sharedTypeToNodeKeys.get(sharedType);
|
|
1156
|
+
if (nodeKeys === undefined) {
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
for (const nodeKey of nodeKeys) {
|
|
1160
|
+
this._nodeMap.delete(nodeKey);
|
|
1161
|
+
this._nodeKeyToSharedType.delete(nodeKey);
|
|
1162
|
+
}
|
|
1163
|
+
this._sharedTypeToNodeKeys.delete(sharedType);
|
|
1164
|
+
}
|
|
1165
|
+
deleteNode(nodeKey) {
|
|
1166
|
+
const sharedType = this._nodeKeyToSharedType.get(nodeKey);
|
|
1167
|
+
if (sharedType) {
|
|
1168
|
+
this.delete(sharedType);
|
|
1169
|
+
}
|
|
1170
|
+
this._nodeMap.delete(nodeKey);
|
|
1171
|
+
}
|
|
1172
|
+
has(sharedType) {
|
|
1173
|
+
return this._sharedTypeToNodeKeys.has(sharedType);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function createBaseBinding(editor, id, doc, docMap, excludedProperties) {
|
|
1069
1178
|
if (!(doc !== undefined && doc !== null)) {
|
|
1070
1179
|
formatDevErrorMessage(`createBinding: doc is null or undefined`);
|
|
1071
1180
|
}
|
|
1072
|
-
const
|
|
1073
|
-
const root = $createCollabElementNode(rootXmlText, null, 'root');
|
|
1074
|
-
root._key = 'root';
|
|
1075
|
-
return {
|
|
1181
|
+
const binding = {
|
|
1076
1182
|
clientID: doc.clientID,
|
|
1077
|
-
collabNodeMap: new Map(),
|
|
1078
1183
|
cursors: new Map(),
|
|
1079
1184
|
cursorsContainer: null,
|
|
1080
1185
|
doc,
|
|
@@ -1082,10 +1187,41 @@ function createBinding(editor, provider, id, doc, docMap, excludedProperties) {
|
|
|
1082
1187
|
editor,
|
|
1083
1188
|
excludedProperties: excludedProperties || new Map(),
|
|
1084
1189
|
id,
|
|
1085
|
-
nodeProperties: new Map()
|
|
1190
|
+
nodeProperties: new Map()
|
|
1191
|
+
};
|
|
1192
|
+
initializeNodeProperties(binding);
|
|
1193
|
+
return binding;
|
|
1194
|
+
}
|
|
1195
|
+
function createBinding(editor, provider, id, doc, docMap, excludedProperties) {
|
|
1196
|
+
if (!(doc !== undefined && doc !== null)) {
|
|
1197
|
+
formatDevErrorMessage(`createBinding: doc is null or undefined`);
|
|
1198
|
+
}
|
|
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(),
|
|
1086
1205
|
root
|
|
1087
1206
|
};
|
|
1088
1207
|
}
|
|
1208
|
+
function createBindingV2__EXPERIMENTAL(editor, id, doc, docMap, options = {}) {
|
|
1209
|
+
if (!(doc !== undefined && doc !== null)) {
|
|
1210
|
+
formatDevErrorMessage(`createBinding: doc is null or undefined`);
|
|
1211
|
+
}
|
|
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
|
+
}
|
|
1222
|
+
function isBindingV1(binding) {
|
|
1223
|
+
return Object.hasOwn(binding, 'collabNodeMap');
|
|
1224
|
+
}
|
|
1089
1225
|
|
|
1090
1226
|
function createRelativePosition(point, binding) {
|
|
1091
1227
|
const collabNodeMap = binding.collabNodeMap;
|
|
@@ -1122,6 +1258,49 @@ function createRelativePosition(point, binding) {
|
|
|
1122
1258
|
}
|
|
1123
1259
|
return yjs.createRelativePositionFromTypeIndex(sharedType, offset);
|
|
1124
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
|
+
}
|
|
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`);
|
|
1287
|
+
}
|
|
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();
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
i++;
|
|
1298
|
+
child = child.getNextSibling();
|
|
1299
|
+
}
|
|
1300
|
+
return yjs.createRelativePositionFromTypeIndex(yType, i);
|
|
1301
|
+
}
|
|
1302
|
+
return null;
|
|
1303
|
+
}
|
|
1125
1304
|
function createAbsolutePosition(relativePosition, binding) {
|
|
1126
1305
|
return yjs.createAbsolutePositionFromRelativePosition(relativePosition, binding.doc);
|
|
1127
1306
|
}
|
|
@@ -1263,6 +1442,9 @@ function updateCursor(binding, cursor, nextSelection, nodeMap) {
|
|
|
1263
1442
|
selections.pop();
|
|
1264
1443
|
}
|
|
1265
1444
|
}
|
|
1445
|
+
/**
|
|
1446
|
+
* @deprecated Use `$getAnchorAndFocusForUserState` instead.
|
|
1447
|
+
*/
|
|
1266
1448
|
function getAnchorAndFocusCollabNodesForUserState(binding, userState) {
|
|
1267
1449
|
const {
|
|
1268
1450
|
anchorPos,
|
|
@@ -1287,6 +1469,56 @@ function getAnchorAndFocusCollabNodesForUserState(binding, userState) {
|
|
|
1287
1469
|
focusOffset
|
|
1288
1470
|
};
|
|
1289
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
|
+
};
|
|
1496
|
+
}
|
|
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;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return {
|
|
1516
|
+
anchorKey: anchorNode !== null ? anchorNode.getKey() : null,
|
|
1517
|
+
anchorOffset,
|
|
1518
|
+
focusKey: focusNode !== null ? focusNode.getKey() : null,
|
|
1519
|
+
focusOffset
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1290
1522
|
function $syncLocalCursorPosition(binding, provider) {
|
|
1291
1523
|
const awareness = provider.awareness;
|
|
1292
1524
|
const localState = awareness.getLocalState();
|
|
@@ -1294,14 +1526,12 @@ function $syncLocalCursorPosition(binding, provider) {
|
|
|
1294
1526
|
return;
|
|
1295
1527
|
}
|
|
1296
1528
|
const {
|
|
1297
|
-
|
|
1529
|
+
anchorKey,
|
|
1298
1530
|
anchorOffset,
|
|
1299
|
-
|
|
1531
|
+
focusKey,
|
|
1300
1532
|
focusOffset
|
|
1301
|
-
} =
|
|
1302
|
-
if (
|
|
1303
|
-
const anchorKey = anchorCollabNode.getKey();
|
|
1304
|
-
const focusKey = focusCollabNode.getKey();
|
|
1533
|
+
} = $getAnchorAndFocusForUserState(binding, localState);
|
|
1534
|
+
if (anchorKey !== null && focusKey !== null) {
|
|
1305
1535
|
const selection = lexical.$getSelection();
|
|
1306
1536
|
if (!lexical.$isRangeSelection(selection)) {
|
|
1307
1537
|
return;
|
|
@@ -1342,6 +1572,46 @@ sharedType, offset) {
|
|
|
1342
1572
|
}
|
|
1343
1573
|
return [null, 0];
|
|
1344
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
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
return [node, lexicalOffset];
|
|
1600
|
+
} else {
|
|
1601
|
+
const nodes = mapping.get(yType);
|
|
1602
|
+
if (nodes === undefined) {
|
|
1603
|
+
return [null, 0];
|
|
1604
|
+
}
|
|
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++;
|
|
1610
|
+
}
|
|
1611
|
+
const textNode = nodes[i];
|
|
1612
|
+
return [textNode, Math.min(adjustedOffset, textNode.getTextContentSize())];
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1345
1615
|
function getAwarenessStatesDefault(_binding, provider) {
|
|
1346
1616
|
return provider.awareness.getStates();
|
|
1347
1617
|
}
|
|
@@ -1373,14 +1643,12 @@ function syncCursorPositions(binding, provider, options) {
|
|
|
1373
1643
|
}
|
|
1374
1644
|
if (focusing) {
|
|
1375
1645
|
const {
|
|
1376
|
-
|
|
1646
|
+
anchorKey,
|
|
1377
1647
|
anchorOffset,
|
|
1378
|
-
|
|
1648
|
+
focusKey,
|
|
1379
1649
|
focusOffset
|
|
1380
|
-
} =
|
|
1381
|
-
if (
|
|
1382
|
-
const anchorKey = anchorCollabNode.getKey();
|
|
1383
|
-
const focusKey = focusCollabNode.getKey();
|
|
1650
|
+
} = editor.read(() => $getAnchorAndFocusForUserState(binding, awareness));
|
|
1651
|
+
if (anchorKey !== null && focusKey !== null) {
|
|
1384
1652
|
selection = cursor.selection;
|
|
1385
1653
|
if (selection === null) {
|
|
1386
1654
|
selection = createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset);
|
|
@@ -1431,8 +1699,13 @@ function syncLexicalSelectionToYjs(binding, provider, prevSelection, nextSelecti
|
|
|
1431
1699
|
}
|
|
1432
1700
|
}
|
|
1433
1701
|
if (lexical.$isRangeSelection(nextSelection)) {
|
|
1434
|
-
|
|
1435
|
-
|
|
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);
|
|
1708
|
+
}
|
|
1436
1709
|
}
|
|
1437
1710
|
if (shouldUpdatePosition(currentAnchorPos, anchorPos) || shouldUpdatePosition(currentFocusPos, focusPos)) {
|
|
1438
1711
|
awareness.setLocalState({
|
|
@@ -1447,6 +1720,568 @@ function syncLexicalSelectionToYjs(binding, provider, prevSelection, nextSelecti
|
|
|
1447
1720
|
}
|
|
1448
1721
|
}
|
|
1449
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;
|
|
1739
|
+
}
|
|
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`);
|
|
1745
|
+
}
|
|
1746
|
+
if (!node) {
|
|
1747
|
+
node = new nodeInfo.klass();
|
|
1748
|
+
keysChanged = null;
|
|
1749
|
+
childListChanged = true;
|
|
1750
|
+
}
|
|
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
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
} else {
|
|
1769
|
+
{
|
|
1770
|
+
formatDevErrorMessage(`XmlHook is not supported`);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
};
|
|
1774
|
+
{
|
|
1775
|
+
el.toArray().forEach($createChildren);
|
|
1776
|
+
}
|
|
1777
|
+
$spliceChildren(node, children);
|
|
1778
|
+
}
|
|
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];
|
|
1801
|
+
} else {
|
|
1802
|
+
properties[k] = attrs[k];
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
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]);
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
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;
|
|
1845
|
+
}
|
|
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++;
|
|
1858
|
+
} else {
|
|
1859
|
+
node.splice(nextIndex, 0, [nextChildNode]);
|
|
1860
|
+
nextIndex++;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
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, []);
|
|
1869
|
+
}
|
|
1870
|
+
};
|
|
1871
|
+
const $createOrUpdateTextNodesFromYText = (text, binding, snapshot, prevSnapshot, computeYChange) => {
|
|
1872
|
+
const deltas = toDelta(text, snapshot, prevSnapshot, computeYChange);
|
|
1873
|
+
|
|
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`);
|
|
1885
|
+
}
|
|
1886
|
+
const node = new nodeInfo.klass();
|
|
1887
|
+
if (!lexical.$isTextNode(node)) {
|
|
1888
|
+
throw new Error(`$createTextNodesFromYText: Node ${type} is not a TextNode`);
|
|
1889
|
+
}
|
|
1890
|
+
return node;
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
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);
|
|
1904
|
+
}
|
|
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);
|
|
1912
|
+
}
|
|
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)
|
|
1927
|
+
};
|
|
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);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
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;
|
|
1949
|
+
}
|
|
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);
|
|
1956
|
+
}
|
|
1957
|
+
return eq;
|
|
1958
|
+
};
|
|
1959
|
+
const normalizeNodeContent = node => {
|
|
1960
|
+
if (!(node instanceof lexical.ElementNode)) {
|
|
1961
|
+
return [];
|
|
1962
|
+
}
|
|
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);
|
|
1974
|
+
} else {
|
|
1975
|
+
res.push(n);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
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
|
+
}
|
|
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;
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
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;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
n = n.right;
|
|
2050
|
+
}
|
|
2051
|
+
return {
|
|
2052
|
+
nAttrs,
|
|
2053
|
+
str
|
|
2054
|
+
};
|
|
2055
|
+
};
|
|
2056
|
+
const $updateYText = (ytext, ltexts, binding) => {
|
|
2057
|
+
binding.mapping.set(ytext, ltexts);
|
|
2058
|
+
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
|
+
}
|
|
2068
|
+
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()
|
|
2081
|
+
};
|
|
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;
|
|
2092
|
+
}
|
|
2093
|
+
cursorOffset += c.insert.length;
|
|
2094
|
+
}
|
|
2095
|
+
} else {
|
|
2096
|
+
cursorOffset = nextText.length;
|
|
2097
|
+
}
|
|
2098
|
+
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;
|
|
2124
|
+
}
|
|
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!');
|
|
2154
|
+
}
|
|
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
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
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);
|
|
2202
|
+
} else {
|
|
2203
|
+
break;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
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);
|
|
2215
|
+
}
|
|
2216
|
+
} else if (equalYTypePNode(rightY, rightL, binding)) {
|
|
2217
|
+
// update mapping
|
|
2218
|
+
binding.mapping.set(rightY, rightL);
|
|
2219
|
+
} else {
|
|
2220
|
+
break;
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
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);
|
|
2232
|
+
}
|
|
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;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
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;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
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);
|
|
2274
|
+
}
|
|
2275
|
+
if (left + right < lChildCnt) {
|
|
2276
|
+
const ins = [];
|
|
2277
|
+
for (let i = left; i < lChildCnt - right; i++) {
|
|
2278
|
+
ins.push($createTypeFromTextOrElementNode(lChildren[i], binding));
|
|
2279
|
+
}
|
|
2280
|
+
yDomFragment.insert(left, ins);
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
const matchNodeName = (yElement, lnode) => !(lnode instanceof Array) && yElement.nodeName === lnode.getType();
|
|
2284
|
+
|
|
1450
2285
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1451
2286
|
function $syncStateEvent(binding, event) {
|
|
1452
2287
|
const {
|
|
@@ -1533,23 +2368,7 @@ function syncYjsChangesToLexical(binding, provider, events, isFromUndoManger, sy
|
|
|
1533
2368
|
const event = events[i];
|
|
1534
2369
|
$syncEvent(binding, event);
|
|
1535
2370
|
}
|
|
1536
|
-
|
|
1537
|
-
if (lexical.$isRangeSelection(selection)) {
|
|
1538
|
-
if (doesSelectionNeedRecovering(selection)) {
|
|
1539
|
-
const prevSelection = currentEditorState._selection;
|
|
1540
|
-
if (lexical.$isRangeSelection(prevSelection)) {
|
|
1541
|
-
$syncLocalCursorPosition(binding, provider);
|
|
1542
|
-
if (doesSelectionNeedRecovering(selection)) {
|
|
1543
|
-
// If the selected node is deleted, move the selection to the previous or parent node.
|
|
1544
|
-
const anchorNodeKey = selection.anchor.key;
|
|
1545
|
-
$moveSelectionToPreviousNode(anchorNodeKey, currentEditorState);
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
syncLexicalSelectionToYjs(binding, provider, prevSelection, lexical.$getSelection());
|
|
1549
|
-
} else {
|
|
1550
|
-
$syncLocalCursorPosition(binding, provider);
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
2371
|
+
$syncCursorFromYjs(currentEditorState, binding, provider);
|
|
1553
2372
|
if (!isFromUndoManger) {
|
|
1554
2373
|
// If it is an external change, we don't want the current scroll position to get changed
|
|
1555
2374
|
// since the user might've intentionally scrolled somewhere else in the document.
|
|
@@ -1558,19 +2377,31 @@ function syncYjsChangesToLexical(binding, provider, events, isFromUndoManger, sy
|
|
|
1558
2377
|
}, {
|
|
1559
2378
|
onUpdate: () => {
|
|
1560
2379
|
syncCursorPositionsFn(binding, provider);
|
|
1561
|
-
|
|
1562
|
-
// we need to re-add a paragraph. To ensure this insertion properly syncs with other clients,
|
|
1563
|
-
// it must be placed outside of the update block above that has tags 'collaboration' or 'historic'.
|
|
1564
|
-
editor.update(() => {
|
|
1565
|
-
if (lexical.$getRoot().getChildrenSize() === 0) {
|
|
1566
|
-
lexical.$getRoot().append(lexical.$createParagraphNode());
|
|
1567
|
-
}
|
|
1568
|
-
});
|
|
2380
|
+
editor.update(() => $ensureEditorNotEmpty());
|
|
1569
2381
|
},
|
|
1570
2382
|
skipTransforms: true,
|
|
1571
2383
|
tag: isFromUndoManger ? lexical.HISTORIC_TAG : lexical.COLLABORATION_TAG
|
|
1572
2384
|
});
|
|
1573
2385
|
}
|
|
2386
|
+
function $syncCursorFromYjs(editorState, binding, provider) {
|
|
2387
|
+
const selection = lexical.$getSelection();
|
|
2388
|
+
if (lexical.$isRangeSelection(selection)) {
|
|
2389
|
+
if (doesSelectionNeedRecovering(selection)) {
|
|
2390
|
+
const prevSelection = editorState._selection;
|
|
2391
|
+
if (lexical.$isRangeSelection(prevSelection)) {
|
|
2392
|
+
$syncLocalCursorPosition(binding, provider);
|
|
2393
|
+
if (doesSelectionNeedRecovering(selection)) {
|
|
2394
|
+
// If the selected node is deleted, move the selection to the previous or parent node.
|
|
2395
|
+
const anchorNodeKey = selection.anchor.key;
|
|
2396
|
+
$moveSelectionToPreviousNode(anchorNodeKey, editorState);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
syncLexicalSelectionToYjs(binding, provider, prevSelection, lexical.$getSelection());
|
|
2400
|
+
} else {
|
|
2401
|
+
$syncLocalCursorPosition(binding, provider);
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
1574
2405
|
function $handleNormalizationMergeConflicts(binding, normalizedNodes) {
|
|
1575
2406
|
// We handle the merge operations here
|
|
1576
2407
|
const normalizedNodesKeys = Array.from(normalizedNodes);
|
|
@@ -1611,6 +2442,15 @@ function $handleNormalizationMergeConflicts(binding, normalizedNodes) {
|
|
|
1611
2442
|
collabNode._text = text;
|
|
1612
2443
|
}
|
|
1613
2444
|
}
|
|
2445
|
+
|
|
2446
|
+
// If there was a collision on the top level paragraph
|
|
2447
|
+
// we need to re-add a paragraph. To ensure this insertion properly syncs with other clients,
|
|
2448
|
+
// it must be placed outside of the update block above that has tags 'collaboration' or 'historic'.
|
|
2449
|
+
function $ensureEditorNotEmpty() {
|
|
2450
|
+
if (lexical.$getRoot().getChildrenSize() === 0) {
|
|
2451
|
+
lexical.$getRoot().append(lexical.$createParagraphNode());
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
1614
2454
|
function syncLexicalUpdateToYjs(binding, provider, prevEditorState, currEditorState, dirtyElements, dirtyLeaves, normalizedNodes, tags) {
|
|
1615
2455
|
syncWithTransaction(binding, () => {
|
|
1616
2456
|
currEditorState.read(() => {
|
|
@@ -1640,6 +2480,96 @@ function syncLexicalUpdateToYjs(binding, provider, prevEditorState, currEditorSt
|
|
|
1640
2480
|
});
|
|
1641
2481
|
});
|
|
1642
2482
|
}
|
|
2483
|
+
function $syncEventV2(binding, event) {
|
|
2484
|
+
const {
|
|
2485
|
+
target
|
|
2486
|
+
} = event;
|
|
2487
|
+
if (target instanceof yjs.XmlElement && event instanceof yjs.YXmlEvent) {
|
|
2488
|
+
$createOrUpdateNodeFromYElement(target, binding, event.attributesChanged,
|
|
2489
|
+
// @ts-expect-error childListChanged is private
|
|
2490
|
+
event.childListChanged);
|
|
2491
|
+
} else if (target instanceof yjs.XmlText && event instanceof yjs.YTextEvent) {
|
|
2492
|
+
const parent = target.parent;
|
|
2493
|
+
if (parent instanceof yjs.XmlElement) {
|
|
2494
|
+
// Need to sync via parent element in order to attach new next nodes.
|
|
2495
|
+
$createOrUpdateNodeFromYElement(parent, binding, new Set(), true);
|
|
2496
|
+
} else {
|
|
2497
|
+
{
|
|
2498
|
+
formatDevErrorMessage(`Expected XmlElement parent for XmlText`);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
} else {
|
|
2502
|
+
{
|
|
2503
|
+
formatDevErrorMessage(`Expected xml or text event`);
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
function syncYjsChangesToLexicalV2__EXPERIMENTAL(binding, provider, events, transaction, isFromUndoManger) {
|
|
2508
|
+
const editor = binding.editor;
|
|
2509
|
+
const editorState = editor._editorState;
|
|
2510
|
+
|
|
2511
|
+
// Remove deleted nodes from the mapping
|
|
2512
|
+
yjs.iterateDeletedStructs(transaction, transaction.deleteSet, struct => {
|
|
2513
|
+
if (struct.constructor === yjs.Item) {
|
|
2514
|
+
const content = struct.content;
|
|
2515
|
+
const type = content.type;
|
|
2516
|
+
if (type) {
|
|
2517
|
+
binding.mapping.delete(type);
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
});
|
|
2521
|
+
|
|
2522
|
+
// This line precompute the delta before editor update. The reason is
|
|
2523
|
+
// delta is computed when it is accessed. Note that this can only be
|
|
2524
|
+
// safely computed during the event call. If it is accessed after event
|
|
2525
|
+
// call it might result in unexpected behavior.
|
|
2526
|
+
// https://github.com/yjs/yjs/blob/00ef472d68545cb260abd35c2de4b3b78719c9e4/src/utils/YEvent.js#L132
|
|
2527
|
+
events.forEach(event => event.delta);
|
|
2528
|
+
editor.update(() => {
|
|
2529
|
+
for (let i = 0; i < events.length; i++) {
|
|
2530
|
+
const event = events[i];
|
|
2531
|
+
$syncEventV2(binding, event);
|
|
2532
|
+
}
|
|
2533
|
+
$syncCursorFromYjs(editorState, binding, provider);
|
|
2534
|
+
if (!isFromUndoManger) {
|
|
2535
|
+
// If it is an external change, we don't want the current scroll position to get changed
|
|
2536
|
+
// since the user might've intentionally scrolled somewhere else in the document.
|
|
2537
|
+
lexical.$addUpdateTag(lexical.SKIP_SCROLL_INTO_VIEW_TAG);
|
|
2538
|
+
}
|
|
2539
|
+
}, {
|
|
2540
|
+
// Need any text node normalization to be synchronously updated back to Yjs, otherwise the
|
|
2541
|
+
// binding.mapping will get out of sync.
|
|
2542
|
+
discrete: true,
|
|
2543
|
+
onUpdate: () => {
|
|
2544
|
+
syncCursorPositions(binding, provider);
|
|
2545
|
+
editor.update(() => $ensureEditorNotEmpty());
|
|
2546
|
+
},
|
|
2547
|
+
skipTransforms: true,
|
|
2548
|
+
tag: isFromUndoManger ? lexical.HISTORIC_TAG : lexical.COLLABORATION_TAG
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
function syncLexicalUpdateToYjsV2__EXPERIMENTAL(binding, provider, prevEditorState, currEditorState, dirtyElements, normalizedNodes, tags) {
|
|
2552
|
+
const isFromYjs = tags.has(lexical.COLLABORATION_TAG) || tags.has(lexical.HISTORIC_TAG);
|
|
2553
|
+
if (isFromYjs && normalizedNodes.size === 0) {
|
|
2554
|
+
return;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
// Nodes are normalized synchronously (`discrete: true` above), so the mapping may now be
|
|
2558
|
+
// incorrect for these nodes, as they point to `getLatest` which is mutable within an update.
|
|
2559
|
+
normalizedNodes.forEach(nodeKey => {
|
|
2560
|
+
binding.mapping.deleteNode(nodeKey);
|
|
2561
|
+
});
|
|
2562
|
+
syncWithTransaction(binding, () => {
|
|
2563
|
+
currEditorState.read(() => {
|
|
2564
|
+
if (dirtyElements.has('root')) {
|
|
2565
|
+
$updateYFragment(binding.doc, binding.root, lexical.$getRoot(), binding, new Set(dirtyElements.keys()));
|
|
2566
|
+
}
|
|
2567
|
+
const selection = lexical.$getSelection();
|
|
2568
|
+
const prevSelection = prevEditorState._selection;
|
|
2569
|
+
syncLexicalSelectionToYjs(binding, provider, prevSelection, selection);
|
|
2570
|
+
});
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
1643
2573
|
|
|
1644
2574
|
/**
|
|
1645
2575
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
@@ -1688,10 +2618,13 @@ function setLocalStateFocus(provider, name, color, focusing, awarenessData) {
|
|
|
1688
2618
|
exports.CONNECTED_COMMAND = CONNECTED_COMMAND;
|
|
1689
2619
|
exports.TOGGLE_CONNECT_COMMAND = TOGGLE_CONNECT_COMMAND;
|
|
1690
2620
|
exports.createBinding = createBinding;
|
|
2621
|
+
exports.createBindingV2__EXPERIMENTAL = createBindingV2__EXPERIMENTAL;
|
|
1691
2622
|
exports.createUndoManager = createUndoManager;
|
|
1692
2623
|
exports.getAnchorAndFocusCollabNodesForUserState = getAnchorAndFocusCollabNodesForUserState;
|
|
1693
2624
|
exports.initLocalState = initLocalState;
|
|
1694
2625
|
exports.setLocalStateFocus = setLocalStateFocus;
|
|
1695
2626
|
exports.syncCursorPositions = syncCursorPositions;
|
|
1696
2627
|
exports.syncLexicalUpdateToYjs = syncLexicalUpdateToYjs;
|
|
2628
|
+
exports.syncLexicalUpdateToYjsV2__EXPERIMENTAL = syncLexicalUpdateToYjsV2__EXPERIMENTAL;
|
|
1697
2629
|
exports.syncYjsChangesToLexical = syncYjsChangesToLexical;
|
|
2630
|
+
exports.syncYjsChangesToLexicalV2__EXPERIMENTAL = syncYjsChangesToLexicalV2__EXPERIMENTAL;
|