@lobehub/editor 4.24.0 → 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 (144) 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/renderer/engine/render-builtin-node.js +17 -8
  140. package/es/types/kernel.d.ts +4 -0
  141. package/es/utils/updatePosition.js +5 -2
  142. package/package.json +3 -1
  143. package/patches/@lexical__yjs@0.42.0.patch +42 -0
  144. package/scripts/postinstall-lexical-patch.cjs +62 -36
@@ -0,0 +1,123 @@
1
+ import { $isTableRowDiffNode } from "../node/TableRowDiffNode.js";
2
+ import { LITEXML_DIFFNODE_COMMAND } from "../command/symbols.js";
3
+ import { useTranslation } from "../../../editor-kernel/react/useTranslation.js";
4
+ import { styles } from "./DiffNodeToolbar/style.js";
5
+ import { ActionIcon, Block } from "@lobehub/ui";
6
+ import { Check, X } from "lucide-react";
7
+ import { useCallback, useEffect, useRef, useState } from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ import { useThemeMode } from "antd-style";
10
+ import { $getNearestNodeFromDOMNode } from "lexical";
11
+ import { createPortal } from "react-dom";
12
+ //#region src/plugins/litexml/react/TableRowDiffToolbar.tsx
13
+ const TableRowDiffToolbar = ({ editor }) => {
14
+ const [activeRow, setActiveRow] = useState(null);
15
+ const hideTimerRef = useRef(void 0);
16
+ const toolbarRef = useRef(null);
17
+ const t = useTranslation();
18
+ const { isDarkMode } = useThemeMode();
19
+ const clearHideTimer = useCallback(() => {
20
+ if (hideTimerRef.current) clearTimeout(hideTimerRef.current);
21
+ }, []);
22
+ const scheduleHide = useCallback(() => {
23
+ clearHideTimer();
24
+ hideTimerRef.current = setTimeout(() => setActiveRow(null), 120);
25
+ }, [clearHideTimer]);
26
+ useEffect(() => {
27
+ const root = editor.getRootElement();
28
+ if (!root) return;
29
+ const onPointerOver = (event) => {
30
+ const target = event.target;
31
+ if (!(target instanceof Element)) return;
32
+ const row = target.closest("tr[data-diff-type]");
33
+ if (!row || !root.contains(row)) return;
34
+ const nodeKey = editor.read(() => {
35
+ const node = $getNearestNodeFromDOMNode(row);
36
+ return $isTableRowDiffNode(node) ? node.getKey() : null;
37
+ });
38
+ if (!nodeKey) return;
39
+ clearHideTimer();
40
+ const rect = row.getBoundingClientRect();
41
+ setActiveRow({
42
+ left: Math.max(8, rect.right - 72),
43
+ nodeKey,
44
+ top: Math.max(8, rect.top + 4)
45
+ });
46
+ };
47
+ const onPointerOut = (event) => {
48
+ const related = event.relatedTarget;
49
+ if (related instanceof Element && (related.closest("tr[data-diff-type]") || toolbarRef.current?.contains(related))) return;
50
+ scheduleHide();
51
+ };
52
+ root.addEventListener("pointerover", onPointerOver);
53
+ root.addEventListener("pointerout", onPointerOut);
54
+ return () => {
55
+ clearHideTimer();
56
+ root.removeEventListener("pointerover", onPointerOver);
57
+ root.removeEventListener("pointerout", onPointerOut);
58
+ };
59
+ }, [
60
+ clearHideTimer,
61
+ editor,
62
+ scheduleHide
63
+ ]);
64
+ if (!activeRow || typeof document === "undefined") return null;
65
+ const dispatch = (action) => {
66
+ editor.dispatchCommand(LITEXML_DIFFNODE_COMMAND, {
67
+ action,
68
+ nodeKey: activeRow.nodeKey
69
+ });
70
+ setActiveRow(null);
71
+ };
72
+ const handleActionPointerDown = (event, action) => {
73
+ event.preventDefault();
74
+ event.stopPropagation();
75
+ dispatch(action);
76
+ };
77
+ return createPortal(/* @__PURE__ */ jsx("div", {
78
+ contentEditable: false,
79
+ onPointerEnter: clearHideTimer,
80
+ onPointerLeave: scheduleHide,
81
+ ref: toolbarRef,
82
+ style: {
83
+ left: activeRow.left,
84
+ pointerEvents: "auto",
85
+ position: "fixed",
86
+ top: activeRow.top,
87
+ zIndex: 10002
88
+ },
89
+ children: /* @__PURE__ */ jsxs(Block, {
90
+ className: isDarkMode ? styles.toolbarDark : styles.toolbarLight,
91
+ gap: 2,
92
+ horizontal: true,
93
+ padding: 2,
94
+ shadow: true,
95
+ variant: "outlined",
96
+ children: [/* @__PURE__ */ jsx(ActionIcon, {
97
+ "aria-label": "Reject row change",
98
+ className: styles.reject,
99
+ danger: true,
100
+ icon: X,
101
+ onPointerDown: (event) => handleActionPointerDown(event, 0),
102
+ size: {
103
+ blockSize: 28,
104
+ size: 16
105
+ },
106
+ title: t("modifier.reject")
107
+ }), /* @__PURE__ */ jsx(ActionIcon, {
108
+ "aria-label": "Accept row change",
109
+ className: styles.accept,
110
+ icon: Check,
111
+ onPointerDown: (event) => handleActionPointerDown(event, 1),
112
+ size: {
113
+ blockSize: 28,
114
+ size: 16
115
+ },
116
+ title: t("modifier.accept")
117
+ })]
118
+ })
119
+ }), document.body);
120
+ };
121
+ TableRowDiffToolbar.displayName = "TableRowDiffToolbar";
122
+ //#endregion
123
+ export { TableRowDiffToolbar as default };
@@ -4,7 +4,10 @@ function hasDiffNode(editor) {
4
4
  if (!editor) return false;
5
5
  const values = editor.getLexicalEditor()?.getEditorState()._nodeMap.values();
6
6
  if (!values) return false;
7
- for (const node of values) if (node.getType() === "diff") return true;
7
+ for (const node of values) {
8
+ const nodeType = node.getType();
9
+ if (nodeType === "diff" || nodeType === "table-row-diff") return true;
10
+ }
8
11
  return false;
9
12
  }
