@lobehub/editor 4.29.0 → 4.29.2

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.
Files changed (48) hide show
  1. package/README.md +86 -0
  2. package/es/editor-kernel/index.d.ts +2 -2
  3. package/es/editor-kernel/index.js +2 -2
  4. package/es/headless/index.d.ts +5 -2
  5. package/es/headless/index.js +20 -22
  6. package/es/headless.d.ts +6 -507
  7. package/es/headless.js +906 -411
  8. package/es/i-node-identity-service-BBC_bg97.d.ts +522 -0
  9. package/es/index.d.ts +4 -2
  10. package/es/index.js +5 -2
  11. package/es/plugins/block/react/ReactBlockPlugin.js +0 -3
  12. package/es/plugins/common/data-source/json-data-source.js +33 -25
  13. package/es/plugins/common/data-source/text-data-source.js +6 -1
  14. package/es/plugins/common/index.d.ts +3 -1
  15. package/es/plugins/common/node/node-id.d.ts +7 -0
  16. package/es/plugins/common/node/node-id.js +484 -0
  17. package/es/plugins/common/node/node-identity-policy.d.ts +26 -0
  18. package/es/plugins/common/node/node-identity-policy.js +68 -0
  19. package/es/plugins/common/plugin/index.js +28 -2
  20. package/es/plugins/common/service/i-node-identity-service.d.ts +19 -0
  21. package/es/plugins/common/service/i-node-identity-service.js +4 -0
  22. package/es/plugins/common/service/node-identity-service.js +87 -0
  23. package/es/plugins/common/utils/index.js +3 -1
  24. package/es/plugins/list/plugin/index.js +7 -1
  25. package/es/plugins/litexml/command/index.d.ts +2 -2
  26. package/es/plugins/litexml/command/index.js +464 -292
  27. package/es/plugins/litexml/command/symbols.d.ts +18 -7
  28. package/es/plugins/litexml/command/symbols.js +2 -1
  29. package/es/plugins/litexml/data-source/litexml-data-source.js +35 -32
  30. package/es/plugins/litexml/diff-validation.js +68 -14
  31. package/es/plugins/litexml/index.d.ts +2 -2
  32. package/es/plugins/litexml/node/identity-policy.js +91 -0
  33. package/es/plugins/litexml/plugin/index.js +6 -0
  34. package/es/plugins/litexml/table-cell-diff.js +4 -0
  35. package/es/plugins/litexml/table-row-diff.js +4 -0
  36. package/es/plugins/litexml/utils/index.js +10 -23
  37. package/es/plugins/markdown/command/index.js +77 -4
  38. package/es/plugins/markdown/data-source/markdown-data-source.js +6 -1
  39. package/es/plugins/math/plugin/index.js +2 -2
  40. package/es/plugins/slash/service/i-slash-service.d.ts +2 -2
  41. package/es/plugins/yjs/plugin/index.js +5 -3
  42. package/es/plugins/yjs/plugin/utils/editor-state.js +8 -1
  43. package/es/plugins/yjs/plugin/utils/node-identity.js +106 -0
  44. package/es/plugins/yjs/service/index.js +38 -4
  45. package/es/{symbols-Do7jD4Tt.d.ts → symbols-Dyr9yyS0.d.ts} +18 -7
  46. package/package.json +4 -4
  47. package/scripts/postinstall-lexical-patch.cjs +0 -21
  48. package/patches/lexical@0.42.0.patch +0 -82
package/es/headless.js CHANGED
@@ -1,10 +1,13 @@
1
- import { LITEXML_APPLY_COMMAND, LITEXML_DIFFNODE_ALL_COMMAND, LITEXML_DIFFNODE_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_REMOVE_COMMAND } from "./plugins/litexml/command/symbols.js";
1
+ import { getNodeIdentityPolicyRevision, registerNodeIdentityPolicy, subscribeNodeIdentityPolicy } from "./plugins/common/node/node-identity-policy.js";
2
+ import { $clearNodeId, $copyNodeProperties, $ensureUniqueNodeIds, $findNodeById, $getActiveNodeIdKeys, $getNodeById, $getNodeId, $normalizeNodeIds, $setNodeId, isValidContentNodeId, migrateSerializedNodeIds } from "./plugins/common/node/node-id.js";
3
+ import { INodeIdentityService } from "./plugins/common/service/i-node-identity-service.js";
4
+ import { LITEXML_APPLY_COMMAND, LITEXML_DIFFNODE_ALL_COMMAND, LITEXML_DIFFNODE_COMMAND, LITEXML_INSERT_COMMAND, LITEXML_MODIFY_COMMAND, LITEXML_MODIFY_WITH_RESULTS_COMMAND, LITEXML_REMOVE_COMMAND } from "./plugins/litexml/command/symbols.js";
2
5
  import { createHeadlessEditor as createHeadlessEditor$1 } from "@lexical/headless";
3
6
  import { createEmptyHistoryState, registerHistory } from "@lexical/history";
4
7
  import { $computeTableMapSkipCellCheck, $createTableCellNode, $createTableNodeWithDimensions, $createTableRowNode, $createTableSelection, $deleteTableColumnAtSelection, $deleteTableRowAtSelection, $findTableNode, $insertTableColumnAtSelection, $insertTableRowAtSelection, $isSimpleTable, $isTableCellNode, $isTableNode, $isTableRowNode, $isTableSelection, TableCellNode, TableNode, TableNode as TableNode$1, TableRowNode, registerTableCellUnmergeTransform, registerTablePlugin, registerTableSelectionObserver, setScrollableTablesActive } from "@lexical/table";
5
8
  import { get, merge, template, templateSettings } from "es-toolkit/compat";
6
9
  import EventEmitter from "eventemitter3";
7
- import { $applyNodeReplacement, $caretFromPoint, $createLineBreakNode, $createNodeSelection, $createParagraphNode, $createRangeSelection, $createTextNode, $getCaretRange, $getCharacterOffsets, $getChildCaret, $getNearestNodeFromDOMNode, $getNodeByKey, $getPreviousSelection, $getRoot, $getSelection, $insertNodes, $isBlockElementNode, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isNodeSelection, $isRangeSelection, $isRootNode, $isRootOrShadowRoot, $isTextNode, $isTextPointCaret, $nodesOfType, $normalizeSelection__EXPERIMENTAL, $parseSerializedNode, $setSelection, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, CONTROLLED_TEXT_INSERTION_COMMAND, COPY_COMMAND, DecoratorNode, ElementNode, FORMAT_TEXT_COMMAND, HISTORIC_TAG, HISTORY_MERGE_TAG, HISTORY_PUSH_TAG, INDENT_CONTENT_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, INSERT_TAB_COMMAND, IS_BOLD, IS_CODE, IS_ITALIC, IS_STRIKETHROUGH, IS_SUBSCRIPT, IS_SUPERSCRIPT, IS_UNDERLINE, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND, KEY_ENTER_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, PASTE_COMMAND, ParagraphNode, REDO_COMMAND, SELECTION_CHANGE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, SELECT_ALL_COMMAND, TEXT_TYPE_TO_FORMAT, TextNode, UNDO_COMMAND, createCommand, createEditor, getNearestEditorFromDOMNode, isHTMLElement, isModifierMatch, resetRandomKey, setDOMUnmanaged } from "lexical";
10
+ import { $applyNodeReplacement, $caretFromPoint, $createLineBreakNode, $createNodeSelection, $createParagraphNode, $createRangeSelection, $createTextNode, $getCaretRange, $getCharacterOffsets, $getChildCaret, $getNearestNodeFromDOMNode, $getNodeByKey, $getPreviousSelection, $getRoot, $getSelection, $insertNodes, $isBlockElementNode, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isNodeSelection, $isRangeSelection, $isRootNode, $isRootOrShadowRoot, $isTextNode, $isTextPointCaret, $nodesOfType, $normalizeSelection__EXPERIMENTAL, $parseSerializedNode, $setSelection, COLLABORATION_TAG, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, CONTROLLED_TEXT_INSERTION_COMMAND, COPY_COMMAND, DecoratorNode, ElementNode, FORMAT_TEXT_COMMAND, HISTORIC_TAG, HISTORY_MERGE_TAG, HISTORY_PUSH_TAG, INDENT_CONTENT_COMMAND, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, INSERT_TAB_COMMAND, IS_BOLD, IS_CODE, IS_ITALIC, IS_STRIKETHROUGH, IS_SUBSCRIPT, IS_SUPERSCRIPT, IS_UNDERLINE, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, KEY_DOWN_COMMAND, KEY_ENTER_COMMAND, KEY_TAB_COMMAND, OUTDENT_CONTENT_COMMAND, PASTE_COMMAND, ParagraphNode, REDO_COMMAND, RootNode, SELECTION_CHANGE_COMMAND, SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, SELECT_ALL_COMMAND, TEXT_TYPE_TO_FORMAT, TextNode, UNDO_COMMAND, createCommand, createEditor, getNearestEditorFromDOMNode, isHTMLElement, isModifierMatch, setDOMUnmanaged } from "lexical";
8
11
  import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode, registerRichText } from "@lexical/rich-text";
9
12
  import createDebug from "debug";
10
13
  import { $filter, $findMatchingParent, $getNearestBlockElementAncestorOrThrow, $getNearestNodeOfType, $insertNodeToNearestRoot, $wrapNodeInElement, CAN_USE_DOM, IS_APPLE as isApple, addClassNamesToElement, calculateZoomLevel, isHTMLAnchorElement, isHTMLElement as isHTMLElement$1, mergeRegister } from "@lexical/utils";
