@lobehub/editor 4.24.1 → 4.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/es/codemirror/style.js +42 -0
  2. package/es/editor-kernel/kernel.js +3 -0
  3. package/es/editor-kernel/react/useDecorators.js +2 -4
  4. package/es/headless/collapsible-plugin.js +74 -0
  5. package/es/headless/index.js +2 -0
  6. package/es/headless.d.ts +18 -0
  7. package/es/headless.js +2479 -71
  8. package/es/index.d.ts +26 -3
  9. package/es/index.js +17 -2
  10. package/es/locale/index.d.ts +14 -0
  11. package/es/locale/index.js +14 -0
  12. package/es/plugins/block/command/index.js +24 -0
  13. package/es/plugins/block/plugin/index.js +7 -5
  14. package/es/plugins/block/react/ReactBlockPlugin.js +43 -1
  15. package/es/plugins/block/react/core/menu-position.js +20 -0
  16. package/es/plugins/block/react/drag/drag-session.js +3 -2
  17. package/es/plugins/block/react/drag/drag-utils.js +13 -1
  18. package/es/plugins/codemirror-block/index.d.ts +2 -1
  19. package/es/plugins/codemirror-block/plugin/index.js +2 -0
  20. package/es/plugins/codemirror-block/react/CodemirrorNode.js +95 -5
  21. package/es/plugins/codemirror-block/react/useCodemirrorEditLock.js +33 -0
  22. package/es/plugins/codemirror-block/service/index.d.ts +40 -0
  23. package/es/plugins/codemirror-block/service/index.js +59 -0
  24. package/es/plugins/collapsible/command/index.d.ts +17 -0
  25. package/es/plugins/collapsible/command/index.js +72 -0
  26. package/es/plugins/collapsible/index.d.ts +7 -0
  27. package/es/plugins/collapsible/node/CollapsibleNode.d.ts +30 -0
  28. package/es/plugins/collapsible/node/CollapsibleNode.js +138 -0
  29. package/es/plugins/collapsible/plugin/index.d.ts +11 -0
  30. package/es/plugins/collapsible/plugin/index.js +535 -0
  31. package/es/plugins/collapsible/react/ReactCollapsiblePlugin.d.ts +6 -0
  32. package/es/plugins/collapsible/react/ReactCollapsiblePlugin.js +19 -0
  33. package/es/plugins/collapsible/react/index.d.ts +3 -0
  34. package/es/plugins/collapsible/react/style.js +67 -0
  35. package/es/plugins/collapsible/react/type.d.ts +6 -0
  36. package/es/plugins/common/plugin/index.js +3 -2
  37. package/es/plugins/common/plugin/progressive-select-all.js +81 -0
  38. package/es/plugins/common/plugin/register.js +73 -4
  39. package/es/plugins/link/command/index.js +26 -3
  40. package/es/plugins/link/conversion/index.js +237 -0
  41. package/es/plugins/link/index.d.ts +5 -3
  42. package/es/plugins/link/node/LinkBlockCardNode.d.ts +16 -0
  43. package/es/plugins/link/node/LinkBlockCardNode.js +66 -0
  44. package/es/plugins/link/node/LinkCardNode.d.ts +42 -0
  45. package/es/plugins/link/node/LinkCardNode.js +161 -0
  46. package/es/plugins/link/node/LinkIframeNode.d.ts +33 -0
  47. package/es/plugins/link/node/LinkIframeNode.js +131 -0
  48. package/es/plugins/link/node/LinkNode.d.ts +48 -2
  49. package/es/plugins/link/node/LinkNode.js +14 -6
  50. package/es/plugins/link/node/SchemaNode.d.ts +36 -0
  51. package/es/plugins/link/node/SchemaNode.js +149 -0
  52. package/es/plugins/link/normalization/index.d.ts +7 -0
  53. package/es/plugins/link/normalization/index.js +38 -0
  54. package/es/plugins/link/plugin/index.d.ts +26 -1
  55. package/es/plugins/link/plugin/index.js +124 -4
  56. package/es/plugins/link/react/ReactLinkPlugin.js +165 -7
  57. package/es/plugins/link/react/components/LinkCard.js +235 -0
  58. package/es/plugins/link/react/components/LinkEdit.js +93 -14
  59. package/es/plugins/link/react/components/LinkIframe.js +128 -0
  60. package/es/plugins/link/react/components/LinkToolbar.js +440 -111
  61. package/es/plugins/link/react/components/SchemaLink.js +49 -0
  62. package/es/plugins/link/react/index.d.ts +2 -2
  63. package/es/plugins/link/react/renderer-registry.d.ts +81 -0
  64. package/es/plugins/link/react/renderer-registry.js +80 -0
  65. package/es/plugins/link/react/style.js +63 -1
  66. package/es/plugins/link/react/type.d.ts +24 -1
  67. package/es/plugins/link/service/i-link-service.d.ts +156 -1
  68. package/es/plugins/link/service/i-link-service.js +258 -5
  69. package/es/plugins/list/plugin/index.js +9 -2
  70. package/es/plugins/litexml/command/diffCommand.js +94 -14
  71. package/es/plugins/litexml/command/index.js +187 -6
  72. package/es/plugins/litexml/diff-validation.js +237 -0
  73. package/es/plugins/litexml/node/DiffContentNode.js +67 -0
  74. package/es/plugins/litexml/node/DiffNode.d.ts +1 -1
  75. package/es/plugins/litexml/node/DiffNode.js +5 -2
  76. package/es/plugins/litexml/node/TableCellDiffNode.js +77 -0
  77. package/es/plugins/litexml/node/TableRowDiffNode.js +73 -0
  78. package/es/plugins/litexml/plugin/index.d.ts +2 -0
  79. package/es/plugins/litexml/plugin/index.js +62 -9
  80. package/es/plugins/litexml/react/DiffNodeToolbar/index.js +10 -12
  81. package/es/plugins/litexml/react/TableRowDiffToolbar.js +123 -0
  82. package/es/plugins/litexml/react/hooks/useHasDiffNode.js +5 -2
  83. package/es/plugins/litexml/react/index.js +6 -2
  84. package/es/plugins/litexml/react/style.js +18 -5
  85. package/es/plugins/litexml/table-cell-diff.js +153 -0
  86. package/es/plugins/litexml/table-row-diff.js +82 -0
  87. package/es/plugins/markdown/data-source/markdown/parse.js +25 -1
  88. package/es/plugins/markdown/plugin/index.js +5 -3
  89. package/es/plugins/slash/react/ReactSlashPlugin.js +22 -43
  90. package/es/plugins/slash/react/components/DefaultSlashMenu.js +76 -16
  91. package/es/plugins/slash/react/menu-utils.js +84 -0
  92. package/es/plugins/slash/service/i-slash-service.d.ts +3 -0
  93. package/es/plugins/table/plugin/index.js +4 -1
  94. package/es/plugins/table/plugin/single-cell-paste.js +79 -0
  95. package/es/plugins/table/react/TableController/hooks.js +7 -4
  96. package/es/plugins/table/react/hooks.js +2 -2
  97. package/es/plugins/table/react/index.js +2 -0
  98. package/es/plugins/table/react/style.js +15 -5
  99. package/es/plugins/table/react/useScrollFocusedTableCellIntoView.js +46 -0
  100. package/es/plugins/toc/index.d.ts +10 -0
  101. package/es/plugins/toc/plugin/index.d.ts +8 -0
  102. package/es/plugins/toc/plugin/index.js +56 -0
  103. package/es/plugins/toc/react/ReactTocPlugin.d.ts +6 -0
  104. package/es/plugins/toc/react/ReactTocPlugin.js +36 -0
  105. package/es/plugins/toc/react/getNearestScrollContainer.d.ts +6 -0
  106. package/es/plugins/toc/react/getNearestScrollContainer.js +18 -0
  107. package/es/plugins/toc/react/hooks/useActiveHeading.js +59 -0
  108. package/es/plugins/toc/react/hooks/useTocItems.js +50 -0
  109. package/es/plugins/toc/react/index.d.ts +5 -0
  110. package/es/plugins/toc/react/type.d.ts +30 -0
  111. package/es/plugins/toc/react/useToc.d.ts +5 -0
  112. package/es/plugins/toc/react/useToc.js +41 -0
  113. package/es/plugins/toc/service/i-toc-service.d.ts +39 -0
  114. package/es/plugins/toc/service/i-toc-service.js +125 -0
  115. package/es/plugins/toc/service/index.d.ts +3 -0
  116. package/es/plugins/toc/types.d.ts +23 -0
  117. package/es/plugins/toc/utils/index.d.ts +6 -0
  118. package/es/plugins/toc/utils/index.js +30 -0
  119. package/es/plugins/toolbar/react/index.js +26 -12
  120. package/es/plugins/toolbar/react/style.js +3 -2
  121. package/es/plugins/virtual-block/plugin/register.js +34 -12
  122. package/es/plugins/yjs/index.d.ts +6 -0
  123. package/es/plugins/yjs/plugin/index.d.ts +7 -0
  124. package/es/plugins/yjs/plugin/index.js +191 -0
  125. package/es/plugins/yjs/plugin/types.d.ts +16 -0
  126. package/es/plugins/yjs/plugin/utils/awareness.js +9 -0
  127. package/es/plugins/yjs/plugin/utils/editor-state.js +47 -0
  128. package/es/plugins/yjs/plugin/utils/history.js +31 -0
  129. package/es/plugins/yjs/plugin/utils/node-properties.js +39 -0
  130. package/es/plugins/yjs/plugin/utils/sync.js +24 -0
  131. package/es/plugins/yjs/react/codemirrorEditLockProvider.js +59 -0
  132. package/es/plugins/yjs/react/index.d.ts +23 -0
  133. package/es/plugins/yjs/react/index.js +200 -0
  134. package/es/plugins/yjs/service/index.d.ts +47 -0
  135. package/es/plugins/yjs/service/index.js +84 -0
  136. package/es/plugins/yjs/websocket-provider.d.ts +78 -0
  137. package/es/plugins/yjs/websocket-provider.js +263 -0
  138. package/es/renderer/LexicalRenderer.js +9 -2
  139. package/es/types/kernel.d.ts +4 -0
  140. package/es/utils/updatePosition.js +5 -2
  141. package/package.json +3 -1
  142. package/patches/@lexical__yjs@0.42.0.patch +42 -0
  143. package/scripts/postinstall-lexical-patch.cjs +62 -36
package/es/headless.js CHANGED
@@ -1,10 +1,10 @@
1
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";
2
2
  import { createHeadlessEditor as createHeadlessEditor$1 } from "@lexical/headless";
3
3
  import { createEmptyHistoryState, registerHistory } from "@lexical/history";
4
- import { $computeTableMapSkipCellCheck, $createTableNodeWithDimensions, $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";
4
+ 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
5
  import { get, merge, template, templateSettings } from "es-toolkit/compat";
6
6
  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, TEXT_TYPE_TO_FORMAT, TextNode, UNDO_COMMAND, createCommand, createEditor, getNearestEditorFromDOMNode, isHTMLElement, isModifierMatch, resetRandomKey, setDOMUnmanaged } from "lexical";
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";
8
8
  import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode, registerRichText } from "@lexical/rich-text";
9
9
  import createDebug from "debug";
10
10
  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";
@@ -46,6 +46,12 @@ var locale_default = {
46
46
  replace: "Replace"
47
47
  },
48
48
  link: {
49
+ copy: "Copy Link",
50
+ copySuccess: "Link copied",
51
+ convertToCard: "Convert to Card",
52
+ convertToIframe: "Convert to Iframe",
53
+ convertToLink: "Convert to Link",
54
+ convertToSchema: "Convert to Schema",
49
55
  edit: "Edit Link",
50
56
  editLinkTitle: "Link",
51
57
  editTextTitle: "Text",
@@ -76,6 +82,14 @@ var locale_default = {
76
82
  insertColumnRight: "Insert {{count}} column(s) to the right",
77
83
  insertRowAbove: "Insert {{count}} row(s) above",
78
84
  insertRowBelow: "Insert {{count}} row(s) below"
85
+ },
86
+ toc: {
87
+ ariaLabel: "Table of contents",
88
+ empty: "No headings",
89
+ expand: "Expand table of contents",
90
+ pin: "Pin table of contents",
91
+ title: "TOC",
92
+ unpin: "Unpin table of contents"
79
93
  }
80
94
  };
81
95
  //#endregion
@@ -955,6 +969,9 @@ var Kernel = class Kernel extends EventEmitter {
955
969
  this.historyState.current = null;
956
970
  }
957
971
  datasource.read(this.editor, content, options);
972
+ this.pluginsInstances.forEach((plugin) => {
973
+ plugin.onDocumentChange?.(type, content, options);
974
+ });
958
975
  this.emit("documentChange", type, content);
959
976
  this.logger.debug(`📥 Set ${type} document`);
960
977
  }
@@ -1597,8 +1614,309 @@ function registerCursorNode(editor) {
1597
1614
  }, COMMAND_PRIORITY_HIGH));
1598
1615
  }
1599
1616
  //#endregion