10
13
  function useHasDiffNode(editor) {
@@ -32,4 +35,4 @@ function useHasDiffNode(editor) {
32
35
  return { hasDiff };
33
36
  }
34
37
  //#endregion
35
- export { useHasDiffNode };
38
+ export { hasDiffNode, useHasDiffNode };
@@ -3,12 +3,14 @@ import { useLexicalComposerContext } from "../../../editor-kernel/react/react-co
3
3
  import { INodePlugin } from "../../inode/plugin/index.js";
4
4
  import { LitexmlPlugin } from "../plugin/index.js";
5
5
  import ReactDiffNodeToolbar from "./DiffNodeToolbar/index.js";
6
- import { styles } from "./style.js";
6
+ import { styles, tableCellDiffStyles, tableRowDiffStyles } from "./style.js";
7
+ import TableRowDiffToolbar from "./TableRowDiffToolbar.js";
7
8
  import { useLayoutEffect } from "react";
8
9
  import { jsx } from "react/jsx-runtime";
9
10
  //#region src/plugins/litexml/react/index.tsx
10
11
  const ReactLiteXmlPlugin = () => {
11
12
  const [editor] = useLexicalComposerContext();
13
+ const lexicalEditor = editor.getLexicalEditor();
12
14
  useLayoutEffect(() => {
13
15
  editor.registerPlugin(INodePlugin);
14
16
  editor.registerPlugin(LitexmlPlugin, {
@@ -16,10 +18,12 @@ const ReactLiteXmlPlugin = () => {
16
18
  editor,
17
19
  node
18
20
  }),
21
+ tableRowDiffTheme: tableRowDiffStyles,
22
+ tableCellDiffTheme: tableCellDiffStyles,
19
23
  theme: styles
20
24
  });
21
25
  }, [editor]);
22
- return null;
26
+ return lexicalEditor ? /* @__PURE__ */ jsx(TableRowDiffToolbar, { editor: lexicalEditor }) : null;
23
27
  };
24
28
  ReactLiteXmlPlugin.displayName = "ReactLiteXmlPlugin";
25
29
  //#endregion
@@ -3,17 +3,17 @@ import { createStaticStyles } from "antd-style";
3
3
  const styles = createStaticStyles(({ css, cssVar }) => css`
4
4
  position: relative;
5
5
 
6
- .toolbar {
6
+ > .toolbar {
7
7
  position: absolute;
8
8
  z-index: 10;
9
- inset-block-end: 0;
10
- inset-inline-end: 8px;
9
+ inset-block-start: 0;
10
+ inset-inline-start: 8px;
11
11
 
12
12
  opacity: 0;
13
13
  }
14
14
 
15
15
  &:hover {
16
- .toolbar {
16
+ > .toolbar {
17
17
  opacity: 1;
18
18
  }
19
19
  }
@@ -120,5 +120,18 @@ const styles = createStaticStyles(({ css, cssVar }) => css`
120
120
  }