@@ -250,7 +253,7 @@ function $isCursorInQuote(selection) {
250
253
  function exportNodeToJSON$1(node) {
251
254
  const serializedNode = node.exportJSON();
252
255
  const nodeClass = node.constructor;
253
- serializedNode.id = node.getKey();
256
+ serializedNode.id = $getNodeId(node);
254
257
  if (serializedNode.type !== nodeClass.getType()) throw new Error(`LexicalNode: Node ${nodeClass.name} does not match the serialized type. Check if .exportJSON() is implemented and it is returning the correct type.`);
255
258
  if ($isElementNode(node)) {
256
259
  const serializedChildren = serializedNode.children;
@@ -593,7 +596,7 @@ const isHotkeyMatch = (event, hotkey) => {
593
596
  };
594
597
  //#endregion
595
598
  //#region src/utils/hotkey/registerHotkey.ts
596
- const logger$9 = createDebugLogger("hotkey");
599
+ const logger$10 = createDebugLogger("hotkey");
597
600
  const registerHotkey = (hotkey, callback, options = {}) => {
598
601
  return (e) => {
599
602
  if (!isHotkeyMatch(e, hotkey.keys)) return false;
@@ -605,7 +608,7 @@ const registerHotkey = (hotkey, callback, options = {}) => {
605
608
  ...keys,
606
609
  scopes: hotkey.scopes
607
610
  });
608
- logger$9.debug(`⌨️ Hotkey matched: ${hotkey.id} [${hotkey.keys}]`, hotkey);
611
+ logger$10.debug(`⌨️ Hotkey matched: ${hotkey.id} [${hotkey.keys}]`, hotkey);
609
612
  return true;
610
613
  };
611
614
  };
@@ -1492,7 +1495,7 @@ function registerCommands(editor) {
1492
1495
  }
1493
1496
  //#endregion
1494
1497
  //#region src/plugins/common/node/cursor.ts
1495
- const logger$8 = createDebugLogger("common", "cursor");
1498
+ const logger$9 = createDebugLogger("common", "cursor");
1496
1499
  var CardLikeElementNode = class extends ElementNode {
1497
1500
  isCardLike() {
1498
1501
  return true;
@@ -1531,7 +1534,7 @@ function registerCursorNode(editor) {
1531
1534
  const needAddCursor = [];
1532
1535
  for (const [kClass, nodeMaps] of mutatedNodes) if (DecoratorNode.prototype.isPrototypeOf(kClass.prototype)) for (const [key, mutation] of nodeMaps) {
1533
1536
  const node = $getNodeByKey(key);
1534
- logger$8.debug("🎭 DecoratorNode mutated:", node?.getType(), mutation, node);
1537
+ logger$9.debug("🎭 DecoratorNode mutated:", node?.getType(), mutation, node);
1535
1538
  if (mutation === "created" && node?.isInline() && node.getNextSibling() === null) needAddCursor.push(node);
1536
1539
  }
1537
1540
  if (needAddCursor.length > 0) editor.update(() => {
@@ -1633,7 +1636,7 @@ function registerCursorNode(editor) {
1633
1636
  $setSelection(sel);
1634
1637
  return true;
1635
1638
  } catch (error) {
1636
- logger$8.error("❌ Cursor selection error:", error);
1639
+ logger$9.error("❌ Cursor selection error:", error);
1637
1640
  }
1638
1641
  else if ($isCursorNode(focusNode)) try {
1639
1642
  const { key: anchorKey, offset: anchorOffset, type: anchorType } = selection.anchor;
@@ -1644,7 +1647,7 @@ function registerCursorNode(editor) {
1644
1647
  $setSelection(sel);
1645
1648
  return true;
1646
1649
  } catch (error) {
1647
- logger$8.error("❌ Cursor navigation error:", error);
1650
+ logger$9.error("❌ Cursor navigation error:", error);
1648
1651
  }
1649
1652
  return false;
1650
1653
  }, COMMAND_PRIORITY_HIGH), editor.registerCommand(KEY_ARROW_RIGHT_COMMAND, (event) => {
@@ -1675,6 +1678,174 @@ function registerCursorNode(editor) {
1675
1678
  }, COMMAND_PRIORITY_HIGH));
1676
1679
  }
1677
1680
  //#endregion
1681
+ //#region src/plugins/common/service/node-identity-service.ts
1682
+ const logger$8 = createDebugLogger("service", "node-identity");
1683
+ /** Implementation owned by CommonPlugin; external callers use INodeIdentityService. */
1684
+ var NodeIdentityService = class {
1685
+ constructor() {
1686
+ this.editor = null;
1687
+ this.indexes = /* @__PURE__ */ new WeakMap();
1688
+ this.listeners = /* @__PURE__ */ new Set();
1689
+ this.bindingToken = null;
1690
+ this.unregisterBinding = null;
1691
+ }
1692
+ bindEditor(editor) {
1693
+ this.unregisterBinding?.();
1694
+ const bindingToken = {};
1695
+ this.bindingToken = bindingToken;
1696
+ this.indexes = /* @__PURE__ */ new WeakMap();
1697
+ this.editor = editor;
1698
+ const unregisterUpdate = editor.registerUpdateListener(({ editorState, prevEditorState }) => {
1699
+ if (editorState !== prevEditorState) this.notify();
1700
+ });
1701
+ const unregisterPolicy = subscribeNodeIdentityPolicy(editor, () => this.notify());
1702
+ let active = true;
1703
+ const unregister = () => {
1704
+ if (!active) return;
1705
+ active = false;
1706
+ unregisterUpdate();
1707
+ unregisterPolicy();
1708
+ };
1709
+ this.unregisterBinding = unregister;
1710
+ return () => {
1711
+ unregister();
1712
+ if (this.bindingToken !== bindingToken) return;
1713
+ this.bindingToken = null;
1714
+ this.unregisterBinding = null;
1715
+ this.editor = null;
1716
+ this.indexes = /* @__PURE__ */ new WeakMap();
1717
+ this.listeners.clear();
1718
+ };
1719
+ }
1720
+ getNodeById(nodeId) {
1721
+ if (typeof nodeId !== "string" || !nodeId.trim()) return null;
1722
+ const editor = this.editor;
1723
+ if (!editor) return null;
1724
+ const editorState = editor.getEditorState();
1725
+ const revision = getNodeIdentityPolicyRevision(editor);
1726
+ let index = this.indexes.get(editorState);
1727
+ if (!index || index.revision !== revision) {
1728
+ index = {
1729
+ keys: editorState.read(() => $getActiveNodeIdKeys(), { editor }),
1730
+ revision
1731
+ };
1732
+ this.indexes.set(editorState, index);
1733
+ }
1734
+ const key = index.keys.get(nodeId);
1735
+ if (!key) return null;
1736
+ return editorState.read(() => {
1737
+ const node = $getNodeByKey(key);
1738
+ const id = node && $getNodeId(node);
1739
+ return node && id === nodeId ? Object.freeze({
1740
+ id,
1741
+ textContent: node.getTextContent(),
1742
+ type: node.getType()
1743
+ }) : null;
1744
+ }, { editor });
1745
+ }
1746
+ subscribe(listener) {
1747
+ this.listeners.add(listener);
1748
+ return () => this.listeners.delete(listener);
1749
+ }
1750
+ notify() {
1751
+ const listeners = Array.from(this.listeners);
1752
+ for (const listener of listeners) try {
1753
+ listener();
1754
+ } catch (error) {
1755
+ logger$8.error("Node identity subscriber failed:", error);
1756
+ }
1757
+ }
1758
+ };
1759
+ //#endregion
1760
+ //#region src/plugins/litexml/node/identity-policy.ts
1761
+ const afterRepresentation = Object.freeze({ childIndex: 1 });
1762
+ /** Shared active-view rule for live Lexical nodes and serialized preflight trees. */
1763
+ function getLiteXmlIdentityProjection(node) {
1764
+ if (node.type === "root" || node.type === "diff-content") return "container";
1765
+ if (node.type === "diff") {
1766
+ if (node.diffType === "remove" || node.diffType === "listItemRemove") return "hidden";
1767
+ if (node.diffType === "modify" || node.diffType === "listItemModify") return afterRepresentation;
1768
+ return "container";
1769
+ }
1770
+ if ((node.type === "table-cell-diff" || node.type === "table-row-diff") && node.diffType === "remove") return "hidden";
1771
+ if (node.type === "listitem" && node.firstChildType === "diff" && node.firstChildDiffType === "listItemRemove") return "hidden";
1772
+ return "content";
1773
+ }
1774
+ /** Review containers and the document root are physical structure, not addressable content. */
1775
+ function isLiteXmlIdentityNode(type) {
1776
+ return type !== "root" && type !== "diff" && type !== "diff-content";
1777
+ }
1778
+ function getDiffType(node) {
1779
+ if (!node) return void 0;
1780
+ if ("diffType" in node && typeof node.diffType === "string") return node.diffType;
1781
+ if ("getDiffType" in node && typeof node.getDiffType === "function") return node.getDiffType();
1782
+ }
1783
+ function getLiveProjection(node) {
1784
+ const type = node.getType();
1785
+ if (type === "root" || type === "diff-content") return "container";
1786
+ if (type !== "diff" && type !== "table-cell-diff" && type !== "table-row-diff" && type !== "listitem") return "content";
1787
+ const firstChild = $isElementNode(node) ? node.getFirstChild() : null;
1788
+ return getLiteXmlIdentityProjection({
1789
+ diffType: getDiffType(node),
1790
+ firstChildDiffType: getDiffType(firstChild),
1791
+ firstChildType: firstChild?.getType(),
1792
+ type
1793
+ });
1794
+ }
1795
+ function getTableReviewSide(node) {
1796
+ const type = node.getType();
1797
+ if (type !== "table-cell-diff" && type !== "table-row-diff") return null;
1798
+ if (!("getChangeId" in node) || typeof node.getChangeId !== "function") return null;
1799
+ const changeId = node.getChangeId();
1800
+ const side = getDiffType(node);
1801
+ return changeId && side ? {
1802
+ group: `${type}:${changeId}`,
1803
+ side
1804
+ } : null;
1805
+ }
1806
+ function getReviewSide(node) {
1807
+ const selfSide = getTableReviewSide(node);
1808
+ if (selfSide) return selfSide;
1809
+ let branch = node;
1810
+ for (let parent = node.getParent(); parent; branch = parent, parent = parent.getParent()) {
1811
+ const parentDiffType = getDiffType(parent);
1812
+ if (parent.getType() === "diff") {
1813
+ if (parentDiffType === "modify") {
1814
+ if (branch.getType() === "diff-content" && "side" in branch) return {
1815
+ group: `diff:${parent.getKey()}`,
1816
+ side: String(branch.side)
1817
+ };
1818
+ const branchIndex = parent.getChildren().indexOf(branch);
1819
+ if (branchIndex >= 0 && branchIndex < 2) return {
1820
+ group: `diff:${parent.getKey()}`,
1821
+ side: branchIndex === 0 ? "before" : "after"
1822
+ };
1823
+ }
1824
+ if (parentDiffType === "listItemModify") {
1825
+ const branchIndex = parent.getChildren().indexOf(branch);
1826
+ if (branchIndex >= 0 && branchIndex < 2) return {
1827
+ group: `diff:${parent.getKey()}`,
1828
+ side: branchIndex === 0 ? "before" : "after"
1829
+ };
1830
+ }
1831
+ }
1832
+ const tableSide = getTableReviewSide(parent);
1833
+ if (tableSide) return tableSide;
1834
+ }
1835
+ return null;
1836
+ }
1837
+ const liteXmlIdentityPolicy = {
1838
+ canShareId(left, right) {
1839
+ const leftSide = getReviewSide(left);
1840
+ const rightSide = getReviewSide(right);
1841
+ return Boolean(leftSide && rightSide && leftSide.group === rightSide.group && leftSide.side !== rightSide.side);
1842
+ },
1843
+ isIdentityNode(node) {
1844
+ return isLiteXmlIdentityNode(node.getType());
1845
+ },
1846
+ project: getLiveProjection
1847
+ };
1848
+ //#endregion
1678
1849
  //#region src/plugins/litexml/diff-validation.ts
1679
1850
  const ACTIONABLE_DIFF_TYPES = /* @__PURE__ */ new Set([
1680
1851
  "add",
@@ -1686,6 +1857,11 @@ const ACTIONABLE_DIFF_TYPES = /* @__PURE__ */ new Set([
1686
1857
  ]);
1687
1858
  const isActionableDiff = (node) => node?.type === "diff" && ACTIONABLE_DIFF_TYPES.has(node.diffType || "");
1688
1859
  const isElementNode = (node) => Array.isArray(node.children);
1860
+ const getNodeId = (node) => {
1861
+ if (typeof node.id === "string" && node.id.length > 0) return node.id;
1862
+ const properties = node.$?.properties;
1863
+ return typeof properties?.nodeId === "string" ? properties.nodeId : void 0;
1864
+ };
1689
1865
  const cloneNode = (node) => structuredClone(node);
1690
1866
  function hasActionableDiffDescendant(node) {
1691
1867
  return (node.children || []).some((child) => isActionableDiff(child) || hasActionableDiffDescendant(child));
@@ -1711,10 +1887,10 @@ function collectIllegalNestedDiffPaths(root) {
1711
1887
  }
1712
1888
  if (!isElementNode(node)) return;
1713
1889
  node.children?.forEach((child) => {
1714
- visit(child, [...ancestors, node], `${path}/${child.id || child.type || "node"}`);
1890
+ visit(child, [...ancestors, node], `${path}/${getNodeId(child) || child.type || "node"}`);
1715
1891
  });
1716
1892
  };
1717
- visit(root, [], root.id || root.type || "root");
1893
+ visit(root, [], getNodeId(root) || root.type || "root");
1718
1894
  return violations;
1719
1895
  }
1720
1896
  function findNewIllegalDiffPaths(previous, projected) {
@@ -1731,25 +1907,52 @@ function findNewIllegalDiffPaths(previous, projected) {
1731
1907
  });
1732
1908
  return newPaths;
1733
1909
  }
1734
- function findNodeLocation(root, id, ancestors = []) {
1735
- if (root.id === id) return {
1910
+ function findActiveNodeLocation(root, id, ancestors = []) {
1911
+ const projection = getLiteXmlIdentityProjection({
1912
+ diffType: root.diffType,
1913
+ firstChildDiffType: root.children?.[0]?.diffType,
1914
+ firstChildType: root.children?.[0]?.type,
1915
+ type: root.type
1916
+ });
1917
+ if (projection === "hidden") return null;
1918
+ if (projection === "content" && getNodeId(root) === id) {
1919
+ const parent = ancestors.at(-1) || null;
1920
+ return {
1921
+ ancestors,
1922
+ index: parent ? (parent.children || []).indexOf(root) : -1,
1923
+ node: root,
1924
+ parent
1925
+ };
1926
+ }
1927
+ const activeChildren = typeof projection === "object" ? root.children?.[projection.childIndex] ? [root.children[projection.childIndex]] : [] : root.children || [];
1928
+ for (const child of activeChildren) {
1929
+ const nested = findActiveNodeLocation(child, id, [...ancestors, root]);
1930
+ if (nested) return nested;
1931
+ }
1932
+ return null;
1933
+ }
1934
+ function findNodeLocationByNode(root, target, ancestors = []) {
1935
+ if (root === target) return {
1736
1936
  ancestors,
1737
1937
  index: -1,
1738
1938
  node: root,
1739
1939
  parent: null
1740
1940
  };
1741
1941
  for (const [index, child] of (root.children || []).entries()) {
1742
- if (child.id === id) return {
1942
+ if (child === target) return {
1743
1943
  ancestors: [...ancestors, root],
1744
1944
  index,
1745
1945
  node: child,
1746
1946
  parent: root
1747
1947
  };
1748
- const nested = findNodeLocation(child, id, [...ancestors, root]);
1948
+ const nested = findNodeLocationByNode(child, target, [...ancestors, root]);
1749
1949
  if (nested) return nested;
1750
1950
  }
1751
1951
  return null;
1752
1952
  }
1953
+ function hasActiveLiteXmlNodeId(root, id) {
1954
+ return findActiveNodeLocation(root, id) !== null;
1955
+ }
1753
1956
  function replaceNode(location, replacements) {
1754
1957
  if (!location.parent || location.index < 0) return;
1755
1958
  location.parent.children?.splice(location.index, 1, ...replacements);
@@ -1769,8 +1972,9 @@ function createDiffContent(side, children) {
1769
1972
  };
1770
1973
  }
1771
1974
  function projectModifyTarget(root, incoming) {
1772
- if (!incoming.id) return;
1773
- const location = findNodeLocation(root, incoming.id);
1975
+ const incomingId = getNodeId(incoming);
1976
+ if (!incomingId) return;
1977
+ const location = findActiveNodeLocation(root, incomingId);
1774
1978
  if (!location) return;
1775
1979
  const target = location.node;
1776
1980
  const next = cloneNode(incoming);
@@ -1804,10 +2008,30 @@ function projectModifyTarget(root, incoming) {
1804
2008
  replaceNode(location, [next]);
1805
2009
  return;
1806
2010
  }
2011
+ if (target.type === "listitem") {
2012
+ const existingDiff = (target.children || []).find((child) => child.type === "diff");
2013
+ if (existingDiff?.diffType === "listItemModify") {
2014
+ const after = existingDiff.children?.[1];
2015
+ if (after) after.children = cloneNode(next.children || []);
2016
+ return;
2017
+ }
2018
+ if (existingDiff?.diffType === "listItemAdd") {
2019
+ existingDiff.children = cloneNode(next.children || []);
2020
+ return;
2021
+ }
2022
+ target.children = [createDiff("listItemModify", [{
2023
+ children: cloneNode(target.children || []),
2024
+ type: "paragraph"
2025
+ }, {
2026
+ children: cloneNode(next.children || []),
2027
+ type: "paragraph"
2028
+ }])];
2029
+ return;
2030
+ }
1807
2031
  replaceNode(location, [createDiff("modify", [cloneNode(target), next])]);
1808
2032
  }
1809
2033
  function projectRemoveTarget(root, id) {
1810
- const location = findNodeLocation(root, id);
2034
+ const location = findActiveNodeLocation(root, id);
1811
2035
  if (!location) return;
1812
2036
  if (location.ancestors.some(isActionableDiff)) {
1813
2037
  replaceNode(location, []);
@@ -1834,13 +2058,13 @@ function projectRemoveTarget(root, id) {
1834
2058
  }
1835
2059
  function projectInsert(root, operation, readXml) {
1836
2060
  const referenceId = "beforeId" in operation ? operation.beforeId : operation.afterId;
1837
- const reference = referenceId === "root" ? null : findNodeLocation(root, referenceId)?.node || null;
2061
+ const reference = referenceId === "root" ? null : findActiveNodeLocation(root, referenceId)?.node || null;
1838
2062
  const location = referenceId === "root" ? {
1839
2063
  ancestors: [root],
1840
2064
  index: "beforeId" in operation ? 0 : (root.children || []).length - 1,
1841
2065
  node: root.children?.[0] || root,
1842
2066
  parent: root
1843
- } : findNodeLocation(root, referenceId);
2067
+ } : findActiveNodeLocation(root, referenceId);
1844
2068
  if (!location) return;
1845
2069
  const incoming = (readXml(operation.litexml).root?.children || []).map(cloneNode);
1846
2070
  if (incoming.length === 0 || !location.parent) return;
@@ -1879,12 +2103,12 @@ function projectInsert(root, operation, readXml) {
1879
2103
  ].includes(node.type || ""));
1880
2104
  if (!actionableAncestor && blockAncestor && hasActionableDiffDescendant(blockAncestor)) {
1881
2105
  const wrapped = createDiff("modify", [cloneNode(blockAncestor), cloneNode(blockAncestor)]);
1882
- const blockLocation = findNodeLocation(root, blockAncestor.id || "");
2106
+ const blockLocation = findNodeLocationByNode(root, blockAncestor);
1883
2107
  if (blockLocation) replaceNode(blockLocation, [wrapped]);
1884
2108
  return;
1885
2109
  }
1886
2110
  if (actionableAncestor && !isInlineReference) {
1887
- const originLocation = actionableAncestor.id ? findNodeLocation(root, actionableAncestor.id) : null;
2111
+ const originLocation = findNodeLocationByNode(root, actionableAncestor);
1888
2112
  if (originLocation?.parent && originLocation.index >= 0) {
1889
2113
  const originInsertAt = "beforeId" in operation ? originLocation.index : originLocation.index + 1;
1890
2114
  originLocation.parent.children?.splice(originInsertAt, 0, ...incoming.map((node) => createDiff("add", [node])));
@@ -2148,27 +2372,21 @@ function $isTableCellDiffNode(node) {
2148
2372
  }
2149
2373
  //#endregion
2150
2374
  //#region src/plugins/litexml/utils/index.ts
2151
- const getNumericId$1 = (id) => {
2152
- if (typeof id !== "number" && typeof id !== "string") return null;
2153
- const numericId = Number(id);
2154
- return Number.isInteger(numericId) && numericId >= 0 ? numericId : null;
2155
- };
2156
- function $parseSerializedNodeImpl(serializedNode, editor, keepId = false, state = null) {
2375
+ function $parseSerializedNodeImpl(serializedNode, editor) {
2157
2376
  const type = serializedNode.type;
2158
2377
  const registeredNode = editor._nodes.get(type);
2159
2378
  if (registeredNode === void 0) throw new Error(`parseEditorState: type "${type}" not found`);
2160
2379
  const nodeClass = registeredNode.klass;
2161
2380
  if (serializedNode.type !== nodeClass.getType()) throw new Error(`LexicalNode: Node ${nodeClass.name} does not implement .importJSON().`);
2162
- if (keepId) {
2163
- const id = getNumericId$1(serializedNode.id);
2164
- if (id !== null) resetRandomKey(id);
2165
- }
2166
2381
  const node = nodeClass.importJSON(serializedNode);
2382
+ const nodeId = isValidContentNodeId(serializedNode.id) ? serializedNode.id : $getNodeId(node);
2383
+ if (isValidContentNodeId(nodeId)) $setNodeId(node, nodeId);
2384
+ else if (nodeId) $clearNodeId(node);
2167
2385
  const children = serializedNode.children;
2168
2386
  if ($isElementNode(node) && Array.isArray(children)) {
2169
2387
  const childNodes = [];
2170
2388
  for (const serializedJSONChildNode of children) {
2171
- const childNode = $parseSerializedNodeImpl(serializedJSONChildNode, editor, keepId, state);
2389
+ const childNode = $parseSerializedNodeImpl(serializedJSONChildNode, editor);
2172
2390
  childNodes.push(childNode);
2173
2391
  }
2174
2392
  node.append(...childNodes);
@@ -2178,6 +2396,7 @@ function $parseSerializedNodeImpl(serializedNode, editor, keepId = false, state
2178
2396
  function exportNodeToJSON(node) {
2179
2397
  const serializedNode = node.exportJSON();
2180
2398
  const nodeClass = node.constructor;
2399
+ serializedNode.id = $getNodeId(node);
2181
2400
  if (serializedNode.type !== nodeClass.getType()) throw new Error(`LexicalNode: Node ${nodeClass.name} does not match the serialized type. Check if .exportJSON() is implemented and it is returning the correct type.`);
2182
2401
  if ($isElementNode(node)) {
2183
2402
  const serializedChildren = serializedNode.children;
@@ -2193,16 +2412,6 @@ function exportNodeToJSON(node) {
2193
2412
  function $cloneNode(node, editor) {
2194
2413
  return $parseSerializedNodeImpl(exportNodeToJSON(node), editor);
2195
2414
  }
2196
- const maxId = 1679616;
2197
- const startId = 1e6;
2198
- const step = 7211;
2199
- const modInverse = 1394051;
2200
- function idToChar(id) {
2201
- return ((Number(id) * step + startId) % maxId).toString(36).padStart(4, "0");
2202
- }
2203
- function charToId(char) {
2204
- return String((parseInt(char, 36) - startId + maxId) * modInverse % maxId);
2205
- }
2206
2415
  //#endregion
2207
2416
  //#region src/plugins/litexml/table-cell-diff.ts
2208
2417
  function copyCellStructure(source, target) {
@@ -2212,6 +2421,7 @@ function copyCellStructure(source, target) {
2212
2421
  }
2213
2422
  function $createTableCellDiffFromCell(editor, cell, diffType, changeId) {
2214
2423
  const diffCell = $createTableCellDiffNode(diffType, changeId, cell.getHeaderStyles(), cell.getColSpan(), cell.getWidth());
2424
+ $copyNodeProperties(cell, diffCell);
2215
2425
  copyCellStructure(cell, diffCell);
2216
2426
  const diff = $createDiffNode(diffType);
2217
2427
  diff.append(...cell.getChildren().map((child) => $cloneNode(child, editor)));
@@ -2220,6 +2430,7 @@ function $createTableCellDiffFromCell(editor, cell, diffType, changeId) {
2220
2430
  }
2221
2431
  function $createPlainTableCellFromDiff(editor, cell) {
2222
2432
  const plainCell = $createTableCellNode(cell.getHeaderStyles(), cell.getColSpan(), cell.getWidth());
2433
+ $copyNodeProperties(cell, plainCell);
2223
2434
  copyCellStructure(cell, plainCell);
2224
2435
  const diff = cell.getFirstChild();
2225
2436
  if (diff instanceof DiffNode) plainCell.append(...diff.getChildren().map((child) => $cloneNode(child, editor)));
@@ -2434,11 +2645,13 @@ function $areTableRowStructuresCompatible(previousRow, nextRow) {
2434
2645
  function $createTableRowDiffFromRow(editor, row, diffType, changeId) {
2435
2646
  const source = $cloneNode(row, editor);
2436
2647
  const diffRow = $createTableRowDiffNode(diffType, changeId, row.getHeight());
2648
+ $copyNodeProperties(row, diffRow);
2437
2649
  diffRow.append(...source.getChildren());
2438
2650
  return diffRow;
2439
2651
  }
2440
2652
  function $createPlainTableRowFromDiff(editor, row) {
2441
2653
  const plainRow = $createTableRowNode(row.getHeight());
2654
+ $copyNodeProperties(row, plainRow);
2442
2655
  plainRow.append(...row.getChildren().map((child) => $cloneNode(child, editor)));
2443
2656
  return plainRow;
2444
2657
  }
@@ -2506,9 +2719,9 @@ function toArrayXml(litexml) {
2506
2719
  function hasNewIllegalDiffs(previous, projected) {
2507
2720
  return findNewIllegalDiffPaths(previous, projected);
2508
2721
  }
2509
- function projectOperation(dataSource, document, operation) {
2722
+ function projectOperation(dataSource, document, operation, readXml = (xml) => dataSource.readLiteXMLToInode(xml)) {
2510
2723
  try {
2511
- const projected = projectLiteXmlOperation(document, operation, (xml) => dataSource.readLiteXMLToInode(xml));
2724
+ const projected = projectLiteXmlOperation(document, operation, readXml);
2512
2725
  const newIllegalDiffs = hasNewIllegalDiffs(document, projected);
2513
2726
  if (newIllegalDiffs.length > 0) {
2514
2727
  logger$7.warn("⚠️ Skipping operation with illegal nested diff", newIllegalDiffs);
@@ -2521,19 +2734,163 @@ function projectOperation(dataSource, document, operation) {
2521
2734
  }
2522
2735
  }
2523
2736
  function toProjectionOperation(operation) {
2524
- if (operation.action === "remove") return {
2525
- ...operation,
2526
- id: charToId(operation.id)
2527
- };
2528
- if (operation.action === "insert") return {
2529
- ...operation,
2530
- ..."beforeId" in operation ? { beforeId: operation.beforeId === "root" ? "root" : charToId(operation.beforeId) } : { afterId: operation.afterId === "root" ? "root" : charToId(operation.afterId) }
2531
- };
2532
2737
  return operation;
2533
2738
  }
2739
+ function getSerializedNodeId(node) {
2740
+ if (typeof node?.id === "string" && node.id.length > 0) return node.id;
2741
+ const nodeId = node?.$?.properties?.nodeId;
2742
+ return typeof nodeId === "string" && nodeId.length > 0 ? nodeId : void 0;
2743
+ }
2744
+ function getActiveSerializedDocument() {
2745
+ return { root: exportNodeToJSON$1($getRoot()) };
2746
+ }
2747
+ /** Resolve every target before staging any part of one modify operation. */
2748
+ function prepareModify(editor, dataSource, xmls) {
2749
+ const pairs = [];
2750
+ const parsed = /* @__PURE__ */ new Map();
2751
+ const targetIds = /* @__PURE__ */ new Set();
2752
+ for (const xml of xmls) {
2753
+ const inode = dataSource.readLiteXMLToInode(xml);
2754
+ parsed.set(xml, inode);
2755
+ const children = inode.root?.children;
2756
+ if (!children?.length) throw new Error("Modify operation contains no LiteXML nodes.");
2757
+ for (const child of children) {
2758
+ const nodeId = getSerializedNodeId(child);
2759
+ if (!nodeId) throw new Error("Modify operation root is missing its public node id.");
2760
+ if (targetIds.has(nodeId)) throw new Error(`Node id "${nodeId}" is targeted more than once.`);
2761
+ targetIds.add(nodeId);
2762
+ const oldNode = $findNodeById(nodeId);
2763
+ if (!oldNode) throw new Error(`Node id "${nodeId}" was not found.`);
2764
+ const newNode = $parseSerializedNodeImpl(child, editor);
2765
+ if (oldNode.isInline() !== newNode.isInline()) throw new Error(`Node id "${nodeId}" cannot change between inline and block structure.`);
2766
+ const parent = oldNode.getParent();
2767
+ if (($isListNode(parent) || $isListItemNode(oldNode) || $isListItemNode(newNode)) && (!$isListNode(parent) || !$isListItemNode(oldNode) || !$isListItemNode(newNode))) throw new Error(`Node id "${nodeId}" has an incompatible list-item structure.`);
2768
+ if ($isTableNode(parent) && (!$isTableRowNode(oldNode) || !$isTableRowNode(newNode)) || $isTableRowNode(parent) && (!$isTableCellNode(oldNode) || !$isTableCellNode(newNode)) || $isTableRowNode(newNode) && !$isTableNode(parent) || $isTableCellNode(newNode) && !$isTableRowNode(parent)) throw new Error(`Node id "${nodeId}" has an incompatible table structure.`);
2769
+ if (($isTableRowNode(oldNode) || $isTableRowNode(newNode)) && (!$isTableRowNode(oldNode) || !$isTableRowNode(newNode) || !$isTableNode(parent) || !$areTableRowStructuresCompatible(oldNode, newNode))) throw new Error(`Node id "${nodeId}" has an incompatible table row structure.`);
2770
+ pairs.push({
2771
+ newNode,
2772
+ oldNode
2773
+ });
2774
+ }
2775
+ }
2776
+ const targetKeys = new Set(pairs.map(({ oldNode }) => oldNode.getKey()));
2777
+ for (const { oldNode } of pairs) for (let parent = oldNode.getParent(); parent; parent = parent.getParent()) if (targetKeys.has(parent.getKey())) throw new Error("Modify operation targets overlapping ancestor and descendant nodes.");
2778
+ return {
2779
+ pairs,
2780
+ readXml: (xml) => parsed.get(xml)
2781
+ };
2782
+ }
2783
+ function getBatchTargetError(document, operation, dataSource) {
2784
+ if (operation.action === "remove") return hasActiveLiteXmlNodeId(document.root, operation.id) ? void 0 : `Node id "${operation.id}" was not found.`;
2785
+ if (operation.action === "insert") {
2786
+ try {
2787
+ if (!dataSource.readLiteXMLToInode(operation.litexml).root.children?.length) return "Insert operation contains no LiteXML nodes.";
2788
+ } catch (error) {
2789
+ return error instanceof Error ? error.message : String(error);
2790
+ }
2791
+ const anchorId = "beforeId" in operation ? operation.beforeId : operation.afterId;
2792
+ if (anchorId === "root") return (document.root.children?.length || 0) > 0 ? void 0 : "The document has no node to use as an insertion anchor.";
2793
+ return hasActiveLiteXmlNodeId(document.root, anchorId) ? void 0 : `Insertion anchor "${anchorId}" was not found.`;
2794
+ }
2795
+ }
2796
+ function applyLiteXMLBatch(editor, dataSource, operations) {
2797
+ let projectedDocument = getActiveSerializedDocument();
2798
+ const afterAnchors = /* @__PURE__ */ new Map();
2799
+ const results = [];
2800
+ operations.forEach((operation, index) => {
2801
+ const targetError = getBatchTargetError(projectedDocument, operation, dataSource);
2802
+ if (targetError) {
2803
+ results.push({
2804
+ action: operation.action,
2805
+ index,
2806
+ reason: targetError,
2807
+ status: "failed"
2808
+ });
2809
+ return;
2810
+ }
2811
+ let preparedModify;
2812
+ if (operation.action === "modify") try {
2813
+ preparedModify = prepareModify(editor, dataSource, toArrayXml(operation.litexml));
2814
+ } catch (error) {
2815
+ results.push({
2816
+ action: operation.action,
2817
+ index,
2818
+ reason: error instanceof Error ? error.message : String(error),
2819
+ status: "failed"
2820
+ });
2821
+ return;
2822
+ }
2823
+ if (!projectOperation(dataSource, projectedDocument, toProjectionOperation(operation), preparedModify?.readXml)) {
2824
+ results.push({
2825
+ action: operation.action,
2826
+ index,
2827
+ reason: "The operation would create an invalid nested review diff.",
2828
+ status: "failed"
2829
+ });
2830
+ return;
2831
+ }
2832
+ try {
2833
+ let applied = false;
2834
+ switch (operation.action) {
2835
+ case "modify":
2836
+ applied = applyPreparedModify(editor, preparedModify);
2837
+ break;
2838
+ case "remove":
2839
+ applied = handleRemove(editor, operation.id, true);
2840
+ break;
2841
+ case "insert": {
2842
+ const cursorIds = "afterId" in operation ? afterAnchors.get(operation.afterId) : void 0;
2843
+ let override;
2844
+ if (cursorIds) for (let cursorIndex = cursorIds.length - 1; cursorIndex >= 0; cursorIndex--) {
2845
+ const cursor = $findNodeById(cursorIds[cursorIndex]);
2846
+ if (cursor) {
2847
+ override = cursor;
2848
+ break;
2849
+ }
2850
+ }
2851
+ const insertedNodeId = handleInsert(editor, {
2852
+ ...operation,
2853
+ delay: true
2854
+ }, dataSource, override);
2855
+ applied = insertedNodeId !== null;
2856
+ if ("afterId" in operation && insertedNodeId) {
2857
+ const nextCursors = afterAnchors.get(operation.afterId) || [];
2858
+ nextCursors.push(insertedNodeId);
2859
+ afterAnchors.set(operation.afterId, nextCursors);
2860
+ }
2861
+ break;
2862
+ }
2863
+ }
2864
+ if (!applied) {
2865
+ results.push({
2866
+ action: operation.action,
2867
+ index,
2868
+ reason: "The editor could not apply the operation.",
2869
+ status: "failed"
2870
+ });
2871
+ return;
2872
+ }
2873
+ projectedDocument = getActiveSerializedDocument();
2874
+ results.push({
2875
+ action: operation.action,
2876
+ index,
2877
+ status: "applied"
2878
+ });
2879
+ } catch (error) {
2880
+ results.push({
2881
+ action: operation.action,
2882
+ index,
2883
+ reason: error instanceof Error ? error.message : String(error),
2884
+ status: "failed"
2885
+ });
2886
+ }
2887
+ });
2888
+ return results;
2889
+ }
2534
2890
  function tryParseChild(child, editor) {
2535
2891
  try {
2536
- const oldNode = $getNodeByKey(child.id);
2892
+ const nodeId = typeof child.id === "string" ? child.id : child.$?.properties?.nodeId;
2893
+ const oldNode = typeof nodeId === "string" ? $findNodeById(nodeId) : null;
2537
2894
  return {
2538
2895
  newNode: $parseSerializedNodeImpl(child, editor),
2539
2896
  oldNode
@@ -2550,14 +2907,14 @@ function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNode
2550
2907
  if ($isTableRowNode(oldNode) || $isTableRowNode(newNode)) {
2551
2908
  if (!$isTableRowNode(oldNode) || !$isTableRowNode(newNode) || !$isTableNode(oldNode.getParent()) || !$areTableRowStructuresCompatible(oldNode, newNode)) {
2552
2909
  logger$7.error(`❌ Invalid table row modification for row ${oldNode.getKey()}.`);
2553
- return;
2910
+ return false;
2554
2911
  }
2555
2912
  const changeId = `${oldNode.getKey()}:${newNode.getKey()}`;
2556
2913
  const removeRow = $createTableRowDiffFromRow(editor, oldNode, "remove", changeId);
2557
2914
  const addRow = $createTableRowDiffFromRow(editor, newNode, "add", changeId);
2558
2915
  oldNode.replace(removeRow, false);
2559
2916
  removeRow.insertAfter(addRow);
2560
- return;
2917
+ return true;
2561
2918
  }
2562
2919
  if ($isTableCellNode(oldNode) && $isTableCellNode(newNode)) {
2563
2920
  const existingDiff = oldNode.getChildren().find($isDiffNode);
@@ -2567,7 +2924,7 @@ function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNode
2567
2924
  newNode.getChildren().forEach((child) => {
2568
2925
  existingDiff.append($cloneNode(child, editor));
2569
2926
  });
2570
- return;
2927
+ return true;
2571
2928
  }
2572
2929
  const after = existingDiff.getChildren().find((child) => $isDiffContentNode(child) && child.side === "after");
2573
2930
  if (existingDiff.diffType === "modify" && after && $isDiffContentNode(after)) {
@@ -2575,7 +2932,7 @@ function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNode
2575
2932
  newNode.getChildren().forEach((child) => {
2576
2933
  after.append($cloneNode(child, editor));
2577
2934
  });
2578
- return;
2935
+ return true;
2579
2936
  }
2580
2937
  }
2581
2938
  const before = $createDiffContentNode("before");
@@ -2586,27 +2943,42 @@ function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNode
2586
2943
  diffNode.append(before, after);
2587
2944
  oldNode.clear();
2588
2945
  oldNode.append(diffNode);
2589
- return;
2946
+ return true;
2590
2947
  }
2591
2948
  const oldBlock = $closest(oldNode, (node) => node.isInline() === false);
2592
2949
  if (!oldBlock) throw new Error("Old block node not found for diffing.");
2593
2950
  if ($closest(oldNode, (node) => node.getType() === DiffNode.getType())) {
2594
2951
  oldNode.replace(newNode, false);
2595
- return;
2952
+ return true;
2596
2953
  }
2597
2954
  if ($isListItemNode(oldNode) && $isListItemNode(newNode)) {
2955
+ const existingListDiff = oldNode.getChildren().find($isDiffNode);
2956
+ if (existingListDiff?.diffType === "listItemModify") {
2957
+ const after = existingListDiff.getChildAtIndex(1);
2958
+ if ($isElementNode(after)) {
2959
+ after.clear();
2960
+ newNode.getChildren().forEach((child) => after.append($cloneNode(child, editor)));
2961
+ return true;
2962
+ }
2963
+ }
2964
+ if (existingListDiff?.diffType === "listItemAdd") {
2965
+ existingListDiff.clear();
2966
+ newNode.getChildren().forEach((child) => existingListDiff.append($cloneNode(child, editor)));
2967
+ return true;
2968
+ }
2598
2969
  const before = $createParagraphNode();
2599
2970
  const after = $createParagraphNode();
2600
2971
  oldNode.getChildren().forEach((child) => before.append(child));
2601
2972
  newNode.getChildren().forEach((child) => after.append(child));
2602
2973
  oldNode.clear();
2603
2974
  oldNode.append($createDiffNode("listItemModify").append(before, after));
2604
- return;
2975
+ return true;
2605
2976
  }
2606
2977
  if (oldNode === oldBlock) {
2607
2978
  const diffNode = $createDiffNode("modify");
2608
2979
  oldNode.replace(diffNode, false);
2609
2980
  diffNode.append($createDiffContentNode("before").append(oldBlock), $createDiffContentNode("after").append(newNode));
2981
+ return true;
2610
2982
  } else {
2611
2983
  if (!modifyBlockNodes.has(oldBlock.getKey())) {
2612
2984
  modifyBlockNodes.add(oldBlock.getKey());
@@ -2615,6 +2987,7 @@ function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNode
2615
2987
  diffNodeMap.set(oldBlock.getKey(), diffNode);
2616
2988
  }
2617
2989
  oldNode.replace(newNode, false);
2990
+ return true;
2618
2991
  }
2619
2992
  }
2620
2993
  function finalizeModifyBlocks(modifyBlockNodes, diffNodeMap, editor) {
@@ -2635,6 +3008,16 @@ function finalizeModifyBlocks(modifyBlockNodes, diffNodeMap, editor) {
2635
3008
  newDiffNode.append(p);
2636
3009
  blockNode.append(newDiffNode);
2637
3010
  continue;
3011
+ } else if ($isTableCellNode(blockNode) && $isTableRowNode(blockNode.getParent()) && $isTableCellNode(diffNode.getFirstChild())) {
3012
+ const beforeCell = diffNode.getFirstChild();
3013
+ if (!$isTableCellNode(beforeCell)) continue;
3014
+ const before = $createDiffContentNode("before");
3015
+ const after = $createDiffContentNode("after");
3016
+ beforeCell.getChildren().forEach((child) => before.append(child));
3017
+ blockNode.getChildren().forEach((child) => after.append($cloneNode(child, editor)));
3018
+ blockNode.clear();
3019
+ blockNode.append($createDiffNode("modify").append(before, after));
3020
+ continue;
2638
3021
  } else {
2639
3022
  diffNode.append($cloneNode(blockNode, editor));
2640
3023
  blockNode.replace(diffNode, false);
@@ -2665,6 +3048,18 @@ function wrapBlockModify(oldBlock, editor, changeFn) {
2665
3048
  oldBlock.append(diffNode);
2666
3049
  return;
2667
3050
  }
3051
+ if ($isTableCellNode(oldBlock) && !$isTableCellDiffNode(oldBlock) && $isTableRowNode(oldBlock.getParent())) {
3052
+ const before = $createDiffContentNode("before");
3053
+ oldBlock.getChildren().forEach((child) => before.append($cloneNode(child, editor)));
3054
+ changeFn();
3055
+ const newBlock = $getNodeByKey(oldBlock.getKey());
3056
+ if (!$isTableCellNode(newBlock)) throw new Error("Updated table cell node not found for modify wrapper.");
3057
+ const after = $createDiffContentNode("after");
3058
+ newBlock.getChildren().forEach((child) => after.append($cloneNode(child, editor)));
3059
+ newBlock.clear();
3060
+ newBlock.append($createDiffNode("modify").append(before, after));
3061
+ return;
3062
+ }
2668
3063
  const diffNode = $createDiffNode("modify");
2669
3064
  diffNode.append($cloneNode(oldBlock, editor));
2670
3065
  changeFn();
@@ -2674,344 +3069,330 @@ function wrapBlockModify(oldBlock, editor, changeFn) {
2674
3069
  newBlock.replace(diffNode, false);
2675
3070
  }
2676
3071
  function registerLiteXMLCommand(editor, dataSource) {
2677
- return mergeRegister(editor.registerCommand(LITEXML_MODIFY_COMMAND, (payload) => {
2678
- const resultPayload = payload.reduce((acc, cur) => {
2679
- if (cur.action === "insert") acc.unshift(cur);
2680
- else acc.push(cur);
2681
- return acc;
2682
- }, []);
2683
- let projectedDocument = getKernelFromEditor(editor).getDocument("json");
2684
- const safePayload = resultPayload.filter((item) => {
2685
- const nextProjection = projectOperation(dataSource, projectedDocument, toProjectionOperation(item));
2686
- if (!nextProjection) return false;
2687
- projectedDocument = nextProjection;
2688
- return true;
2689
- });
2690
- try {
2691
- safePayload.forEach((item) => {
2692
- const { action } = item;
2693
- switch (action) {
2694
- case "modify": {
2695
- const { litexml } = item;
2696
- handleModify(editor, dataSource, toArrayXml(litexml), true);
2697
- break;
2698
- }
2699
- case "remove": {
2700
- const { id } = item;
2701
- handleRemove(editor, charToId(id), true);
2702
- break;
2703
- }
2704
- case "insert":
2705
- handleInsert(editor, {
2706
- ...item,
2707
- delay: true
2708
- }, dataSource);
2709
- break;
2710
- default: logger$7.warn(`⚠️ Unknown action type: ${action}`);
2711
- }
2712
- });
2713
- return false;
2714
- } catch (error) {
2715
- logger$7.error("❌ Error processing LITEXML_MODIFY_COMMAND:", error);
2716
- return false;
2717
- }
3072
+ return mergeRegister(editor.registerCommand(LITEXML_MODIFY_COMMAND, (operations) => {
3073
+ applyLiteXMLBatch(editor, dataSource, operations);
3074
+ return false;
3075
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_MODIFY_WITH_RESULTS_COMMAND, ({ operations, onResults }) => {
3076
+ onResults(applyLiteXMLBatch(editor, dataSource, operations));
3077
+ return true;
2718
3078
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_APPLY_COMMAND, (payload) => {
2719
3079
  const { litexml, delay } = payload;
2720
3080
  const arrayXml = toArrayXml(litexml);
2721
3081
  if (!delay) {
2722
- handleModify(editor, dataSource, arrayXml, delay);
3082
+ handleModify(editor, dataSource, arrayXml);
2723
3083
  return false;
2724
3084
  }
2725
3085
  const operation = {
2726
3086
  action: "modify",
2727
3087
  litexml
2728
3088
  };
2729
- if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleModify(editor, dataSource, arrayXml, delay);
3089
+ const document = getActiveSerializedDocument();
3090
+ try {
3091
+ const prepared = prepareModify(editor, dataSource, arrayXml);
3092
+ if (projectOperation(dataSource, document, toProjectionOperation(operation), prepared.readXml)) applyPreparedModify(editor, prepared);
3093
+ } catch (error) {
3094
+ logger$7.error("❌ Failed to apply LiteXML modification:", error);
3095
+ }
2730
3096
  return false;
2731
3097
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_REMOVE_COMMAND, (payload) => {
2732
3098
  const { id, delay } = payload;
2733
- const key = charToId(id);
2734
3099
  if (!delay) {
2735
- handleRemove(editor, key, delay);
3100
+ handleRemove(editor, id, delay);
2736
3101
  return false;
2737
3102
  }
2738
3103
  const operation = {
2739
3104
  action: "remove",
2740
3105
  id
2741
3106
  };
2742
- if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleRemove(editor, key, delay);
3107
+ if (projectOperation(dataSource, getActiveSerializedDocument(), toProjectionOperation(operation))) handleRemove(editor, id, delay);
2743
3108
  return false;
2744
3109
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_INSERT_COMMAND, (payload) => {
2745
3110
  if (!payload.delay) {
2746
3111
  handleInsert(editor, payload, dataSource);
2747
3112
  return false;
2748
3113
  }
2749
- if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation({
3114
+ if (projectOperation(dataSource, getActiveSerializedDocument(), toProjectionOperation({
2750
3115
  action: "insert",
2751
3116
  ...payload
2752
3117
  }))) handleInsert(editor, payload, dataSource);
2753
3118
  return false;
2754
3119
  }, COMMAND_PRIORITY_EDITOR));
2755
3120
  }
2756
- function handleModify(editor, dataSource, arrayXml, delay) {
2757
- if (delay) editor.update(() => {
2758
- const modifyBlockNodes = /* @__PURE__ */ new Set();
2759
- const diffNodeMap = /* @__PURE__ */ new Map();
2760
- arrayXml.forEach((xml) => {
2761
- dataSource.readLiteXMLToInode(xml).root.children.forEach((child) => {
2762
- try {
2763
- const { oldNode, newNode } = tryParseChild(child, editor);
2764
- if (oldNode && newNode) handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor);
2765
- else logger$7.warn(`⚠️ Node with key ${child.id} not found for diffing.`);
2766
- } catch (error) {
2767
- logger$7.error("❌ Error replacing node:", error);
2768
- }
2769
- });
2770
- });
2771
- finalizeModifyBlocks(modifyBlockNodes, diffNodeMap, editor);
2772
- });
2773
- else editor.update(() => {
2774
- arrayXml.forEach((xml) => {
2775
- const inode = dataSource.readLiteXMLToInode(xml);
2776
- let prevNode = null;
2777
- inode.root.children.forEach((child) => {
2778
- try {
2779
- const { oldNode, newNode } = tryParseChild(child, editor);
2780
- if (oldNode && newNode) prevNode = oldNode.replace(newNode, false);
2781
- else if (newNode) {
2782
- if (prevNode) {
2783
- if (!newNode.isInline()) {
2784
- const prevBlock = $closest(prevNode, (node) => node.isInline() === false);
2785
- if (prevBlock) prevNode = prevBlock.insertAfter(newNode);
2786
- else {
2787
- $insertNodes([newNode]);
2788
- prevNode = newNode;
2789
- }
2790
- } else prevNode = prevNode.insertAfter(newNode);
2791
- } else $insertNodes([newNode]);
3121
+ function applyPreparedModify(editor, prepared) {
3122
+ const modifyBlockNodes = /* @__PURE__ */ new Set();
3123
+ const diffNodeMap = /* @__PURE__ */ new Map();
3124
+ for (const { oldNode, newNode } of prepared.pairs) if (!handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor)) throw new Error(`Node id "${$getNodeId(oldNode)}" could not be modified.`);
3125
+ finalizeModifyBlocks(modifyBlockNodes, diffNodeMap, editor);
3126
+ return prepared.pairs.length > 0;
3127
+ }
3128
+ function handleModify(editor, dataSource, arrayXml) {
3129
+ let applied = false;
3130
+ arrayXml.forEach((xml) => {
3131
+ const inode = dataSource.readLiteXMLToInode(xml);
3132
+ let prevNode = null;
3133
+ inode.root.children.forEach((child) => {
3134
+ try {
3135
+ const { oldNode, newNode } = tryParseChild(child, editor);
3136
+ if (oldNode && newNode) {
3137
+ prevNode = oldNode.replace(newNode, false);
3138
+ applied = true;
3139
+ } else if (newNode) {
3140
+ if (prevNode) {
3141
+ if (!newNode.isInline()) {
3142
+ const prevBlock = $closest(prevNode, (node) => node.isInline() === false);
3143
+ if (prevBlock) prevNode = prevBlock.insertAfter(newNode);
3144
+ else {
3145
+ $insertNodes([newNode]);
3146
+ prevNode = newNode;
3147
+ }
3148
+ applied = true;
3149
+ } else {
3150
+ prevNode = prevNode.insertAfter(newNode);
3151
+ applied = true;
3152
+ }
3153
+ } else {
3154
+ $insertNodes([newNode]);
3155
+ applied = true;
2792
3156
  }
2793
- } catch (error) {
2794
- logger$7.error("❌ Error replacing node:", error);
2795
3157
  }
2796
- });
3158
+ } catch (error) {
3159
+ logger$7.error("❌ Error replacing node:", error);
3160
+ }
2797
3161
  });
2798
3162
  });
3163
+ return applied;
2799
3164
  }
2800
- function handleRemove(editor, key, delay) {
2801
- editor.update(() => {
2802
- const node = $getNodeByKey(key);
2803
- if (!node) return;
2804
- if (!delay) {
2805
- if ($isTableCellNode(node)) {
2806
- const table = $getTableForCell(node);
2807
- const columnIndex = $getTableCellColumnIndex(node);
2808
- const span = node.getColSpan();
2809
- node.remove();
2810
- if (table && columnIndex >= 0) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
2811
- return;
2812
- }
2813
- node.remove();
2814
- return;
2815
- }
3165
+ function handleRemove(editor, nodeId, delay) {
3166
+ let applied = false;
3167
+ const node = $findNodeById(nodeId);
3168
+ if (!node) return false;
3169
+ if (!delay) {
2816
3170
  if ($isTableCellNode(node)) {
2817
3171
  const table = $getTableForCell(node);
2818
3172
  const columnIndex = $getTableCellColumnIndex(node);
2819
- if (!table || columnIndex < 0) {
2820
- logger$7.error(`❌ Table cell ${node.getKey()} is not attached to a valid table row.`);
2821
- return;
2822
- }
2823
- const changeId = `${table.getKey()}:column:${columnIndex}`;
2824
- node.replace($createTableCellDiffFromCell(editor, node, "remove", changeId), false);
2825
- return;
3173
+ const span = node.getColSpan();
3174
+ node.remove();
3175
+ applied = true;
3176
+ if (table && columnIndex >= 0) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
3177
+ return true;
2826
3178
  }
2827
- if ($isTableRowNode(node) && $isTableNode(node.getParent())) {
2828
- node.replace($createTableRowDiffFromRow(editor, node, "remove"), false);
2829
- return;
3179
+ node.remove();
3180
+ applied = true;
3181
+ return true;
3182
+ }
3183
+ if ($isTableCellNode(node)) {
3184
+ const table = $getTableForCell(node);
3185
+ const columnIndex = $getTableCellColumnIndex(node);
3186
+ if (!table || columnIndex < 0) {
3187
+ logger$7.error(`❌ Table cell ${node.getKey()} is not attached to a valid table row.`);
3188
+ return false;
2830
3189
  }
2831
- if (node.isInline() === false) {
2832
- const originDiffNode = $closest(node, (node) => node.getType() === DiffNode.getType());
2833
- if (originDiffNode) {
2834
- switch (originDiffNode.diffType) {
2835
- case "add":
2836
- originDiffNode.remove();
2837
- return;
2838
- case "modify": {
2839
- const children = originDiffNode.getChildren();
2840
- const newDiff = $createDiffNode("remove");
2841
- const before = children[0];
2842
- newDiff.append(...$isDiffContentNode(before) ? before.getChildren() : [before]);
2843
- originDiffNode.replace(newDiff, false);
2844
- return;
2845
- }
2846
- case "listItemModify": {
2847
- const children = originDiffNode.getChildren();
2848
- originDiffNode.replace(children[0], false).selectEnd();
2849
- return;
2850
- }
3190
+ const changeId = `${table.getKey()}:column:${columnIndex}`;
3191
+ node.replace($createTableCellDiffFromCell(editor, node, "remove", changeId), false);
3192
+ applied = true;
3193
+ return true;
3194
+ }
3195
+ if ($isTableRowNode(node) && $isTableNode(node.getParent())) {
3196
+ node.replace($createTableRowDiffFromRow(editor, node, "remove"), false);
3197
+ applied = true;
3198
+ return true;
3199
+ }
3200
+ if (node.isInline() === false) {
3201
+ const originDiffNode = $closest(node, (node) => node.getType() === DiffNode.getType());
3202
+ if (originDiffNode) {
3203
+ switch (originDiffNode.diffType) {
3204
+ case "add":
3205
+ originDiffNode.remove();
3206
+ applied = true;
3207
+ return true;
3208
+ case "modify": {
3209
+ const children = originDiffNode.getChildren();
3210
+ const newDiff = $createDiffNode("remove");
3211
+ const before = children[0];
3212
+ newDiff.append(...$isDiffContentNode(before) ? before.getChildren() : [before]);
3213
+ originDiffNode.replace(newDiff, false);
3214
+ applied = true;
3215
+ return true;
3216
+ }
3217
+ case "listItemModify": {
3218
+ const children = originDiffNode.getChildren();
3219
+ originDiffNode.replace(children[0], false).selectEnd();
3220
+ applied = true;
3221
+ return true;
2851
3222
  }
2852
- return;
2853
- }
2854
- if ($isListItemNode(node)) {
2855
- const diffNode = $createDiffNode("listItemRemove");
2856
- node.getChildren().forEach((child) => {
2857
- diffNode.append($cloneNode(child, editor));
2858
- });
2859
- node.clear();
2860
- node.append(diffNode);
2861
- } else {
2862
- const diffNode = $createDiffNode("remove");
2863
- diffNode.append($cloneNode(node, editor));
2864
- node.replace(diffNode, false);
2865
- }
2866
- } else {
2867
- const oldBlock = $closest(node, (node) => node.isInline() === false);
2868
- if (!oldBlock) throw new Error("Old block node not found for removal.");
2869
- if ($closest(node, (node) => node.getType() === DiffNode.getType())) {
2870
- node.remove();
2871
- return;
2872
3223
  }
2873
- wrapBlockModify(oldBlock, editor, () => {
2874
- node.remove();
3224
+ return false;
3225
+ }
3226
+ if ($isListItemNode(node)) {
3227
+ const diffNode = $createDiffNode("listItemRemove");
3228
+ node.getChildren().forEach((child) => {
3229
+ diffNode.append($cloneNode(child, editor));
2875
3230
  });
3231
+ node.clear();
3232
+ node.append(diffNode);
3233
+ applied = true;
3234
+ } else {
3235
+ const diffNode = $createDiffNode("remove");
3236
+ diffNode.append($cloneNode(node, editor));
3237
+ node.replace(diffNode, false);
3238
+ applied = true;
2876
3239
  }
2877
- });
3240
+ } else {
3241
+ const oldBlock = $closest(node, (node) => node.isInline() === false);
3242
+ if (!oldBlock) throw new Error("Old block node not found for removal.");
3243
+ if ($closest(node, (node) => node.getType() === DiffNode.getType())) {
3244
+ node.remove();
3245
+ applied = true;
3246
+ return true;
3247
+ }
3248
+ wrapBlockModify(oldBlock, editor, () => {
3249
+ node.remove();
3250
+ });
3251
+ applied = true;
3252
+ }
3253
+ return applied;
2878
3254
  }
2879
- function handleInsert(editor, payload, dataSource) {
3255
+ function handleInsert(editor, payload, dataSource, referenceNodeOverride) {
2880
3256
  const { litexml, delay } = payload;
2881
3257
  const isBefore = "beforeId" in payload;
2882
3258
  const inode = dataSource.readLiteXMLToInode(litexml);
2883
- editor.update(() => {
2884
- try {
2885
- let referenceNode = null;
3259
+ let insertedNodeId = null;
3260
+ try {
3261
+ let referenceNode = referenceNodeOverride || null;
3262
+ if (!referenceNode) {
2886
3263
  if (isBefore) {
2887
3264
  if (payload.beforeId === "root") referenceNode = $getRoot().getFirstChild();
2888
- else referenceNode = $getNodeByKey(charToId(payload.beforeId));
3265
+ else referenceNode = $findNodeById(payload.beforeId);
2889
3266
  } else if (payload.afterId === "root") referenceNode = $getRoot().getLastChild();
2890
- else referenceNode = $getNodeByKey(charToId(payload.afterId));
2891
- if (!referenceNode) throw new Error("Reference node not found for insertion.");
2892
- const newNodes = inode.root.children.map((child) => $parseSerializedNodeImpl(child, editor));
2893
- const referencesTableCell = $isTableCellNode(referenceNode);
2894
- const insertsOnlyTableCells = newNodes.length > 0 && newNodes.every($isTableCellNode);
2895
- if (referencesTableCell || insertsOnlyTableCells) {
2896
- if (!referencesTableCell || !insertsOnlyTableCells) {
2897
- logger$7.error("❌ Table cells can only be inserted next to another cell in the same row.");
2898
- return;
2899
- }
2900
- const cellReference = referenceNode;
2901
- const table = $getTableForCell(cellReference);
2902
- const referenceIndex = $getTableCellColumnIndex(cellReference);
2903
- if (!table || referenceIndex < 0) {
2904
- logger$7.error("❌ Table cell insertion requires a valid table parent.");
2905
- return;
2906
- }
2907
- const rowWidthBefore = $getLogicalRowWidth(cellReference);
2908
- const insertionIndex = isBefore ? referenceIndex : referenceIndex + cellReference.getColSpan();
2909
- $updateTableWidthsForCellInsertion(table, rowWidthBefore, insertionIndex, newNodes.reduce((total, node) => total + ($isTableCellNode(node) ? node.getColSpan() : 0), 0));
2910
- let spanOffset = 0;
2911
- const cells = newNodes.map((node) => {
2912
- const result = delay ? $createTableCellDiffFromCell(editor, node, "add", `${table.getKey()}:column:${insertionIndex + spanOffset}`) : node;
2913
- spanOffset += node.getColSpan();
2914
- return result;
2915
- });
2916
- if (isBefore) cells.reverse().forEach((cell) => {
2917
- referenceNode = referenceNode.insertBefore(cell);
2918
- });
2919
- else cells.forEach((cell) => {
2920
- referenceNode = referenceNode.insertAfter(cell);
2921
- });
2922
- return;
3267
+ else referenceNode = $findNodeById(payload.afterId);
3268
+ }
3269
+ if (!referenceNode) throw new Error("Reference node not found for insertion.");
3270
+ const newNodes = inode.root.children.map((child) => $parseSerializedNodeImpl(child, editor));
3271
+ $ensureUniqueNodeIds(newNodes);
3272
+ insertedNodeId = newNodes.length ? $getNodeId(newNodes.at(-1)) || null : null;
3273
+ const referencesTableCell = $isTableCellNode(referenceNode);
3274
+ const insertsOnlyTableCells = newNodes.length > 0 && newNodes.every($isTableCellNode);
3275
+ if (referencesTableCell || insertsOnlyTableCells) {
3276
+ if (!referencesTableCell || !insertsOnlyTableCells) {
3277
+ logger$7.error("❌ Table cells can only be inserted next to another cell in the same row.");
3278
+ return null;
2923
3279
  }
2924
- if (!delay) {
2925
- if (isBefore) newNodes.reverse().forEach((node) => {
2926
- referenceNode = referenceNode.insertBefore(node);
2927
- });
2928
- else newNodes.forEach((node) => {
2929
- if (referenceNode) referenceNode = referenceNode.insertAfter(node);
2930
- });
2931
- return;
3280
+ const cellReference = referenceNode;
3281
+ const table = $getTableForCell(cellReference);
3282
+ const referenceIndex = $getTableCellColumnIndex(cellReference);
3283
+ if (!table || referenceIndex < 0) {
3284
+ logger$7.error("❌ Table cell insertion requires a valid table parent.");
3285
+ return null;
2932
3286
  }
2933
- const referencesTableRow = $isTableRowNode(referenceNode);
2934
- const insertsOnlyTableRows = newNodes.every($isTableRowNode);
2935
- if (referencesTableRow || insertsOnlyTableRows) {
2936
- if (!referencesTableRow || !insertsOnlyTableRows || !$isTableNode(referenceNode.getParent())) {
2937
- logger$7.error("❌ Table rows can only be inserted next to another row in the same table.");
2938
- return;
2939
- }
2940
- if (isBefore) newNodes.reverse().forEach((node) => {
2941
- if (!$isTableRowNode(node)) return;
2942
- const diffRow = $createTableRowDiffFromRow(editor, node, "add");
2943
- referenceNode = referenceNode.insertBefore(diffRow);
2944
- });
2945
- else newNodes.forEach((node) => {
2946
- if (!$isTableRowNode(node)) return;
2947
- const diffRow = $createTableRowDiffFromRow(editor, node, "add");
2948
- referenceNode = referenceNode.insertAfter(diffRow);
2949
- });
2950
- return;
3287
+ const rowWidthBefore = $getLogicalRowWidth(cellReference);
3288
+ const insertionIndex = isBefore ? referenceIndex : referenceIndex + cellReference.getColSpan();
3289
+ $updateTableWidthsForCellInsertion(table, rowWidthBefore, insertionIndex, newNodes.reduce((total, node) => total + ($isTableCellNode(node) ? node.getColSpan() : 0), 0));
3290
+ let spanOffset = 0;
3291
+ const cells = newNodes.map((node) => {
3292
+ const result = delay ? $createTableCellDiffFromCell(editor, node, "add", `${table.getKey()}:column:${insertionIndex + spanOffset}`) : node;
3293
+ spanOffset += node.getColSpan();
3294
+ return result;
3295
+ });
3296
+ if (isBefore) cells.reverse().forEach((cell) => {
3297
+ referenceNode = referenceNode.insertBefore(cell);
3298
+ });
3299
+ else cells.forEach((cell) => {
3300
+ referenceNode = referenceNode.insertAfter(cell);
3301
+ });
3302
+ return insertedNodeId;
3303
+ }
3304
+ if (!delay) {
3305
+ if (isBefore) newNodes.reverse().forEach((node) => {
3306
+ referenceNode = referenceNode.insertBefore(node);
3307
+ });
3308
+ else newNodes.forEach((node) => {
3309
+ if (referenceNode) referenceNode = referenceNode.insertAfter(node);
3310
+ });
3311
+ return insertedNodeId;
3312
+ }
3313
+ const referencesTableRow = $isTableRowNode(referenceNode);
3314
+ const insertsOnlyTableRows = newNodes.every($isTableRowNode);
3315
+ if (referencesTableRow || insertsOnlyTableRows) {
3316
+ if (!referencesTableRow || !insertsOnlyTableRows || !$isTableNode(referenceNode.getParent())) {
3317
+ logger$7.error("❌ Table rows can only be inserted next to another row in the same table.");
3318
+ return null;
2951
3319
  }
2952
- if (isBefore) {
2953
- if (referenceNode.isInline() === false) {
2954
- const originDiffNode = $closest(referenceNode, (node) => node.getType() === DiffNode.getType());
2955
- if (originDiffNode) referenceNode = originDiffNode;
2956
- newNodes.map((node) => {
2957
- if ($isListItemNode(node)) {
2958
- const diffNode = $createDiffNode("listItemAdd");
2959
- node.getChildren().forEach((child) => diffNode.append(child));
2960
- return node.append(diffNode);
2961
- }
2962
- const diffNode = $createDiffNode("add");
2963
- diffNode.append(node);
2964
- return diffNode;
2965
- }).reverse().forEach((diffNode) => {
2966
- if (referenceNode) referenceNode = referenceNode.insertBefore(diffNode);
2967
- });
2968
- } else {
2969
- const refBlock = $closest(referenceNode, (node) => node.isInline() === false);
2970
- if (!refBlock) throw new Error("Reference block node not found for insertion.");
2971
- if ($closest(referenceNode, (node) => node.getType() === DiffNode.getType())) newNodes.forEach((node) => {
2972
- if (referenceNode) referenceNode = referenceNode.insertBefore(node);
2973
- });
2974
- else wrapBlockModify(refBlock, editor, () => {
2975
- newNodes.forEach((node) => {
2976
- if (referenceNode) referenceNode = referenceNode.insertBefore(node);
2977
- });
2978
- });
2979
- }
2980
- } else if (referenceNode.isInline() === false) {
3320
+ if (isBefore) newNodes.reverse().forEach((node) => {
3321
+ if (!$isTableRowNode(node)) return;
3322
+ const diffRow = $createTableRowDiffFromRow(editor, node, "add");
3323
+ referenceNode = referenceNode.insertBefore(diffRow);
3324
+ });
3325
+ else newNodes.forEach((node) => {
3326
+ if (!$isTableRowNode(node)) return;
3327
+ const diffRow = $createTableRowDiffFromRow(editor, node, "add");
3328
+ referenceNode = referenceNode.insertAfter(diffRow);
3329
+ });
3330
+ return insertedNodeId;
3331
+ }
3332
+ if (isBefore) {
3333
+ if (referenceNode.isInline() === false) {
2981
3334
  const originDiffNode = $closest(referenceNode, (node) => node.getType() === DiffNode.getType());
2982
3335
  if (originDiffNode) referenceNode = originDiffNode;
2983
- newNodes.forEach((node) => {
2984
- if (referenceNode) {
2985
- if ($isListItemNode(node)) {
2986
- const diffNode = $createDiffNode("listItemAdd");
2987
- node.getChildren().forEach((child) => {
2988
- diffNode.append(child);
2989
- });
2990
- node.append(diffNode);
2991
- referenceNode = referenceNode.insertAfter(node);
2992
- } else {
2993
- const diffNode = $createDiffNode("add");
2994
- diffNode.append(node);
2995
- referenceNode = referenceNode.insertAfter(diffNode);
2996
- }
3336
+ newNodes.map((node) => {
3337
+ if ($isListItemNode(node)) {
3338
+ const diffNode = $createDiffNode("listItemAdd");
3339
+ node.getChildren().forEach((child) => diffNode.append(child));
3340
+ return node.append(diffNode);
2997
3341
  }
3342
+ const diffNode = $createDiffNode("add");
3343
+ diffNode.append(node);
3344
+ return diffNode;
3345
+ }).reverse().forEach((diffNode) => {
3346
+ if (referenceNode) referenceNode = referenceNode.insertBefore(diffNode);
2998
3347
  });
2999
3348
  } else {
3000
3349
  const refBlock = $closest(referenceNode, (node) => node.isInline() === false);
3001
3350
  if (!refBlock) throw new Error("Reference block node not found for insertion.");
3002
3351
  if ($closest(referenceNode, (node) => node.getType() === DiffNode.getType())) newNodes.forEach((node) => {
3003
- if (referenceNode) referenceNode = referenceNode.insertAfter(node);
3352
+ if (referenceNode) referenceNode = referenceNode.insertBefore(node);
3004
3353
  });
3005
3354
  else wrapBlockModify(refBlock, editor, () => {
3006
3355
  newNodes.forEach((node) => {
3007
- if (referenceNode) referenceNode = referenceNode.insertAfter(node);
3356
+ if (referenceNode) referenceNode = referenceNode.insertBefore(node);
3008
3357
  });
3009
3358
  });
3010
3359
  }
3011
- } catch (error) {
3012
- logger$7.error("❌ Error inserting node:", error);
3360
+ } else if (referenceNode.isInline() === false) {
3361
+ const originDiffNode = $closest(referenceNode, (node) => node.getType() === DiffNode.getType());
3362
+ if (originDiffNode) referenceNode = originDiffNode;
3363
+ newNodes.forEach((node) => {
3364
+ if (referenceNode) {
3365
+ if ($isListItemNode(node)) {
3366
+ const diffNode = $createDiffNode("listItemAdd");
3367
+ node.getChildren().forEach((child) => {
3368
+ diffNode.append(child);
3369
+ });
3370
+ node.append(diffNode);
3371
+ referenceNode = referenceNode.insertAfter(node);
3372
+ } else {
3373
+ const diffNode = $createDiffNode("add");
3374
+ diffNode.append(node);
3375
+ referenceNode = referenceNode.insertAfter(diffNode);
3376
+ }
3377
+ }
3378
+ });
3379
+ } else {
3380
+ const refBlock = $closest(referenceNode, (node) => node.isInline() === false);
3381
+ if (!refBlock) throw new Error("Reference block node not found for insertion.");
3382
+ if ($closest(referenceNode, (node) => node.getType() === DiffNode.getType())) newNodes.forEach((node) => {
3383
+ if (referenceNode) referenceNode = referenceNode.insertAfter(node);
3384
+ });
3385
+ else wrapBlockModify(refBlock, editor, () => {
3386
+ newNodes.forEach((node) => {
3387
+ if (referenceNode) referenceNode = referenceNode.insertAfter(node);
3388
+ });
3389
+ });
3013
3390
  }
3014
- });
3391
+ } catch (error) {
3392
+ logger$7.error("❌ Error inserting node:", error);
3393
+ insertedNodeId = null;
3394
+ }
3395
+ return insertedNodeId;
3015
3396
  }
3016
3397
  //#endregion
3017
3398
  //#region src/plugins/litexml/command/diffCommand.ts
@@ -3294,13 +3675,8 @@ var LitexmlDataSource = class extends DataSource {
3294
3675
  read(editor, data) {
3295
3676
  try {
3296
3677
  const inode = this.readLiteXMLToInode(data);
3297
- const newState = editor.parseEditorState({ root: INodeHelper.createRootNode() }, (state) => {
3298
- try {
3299
- const root = $parseSerializedNodeImpl(inode.root, editor, true, state);
3300
- state._nodeMap.set(root.getKey(), root);
3301
- } catch (error) {
3302
- console.error(error);
3303
- }
3678
+ const newState = editor.parseEditorState(inode, () => {
3679
+ $normalizeNodeIds($getRoot());
3304
3680
  });
3305
3681
  editor.setEditorState(newState);
3306
3682
  } catch (error) {
@@ -3366,6 +3742,22 @@ var LitexmlDataSource = class extends DataSource {
3366
3742
  processXMLElement(xmlElement, parentNode) {
3367
3743
  const tagName = xmlElement.tagName.toLowerCase();
3368
3744
  const customReaders = this.litexmlService.getXMLReaders();
3745
+ const publicId = xmlElement.getAttribute("id") || void 0;
3746
+ const attachPublicId = (node, id = publicId) => {
3747
+ if (!node || !id) return;
3748
+ node.id = id;
3749
+ node.$ = {
3750
+ ...node.$,
3751
+ properties: {
3752
+ ...node.$?.properties,
3753
+ nodeId: id
3754
+ }
3755
+ };
3756
+ };
3757
+ const append = (node) => {
3758
+ attachPublicId(node);
3759
+ INodeHelper.appendChild(parentNode, node);
3760
+ };
3369
3761
  if (customReaders[tagName]) {
3370
3762
  const readerOrReaders = customReaders[tagName];
3371
3763
  const readers = Array.isArray(readerOrReaders) ? readerOrReaders : [readerOrReaders];
@@ -3375,16 +3767,9 @@ var LitexmlDataSource = class extends DataSource {
3375
3767
  const result = reader(xmlElement, children);
3376
3768
  if (result !== false) {
3377
3769
  if (Array.isArray(result)) {
3378
- if (result.length > 0) {
3379
- const attrId = xmlElement.getAttribute("id");
3380
- result[0].id = attrId ? charToId(attrId) : void 0;
3381
- }
3770
+ if (result.length > 0) attachPublicId(result[0]);
3382
3771
  INodeHelper.appendChild(parentNode, ...result);
3383
- } else if (result) {
3384
- const attrId = xmlElement.getAttribute("id");
3385
- result.id = attrId ? charToId(attrId) : void 0;
3386
- INodeHelper.appendChild(parentNode, result);
3387
- }
3772
+ } else if (result) append(result);
3388
3773
  return;
3389
3774
  }
3390
3775
  }
@@ -3394,7 +3779,7 @@ var LitexmlDataSource = class extends DataSource {
3394
3779
  case "paragraph": {
3395
3780
  const paragraph = INodeHelper.createParagraph();
3396
3781
  this.processXMLChildren(xmlElement, paragraph);
3397
- INodeHelper.appendChild(parentNode, paragraph);
3782
+ append(paragraph);
3398
3783
  break;
3399
3784
  }
3400
3785
  case "h1":
@@ -3409,7 +3794,7 @@ var LitexmlDataSource = class extends DataSource {
3409
3794
  tag: `h${level}`
3410
3795
  });
3411
3796
  this.processXMLChildren(xmlElement, heading);
3412
- INodeHelper.appendChild(parentNode, heading);
3797
+ append(heading);
3413
3798
  break;
3414
3799
  }
3415
3800
  case "ul":
@@ -3421,6 +3806,7 @@ var LitexmlDataSource = class extends DataSource {
3421
3806
  children: [],
3422
3807
  value: 1
3423
3808
  });
3809
+ attachPublicId(listItem, child.getAttribute("id") || void 0);
3424
3810
  this.processXMLChildren(child, listItem);
3425
3811
  INodeHelper.appendChild(parentNode, listItem);
3426
3812
  });
@@ -3428,20 +3814,15 @@ var LitexmlDataSource = class extends DataSource {
3428
3814
  case "blockquote": {
3429
3815
  const quote = INodeHelper.createElementNode("quote", { children: [] });
3430
3816
  this.processXMLChildren(xmlElement, quote);
3431
- INodeHelper.appendChild(parentNode, quote);
3817
+ append(quote);
3432
3818
  break;
3433
3819
  }
3434
- case "code": {
3435
- const codeNode = INodeHelper.createElementNode("codeInline", { children: [INodeHelper.createTextNode(xmlElement.textContent || "")] });
3436
- INodeHelper.appendChild(parentNode, codeNode);
3820
+ case "code":
3821
+ append(INodeHelper.createElementNode("codeInline", { children: [INodeHelper.createTextNode(xmlElement.textContent || "")] }));
3437
3822
  break;
3438
- }
3439
3823
  case "text": {
3440
3824
  const textContent = xmlElement.textContent || "";
3441
- if (textContent) {
3442
- const textNode = INodeHelper.createTextNode(textContent);
3443
- INodeHelper.appendChild(parentNode, textNode);
3444
- }
3825
+ if (textContent) append(INodeHelper.createTextNode(textContent));
3445
3826
  break;
3446
3827
  }
3447
3828
  default: this.processXMLChildren(xmlElement, parentNode);
@@ -3492,10 +3873,12 @@ var LitexmlDataSource = class extends DataSource {
3492
3873
  lines.push(...handled.lines);
3493
3874
  return;
3494
3875
  }
3495
- const attrs = this.buildXMLAttributes({
3496
- id: idToChar(node.getKey()),
3876
+ const attributes = {
3877
+ id: $getNodeId(node),
3497
3878
  ...handled.attributes
3498
- });
3879
+ };
3880
+ attributes.id = $getNodeId(node);
3881
+ const attrs = this.buildXMLAttributes(attributes);
3499
3882
  const openTag = `${indentStr}<${handled.tagName}${attrs}>`;
3500
3883
  const closeTag = `</${handled.tagName}>`;
3501
3884
  if (handled.textContent) lines.push(`${openTag}${handled.textContent}${closeTag}`);
@@ -3527,7 +3910,7 @@ var LitexmlDataSource = class extends DataSource {
3527
3910
  * Escape XML special characters
3528
3911
  */
3529
3912
  escapeXML(text) {
3530
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
3913
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;").replaceAll(" ", "&#9;").replaceAll("\n", "&#10;").replaceAll("\r", "&#13;");
3531
3914
  }
3532
3915
  };
3533
3916
  //#endregion
@@ -3900,6 +4283,74 @@ const SPICAL_TEXT = "￰";
3900
4283
  const getLineNumber = (content, charIndex) => {
3901
4284
  return content.slice(0, Math.max(0, charIndex)).split("\n").length;
3902
4285
  };
4286
+ function getNodeReferenceForKey(editor, key) {
4287
+ return editor.getEditorState().read(() => {
4288
+ const node = $getNodeByKey(key);
4289
+ if (!node) return null;
4290
+ if ($isRootNode(node)) return { kind: "root" };
4291
+ const id = $getNodeId(node);
4292
+ return id ? {
4293
+ id,
4294
+ kind: "node"
4295
+ } : null;
4296
+ });
4297
+ }
4298
+ function getNodeKeyForReference(editor, reference, offset) {
4299
+ return editor.getEditorState().read(() => {
4300
+ if (reference.kind === "node") {
4301
+ const node = $findNodeById(reference.id);
4302
+ return node ? {
4303
+ key: node.getKey(),
4304
+ offset
4305
+ } : void 0;
4306
+ }
4307
+ const root = $getRoot();
4308
+ const findBoundaryText = (node, first) => {
4309
+ if ($isTextNode(node)) return node;
4310
+ if (!$isElementNode(node)) return null;
4311
+ const children = node.getChildren();
4312
+ const orderedChildren = first ? children : [...children].reverse();
4313
+ for (const child of orderedChildren) {
4314
+ const textNode = findBoundaryText(child, first);
4315
+ if (textNode) return textNode;
4316
+ }
4317
+ return null;
4318
+ };
4319
+ if (offset === 0) {
4320
+ const firstText = findBoundaryText(root, true);
4321
+ if (firstText) return {
4322
+ key: firstText.getKey(),
4323
+ offset: 0
4324
+ };
4325
+ }
4326
+ if (offset === root.getChildrenSize()) {
4327
+ const lastText = findBoundaryText(root, false);
4328
+ if (lastText) return {
4329
+ key: lastText.getKey(),
4330
+ offset: lastText.getTextContentSize()
4331
+ };
4332
+ }
4333
+ return {
4334
+ key: root.getKey(),
4335
+ offset
4336
+ };
4337
+ }, { editor });
4338
+ }
4339
+ function mapSelectionToEditor(selection, sourceEditor, targetEditor) {
4340
+ const startReference = getNodeReferenceForKey(sourceEditor, selection.startNodeId);
4341
+ const endReference = getNodeReferenceForKey(sourceEditor, selection.endNodeId);
4342
+ if (!startReference || !endReference) return null;
4343
+ const startPoint = getNodeKeyForReference(targetEditor, startReference, selection.startOffset);
4344
+ const endPoint = getNodeKeyForReference(targetEditor, endReference, selection.endOffset);
4345
+ if (!startPoint || !endPoint) return null;
4346
+ return {
4347
+ ...selection,
4348
+ endNodeId: endPoint.key,
4349
+ endOffset: endPoint.offset,
4350
+ startNodeId: startPoint.key,
4351
+ startOffset: startPoint.offset
4352
+ };
4353
+ }
3903
4354
  function registerMarkdownCommand(editor, kernel, service) {
3904
4355
  return mergeRegister(editor.registerCommand(INSERT_MARKDOWN_COMMAND, (payload) => {
3905
4356
  const { markdown } = payload;
@@ -3922,9 +4373,12 @@ function registerMarkdownCommand(editor, kernel, service) {
3922
4373
  return false;
3923
4374
  }, COMMAND_PRIORITY_HIGH), editor.registerCommand(GET_MARKDOWN_SELECTION_COMMAND, (payload) => {
3924
4375
  const newEditor = kernel.cloneNodeEditor();
3925
- const s = kernel.getSelection();
3926
- if (s) {
3927
- newEditor.setSelection(s);
4376
+ const sourceEditor = kernel.getLexicalEditor();
4377
+ const selection = kernel.getSelection();
4378
+ const targetEditor = newEditor.getLexicalEditor();
4379
+ const mappedSelection = sourceEditor && targetEditor && selection ? mapSelectionToEditor(selection, sourceEditor, targetEditor) : null;
4380
+ if (mappedSelection && targetEditor) {
4381
+ newEditor.setSelection(mappedSelection);
3928
4382
  newEditor.getLexicalEditor()?.update(() => {
3929
4383
  const sel = $getSelection();
3930
4384
  if (!sel) return;
@@ -4021,7 +4475,10 @@ var MarkdownDataSource = class extends DataSource {
4021
4475
  const inode = { root: parseMarkdownToLexical(data, this.markdownService.markdownReaders) };
4022
4476
  this.getService?.(INodeService)?.processNodeTree(inode);
4023
4477
  logger$5.debug("Parsed Lexical State:", inode);
4024
- editor.setEditorState(editor.parseEditorState(inode));
4478
+ const editorState = editor.parseEditorState(inode, () => {
4479
+ $normalizeNodeIds($getRoot());
4480
+ });
4481
+ editor.setEditorState(editorState);
4025
4482
  }
4026
4483
  write(editor, options) {
4027
4484
  if (options?.selection) return editor.getEditorState().read(() => {
@@ -4901,6 +5358,7 @@ const LitexmlPlugin = class extends KernelPlugin {
4901
5358
  if (config?.enabled !== false) kernel.registerDataSource(this.datasource);
4902
5359
  }
4903
5360
  onInit(editor) {
5361
+ this.register(registerNodeIdentityPolicy(editor, liteXmlIdentityPolicy));
4904
5362
  this.register(registerLiteXMLCommand(editor, this.datasource));
4905
5363
  this.register(registerLiteXMLDiffCommand(editor));
4906
5364
  this.register(registerLegacyTableCellDiffNormalization(editor));
@@ -4940,6 +5398,7 @@ const LitexmlPlugin = class extends KernelPlugin {
4940
5398
  diffNode.getChildren().forEach((child) => nodeToXML(child, lines, indent));
4941
5399
  break;
4942
5400
  case "remove": break;
5401
+ case "listItemRemove": break;
4943
5402
  case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
4944
5403
  nodeToXML(child, lines, indent);
4945
5404
  });
@@ -4992,6 +5451,7 @@ const LitexmlPlugin = class extends KernelPlugin {
4992
5451
  diffNode.getChildren().forEach((child) => ctx.processChild(ctx, child));
4993
5452
  break;
4994
5453
  case "remove": break;
5454
+ case "listItemRemove": break;
4995
5455
  case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
4996
5456
  ctx.processChild(ctx, child);
4997
5457
  });
@@ -5004,9 +5464,32 @@ const LitexmlPlugin = class extends KernelPlugin {
5004
5464
  //#region src/plugins/common/data-source/json-data-source.ts
5005
5465
  var JSONDataSource = class extends DataSource {
5006
5466
  read(editor, data, options = {}) {
5007
- let dataObj;
5008
- if (typeof data === "string") dataObj = JSON.parse(data);
5009
- else dataObj = data;
5467
+ let inputData;
5468
+ if (typeof data === "string") inputData = JSON.parse(data);
5469
+ else inputData = data;
5470
+ const dataObj = structuredClone(inputData);
5471
+ if (!(options.keepId ?? dataObj.keepId ?? false)) {
5472
+ const stripNodeIds = (node) => {
5473
+ delete node.id;
5474
+ const state = node.$;
5475
+ if (state && typeof state === "object" && state.properties) {
5476
+ const properties = { ...state.properties };
5477
+ delete properties.nodeId;
5478
+ if (Object.keys(properties).length > 0) node.$ = {
5479
+ ...state,
5480
+ properties
5481
+ };
5482
+ else {
5483
+ const nextState = { ...state };
5484
+ delete nextState.properties;
5485
+ if (Object.keys(nextState).length > 0) node.$ = nextState;
5486
+ else delete node.$;
5487
+ }
5488
+ }
5489
+ if (Array.isArray(node.children)) node.children.forEach((child) => stripNodeIds(child));
5490
+ };
5491
+ stripNodeIds(dataObj.root);
5492
+ } else migrateSerializedNodeIds(dataObj.root);
5010
5493
  const process = (node) => {
5011
5494
  for (let i = 0; i < node.children.length; i++) {
5012
5495
  const child = node.children[i];
@@ -5028,24 +5511,10 @@ var JSONDataSource = class extends DataSource {
5028
5511
  }
5029
5512
  };
5030
5513
  process(dataObj.root);
5031
- if (dataObj.keepId || options.keepId) {
5032
- const state = editor.parseEditorState({ root: INodeHelper.createRootNode() }, (state) => {
5033
- try {
5034
- const root = $parseSerializedNodeImpl(dataObj.root, editor, true, state);
5035
- let maxId = -1;
5036
- Array.from(state._nodeMap.keys()).forEach((key) => {
5037
- if (key === "root") return;
5038
- const numericKey = Number(key);
5039
- if (Number.isInteger(numericKey) && numericKey >= 0) maxId = Math.max(maxId, numericKey);
5040
- });
5041
- resetRandomKey(maxId + 1);
5042
- state._nodeMap.set(root.getKey(), root);
5043
- } catch (error) {
5044
- console.error(error);
5045
- }
5046
- });
5047
- editor.setEditorState(state);
5048
- } else editor.setEditorState(editor.parseEditorState({ root: dataObj.root }));
5514
+ const editorState = editor.parseEditorState({ root: dataObj.root }, () => {
5515
+ $normalizeNodeIds($getRoot());
5516
+ });
5517
+ editor.setEditorState(editorState);
5049
5518
  }
5050
5519
  write(editor, options) {
5051
5520
  if (options?.selection) return editor.getEditorState().read(() => {
@@ -5139,7 +5608,10 @@ var TextDataSource = class extends DataSource {
5139
5608
  }
5140
5609
  INodeHelper.appendChild(rootNode, paragraph);
5141
5610
  });
5142
- editor.setEditorState(editor.parseEditorState({ root: rootNode }));
5611
+ const editorState = editor.parseEditorState({ root: rootNode }, () => {
5612
+ $normalizeNodeIds($getRoot());
5613
+ });
5614
+ editor.setEditorState(editorState);
5143
5615
  }
5144
5616
  write(editor, options) {
5145
5617
  if (options?.selection) return editor.getEditorState().read(() => {
@@ -5954,6 +6426,7 @@ const CommonPlugin = class extends KernelPlugin {
5954
6426
  super();
5955
6427
  this.kernel = kernel;
5956
6428
  this.config = config;
6429
+ this.identityService = new NodeIdentityService();
5957
6430
  this.formats = {
5958
6431
  bold: true,
5959
6432
  header: true,
@@ -5963,6 +6436,7 @@ const CommonPlugin = class extends KernelPlugin {
5963
6436
  subscript: false,
5964
6437
  superscript: false
5965
6438
  };
6439
+ kernel.registerService(INodeIdentityService, this.identityService);
5966
6440
  const markdownOption = config.markdownOption ?? true;
5967
6441
  if (typeof markdownOption === "object") {
5968
6442
  this.formats.bold = markdownOption.bold ?? true;
@@ -6103,6 +6577,15 @@ const CommonPlugin = class extends KernelPlugin {
6103
6577
  });
6104
6578
  markdownService.registerMarkdownWriter("text", (ctx, node) => {
6105
6579
  if (!$isTextNode(node)) return;
6580
+ let parentNode = node.getParent();
6581
+ let isInTableCell = false;
6582
+ while (parentNode) {
6583
+ if (parentNode.getType() === "tablecell" || parentNode.getType() === "table-cell-diff") {
6584
+ isInTableCell = true;
6585
+ break;
6586
+ }
6587
+ parentNode = parentNode.getParent();
6588
+ }
6106
6589
  const isBold = formats.bold && node.hasFormat("bold");
6107
6590
  const isItalic = formats.italic && node.hasFormat("italic");
6108
6591
  const isUnderline = node.hasFormat("underline");
@@ -6121,7 +6604,8 @@ const CommonPlugin = class extends KernelPlugin {
6121
6604
  if (res) tailSpace = res[0];
6122
6605
  const append = textContent.trimEnd();
6123
6606
  const lastChar = append.at(-1);
6124
- ctx.appendLine(append);
6607
+ const markdownText = isInTableCell ? append.replaceAll("\\", "\\\\").replaceAll("|", "\\|") : append;
6608
+ ctx.appendLine(markdownText);
6125
6609
  if (isSubscript) ctx.appendLine("~");
6126
6610
  if (isSuperscript) ctx.appendLine("^");
6127
6611
  if (isUnderline) ctx.appendLine("</ins>");
@@ -6137,6 +6621,16 @@ const CommonPlugin = class extends KernelPlugin {
6137
6621
  registerMDReader(markdownService);
6138
6622
  }
6139
6623
  onInit(editor) {
6624
+ this.register(this.identityService.bindEditor(editor));
6625
+ this.register(editor.registerNodeTransform(RootNode, $normalizeNodeIds));
6626
+ this.register(editor.registerCommand(SELECTION_INSERT_CLIPBOARD_NODES_COMMAND, ({ nodes }) => {
6627
+ const clearCopiedIds = (node) => {
6628
+ $clearNodeId(node);
6629
+ if ($isElementNode(node)) node.getChildren().forEach(clearCopiedIds);
6630
+ };
6631
+ nodes.forEach(clearCopiedIds);
6632
+ return false;
6633
+ }, COMMAND_PRIORITY_CRITICAL));
6140
6634
  this.register(this.kernel.registerHighCommand(PASTE_COMMAND, (event) => {
6141
6635
  if (!(event instanceof ClipboardEvent)) return false;
6142
6636
  const clipboardData = event.clipboardData;
@@ -9971,7 +10465,11 @@ const ListPlugin = class extends KernelPlugin {
9971
10465
  return false;
9972
10466
  });
9973
10467
  litexmlService.registerXMLWriter(ListItemNode.getType(), (node, ctx) => {
9974
- if ($isListItemNode(node)) return ctx.createXmlNode("li");
10468
+ if ($isListItemNode(node)) {
10469
+ const child = node.getFirstChild();
10470
+ if (child?.getType() === "diff" && "diffType" in child && child.diffType === "listItemRemove") return { lines: [] };
10471
+ return ctx.createXmlNode("li");
10472
+ }
9975
10473
  return false;
9976
10474
  });
9977
10475
  litexmlService.registerXMLReader("ol", (xmlNode, children) => {
@@ -10045,6 +10543,8 @@ const ListPlugin = class extends KernelPlugin {
10045
10543
  markdownService.registerMarkdownWriter(ListItemNode.getType(), (ctx, node) => {
10046
10544
  const parent = node.getParent();
10047
10545
  if ($isListItemNode(node) && $isListNode(parent)) {
10546
+ const child = node.getFirstChild();
10547
+ if (child?.getType() === "diff" && "diffType" in child && child.diffType === "listItemRemove") return true;
10048
10548
  if ($isListNode(node.getFirstChild())) return;
10049
10549
  const level = getLevel(parent);
10050
10550
  const prefix = " ".repeat(level);
@@ -10347,14 +10847,14 @@ const MathPlugin = class extends KernelPlugin {
10347
10847
  if (!litexmlService) return;
10348
10848
  litexmlService.registerXMLWriter(MathInlineNode.getType(), (node, ctx) => {
10349
10849
  if (node instanceof MathInlineNode) {
10350
- const attributes = { code: node.getTextContent() };
10850
+ const attributes = { code: node.code };
10351
10851
  return ctx.createXmlNode("math", attributes);
10352
10852
  }
10353
10853
  return false;
10354
10854
  });
10355
10855
  litexmlService.registerXMLWriter(MathBlockNode.getType(), (node, ctx) => {
10356
10856
  if (node instanceof MathBlockNode) {
10357
- const attributes = { code: node.getTextContent() };
10857
+ const attributes = { code: node.code };
10358
10858
  return ctx.createXmlNode("mathBlock", attributes);
10359
10859
  }
10360
10860
  return false;
@@ -11822,24 +12322,10 @@ const extractMediaFromEditorState = (state) => {
11822
12322
  };
11823
12323
  //#endregion
11824
12324
  //#region src/headless/index.ts
11825
- const getNumericId = (id) => {
11826
- if (typeof id !== "number" && typeof id !== "string") return null;
11827
- const numericId = Number(id);
11828
- return Number.isInteger(numericId) && numericId >= 0 ? numericId : null;
11829
- };
11830
- const findMaxSerializedId = (node) => {
11831
- if (!node || typeof node !== "object") return -1;
11832
- const record = node;
11833
- const ownMax = getNumericId(record.id) ?? -1;
11834
- if (!Array.isArray(record.children)) return ownMax;
11835
- return record.children.reduce((maxId, child) => Math.max(maxId, findMaxSerializedId(child)), ownMax);
11836
- };
11837
- const createSerializedId = (context) => String(context.nextId++);
11838
- const createCodeChildrenFromLegacyCode = (code, context) => code.split("\n").flatMap((text, index, array) => {
12325
+ const createCodeChildrenFromLegacyCode = (code) => code.split("\n").flatMap((text, index, array) => {
11839
12326
  const textNode = {
11840
12327
  detail: 0,
11841
12328
  format: 0,
11842
- id: createSerializedId(context),
11843
12329
  mode: "normal",
11844
12330
  style: "",
11845
12331
  text,
@@ -11848,18 +12334,17 @@ const createCodeChildrenFromLegacyCode = (code, context) => code.split("\n").fla
11848
12334
  };
11849
12335
  if (index === array.length - 1) return textNode;
11850
12336
  return [textNode, {
11851
- id: createSerializedId(context),
11852
12337
  type: "linebreak",
11853
12338
  version: 1
11854
12339
  }];
11855
12340
  });
11856
- const normalizeLegacyEditorDataNode = (node, context) => {
12341
+ const normalizeLegacyEditorDataNode = (node) => {
11857
12342
  if (!node || typeof node !== "object") return node;
11858
12343
  const record = node;
11859
- const children = Array.isArray(record.children) ? record.children.map((child) => normalizeLegacyEditorDataNode(child, context)) : record.children;
12344
+ const children = Array.isArray(record.children) ? record.children.map((child) => normalizeLegacyEditorDataNode(child)) : record.children;
11860
12345
  if (record.type === "code" && typeof record.code === "string" && !Array.isArray(children)) return {
11861
12346
  ...record,
11862
- children: createCodeChildrenFromLegacyCode(record.code, context),
12347
+ children: createCodeChildrenFromLegacyCode(record.code),
11863
12348
  direction: record.direction ?? "ltr",
11864
12349
  format: record.format ?? "",
11865
12350
  indent: record.indent ?? 0,
@@ -11874,10 +12359,9 @@ const normalizeLegacyEditorDataNode = (node, context) => {
11874
12359
  };
11875
12360
  const normalizeLegacyEditorData = (editorData) => {
11876
12361
  const data = typeof editorData === "string" ? JSON.parse(editorData) : editorData;
11877
- const context = { nextId: findMaxSerializedId(data.root) + 1 };
11878
12362
  return {
11879
12363
  ...data,
11880
- root: normalizeLegacyEditorDataNode(data.root, context)
12364
+ root: normalizeLegacyEditorDataNode(data.root)
11881
12365
  };
11882
12366
  };
11883
12367
  const extractSerializedCodeText = (children) => children.map((child) => {
@@ -11960,6 +12444,17 @@ var HeadlessEditor = class {
11960
12444
  await moment();
11961
12445
  return this;
11962
12446
  }
12447
+ async applyLiteXMLBatchWithResults(operations) {
12448
+ let results;
12449
+ if (!this.kernel.dispatchCommand(LITEXML_MODIFY_WITH_RESULTS_COMMAND, {
12450
+ onResults: (operationResults) => {
12451
+ results = operationResults;
12452
+ },
12453
+ operations
12454
+ }) || !results) throw new Error("LiteXML batch result command is not registered.");
12455
+ await moment();
12456
+ return results;
12457
+ }
11963
12458
  export(options = {}) {
11964
12459
  const snapshot = {
11965
12460
  editorData: preserveSerializedCodeTextInEditorData(this.kernel.getDocument("json")),
@@ -12002,4 +12497,4 @@ function createHeadlessEditor(options) {
12002
12497
  return new HeadlessEditor(options);
12003
12498
  }
12004
12499
  //#endregion
12005
- export { DEFAULT_HEADLESS_EDITOR_PLUGINS, HeadlessEditor, createHeadlessEditor, extractMediaFromEditorState };
12500
+ export { $getNodeById, $getNodeId, DEFAULT_HEADLESS_EDITOR_PLUGINS, HeadlessEditor, INodeIdentityService, createHeadlessEditor, extractMediaFromEditorState };