1617
+ //#region src/plugins/litexml/diff-validation.ts
1618
+ const ACTIONABLE_DIFF_TYPES = /* @__PURE__ */ new Set([
1619
+ "add",
1620
+ "remove",
1621
+ "modify",
1622
+ "listItemAdd",
1623
+ "listItemModify",
1624
+ "listItemRemove"
1625
+ ]);
1626
+ const isActionableDiff = (node) => node?.type === "diff" && ACTIONABLE_DIFF_TYPES.has(node.diffType || "");
1627
+ const isElementNode = (node) => Array.isArray(node.children);
1628
+ const cloneNode = (node) => structuredClone(node);
1629
+ function hasActionableDiffDescendant(node) {
1630
+ return (node.children || []).some((child) => isActionableDiff(child) || hasActionableDiffDescendant(child));
1631
+ }
1632
+ /**
1633
+ * Validate the serialized public tree. Paths use serialized node ids, not
1634
+ * Lexical's private node map, so this remains stable across JSON reloads.
1635
+ */
1636
+ function collectIllegalNestedDiffPaths(root) {
1637
+ const violations = [];
1638
+ const visit = (node, ancestors, path) => {
1639
+ const actionableAncestor = [...ancestors].reverse().find(isActionableDiff);
1640
+ const rowDiffAncestor = [...ancestors].reverse().find((ancestor) => ancestor.type === "table-row-diff");
1641
+ const allowedCellDiffChild = ancestors.at(-1)?.type === "table-cell-diff" && isActionableDiff(node);
1642
+ if (isActionableDiff(node) && actionableAncestor && !allowedCellDiffChild) violations.push(`${path}:diff-inside-diff`);
1643
+ if (rowDiffAncestor) {
1644
+ if (node.type === "table-cell-diff") violations.push(`${path}:cell-inside-row-diff`);
1645
+ else if (isActionableDiff(node)) violations.push(`${path}:diff-inside-row-diff`);
1646
+ }
1647
+ if (actionableAncestor) {
1648
+ if (node.type === "table-cell-diff") violations.push(`${path}:cell-diff-inside-diff`);
1649
+ else if (node.type === "table-row-diff") violations.push(`${path}:row-diff-inside-diff`);
1650
+ }
1651
+ if (!isElementNode(node)) return;
1652
+ node.children?.forEach((child) => {
1653
+ visit(child, [...ancestors, node], `${path}/${child.id || child.type || "node"}`);
1654
+ });
1655
+ };
1656
+ visit(root, [], root.id || root.type || "root");
1657
+ return violations;
1658
+ }
1659
+ function findNewIllegalDiffPaths(previous, projected) {
1660
+ const previousCounts = /* @__PURE__ */ new Map();
1661
+ collectIllegalNestedDiffPaths(previous.root).forEach((path) => {
1662
+ previousCounts.set(path, (previousCounts.get(path) || 0) + 1);
1663
+ });
1664
+ const newPaths = [];
1665
+ const projectedCounts = /* @__PURE__ */ new Map();
1666
+ collectIllegalNestedDiffPaths(projected.root).forEach((path) => {
1667
+ const count = (projectedCounts.get(path) || 0) + 1;
1668
+ projectedCounts.set(path, count);
1669
+ if (count > (previousCounts.get(path) || 0)) newPaths.push(path);
1670
+ });
1671
+ return newPaths;
1672
+ }
1673
+ function findNodeLocation(root, id, ancestors = []) {
1674
+ if (root.id === id) return {
1675
+ ancestors,
1676
+ index: -1,
1677
+ node: root,
1678
+ parent: null
1679
+ };
1680
+ for (const [index, child] of (root.children || []).entries()) {
1681
+ if (child.id === id) return {
1682
+ ancestors: [...ancestors, root],
1683
+ index,
1684
+ node: child,
1685
+ parent: root
1686
+ };
1687
+ const nested = findNodeLocation(child, id, [...ancestors, root]);
1688
+ if (nested) return nested;
1689
+ }
1690
+ return null;
1691
+ }
1692
+ function replaceNode(location, replacements) {
1693
+ if (!location.parent || location.index < 0) return;
1694
+ location.parent.children?.splice(location.index, 1, ...replacements);
1695
+ }
1696
+ function createDiff(diffType, children) {
1697
+ return {
1698
+ children,
1699
+ diffType,
1700
+ type: "diff"
1701
+ };
1702
+ }
1703
+ function createDiffContent(side, children) {
1704
+ return {
1705
+ children,
1706
+ side,
1707
+ type: "diff-content"
1708
+ };
1709
+ }
1710
+ function projectModifyTarget(root, incoming) {
1711
+ if (!incoming.id) return;
1712
+ const location = findNodeLocation(root, incoming.id);
1713
+ if (!location) return;
1714
+ const target = location.node;
1715
+ const next = cloneNode(incoming);
1716
+ if (target.type === "tablerow" && location.parent?.type === "table") {
1717
+ replaceNode(location, [{
1718
+ children: cloneNode(target.children || []),
1719
+ diffType: "remove",
1720
+ type: "table-row-diff"
1721
+ }, {
1722
+ children: cloneNode(next.children || []),
1723
+ diffType: "add",
1724
+ type: "table-row-diff"
1725
+ }]);
1726
+ return;
1727
+ }
1728
+ if (target.type === "tablecell" || target.type === "table-cell-diff") {
1729
+ const existingDiff = (target.children || []).find((child) => child.type === "diff");
1730
+ if (existingDiff && target.type === "table-cell-diff") {
1731
+ existingDiff.children = cloneNode(next.children || []);
1732
+ return;
1733
+ }
1734
+ const after = existingDiff?.children?.find((child) => child.type === "diff-content" && child.side === "after");
1735
+ if (existingDiff?.diffType === "modify" && after) {
1736
+ after.children = cloneNode(next.children || []);
1737
+ return;
1738
+ }
1739
+ target.children = [createDiff("modify", [createDiffContent("before", cloneNode(target.children || [])), createDiffContent("after", cloneNode(next.children || []))])];
1740
+ return;
1741
+ }
1742
+ if (location.ancestors.some(isActionableDiff)) {
1743
+ replaceNode(location, [next]);
1744
+ return;
1745
+ }
1746
+ replaceNode(location, [createDiff("modify", [cloneNode(target), next])]);
1747
+ }
1748
+ function projectRemoveTarget(root, id) {
1749
+ const location = findNodeLocation(root, id);
1750
+ if (!location) return;
1751
+ if (location.ancestors.some(isActionableDiff)) {
1752
+ replaceNode(location, []);
1753
+ return;
1754
+ }
1755
+ const target = location.node;
1756
+ if (target.type === "tablecell" || target.type === "table-cell-diff") {
1757
+ replaceNode(location, [{
1758
+ children: [createDiff("remove", cloneNode(target.children || []))],
1759
+ diffType: "remove",
1760
+ type: "table-cell-diff"
1761
+ }]);
1762
+ return;
1763
+ }
1764
+ if (target.type === "tablerow" && location.parent?.type === "table") {
1765
+ replaceNode(location, [{
1766
+ children: cloneNode(target.children || []),
1767
+ diffType: "remove",
1768
+ type: "table-row-diff"
1769
+ }]);
1770
+ return;
1771
+ }
1772
+ replaceNode(location, [createDiff("remove", [cloneNode(target)])]);
1773
+ }
1774
+ function projectInsert(root, operation, readXml) {
1775
+ const referenceId = "beforeId" in operation ? operation.beforeId : operation.afterId;
1776
+ const reference = referenceId === "root" ? null : findNodeLocation(root, referenceId)?.node || null;
1777
+ const location = referenceId === "root" ? {
1778
+ ancestors: [root],
1779
+ index: "beforeId" in operation ? 0 : (root.children || []).length - 1,
1780
+ node: root.children?.[0] || root,
1781
+ parent: root
1782
+ } : findNodeLocation(root, referenceId);
1783
+ if (!location) return;
1784
+ const incoming = (readXml(operation.litexml).root?.children || []).map(cloneNode);
1785
+ if (incoming.length === 0 || !location.parent) return;
1786
+ const insertAt = "beforeId" in operation ? location.index : location.index + 1;
1787
+ if (incoming.every((node) => node.type === "tablecell") && reference?.type === "tablecell") {
1788
+ location.parent.children?.splice(insertAt, 0, ...incoming.map((node) => ({
1789
+ children: [createDiff("add", cloneNode(node.children || []))],
1790
+ diffType: "add",
1791
+ type: "table-cell-diff"
1792
+ })));
1793
+ return;
1794
+ }
1795
+ if (incoming.every((node) => node.type === "tablerow") && reference?.type === "tablerow") {
1796
+ location.parent.children?.splice(insertAt, 0, ...incoming.map((node) => ({
1797
+ children: cloneNode(node.children || []),
1798
+ diffType: "add",
1799
+ type: "table-row-diff"
1800
+ })));
1801
+ return;
1802
+ }
1803
+ const actionableAncestor = location.ancestors.findLast(isActionableDiff);
1804
+ const isInlineReference = [
1805
+ "text",
1806
+ "span",
1807
+ "link",
1808
+ "autolink",
1809
+ "linebreak"
1810
+ ].includes(reference?.type || "");
1811
+ const blockAncestor = [...location.ancestors].reverse().find((node) => [
1812
+ "paragraph",
1813
+ "heading",
1814
+ "listitem",
1815
+ "table",
1816
+ "tablerow",
1817
+ "tablecell"
1818
+ ].includes(node.type || ""));
1819
+ if (!actionableAncestor && blockAncestor && hasActionableDiffDescendant(blockAncestor)) {
1820
+ const wrapped = createDiff("modify", [cloneNode(blockAncestor), cloneNode(blockAncestor)]);
1821
+ const blockLocation = findNodeLocation(root, blockAncestor.id || "");
1822
+ if (blockLocation) replaceNode(blockLocation, [wrapped]);
1823
+ return;
1824
+ }
1825
+ if (actionableAncestor && !isInlineReference) {
1826
+ const originLocation = actionableAncestor.id ? findNodeLocation(root, actionableAncestor.id) : null;
1827
+ if (originLocation?.parent && originLocation.index >= 0) {
1828
+ const originInsertAt = "beforeId" in operation ? originLocation.index : originLocation.index + 1;
1829
+ originLocation.parent.children?.splice(originInsertAt, 0, ...incoming.map((node) => createDiff("add", [node])));
1830
+ return;
1831
+ }
1832
+ }
1833
+ const inserted = actionableAncestor && isInlineReference ? incoming : incoming.map((node) => createDiff("add", [node]));
1834
+ location.parent.children?.splice(insertAt, 0, ...inserted);
1835
+ }
1836
+ /**
1837
+ * Project one operation on a cloned serialized tree. The real Lexical tree is
1838
+ * untouched; callers compare illegal paths before/after and only dispatch safe
1839
+ * operations to the live editor.
1840
+ */
1841
+ function projectLiteXmlOperation(document, operation, readXml) {
1842
+ const projected = cloneNode(document);
1843
+ if (operation.action === "modify") (Array.isArray(operation.litexml) ? operation.litexml : [operation.litexml]).forEach((xml) => {
1844
+ (readXml(xml).root?.children || []).forEach((child) => {
1845
+ projectModifyTarget(projected.root, child);
1846
+ });
1847
+ });
1848
+ else if (operation.action === "remove") projectRemoveTarget(projected.root, operation.id);
1849
+ else projectInsert(projected.root, operation, readXml);
1850
+ return projected;
1851
+ }
1852
+ //#endregion
1853
+ //#region src/plugins/litexml/node/DiffContentNode.ts
1854
+ /** Groups one side of a modify diff so it can contain any number of block nodes. */
1855
+ var DiffContentNode = class DiffContentNode extends ElementNode {
1856
+ static getType() {
1857
+ return "diff-content";
1858
+ }
1859
+ static clone(node) {
1860
+ return new DiffContentNode(node.__side, node.__key);
1861
+ }
1862
+ static importJSON(serializedNode) {
1863
+ return $createDiffContentNode(serializedNode.side).updateFromJSON(serializedNode);
1864
+ }
1865
+ static importDOM() {
1866
+ return null;
1867
+ }
1868
+ constructor(side, key) {
1869
+ super(key);
1870
+ this.__side = side;
1871
+ }
1872
+ get side() {
1873
+ return this.__side;
1874
+ }
1875
+ setSide(side) {
1876
+ this.getWritable().__side = side;
1877
+ return this;
1878
+ }
1879
+ updateFromJSON(serializedNode) {
1880
+ return super.updateFromJSON(serializedNode).setSide(serializedNode.side);
1881
+ }
1882
+ exportJSON() {
1883
+ return {
1884
+ ...super.exportJSON(),
1885
+ side: this.__side,
1886
+ type: DiffContentNode.getType(),
1887
+ version: 1
1888
+ };
1889
+ }
1890
+ createDOM(_config, _editor) {
1891
+ const element = document.createElement("div");
1892
+ element.dataset.diffContent = this.__side;
1893
+ return element;
1894
+ }
1895
+ updateDOM(previousNode, dom) {
1896
+ if (previousNode.__side !== this.__side) dom.dataset.diffContent = this.__side;
1897
+ return false;
1898
+ }
1899
+ exportDOM(_editor) {
1900
+ const element = document.createElement("div");
1901
+ element.dataset.diffContent = this.__side;
1902
+ return { element };
1903
+ }
1904
+ canBeEmpty() {
1905
+ return true;
1906
+ }
1907
+ isInline() {
1908
+ return false;
1909
+ }
1910
+ };
1911
+ function $createDiffContentNode(side) {
1912
+ return $applyNodeReplacement(new DiffContentNode(side));
1913
+ }
1914
+ function $isDiffContentNode(node) {
1915
+ return node instanceof DiffContentNode;
1916
+ }
1917
+ //#endregion
1600
1918
  //#region src/plugins/litexml/node/DiffNode.ts