121
121
  }
122
122
  `);
123
+ const tableRowDiffStyles = createStaticStyles(({ css, cssVar }) => css`
124
+ &[data-diff-type='remove'] {
125
+ color: ${cssVar.colorTextQuaternary};
126
+ text-decoration: line-through;
127
+ background: ${cssVar.colorErrorBg};
128
+ }
129
+
130
+ &[data-diff-type='add'] {
131
+ background: ${cssVar.colorSuccessBg};
132
+ box-shadow: inset 3px 0 0 ${cssVar.colorSuccess};
133
+ }
134
+ `);
135
+ const tableCellDiffStyles = tableRowDiffStyles;
123
136
  //#endregion
124
- export { styles };
137
+ export { styles, tableCellDiffStyles, tableRowDiffStyles };
@@ -0,0 +1,153 @@
1
+ import { $createDiffNode, DiffNode } from "./node/DiffNode.js";
2
+ import { $createTableCellDiffNode, TableCellDiffNode } from "./node/TableCellDiffNode.js";
3
+ import { $cloneNode } from "./utils/index.js";
4
+ import { $createTableCellNode, $isTableCellNode, $isTableNode, $isTableRowNode, TableNode } from "@lexical/table";
5
+ import { $nodesOfType } from "lexical";
6
+ //#region src/plugins/litexml/table-cell-diff.ts
7
+ function copyCellStructure(source, target) {
8
+ target.setRowSpan(source.getRowSpan());
9
+ target.setBackgroundColor(source.getBackgroundColor());
10
+ target.setVerticalAlign(source.getVerticalAlign());
11
+ }
12
+ function $createTableCellDiffFromCell(editor, cell, diffType, changeId) {
13
+ const diffCell = $createTableCellDiffNode(diffType, changeId, cell.getHeaderStyles(), cell.getColSpan(), cell.getWidth());
14
+ copyCellStructure(cell, diffCell);
15
+ const diff = $createDiffNode(diffType);
16
+ diff.append(...cell.getChildren().map((child) => $cloneNode(child, editor)));
17
+ diffCell.append(diff);
18
+ return diffCell;
19
+ }
20
+ function $createPlainTableCellFromDiff(editor, cell) {
21
+ const plainCell = $createTableCellNode(cell.getHeaderStyles(), cell.getColSpan(), cell.getWidth());
22
+ copyCellStructure(cell, plainCell);
23
+ const diff = cell.getFirstChild();
24
+ if (diff instanceof DiffNode) plainCell.append(...diff.getChildren().map((child) => $cloneNode(child, editor)));
25
+ else plainCell.append(...cell.getChildren().map((child) => $cloneNode(child, editor)));
26
+ return plainCell;
27
+ }
28
+ function $getTableForCell(cell) {
29
+ const row = cell.getParent();
30
+ if (!$isTableRowNode(row)) return null;
31
+ const table = row.getParent();
32
+ return $isTableNode(table) ? table : null;
33
+ }
34
+ function $getTableCellColumnIndex(cell) {
35
+ const row = cell.getParent();
36
+ if (!$isTableRowNode(row)) return -1;
37
+ let index = 0;
38
+ for (const sibling of row.getChildren()) {
39
+ if (sibling === cell) return index;
40
+ if ($isTableCellNode(sibling)) index += sibling.getColSpan();
41
+ }
42
+ return -1;
43
+ }
44
+ function $getLogicalRowWidth(cell) {
45
+ const row = cell.getParent();
46
+ if (!$isTableRowNode(row)) return 0;
47
+ return row.getChildren().reduce((total, child) => total + ($isTableCellNode(child) ? child.getColSpan() : 0), 0);
48
+ }
49
+ function $getTableCellDiffGroup(cell) {
50
+ const changeId = cell.getChangeId();
51
+ const table = $getTableForCell(cell);
52
+ if (!changeId || !table) return [cell];
53
+ return $nodesOfType(TableCellDiffNode).filter((candidate) => candidate.getChangeId() === changeId && candidate.getDiffType() === cell.getDiffType() && $getTableForCell(candidate) === table);
54
+ }
55
+ function $updateTableWidthsForCellInsertion(table, rowWidthBefore, columnIndex, insertedSpan) {
56
+ const currentWidths = table.getColWidths() || Array.from({ length: rowWidthBefore }, () => 250);
57
+ if (rowWidthBefore < currentWidths.length) return;
58
+ const fallback = currentWidths[Math.max(0, columnIndex - 1)] || 250;
59
+ const next = [...currentWidths];
60
+ next.splice(columnIndex, 0, ...Array.from({ length: insertedSpan }, () => fallback));
61
+ table.setColWidths(next);
62
+ }
63
+ function $removeTableWidthsForCompleteCellGroup(table, group, columnIndex, span) {
64
+ const rows = table.getChildren().filter($isTableRowNode);
65
+ const rowKeys = new Set(group.map((cell) => cell.getParent()?.getKey()));
66
+ if (rows.length !== rowKeys.size) return;
67
+ const widths = table.getColWidths();
68
+ if (!widths) return;
69
+ const next = [...widths];
70
+ next.splice(columnIndex, span);
71
+ table.setColWidths(next);
72
+ }
73
+ function $shrinkTableWidthsAfterCellRemoval(table, columnIndex, span) {
74
+ const widths = table.getColWidths();
75
+ if (!widths) return;
76
+ if (table.getChildren().reduce((max, row) => {
77
+ if (!$isTableRowNode(row)) return max;
78
+ return Math.max(max, row.getChildren().reduce((total, cell) => total + ($isTableCellNode(cell) ? cell.getColSpan() : 0), 0));
79
+ }, 0) > widths.length - span) return;
80
+ const next = [...widths];
81
+ next.splice(columnIndex, span);
82
+ table.setColWidths(next);
83
+ }
84
+ function $getMaximumLogicalColumnCount(table) {
85
+ return table.getChildren().reduce((maximum, row) => {
86
+ if (!$isTableRowNode(row)) return maximum;
87
+ const columnCount = row.getChildren().reduce((total, cell) => {
88
+ return total + ($isTableCellNode(cell) ? cell.getColSpan() : 0);
89
+ }, 0);
90
+ return Math.max(maximum, columnCount);
91
+ }, 0);
92
+ }
93
+ function $hasLegacyTableCellDiffs() {
94
+ return $nodesOfType(DiffNode).some((diff) => {
95
+ return (diff.diffType === "add" || diff.diffType === "remove") && $isTableRowNode(diff.getParent()) && diff.getChildren().some($isTableCellNode);
96
+ });
97
+ }
98
+ function $hasIncompleteTableWidths() {
99
+ return $nodesOfType(TableNode).some((table) => {
100
+ return (table.getColWidths()?.length || 0) < $getMaximumLogicalColumnCount(table);
101
+ });
102
+ }
103
+ /**
104
+ * Migrates the old `TableRow > DiffNode > TableCellNode` representation into
105
+ * cell-shaped diff nodes and makes sure the table has a width for every
106
+ * logical column. Safe to call repeatedly.
107
+ */
108
+ function $normalizeLegacyTableCellDiffs(editor) {
109
+ let changed = false;
110
+ $nodesOfType(DiffNode).forEach((legacyDiff) => {
111
+ const diffType = legacyDiff.diffType;
112
+ if (diffType !== "add" && diffType !== "remove" || !$isTableRowNode(legacyDiff.getParent())) return;
113
+ const cells = legacyDiff.getChildren().filter($isTableCellNode);
114
+ if (cells.length === 0) return;
115
+ const changeId = `legacy-table-cell-${legacyDiff.getKey()}`;
116
+ cells.forEach((cell) => {
117
+ legacyDiff.insertBefore($createTableCellDiffFromCell(editor, cell, diffType, changeId));
118
+ });
119
+ legacyDiff.remove();
120
+ changed = true;
121
+ });
122
+ $nodesOfType(TableNode).forEach((table) => {
123
+ const requiredWidthCount = $getMaximumLogicalColumnCount(table);
124
+ const currentWidths = table.getColWidths() || [];
125
+ if (currentWidths.length >= requiredWidthCount) return;
126
+ const fallback = currentWidths.at(-1) || 250;
127
+ table.setColWidths([...currentWidths, ...Array.from({ length: requiredWidthCount - currentWidths.length }, () => fallback)]);
128
+ changed = true;
129
+ });
130
+ return changed;
131
+ }
132
+ function registerLegacyTableCellDiffNormalization(editor) {
133
+ let destroyed = false;
134
+ let scheduled = false;
135
+ const unregister = editor.registerUpdateListener(() => {
136
+ if (scheduled || destroyed) return;
137
+ scheduled = true;
138
+ queueMicrotask(() => {
139
+ scheduled = false;
140
+ if (destroyed) return;
141
+ if (!editor.getEditorState().read(() => $hasLegacyTableCellDiffs() || $hasIncompleteTableWidths())) return;
142
+ editor.update(() => {
143
+ $normalizeLegacyTableCellDiffs(editor);
144
+ }, { discrete: true });
145
+ });
146
+ });
147
+ return () => {
148
+ destroyed = true;
149
+ unregister();
150
+ };
151
+ }
152
+ //#endregion
153
+ export { $createPlainTableCellFromDiff, $createTableCellDiffFromCell, $getLogicalRowWidth, $getTableCellColumnIndex, $getTableCellDiffGroup, $getTableForCell, $normalizeLegacyTableCellDiffs, $removeTableWidthsForCompleteCellGroup, $shrinkTableWidthsAfterCellRemoval, $updateTableWidthsForCellInsertion, registerLegacyTableCellDiffNormalization };
@@ -0,0 +1,82 @@
1
+ import { $cloneNode } from "./utils/index.js";
2
+ import { $createTableRowDiffNode, TableRowDiffNode } from "./node/TableRowDiffNode.js";
3
+ import { $createTableRowNode, $isTableCellNode, $isTableNode } from "@lexical/table";
4
+ import { $nodesOfType } from "lexical";
5
+ //#region src/plugins/litexml/table-row-diff.ts
6
+ function $areTableRowStructuresCompatible(previousRow, nextRow) {
7
+ const previousCells = previousRow.getChildren();
8
+ const nextCells = nextRow.getChildren();
9
+ if (previousCells.length !== nextCells.length || !previousCells.every($isTableCellNode) || !nextCells.every($isTableCellNode)) return false;
10
+ return previousCells.every((previousCell, index) => {
11
+ const nextCell = nextCells[index];
12
+ return $isTableCellNode(nextCell) && previousCell.getColSpan() === nextCell.getColSpan() && previousCell.getRowSpan() === nextCell.getRowSpan();
13
+ });
14
+ }
15
+ function $createTableRowDiffFromRow(editor, row, diffType, changeId) {
16
+ const source = $cloneNode(row, editor);
17
+ const diffRow = $createTableRowDiffNode(diffType, changeId, row.getHeight());
18
+ diffRow.append(...source.getChildren());
19
+ return diffRow;
20
+ }
21
+ function $createPlainTableRowFromDiff(editor, row) {
22
+ const plainRow = $createTableRowNode(row.getHeight());
23
+ plainRow.append(...row.getChildren().map((child) => $cloneNode(child, editor)));
24
+ return plainRow;
25
+ }
26
+ function $getTableRowDiffPair(node) {
27
+ const changeId = node.getChangeId();
28
+ if (!changeId) return null;
29
+ const matchingRows = $nodesOfType(TableRowDiffNode).filter((candidate) => candidate.getChangeId() === changeId);
30
+ if (matchingRows.length !== 2) return null;
31
+ const remove = matchingRows.find((candidate) => candidate.getDiffType() === "remove");
32
+ const add = matchingRows.find((candidate) => candidate.getDiffType() === "add");
33
+ if (!remove || !add) return null;
34
+ const parent = remove.getParent();
35
+ if (!$isTableNode(parent) || add.getParent() !== parent || remove.getNextSibling() !== add || !$areTableRowStructuresCompatible(remove, add)) return null;
36
+ return {
37
+ add,
38
+ remove
39
+ };
40
+ }
41
+ function $normalizeTableRowDiffPairs() {
42
+ const rows = $nodesOfType(TableRowDiffNode);
43
+ const groups = /* @__PURE__ */ new Map();
44
+ rows.forEach((row) => {
45
+ const changeId = row.getChangeId();
46
+ if (!changeId) return;
47
+ const group = groups.get(changeId) || [];
48
+ group.push(row);
49
+ groups.set(changeId, group);
50
+ });
51
+ let changed = false;
52
+ groups.forEach((group) => {
53
+ if (group.length === 2 ? $getTableRowDiffPair(group[0]) : null) return;
54
+ group.forEach((row) => row.setChangeId());
55
+ changed = true;
56
+ });
57
+ return changed;
58
+ }
59
+ function registerTableRowDiffNormalization(editor) {
60
+ let destroyed = false;
61
+ let scheduled = false;
62
+ const unregister = editor.registerUpdateListener(() => {
63
+ if (scheduled || destroyed) return;
64
+ scheduled = true;
65
+ queueMicrotask(() => {
66
+ scheduled = false;
67
+ if (destroyed) return;
68
+ if (!editor.getEditorState().read(() => {
69
+ return $nodesOfType(TableRowDiffNode).some((row) => row.getChangeId() && !$getTableRowDiffPair(row));
70
+ })) return;
71
+ editor.update(() => {
72
+ $normalizeTableRowDiffPairs();
73
+ });
74
+ });
75
+ });
76
+ return () => {
77
+ destroyed = true;
78
+ unregister();
79
+ };
80
+ }
81
+ //#endregion
82
+ export { $areTableRowStructuresCompatible, $createPlainTableRowFromDiff, $createTableRowDiffFromRow, $getTableRowDiffPair, $normalizeTableRowDiffPairs, registerTableRowDiffNormalization };
@@ -72,8 +72,29 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}, parentTyp
72
72
  return ret;
73
73
  }
74
74
  if (child.value.startsWith("<!--") && child.value.endsWith("-->")) return ret;
75
- const tag = child.value.replaceAll(/^<\/?|>$/g, "");
75
+ const tag = getHtmlTagName(child.value);
76
76
  const isEndTag = child.value.startsWith("</");
77
+ const pairedTag = child.value.match(/^<\s*([a-z0-9-]+)\b[^>]*>([\s\S]*)<\/\s*\1\s*>$/i);
78
+ if (!isEndTag && pairedTag) {
79
+ const reader = markdownReaders["html"];
80
+ const htmlChildren = pairedTag[2] ? [INodeHelper.createTextNode(pairedTag[2])] : [];
81
+ if (Array.isArray(reader)) for (const element of reader) {
82
+ const inode = element(child, htmlChildren, index);
83
+ if (inode) {
84
+ ret.push(inode);
85
+ return ret;
86
+ }
87
+ }
88
+ else if (typeof reader === "function") {
89
+ const inode = reader(child, htmlChildren, index);
90
+ if (inode) {
91
+ ret.push(inode);
92
+ return ret;
93
+ }
94
+ }
95
+ ret.push(INodeHelper.createTextNode(child.value));
96
+ return ret;
97
+ }
77
98
  if (selfClosingHtmlTags.has(tag)) {
78
99
  const reader = markdownReaders["html"];
79
100
  if (Array.isArray(reader)) for (const element of reader) {
@@ -160,6 +181,9 @@ function convertMdastToLexical(node, index, ctx, markdownReaders = {}, parentTyp
160
181
  }
161
182
  }
162
183
  }
184
+ function getHtmlTagName(value) {
185
+ return value.match(/^<\/?\s*([a-z0-9-]+)/i)?.[1]?.toLowerCase() || value.replaceAll(/^<\/?|>$/g, "");
186
+ }
163
187
  function registerDefaultReaders(markdownReaders) {
164
188
  if (!markdownReaders["root"]) markdownReaders["root"] = (node, children) => {
165
189
  return {
@@ -13,6 +13,7 @@ init_plugin();
13
13
  init_debug();
14
14
  const DEFAULT_PASTE_MARKDOWN_AUTO_CONVERT_THRESHOLD = 5;
15
15
  const RICH_HTML_SELECTOR = "strong,em,b,i,h1,h2,h3,h4,h5,h6,ul,ol,table,img,blockquote,pre>code,a[href]";
16
+ const normalizeMarkdownForClipboard = (markdown) => markdown.replace(/\r?\n$/, "");
16
17
  const MARKDOWN_DETECTION_RULES = [
17
18
  {
18
19
  name: "headers",
@@ -209,8 +210,9 @@ const MarkdownPlugin = class extends KernelPlugin {
209
210
  if (!clipboardData) return false;
210
211
  const selection = $getSelection();
211
212
  if (!selection || selection.isCollapsed()) return false;
212
- const markdown = this.markdownDataSource.write(editor, { selection: true });
213
- if (!markdown) return false;
213
+ const rawMarkdown = this.markdownDataSource.write(editor, { selection: true });
214
+ if (!rawMarkdown) return false;
215
+ const markdown = normalizeMarkdownForClipboard(rawMarkdown);
214
216
  event.preventDefault();
215
217
  const data = $getClipboardDataFromSelection(selection);
216
218
  setLexicalClipboardDataTransfer(clipboardData, data);
@@ -283,4 +285,4 @@ const MarkdownPlugin = class extends KernelPlugin {
283
285
  }
284
286
  };
285
287
  //#endregion
286
- export { MarkdownPlugin };
288
+ export { MarkdownPlugin, normalizeMarkdownForClipboard };
@@ -3,13 +3,14 @@ import { init_utils, noop } from "../../../editor-kernel/utils.js";
3
3
  import { useLexicalComposerContext } from "../../../editor-kernel/react/react-context.js";
4
4
  import { useLexicalEditor } from "../../../editor-kernel/react/useLexicalEditor.js";
5
5
  import { $splitNodeContainingQuery } from "../utils/utils.js";
6
- import { ISlashService, flattenSlashOptions } from "../service/i-slash-service.js";
6
+ import { ISlashService } from "../service/i-slash-service.js";
7
7
  import { SlashPlugin } from "../plugin/index.js";
8
8
  import SlashMenu from "./components/SlashMenu.js";
9
+ import { findSlashOptionByKey, getNextSlashActiveKey, getNextSlashSpatialActiveKey } from "./menu-utils.js";
9
10
  import { setCancelablePromise } from "./utils.js";
10
11
  import { Children, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
11
12
  import { jsx } from "react/jsx-runtime";
12
- import { COMMAND_PRIORITY_CRITICAL, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_DOWN_COMMAND, KEY_ESCAPE_COMMAND, KEY_TAB_COMMAND } from "lexical";
13
+ import { COMMAND_PRIORITY_CRITICAL, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_DOWN_COMMAND, KEY_ESCAPE_COMMAND, KEY_TAB_COMMAND } from "lexical";
13
14
  import { mergeRegister } from "@lexical/utils";
14
15
  //#region src/plugins/slash/react/ReactSlashPlugin.tsx
15
16
  init_utils();
@@ -36,12 +37,10 @@ const ReactSlashPlugin = ({ children, anchorClassName, getPopupContainer, placem
36
37
  const handleActiveKeyChange = useCallback((key) => {
37
38
  setActiveKey(key);
38
39
  }, []);
39
- const setInitialActiveKey = useCallback((items) => {
40
- if (!activeKey) {
41
- const firstOption = flattenSlashOptions(items)[0];
42
- setActiveKey(firstOption?.key ? String(firstOption.key) : null);
43
- }
44
- }, [activeKey]);
40
+ const updateMenuOptions = useCallback((items) => {
41
+ setOptions(items);
42
+ setActiveKey(getNextSlashActiveKey(items, null, "forward"));
43
+ }, []);
45
44
  useLayoutEffect(() => {
46
45
  const options = Children.map(children, (child) => {
47
46
  if (!child) return null;
@@ -58,18 +57,14 @@ const ReactSlashPlugin = ({ children, anchorClassName, getPopupContainer, placem
58
57
  triggerOpen: (ctx) => {
59
58
  setResolution(ctx);
60
59
  cancelRef.current.cancel();
61
- if (Array.isArray(ctx.items)) {
62
- setOptions(ctx.items);
63
- setInitialActiveKey(ctx.items);
64
- } else {
60
+ if (Array.isArray(ctx.items)) updateMenuOptions(ctx.items);
61
+ else {
65
62
  setLoading(true);
66
63
  const pr = setCancelablePromise((resolve, reject) => {
67
64
  ctx.items(ctx.match || null).then(resolve, reject).finally(() => setLoading(false));
68
65
  });
69
66
  pr.promise.then((items) => {
70
- const typedItems = items;
71
- setOptions(typedItems);
72
- setInitialActiveKey(typedItems);
67
+ updateMenuOptions(items);
73
68
  });
74
69
  cancelRef.current.cancel = () => {
75
70
  pr.cancel();
@@ -89,7 +84,7 @@ const ReactSlashPlugin = ({ children, anchorClassName, getPopupContainer, placem
89
84
  }, [
90
85
  editor,
91
86
  close,
92
- setInitialActiveKey
87
+ updateMenuOptions
93
88
  ]);
94
89
  useLayoutEffect(() => {
95
90
  const slash = editor.requireService(ISlashService);
@@ -118,30 +113,15 @@ const ReactSlashPlugin = ({ children, anchorClassName, getPopupContainer, placem
118
113
  close
119
114
  ]);
120
115
  useLexicalEditor(() => {
121
- const pureOptions = flattenSlashOptions(options).filter((item) => Boolean(item.key));
122
- return mergeRegister(editor.registerHighCommand(KEY_ARROW_DOWN_COMMAND, (payload) => {
123
- const event = payload;
124
- if (pureOptions !== null && pureOptions.length) {
125
- const currentIndex = activeKey ? pureOptions.findIndex((opt) => opt.key === activeKey) : -1;
126
- const newIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % pureOptions.length;
127
- setActiveKey(String(pureOptions[newIndex].key));
128
- event.preventDefault();
129
- event.stopImmediatePropagation();
130
- return true;
131
- }
132
- return false;
133
- }, COMMAND_PRIORITY_CRITICAL), editor.registerHighCommand(KEY_ARROW_UP_COMMAND, (payload) => {
134
- const event = payload;
135
- if (pureOptions !== null && pureOptions.length) {
136
- const currentIndex = activeKey ? pureOptions.findIndex((opt) => opt.key === activeKey) : -1;
137
- const newIndex = currentIndex === -1 ? pureOptions.length - 1 : (currentIndex - 1 + pureOptions.length) % pureOptions.length;
138
- setActiveKey(String(pureOptions[newIndex].key));
139
- event.preventDefault();
140
- event.stopImmediatePropagation();
141
- return true;
142
- }
143
- return false;
144
- }, COMMAND_PRIORITY_CRITICAL), editor.registerHighCommand(KEY_ESCAPE_COMMAND, (payload) => {
116
+ const activateSpatialSibling = (event, direction) => {
117
+ const nextKey = getNextSlashSpatialActiveKey(options, activeKey, direction);
118
+ if (!nextKey) return false;
119
+ setActiveKey(nextKey);
120
+ event.preventDefault();
121
+ event.stopImmediatePropagation();
122
+ return true;
123
+ };
124
+ return mergeRegister(editor.registerHighCommand(KEY_ARROW_DOWN_COMMAND, (payload) => activateSpatialSibling(payload, "down"), COMMAND_PRIORITY_CRITICAL), editor.registerHighCommand(KEY_ARROW_UP_COMMAND, (payload) => activateSpatialSibling(payload, "up"), COMMAND_PRIORITY_CRITICAL), editor.registerHighCommand(KEY_ARROW_RIGHT_COMMAND, (payload) => activateSpatialSibling(payload, "right"), COMMAND_PRIORITY_CRITICAL), editor.registerHighCommand(KEY_ARROW_LEFT_COMMAND, (payload) => activateSpatialSibling(payload, "left"), COMMAND_PRIORITY_CRITICAL), editor.registerHighCommand(KEY_ESCAPE_COMMAND, (payload) => {
145
125
  const event = payload;
146
126
  event.preventDefault();
147
127
  event.stopImmediatePropagation();
@@ -150,7 +130,7 @@ const ReactSlashPlugin = ({ children, anchorClassName, getPopupContainer, placem
150
130
  }, COMMAND_PRIORITY_CRITICAL), editor.registerHighCommand(KEY_TAB_COMMAND, (payload) => {
151
131
  const event = payload;
152
132
  if (options === null || activeKey === null) return false;
153
- const selectedOption = flattenSlashOptions(options).find((opt) => opt.key === activeKey);
133
+ const selectedOption = findSlashOptionByKey(options, activeKey);
154
134
  if (!selectedOption) return false;
155
135
  event.preventDefault();
156
136
  event.stopImmediatePropagation();
@@ -161,7 +141,7 @@ const ReactSlashPlugin = ({ children, anchorClassName, getPopupContainer, placem
161
141
  event.preventDefault();
162
142
  event.stopImmediatePropagation();
163
143
  if (options !== null && activeKey !== null) {
164
- const selectedOption = flattenSlashOptions(options).find((opt) => opt.key === activeKey);
144
+ const selectedOption = findSlashOptionByKey(options, activeKey);
165
145
  if (selectedOption) handleMenuSelect(selectedOption);
166
146
  }
167
147
  return true;
@@ -171,7 +151,6 @@ const ReactSlashPlugin = ({ children, anchorClassName, getPopupContainer, placem
171
151
  }, [
172
152
  options,
173
153
  activeKey,
174
- handleActiveKeyChange,
175
154
  handleMenuSelect,
176
155
  isOpen
177
156
  ]);