@lexical/yjs
Advanced tools
Comparing version 0.7.8 to 0.7.9
@@ -28,3 +28,2 @@ /** | ||
} | ||
getNode() { | ||
@@ -34,19 +33,14 @@ const node = lexical.$getNodeByKey(this._key); | ||
} | ||
getKey() { | ||
return this._key; | ||
} | ||
getSharedType() { | ||
return this._map; | ||
} | ||
getType() { | ||
return this._type; | ||
} | ||
getSize() { | ||
return 1; | ||
} | ||
getOffset() { | ||
@@ -56,3 +50,2 @@ const collabElementNode = this._parent; | ||
} | ||
destroy(binding) { | ||
@@ -62,7 +55,6 @@ const collabNodeMap = binding.collabNodeMap; | ||
} | ||
} | ||
function $createCollabLineBreakNode(map, parent) { | ||
const collabNode = new CollabLineBreakNode(map, parent); // @ts-expect-error: internal field | ||
const collabNode = new CollabLineBreakNode(map, parent); | ||
// @ts-expect-error: internal field | ||
map._collabNode = collabNode; | ||
@@ -79,2 +71,3 @@ return collabNode; | ||
*/ | ||
function simpleDiffWithCursor(a, b, cursor) { | ||
@@ -84,21 +77,16 @@ const aLength = a.length; | ||
let left = 0; // number of same characters counting from left | ||
let right = 0; // number of same characters counting from right | ||
// Iterate left to the right until we find a changed character | ||
// First iteration considers the current cursor position | ||
while (left < aLength && left < bLength && a[left] === b[left] && left < cursor) { | ||
left++; | ||
} // Iterate right to the left until we find a changed character | ||
} | ||
// Iterate right to the left until we find a changed character | ||
while (right + left < aLength && right + left < bLength && a[aLength - right - 1] === b[bLength - right - 1]) { | ||
right++; | ||
} // Try to iterate left further to the right without caring about the current cursor position | ||
} | ||
// Try to iterate left further to the right without caring about the current cursor position | ||
while (right + left < aLength && right + left < bLength && a[left] === b[left]) { | ||
left++; | ||
} | ||
return { | ||
@@ -118,10 +106,7 @@ index: left, | ||
*/ | ||
function diffTextContentAndApplyDelta(collabNode, key, prevText, nextText) { | ||
const selection = lexical.$getSelection(); | ||
let cursorOffset = nextText.length; | ||
if (lexical.$isRangeSelection(selection) && selection.isCollapsed()) { | ||
const anchor = selection.anchor; | ||
if (anchor.key === key) { | ||
@@ -131,7 +116,5 @@ cursorOffset = anchor.offset; | ||
} | ||
const diff = simpleDiffWithCursor(prevText, nextText, cursorOffset); | ||
collabNode.spliceText(diff.index, diff.remove, diff.insert); | ||
} | ||
class CollabTextNode { | ||
@@ -146,3 +129,2 @@ constructor(map, text, parent, type) { | ||
} | ||
getPrevNode(nodeMap) { | ||
@@ -152,7 +134,5 @@ if (nodeMap === null) { | ||
} | ||
const node = nodeMap.get(this._key); | ||
return lexical.$isTextNode(node) ? node : null; | ||
} | ||
getNode() { | ||
@@ -162,19 +142,14 @@ const node = lexical.$getNodeByKey(this._key); | ||
} | ||
getSharedType() { | ||
return this._map; | ||
} | ||
getType() { | ||
return this._type; | ||
} | ||
getKey() { | ||
return this._key; | ||
} | ||
getSize() { | ||
return this._text.length + (this._normalized ? 0 : 1); | ||
} | ||
getOffset() { | ||
@@ -184,3 +159,2 @@ const collabElementNode = this._parent; | ||
} | ||
spliceText(index, delCount, newText) { | ||
@@ -190,7 +164,5 @@ const collabElementNode = this._parent; | ||
const offset = this.getOffset() + 1 + index; | ||
if (delCount !== 0) { | ||
xmlText.delete(offset, delCount); | ||
} | ||
if (newText !== '') { | ||
@@ -200,3 +172,2 @@ xmlText.insert(offset, newText); | ||
} | ||
syncPropertiesAndTextFromLexical(binding, nextLexicalNode, prevNodeMap) { | ||
@@ -206,6 +177,4 @@ const prevLexicalNode = this.getPrevNode(prevNodeMap); | ||
syncPropertiesFromLexical(binding, this._map, prevLexicalNode, nextLexicalNode); | ||
if (prevLexicalNode !== null) { | ||
const prevText = prevLexicalNode.__text; | ||
if (prevText !== nextText) { | ||
@@ -218,13 +187,9 @@ const key = nextLexicalNode.__key; | ||
} | ||
syncPropertiesAndTextFromYjs(binding, keysChanged) { | ||
const lexicalNode = this.getNode(); | ||
if (!(lexicalNode !== null)) { | ||
throw Error(`syncPropertiesAndTextFromYjs: cound not find decorator node`); | ||
} | ||
syncPropertiesFromYjs(binding, this._map, lexicalNode, keysChanged); | ||
const collabText = this._text; | ||
if (lexicalNode.__text !== collabText) { | ||
@@ -235,3 +200,2 @@ const writable = lexicalNode.getWritable(); | ||
} | ||
destroy(binding) { | ||
@@ -241,7 +205,6 @@ const collabNodeMap = binding.collabNodeMap; | ||
} | ||
} | ||
function $createCollabTextNode(map, text, parent, type) { | ||
const collabNode = new CollabTextNode(map, text, parent, type); // @ts-expect-error: internal field | ||
const collabNode = new CollabTextNode(map, text, parent, type); | ||
// @ts-expect-error: internal field | ||
map._collabNode = collabNode; | ||
@@ -261,7 +224,5 @@ return collabNode; | ||
const node = lexical.$getNodeByKey(key); | ||
if (!(node !== null)) { | ||
throw Error(`could not find node by key`); | ||
} | ||
return node; | ||
@@ -272,3 +233,2 @@ } | ||
let collabNode; | ||
if (lexical.$isElementNode(lexicalNode)) { | ||
@@ -297,21 +257,15 @@ const xmlText = new yjs.XmlText(); | ||
} | ||
collabNode._key = lexicalNode.__key; | ||
return collabNode; | ||
} | ||
function getNodeTypeFromSharedType(sharedType) { | ||
const type = sharedType instanceof yjs.Map ? sharedType.get('__type') : sharedType.getAttribute('__type'); | ||
if (!(type != null)) { | ||
throw Error(`Expected shared type to include type attribute`); | ||
} | ||
return type; | ||
} | ||
function getOrInitCollabNodeFromSharedType(binding, sharedType, parent) { | ||
// @ts-expect-error: internal field | ||
const collabNode = sharedType._collabNode; | ||
if (collabNode === undefined) { | ||
@@ -321,14 +275,10 @@ const registeredNodes = binding.editor._nodes; | ||
const nodeInfo = registeredNodes.get(type); | ||
if (!(nodeInfo !== undefined)) { | ||
throw Error(`Node ${type} is not registered`); | ||
} | ||
const sharedParent = sharedType.parent; | ||
const targetParent = parent === undefined && sharedParent !== null ? getOrInitCollabNodeFromSharedType(binding, sharedParent) : parent || null; | ||
if (!(targetParent instanceof CollabElementNode)) { | ||
throw Error(`Expected parent to be a collab element node`); | ||
} | ||
if (sharedType instanceof yjs.XmlText) { | ||
@@ -340,3 +290,2 @@ return $createCollabElementNode(sharedType, targetParent, type); | ||
} | ||
return $createCollabTextNode(sharedType, '', targetParent, type); | ||
@@ -347,3 +296,2 @@ } else if (sharedType instanceof yjs.XmlElement) { | ||
} | ||
return collabNode; | ||
@@ -355,11 +303,8 @@ } | ||
const nodeInfo = registeredNodes.get(type); | ||
if (!(nodeInfo !== undefined)) { | ||
throw Error(`Node ${type} is not registered`); | ||
} | ||
const lexicalNode = new nodeInfo.klass(); | ||
lexicalNode.__parent = parentKey; | ||
collabNode._key = lexicalNode.__key; | ||
if (collabNode instanceof CollabElementNode) { | ||
@@ -375,3 +320,2 @@ const xmlText = collabNode._xmlText; | ||
} | ||
binding.collabNodeMap.set(lexicalNode.__key, collabNode); | ||
@@ -383,21 +327,15 @@ return lexicalNode; | ||
let writableNode; | ||
for (let i = 0; i < properties.length; i++) { | ||
const property = properties[i]; | ||
if (excludedProperties.has(property)) { | ||
continue; | ||
} | ||
const prevValue = lexicalNode[property]; | ||
let nextValue = sharedType instanceof yjs.Map ? sharedType.get(property) : sharedType.getAttribute(property); | ||
if (prevValue !== nextValue) { | ||
if (nextValue instanceof yjs.Doc) { | ||
const yjsDocMap = binding.docMap; | ||
if (prevValue instanceof yjs.Doc) { | ||
yjsDocMap.delete(prevValue.guid); | ||
} | ||
const nestedEditor = lexical.createEditor(); | ||
@@ -409,7 +347,5 @@ const key = nextValue.guid; | ||
} | ||
if (writableNode === undefined) { | ||
writableNode = lexicalNode.getWritable(); | ||
} | ||
writableNode[property] = nextValue; | ||
@@ -423,3 +359,2 @@ } | ||
let properties = nodeProperties.get(type); | ||
if (properties === undefined) { | ||
@@ -431,5 +366,3 @@ properties = Object.keys(nextLexicalNode).filter(property => { | ||
} | ||
const EditorClass = binding.editor.constructor; | ||
for (let i = 0; i < properties.length; i++) { | ||
@@ -439,3 +372,2 @@ const property = properties[i]; | ||
let nextValue = nextLexicalNode[property]; | ||
if (prevValue !== nextValue) { | ||
@@ -445,3 +377,2 @@ if (nextValue instanceof EditorClass) { | ||
let prevDoc; | ||
if (prevValue instanceof EditorClass) { | ||
@@ -452,12 +383,12 @@ // @ts-expect-error Lexical node | ||
yjsDocMap.delete(prevKey); | ||
} // If we already have a document, use it. | ||
} | ||
// If we already have a document, use it. | ||
const doc = prevDoc || new yjs.Doc(); | ||
const key = doc.guid; // @ts-expect-error Lexical node | ||
const key = doc.guid; | ||
// @ts-expect-error Lexical node | ||
nextValue._key = key; | ||
yjsDocMap.set(key, doc); | ||
nextValue = doc; // Mark the node dirty as we've assigned a new key to it | ||
nextValue = doc; | ||
// Mark the node dirty as we've assigned a new key to it | ||
binding.editor.update(() => { | ||
@@ -467,3 +398,2 @@ nextLexicalNode.markDirty(); | ||
} | ||
if (sharedType instanceof yjs.Map) { | ||
@@ -485,3 +415,2 @@ sharedType.set(property, nextValue); | ||
const childrenLength = children.length; | ||
for (; i < childrenLength; i++) { | ||
@@ -493,10 +422,7 @@ const child = children[i]; | ||
const exceedsBoundary = boundaryIsEdge ? index >= offset : index > offset; | ||
if (exceedsBoundary && child instanceof CollabTextNode) { | ||
let textOffset = offset - childOffset - 1; | ||
if (textOffset < 0) { | ||
textOffset = 0; | ||
} | ||
const diffLength = index - offset; | ||
@@ -510,3 +436,2 @@ return { | ||
} | ||
if (index > offset) { | ||
@@ -528,3 +453,2 @@ return { | ||
} | ||
return { | ||
@@ -541,9 +465,9 @@ length: 0, | ||
let recoveryNeeded = false; | ||
try { | ||
const anchorNode = anchor.getNode(); | ||
const focusNode = focus.getNode(); | ||
if ( // We might have removed a node that no longer exists | ||
!anchorNode.isAttached() || !focusNode.isAttached() || // If we've split a node, then the offset might not be right | ||
if ( | ||
// We might have removed a node that no longer exists | ||
!anchorNode.isAttached() || !focusNode.isAttached() || | ||
// If we've split a node, then the offset might not be right | ||
lexical.$isTextNode(anchorNode) && anchor.offset > anchorNode.getTextContentSize() || lexical.$isTextNode(focusNode) && focus.offset > focusNode.getTextContentSize()) { | ||
@@ -557,3 +481,2 @@ recoveryNeeded = true; | ||
} | ||
return recoveryNeeded; | ||
@@ -567,6 +490,4 @@ } | ||
let nodeKey = element.__first; | ||
while (nodeKey !== null) { | ||
const node = nodeMap === null ? lexical.$getNodeByKey(nodeKey) : nodeMap.get(nodeKey); | ||
if (node === null || node === undefined) { | ||
@@ -577,7 +498,5 @@ { | ||
} | ||
children.push(nodeKey); | ||
nodeKey = node.__next; | ||
} | ||
return children; | ||
@@ -587,3 +506,2 @@ } | ||
const oldParent = node.getParent(); | ||
if (oldParent !== null) { | ||
@@ -593,4 +511,4 @@ const writableNode = node.getWritable(); | ||
const prevSibling = node.getPreviousSibling(); | ||
const nextSibling = node.getNextSibling(); // TODO: this function duplicates a bunch of operations, can be simplified. | ||
const nextSibling = node.getNextSibling(); | ||
// TODO: this function duplicates a bunch of operations, can be simplified. | ||
if (prevSibling === null) { | ||
@@ -606,3 +524,2 @@ if (nextSibling !== null) { | ||
const writablePrevSibling = prevSibling.getWritable(); | ||
if (nextSibling !== null) { | ||
@@ -615,6 +532,4 @@ const writableNextSibling = nextSibling.getWritable(); | ||
} | ||
writableNode.__prev = null; | ||
} | ||
if (nextSibling === null) { | ||
@@ -630,3 +545,2 @@ if (prevSibling !== null) { | ||
const writableNextSibling = nextSibling.getWritable(); | ||
if (prevSibling !== null) { | ||
@@ -639,6 +553,4 @@ const writablePrevSibling = prevSibling.getWritable(); | ||
} | ||
writableNode.__next = null; | ||
} | ||
writableParent.__size--; | ||
@@ -664,3 +576,2 @@ writableNode.__parent = null; | ||
} | ||
getPrevNode(nodeMap) { | ||
@@ -670,7 +581,5 @@ if (nodeMap === null) { | ||
} | ||
const node = nodeMap.get(this._key); | ||
return lexical.$isDecoratorNode(node) ? node : null; | ||
} | ||
getNode() { | ||
@@ -680,19 +589,14 @@ const node = lexical.$getNodeByKey(this._key); | ||
} | ||
getSharedType() { | ||
return this._xmlElem; | ||
} | ||
getType() { | ||
return this._type; | ||
} | ||
getKey() { | ||
return this._key; | ||
} | ||
getSize() { | ||
return 1; | ||
} | ||
getOffset() { | ||
@@ -702,3 +606,2 @@ const collabElementNode = this._parent; | ||
} | ||
syncPropertiesFromLexical(binding, nextLexicalNode, prevNodeMap) { | ||
@@ -709,27 +612,20 @@ const prevLexicalNode = this.getPrevNode(prevNodeMap); | ||
} | ||
syncPropertiesFromYjs(binding, keysChanged) { | ||
const lexicalNode = this.getNode(); | ||
if (!(lexicalNode !== null)) { | ||
throw Error(`syncPropertiesFromYjs: cound not find decorator node`); | ||
} | ||
const xmlElem = this._xmlElem; | ||
syncPropertiesFromYjs(binding, xmlElem, lexicalNode, keysChanged); | ||
} | ||
destroy(binding) { | ||
const collabNodeMap = binding.collabNodeMap; | ||
collabNodeMap.delete(this._key); | ||
this._unobservers.forEach(unobserver => unobserver()); | ||
this._unobservers.clear(); | ||
} | ||
} | ||
function $createCollabDecoratorNode(xmlElem, parent, type) { | ||
const collabNode = new CollabDecoratorNode(xmlElem, parent, type); // @ts-expect-error: internal field | ||
const collabNode = new CollabDecoratorNode(xmlElem, parent, type); | ||
// @ts-expect-error: internal field | ||
xmlElem._collabNode = collabNode; | ||
@@ -754,3 +650,2 @@ return collabNode; | ||
} | ||
getPrevNode(nodeMap) { | ||
@@ -760,7 +655,5 @@ if (nodeMap === null) { | ||
} | ||
const node = nodeMap.get(this._key); | ||
return lexical.$isElementNode(node) ? node : null; | ||
} | ||
getNode() { | ||
@@ -770,47 +663,34 @@ const node = lexical.$getNodeByKey(this._key); | ||
} | ||
getSharedType() { | ||
return this._xmlText; | ||
} | ||
getType() { | ||
return this._type; | ||
} | ||
getKey() { | ||
return this._key; | ||
} | ||
isEmpty() { | ||
return this._children.length === 0; | ||
} | ||
getSize() { | ||
return 1; | ||
} | ||
getOffset() { | ||
const collabElementNode = this._parent; | ||
if (!(collabElementNode !== null)) { | ||
throw Error(`getOffset: cound not find collab element node`); | ||
} | ||
return collabElementNode.getChildOffset(this); | ||
} | ||
syncPropertiesFromYjs(binding, keysChanged) { | ||
const lexicalNode = this.getNode(); | ||
if (!(lexicalNode !== null)) { | ||
throw Error(`syncPropertiesFromYjs: cound not find element node`); | ||
} | ||
syncPropertiesFromYjs(binding, this._xmlText, lexicalNode, keysChanged); | ||
} | ||
applyChildrenYjsDelta(binding, deltas) { | ||
const children = this._children; | ||
let currIndex = 0; | ||
for (let i = 0; i < deltas.length; i++) { | ||
@@ -820,3 +700,2 @@ const delta = deltas[i]; | ||
const deleteDelta = delta.delete; | ||
if (delta.retain != null) { | ||
@@ -826,3 +705,2 @@ currIndex += delta.retain; | ||
let deletionSize = deleteDelta; | ||
while (deletionSize > 0) { | ||
@@ -835,3 +713,2 @@ const { | ||
} = getPositionFromElementAndOffset(this, currIndex, false); | ||
if (node instanceof CollabElementNode || node instanceof CollabLineBreakNode || node instanceof CollabDecoratorNode) { | ||
@@ -844,4 +721,4 @@ children.splice(nodeIndex, 1); | ||
const nodeSize = node.getSize(); | ||
if (offset === 0 && delCount === 1 && nodeIndex > 0 && prevCollabNode instanceof CollabTextNode && length === nodeSize && // If the node has no keys, it's been deleted | ||
if (offset === 0 && delCount === 1 && nodeIndex > 0 && prevCollabNode instanceof CollabTextNode && length === nodeSize && | ||
// If the node has no keys, it's been deleted | ||
Array.from(node._map.keys()).length === 0) { | ||
@@ -857,3 +734,2 @@ // Merge the text node with previous. | ||
} | ||
deletionSize -= delCount; | ||
@@ -871,3 +747,2 @@ } else { | ||
} = getPositionFromElementAndOffset(this, currIndex, true); | ||
if (node instanceof CollabTextNode) { | ||
@@ -879,2 +754,3 @@ node._text = spliceString(node._text, offset, 0, insertDelta); | ||
// never can be dangling text. | ||
// We have a conflict where there was likely a CollabTextNode and | ||
@@ -886,3 +762,2 @@ // an Lexical TextNode too, but they were removed in a merge. So | ||
} | ||
currIndex += insertDelta.length; | ||
@@ -903,11 +778,8 @@ } else { | ||
} | ||
syncChildrenFromYjs(binding) { | ||
// Now diff the children of the collab node with that of our existing Lexical node. | ||
const lexicalNode = this.getNode(); | ||
if (!(lexicalNode !== null)) { | ||
throw Error(`syncChildrenFromYjs: cound not find element node`); | ||
} | ||
const key = lexicalNode.__key; | ||
@@ -924,7 +796,5 @@ const prevLexicalChildrenKeys = createChildrenArray(lexicalNode, null); | ||
let prevChildNode = null; | ||
if (collabChildrenLength !== lexicalChildrenKeysLength) { | ||
writableLexicalNode = lexicalNode.getWritable(); | ||
} | ||
for (let i = 0; i < collabChildrenLength; i++) { | ||
@@ -935,11 +805,8 @@ const lexicalChildKey = prevLexicalChildrenKeys[prevIndex]; | ||
const collabKey = childCollabNode._key; | ||
if (collabLexicalChildNode !== null && lexicalChildKey === collabKey) { | ||
const childNeedsUpdating = lexical.$isTextNode(collabLexicalChildNode); // Update | ||
const childNeedsUpdating = lexical.$isTextNode(collabLexicalChildNode); | ||
// Update | ||
visitedKeys.add(lexicalChildKey); | ||
if (childNeedsUpdating) { | ||
childCollabNode._key = lexicalChildKey; | ||
if (childCollabNode instanceof CollabElementNode) { | ||
@@ -965,7 +832,5 @@ const xmlText = childCollabNode._xmlText; | ||
collabKeys = new Set(); | ||
for (let s = 0; s < collabChildrenLength; s++) { | ||
const child = collabChildren[s]; | ||
const childKey = child._key; | ||
if (childKey !== '') { | ||
@@ -976,3 +841,2 @@ collabKeys.add(childKey); | ||
} | ||
if (collabLexicalChildNode !== null && lexicalChildKey !== undefined && !collabKeys.has(lexicalChildKey)) { | ||
@@ -985,13 +849,10 @@ const nodeToRemove = $getNodeByKeyOrThrow(lexicalChildKey); | ||
} | ||
writableLexicalNode = lexicalNode.getWritable(); // Create/Replace | ||
writableLexicalNode = lexicalNode.getWritable(); | ||
// Create/Replace | ||
const lexicalChildNode = createLexicalNodeFromCollabNode(binding, childCollabNode, key); | ||
const childKey = lexicalChildNode.__key; | ||
collabNodeMap.set(childKey, childCollabNode); | ||
if (prevChildNode === null) { | ||
const nextSibling = writableLexicalNode.getFirstChild(); | ||
writableLexicalNode.__first = childKey; | ||
if (nextSibling !== null) { | ||
@@ -1007,3 +868,2 @@ const writableNextSibling = nextSibling.getWritable(); | ||
lexicalChildNode.__prev = prevChildNode.__key; | ||
if (nextSibling !== null) { | ||
@@ -1015,7 +875,5 @@ const writableNextSibling = nextSibling.getWritable(); | ||
} | ||
if (i === collabChildrenLength - 1) { | ||
writableLexicalNode.__last = childKey; | ||
} | ||
writableLexicalNode.__size++; | ||
@@ -1025,6 +883,4 @@ prevChildNode = lexicalChildNode; | ||
} | ||
for (let i = 0; i < lexicalChildrenKeysLength; i++) { | ||
const lexicalChildKey = prevLexicalChildrenKeys[i]; | ||
if (!visitedKeys.has(lexicalChildKey)) { | ||
@@ -1034,7 +890,5 @@ // Remove | ||
const collabNode = binding.collabNodeMap.get(lexicalChildKey); | ||
if (collabNode !== undefined) { | ||
collabNode.destroy(binding); | ||
} | ||
removeFromParent(lexicalChildNode); | ||
@@ -1044,12 +898,9 @@ } | ||
} | ||
syncPropertiesFromLexical(binding, nextLexicalNode, prevNodeMap) { | ||
syncPropertiesFromLexical(binding, this._xmlText, this.getPrevNode(prevNodeMap), nextLexicalNode); | ||
} | ||
_syncChildFromLexical(binding, index, key, prevNodeMap, dirtyElements, dirtyLeaves) { | ||
const childCollabNode = this._children[index]; // Update | ||
const childCollabNode = this._children[index]; | ||
// Update | ||
const nextChildNode = $getNodeByKeyOrThrow(key); | ||
if (childCollabNode instanceof CollabElementNode && lexical.$isElementNode(nextChildNode)) { | ||
@@ -1064,3 +915,2 @@ childCollabNode.syncPropertiesFromLexical(binding, nextChildNode, prevNodeMap); | ||
} | ||
syncChildrenFromLexical(binding, nextLexicalNode, prevNodeMap, dirtyElements, dirtyLeaves) { | ||
@@ -1077,11 +927,8 @@ const prevLexicalNode = this.getPrevNode(prevNodeMap); | ||
let nextIndex = 0; | ||
while (prevIndex <= prevEndIndex && nextIndex <= nextEndIndex) { | ||
const prevKey = prevChildren[prevIndex]; | ||
const nextKey = nextChildren[nextIndex]; | ||
if (prevKey === nextKey) { | ||
// Nove move, create or remove | ||
this._syncChildFromLexical(binding, nextIndex, nextKey, prevNodeMap, dirtyElements, dirtyLeaves); | ||
prevIndex++; | ||
@@ -1093,10 +940,7 @@ nextIndex++; | ||
} | ||
if (nextChildrenSet === undefined) { | ||
nextChildrenSet = new Set(nextChildren); | ||
} | ||
const nextHasPrevKey = nextChildrenSet.has(prevKey); | ||
const prevHasNextKey = prevChildrenSet.has(nextKey); | ||
if (!nextHasPrevKey) { | ||
@@ -1111,3 +955,2 @@ // Remove | ||
collabNodeMap.set(nextKey, collabNode); | ||
if (prevHasNextKey) { | ||
@@ -1124,6 +967,4 @@ this.splice(binding, nextIndex, 1, collabNode); | ||
} | ||
const appendNewChildren = prevIndex > prevEndIndex; | ||
const removeOldChildren = nextIndex > nextEndIndex; | ||
if (appendNewChildren && !removeOldChildren) { | ||
@@ -1143,3 +984,2 @@ for (; nextIndex <= nextEndIndex; ++nextIndex) { | ||
} | ||
append(collabNode) { | ||
@@ -1150,3 +990,2 @@ const xmlText = this._xmlText; | ||
const offset = lastChild !== undefined ? lastChild.getOffset() + lastChild.getSize() : 0; | ||
if (collabNode instanceof CollabElementNode) { | ||
@@ -1156,7 +995,5 @@ xmlText.insertEmbed(offset, collabNode._xmlText); | ||
const map = collabNode._map; | ||
if (map.parent === null) { | ||
xmlText.insertEmbed(offset, map); | ||
} | ||
xmlText.insert(offset + 1, collabNode._text); | ||
@@ -1168,10 +1005,7 @@ } else if (collabNode instanceof CollabLineBreakNode) { | ||
} | ||
this._children.push(collabNode); | ||
} | ||
splice(binding, index, delCount, collabNode) { | ||
const children = this._children; | ||
const child = children[index]; | ||
if (child === undefined) { | ||
@@ -1181,15 +1015,10 @@ if (!(collabNode !== undefined)) { | ||
} | ||
this.append(collabNode); | ||
return; | ||
} | ||
const offset = child.getOffset(); | ||
if (!(offset !== -1)) { | ||
throw Error(`splice: expected offset to be greater than zero`); | ||
} | ||
const xmlText = this._xmlText; | ||
if (delCount !== 0) { | ||
@@ -1200,3 +1029,2 @@ // What if we delete many nodes, don't we need to get all their | ||
} | ||
if (collabNode instanceof CollabElementNode) { | ||
@@ -1206,7 +1034,5 @@ xmlText.insertEmbed(offset, collabNode._xmlText); | ||
const map = collabNode._map; | ||
if (map.parent === null) { | ||
xmlText.insertEmbed(offset, map); | ||
} | ||
xmlText.insert(offset + 1, collabNode._text); | ||
@@ -1218,6 +1044,4 @@ } else if (collabNode instanceof CollabLineBreakNode) { | ||
} | ||
if (delCount !== 0) { | ||
const childrenToDelete = children.slice(index, index + delCount); | ||
for (let i = 0; i < childrenToDelete.length; i++) { | ||
@@ -1227,3 +1051,2 @@ childrenToDelete[i].destroy(binding); | ||
} | ||
if (collabNode !== undefined) { | ||
@@ -1235,35 +1058,26 @@ children.splice(index, delCount, collabNode); | ||
} | ||
getChildOffset(collabNode) { | ||
let offset = 0; | ||
const children = this._children; | ||
for (let i = 0; i < children.length; i++) { | ||
const child = children[i]; | ||
if (child === collabNode) { | ||
return offset; | ||
} | ||
offset += child.getSize(); | ||
} | ||
return -1; | ||
} | ||
destroy(binding) { | ||
const collabNodeMap = binding.collabNodeMap; | ||
const children = this._children; | ||
for (let i = 0; i < children.length; i++) { | ||
children[i].destroy(binding); | ||
} | ||
collabNodeMap.delete(this._key); | ||
} | ||
} | ||
function $createCollabElementNode(xmlText, parent, type) { | ||
const collabNode = new CollabElementNode(xmlText, parent, type); // @ts-expect-error: internal field | ||
const collabNode = new CollabElementNode(xmlText, parent, type); | ||
// @ts-expect-error: internal field | ||
xmlText._collabNode = collabNode; | ||
@@ -1284,3 +1098,2 @@ return collabNode; | ||
} | ||
const rootXmlText = doc.get('root', yjs.XmlText); | ||
@@ -1310,22 +1123,16 @@ const root = $createCollabElementNode(rootXmlText, null, 'root'); | ||
*/ | ||
function createRelativePosition(point, binding) { | ||
const collabNodeMap = binding.collabNodeMap; | ||
const collabNode = collabNodeMap.get(point.key); | ||
if (collabNode === undefined) { | ||
return null; | ||
} | ||
let offset = point.offset; | ||
let sharedType = collabNode.getSharedType(); | ||
if (collabNode instanceof CollabTextNode) { | ||
sharedType = collabNode._parent._xmlText; | ||
const currentOffset = collabNode.getOffset(); | ||
if (currentOffset === -1) { | ||
return null; | ||
} | ||
offset = currentOffset + 1 + offset; | ||
@@ -1337,3 +1144,2 @@ } else if (collabNode instanceof CollabElementNode && point.type === 'element') { | ||
let node = parent.getFirstChild(); | ||
while (node !== null && i++ < offset) { | ||
@@ -1345,16 +1151,11 @@ if (lexical.$isTextNode(node)) { | ||
} | ||
node = node.getNextSibling(); | ||
} | ||
offset = accumulatedOffset; | ||
} | ||
return yjs.createRelativePositionFromTypeIndex(sharedType, offset); | ||
} | ||
function createAbsolutePosition(relativePosition, binding) { | ||
return yjs.createAbsolutePositionFromRelativePosition(relativePosition, binding.doc); | ||
} | ||
function shouldUpdatePosition(currentPos, pos) { | ||
@@ -1368,6 +1169,4 @@ if (currentPos == null) { | ||
} | ||
return false; | ||
} | ||
function createCursor(name, color) { | ||
@@ -1380,10 +1179,7 @@ return { | ||
} | ||
function destroySelection(binding, selection) { | ||
const cursorsContainer = binding.cursorsContainer; | ||
if (cursorsContainer !== null) { | ||
const selections = selection.selections; | ||
const selectionsLength = selections.length; | ||
for (let i = 0; i < selectionsLength; i++) { | ||
@@ -1394,6 +1190,4 @@ cursorsContainer.removeChild(selections[i]); | ||
} | ||
function destroyCursor(binding, cursor) { | ||
const selection = cursor.selection; | ||
if (selection !== null) { | ||
@@ -1403,3 +1197,2 @@ destroySelection(binding, selection); | ||
} | ||
function createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset) { | ||
@@ -1428,3 +1221,2 @@ const color = cursor.color; | ||
} | ||
function updateCursor(binding, cursor, nextSelection, nodeMap) { | ||
@@ -1434,16 +1226,11 @@ const editor = binding.editor; | ||
const cursorsContainer = binding.cursorsContainer; | ||
if (cursorsContainer === null || rootElement === null) { | ||
return; | ||
} | ||
const cursorsContainerOffsetParent = cursorsContainer.offsetParent; | ||
if (cursorsContainerOffsetParent === null) { | ||
return; | ||
} | ||
const containerRect = cursorsContainerOffsetParent.getBoundingClientRect(); | ||
const prevSelection = cursor.selection; | ||
if (nextSelection === null) { | ||
@@ -1460,3 +1247,2 @@ if (prevSelection === null) { | ||
} | ||
const caret = nextSelection.caret; | ||
@@ -1471,8 +1257,8 @@ const color = nextSelection.color; | ||
const focusNode = nodeMap.get(focusKey); | ||
if (anchorNode == null || focusNode == null) { | ||
return; | ||
} | ||
let selectionRects; | ||
let selectionRects; // In the case of a collapsed selection on a linebreak, we need | ||
// In the case of a collapsed selection on a linebreak, we need | ||
// to improvise as the browser will return nothing here as <br> | ||
@@ -1482,3 +1268,2 @@ // apparantly take up no visual space :/ | ||
// nothing all the time. | ||
if (anchorNode === focusNode && lexical.$isLineBreakNode(anchorNode)) { | ||
@@ -1489,17 +1274,12 @@ const brRect = editor.getElementByKey(anchorKey).getBoundingClientRect(); | ||
const range = selection.createDOMRange(editor, anchorNode, anchor.offset, focusNode, focus.offset); | ||
if (range === null) { | ||
return; | ||
} | ||
selectionRects = selection.createRectsFromDOMRange(editor, range); | ||
} | ||
const selectionsLength = selections.length; | ||
const selectionRectsLength = selectionRects.length; | ||
for (let i = 0; i < selectionRectsLength; i++) { | ||
const selectionRect = selectionRects[i]; | ||
let selection = selections[i]; | ||
if (selection === undefined) { | ||
@@ -1512,3 +1292,2 @@ selection = document.createElement('span'); | ||
} | ||
const top = selectionRect.top - containerRect.top; | ||
@@ -1519,3 +1298,2 @@ const left = selectionRect.left - containerRect.left; | ||
selection.firstChild.style.cssText = `${style}left:0;top:0;background-color:${color};opacity:0.3;`; | ||
if (i === selectionRectsLength - 1) { | ||
@@ -1527,3 +1305,2 @@ if (caret.parentNode !== selection) { | ||
} | ||
for (let i = selectionsLength - 1; i >= selectionRectsLength; i--) { | ||
@@ -1535,22 +1312,16 @@ const selection = selections[i]; | ||
} | ||
function syncLocalCursorPosition(binding, provider) { | ||
const awareness = provider.awareness; | ||
const localState = awareness.getLocalState(); | ||
if (localState === null) { | ||
return; | ||
} | ||
const anchorPos = localState.anchorPos; | ||
const focusPos = localState.focusPos; | ||
if (anchorPos !== null && focusPos !== null) { | ||
const anchorAbsPos = createAbsolutePosition(anchorPos, binding); | ||
const focusAbsPos = createAbsolutePosition(focusPos, binding); | ||
if (anchorAbsPos !== null && focusAbsPos !== null) { | ||
const [anchorCollabNode, anchorOffset] = getCollabNodeAndOffset(anchorAbsPos.type, anchorAbsPos.index); | ||
const [focusCollabNode, focusOffset] = getCollabNodeAndOffset(focusAbsPos.type, focusAbsPos.index); | ||
if (anchorCollabNode !== null && focusCollabNode !== null) { | ||
@@ -1560,7 +1331,5 @@ const anchorKey = anchorCollabNode.getKey(); | ||
const selection = lexical.$getSelection(); | ||
if (!lexical.$isRangeSelection(selection)) { | ||
return; | ||
} | ||
const anchor = selection.anchor; | ||
@@ -1574,7 +1343,5 @@ const focus = selection.focus; | ||
} | ||
function setPoint(point, key, offset) { | ||
if (point.key !== key || point.offset !== offset) { | ||
let anchorNode = lexical.$getNodeByKey(key); | ||
if (anchorNode !== null && !lexical.$isElementNode(anchorNode) && !lexical.$isTextNode(anchorNode)) { | ||
@@ -1586,15 +1353,12 @@ const parent = anchorNode.getParentOrThrow(); | ||
} | ||
point.set(key, offset, lexical.$isElementNode(anchorNode) ? 'element' : 'text'); | ||
} | ||
} | ||
function getCollabNodeAndOffset( // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
function getCollabNodeAndOffset( | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
sharedType, offset) { | ||
const collabNode = sharedType._collabNode; | ||
if (collabNode === undefined) { | ||
return [null, 0]; | ||
} | ||
if (collabNode instanceof CollabElementNode) { | ||
@@ -1605,3 +1369,2 @@ const { | ||
} = getPositionFromElementAndOffset(collabNode, offset, true); | ||
if (node === null) { | ||
@@ -1613,6 +1376,4 @@ return [collabNode, 0]; | ||
} | ||
return [null, 0]; | ||
} | ||
function syncCursorPositions(binding, provider) { | ||
@@ -1625,7 +1386,5 @@ const awarenessStates = Array.from(provider.awareness.getStates()); | ||
const visitedClientIDs = new Set(); | ||
for (let i = 0; i < awarenessStates.length; i++) { | ||
const awarenessState = awarenessStates[i]; | ||
const [clientID, awareness] = awarenessState; | ||
if (clientID !== localClientID) { | ||
@@ -1642,3 +1401,2 @@ visitedClientIDs.add(clientID); | ||
let cursor = cursors.get(clientID); | ||
if (cursor === undefined) { | ||
@@ -1648,11 +1406,8 @@ cursor = createCursor(name, color); | ||
} | ||
if (anchorPos !== null && focusPos !== null && focusing) { | ||
const anchorAbsPos = createAbsolutePosition(anchorPos, binding); | ||
const focusAbsPos = createAbsolutePosition(focusPos, binding); | ||
if (anchorAbsPos !== null && focusAbsPos !== null) { | ||
const [anchorCollabNode, anchorOffset] = getCollabNodeAndOffset(anchorAbsPos.type, anchorAbsPos.index); | ||
const [focusCollabNode, focusOffset] = getCollabNodeAndOffset(focusAbsPos.type, focusAbsPos.index); | ||
if (anchorCollabNode !== null && focusCollabNode !== null) { | ||
@@ -1662,3 +1417,2 @@ const anchorKey = anchorCollabNode.getKey(); | ||
selection = cursor.selection; | ||
if (selection === null) { | ||
@@ -1677,15 +1431,10 @@ selection = createCursorSelection(cursor, anchorKey, anchorOffset, focusKey, focusOffset); | ||
} | ||
updateCursor(binding, cursor, selection, nodeMap); | ||
} | ||
} | ||
const allClientIDs = Array.from(cursors.keys()); | ||
for (let i = 0; i < allClientIDs.length; i++) { | ||
const clientID = allClientIDs[i]; | ||
if (!visitedClientIDs.has(clientID)) { | ||
const cursor = cursors.get(clientID); | ||
if (cursor !== undefined) { | ||
@@ -1701,7 +1450,5 @@ destroyCursor(binding, cursor); | ||
const localState = awareness.getLocalState(); | ||
if (localState === null) { | ||
return; | ||
} | ||
const { | ||
@@ -1716,3 +1463,2 @@ anchorPos: currentAnchorPos, | ||
let focusPos = null; | ||
if (nextSelection === null || currentAnchorPos !== null && !nextSelection.is(prevSelection)) { | ||
@@ -1723,3 +1469,2 @@ if (prevSelection === null) { | ||
} | ||
if (lexical.$isRangeSelection(nextSelection)) { | ||
@@ -1729,3 +1474,2 @@ anchorPos = createRelativePosition(nextSelection.anchor, binding); | ||
} | ||
if (shouldUpdatePosition(currentAnchorPos, anchorPos) || shouldUpdatePosition(currentFocusPos, focusPos)) { | ||
@@ -1750,2 +1494,3 @@ awareness.setLocalState({ | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
function syncEvent(binding, event) { | ||
@@ -1756,3 +1501,2 @@ const { | ||
const collabNode = getOrInitCollabNodeFromSharedType(binding, target); | ||
if (collabNode instanceof CollabElementNode && event instanceof yjs.YTextEvent) { | ||
@@ -1764,8 +1508,8 @@ // @ts-expect-error We need to access the private property of the class | ||
delta | ||
} = event; // Update | ||
} = event; | ||
// Update | ||
if (keysChanged.size > 0) { | ||
collabNode.syncPropertiesFromYjs(binding, keysChanged); | ||
} | ||
if (childListChanged) { | ||
@@ -1778,4 +1522,5 @@ collabNode.applyChildrenYjsDelta(binding, delta); | ||
keysChanged | ||
} = event; // Update | ||
} = event; | ||
// Update | ||
if (keysChanged.size > 0) { | ||
@@ -1787,4 +1532,5 @@ collabNode.syncPropertiesAndTextFromYjs(binding, keysChanged); | ||
attributesChanged | ||
} = event; // Update | ||
} = event; | ||
// Update | ||
if (attributesChanged.size > 0) { | ||
@@ -1799,3 +1545,2 @@ collabNode.syncPropertiesFromYjs(binding, attributesChanged); | ||
} | ||
function syncYjsChangesToLexical(binding, provider, events) { | ||
@@ -1806,3 +1551,2 @@ const editor = binding.editor; | ||
const pendingEditorState = editor._pendingEditorState; | ||
for (let i = 0; i < events.length; i++) { | ||
@@ -1812,5 +1556,3 @@ const event = events[i]; | ||
} | ||
const selection = lexical.$getSelection(); | ||
if (lexical.$isRangeSelection(selection)) { | ||
@@ -1822,3 +1564,2 @@ // We can't use Yjs's cursor position here, as it doesn't always | ||
const prevSelection = currentEditorState._selection; | ||
if (lexical.$isRangeSelection(prevSelection)) { | ||
@@ -1829,3 +1570,2 @@ const prevOffsetView = offset.$createOffsetView(editor, 0, currentEditorState); | ||
const nextSelection = nextOffsetView.createSelectionFromOffsets(start, end, prevOffsetView); | ||
if (nextSelection !== null) { | ||
@@ -1836,12 +1576,12 @@ lexical.$setSelection(nextSelection); | ||
syncLocalCursorPosition(binding, provider); | ||
if (doesSelectionNeedRecovering(selection)) { | ||
const root = lexical.$getRoot(); | ||
if (doesSelectionNeedRecovering(selection)) { | ||
const root = lexical.$getRoot(); // If there was a collision on the top level paragraph | ||
// If there was a collision on the top level paragraph | ||
// we need to re-add a paragraph | ||
if (root.getChildrenSize() === 0) { | ||
root.append(lexical.$createParagraphNode()); | ||
} // Fallback | ||
} | ||
// Fallback | ||
lexical.$getRoot().selectEnd(); | ||
@@ -1851,3 +1591,2 @@ } | ||
} | ||
syncLexicalSelectionToYjs(binding, provider, prevSelection, lexical.$getSelection()); | ||
@@ -1866,3 +1605,2 @@ } else { | ||
} | ||
function handleNormalizationMergeConflicts(binding, normalizedNodes) { | ||
@@ -1873,3 +1611,2 @@ // We handle the merge operations here | ||
const mergedNodes = []; | ||
for (let i = 0; i < normalizedNodesKeys.length; i++) { | ||
@@ -1879,3 +1616,2 @@ const nodeKey = normalizedNodesKeys[i]; | ||
const collabNode = collabNodeMap.get(nodeKey); | ||
if (collabNode instanceof CollabTextNode) { | ||
@@ -1888,12 +1624,8 @@ if (lexical.$isTextNode(lexicalNode)) { | ||
const offset = collabNode.getOffset(); | ||
if (offset === -1) { | ||
continue; | ||
} | ||
const parent = collabNode._parent; | ||
collabNode._normalized = true; | ||
parent._xmlText.delete(offset, 1); | ||
collabNodeMap.delete(nodeKey); | ||
@@ -1906,6 +1638,4 @@ const parentChildren = parent._children; | ||
} | ||
for (let i = 0; i < mergedNodes.length; i++) { | ||
const [collabNode, text] = mergedNodes[i]; | ||
if (collabNode instanceof CollabTextNode && typeof text === 'string') { | ||
@@ -1916,3 +1646,2 @@ collabNode._text = text; | ||
} | ||
function syncLexicalUpdateToYjs(binding, provider, prevEditorState, currEditorState, dirtyElements, dirtyLeaves, normalizedNodes, tags) { | ||
@@ -1932,6 +1661,4 @@ syncWithTransaction(binding, () => { | ||
} | ||
return; | ||
} | ||
if (dirtyElements.has('root')) { | ||
@@ -1944,3 +1671,2 @@ const prevNodeMap = prevEditorState._nodeMap; | ||
} | ||
const selection = lexical.$getSelection(); | ||
@@ -1975,3 +1701,2 @@ const prevSelection = prevEditorState._selection; | ||
let localState = awareness.getLocalState(); | ||
if (localState === null) { | ||
@@ -1986,3 +1711,2 @@ localState = { | ||
} | ||
localState.focusing = focusing; | ||
@@ -1989,0 +1713,0 @@ awareness.setLocalState(localState); |
@@ -14,9 +14,9 @@ { | ||
"license": "MIT", | ||
"version": "0.7.8", | ||
"version": "0.7.9", | ||
"main": "LexicalYjs.js", | ||
"dependencies": { | ||
"@lexical/offset": "0.7.8" | ||
"@lexical/offset": "0.7.9" | ||
}, | ||
"peerDependencies": { | ||
"lexical": "0.7.8", | ||
"lexical": "0.7.9", | ||
"yjs": ">=13.5.22" | ||
@@ -23,0 +23,0 @@ }, |
1966
99397
+ Added@lexical/offset@0.7.9(transitive)
+ Addedlexical@0.7.9(transitive)
- Removed@lexical/offset@0.7.8(transitive)
- Removedlexical@0.7.8(transitive)
Updated@lexical/offset@0.7.9