1601
- /** DiffNode - contains two block children: original and modified */
1919
+ /** DiffNode - contains the original and modified sides, optionally grouped into multiple blocks. */
1602
1920
  var DiffNode = class DiffNode extends CardLikeElementNode {
1603
1921
  static getType() {
1604
1922
  return "diff";
@@ -1690,6 +2008,83 @@ var DiffNode = class DiffNode extends CardLikeElementNode {
1690
2008
  function $createDiffNode(diffType = "unchanged") {
1691
2009
  return $applyNodeReplacement(new DiffNode(diffType));
1692
2010
  }
2011
+ function $isDiffNode(node) {
2012
+ return node instanceof DiffNode;
2013
+ }
2014
+ //#endregion
2015
+ //#region src/plugins/litexml/node/TableCellDiffNode.ts
2016
+ /**
2017
+ * Review-only table cell. The diff state lives on a TableCellNode subclass so
2018
+ * TableRowNode children remain cells throughout staging and approval.
2019
+ */
2020
+ var TableCellDiffNode = class TableCellDiffNode extends TableCellNode {
2021
+ static getType() {
2022
+ return "table-cell-diff";
2023
+ }
2024
+ static clone(node) {
2025
+ const clone = new TableCellDiffNode(node.__diffType, node.__changeId, node.getHeaderStyles(), node.getColSpan(), node.getWidth(), node.__key);
2026
+ clone.__rowSpan = node.__rowSpan;
2027
+ clone.__backgroundColor = node.__backgroundColor;
2028
+ clone.__verticalAlign = node.__verticalAlign;
2029
+ return clone;
2030
+ }
2031
+ static importJSON(serializedNode) {
2032
+ return $createTableCellDiffNode(serializedNode.diffType, serializedNode.changeId, serializedNode.headerState, serializedNode.colSpan, serializedNode.width).updateFromJSON(serializedNode);
2033
+ }
2034
+ constructor(diffType, changeId, headerState, colSpan, width, key) {
2035
+ super(headerState, colSpan, width, key);
2036
+ this.__diffType = diffType;
2037
+ this.__changeId = changeId;
2038
+ }
2039
+ createDOM(config) {
2040
+ const element = super.createDOM(config);
2041
+ const theme = config.theme.tableCellDiff;
2042
+ if (typeof theme === "string" && theme) element.classList.add(theme);
2043
+ this.syncDOMState(element);
2044
+ return element;
2045
+ }
2046
+ updateDOM(prevNode) {
2047
+ return super.updateDOM(prevNode) || prevNode.__diffType !== this.__diffType || prevNode.__changeId !== this.__changeId;
2048
+ }
2049
+ exportJSON() {
2050
+ return {
2051
+ ...super.exportJSON(),
2052
+ changeId: this.__changeId,
2053
+ diffType: this.__diffType,
2054
+ type: TableCellDiffNode.getType(),
2055
+ version: 1
2056
+ };
2057
+ }
2058
+ getChangeId() {
2059
+ return this.getLatest().__changeId;
2060
+ }
2061
+ getDiffType() {
2062
+ return this.getLatest().__diffType;
2063
+ }
2064
+ setChangeId(changeId) {
2065
+ this.getWritable().__changeId = changeId;
2066
+ return this;
2067
+ }
2068
+ setDiffType(diffType) {
2069
+ this.getWritable().__diffType = diffType;
2070
+ return this;
2071
+ }
2072
+ updateFromJSON(serializedNode) {
2073
+ return super.updateFromJSON(serializedNode).setDiffType(serializedNode.diffType).setChangeId(serializedNode.changeId);
2074
+ }
2075
+ syncDOMState(element) {
2076
+ element.contentEditable = "false";
2077
+ element.dataset.diffType = this.__diffType;
2078
+ if (this.__changeId) element.dataset.diffChangeId = this.__changeId;
2079
+ else delete element.dataset.diffChangeId;
2080
+ }
2081
+ };
2082
+ function $createTableCellDiffNode(diffType, changeId, headerState, colSpan, width) {
2083
+ return $applyNodeReplacement(new TableCellDiffNode(diffType, changeId, headerState, colSpan, width));
2084
+ }
2085
+ function $isTableCellDiffNode(node) {
2086
+ return node instanceof TableCellDiffNode;
2087
+ }
1693
2088
  //#endregion
1694
2089
  //#region src/plugins/litexml/utils/index.ts
1695
2090
  const getNumericId$1 = (id) => {
@@ -1748,11 +2143,333 @@ function charToId(char) {
1748
2143
  return String((parseInt(char, 36) - startId + maxId) * modInverse % maxId);
1749
2144
  }
1750
2145
  //#endregion
2146
+ //#region src/plugins/litexml/table-cell-diff.ts
2147
+ function copyCellStructure(source, target) {
2148
+ target.setRowSpan(source.getRowSpan());
2149
+ target.setBackgroundColor(source.getBackgroundColor());
2150
+ target.setVerticalAlign(source.getVerticalAlign());
2151
+ }
2152
+ function $createTableCellDiffFromCell(editor, cell, diffType, changeId) {
2153
+ const diffCell = $createTableCellDiffNode(diffType, changeId, cell.getHeaderStyles(), cell.getColSpan(), cell.getWidth());
2154
+ copyCellStructure(cell, diffCell);
2155
+ const diff = $createDiffNode(diffType);
2156
+ diff.append(...cell.getChildren().map((child) => $cloneNode(child, editor)));
2157
+ diffCell.append(diff);
2158
+ return diffCell;
2159
+ }
2160
+ function $createPlainTableCellFromDiff(editor, cell) {
2161
+ const plainCell = $createTableCellNode(cell.getHeaderStyles(), cell.getColSpan(), cell.getWidth());
2162
+ copyCellStructure(cell, plainCell);
2163
+ const diff = cell.getFirstChild();
2164
+ if (diff instanceof DiffNode) plainCell.append(...diff.getChildren().map((child) => $cloneNode(child, editor)));
2165
+ else plainCell.append(...cell.getChildren().map((child) => $cloneNode(child, editor)));
2166
+ return plainCell;
2167
+ }
2168
+ function $getTableForCell(cell) {
2169
+ const row = cell.getParent();
2170
+ if (!$isTableRowNode(row)) return null;
2171
+ const table = row.getParent();
2172
+ return $isTableNode(table) ? table : null;
2173
+ }
2174
+ function $getTableCellColumnIndex(cell) {
2175
+ const row = cell.getParent();
2176
+ if (!$isTableRowNode(row)) return -1;
2177
+ let index = 0;
2178
+ for (const sibling of row.getChildren()) {
2179
+ if (sibling === cell) return index;
2180
+ if ($isTableCellNode(sibling)) index += sibling.getColSpan();
2181
+ }
2182
+ return -1;
2183
+ }
2184
+ function $getLogicalRowWidth(cell) {
2185
+ const row = cell.getParent();
2186
+ if (!$isTableRowNode(row)) return 0;
2187
+ return row.getChildren().reduce((total, child) => total + ($isTableCellNode(child) ? child.getColSpan() : 0), 0);
2188
+ }
2189
+ function $getTableCellDiffGroup(cell) {
2190
+ const changeId = cell.getChangeId();
2191
+ const table = $getTableForCell(cell);
2192
+ if (!changeId || !table) return [cell];
2193
+ return $nodesOfType(TableCellDiffNode).filter((candidate) => candidate.getChangeId() === changeId && candidate.getDiffType() === cell.getDiffType() && $getTableForCell(candidate) === table);
2194
+ }
2195
+ function $updateTableWidthsForCellInsertion(table, rowWidthBefore, columnIndex, insertedSpan) {
2196
+ const currentWidths = table.getColWidths() || Array.from({ length: rowWidthBefore }, () => 250);
2197
+ if (rowWidthBefore < currentWidths.length) return;
2198
+ const fallback = currentWidths[Math.max(0, columnIndex - 1)] || 250;
2199
+ const next = [...currentWidths];
2200
+ next.splice(columnIndex, 0, ...Array.from({ length: insertedSpan }, () => fallback));
2201
+ table.setColWidths(next);
2202
+ }
2203
+ function $removeTableWidthsForCompleteCellGroup(table, group, columnIndex, span) {
2204
+ const rows = table.getChildren().filter($isTableRowNode);
2205
+ const rowKeys = new Set(group.map((cell) => cell.getParent()?.getKey()));
2206
+ if (rows.length !== rowKeys.size) return;
2207
+ const widths = table.getColWidths();
2208
+ if (!widths) return;
2209
+ const next = [...widths];
2210
+ next.splice(columnIndex, span);
2211
+ table.setColWidths(next);
2212
+ }
2213
+ function $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span) {
2214
+ const widths = table.getColWidths();
2215
+ if (!widths) return;
2216
+ if (table.getChildren().reduce((max, row) => {
2217
+ if (!$isTableRowNode(row)) return max;
2218
+ return Math.max(max, row.getChildren().reduce((total, cell) => total + ($isTableCellNode(cell) ? cell.getColSpan() : 0), 0));
2219
+ }, 0) > widths.length - span) return;
2220
+ const next = [...widths];
2221
+ next.splice(columnIndex, span);
2222
+ table.setColWidths(next);
2223
+ }
2224
+ function $getMaximumLogicalColumnCount(table) {
2225
+ return table.getChildren().reduce((maximum, row) => {
2226
+ if (!$isTableRowNode(row)) return maximum;
2227
+ const columnCount = row.getChildren().reduce((total, cell) => {
2228
+ return total + ($isTableCellNode(cell) ? cell.getColSpan() : 0);
2229
+ }, 0);
2230
+ return Math.max(maximum, columnCount);
2231
+ }, 0);
2232
+ }
2233
+ function $hasLegacyTableCellDiffs() {
2234
+ return $nodesOfType(DiffNode).some((diff) => {
2235
+ return (diff.diffType === "add" || diff.diffType === "remove") && $isTableRowNode(diff.getParent()) && diff.getChildren().some($isTableCellNode);
2236
+ });
2237
+ }
2238
+ function $hasIncompleteTableWidths() {
2239
+ return $nodesOfType(TableNode).some((table) => {
2240
+ return (table.getColWidths()?.length || 0) < $getMaximumLogicalColumnCount(table);
2241
+ });
2242
+ }
2243
+ /**
2244
+ * Migrates the old `TableRow > DiffNode > TableCellNode` representation into
2245
+ * cell-shaped diff nodes and makes sure the table has a width for every
2246
+ * logical column. Safe to call repeatedly.
2247
+ */
2248
+ function $normalizeLegacyTableCellDiffs(editor) {
2249
+ let changed = false;
2250
+ $nodesOfType(DiffNode).forEach((legacyDiff) => {
2251
+ const diffType = legacyDiff.diffType;
2252
+ if (diffType !== "add" && diffType !== "remove" || !$isTableRowNode(legacyDiff.getParent())) return;
2253
+ const cells = legacyDiff.getChildren().filter($isTableCellNode);
2254
+ if (cells.length === 0) return;
2255
+ const changeId = `legacy-table-cell-${legacyDiff.getKey()}`;
2256
+ cells.forEach((cell) => {
2257
+ legacyDiff.insertBefore($createTableCellDiffFromCell(editor, cell, diffType, changeId));
2258
+ });
2259
+ legacyDiff.remove();
2260
+ changed = true;
2261
+ });
2262
+ $nodesOfType(TableNode).forEach((table) => {
2263
+ const requiredWidthCount = $getMaximumLogicalColumnCount(table);
2264
+ const currentWidths = table.getColWidths() || [];
2265
+ if (currentWidths.length >= requiredWidthCount) return;
2266
+ const fallback = currentWidths.at(-1) || 250;
2267
+ table.setColWidths([...currentWidths, ...Array.from({ length: requiredWidthCount - currentWidths.length }, () => fallback)]);
2268
+ changed = true;
2269
+ });
2270
+ return changed;
2271
+ }
2272
+ function registerLegacyTableCellDiffNormalization(editor) {
2273
+ let destroyed = false;
2274
+ let scheduled = false;
2275
+ const unregister = editor.registerUpdateListener(() => {
2276
+ if (scheduled || destroyed) return;
2277
+ scheduled = true;
2278
+ queueMicrotask(() => {
2279
+ scheduled = false;
2280
+ if (destroyed) return;
2281
+ if (!editor.getEditorState().read(() => $hasLegacyTableCellDiffs() || $hasIncompleteTableWidths())) return;
2282
+ editor.update(() => {
2283
+ $normalizeLegacyTableCellDiffs(editor);
2284
+ }, { discrete: true });
2285
+ });
2286
+ });
2287
+ return () => {
2288
+ destroyed = true;
2289
+ unregister();
2290
+ };
2291
+ }
2292
+ //#endregion
2293
+ //#region src/plugins/litexml/node/TableRowDiffNode.ts
2294
+ /**
2295
+ * A review-only table row. Keeping the diff state on a TableRowNode subclass
2296
+ * preserves Lexical's table invariant: TableNode children are always rows.
2297
+ */
2298
+ var TableRowDiffNode = class TableRowDiffNode extends TableRowNode {
2299
+ static getType() {
2300
+ return "table-row-diff";
2301
+ }
2302
+ static clone(node) {
2303
+ return new TableRowDiffNode(node.__diffType, node.__changeId, node.getHeight(), node.__key);
2304
+ }
2305
+ static importJSON(serializedNode) {
2306
+ return $createTableRowDiffNode(serializedNode.diffType, serializedNode.changeId, serializedNode.height).updateFromJSON(serializedNode);
2307
+ }
2308
+ constructor(diffType, changeId, height, key) {
2309
+ super(height, key);
2310
+ this.__diffType = diffType;
2311
+ this.__changeId = changeId;
2312
+ }
2313
+ createDOM(config) {
2314
+ const element = super.createDOM(config);
2315
+ const theme = config.theme.tableRowDiff;
2316
+ if (typeof theme === "string" && theme) element.classList.add(theme);
2317
+ this.syncDOMState(element);
2318
+ return element;
2319
+ }
2320
+ exportJSON() {
2321
+ return {
2322
+ ...super.exportJSON(),
2323
+ changeId: this.__changeId,
2324
+ diffType: this.__diffType,
2325
+ type: TableRowDiffNode.getType(),
2326
+ version: 1
2327
+ };
2328
+ }
2329
+ getChangeId() {
2330
+ return this.getLatest().__changeId;
2331
+ }
2332
+ getDiffType() {
2333
+ return this.getLatest().__diffType;
2334
+ }
2335
+ setChangeId(changeId) {
2336
+ this.getWritable().__changeId = changeId;
2337
+ return this;
2338
+ }
2339
+ setDiffType(diffType) {
2340
+ this.getWritable().__diffType = diffType;
2341
+ return this;
2342
+ }
2343
+ updateDOM(prevNode) {
2344
+ return super.updateDOM(prevNode) || prevNode.__diffType !== this.__diffType || prevNode.__changeId !== this.__changeId;
2345
+ }
2346
+ updateFromJSON(serializedNode) {
2347
+ return super.updateFromJSON(serializedNode).setDiffType(serializedNode.diffType).setChangeId(serializedNode.changeId);
2348
+ }
2349
+ syncDOMState(element) {
2350
+ element.dataset.diffType = this.__diffType;
2351
+ if (this.__changeId) element.dataset.diffChangeId = this.__changeId;
2352
+ else delete element.dataset.diffChangeId;
2353
+ element.contentEditable = "false";
2354
+ }
2355
+ };
2356
+ function $createTableRowDiffNode(diffType, changeId, height) {
2357
+ return $applyNodeReplacement(new TableRowDiffNode(diffType, changeId, height));
2358
+ }
2359
+ function $isTableRowDiffNode(node) {
2360
+ return node instanceof TableRowDiffNode;
2361
+ }
2362
+ //#endregion
2363
+ //#region src/plugins/litexml/table-row-diff.ts
2364
+ function $areTableRowStructuresCompatible(previousRow, nextRow) {
2365
+ const previousCells = previousRow.getChildren();
2366
+ const nextCells = nextRow.getChildren();
2367
+ if (previousCells.length !== nextCells.length || !previousCells.every($isTableCellNode) || !nextCells.every($isTableCellNode)) return false;
2368
+ return previousCells.every((previousCell, index) => {
2369
+ const nextCell = nextCells[index];
2370
+ return $isTableCellNode(nextCell) && previousCell.getColSpan() === nextCell.getColSpan() && previousCell.getRowSpan() === nextCell.getRowSpan();
2371
+ });
2372
+ }
2373
+ function $createTableRowDiffFromRow(editor, row, diffType, changeId) {
2374
+ const source = $cloneNode(row, editor);
2375
+ const diffRow = $createTableRowDiffNode(diffType, changeId, row.getHeight());
2376
+ diffRow.append(...source.getChildren());
2377
+ return diffRow;
2378
+ }
2379
+ function $createPlainTableRowFromDiff(editor, row) {
2380
+ const plainRow = $createTableRowNode(row.getHeight());
2381
+ plainRow.append(...row.getChildren().map((child) => $cloneNode(child, editor)));
2382
+ return plainRow;
2383
+ }
2384
+ function $getTableRowDiffPair(node) {
2385
+ const changeId = node.getChangeId();
2386
+ if (!changeId) return null;
2387
+ const matchingRows = $nodesOfType(TableRowDiffNode).filter((candidate) => candidate.getChangeId() === changeId);
2388
+ if (matchingRows.length !== 2) return null;
2389
+ const remove = matchingRows.find((candidate) => candidate.getDiffType() === "remove");
2390
+ const add = matchingRows.find((candidate) => candidate.getDiffType() === "add");
2391
+ if (!remove || !add) return null;
2392
+ const parent = remove.getParent();
2393
+ if (!$isTableNode(parent) || add.getParent() !== parent || remove.getNextSibling() !== add || !$areTableRowStructuresCompatible(remove, add)) return null;
2394
+ return {
2395
+ add,
2396
+ remove
2397
+ };
2398
+ }
2399
+ function $normalizeTableRowDiffPairs() {
2400
+ const rows = $nodesOfType(TableRowDiffNode);
2401
+ const groups = /* @__PURE__ */ new Map();
2402
+ rows.forEach((row) => {
2403
+ const changeId = row.getChangeId();
2404
+ if (!changeId) return;
2405
+ const group = groups.get(changeId) || [];
2406
+ group.push(row);
2407
+ groups.set(changeId, group);
2408
+ });
2409
+ let changed = false;
2410
+ groups.forEach((group) => {
2411
+ if (group.length === 2 ? $getTableRowDiffPair(group[0]) : null) return;
2412
+ group.forEach((row) => row.setChangeId());
2413
+ changed = true;
2414
+ });
2415
+ return changed;
2416
+ }
2417
+ function registerTableRowDiffNormalization(editor) {
2418
+ let destroyed = false;
2419
+ let scheduled = false;
2420
+ const unregister = editor.registerUpdateListener(() => {
2421
+ if (scheduled || destroyed) return;
2422
+ scheduled = true;
2423
+ queueMicrotask(() => {
2424
+ scheduled = false;
2425
+ if (destroyed) return;
2426
+ if (!editor.getEditorState().read(() => {
2427
+ return $nodesOfType(TableRowDiffNode).some((row) => row.getChangeId() && !$getTableRowDiffPair(row));
2428
+ })) return;
2429
+ editor.update(() => {
2430
+ $normalizeTableRowDiffPairs();
2431
+ });
2432
+ });
2433
+ });
2434
+ return () => {
2435
+ destroyed = true;
2436
+ unregister();
2437
+ };
2438
+ }
2439
+ //#endregion
1751
2440
  //#region src/plugins/litexml/command/index.ts
1752
2441
  const logger$7 = createDebugLogger("plugin", "litexml");
1753
2442
  function toArrayXml(litexml) {
1754
2443
  return Array.isArray(litexml) ? litexml : [litexml];
1755
2444
  }
2445
+ function hasNewIllegalDiffs(previous, projected) {
2446
+ return findNewIllegalDiffPaths(previous, projected);
2447
+ }
2448
+ function projectOperation(dataSource, document, operation) {
2449
+ try {
2450
+ const projected = projectLiteXmlOperation(document, operation, (xml) => dataSource.readLiteXMLToInode(xml));
2451
+ const newIllegalDiffs = hasNewIllegalDiffs(document, projected);
2452
+ if (newIllegalDiffs.length > 0) {
2453
+ logger$7.warn("⚠️ Skipping operation with illegal nested diff", newIllegalDiffs);
2454
+ return null;
2455
+ }
2456
+ return projected;
2457
+ } catch (error) {
2458
+ logger$7.error("❌ Failed to preflight LiteXML operation:", error);
2459
+ return null;
2460
+ }
2461
+ }
2462
+ function toProjectionOperation(operation) {
2463
+ if (operation.action === "remove") return {
2464
+ ...operation,
2465
+ id: charToId(operation.id)
2466
+ };
2467
+ if (operation.action === "insert") return {
2468
+ ...operation,
2469
+ ..."beforeId" in operation ? { beforeId: operation.beforeId === "root" ? "root" : charToId(operation.beforeId) } : { afterId: operation.afterId === "root" ? "root" : charToId(operation.afterId) }
2470
+ };
2471
+ return operation;
2472
+ }
1756
2473
  function tryParseChild(child, editor) {
1757
2474
  try {
1758
2475
  const oldNode = $getNodeByKey(child.id);
@@ -1769,6 +2486,47 @@ function tryParseChild(child, editor) {
1769
2486
  }
1770
2487
  }
1771
2488
  function handleReplaceForApplyDelay(oldNode, newNode, modifyBlockNodes, diffNodeMap, editor) {
2489
+ if ($isTableRowNode(oldNode) || $isTableRowNode(newNode)) {
2490
+ if (!$isTableRowNode(oldNode) || !$isTableRowNode(newNode) || !$isTableNode(oldNode.getParent()) || !$areTableRowStructuresCompatible(oldNode, newNode)) {
2491
+ logger$7.error(`❌ Invalid table row modification for row ${oldNode.getKey()}.`);
2492
+ return;
2493
+ }
2494
+ const changeId = `${oldNode.getKey()}:${newNode.getKey()}`;
2495
+ const removeRow = $createTableRowDiffFromRow(editor, oldNode, "remove", changeId);
2496
+ const addRow = $createTableRowDiffFromRow(editor, newNode, "add", changeId);
2497
+ oldNode.replace(removeRow, false);
2498
+ removeRow.insertAfter(addRow);
2499
+ return;
2500
+ }
2501
+ if ($isTableCellNode(oldNode) && $isTableCellNode(newNode)) {
2502
+ const existingDiff = oldNode.getChildren().find($isDiffNode);
2503
+ if (existingDiff) {
2504
+ if ($isTableCellDiffNode(oldNode)) {
2505
+ existingDiff.clear();
2506
+ newNode.getChildren().forEach((child) => {
2507
+ existingDiff.append($cloneNode(child, editor));
2508
+ });
2509
+ return;
2510
+ }
2511
+ const after = existingDiff.getChildren().find((child) => $isDiffContentNode(child) && child.side === "after");
2512
+ if (existingDiff.diffType === "modify" && after && $isDiffContentNode(after)) {
2513
+ after.clear();
2514
+ newNode.getChildren().forEach((child) => {
2515
+ after.append($cloneNode(child, editor));
2516
+ });
2517
+ return;
2518
+ }
2519
+ }
2520
+ const before = $createDiffContentNode("before");
2521
+ const after = $createDiffContentNode("after");
2522
+ oldNode.getChildren().forEach((child) => before.append($cloneNode(child, editor)));
2523
+ newNode.getChildren().forEach((child) => after.append($cloneNode(child, editor)));
2524
+ const diffNode = $createDiffNode("modify");
2525
+ diffNode.append(before, after);
2526
+ oldNode.clear();
2527
+ oldNode.append(diffNode);
2528
+ return;
2529
+ }
1772
2530
  const oldBlock = $closest(oldNode, (node) => node.isInline() === false);
1773
2531
  if (!oldBlock) throw new Error("Old block node not found for diffing.");
1774
2532
  if ($closest(oldNode, (node) => node.getType() === DiffNode.getType())) {
@@ -1852,8 +2610,15 @@ function registerLiteXMLCommand(editor, dataSource) {
1852
2610
  else acc.push(cur);
1853
2611
  return acc;
1854
2612
  }, []);
2613
+ let projectedDocument = getKernelFromEditor(editor).getDocument("json");
2614
+ const safePayload = resultPayload.filter((item) => {
2615
+ const nextProjection = projectOperation(dataSource, projectedDocument, toProjectionOperation(item));
2616
+ if (!nextProjection) return false;
2617
+ projectedDocument = nextProjection;
2618
+ return true;
2619
+ });
1855
2620
  try {
1856
- resultPayload.forEach((item) => {
2621
+ safePayload.forEach((item) => {
1857
2622
  const { action } = item;
1858
2623
  switch (action) {
1859
2624
  case "modify": {
@@ -1882,14 +2647,39 @@ function registerLiteXMLCommand(editor, dataSource) {
1882
2647
  }
1883
2648
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_APPLY_COMMAND, (payload) => {
1884
2649
  const { litexml, delay } = payload;
1885
- handleModify(editor, dataSource, toArrayXml(litexml), delay);
2650
+ const arrayXml = toArrayXml(litexml);
2651
+ if (!delay) {
2652
+ handleModify(editor, dataSource, arrayXml, delay);
2653
+ return false;
2654
+ }
2655
+ const operation = {
2656
+ action: "modify",
2657
+ litexml
2658
+ };
2659
+ if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleModify(editor, dataSource, arrayXml, delay);
1886
2660
  return false;
1887
2661
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_REMOVE_COMMAND, (payload) => {
1888
2662
  const { id, delay } = payload;
1889
- handleRemove(editor, charToId(id), delay);
2663
+ const key = charToId(id);
2664
+ if (!delay) {
2665
+ handleRemove(editor, key, delay);
2666
+ return false;
2667
+ }
2668
+ const operation = {
2669
+ action: "remove",
2670
+ id
2671
+ };
2672
+ if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation(operation))) handleRemove(editor, key, delay);
1890
2673
  return false;
1891
2674
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_INSERT_COMMAND, (payload) => {
1892
- handleInsert(editor, payload, dataSource);
2675
+ if (!payload.delay) {
2676
+ handleInsert(editor, payload, dataSource);
2677
+ return false;
2678
+ }
2679
+ if (projectOperation(dataSource, getKernelFromEditor(editor).getDocument("json"), toProjectionOperation({
2680
+ action: "insert",
2681
+ ...payload
2682
+ }))) handleInsert(editor, payload, dataSource);
1893
2683
  return false;
1894
2684
  }, COMMAND_PRIORITY_EDITOR));
1895
2685
  }
@@ -1942,9 +2732,32 @@ function handleRemove(editor, key, delay) {
1942
2732
  const node = $getNodeByKey(key);
1943
2733
  if (!node) return;
1944
2734
  if (!delay) {
2735
+ if ($isTableCellNode(node)) {
2736
+ const table = $getTableForCell(node);
2737
+ const columnIndex = $getTableCellColumnIndex(node);
2738
+ const span = node.getColSpan();
2739
+ node.remove();
2740
+ if (table && columnIndex >= 0) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
2741
+ return;
2742
+ }
1945
2743
  node.remove();
1946
2744
  return;
1947
2745
  }
2746
+ if ($isTableCellNode(node)) {
2747
+ const table = $getTableForCell(node);
2748
+ const columnIndex = $getTableCellColumnIndex(node);
2749
+ if (!table || columnIndex < 0) {
2750
+ logger$7.error(`❌ Table cell ${node.getKey()} is not attached to a valid table row.`);
2751
+ return;
2752
+ }
2753
+ const changeId = `${table.getKey()}:column:${columnIndex}`;
2754
+ node.replace($createTableCellDiffFromCell(editor, node, "remove", changeId), false);
2755
+ return;
2756
+ }
2757
+ if ($isTableRowNode(node) && $isTableNode(node.getParent())) {
2758
+ node.replace($createTableRowDiffFromRow(editor, node, "remove"), false);
2759
+ return;
2760
+ }
1948
2761
  if (node.isInline() === false) {
1949
2762
  const originDiffNode = $closest(node, (node) => node.getType() === DiffNode.getType());
1950
2763
  if (originDiffNode) {
@@ -2006,6 +2819,37 @@ function handleInsert(editor, payload, dataSource) {
2006
2819
  else referenceNode = $getNodeByKey(charToId(payload.afterId));
2007
2820
  if (!referenceNode) throw new Error("Reference node not found for insertion.");
2008
2821
  const newNodes = inode.root.children.map((child) => $parseSerializedNodeImpl(child, editor));
2822
+ const referencesTableCell = $isTableCellNode(referenceNode);
2823
+ const insertsOnlyTableCells = newNodes.length > 0 && newNodes.every($isTableCellNode);
2824
+ if (referencesTableCell || insertsOnlyTableCells) {
2825
+ if (!referencesTableCell || !insertsOnlyTableCells) {
2826
+ logger$7.error("❌ Table cells can only be inserted next to another cell in the same row.");
2827
+ return;
2828
+ }
2829
+ const cellReference = referenceNode;
2830
+ const table = $getTableForCell(cellReference);
2831
+ const referenceIndex = $getTableCellColumnIndex(cellReference);
2832
+ if (!table || referenceIndex < 0) {
2833
+ logger$7.error("❌ Table cell insertion requires a valid table parent.");
2834
+ return;
2835
+ }
2836
+ const rowWidthBefore = $getLogicalRowWidth(cellReference);
2837
+ const insertionIndex = isBefore ? referenceIndex : referenceIndex + cellReference.getColSpan();
2838
+ $updateTableWidthsForCellInsertion(table, rowWidthBefore, insertionIndex, newNodes.reduce((total, node) => total + ($isTableCellNode(node) ? node.getColSpan() : 0), 0));
2839
+ let spanOffset = 0;
2840
+ const cells = newNodes.map((node) => {
2841
+ const result = delay ? $createTableCellDiffFromCell(editor, node, "add", `${table.getKey()}:column:${insertionIndex + spanOffset}`) : node;
2842
+ spanOffset += node.getColSpan();
2843
+ return result;
2844
+ });
2845
+ if (isBefore) cells.reverse().forEach((cell) => {
2846
+ referenceNode = referenceNode.insertBefore(cell);
2847
+ });
2848
+ else cells.forEach((cell) => {
2849
+ referenceNode = referenceNode.insertAfter(cell);
2850
+ });
2851
+ return;
2852
+ }
2009
2853
  if (!delay) {
2010
2854
  if (isBefore) newNodes.reverse().forEach((node) => {
2011
2855
  referenceNode = referenceNode.insertBefore(node);
@@ -2015,6 +2859,25 @@ function handleInsert(editor, payload, dataSource) {
2015
2859
  });
2016
2860
  return;
2017
2861
  }
2862
+ const referencesTableRow = $isTableRowNode(referenceNode);
2863
+ const insertsOnlyTableRows = newNodes.every($isTableRowNode);
2864
+ if (referencesTableRow || insertsOnlyTableRows) {
2865
+ if (!referencesTableRow || !insertsOnlyTableRows || !$isTableNode(referenceNode.getParent())) {
2866
+ logger$7.error("❌ Table rows can only be inserted next to another row in the same table.");
2867
+ return;
2868
+ }
2869
+ if (isBefore) newNodes.reverse().forEach((node) => {
2870
+ if (!$isTableRowNode(node)) return;
2871
+ const diffRow = $createTableRowDiffFromRow(editor, node, "add");
2872
+ referenceNode = referenceNode.insertBefore(diffRow);
2873
+ });
2874
+ else newNodes.forEach((node) => {
2875
+ if (!$isTableRowNode(node)) return;
2876
+ const diffRow = $createTableRowDiffFromRow(editor, node, "add");
2877
+ referenceNode = referenceNode.insertAfter(diffRow);
2878
+ });
2879
+ return;
2880
+ }
2018
2881
  if (isBefore) {
2019
2882
  if (referenceNode.isInline() === false) {
2020
2883
  const originDiffNode = $closest(referenceNode, (node) => node.getType() === DiffNode.getType());
@@ -2076,11 +2939,69 @@ function handleInsert(editor, payload, dataSource) {
2076
2939
  }
2077
2940
  //#endregion
2078
2941
  //#region src/plugins/litexml/command/diffCommand.ts
2079
- function doAction(node, action) {
2942
+ function doTableRowAction(editor, node, action) {
2943
+ const pair = $getTableRowDiffPair(node);
2944
+ if (pair) {
2945
+ const survivingRow = action === 1 ? pair.add : pair.remove;
2946
+ const discardedRow = action === 1 ? pair.remove : pair.add;
2947
+ const plainRow = $createPlainTableRowFromDiff(editor, survivingRow);
2948
+ discardedRow.remove();
2949
+ survivingRow.replace(plainRow, false);
2950
+ plainRow.selectStart();
2951
+ return;
2952
+ }
2953
+ if (node.getDiffType() === "remove") {
2954
+ if (action === 1) node.remove();
2955
+ else {
2956
+ const plainRow = $createPlainTableRowFromDiff(editor, node);
2957
+ node.replace(plainRow, false);
2958
+ plainRow.selectStart();
2959
+ }
2960
+ return;
2961
+ }
2962
+ if (action === 1) {
2963
+ const plainRow = $createPlainTableRowFromDiff(editor, node);
2964
+ node.replace(plainRow, false);
2965
+ plainRow.selectStart();
2966
+ } else node.remove();
2967
+ }
2968
+ function doTableCellAction(editor, node, action) {
2969
+ const group = $getTableCellDiffGroup(node);
2970
+ const table = $getTableForCell(node);
2971
+ const columnIndex = $getTableCellColumnIndex(node);
2972
+ const span = node.getColSpan();
2973
+ if (node.getDiffType() === "add" && action === 1 || node.getDiffType() === "remove" && action === 0) {
2974
+ group.forEach((cell) => cell.replace($createPlainTableCellFromDiff(editor, cell), false));
2975
+ return;
2976
+ }
2977
+ if (table && columnIndex >= 0) {
2978
+ const widthCount = table.getColWidths()?.length;
2979
+ $removeTableWidthsForCompleteCellGroup(table, group, columnIndex, span);
2980
+ group.forEach((cell) => cell.remove());
2981
+ if (table.getColWidths()?.length === widthCount) $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span);
2982
+ return;
2983
+ }
2984
+ group.forEach((cell) => cell.remove());
2985
+ }
2986
+ function doAction(editor, node, action) {
2987
+ if ($isTableRowDiffNode(node)) {
2988
+ doTableRowAction(editor, node, action);
2989
+ return;
2990
+ }
2991
+ const parent = node.getParent();
2992
+ if ($isTableCellDiffNode(parent)) {
2993
+ doTableCellAction(editor, parent, action);
2994
+ return;
2995
+ }
2080
2996
  if (node.diffType === "modify") {
2081
2997
  const children = node.getChildren();
2082
- if (action === 1) node.replace(children[1], false).selectEnd();
2083
- else if (action === 0) node.replace(children[0], false).selectEnd();
2998
+ const selectedChild = action === 1 ? children[1] : children[0];
2999
+ if ($isDiffContentNode(selectedChild)) {
3000
+ const parent = node.getParentOrThrow();
3001
+ selectedChild.getChildren().forEach((child) => node.insertBefore(child));
3002
+ node.remove();
3003
+ if ($isElementNode(parent)) parent.selectEnd();
3004
+ } else if (selectedChild) node.replace(selectedChild, false).selectEnd();
2084
3005
  }
2085
3006
  if (node.diffType === "remove") {
2086
3007
  if (action === 1) node.remove();
@@ -2092,7 +3013,10 @@ function doAction(node, action) {
2092
3013
  if (node.diffType === "add") {
2093
3014
  if (action === 1) {
2094
3015
  const children = node.getChildren();
2095
- node.replace(children[0], false).selectEnd();
3016
+ const parent = node.getParentOrThrow();
3017
+ children.forEach((child) => node.insertBefore(child));
3018
+ node.remove();
3019
+ if ($isElementNode(parent)) parent.selectEnd();
2096
3020
  } else if (action === 0) node.remove();
2097
3021
  }
2098
3022
  if (node.diffType === "listItemModify") {
@@ -2138,23 +3062,37 @@ function doAction(node, action) {
2138
3062
  function registerLiteXMLDiffCommand(editor) {
2139
3063
  return mergeRegister(editor.registerCommand(LITEXML_DIFFNODE_COMMAND, (payload) => {
2140
3064
  const { action, nodeKey } = payload;
2141
- const node = editor.getEditorState().read(() => {
2142
- return $getNodeByKey(nodeKey);
2143
- });
2144
- if (!node) return false;
2145
3065
  editor.update(() => {
2146
- doAction(node, action);
3066
+ const node = $getNodeByKey(nodeKey);
3067
+ if (!(node instanceof DiffNode) && !$isTableRowDiffNode(node)) return;
3068
+ doAction(editor, node, action);
2147
3069
  });
2148
3070
  return false;
2149
3071
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(LITEXML_DIFFNODE_ALL_COMMAND, (payload) => {
2150
3072
  const { action } = payload;
2151
- const nodes = editor.getEditorState().read(() => {
2152
- return Array.from(editor._editorState._nodeMap.values()).filter((n) => n instanceof DiffNode && !!n.getParent());
3073
+ const nodeKeys = editor.getEditorState().read(() => {
3074
+ return Array.from(editor._editorState._nodeMap.values()).filter((node) => (node instanceof DiffNode || $isTableRowDiffNode(node)) && !!node.getParent()).map((node) => node.getKey());
2153
3075
  });
2154
- if (!nodes.length) return false;
3076
+ if (!nodeKeys.length) return false;
2155
3077
  editor.update(() => {
2156
- nodes.forEach((node) => {
2157
- doAction(node, action);
3078
+ const handled = /* @__PURE__ */ new Set();
3079
+ nodeKeys.forEach((nodeKey) => {
3080
+ if (handled.has(nodeKey)) return;
3081
+ const node = $getNodeByKey(nodeKey);
3082
+ if (!(node instanceof DiffNode) && !$isTableRowDiffNode(node)) return;
3083
+ const pair = $isTableRowDiffNode(node) ? $getTableRowDiffPair(node) : null;
3084
+ if (pair) {
3085
+ handled.add(pair.remove.getKey());
3086
+ handled.add(pair.add.getKey());
3087
+ } else {
3088
+ const parent = node instanceof DiffNode ? node.getParent() : null;
3089
+ if ($isTableCellDiffNode(parent)) $getTableCellDiffGroup(parent).forEach((cell) => {
3090
+ const diff = cell.getFirstChild();
3091
+ if (diff instanceof DiffNode) handled.add(diff.getKey());
3092
+ });
3093
+ handled.add(nodeKey);
3094
+ }
3095
+ doAction(editor, node, action);
2158
3096
  });
2159
3097
  });
2160
3098
  return false;
@@ -2587,8 +3525,29 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}, parentTyp
2587
3525
  return ret;
2588
3526
  }
2589
3527
  if (child.value.startsWith("<!--") && child.value.endsWith("-->")) return ret;
2590
- const tag = child.value.replaceAll(/^<\/?|>$/g, "");
3528
+ const tag = getHtmlTagName$1(child.value);
2591
3529
  const isEndTag = child.value.startsWith("</");
3530
+ const pairedTag = child.value.match(/^<\s*([a-z0-9-]+)\b[^>]*>([\s\S]*)<\/\s*\1\s*>$/i);
3531
+ if (!isEndTag && pairedTag) {
3532
+ const reader = markdownReaders["html"];
3533
+ const htmlChildren = pairedTag[2] ? [INodeHelper.createTextNode(pairedTag[2])] : [];
3534
+ if (Array.isArray(reader)) for (const element of reader) {
3535
+ const inode = element(child, htmlChildren, index);
3536
+ if (inode) {
3537
+ ret.push(inode);
3538
+ return ret;
3539
+ }
3540
+ }
3541
+ else if (typeof reader === "function") {
3542
+ const inode = reader(child, htmlChildren, index);
3543
+ if (inode) {
3544
+ ret.push(inode);
3545
+ return ret;
3546
+ }
3547
+ }
3548
+ ret.push(INodeHelper.createTextNode(child.value));
3549
+ return ret;
3550
+ }
2592
3551
  if (selfClosingHtmlTags.has(tag)) {
2593
3552
  const reader = markdownReaders["html"];
2594
3553
  if (Array.isArray(reader)) for (const element of reader) {
@@ -2675,6 +3634,9 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}, parentTyp
2675
3634
  }
2676
3635
  }
2677
3636
  }
3637
+ function getHtmlTagName$1(value) {
3638
+ return value.match(/^<\/?\s*([a-z0-9-]+)/i)?.[1]?.toLowerCase() || value.replaceAll(/^<\/?|>$/g, "");
3639
+ }
2678
3640
  function registerDefaultReaders(markdownReaders) {
2679
3641
  if (!markdownReaders["root"]) markdownReaders["root"] = (node, children) => {
2680
3642
  return {
@@ -3552,6 +4514,7 @@ function detectCodeLanguage(code) {
3552
4514
  //#region src/plugins/markdown/plugin/index.ts
3553
4515
  const DEFAULT_PASTE_MARKDOWN_AUTO_CONVERT_THRESHOLD = 5;
3554
4516
  const RICH_HTML_SELECTOR = "strong,em,b,i,h1,h2,h3,h4,h5,h6,ul,ol,table,img,blockquote,pre>code,a[href]";
4517
+ const normalizeMarkdownForClipboard = (markdown) => markdown.replace(/\r?\n$/, "");
3555
4518
  const MARKDOWN_DETECTION_RULES = [
3556
4519
  {
3557
4520
  name: "headers",
@@ -3748,8 +4711,9 @@ const MarkdownPlugin = class extends KernelPlugin {
3748
4711
  if (!clipboardData) return false;
3749
4712
  const selection = $getSelection();
3750
4713
  if (!selection || selection.isCollapsed()) return false;
3751
- const markdown = this.markdownDataSource.write(editor, { selection: true });
3752
- if (!markdown) return false;
4714
+ const rawMarkdown = this.markdownDataSource.write(editor, { selection: true });
4715
+ if (!rawMarkdown) return false;
4716
+ const markdown = normalizeMarkdownForClipboard(rawMarkdown);
3753
4717
  event.preventDefault();
3754
4718
  const data = $getClipboardDataFromSelection(selection);
3755
4719
  setLexicalClipboardDataTransfer(clipboardData, data);
@@ -3835,14 +4799,23 @@ const LitexmlPlugin = class extends KernelPlugin {
3835
4799
  super();
3836
4800
  this.kernel = kernel;
3837
4801
  this.config = config;
3838
- kernel.registerThemes({ diffNode: config?.theme || "editor_diffNode" });
4802
+ kernel.registerThemes({
4803
+ diffNode: config?.theme || "editor_diffNode",
4804
+ tableRowDiff: config?.tableRowDiffTheme || "editor_tableRowDiff",
4805
+ tableCellDiff: config?.tableCellDiffTheme || "editor_tableCellDiff"
4806
+ });
3839
4807
  const litexmlService = new LitexmlService();
3840
4808
  this.service = litexmlService;
3841
4809
  kernel.registerService(ILitexmlService, litexmlService);
3842
4810
  this.datasource = new LitexmlDataSource("litexml", (serviceId) => {
3843
4811
  return kernel.requireService(serviceId);
3844
4812
  }, litexmlService);
3845
- kernel.registerNodes([DiffNode]);
4813
+ kernel.registerNodes([
4814
+ DiffNode,
4815
+ DiffContentNode,
4816
+ TableRowDiffNode,
4817
+ TableCellDiffNode
4818
+ ]);
3846
4819
  kernel.registerDecorator(DiffNode.getType(), {
3847
4820
  queryDOM: (el) => el.querySelector(".toolbar"),
3848
4821
  render: (node, editor) => {
@@ -3854,6 +4827,8 @@ const LitexmlPlugin = class extends KernelPlugin {
3854
4827
  onInit(editor) {
3855
4828
  this.register(registerLiteXMLCommand(editor, this.datasource));
3856
4829
  this.register(registerLiteXMLDiffCommand(editor));
4830
+ this.register(registerLegacyTableCellDiffNormalization(editor));
4831
+ this.register(registerTableRowDiffNormalization(editor));
3857
4832
  this.register(editor.registerNodeTransform(DiffNode, (node) => {
3858
4833
  if (node.diffType === "modify" && node.getChildrenSize() === 1) node.setDiffType("remove");
3859
4834
  }));
@@ -3878,11 +4853,14 @@ const LitexmlPlugin = class extends KernelPlugin {
3878
4853
  const diffNode = node;
3879
4854
  const lines = [];
3880
4855
  switch (diffNode.diffType) {
3881
- case "modify":
3882
- nodeToXML(diffNode.getChildAtIndex(1), lines, indent);
4856
+ case "modify": {
4857
+ const after = diffNode.getChildAtIndex(1);
4858
+ if ($isDiffContentNode(after)) after.getChildren().forEach((child) => nodeToXML(child, lines, indent));
4859
+ else nodeToXML(after, lines, indent);
3883
4860
  break;
4861
+ }
3884
4862
  case "add":
3885
- nodeToXML(diffNode.getChildAtIndex(0), lines, indent);
4863
+ diffNode.getChildren().forEach((child) => nodeToXML(child, lines, indent));
3886
4864
  break;
3887
4865
  case "remove": break;
3888
4866
  case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
@@ -3891,16 +4869,49 @@ const LitexmlPlugin = class extends KernelPlugin {
3891
4869
  }
3892
4870
  return { lines };
3893
4871
  });
4872
+ this.service.registerXMLWriter(TableRowDiffNode.getType(), (node, ctx) => {
4873
+ if (!$isTableRowDiffNode(node)) return false;
4874
+ if (node.getDiffType() === "remove") return { lines: [] };
4875
+ return ctx.createXmlNode("tr", {});
4876
+ });
4877
+ this.service.registerXMLWriter(TableCellDiffNode.getType(), (node, ctx) => {
4878
+ if (!$isTableCellDiffNode(node)) return false;
4879
+ if (node.getDiffType() === "remove") return { lines: [] };
4880
+ const attributes = {};
4881
+ if (node.getColSpan() > 1) attributes.colSpan = String(node.getColSpan());
4882
+ if (node.getRowSpan() > 1) attributes.rowSpan = String(node.getRowSpan());
4883
+ if (node.getBackgroundColor()) attributes.backgroundColor = node.getBackgroundColor();
4884
+ return ctx.createXmlNode("td", attributes);
4885
+ });
3894
4886
  }
3895
4887
  registerMarkdown() {
3896
- this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(DiffNode.getType(), (ctx, node) => {
4888
+ const markdownService = this.kernel.requireService(IMarkdownShortCutService);
4889
+ markdownService?.registerMarkdownWriter(TableCellDiffNode.getType(), (ctx, node) => {
4890
+ if (!$isTableCellDiffNode(node)) return false;
4891
+ if (node.getDiffType() === "remove") return true;
4892
+ if (!node.getNextSibling()) ctx.wrap("|", "|");
4893
+ else ctx.wrap("|", "");
4894
+ return true;
4895
+ });
4896
+ markdownService?.registerMarkdownWriter(TableRowDiffNode.getType(), (ctx, node) => {
4897
+ if (!$isTableRowDiffNode(node)) return false;
4898
+ if (node.getDiffType() === "remove") return true;
4899
+ const parent = node.getParent();
4900
+ if (!$isTableNode(parent)) return true;
4901
+ if (node.getPreviousSiblings().some((sibling) => !$isTableRowDiffNode(sibling) || sibling.getDiffType() !== "remove")) ctx.wrap("", "\n");
4902
+ else ctx.wrap("", `\n${parent.getColWidths()?.map(() => "|:--").join("")}|\n`);
4903
+ });
4904
+ markdownService?.registerMarkdownWriter(DiffNode.getType(), (ctx, node) => {
3897
4905
  const diffNode = node;
3898
4906
  switch (diffNode.diffType) {
3899
- case "modify":
3900
- ctx.processChild(ctx, diffNode.getChildAtIndex(1));
4907
+ case "modify": {
4908
+ const after = diffNode.getChildAtIndex(1);
4909
+ if ($isDiffContentNode(after)) after.getChildren().forEach((child) => ctx.processChild(ctx, child));
4910
+ else ctx.processChild(ctx, after);
3901
4911
  break;
4912
+ }
3902
4913
  case "add":
3903
- ctx.processChild(ctx, diffNode.getChildAtIndex(0));
4914
+ diffNode.getChildren().forEach((child) => ctx.processChild(ctx, child));
3904
4915
  break;
3905
4916
  case "remove": break;
3906
4917
  case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
@@ -4414,6 +5425,84 @@ function runPasteHandlers(ctx, handlers) {
4414
5425
  return false;
4415
5426
  }
4416
5427
  //#endregion
5428
+ //#region src/plugins/common/plugin/progressive-select-all.ts
5429
+ const COLLAPSIBLE_NODE_TYPE = "collapsible";
5430
+ function registerProgressiveSelectAll(editor) {
5431
+ return editor.registerCommand(SELECT_ALL_COMMAND, () => {
5432
+ const selection = $getSelection();
5433
+ if ($isTableSelection(selection)) return $expandTableSelection(selection);
5434
+ if (!$isRangeSelection(selection)) return false;
5435
+ const anchorCell = $findNearestNode(selection.anchor.getNode(), $isTableCellNode);
5436
+ const focusCell = $findNearestNode(selection.focus.getNode(), $isTableCellNode);
5437
+ if (anchorCell && focusCell && anchorCell.is(focusCell)) {
5438
+ const table = $findNearestNode(anchorCell, $isTableNode);
5439
+ if (table) {
5440
+ const tableSelection = $createTableSelection();
5441
+ tableSelection.set(table.getKey(), anchorCell.getKey(), anchorCell.getKey());
5442
+ $setSelection(tableSelection);
5443
+ return true;
5444
+ }
5445
+ }
5446
+ const anchorCollapsible = $findCollapsibleAncestor(selection.anchor.getNode());
5447
+ const focusCollapsible = $findCollapsibleAncestor(selection.focus.getNode());
5448
+ if (!anchorCollapsible || !focusCollapsible || !anchorCollapsible.is(focusCollapsible)) return false;
5449
+ if ($isEntireElementSelected(selection, anchorCollapsible)) $selectDocument();
5450
+ else anchorCollapsible.select(0, anchorCollapsible.getChildrenSize());
5451
+ return true;
5452
+ }, COMMAND_PRIORITY_HIGH);
5453
+ }
5454
+ function $expandTableSelection(selection) {
5455
+ const table = $getNodeByKey(selection.tableKey);
5456
+ if (!$isTableNode(table)) return false;
5457
+ const cells = $getTableCells(table);
5458
+ if (!cells) return false;
5459
+ const selectedCellKeys = new Set(selection.getNodes().filter($isTableCellNode).map((cell) => cell.getKey()));
5460
+ if (cells.keys.every((key) => selectedCellKeys.has(key))) {
5461
+ $selectDocument();
5462
+ return true;
5463
+ }
5464
+ const tableSelection = $createTableSelection();
5465
+ tableSelection.set(table.getKey(), cells.first.getKey(), cells.last.getKey());
5466
+ $setSelection(tableSelection);
5467
+ return true;
5468
+ }
5469
+ function $getTableCells(table) {
5470
+ if (!$isTableNode(table)) return null;
5471
+ const [tableMap] = $computeTableMapSkipCellCheck(table, null, null);
5472
+ const entries = tableMap.flat().filter(Boolean);
5473
+ const first = entries[0]?.cell;
5474
+ const last = entries.at(-1)?.cell;
5475
+ if (!first || !last) return null;
5476
+ return {
5477
+ first,
5478
+ keys: Array.from(new Set(entries.map(({ cell }) => cell.getKey()))),
5479
+ last
5480
+ };
5481
+ }
5482
+ function $findNearestNode(node, predicate) {
5483
+ let current = node;
5484
+ while (current) {
5485
+ if (predicate(current)) return current;
5486
+ current = current.getParent();
5487
+ }
5488
+ return null;
5489
+ }
5490
+ function $findCollapsibleAncestor(node) {
5491
+ return $findNearestNode(node, (candidate) => $isElementNode(candidate) && candidate.getType() === COLLAPSIBLE_NODE_TYPE);
5492
+ }
5493
+ function $isEntireElementSelected(selection, element) {
5494
+ const startOffset = 0;
5495
+ const endOffset = element.getChildrenSize();
5496
+ return $isPointAt(selection.anchor, element, startOffset) && $isPointAt(selection.focus, element, endOffset) || $isPointAt(selection.focus, element, startOffset) && $isPointAt(selection.anchor, element, endOffset);
5497
+ }
5498
+ function $isPointAt(point, element, offset) {
5499
+ return point.key === element.getKey() && point.offset === offset && point.type === "element";
5500
+ }
5501
+ function $selectDocument() {
5502
+ const root = $getRoot();
5503
+ root.select(0, root.getChildrenSize());
5504
+ }
5505
+ //#endregion
4417
5506
  //#region src/plugins/common/plugin/register.ts
4418
5507
  const logger$3 = createDebugLogger("plugin", "common");
4419
5508
  function resolveElement(element, isBackward, focusOffset) {
@@ -4429,7 +5518,9 @@ function resolveElement(element, isBackward, focusOffset) {
4429
5518
  block = parent;
4430
5519
  }
4431
5520
  }
4432
- return block.getChildAtIndex(isBackward ? offset - 1 : offset);
5521
+ const childIndex = isBackward ? offset - 1 : offset;
5522
+ if (childIndex < 0 || childIndex >= block.getChildrenSize()) return null;
5523
+ return block.getChildAtIndex(childIndex);
4433
5524
  }
4434
5525
  function isCodeNodeLastLine(focusNode) {
4435
5526
  if (!$isCodeHighlightNode(focusNode)) return false;
@@ -4474,7 +5565,55 @@ function $isSelectionAtEndOfRoot(selection) {
4474
5565
  const focus = selection.focus;
4475
5566
  return focus.key === "root" && focus.offset === $getRoot().getChildrenSize();
4476
5567
  }
4477
- function registerHeaderBackspace(editor) {
5568
+ function $isSelectionInList(selection) {
5569
+ return Boolean($closest(selection.focus.getNode(), (node) => $isListNode(node) || $isListItemNode(node)));
5570
+ }
5571
+ function $getLeadingQuoteNode(selection) {
5572
+ const anchor = selection.anchor;
5573
+ if (anchor.offset !== 0) return false;
5574
+ let node = anchor.getNode();
5575
+ let current = node;
5576
+ let quoteNode = null;
5577
+ while (current) {
5578
+ if ($isQuoteNode(current)) {
5579
+ quoteNode = current;
5580
+ break;
5581
+ }
5582
+ current = current.getParent();
5583
+ }
5584
+ if (!quoteNode) return false;
5585
+ while (node !== quoteNode) {
5586
+ if (node.getIndexWithinParent() !== 0) return false;
5587
+ const parent = node.getParent();
5588
+ if (!parent) return false;
5589
+ node = parent;
5590
+ }
5591
+ return quoteNode;
5592
+ }
5593
+ function $unwrapQuoteNode(quoteNode) {
5594
+ const children = quoteNode.getChildren();
5595
+ if (children.length === 0) {
5596
+ const paragraphNode = $createParagraphNode();
5597
+ quoteNode.replace(paragraphNode);
5598
+ paragraphNode.select(0, 0);
5599
+ return;
5600
+ }
5601
+ let firstNode = null;
5602
+ for (const child of children) {
5603
+ if ($isElementNode(child)) {
5604
+ quoteNode.insertBefore(child);
5605
+ firstNode ||= child;
5606
+ continue;
5607
+ }
5608
+ const paragraphNode = $createParagraphNode();
5609
+ paragraphNode.append(child);
5610
+ quoteNode.insertBefore(paragraphNode);
5611
+ firstNode ||= paragraphNode;
5612
+ }
5613
+ quoteNode.remove();
5614
+ if ($isElementNode(firstNode)) firstNode.select(0, 0);
5615
+ }
5616
+ function registerBlockBackspace(editor) {
4478
5617
  return editor.registerCommand(KEY_BACKSPACE_COMMAND, (payload) => {
4479
5618
  const headingNode = editor.getEditorState().read(() => {
4480
5619
  const selection = $getSelection();
@@ -4502,6 +5641,22 @@ function registerHeaderBackspace(editor) {
4502
5641
  });
4503
5642
  return true;
4504
5643
  }
5644
+ if (editor.getEditorState().read(() => {
5645
+ const selection = $getSelection();
5646
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
5647
+ return $getLeadingQuoteNode(selection);
5648
+ })) {
5649
+ payload.stopImmediatePropagation();
5650
+ payload.preventDefault();
5651
+ payload.stopPropagation();
5652
+ editor.update(() => {
5653
+ const selection = $getSelection();
5654
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) return;
5655
+ const leadingQuoteNode = $getLeadingQuoteNode(selection);
5656
+ if (leadingQuoteNode) $unwrapQuoteNode(leadingQuoteNode);
5657
+ });
5658
+ return true;
5659
+ }
4505
5660
  return false;
4506
5661
  }, COMMAND_PRIORITY_NORMAL);
4507
5662
  }
@@ -4556,6 +5711,7 @@ function registerRichKeydown(editor, kernel, options) {
4556
5711
  return true;
4557
5712
  }
4558
5713
  } else if ($isRangeSelection(selection)) {
5714
+ if ($isSelectionInList(selection)) return false;
4559
5715
  const possibleNode = $getAdjacentNode(selection.focus, true);
4560
5716
  const upblock = possibleNode || $getDownUpNode(selection.focus, true);
4561
5717
  if (!event.shiftKey && $isDecoratorNode(possibleNode)) {
@@ -4625,6 +5781,7 @@ function registerRichKeydown(editor, kernel, options) {
4625
5781
  event.preventDefault();
4626
5782
  return true;
4627
5783
  }
5784
+ if ($isSelectionInList(selection)) return false;
4628
5785
  const possibleNode = $getAdjacentNode(selection.focus, false);
4629
5786
  const upblock = possibleNode || $getDownUpNode(selection.focus, false);
4630
5787
  if (!event.shiftKey && $isDecoratorNode(possibleNode)) {
@@ -4919,7 +6076,7 @@ const CommonPlugin = class extends KernelPlugin {
4919
6076
  if (this.config?.pasteAsPlainText) return runPasteHandlers(ctx, [handleFilePaste, handlePlainTextPaste]);
4920
6077
  return false;
4921
6078
  }, COMMAND_PRIORITY_CRITICAL));
4922
- this.registerClears(registerRichText(editor), CAN_USE_DOM ? registerDragonSupport(editor) : noop, registerHistory(editor, this.kernel.getHistoryState(), 300), registerHeaderBackspace(editor), registerRichKeydown(editor, this.kernel, { enableHotkey: this.config?.enableHotkey }), registerCommands(editor), registerBreakLineClick(editor), registerCursorNode(editor), registerLastElement(editor), editor.registerCommand(INSERT_LINE_BREAK_COMMAND, () => {
6079
+ this.registerClears(registerRichText(editor), registerProgressiveSelectAll(editor), CAN_USE_DOM ? registerDragonSupport(editor) : noop, registerHistory(editor, this.kernel.getHistoryState(), 300), registerBlockBackspace(editor), registerRichKeydown(editor, this.kernel, { enableHotkey: this.config?.enableHotkey }), registerCommands(editor), registerBreakLineClick(editor), registerCursorNode(editor), registerLastElement(editor), editor.registerCommand(INSERT_LINE_BREAK_COMMAND, () => {
4923
6080
  const selection = $getSelection();
4924
6081
  if (!$isRangeSelection(selection)) return false;
4925
6082
  const { inCell, inTable } = $isCursorInTable(selection);
@@ -5793,6 +6950,62 @@ function modeMatch(mode = "") {
5793
6950
  return LANGUAGES.find((m) => m.value === mode || m.ext?.includes(mode))?.value || "plain";
5794
6951
  }
5795
6952
  //#endregion
6953
+ //#region src/plugins/codemirror-block/service/index.ts
6954
+ const ICodemirrorEditLockService = genServiceId("CodemirrorEditLockService");
6955
+ var CodemirrorEditLockService = class {
6956
+ constructor() {
6957
+ this.providerUnsubscribers = /* @__PURE__ */ new Map();
6958
+ this.providers = /* @__PURE__ */ new Set();
6959
+ this.subscribers = /* @__PURE__ */ new Set();
6960
+ }
6961
+ acquireLock(key, label) {
6962
+ if (this.getRemoteLockOwner(key)) return false;
6963
+ const acquiredProviders = [];
6964
+ for (const provider of this.providers) {
6965
+ if (!provider.acquireLock(key, label)) {
6966
+ acquiredProviders.forEach((acquiredProvider) => acquiredProvider.releaseLock(key));
6967
+ this.notify();
6968
+ return false;
6969
+ }
6970
+ acquiredProviders.push(provider);
6971
+ }
6972
+ this.notify();
6973
+ return true;
6974
+ }
6975
+ getRemoteLockOwner(key) {
6976
+ for (const provider of this.providers) {
6977
+ const owner = provider.getRemoteLockOwner(key);
6978
+ if (owner) return owner;
6979
+ }
6980
+ return null;
6981
+ }
6982
+ registerProvider(provider) {
6983
+ this.providers.add(provider);
6984
+ this.providerUnsubscribers.set(provider, provider.subscribe(() => this.notify()));
6985
+ this.notify();
6986
+ return () => {
6987
+ this.providerUnsubscribers.get(provider)?.();
6988
+ this.providerUnsubscribers.delete(provider);
6989
+ this.providers.delete(provider);
6990
+ this.notify();
6991
+ };
6992
+ }
6993
+ releaseLock(key) {
6994
+ this.providers.forEach((provider) => provider.releaseLock(key));
6995
+ this.notify();
6996
+ }
6997
+ subscribe(listener) {
6998
+ this.subscribers.add(listener);
6999
+ listener();
7000
+ return () => {
7001
+ this.subscribers.delete(listener);
7002
+ };
7003
+ }
7004
+ notify() {
7005
+ this.subscribers.forEach((listener) => listener());
7006
+ }
7007
+ };
7008
+ //#endregion
5796
7009
  //#region src/plugins/codemirror-block/plugin/index.ts
5797
7010
  const CodemirrorPlugin = class extends KernelPlugin {
5798
7011
  static {
@@ -5803,6 +7016,7 @@ const CodemirrorPlugin = class extends KernelPlugin {
5803
7016
  this.kernel = kernel;
5804
7017
  kernel.registerNodes([CodeMirrorNode]);
5805
7018
  kernel.registerThemes({ hr: config?.theme || "" });
7019
+ kernel.registerServiceHotReload(ICodemirrorEditLockService, new CodemirrorEditLockService());
5806
7020
  this.registerDecorator(kernel, "codemirror", (node, editor) => {
5807
7021
  return config?.decorator ? config.decorator(node, editor) : null;
5808
7022
  });
@@ -6762,6 +7976,263 @@ const ImagePlugin = class extends KernelPlugin {
6762
7976
  }
6763
7977
  };
6764
7978
  //#endregion
7979
+ //#region src/plugins/link/service/i-link-service.ts
7980
+ const ILinkService = genServiceId("LinkService");
7981
+ const DEFAULT_LABELS = {
7982
+ convertToBlockCard: "Convert to block card",
7983
+ convertToCard: "Convert to card",
7984
+ convertToIframe: "Convert to iframe",
7985
+ convertToLink: "Convert to link",
7986
+ convertToSchema: "Convert to schema"
7987
+ };
7988
+ var LinkProtocolPolicy = class {
7989
+ constructor() {
7990
+ this.allowedProtocols = /* @__PURE__ */ new Set([
7991
+ "http:",
7992
+ "https:",
7993
+ "mailto:",
7994
+ "sms:",
7995
+ "tel:"
7996
+ ]);
7997
+ }
7998
+ getAllowedProtocols() {
7999
+ return this.allowedProtocols;
8000
+ }
8001
+ setAllowedProtocols(protocols) {
8002
+ this.allowedProtocols = new Set(protocols.map(normalizeProtocol));
8003
+ }
8004
+ };
8005
+ var LinkRuleRegistry = class {
8006
+ constructor() {
8007
+ this.embedRules = [];
8008
+ this.schemaRules = [];
8009
+ }
8010
+ getEmbedRule(url, context) {
8011
+ return this.embedRules.find((rule) => rule.match(url, context)) || null;
8012
+ }
8013
+ getSchemaRule(url, context, parseSchemaUrl) {
8014
+ const schema = context.schema === void 0 ? parseSchemaUrl(url) : context.schema;
8015
+ return this.schemaRules.find((rule) => rule.match(url, {
8016
+ ...context,
8017
+ schema
8018
+ })) || null;
8019
+ }
8020
+ getSchemaRuleById(schemaType) {
8021
+ return this.schemaRules.find((rule) => rule.id === schemaType) || null;
8022
+ }
8023
+ setEmbedRules(rules = []) {
8024
+ this.embedRules = rules;
8025
+ }
8026
+ setSchemaRules(rules = []) {
8027
+ this.schemaRules = rules;
8028
+ }
8029
+ };
8030
+ var LegacySchemaLinkRegistry = class {
8031
+ constructor() {
8032
+ this.protocols = /* @__PURE__ */ new Set();
8033
+ }
8034
+ hasSchemaLinkRenderer(url) {
8035
+ try {
8036
+ const { protocol } = new URL(url);
8037
+ return this.protocols.has(protocol);
8038
+ } catch {
8039
+ return false;
8040
+ }
8041
+ }
8042
+ setSchemaLinkRenderers(renderers = []) {
8043
+ this.protocols = new Set(renderers.map(({ protocol }) => normalizeProtocol(protocol)));
8044
+ }
8045
+ };
8046
+ var LinkToolbarController = class {
8047
+ constructor() {
8048
+ this.baseEnabled = true;
8049
+ this.labels = DEFAULT_LABELS;
8050
+ this.toolbarActions = [];
8051
+ this.toolbarSuppressions = /* @__PURE__ */ new Set();
8052
+ }
8053
+ get enabled() {
8054
+ return this.baseEnabled && this.toolbarSuppressions.size === 0;
8055
+ }
8056
+ getLabels() {
8057
+ return this.labels;
8058
+ }
8059
+ getToolbarActions(context) {
8060
+ return this.toolbarActions.filter((action) => action.visible?.(context) ?? true);
8061
+ }
8062
+ setLabels(labels) {
8063
+ this.labels = {
8064
+ ...DEFAULT_LABELS,
8065
+ ...labels
8066
+ };
8067
+ }
8068
+ setToolbarActions(actions = []) {
8069
+ this.toolbarActions = actions;
8070
+ }
8071
+ setEnabled(enable) {
8072
+ const previousEnabled = this.enabled;
8073
+ this.baseEnabled = enable;
8074
+ return previousEnabled !== this.enabled;
8075
+ }
8076
+ suppress(reason = "unknown") {
8077
+ const previousEnabled = this.enabled;
8078
+ const token = Symbol(reason);
8079
+ this.toolbarSuppressions.add(token);
8080
+ return {
8081
+ changed: previousEnabled !== this.enabled,
8082
+ token
8083
+ };
8084
+ }
8085
+ restore(token) {
8086
+ const previousEnabled = this.enabled;
8087
+ if (!this.toolbarSuppressions.delete(token)) return false;
8088
+ return previousEnabled !== this.enabled;
8089
+ }
8090
+ };
8091
+ var LinkService = class extends EventEmitter {
8092
+ constructor(..._args) {
8093
+ super(..._args);
8094
+ this.cardMetadataLoadingKeys = /* @__PURE__ */ new Set();
8095
+ this.protocolPolicy = new LinkProtocolPolicy();
8096
+ this.legacySchemaLinkRegistry = new LegacySchemaLinkRegistry();
8097
+ this.ruleRegistry = new LinkRuleRegistry();
8098
+ this.toolbarController = new LinkToolbarController();
8099
+ this.toolbarItems = /* @__PURE__ */ new Map();
8100
+ }
8101
+ get enableLinkToolbar() {
8102
+ return this.toolbarController.enabled;
8103
+ }
8104
+ getAllowedProtocols() {
8105
+ return this.protocolPolicy.getAllowedProtocols();
8106
+ }
8107
+ getEmbedRule(url, context) {
8108
+ return this.ruleRegistry.getEmbedRule(url, context);
8109
+ }
8110
+ getLabels() {
8111
+ return this.toolbarController.getLabels();
8112
+ }
8113
+ hasSchemaLinkRenderer(url) {
8114
+ return this.legacySchemaLinkRegistry.hasSchemaLinkRenderer(url);
8115
+ }
8116
+ isCardMetadataLoading(nodeKey) {
8117
+ return this.cardMetadataLoadingKeys.has(nodeKey);
8118
+ }
8119
+ setCardMetadataLoading(nodeKey, loading) {
8120
+ if (!(loading ? !this.cardMetadataLoadingKeys.has(nodeKey) : this.cardMetadataLoadingKeys.has(nodeKey))) return;
8121
+ if (loading) this.cardMetadataLoadingKeys.add(nodeKey);
8122
+ else this.cardMetadataLoadingKeys.delete(nodeKey);
8123
+ this.emit("cardMetadataChange");
8124
+ }
8125
+ getSchemaRule(url, context) {
8126
+ return this.ruleRegistry.getSchemaRule(url, context, (url) => this.parseSchemaUrl(url));
8127
+ }
8128
+ getToolbarActions(context) {
8129
+ return this.toolbarController.getToolbarActions(context);
8130
+ }
8131
+ parseSchemaUrl(url) {
8132
+ try {
8133
+ const parsedUrl = new URL(url);
8134
+ return {
8135
+ hash: parsedUrl.hash,
8136
+ host: parsedUrl.host,
8137
+ params: Object.fromEntries(parsedUrl.searchParams.entries()),
8138
+ pathname: parsedUrl.pathname,
8139
+ protocol: parsedUrl.protocol,
8140
+ raw: url,
8141
+ search: parsedUrl.search
8142
+ };
8143
+ } catch {
8144
+ return null;
8145
+ }
8146
+ }
8147
+ updateConfig(config) {
8148
+ this.setAllowedProtocols([
8149
+ "http:",
8150
+ "https:",
8151
+ "mailto:",
8152
+ "sms:",
8153
+ "tel:",
8154
+ ...config?.schemaLinkRenderers?.map(({ protocol }) => protocol) || [],
8155
+ ...config?.allowedProtocols || []
8156
+ ]);
8157
+ this.setEmbedRules(config?.linkEmbedRules);
8158
+ this.setLabels(config?.labels);
8159
+ this.setSchemaLinkRenderers(config?.schemaLinkRenderers);
8160
+ this.setSchemaRules([...config?.schemaRules || [], ...createLegacySchemaRules(config?.schemaLinkRenderers)]);
8161
+ this.setToolbarActions(config?.toolbarActions);
8162
+ }
8163
+ setAllowedProtocols(protocols) {
8164
+ this.protocolPolicy.setAllowedProtocols(protocols);
8165
+ }
8166
+ setEmbedRules(rules = []) {
8167
+ this.ruleRegistry.setEmbedRules(rules);
8168
+ }
8169
+ setLabels(labels) {
8170
+ this.toolbarController.setLabels(labels);
8171
+ }
8172
+ setSchemaLinkRenderers(renderers = []) {
8173
+ this.legacySchemaLinkRegistry.setSchemaLinkRenderers(renderers);
8174
+ }
8175
+ setSchemaRules(rules = []) {
8176
+ this.ruleRegistry.setSchemaRules(rules);
8177
+ }
8178
+ setToolbarActions(actions = []) {
8179
+ this.toolbarController.setToolbarActions(actions);
8180
+ this.emit("change");
8181
+ }
8182
+ getToolbarItems(context) {
8183
+ return Array.from(this.toolbarItems.values()).filter((item) => item.when ? item.when(context) : true).sort((a, b) => (a.order || 0) - (b.order || 0));
8184
+ }
8185
+ registerToolbarItem(item) {
8186
+ this.toolbarItems.set(item.key, item);
8187
+ this.emit("change");
8188
+ return () => {
8189
+ this.toolbarItems.delete(item.key);
8190
+ this.emit("change");
8191
+ };
8192
+ }
8193
+ setLinkToolbar(enable) {
8194
+ if (this.toolbarController.setEnabled(enable)) this.emit("linkToolbarChange", enable);
8195
+ }
8196
+ suppressLinkToolbar(reason = "unknown") {
8197
+ const { changed, token } = this.toolbarController.suppress(reason);
8198
+ if (changed) this.emit("linkToolbarChange", this.toolbarController.enabled);
8199
+ return token;
8200
+ }
8201
+ restoreLinkToolbar(token) {
8202
+ if (this.toolbarController.restore(token)) this.emit("linkToolbarChange", this.toolbarController.enabled);
8203
+ }
8204
+ subscribe(listener) {
8205
+ this.on("change", listener);
8206
+ return () => {
8207
+ this.off("change", listener);
8208
+ };
8209
+ }
8210
+ subscribeCardMetadata(listener) {
8211
+ this.on("cardMetadataChange", listener);
8212
+ return () => {
8213
+ this.off("cardMetadataChange", listener);
8214
+ };
8215
+ }
8216
+ };
8217
+ function normalizeProtocol(protocol) {
8218
+ return `${protocol.split(":")[0]}:`;
8219
+ }
8220
+ function createLegacySchemaRules(renderers = []) {
8221
+ return renderers.map(({ protocol }) => {
8222
+ const normalizedProtocol = normalizeProtocol(protocol);
8223
+ return {
8224
+ id: normalizedProtocol.replace(":", ""),
8225
+ match: (url) => {
8226
+ try {
8227
+ return new URL(url).protocol === normalizedProtocol;
8228
+ } catch {
8229
+ return false;
8230
+ }
8231
+ }
8232
+ };
8233
+ });
8234
+ }
8235
+ //#endregion
6765
8236
  //#region src/plugins/link/node/LinkNode.ts
6766
8237
  const logger = createDebugLogger("plugin", "link");
6767
8238
  const SUPPORTED_URL_PROTOCOLS$1 = /* @__PURE__ */ new Set([
@@ -6803,7 +8274,7 @@ var LinkNode = class LinkNode extends ElementNode {
6803
8274
  event.target.classList.add("hover");
6804
8275
  editor.dispatchCommand(HOVER_LINK_COMMAND, {
6805
8276
  event,
6806
- linkNode: this
8277
+ node: this
6807
8278
  });
6808
8279
  }
6809
8280
  });
@@ -6817,7 +8288,8 @@ var LinkNode = class LinkNode extends ElementNode {
6817
8288
  }
6818
8289
  updateLinkDOM(prevNode, anchor, _config) {
6819
8290
  if (isHTMLAnchorElement(anchor)) {
6820
- if (!prevNode || prevNode.__url !== this.__url) anchor.href = this.sanitizeUrl(this.__url);
8291
+ const linkService = getLinkServiceFromConfig(_config);
8292
+ if (!prevNode || prevNode.__url !== this.__url) anchor.href = this.sanitizeUrl(this.__url, linkService?.getAllowedProtocols());
6821
8293
  for (const attr of [
6822
8294
  "target",
6823
8295
  "rel",
@@ -6848,11 +8320,11 @@ var LinkNode = class LinkNode extends ElementNode {
6848
8320
  updateFromJSON(serializedNode) {
6849
8321
  return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setRel(serializedNode.rel || null).setTarget(serializedNode.target || null).setTitle(serializedNode.title || null);
6850
8322
  }
6851
- sanitizeUrl(url) {
8323
+ sanitizeUrl(url, allowedProtocols = SUPPORTED_URL_PROTOCOLS$1) {
6852
8324
  url = formatUrl(url);
6853
8325
  try {
6854
8326
  const parsedUrl = new URL(formatUrl(url));
6855
- if (!SUPPORTED_URL_PROTOCOLS$1.has(parsedUrl.protocol)) return "about:blank";
8327
+ if (!allowedProtocols.has(parsedUrl.protocol)) return "about:blank";
6856
8328
  } catch {
6857
8329
  return url;
6858
8330
  }
@@ -6932,7 +8404,13 @@ var LinkNode = class LinkNode extends ElementNode {
6932
8404
  isWebSiteURI() {
6933
8405
  return this.__url.startsWith("https://") || this.__url.startsWith("http://");
6934
8406
  }
8407
+ getDOMSlot(element) {
8408
+ return super.getDOMSlot(element);
8409
+ }
6935
8410
  };
8411
+ function getLinkServiceFromConfig(config) {
8412
+ return getKernelFromEditorConfig(config)?.requireService(ILinkService) || null;
8413
+ }
6936
8414
  function $convertAnchorElement(domNode) {
6937
8415
  let node = null;
6938
8416
  if (isHTMLAnchorElement(domNode)) {
@@ -6960,7 +8438,7 @@ function $createLinkNode(url = "", attributes) {
6960
8438
  * @returns true if node is a LinkNode, false otherwise.
6961
8439
  */
6962
8440
  function $isLinkNode(node) {
6963
- return node instanceof LinkNode;
8441
+ return node instanceof LinkNode || node?.getType?.() === "link" || node?.getType?.() === "autolink";
6964
8442
  }
6965
8443
  var AutoLinkNode = class AutoLinkNode extends LinkNode {
6966
8444
  constructor(url = "", attributes = {}, key) {
@@ -7208,6 +8686,7 @@ function formatUrl(url) {
7208
8686
  //#region src/plugins/link/command/index.ts
7209
8687
  const INSERT_LINK_COMMAND = createCommand("INSERT_LINK_COMMAND");
7210
8688
  const UPDATE_LINK_TEXT_COMMAND = createCommand("UPDATE_LINK_TEXT_COMMAND");
8689
+ const UNLINK_LINK_COMMAND = createCommand("UNLINK_LINK_COMMAND");
7211
8690
  function registerLinkCommand(editor) {
7212
8691
  return mergeRegister(editor.registerCommand(INSERT_LINK_COMMAND, (payload) => {
7213
8692
  const { url, title = url } = payload;
@@ -7217,38 +8696,564 @@ function registerLinkCommand(editor) {
7217
8696
  linkNode.append(textNode);
7218
8697
  $insertNodes([linkNode]);
7219
8698
  });
7220
- return false;
7221
- }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UPDATE_LINK_TEXT_COMMAND, (payload) => {
7222
- const { key, text } = payload;
7223
- editor.update(() => {
7224
- const linkNode = $getNodeByKey(key);
7225
- if (linkNode) {
7226
- const newLinkNode = $createLinkNode(linkNode.getURL(), { title: text });
7227
- const textNode = $createTextNode(text);
7228
- newLinkNode.append(textNode);
7229
- linkNode?.replace(newLinkNode);
7230
- newLinkNode.select(1);
8699
+ return false;
8700
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UPDATE_LINK_TEXT_COMMAND, (payload) => {
8701
+ const { key, text } = payload;
8702
+ editor.update(() => {
8703
+ const linkNode = $getNodeByKey(key);
8704
+ if (linkNode) {
8705
+ const newLinkNode = $createLinkNode(linkNode.getURL(), { title: text });
8706
+ const textNode = $createTextNode(text);
8707
+ newLinkNode.append(textNode);
8708
+ linkNode?.replace(newLinkNode);
8709
+ newLinkNode.select(1);
8710
+ }
8711
+ });
8712
+ return false;
8713
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UNLINK_LINK_COMMAND, ({ key }) => {
8714
+ editor.update(() => {
8715
+ const node = $getNodeByKey(key);
8716
+ if (!$isLinkNode(node)) return;
8717
+ let selection = $getSelection();
8718
+ if (!selection || !$isRangeSelection(selection)) {
8719
+ $setSelection($createRangeSelection());
8720
+ selection = $getSelection();
8721
+ }
8722
+ const first = node.getFirstDescendant();
8723
+ const last = node.getLastDescendant();
8724
+ if (selection && $isRangeSelection(selection) && $isTextNode(first) && $isTextNode(last)) {
8725
+ selection.anchor.set(first.getKey(), 0, "text");
8726
+ selection.focus.set(last.getKey(), last.getTextContentSize(), "text");
8727
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
8728
+ return;
8729
+ }
8730
+ const children = node.getChildren();
8731
+ for (const child of children) node.insertBefore(child);
8732
+ node.remove();
8733
+ });
8734
+ return true;
8735
+ }, COMMAND_PRIORITY_EDITOR));
8736
+ }
8737
+ createCommand();
8738
+ var LinkCardNode = class LinkCardNode extends DecoratorNode {
8739
+ static getType() {
8740
+ return "link-card";
8741
+ }
8742
+ static clone(node) {
8743
+ return new LinkCardNode(node.__url, node.__title, node.__icon, node.__description, node.__openTarget, node.__key);
8744
+ }
8745
+ static importJSON(serializedNode) {
8746
+ return $createLinkCardNode({
8747
+ description: serializedNode.description,
8748
+ icon: serializedNode.icon,
8749
+ openTarget: serializedNode.openTarget,
8750
+ title: serializedNode.title,
8751
+ url: serializedNode.url
8752
+ }).updateFromJSON(serializedNode);
8753
+ }
8754
+ static importDOM() {
8755
+ return { a: (node) => {
8756
+ if (node instanceof HTMLAnchorElement && node.dataset.linkCard === "true" && node.dataset.linkCardLayout !== "block") return {
8757
+ conversion: $convertLinkCardElement,
8758
+ priority: 2
8759
+ };
8760
+ return null;
8761
+ } };
8762
+ }
8763
+ constructor(url, title, icon, description, openTarget, key) {
8764
+ super(key);
8765
+ this.__url = url;
8766
+ this.__title = title || url;
8767
+ this.__icon = icon || "";
8768
+ this.__description = description || "";
8769
+ this.__openTarget = openTarget ?? "_blank";
8770
+ }
8771
+ createDOM(config, editor) {
8772
+ const element = document.createElement("span");
8773
+ element.dataset.linkCard = "true";
8774
+ addClassNamesToElement(element, config.theme.linkCard);
8775
+ element.addEventListener("mouseenter", (event) => {
8776
+ if (event.target instanceof HTMLElement) {
8777
+ event.target.classList.add("hover");
8778
+ editor.dispatchCommand(HOVER_LINK_COMMAND, {
8779
+ event,
8780
+ node: this
8781
+ });
8782
+ }
8783
+ });
8784
+ element.addEventListener("mouseleave", (event) => {
8785
+ if (event.target instanceof HTMLElement) {
8786
+ event.target.classList.remove("hover");
8787
+ editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
8788
+ }
8789
+ });
8790
+ return element;
8791
+ }
8792
+ updateDOM() {
8793
+ return false;
8794
+ }
8795
+ isInline() {
8796
+ return true;
8797
+ }
8798
+ isKeyboardSelectable() {
8799
+ return true;
8800
+ }
8801
+ exportDOM() {
8802
+ const element = document.createElement("a");
8803
+ element.dataset.linkCard = "true";
8804
+ element.dataset.icon = this.getIcon();
8805
+ element.dataset.description = this.getDescription();
8806
+ if (this.getOpenTarget()) element.target = this.getOpenTarget();
8807
+ element.href = this.getURL();
8808
+ element.textContent = this.getTitle();
8809
+ return { element };
8810
+ }
8811
+ exportJSON() {
8812
+ return {
8813
+ ...super.exportJSON(),
8814
+ description: this.getDescription(),
8815
+ icon: this.getIcon(),
8816
+ openTarget: this.getOpenTarget(),
8817
+ title: this.getTitle(),
8818
+ url: this.getURL()
8819
+ };
8820
+ }
8821
+ updateFromJSON(serializedNode) {
8822
+ return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setTitle(serializedNode.title).setIcon(serializedNode.icon).setDescription(serializedNode.description).setOpenTarget(serializedNode.openTarget ?? "_blank");
8823
+ }
8824
+ getTextContent() {
8825
+ return this.getTitle();
8826
+ }
8827
+ getURL() {
8828
+ return this.getLatest().__url;
8829
+ }
8830
+ setURL(url) {
8831
+ const writable = this.getWritable();
8832
+ writable.__url = url;
8833
+ return writable;
8834
+ }
8835
+ getTitle() {
8836
+ return this.getLatest().__title || this.getURL();
8837
+ }
8838
+ setTitle(title) {
8839
+ const writable = this.getWritable();
8840
+ writable.__title = title || this.getURL();
8841
+ return writable;
8842
+ }
8843
+ getIcon() {
8844
+ return this.getLatest().__icon;
8845
+ }
8846
+ setIcon(icon) {
8847
+ const writable = this.getWritable();
8848
+ writable.__icon = icon || "";
8849
+ return writable;
8850
+ }
8851
+ getDescription() {
8852
+ return this.getLatest().__description;
8853
+ }
8854
+ setDescription(description) {
8855
+ const writable = this.getWritable();
8856
+ writable.__description = description || "";
8857
+ return writable;
8858
+ }
8859
+ getOpenTarget() {
8860
+ return this.getLatest().__openTarget;
8861
+ }
8862
+ setOpenTarget(openTarget) {
8863
+ const writable = this.getWritable();
8864
+ writable.__openTarget = openTarget ?? "_blank";
8865
+ return writable;
8866
+ }
8867
+ decorate(editor) {
8868
+ const decorator = getKernelFromEditor(editor)?.getDecorator(LinkCardNode.getType());
8869
+ if (!decorator) return null;
8870
+ if (typeof decorator === "function") return decorator(this, editor);
8871
+ return decorator.render(this, editor);
8872
+ }
8873
+ };
8874
+ function $createLinkCardNode(payload) {
8875
+ return $applyNodeReplacement(new LinkCardNode(payload.url, payload.title, payload.icon, payload.description, payload.openTarget));
8876
+ }
8877
+ function $convertLinkCardElement(domNode) {
8878
+ const element = domNode;
8879
+ return { node: $createLinkCardNode({
8880
+ description: element.dataset.description,
8881
+ icon: element.dataset.icon,
8882
+ openTarget: element.getAttribute("target"),
8883
+ title: element.textContent || element.href,
8884
+ url: element.getAttribute("href") || ""
8885
+ }) };
8886
+ }
8887
+ function $isLinkCardNode(node) {
8888
+ return node instanceof LinkCardNode || node?.getType?.() === LinkCardNode.getType();
8889
+ }
8890
+ //#endregion
8891
+ //#region src/plugins/link/node/LinkBlockCardNode.ts
8892
+ var LinkBlockCardNode = class LinkBlockCardNode extends LinkCardNode {
8893
+ static getType() {
8894
+ return "link-block-card";
8895
+ }
8896
+ static clone(node) {
8897
+ return new LinkBlockCardNode(node.__url, node.__title, node.__icon, node.__description, node.__openTarget, node.__key);
8898
+ }
8899
+ static importJSON(serializedNode) {
8900
+ return $createLinkBlockCardNode({
8901
+ description: serializedNode.description,
8902
+ icon: serializedNode.icon,
8903
+ openTarget: serializedNode.openTarget,
8904
+ title: serializedNode.title,
8905
+ url: serializedNode.url
8906
+ }).updateFromJSON(serializedNode);
8907
+ }
8908
+ static importDOM() {
8909
+ return { a: (node) => {
8910
+ if (node instanceof HTMLAnchorElement && node.dataset.linkCard === "true" && node.dataset.linkCardLayout === "block") return {
8911
+ conversion: $convertLinkBlockCardElement,
8912
+ priority: 3
8913
+ };
8914
+ return null;
8915
+ } };
8916
+ }
8917
+ constructor(url, title, icon, description, openTarget, key) {
8918
+ super(url, title, icon, description, openTarget, key);
8919
+ }
8920
+ createDOM(config, editor) {
8921
+ const element = super.createDOM(config, editor);
8922
+ element.dataset.linkCardLayout = "block";
8923
+ return element;
8924
+ }
8925
+ exportDOM() {
8926
+ const output = super.exportDOM();
8927
+ if (output.element instanceof HTMLElement) output.element.dataset.linkCardLayout = "block";
8928
+ return output;
8929
+ }
8930
+ updateFromJSON(serializedNode) {
8931
+ return super.updateFromJSON(serializedNode);
8932
+ }
8933
+ isInline() {
8934
+ return false;
8935
+ }
8936
+ };
8937
+ function $createLinkBlockCardNode(payload) {
8938
+ return $applyNodeReplacement(new LinkBlockCardNode(payload.url, payload.title, payload.icon, payload.description, payload.openTarget));
8939
+ }
8940
+ function $convertLinkBlockCardElement(domNode) {
8941
+ const element = domNode;
8942
+ return { node: $createLinkBlockCardNode({
8943
+ description: element.dataset.description,
8944
+ icon: element.dataset.icon,
8945
+ openTarget: element.getAttribute("target"),
8946
+ title: element.textContent || element.href,
8947
+ url: element.getAttribute("href") || ""
8948
+ }) };
8949
+ }
8950
+ function $isLinkBlockCardNode(node) {
8951
+ return node instanceof LinkBlockCardNode || node?.getType?.() === LinkBlockCardNode.getType();
8952
+ }
8953
+ //#endregion
8954
+ //#region src/plugins/link/node/LinkIframeNode.ts
8955
+ var LinkIframeNode = class LinkIframeNode extends DecoratorNode {
8956
+ static getType() {
8957
+ return "link-iframe";
8958
+ }
8959
+ static clone(node) {
8960
+ return new LinkIframeNode(node.__url, node.__src, node.__title, node.__key);
8961
+ }
8962
+ static importJSON(serializedNode) {
8963
+ return $createLinkIframeNode({
8964
+ src: serializedNode.src,
8965
+ title: serializedNode.title,
8966
+ url: serializedNode.url
8967
+ }).updateFromJSON(serializedNode);
8968
+ }
8969
+ static importDOM() {
8970
+ return { a: (node) => {
8971
+ if (node instanceof HTMLAnchorElement && node.dataset.linkIframe === "true") return {
8972
+ conversion: $convertLinkIframeElement,
8973
+ priority: 2
8974
+ };
8975
+ return null;
8976
+ } };
8977
+ }
8978
+ constructor(url, src, title, key) {
8979
+ super(key);
8980
+ this.__url = url;
8981
+ this.__src = src || url;
8982
+ this.__title = title || url;
8983
+ }
8984
+ createDOM(config, editor) {
8985
+ const element = document.createElement("div");
8986
+ element.dataset.linkIframe = "true";
8987
+ addClassNamesToElement(element, config.theme.linkIframe);
8988
+ element.addEventListener("mouseenter", (event) => {
8989
+ if (event.target instanceof HTMLElement) {
8990
+ event.target.classList.add("hover");
8991
+ editor.dispatchCommand(HOVER_LINK_COMMAND, {
8992
+ event,
8993
+ node: this
8994
+ });
8995
+ }
8996
+ });
8997
+ element.addEventListener("mouseleave", (event) => {
8998
+ if (event.target instanceof HTMLElement) {
8999
+ event.target.classList.remove("hover");
9000
+ editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
9001
+ }
9002
+ });
9003
+ return element;
9004
+ }
9005
+ updateDOM() {
9006
+ return false;
9007
+ }
9008
+ isInline() {
9009
+ return false;
9010
+ }
9011
+ exportDOM() {
9012
+ const element = document.createElement("a");
9013
+ element.dataset.linkIframe = "true";
9014
+ element.dataset.src = this.getSrc();
9015
+ element.href = this.getURL();
9016
+ element.textContent = this.getTitle();
9017
+ return { element };
9018
+ }
9019
+ exportJSON() {
9020
+ return {
9021
+ ...super.exportJSON(),
9022
+ src: this.getSrc(),
9023
+ title: this.getTitle(),
9024
+ url: this.getURL()
9025
+ };
9026
+ }
9027
+ updateFromJSON(serializedNode) {
9028
+ return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setSrc(serializedNode.src).setTitle(serializedNode.title);
9029
+ }
9030
+ getTextContent() {
9031
+ return this.getTitle();
9032
+ }
9033
+ getURL() {
9034
+ return this.getLatest().__url;
9035
+ }
9036
+ setURL(url) {
9037
+ const writable = this.getWritable();
9038
+ writable.__url = url;
9039
+ return writable;
9040
+ }
9041
+ getSrc() {
9042
+ return this.getLatest().__src || this.getURL();
9043
+ }
9044
+ setSrc(src) {
9045
+ const writable = this.getWritable();
9046
+ writable.__src = src || this.getURL();
9047
+ return writable;
9048
+ }
9049
+ getTitle() {
9050
+ return this.getLatest().__title || this.getURL();
9051
+ }
9052
+ setTitle(title) {
9053
+ const writable = this.getWritable();
9054
+ writable.__title = title || this.getURL();
9055
+ return writable;
9056
+ }
9057
+ decorate(editor) {
9058
+ const decorator = getKernelFromEditor(editor)?.getDecorator(LinkIframeNode.getType());
9059
+ if (!decorator) return null;
9060
+ if (typeof decorator === "function") return decorator(this, editor);
9061
+ return decorator.render(this, editor);
9062
+ }
9063
+ };
9064
+ function $createLinkIframeNode(payload) {
9065
+ return $applyNodeReplacement(new LinkIframeNode(payload.url, payload.src, payload.title));
9066
+ }
9067
+ function $convertLinkIframeElement(domNode) {
9068
+ const element = domNode;
9069
+ return { node: $createLinkIframeNode({
9070
+ src: element.dataset.src,
9071
+ title: element.textContent || element.href,
9072
+ url: element.getAttribute("href") || ""
9073
+ }) };
9074
+ }
9075
+ function $isLinkIframeNode(node) {
9076
+ return node instanceof LinkIframeNode || node?.getType?.() === LinkIframeNode.getType();
9077
+ }
9078
+ //#endregion
9079
+ //#region src/plugins/link/node/SchemaNode.ts
9080
+ var SchemaNode = class SchemaNode extends DecoratorNode {
9081
+ static getType() {
9082
+ return "schema-link";
9083
+ }
9084
+ static clone(node) {
9085
+ return new SchemaNode(node.__url, node.__schemaType, node.__payload, node.__title, node.__key);
9086
+ }
9087
+ static importJSON(serializedNode) {
9088
+ return $createSchemaNode({
9089
+ payload: serializedNode.payload,
9090
+ schemaType: serializedNode.schemaType,
9091
+ title: serializedNode.title,
9092
+ url: serializedNode.url
9093
+ }).updateFromJSON(serializedNode);
9094
+ }
9095
+ static importDOM() {
9096
+ return { a: (node) => {
9097
+ if (node instanceof HTMLAnchorElement && node.dataset.schemaLink === "true") return {
9098
+ conversion: $convertSchemaElement,
9099
+ priority: 2
9100
+ };
9101
+ return null;
9102
+ } };
9103
+ }
9104
+ constructor(url, schemaType, payload, title, key) {
9105
+ super(key);
9106
+ this.__url = url;
9107
+ this.__schemaType = schemaType || "";
9108
+ this.__payload = payload;
9109
+ this.__title = title || url;
9110
+ }
9111
+ createDOM(config, editor) {
9112
+ const element = document.createElement("div");
9113
+ element.dataset.schemaLink = "true";
9114
+ addClassNamesToElement(element, config.theme.schemaLink);
9115
+ element.addEventListener("mouseenter", (event) => {
9116
+ if (event.target instanceof HTMLElement) {
9117
+ event.target.classList.add("hover");
9118
+ editor.dispatchCommand(HOVER_LINK_COMMAND, {
9119
+ event,
9120
+ node: this
9121
+ });
9122
+ }
9123
+ });
9124
+ element.addEventListener("mouseleave", (event) => {
9125
+ if (event.target instanceof HTMLElement) {
9126
+ event.target.classList.remove("hover");
9127
+ editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
7231
9128
  }
7232
9129
  });
9130
+ return element;
9131
+ }
9132
+ updateDOM() {
7233
9133
  return false;
7234
- }, COMMAND_PRIORITY_EDITOR));
7235
- }
7236
- //#endregion
7237
- //#region src/plugins/link/service/i-link-service.ts
7238
- const ILinkService = genServiceId("LinkService");
7239
- var LinkService = class extends EventEmitter {
7240
- constructor(..._args) {
7241
- super(..._args);
7242
- this._enableLinkToolbar = true;
7243
9134
  }
7244
- get enableLinkToolbar() {
7245
- return this._enableLinkToolbar;
9135
+ exportDOM() {
9136
+ const element = document.createElement("a");
9137
+ element.dataset.schemaLink = "true";
9138
+ element.dataset.schemaType = this.getSchemaType();
9139
+ element.dataset.payload = JSON.stringify(this.getPayload() ?? null);
9140
+ element.href = this.getURL();
9141
+ element.textContent = this.getTitle();
9142
+ return { element };
7246
9143
  }
7247
- setLinkToolbar(enable) {
7248
- this._enableLinkToolbar = enable;
7249
- this.emit("linkToolbarChange", enable);
9144
+ exportJSON() {
9145
+ return {
9146
+ ...super.exportJSON(),
9147
+ payload: this.getPayload(),
9148
+ schemaType: this.getSchemaType(),
9149
+ title: this.getTitle(),
9150
+ url: this.getURL()
9151
+ };
9152
+ }
9153
+ updateFromJSON(serializedNode) {
9154
+ return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setSchemaType(serializedNode.schemaType).setPayload(serializedNode.payload).setTitle(serializedNode.title);
9155
+ }
9156
+ getTextContent() {
9157
+ return this.getTitle();
9158
+ }
9159
+ getURL() {
9160
+ return this.getLatest().__url;
9161
+ }
9162
+ setURL(url) {
9163
+ const writable = this.getWritable();
9164
+ writable.__url = url;
9165
+ return writable;
9166
+ }
9167
+ getSchemaType() {
9168
+ return this.getLatest().__schemaType;
9169
+ }
9170
+ setSchemaType(schemaType) {
9171
+ const writable = this.getWritable();
9172
+ writable.__schemaType = schemaType || "";
9173
+ return writable;
9174
+ }
9175
+ getPayload() {
9176
+ return this.getLatest().__payload;
9177
+ }
9178
+ setPayload(payload) {
9179
+ const writable = this.getWritable();
9180
+ writable.__payload = payload;
9181
+ return writable;
9182
+ }
9183
+ getTitle() {
9184
+ return this.getLatest().__title || this.getURL();
9185
+ }
9186
+ setTitle(title) {
9187
+ const writable = this.getWritable();
9188
+ writable.__title = title || this.getURL();
9189
+ return writable;
9190
+ }
9191
+ decorate(editor) {
9192
+ const decorator = getKernelFromEditor(editor)?.getDecorator(SchemaNode.getType());
9193
+ if (!decorator) return null;
9194
+ if (typeof decorator === "function") return decorator(this, editor);
9195
+ return decorator.render(this, editor);
7250
9196
  }
7251
9197
  };
9198
+ function $createSchemaNode(payload) {
9199
+ return $applyNodeReplacement(new SchemaNode(payload.url, payload.schemaType, payload.payload, payload.title));
9200
+ }
9201
+ function $convertSchemaElement(domNode) {
9202
+ const element = domNode;
9203
+ return { node: $createSchemaNode({
9204
+ payload: parsePayload(element.dataset.payload),
9205
+ schemaType: element.dataset.schemaType,
9206
+ title: element.textContent || element.href,
9207
+ url: element.getAttribute("href") || ""
9208
+ }) };
9209
+ }
9210
+ function parsePayload(value) {
9211
+ if (!value) return void 0;
9212
+ try {
9213
+ return JSON.parse(value);
9214
+ } catch {
9215
+ return value;
9216
+ }
9217
+ }
9218
+ function $isSchemaNode(node) {
9219
+ return node instanceof SchemaNode || node?.getType?.() === SchemaNode.getType();
9220
+ }
9221
+ //#endregion
9222
+ //#region src/plugins/link/normalization/index.ts
9223
+ function normalizeSchemaLinkNode(node, editor, linkService) {
9224
+ const url = node.getURL();
9225
+ const title = node.getTitle() || node.getTextContent() || url;
9226
+ const schema = linkService.parseSchemaUrl(url);
9227
+ const rule = linkService.getSchemaRule(url, {
9228
+ editor,
9229
+ schema,
9230
+ text: node.getTextContent(),
9231
+ title
9232
+ });
9233
+ if (!rule) return false;
9234
+ const parsed = rule.parse?.(url, schema);
9235
+ const payload = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? normalizeSchemaPayload(parsed) : { payload: parsed };
9236
+ if (linkService.hasSchemaLinkRenderer(url)) payload.payload = {
9237
+ ...payload.payload && typeof payload.payload === "object" ? payload.payload : {},
9238
+ __legacyLink: {
9239
+ rel: node.getRel(),
9240
+ target: node.getTarget(),
9241
+ text: node.getTextContent(),
9242
+ title: node.getTitle()
9243
+ }
9244
+ };
9245
+ node.replace($createSchemaNode({
9246
+ payload: payload.payload,
9247
+ schemaType: payload.schemaType || rule.id,
9248
+ title: payload.title || title,
9249
+ url: payload.url || url
9250
+ }));
9251
+ return true;
9252
+ }
9253
+ function normalizeSchemaPayload(payload) {
9254
+ if ("payload" in payload || "schemaType" in payload || "title" in payload || "url" in payload) return payload;
9255
+ return { payload };
9256
+ }
7252
9257
  //#endregion
7253
9258
  //#region src/plugins/link/utils/index.ts
7254
9259
  const SUPPORTED_URL_PROTOCOLS = /* @__PURE__ */ new Set([
@@ -7377,10 +9382,22 @@ const LinkPlugin = class extends KernelPlugin {
7377
9382
  super();
7378
9383
  this.kernel = kernel;
7379
9384
  this.config = config;
7380
- this.linkRegex = /^https?:\/\/\S+$/;
9385
+ this.linkRegex = /^[a-z][\d+.a-z-]*:\/\/\S+$/i;
7381
9386
  this.service = new LinkService();
7382
- kernel.registerNodes([LinkNode, AutoLinkNode]);
9387
+ kernel.registerNodes([
9388
+ LinkNode,
9389
+ AutoLinkNode,
9390
+ LinkCardNode,
9391
+ LinkBlockCardNode,
9392
+ LinkIframeNode,
9393
+ SchemaNode
9394
+ ]);
9395
+ this.service.updateConfig(config);
7383
9396
  kernel.registerService(ILinkService, this.service);
9397
+ this.registerDecorator(kernel, LinkCardNode.getType(), (node, editor) => config?.decoratorCard ? config.decoratorCard(node, editor) : null);
9398
+ this.registerDecorator(kernel, LinkBlockCardNode.getType(), (node, editor) => config?.decoratorCard ? config.decoratorCard(node, editor) : null);
9399
+ this.registerDecorator(kernel, LinkIframeNode.getType(), (node, editor) => config?.decoratorIframe ? config.decoratorIframe(node, editor) : null);
9400
+ this.registerDecorator(kernel, SchemaNode.getType(), (node, editor) => config?.decoratorSchema ? config.decoratorSchema(node, editor) : null);
7384
9401
  if (config?.theme) kernel.registerThemes(config.theme);
7385
9402
  if (config?.linkRegex) this.linkRegex = config.linkRegex;
7386
9403
  }
@@ -7404,6 +9421,9 @@ const LinkPlugin = class extends KernelPlugin {
7404
9421
  }
7405
9422
  return false;
7406
9423
  }, COMMAND_PRIORITY_NORMAL));
9424
+ if (this.config?.normalizeSchemaLinks !== false) this.register(editor.registerNodeTransform(LinkNode, (node) => {
9425
+ normalizeSchemaLinkNode(node, editor, this.service);
9426
+ }));
7407
9427
  this.registerMarkdown();
7408
9428
  this.registerLiteXml();
7409
9429
  }
@@ -7428,6 +9448,74 @@ const LinkPlugin = class extends KernelPlugin {
7428
9448
  version: 1
7429
9449
  })];
7430
9450
  });
9451
+ litexmlService.registerXMLWriter(LinkCardNode.getType(), (node, ctx) => {
9452
+ if (!$isLinkCardNode(node)) return false;
9453
+ return ctx.createXmlNode("link-card", {
9454
+ description: node.getDescription(),
9455
+ href: node.getURL(),
9456
+ icon: node.getIcon(),
9457
+ openTarget: node.getOpenTarget() || "_blank",
9458
+ title: node.getTitle()
9459
+ });
9460
+ });
9461
+ litexmlService.registerXMLWriter(LinkBlockCardNode.getType(), (node, ctx) => {
9462
+ if (!$isLinkBlockCardNode(node)) return false;
9463
+ return ctx.createXmlNode("link-card", {
9464
+ description: node.getDescription(),
9465
+ href: node.getURL(),
9466
+ icon: node.getIcon(),
9467
+ layout: "block",
9468
+ openTarget: node.getOpenTarget() || "_blank",
9469
+ title: node.getTitle()
9470
+ });
9471
+ });
9472
+ litexmlService.registerXMLReader("link-card", (xmlNode) => {
9473
+ return [INodeHelper.createTypeNode(xmlNode.getAttribute("layout") === "block" ? LinkBlockCardNode.getType() : LinkCardNode.getType(), {
9474
+ description: xmlNode.getAttribute("description") || "",
9475
+ icon: xmlNode.getAttribute("icon") || "",
9476
+ openTarget: xmlNode.getAttribute("openTarget") || "_blank",
9477
+ title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
9478
+ type: xmlNode.getAttribute("layout") === "block" ? LinkBlockCardNode.getType() : LinkCardNode.getType(),
9479
+ url: xmlNode.getAttribute("href") || "",
9480
+ version: 1
9481
+ })];
9482
+ });
9483
+ litexmlService.registerXMLWriter(LinkIframeNode.getType(), (node, ctx) => {
9484
+ if (!$isLinkIframeNode(node)) return false;
9485
+ return ctx.createXmlNode("link-iframe", {
9486
+ href: node.getURL(),
9487
+ src: node.getSrc(),
9488
+ title: node.getTitle()
9489
+ });
9490
+ });
9491
+ litexmlService.registerXMLReader("link-iframe", (xmlNode) => {
9492
+ return [INodeHelper.createTypeNode(LinkIframeNode.getType(), {
9493
+ src: xmlNode.getAttribute("src") || xmlNode.getAttribute("href") || "",
9494
+ title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
9495
+ type: LinkIframeNode.getType(),
9496
+ url: xmlNode.getAttribute("href") || "",
9497
+ version: 1
9498
+ })];
9499
+ });
9500
+ litexmlService.registerXMLWriter(SchemaNode.getType(), (node, ctx) => {
9501
+ if (!$isSchemaNode(node)) return false;
9502
+ return ctx.createXmlNode("schema-link", {
9503
+ href: node.getURL(),
9504
+ payload: JSON.stringify(node.getPayload() ?? null),
9505
+ schemaType: node.getSchemaType(),
9506
+ title: node.getTitle()
9507
+ });
9508
+ });
9509
+ litexmlService.registerXMLReader("schema-link", (xmlNode) => {
9510
+ return [INodeHelper.createTypeNode(SchemaNode.getType(), {
9511
+ payload: parseJsonAttribute(xmlNode.getAttribute("payload")),
9512
+ schemaType: xmlNode.getAttribute("schemaType") || "",
9513
+ title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
9514
+ type: SchemaNode.getType(),
9515
+ url: xmlNode.getAttribute("href") || "",
9516
+ version: 1
9517
+ })];
9518
+ });
7431
9519
  }
7432
9520
  registerMarkdown() {
7433
9521
  this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownShortCut({
@@ -7447,6 +9535,30 @@ const LinkPlugin = class extends KernelPlugin {
7447
9535
  this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkNode.getType(), (ctx, node) => {
7448
9536
  if ($isLinkNode(node)) ctx.wrap("[", `](${node.getURL()})`);
7449
9537
  });
9538
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkCardNode.getType(), (ctx, node) => {
9539
+ if ($isLinkCardNode(node)) {
9540
+ ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
9541
+ return true;
9542
+ }
9543
+ });
9544
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkBlockCardNode.getType(), (ctx, node) => {
9545
+ if ($isLinkBlockCardNode(node)) {
9546
+ ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
9547
+ return true;
9548
+ }
9549
+ });
9550
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkIframeNode.getType(), (ctx, node) => {
9551
+ if ($isLinkIframeNode(node)) {
9552
+ ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
9553
+ return true;
9554
+ }
9555
+ });
9556
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(SchemaNode.getType(), (ctx, node) => {
9557
+ if ($isSchemaNode(node)) {
9558
+ ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
9559
+ return true;
9560
+ }
9561
+ });
7450
9562
  this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownReader("link", (node, children) => {
7451
9563
  return [INodeHelper.createElementNode("link", {
7452
9564
  children,
@@ -7461,6 +9573,14 @@ const LinkPlugin = class extends KernelPlugin {
7461
9573
  });
7462
9574
  }
7463
9575
  };
9576
+ function parseJsonAttribute(value) {
9577
+ if (!value) return void 0;
9578
+ try {
9579
+ return JSON.parse(value);
9580
+ } catch {
9581
+ return value;
9582
+ }
9583
+ }
7464
9584
  //#endregion
7465
9585
  //#region src/utils/cx.ts
7466
9586
  const cx = (...classNames) => classNames.filter(Boolean).join(" ");
@@ -7819,7 +9939,11 @@ const ListPlugin = class extends KernelPlugin {
7819
9939
  type: "element"
7820
9940
  });
7821
9941
  markdownService.registerMarkdownWriter(ListNode.getType(), (ctx, node) => {
7822
- if ($isListNode(node)) ctx.wrap("", "\n");
9942
+ if ($isListNode(node)) {
9943
+ const parent = node.getParent();
9944
+ const needsLeadingNewline = $isListItemNode(parent) && node.getPreviousSibling() !== null;
9945
+ ctx.wrap(needsLeadingNewline ? "\n" : "", "\n");
9946
+ }
7823
9947
  });
7824
9948
  const getLevel = (node) => {
7825
9949
  if (!node) return 0;
@@ -7850,7 +9974,10 @@ const ListPlugin = class extends KernelPlugin {
7850
9974
  let start = node.start || 1;
7851
9975
  return INodeHelper.createElementNode("list", {
7852
9976
  children: children.map((v) => {
7853
- if (v.type === "listitem") v.value = start++;
9977
+ if (v.type === "listitem") {
9978
+ v.value = start;
9979
+ if (v.children?.[0]?.type !== "list") start++;
9980
+ }
7854
9981
  return v;
7855
9982
  }),
7856
9983
  direction: "ltr",
@@ -8922,10 +11049,88 @@ var TableControllerMenuService = class {
8922
11049
  }
8923
11050
  };
8924
11051
  //#endregion
11052
+ //#region src/plugins/table/plugin/single-cell-paste.ts
11053
+ const LEXICAL_CLIPBOARD_TYPE = "application/x-lexical-editor";
11054
+ const countNodesByType = (node, type) => {
11055
+ return (node.type === type ? 1 : 0) + (Array.isArray(node.children) ? node.children.reduce((count, child) => count + countNodesByType(child, type), 0) : 0);
11056
+ };
11057
+ const parseLexicalTableClipboard = (serializedClipboard) => {
11058
+ if (!serializedClipboard) return void 0;
11059
+ try {
11060
+ const clipboard = JSON.parse(serializedClipboard);
11061
+ if (!Array.isArray(clipboard.nodes) || clipboard.nodes.length !== 1) return { isSingleCell: false };
11062
+ const [rootNode] = clipboard.nodes;
11063
+ if (rootNode?.type !== "table") return { isSingleCell: false };
11064
+ const cells = [];
11065
+ const collectCells = (node) => {
11066
+ if (node.type === "tablecell") cells.push(node);
11067
+ node.children?.forEach(collectCells);
11068
+ };
11069
+ collectCells(rootNode);
11070
+ const isSingleCell = countNodesByType(rootNode, "table") === 1 && cells.length === 1;
11071
+ return {
11072
+ cell: isSingleCell ? cells[0] : void 0,
11073
+ isSingleCell
11074
+ };
11075
+ } catch {
11076
+ return;
11077
+ }
11078
+ };
11079
+ const getSerializedNodeText = (node) => {
11080
+ if (node.type === "text") return node.text || "";
11081
+ if (node.type === "linebreak") return "\n";
11082
+ if (node.type === "tab") return " ";
11083
+ return node.children?.map(getSerializedNodeText).join("") || "";
11084
+ };
11085
+ const getSerializedCellText = (cell) => {
11086
+ return cell.children?.map(getSerializedNodeText).join("\n") || "";
11087
+ };
11088
+ const getSingleCellHTMLText = (html) => {
11089
+ if (!isSingleCellHTMLTable(html) || typeof DOMParser === "undefined") return void 0;
11090
+ const cell = new DOMParser().parseFromString(html, "text/html").body.querySelector("td, th");
11091
+ if (!cell) return void 0;
11092
+ cell.querySelectorAll("br").forEach((node) => node.replaceWith("\n"));
11093
+ return Array.from(cell.children).map((node) => node.textContent || "").join("\n") || cell.textContent || "";
11094
+ };
11095
+ const isSingleCellHTMLTable = (html) => {
11096
+ if (!html || typeof DOMParser === "undefined") return false;
11097
+ const document = new DOMParser().parseFromString(html, "text/html");
11098
+ const tables = document.body.querySelectorAll("table");
11099
+ if (tables.length !== 1 || tables[0].querySelectorAll("td, th").length !== 1) return false;
11100
+ const remainder = document.body.cloneNode(true);
11101
+ remainder.querySelector("table")?.remove();
11102
+ remainder.querySelectorAll("link, meta, script, style").forEach((node) => node.remove());
11103
+ return !remainder.querySelector("audio, canvas, iframe, img, object, svg, video") && remainder.textContent?.trim() === "";
11104
+ };
11105
+ const isSingleCellTableClipboard = (clipboardData) => {
11106
+ const lexicalResult = parseLexicalTableClipboard(clipboardData.getData(LEXICAL_CLIPBOARD_TYPE));
11107
+ if (lexicalResult !== void 0) return lexicalResult.isSingleCell;
11108
+ return isSingleCellHTMLTable(clipboardData.getData("text/html"));
11109
+ };
11110
+ const getSingleCellPlainText = (clipboardData) => {
11111
+ const lexicalResult = parseLexicalTableClipboard(clipboardData.getData(LEXICAL_CLIPBOARD_TYPE));
11112
+ if (lexicalResult?.cell) return getSerializedCellText(lexicalResult.cell);
11113
+ const htmlText = getSingleCellHTMLText(clipboardData.getData("text/html"));
11114
+ if (htmlText !== void 0) return htmlText;
11115
+ return clipboardData.getData("text/plain").replace(/(?:\r\n|\n)$/, "");
11116
+ };
11117
+ const handleSingleCellTablePaste = (editor, event) => {
11118
+ if (!("clipboardData" in event)) return false;
11119
+ const clipboardData = event.clipboardData;
11120
+ if (!clipboardData || !isSingleCellTableClipboard(clipboardData)) return false;
11121
+ event.preventDefault();
11122
+ editor.dispatchCommand(CONTROLLED_TEXT_INSERTION_COMMAND, getSingleCellPlainText(clipboardData));
11123
+ return true;
11124
+ };
11125
+ const registerSingleCellTablePaste = (editor) => {
11126
+ return editor.registerCommand(PASTE_COMMAND, (event) => handleSingleCellTablePaste(editor, event), COMMAND_PRIORITY_HIGH);
11127
+ };
11128
+ //#endregion
8925
11129
  //#region src/plugins/table/plugin/index.ts
8926
11130
  const tableCellProcessor = (before, content, after) => {
8927
11131
  return before + content.replace(/\n+$/, "").replaceAll(/\n+/g, "<br />") + after;
8928
11132
  };
11133
+ const isRemovedTableRowDiff = (node) => node.getType() === "table-row-diff" && "getDiffType" in node && typeof node.getDiffType === "function" && node.getDiffType() === "remove";
8929
11134
  function isHeadlessEditor(editor) {
8930
11135
  return editor._headless === true;
8931
11136
  }
@@ -8974,6 +11179,7 @@ const TablePlugin = class extends KernelPlugin {
8974
11179
  }
8975
11180
  onInit(editor) {
8976
11181
  this.register(registerTableCellUnmergeTransform(editor));
11182
+ this.register(registerSingleCellTablePaste(editor));
8977
11183
  if (!isHeadlessEditor(editor)) {
8978
11184
  setScrollableTablesActive(editor, true);
8979
11185
  this.register(registerTablePlugin(editor));
@@ -9220,7 +11426,7 @@ const TablePlugin = class extends KernelPlugin {
9220
11426
  markdownService.registerMarkdownWriter(TableRowNode.getType(), (ctx, node) => {
9221
11427
  const parent = node.getParent();
9222
11428
  if (!$isTableNode(parent)) return;
9223
- if (!node.getPreviousSibling()) ctx.wrap("", `\n${parent.getColWidths()?.map(() => {
11429
+ if (!node.getPreviousSiblings().some((sibling) => !isRemovedTableRowDiff(sibling))) ctx.wrap("", `\n${parent.getColWidths()?.map(() => {
9224
11430
  return "|:--";
9225
11431
  }).join("")}|\n`);
9226
11432
  else ctx.wrap("", "\n");
@@ -9267,6 +11473,207 @@ const TablePlugin = class extends KernelPlugin {
9267
11473
  }
9268
11474
  };
9269
11475
  //#endregion
11476
+ //#region src/plugins/collapsible/node/CollapsibleNode.ts
11477
+ const CONTENT_SELECTOR = "[data-collapsible-content=\"true\"]";
11478
+ const TOGGLE_SELECTOR = "[data-collapsible-toggle=\"true\"]";
11479
+ var CollapsibleNode = class CollapsibleNode extends ElementNode {
11480
+ static getType() {
11481
+ return "collapsible";
11482
+ }
11483
+ static clone(node) {
11484
+ return new CollapsibleNode(node.__title, node.__collapsed, node.__key);
11485
+ }
11486
+ static importDOM() {
11487
+ return { details: () => ({
11488
+ conversion: $convertDetailsElement,
11489
+ priority: 1
11490
+ }) };
11491
+ }
11492
+ static importJSON(serializedNode) {
11493
+ const node = $createCollapsibleNode(serializedNode.title || "Details").updateFromJSON(serializedNode);
11494
+ if (shouldPrependTitle(serializedNode)) {
11495
+ const titleParagraph = $createTitleParagraph(serializedNode.title || "Details");
11496
+ const firstChild = node.getFirstChild();
11497
+ if (firstChild) firstChild.insertBefore(titleParagraph);
11498
+ else node.append(titleParagraph);
11499
+ }
11500
+ return node;
11501
+ }
11502
+ constructor(title = "Details", collapsed = false, key) {
11503
+ super(key);
11504
+ this.__title = title;
11505
+ this.__collapsed = collapsed;
11506
+ }
11507
+ createDOM(config, editor) {
11508
+ const element = document.createElement("section");
11509
+ addClassNamesToElement(element, config.theme.collapsible);
11510
+ element.dataset.collapsible = "true";
11511
+ element.dataset.collapsibleCollapsed = String(this.__collapsed);
11512
+ const toggle = document.createElement("button");
11513
+ toggle.type = "button";
11514
+ toggle.contentEditable = "false";
11515
+ toggle.dataset.collapsibleToggle = "true";
11516
+ toggle.setAttribute("aria-expanded", String(!this.__collapsed));
11517
+ toggle.setAttribute("aria-label", this.__collapsed ? "Expand collapsible block" : "Collapse collapsible block");
11518
+ toggle.addEventListener("click", () => {
11519
+ editor.update(() => {
11520
+ const latest = this.getLatest();
11521
+ if ($isCollapsibleNode(latest)) latest.setCollapsed(!latest.isCollapsed());
11522
+ });
11523
+ });
11524
+ const content = document.createElement("div");
11525
+ content.dataset.collapsibleContent = "true";
11526
+ element.append(toggle, content);
11527
+ return element;
11528
+ }
11529
+ updateDOM(prevNode, dom) {
11530
+ const toggle = dom.querySelector(TOGGLE_SELECTOR);
11531
+ dom.dataset.collapsibleCollapsed = String(this.__collapsed);
11532
+ if (toggle && prevNode.__collapsed !== this.__collapsed) {
11533
+ toggle.setAttribute("aria-expanded", String(!this.__collapsed));
11534
+ toggle.setAttribute("aria-label", this.__collapsed ? "Expand collapsible block" : "Collapse collapsible block");
11535
+ }
11536
+ return false;
11537
+ }
11538
+ exportDOM() {
11539
+ const element = document.createElement("details");
11540
+ if (!this.__collapsed) element.setAttribute("open", "");
11541
+ const summary = document.createElement("summary");
11542
+ summary.textContent = this.getTitle();
11543
+ element.append(summary);
11544
+ return { element };
11545
+ }
11546
+ exportJSON() {
11547
+ return {
11548
+ ...super.exportJSON(),
11549
+ collapsed: this.isCollapsed(),
11550
+ title: this.getTitle()
11551
+ };
11552
+ }
11553
+ getDOMSlot(element) {
11554
+ const content = element.querySelector(CONTENT_SELECTOR);
11555
+ if (content instanceof HTMLElement) return super.getDOMSlot(element).withElement(content);
11556
+ return super.getDOMSlot(element);
11557
+ }
11558
+ getTitle() {
11559
+ const latest = this.getLatest();
11560
+ const firstChild = latest.getFirstChild();
11561
+ return firstChild ? firstChild.getTextContent().trim() : latest.__title;
11562
+ }
11563
+ isCollapsed() {
11564
+ return this.getLatest().__collapsed;
11565
+ }
11566
+ isShadowRoot() {
11567
+ return true;
11568
+ }
11569
+ setCollapsed(collapsed) {
11570
+ const writable = this.getWritable();
11571
+ writable.__collapsed = collapsed;
11572
+ return writable;
11573
+ }
11574
+ setTitle(title) {
11575
+ const writable = this.getWritable();
11576
+ writable.__title = title;
11577
+ return writable;
11578
+ }
11579
+ updateFromJSON(serializedNode) {
11580
+ return super.updateFromJSON(serializedNode).setTitle(serializedNode.title || "Details").setCollapsed(Boolean(serializedNode.collapsed));
11581
+ }
11582
+ };
11583
+ function $createCollapsibleNode(title = "Details", collapsed = false) {
11584
+ return $applyNodeReplacement(new CollapsibleNode(title, collapsed));
11585
+ }
11586
+ function $isCollapsibleNode(node) {
11587
+ return node instanceof CollapsibleNode;
11588
+ }
11589
+ function $convertDetailsElement(domNode) {
11590
+ if (!(domNode instanceof HTMLDetailsElement)) return { node: null };
11591
+ return { node: $createCollapsibleNode(domNode.querySelector("summary")?.textContent?.trim() || "Details", !domNode.open) };
11592
+ }
11593
+ function $createTitleParagraph(title) {
11594
+ const paragraph = $createParagraphNode();
11595
+ paragraph.append($createTextNode(title));
11596
+ return paragraph;
11597
+ }
11598
+ function getSerializedText(node) {
11599
+ if (!node || typeof node !== "object") return "";
11600
+ if ("text" in node && typeof node.text === "string") return node.text;
11601
+ if ("children" in node && Array.isArray(node.children)) return node.children.map((child) => getSerializedText(child)).join("");
11602
+ return "";
11603
+ }
11604
+ function shouldPrependTitle(serializedNode) {
11605
+ const title = serializedNode.title?.trim();
11606
+ if (!title) return false;
11607
+ const firstChild = (Array.isArray(serializedNode.children) ? serializedNode.children : [])[0];
11608
+ return getSerializedText(firstChild).trim() !== title;
11609
+ }
11610
+ //#endregion
11611
+ //#region src/headless/collapsible-plugin.ts
11612
+ const HeadlessCollapsiblePlugin = class extends KernelPlugin {
11613
+ static {
11614
+ this.pluginName = "HeadlessCollapsiblePlugin";
11615
+ }
11616
+ constructor(kernel) {
11617
+ super();
11618
+ this.kernel = kernel;
11619
+ kernel.registerNodes([CollapsibleNode]);
11620
+ }
11621
+ onInit(_editor) {
11622
+ const markdownService = this.kernel.requireService(IMarkdownShortCutService);
11623
+ markdownService?.registerMarkdownWriter(CollapsibleNode.getType(), (ctx, node) => {
11624
+ if (!$isCollapsibleNode(node)) return false;
11625
+ ctx.appendLine(`<details${node.isCollapsed() ? "" : " open"}>\n`);
11626
+ ctx.appendLine(`<summary>${escapeHtml(node.getTitle())}</summary>\n\n`);
11627
+ node.getChildren().slice(1).forEach((child) => ctx.processChild(ctx, child));
11628
+ ctx.appendLine("\n</details>\n");
11629
+ return true;
11630
+ });
11631
+ markdownService?.registerMarkdownReader("html", (node, children) => {
11632
+ const tag = getHtmlTagName(node.value);
11633
+ if (tag === "summary") return INodeHelper.createTypeNode("__collapsible_summary", { text: getNodeText(children) });
11634
+ if (tag !== "details") return false;
11635
+ const summaryNode = children.find((child) => child.type === "__collapsible_summary");
11636
+ const contentChildren = children.filter((child) => child.type !== "__collapsible_summary");
11637
+ const title = summaryNode && "text" in summaryNode && typeof summaryNode.text === "string" ? summaryNode.text : extractSummaryTitle(node.value) || "Details";
11638
+ return INodeHelper.createElementNode(CollapsibleNode.getType(), {
11639
+ children: ensureTitleChildren(contentChildren, title),
11640
+ collapsed: !/\sopen(?:\s|>|$)/i.test(node.value),
11641
+ title
11642
+ });
11643
+ });
11644
+ const litexmlService = this.kernel.requireService(ILitexmlService);
11645
+ litexmlService?.registerXMLWriter(CollapsibleNode.getType(), (node, ctx) => {
11646
+ if (!$isCollapsibleNode(node)) return false;
11647
+ return ctx.createXmlNode("collapsible", {
11648
+ collapsed: String(node.isCollapsed()),
11649
+ title: node.getTitle()
11650
+ });
11651
+ });
11652
+ litexmlService?.registerXMLReader("collapsible", (xmlNode, children) => {
11653
+ const title = xmlNode.getAttribute("title") || "Details";
11654
+ return INodeHelper.createElementNode(CollapsibleNode.getType(), {
11655
+ children: ensureTitleChildren(children, title),
11656
+ collapsed: xmlNode.getAttribute("collapsed") === "true",
11657
+ title
11658
+ });
11659
+ });
11660
+ }
11661
+ };
11662
+ const getHtmlTagName = (value) => value.match(/^<\/?\s*([\da-z-]+)/i)?.[1]?.toLowerCase() || "";
11663
+ const escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
11664
+ const extractSummaryTitle = (value) => value.match(/<summary\b[^>]*>([\S\s]*?)<\/summary>/i)?.[1]?.trim() || "";
11665
+ const getNodeText = (children) => children.map((child) => {
11666
+ if (!child || typeof child !== "object") return "";
11667
+ if ("text" in child && typeof child.text === "string") return child.text;
11668
+ if ("children" in child && Array.isArray(child.children)) return getNodeText(child.children);
11669
+ return "";
11670
+ }).join("");
11671
+ const ensureTitleChildren = (children, title) => {
11672
+ const trimmedTitle = title.trim();
11673
+ if (!trimmedTitle || getNodeText(children.slice(0, 1)).trim() === trimmedTitle) return children;
11674
+ return [INodeHelper.createParagraph({ children: [INodeHelper.createTextNode(title)] }), ...children];
11675
+ };
11676
+ //#endregion
9270
11677
  //#region src/headless/extract-media-from-editor-state.ts
9271
11678
  const IMAGE_TYPES = /* @__PURE__ */ new Set(["image", "block-image"]);
9272
11679
  const FILE_TYPE = "file";
@@ -9416,6 +11823,7 @@ const DEFAULT_HEADLESS_EDITOR_PLUGINS = [
9416
11823
  HRPlugin,
9417
11824
  ListPlugin,
9418
11825
  TablePlugin,
11826
+ HeadlessCollapsiblePlugin,
9419
11827
  LitexmlPlugin
9420
11828
  ];
9421
11829
  var HeadlessEditor = class {