@lobehub/editor 4.24.1 → 4.25.1

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 (148) hide show
  1. package/es/codemirror/style.js +42 -0
  2. package/es/editor-kernel/kernel.d.ts +9 -0
  3. package/es/editor-kernel/kernel.js +74 -11
  4. package/es/editor-kernel/react/useDecorators.js +2 -4
  5. package/es/headless/collapsible-plugin.js +74 -0
  6. package/es/headless/index.js +2 -0
  7. package/es/headless.d.ts +30 -0
  8. package/es/headless.js +2618 -139
  9. package/es/index.d.ts +26 -3
  10. package/es/index.js +17 -2
  11. package/es/locale/index.d.ts +14 -0
  12. package/es/locale/index.js +14 -0
  13. package/es/plugins/block/command/index.js +24 -0
  14. package/es/plugins/block/plugin/index.js +7 -5
  15. package/es/plugins/block/react/ReactBlockPlugin.js +43 -1
  16. package/es/plugins/block/react/core/menu-position.js +20 -0
  17. package/es/plugins/block/react/drag/drag-session.js +3 -2
  18. package/es/plugins/block/react/drag/drag-utils.js +13 -1
  19. package/es/plugins/codemirror-block/index.d.ts +2 -1
  20. package/es/plugins/codemirror-block/plugin/index.js +2 -0
  21. package/es/plugins/codemirror-block/react/CodemirrorNode.js +95 -5
  22. package/es/plugins/codemirror-block/react/useCodemirrorEditLock.js +33 -0
  23. package/es/plugins/codemirror-block/service/index.d.ts +40 -0
  24. package/es/plugins/codemirror-block/service/index.js +59 -0
  25. package/es/plugins/collapsible/command/index.d.ts +17 -0
  26. package/es/plugins/collapsible/command/index.js +72 -0
  27. package/es/plugins/collapsible/index.d.ts +7 -0
  28. package/es/plugins/collapsible/node/CollapsibleNode.d.ts +30 -0
  29. package/es/plugins/collapsible/node/CollapsibleNode.js +138 -0
  30. package/es/plugins/collapsible/plugin/index.d.ts +11 -0
  31. package/es/plugins/collapsible/plugin/index.js +535 -0
  32. package/es/plugins/collapsible/react/ReactCollapsiblePlugin.d.ts +6 -0
  33. package/es/plugins/collapsible/react/ReactCollapsiblePlugin.js +19 -0
  34. package/es/plugins/collapsible/react/index.d.ts +3 -0
  35. package/es/plugins/collapsible/react/style.js +67 -0
  36. package/es/plugins/collapsible/react/type.d.ts +6 -0
  37. package/es/plugins/common/plugin/index.js +14 -4
  38. package/es/plugins/common/plugin/progressive-select-all.js +81 -0
  39. package/es/plugins/common/plugin/register.js +73 -4
  40. package/es/plugins/common/react/ReactPlainText.js +27 -9
  41. package/es/plugins/link/command/index.js +26 -3
  42. package/es/plugins/link/conversion/index.js +237 -0
  43. package/es/plugins/link/index.d.ts +5 -3
  44. package/es/plugins/link/node/LinkBlockCardNode.d.ts +16 -0
  45. package/es/plugins/link/node/LinkBlockCardNode.js +66 -0
  46. package/es/plugins/link/node/LinkCardNode.d.ts +42 -0
  47. package/es/plugins/link/node/LinkCardNode.js +161 -0
  48. package/es/plugins/link/node/LinkIframeNode.d.ts +33 -0
  49. package/es/plugins/link/node/LinkIframeNode.js +131 -0
  50. package/es/plugins/link/node/LinkNode.d.ts +48 -2
  51. package/es/plugins/link/node/LinkNode.js +14 -6
  52. package/es/plugins/link/node/SchemaNode.d.ts +36 -0
  53. package/es/plugins/link/node/SchemaNode.js +149 -0
  54. package/es/plugins/link/normalization/index.d.ts +7 -0
  55. package/es/plugins/link/normalization/index.js +38 -0
  56. package/es/plugins/link/plugin/index.d.ts +26 -1
  57. package/es/plugins/link/plugin/index.js +124 -4
  58. package/es/plugins/link/react/ReactLinkPlugin.js +165 -7
  59. package/es/plugins/link/react/components/LinkCard.js +235 -0
  60. package/es/plugins/link/react/components/LinkEdit.js +93 -14
  61. package/es/plugins/link/react/components/LinkIframe.js +128 -0
  62. package/es/plugins/link/react/components/LinkToolbar.js +440 -111
  63. package/es/plugins/link/react/components/SchemaLink.js +49 -0
  64. package/es/plugins/link/react/index.d.ts +2 -2
  65. package/es/plugins/link/react/renderer-registry.d.ts +81 -0
  66. package/es/plugins/link/react/renderer-registry.js +80 -0
  67. package/es/plugins/link/react/style.js +63 -1
  68. package/es/plugins/link/react/type.d.ts +24 -1
  69. package/es/plugins/link/service/i-link-service.d.ts +156 -1
  70. package/es/plugins/link/service/i-link-service.js +258 -5
  71. package/es/plugins/list/plugin/index.js +9 -2
  72. package/es/plugins/litexml/command/diffCommand.js +94 -14
  73. package/es/plugins/litexml/command/index.js +187 -6
  74. package/es/plugins/litexml/diff-validation.js +237 -0
  75. package/es/plugins/litexml/node/DiffContentNode.js +67 -0
  76. package/es/plugins/litexml/node/DiffNode.d.ts +1 -1
  77. package/es/plugins/litexml/node/DiffNode.js +5 -2
  78. package/es/plugins/litexml/node/TableCellDiffNode.js +77 -0
  79. package/es/plugins/litexml/node/TableRowDiffNode.js +73 -0
  80. package/es/plugins/litexml/plugin/index.d.ts +2 -0
  81. package/es/plugins/litexml/plugin/index.js +62 -9
  82. package/es/plugins/litexml/react/DiffNodeToolbar/index.js +10 -12
  83. package/es/plugins/litexml/react/TableRowDiffToolbar.js +123 -0
  84. package/es/plugins/litexml/react/hooks/useHasDiffNode.js +5 -2
  85. package/es/plugins/litexml/react/index.js +6 -2
  86. package/es/plugins/litexml/react/style.js +18 -5
  87. package/es/plugins/litexml/table-cell-diff.js +153 -0
  88. package/es/plugins/litexml/table-row-diff.js +82 -0
  89. package/es/plugins/markdown/data-source/markdown/parse.js +25 -1
  90. package/es/plugins/markdown/plugin/index.js +5 -3
  91. package/es/plugins/slash/react/ReactSlashPlugin.js +22 -43
  92. package/es/plugins/slash/react/components/DefaultSlashMenu.js +76 -16
  93. package/es/plugins/slash/react/menu-utils.js +84 -0
  94. package/es/plugins/slash/service/i-slash-service.d.ts +3 -0
  95. package/es/plugins/table/plugin/index.js +4 -1
  96. package/es/plugins/table/plugin/single-cell-paste.js +79 -0
  97. package/es/plugins/table/react/TableController/hooks.js +7 -4
  98. package/es/plugins/table/react/hooks.js +2 -2
  99. package/es/plugins/table/react/index.js +2 -0
  100. package/es/plugins/table/react/style.js +15 -5
  101. package/es/plugins/table/react/useScrollFocusedTableCellIntoView.js +46 -0
  102. package/es/plugins/toc/index.d.ts +10 -0
  103. package/es/plugins/toc/plugin/index.d.ts +8 -0
  104. package/es/plugins/toc/plugin/index.js +56 -0
  105. package/es/plugins/toc/react/ReactTocPlugin.d.ts +6 -0
  106. package/es/plugins/toc/react/ReactTocPlugin.js +36 -0
  107. package/es/plugins/toc/react/getNearestScrollContainer.d.ts +6 -0
  108. package/es/plugins/toc/react/getNearestScrollContainer.js +18 -0
  109. package/es/plugins/toc/react/hooks/useActiveHeading.js +59 -0
  110. package/es/plugins/toc/react/hooks/useTocItems.js +50 -0
  111. package/es/plugins/toc/react/index.d.ts +5 -0
  112. package/es/plugins/toc/react/type.d.ts +30 -0
  113. package/es/plugins/toc/react/useToc.d.ts +5 -0
  114. package/es/plugins/toc/react/useToc.js +41 -0
  115. package/es/plugins/toc/service/i-toc-service.d.ts +39 -0
  116. package/es/plugins/toc/service/i-toc-service.js +125 -0
  117. package/es/plugins/toc/service/index.d.ts +3 -0
  118. package/es/plugins/toc/types.d.ts +23 -0
  119. package/es/plugins/toc/utils/index.d.ts +6 -0
  120. package/es/plugins/toc/utils/index.js +30 -0
  121. package/es/plugins/toolbar/react/index.js +26 -12
  122. package/es/plugins/toolbar/react/style.js +3 -2
  123. package/es/plugins/virtual-block/plugin/register.js +34 -12
  124. package/es/plugins/yjs/index.d.ts +6 -0
  125. package/es/plugins/yjs/plugin/index.d.ts +7 -0
  126. package/es/plugins/yjs/plugin/index.js +191 -0
  127. package/es/plugins/yjs/plugin/types.d.ts +16 -0
  128. package/es/plugins/yjs/plugin/utils/awareness.js +9 -0
  129. package/es/plugins/yjs/plugin/utils/editor-state.js +47 -0
  130. package/es/plugins/yjs/plugin/utils/history.js +31 -0
  131. package/es/plugins/yjs/plugin/utils/node-properties.js +39 -0
  132. package/es/plugins/yjs/plugin/utils/sync.js +24 -0
  133. package/es/plugins/yjs/react/codemirrorEditLockProvider.js +59 -0
  134. package/es/plugins/yjs/react/index.d.ts +23 -0
  135. package/es/plugins/yjs/react/index.js +200 -0
  136. package/es/plugins/yjs/service/index.d.ts +47 -0
  137. package/es/plugins/yjs/service/index.js +84 -0
  138. package/es/plugins/yjs/websocket-provider.d.ts +78 -0
  139. package/es/plugins/yjs/websocket-provider.js +263 -0
  140. package/es/react/hooks/useEditor.d.ts +24 -2
  141. package/es/react/hooks/useEditor.js +13 -3
  142. package/es/react.d.ts +2 -2
  143. package/es/renderer/LexicalRenderer.js +9 -2
  144. package/es/types/kernel.d.ts +16 -0
  145. package/es/utils/updatePosition.js +5 -2
  146. package/package.json +3 -1
  147. package/patches/@lexical__yjs@0.42.0.patch +42 -0
  148. package/scripts/postinstall-lexical-patch.cjs +62 -36
@@ -1,7 +1,6 @@
1
1
  import { INodeHelper, init_helper } from "../../../editor-kernel/inode/helper.js";
2
2
  import { $isCursorInQuote, $isCursorInTable, createBlockNode, init_utils, sampleReader } from "../utils/index.js";
3
3
  import { KernelPlugin, init_plugin } from "../../../editor-kernel/plugin.js";
4
- import { init_utils as init_utils$1, noop } from "../../../editor-kernel/utils.js";
5
4
  import { registerCommands } from "../command/index.js";
6
5
  import { CursorNode, registerCursorNode } from "../node/cursor.js";
7
6
  import { ILitexmlService } from "../../litexml/service/litexml-service.js";
@@ -12,14 +11,14 @@ import TextDataSource from "../data-source/text-data-source.js";
12
11
  import { registerBreakLineClick } from "../node/ElementDOMSlot.js";
13
12
  import { registerMDReader } from "./mdReader.js";
14
13
  import { handleFilePaste, handlePlainTextPaste, handleVSCodePaste, runPasteHandlers } from "./paste-handler.js";
15
- import { registerHeaderBackspace, registerLastElement, registerRichKeydown } from "./register.js";
14
+ import { registerProgressiveSelectAll } from "./progressive-select-all.js";
15
+ import { registerBlockBackspace, registerLastElement, registerRichKeydown } from "./register.js";
16
16
  import { registerHistory } from "@lexical/history";
17
17
  import { $createLineBreakNode, $createParagraphNode, $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, PASTE_COMMAND, ParagraphNode, TEXT_TYPE_TO_FORMAT, TextNode } from "lexical";
18
18
  import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode, registerRichText } from "@lexical/rich-text";
19
19
  import { CAN_USE_DOM } from "@lexical/utils";
20
20
  import { registerDragonSupport } from "@lexical/dragon";
21
21
  //#region src/plugins/common/plugin/index.ts
22
- init_utils$1();
23
22
  init_helper();
24
23
  init_plugin();
25
24
  init_utils();
@@ -231,7 +230,18 @@ const CommonPlugin = class extends KernelPlugin {
231
230
  if (this.config?.pasteAsPlainText) return runPasteHandlers(ctx, [handleFilePaste, handlePlainTextPaste]);
232
231
  return false;
233
232
  }, COMMAND_PRIORITY_CRITICAL));
234
- 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, () => {
233
+ let dragonCleanup;
234
+ const clearDragon = () => {
235
+ dragonCleanup?.();
236
+ dragonCleanup = void 0;
237
+ };
238
+ this.register(this.kernel.registerRootListener((rootElement) => {
239
+ clearDragon();
240
+ if (!rootElement || !CAN_USE_DOM) return;
241
+ dragonCleanup = registerDragonSupport(editor);
242
+ }));
243
+ this.register(clearDragon);
244
+ this.registerClears(registerRichText(editor), registerProgressiveSelectAll(editor), 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, () => {
235
245
  const selection = $getSelection();
236
246
  if (!$isRangeSelection(selection)) return false;
237
247
  const { inCell, inTable } = $isCursorInTable(selection);
@@ -0,0 +1,81 @@
1
+ import { $computeTableMapSkipCellCheck, $createTableSelection, $isTableCellNode, $isTableNode, $isTableSelection } from "@lexical/table";
2
+ import { $getNodeByKey, $getRoot, $getSelection, $isElementNode, $isRangeSelection, $setSelection, COMMAND_PRIORITY_HIGH, SELECT_ALL_COMMAND } from "lexical";
3
+ //#region src/plugins/common/plugin/progressive-select-all.ts
4
+ const COLLAPSIBLE_NODE_TYPE = "collapsible";
5
+ function registerProgressiveSelectAll(editor) {
6
+ return editor.registerCommand(SELECT_ALL_COMMAND, () => {
7
+ const selection = $getSelection();
8
+ if ($isTableSelection(selection)) return $expandTableSelection(selection);
9
+ if (!$isRangeSelection(selection)) return false;
10
+ const anchorCell = $findNearestNode(selection.anchor.getNode(), $isTableCellNode);
11
+ const focusCell = $findNearestNode(selection.focus.getNode(), $isTableCellNode);
12
+ if (anchorCell && focusCell && anchorCell.is(focusCell)) {
13
+ const table = $findNearestNode(anchorCell, $isTableNode);
14
+ if (table) {
15
+ const tableSelection = $createTableSelection();
16
+ tableSelection.set(table.getKey(), anchorCell.getKey(), anchorCell.getKey());
17
+ $setSelection(tableSelection);
18
+ return true;
19
+ }
20
+ }
21
+ const anchorCollapsible = $findCollapsibleAncestor(selection.anchor.getNode());
22
+ const focusCollapsible = $findCollapsibleAncestor(selection.focus.getNode());
23
+ if (!anchorCollapsible || !focusCollapsible || !anchorCollapsible.is(focusCollapsible)) return false;
24
+ if ($isEntireElementSelected(selection, anchorCollapsible)) $selectDocument();
25
+ else anchorCollapsible.select(0, anchorCollapsible.getChildrenSize());
26
+ return true;
27
+ }, COMMAND_PRIORITY_HIGH);
28
+ }
29
+ function $expandTableSelection(selection) {
30
+ const table = $getNodeByKey(selection.tableKey);
31
+ if (!$isTableNode(table)) return false;
32
+ const cells = $getTableCells(table);
33
+ if (!cells) return false;
34
+ const selectedCellKeys = new Set(selection.getNodes().filter($isTableCellNode).map((cell) => cell.getKey()));
35
+ if (cells.keys.every((key) => selectedCellKeys.has(key))) {
36
+ $selectDocument();
37
+ return true;
38
+ }
39
+ const tableSelection = $createTableSelection();
40
+ tableSelection.set(table.getKey(), cells.first.getKey(), cells.last.getKey());
41
+ $setSelection(tableSelection);
42
+ return true;
43
+ }
44
+ function $getTableCells(table) {
45
+ if (!$isTableNode(table)) return null;
46
+ const [tableMap] = $computeTableMapSkipCellCheck(table, null, null);
47
+ const entries = tableMap.flat().filter(Boolean);
48
+ const first = entries[0]?.cell;
49
+ const last = entries.at(-1)?.cell;
50
+ if (!first || !last) return null;
51
+ return {
52
+ first,
53
+ keys: Array.from(new Set(entries.map(({ cell }) => cell.getKey()))),
54
+ last
55
+ };
56
+ }
57
+ function $findNearestNode(node, predicate) {
58
+ let current = node;
59
+ while (current) {
60
+ if (predicate(current)) return current;
61
+ current = current.getParent();
62
+ }
63
+ return null;
64
+ }
65
+ function $findCollapsibleAncestor(node) {
66
+ return $findNearestNode(node, (candidate) => $isElementNode(candidate) && candidate.getType() === COLLAPSIBLE_NODE_TYPE);
67
+ }
68
+ function $isEntireElementSelected(selection, element) {
69
+ const startOffset = 0;
70
+ const endOffset = element.getChildrenSize();
71
+ return $isPointAt(selection.anchor, element, startOffset) && $isPointAt(selection.focus, element, endOffset) || $isPointAt(selection.focus, element, startOffset) && $isPointAt(selection.anchor, element, endOffset);
72
+ }
73
+ function $isPointAt(point, element, offset) {
74
+ return point.key === element.getKey() && point.offset === offset && point.type === "element";
75
+ }
76
+ function $selectDocument() {
77
+ const root = $getRoot();
78
+ root.select(0, root.getChildrenSize());
79
+ }
80
+ //#endregion
81
+ export { registerProgressiveSelectAll };
@@ -2,8 +2,9 @@ import { createDebugLogger, init_debug } from "../../../utils/debug.js";
2
2
  import { HotkeyEnum, init_hotkey } from "../../../types/hotkey.js";
3
3
  import { $closest, init_utils } from "../../../editor-kernel/utils.js";
4
4
  import { $createNodeSelection, $createParagraphNode, $getRoot, $getSelection, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isNodeSelection, $isRangeSelection, $isRootOrShadowRoot, $isTextNode, $setSelection, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, FORMAT_TEXT_COMMAND, HISTORIC_TAG, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, REDO_COMMAND, UNDO_COMMAND } from "lexical";
5
- import { $isHeadingNode, QuoteNode } from "@lexical/rich-text";
5
+ import { $isHeadingNode, $isQuoteNode, QuoteNode } from "@lexical/rich-text";
6
6
  import { mergeRegister } from "@lexical/utils";
7
+ import { $isListItemNode, $isListNode } from "@lexical/list";
7
8
  import { $isCodeHighlightNode, $isCodeNode } from "@lexical/code-core";
8
9
  //#region src/plugins/common/plugin/register.ts
9
10
  init_utils();
@@ -23,7 +24,9 @@ function resolveElement(element, isBackward, focusOffset) {
23
24
  block = parent;
24
25
  }
25
26
  }
26
- return block.getChildAtIndex(isBackward ? offset - 1 : offset);
27
+ const childIndex = isBackward ? offset - 1 : offset;
28
+ if (childIndex < 0 || childIndex >= block.getChildrenSize()) return null;
29
+ return block.getChildAtIndex(childIndex);
27
30
  }
28
31
  function isCodeNodeLastLine(focusNode) {
29
32
  if (!$isCodeHighlightNode(focusNode)) return false;
@@ -68,7 +71,55 @@ function $isSelectionAtEndOfRoot(selection) {
68
71
  const focus = selection.focus;
69
72
  return focus.key === "root" && focus.offset === $getRoot().getChildrenSize();
70
73
  }
71
- function registerHeaderBackspace(editor) {
74
+ function $isSelectionInList(selection) {
75
+ return Boolean($closest(selection.focus.getNode(), (node) => $isListNode(node) || $isListItemNode(node)));
76
+ }
77
+ function $getLeadingQuoteNode(selection) {
78
+ const anchor = selection.anchor;
79
+ if (anchor.offset !== 0) return false;
80
+ let node = anchor.getNode();
81
+ let current = node;
82
+ let quoteNode = null;
83
+ while (current) {
84
+ if ($isQuoteNode(current)) {
85
+ quoteNode = current;
86
+ break;
87
+ }
88
+ current = current.getParent();
89
+ }
90
+ if (!quoteNode) return false;
91
+ while (node !== quoteNode) {
92
+ if (node.getIndexWithinParent() !== 0) return false;
93
+ const parent = node.getParent();
94
+ if (!parent) return false;
95
+ node = parent;
96
+ }
97
+ return quoteNode;
98
+ }
99
+ function $unwrapQuoteNode(quoteNode) {
100
+ const children = quoteNode.getChildren();
101
+ if (children.length === 0) {
102
+ const paragraphNode = $createParagraphNode();
103
+ quoteNode.replace(paragraphNode);
104
+ paragraphNode.select(0, 0);
105
+ return;
106
+ }
107
+ let firstNode = null;
108
+ for (const child of children) {
109
+ if ($isElementNode(child)) {
110
+ quoteNode.insertBefore(child);
111
+ firstNode ||= child;
112
+ continue;
113
+ }
114
+ const paragraphNode = $createParagraphNode();
115
+ paragraphNode.append(child);
116
+ quoteNode.insertBefore(paragraphNode);
117
+ firstNode ||= paragraphNode;
118
+ }
119
+ quoteNode.remove();
120
+ if ($isElementNode(firstNode)) firstNode.select(0, 0);
121
+ }
122
+ function registerBlockBackspace(editor) {
72
123
  return editor.registerCommand(KEY_BACKSPACE_COMMAND, (payload) => {
73
124
  const headingNode = editor.getEditorState().read(() => {
74
125
  const selection = $getSelection();
@@ -96,6 +147,22 @@ function registerHeaderBackspace(editor) {
96
147
  });
97
148
  return true;
98
149
  }
150
+ if (editor.getEditorState().read(() => {
151
+ const selection = $getSelection();
152
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
153
+ return $getLeadingQuoteNode(selection);
154
+ })) {
155
+ payload.stopImmediatePropagation();
156
+ payload.preventDefault();
157
+ payload.stopPropagation();
158
+ editor.update(() => {
159
+ const selection = $getSelection();
160
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) return;
161
+ const leadingQuoteNode = $getLeadingQuoteNode(selection);
162
+ if (leadingQuoteNode) $unwrapQuoteNode(leadingQuoteNode);
163
+ });
164
+ return true;
165
+ }
99
166
  return false;
100
167
  }, COMMAND_PRIORITY_NORMAL);
101
168
  }
@@ -150,6 +217,7 @@ function registerRichKeydown(editor, kernel, options) {
150
217
  return true;
151
218
  }
152
219
  } else if ($isRangeSelection(selection)) {
220
+ if ($isSelectionInList(selection)) return false;
153
221
  const possibleNode = $getAdjacentNode(selection.focus, true);
154
222
  const upblock = possibleNode || $getDownUpNode(selection.focus, true);
155
223
  if (!event.shiftKey && $isDecoratorNode(possibleNode)) {
@@ -219,6 +287,7 @@ function registerRichKeydown(editor, kernel, options) {
219
287
  event.preventDefault();
220
288
  return true;
221
289
  }
290
+ if ($isSelectionInList(selection)) return false;
222
291
  const possibleNode = $getAdjacentNode(selection.focus, false);
223
292
  const upblock = possibleNode || $getDownUpNode(selection.focus, false);
224
293
  if (!event.shiftKey && $isDecoratorNode(possibleNode)) {
@@ -304,4 +373,4 @@ function registerLastElement(editor) {
304
373
  });
305
374
  }
306
375
  //#endregion
307
- export { $getAdjacentNode, $getDownUpNode, registerHeaderBackspace, registerLastElement, registerRichKeydown };
376
+ export { $getAdjacentNode, $getDownUpNode, registerBlockBackspace, registerLastElement, registerRichKeydown };
@@ -46,6 +46,12 @@ const ReactPlainText = memo(({ style, children, theme = {}, onChange, editable,
46
46
  }, [markdownOption]);
47
47
  const [isInitialized, setIsInitialized] = useState(false);
48
48
  const { props: { type, content, placeholder, lineEmptyPlaceholder } } = Children.only(children);
49
+ const initialDocumentRef = useRef({
50
+ content,
51
+ type
52
+ });
53
+ const editableRef = useRef(editable);
54
+ editableRef.current = editable;
49
55
  useLayoutEffect(() => {
50
56
  editor.registerPlugin(MarkdownPlugin, {
51
57
  autoFormatMarkdown,
@@ -76,13 +82,22 @@ const ReactPlainText = memo(({ style, children, theme = {}, onChange, editable,
76
82
  ]);
77
83
  useEffect(() => {
78
84
  const container = editorContainerRef.current;
79
- if (container && !isInitialized) {
80
- editor.setRootElement(container, editable);
81
- editor.setDocument(type, content);
85
+ if (!container) return;
86
+ if (!editor.getLexicalEditor()) {
87
+ editor.setRootElement(container, editableRef.current);
88
+ editor.setDocument(initialDocumentRef.current.type, initialDocumentRef.current.content);
82
89
  setIsInitialized(true);
83
- }
90
+ } else if (editor.getRootElement() !== container) editor.setRootElement(container, editableRef.current);
91
+ return () => {
92
+ if (editor.getRootElement() === container) editor.setRootElement(null);
93
+ };
94
+ }, [editor]);
95
+ useEffect(() => {
96
+ if (!isInitialized) return;
97
+ const lexicalEditor = editor.getLexicalEditor();
98
+ if (!lexicalEditor) return;
84
99
  let previousContent;
85
- return editor.getLexicalEditor()?.registerUpdateListener(({ dirtyElements, dirtyLeaves }) => {
100
+ return lexicalEditor.registerUpdateListener(({ dirtyElements, dirtyLeaves }) => {
86
101
  onChange?.(editor);
87
102
  if (onTextChange && (dirtyElements.size > 0 || dirtyLeaves.size > 0)) {
88
103
  const currentContent = JSON.stringify(editor.getDocument(type));
@@ -95,16 +110,19 @@ const ReactPlainText = memo(({ style, children, theme = {}, onChange, editable,
95
110
  });
96
111
  }, [
97
112
  editor,
113
+ isInitialized,
98
114
  type,
99
- content,
100
115
  onChange,
101
- onTextChange,
102
- isInitialized
116
+ onTextChange
103
117
  ]);
104
118
  useEffect(() => {
105
119
  if (!isInitialized) return;
106
120
  if (typeof editable === "boolean") editor.setEditable(editable);
107
- }, [isInitialized, editable]);
121
+ }, [
122
+ editor,
123
+ isInitialized,
124
+ editable
125
+ ]);
108
126
  useEffect(() => {
109
127
  if (editor && onPressEnter) return editor.registerHighCommand(KEY_DOWN_COMMAND, (event) => {
110
128
  if (event.key === "Enter" && !event.isComposing && onPressEnter({
@@ -1,9 +1,10 @@
1
- import { $createLinkNode } from "../node/LinkNode.js";
2
- import { $createTextNode, $getNodeByKey, $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from "lexical";
1
+ import { $createLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND } from "../node/LinkNode.js";
2
+ import { $createRangeSelection, $createTextNode, $getNodeByKey, $getSelection, $insertNodes, $isRangeSelection, $isTextNode, $setSelection, COMMAND_PRIORITY_EDITOR, createCommand } from "lexical";
3
3
  import { mergeRegister } from "@lexical/utils";
4
4
  //#region src/plugins/link/command/index.ts
5
5
  const INSERT_LINK_COMMAND = createCommand("INSERT_LINK_COMMAND");
6
6
  const UPDATE_LINK_TEXT_COMMAND = createCommand("UPDATE_LINK_TEXT_COMMAND");
7
+ const UNLINK_LINK_COMMAND = createCommand("UNLINK_LINK_COMMAND");
7
8
  function registerLinkCommand(editor) {
8
9
  return mergeRegister(editor.registerCommand(INSERT_LINK_COMMAND, (payload) => {
9
10
  const { url, title = url } = payload;
@@ -27,7 +28,29 @@ function registerLinkCommand(editor) {
27
28
  }
28
29
  });
29
30
  return false;
31
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(UNLINK_LINK_COMMAND, ({ key }) => {
32
+ editor.update(() => {
33
+ const node = $getNodeByKey(key);
34
+ if (!$isLinkNode(node)) return;
35
+ let selection = $getSelection();
36
+ if (!selection || !$isRangeSelection(selection)) {
37
+ $setSelection($createRangeSelection());
38
+ selection = $getSelection();
39
+ }
40
+ const first = node.getFirstDescendant();
41
+ const last = node.getLastDescendant();
42
+ if (selection && $isRangeSelection(selection) && $isTextNode(first) && $isTextNode(last)) {
43
+ selection.anchor.set(first.getKey(), 0, "text");
44
+ selection.focus.set(last.getKey(), last.getTextContentSize(), "text");
45
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
46
+ return;
47
+ }
48
+ const children = node.getChildren();
49
+ for (const child of children) node.insertBefore(child);
50
+ node.remove();
51
+ });
52
+ return true;
30
53
  }, COMMAND_PRIORITY_EDITOR));
31
54
  }
32
55
  //#endregion
33
- export { INSERT_LINK_COMMAND, UPDATE_LINK_TEXT_COMMAND, registerLinkCommand };
56
+ export { INSERT_LINK_COMMAND, UNLINK_LINK_COMMAND, UPDATE_LINK_TEXT_COMMAND, registerLinkCommand };
@@ -0,0 +1,237 @@
1
+ import { getNodeTitle, getNodeUrl } from "../service/i-link-service.js";
2
+ import { $createLinkNode, $isLinkNode } from "../node/LinkNode.js";
3
+ import { $createLinkCardNode, $isLinkCardNode } from "../node/LinkCardNode.js";
4
+ import { $createLinkBlockCardNode, $isLinkBlockCardNode } from "../node/LinkBlockCardNode.js";
5
+ import { $createLinkIframeNode, $isLinkIframeNode } from "../node/LinkIframeNode.js";
6
+ import { $createSchemaNode, $isSchemaNode } from "../node/SchemaNode.js";
7
+ import { $createParagraphNode, $createTextNode, $getNodeByKey, $isParagraphNode, $isRootNode } from "lexical";
8
+ //#region src/plugins/link/conversion/index.ts
9
+ function getLinkToolbarCapabilities(node, editor, linkService) {
10
+ const url = getNodeUrl(node);
11
+ const title = getNodeTitle(node);
12
+ const context = createRuleContext(editor, title, title);
13
+ const embedRule = linkService?.getEmbedRule(url, context);
14
+ const schemaRule = $isLinkNode(node) && linkService?.getSchemaRule(url, {
15
+ ...context,
16
+ schema: linkService.parseSchemaUrl(url)
17
+ });
18
+ return {
19
+ canConvertToBlockCard: $isLinkNode(node) && Boolean(embedRule?.allowBlockCard) || $isLinkCardNode(node) && !$isLinkBlockCardNode(node) || $isLinkIframeNode(node),
20
+ canConvertToCard: $isLinkNode(node) && Boolean(embedRule?.allowCard) || $isLinkIframeNode(node) || $isLinkBlockCardNode(node),
21
+ canConvertToIframe: $isLinkNode(node) && Boolean(embedRule?.allowIframe) || $isLinkCardNode(node),
22
+ canConvertToLink: !$isLinkNode(node),
23
+ canConvertToSchema: $isLinkNode(node) && Boolean(schemaRule)
24
+ };
25
+ }
26
+ function convertLinkToolbarNodeToLink(node) {
27
+ const url = getNodeUrl(node);
28
+ const title = getNodeTitle(node);
29
+ const linkNode = $createLinkNode(url, {
30
+ target: $isLinkCardNode(node) ? node.getOpenTarget() : null,
31
+ title
32
+ });
33
+ linkNode.append($createTextNode(title));
34
+ replaceWithInlineNode(node, linkNode);
35
+ return linkNode;
36
+ }
37
+ function convertLinkToolbarNodeByKeyToLink(editor, key) {
38
+ let replacementKey = null;
39
+ editor.update(() => {
40
+ const node = $getNodeByKey(key);
41
+ if (!$isLinkToolbarNode(node)) return;
42
+ const linkNode = convertLinkToolbarNodeToLink(node);
43
+ replacementKey = linkNode.getKey();
44
+ linkNode.selectEnd();
45
+ }, { discrete: true });
46
+ return replacementKey;
47
+ }
48
+ function convertLinkNodeToSchema(node, editor, linkService) {
49
+ const url = node.getURL();
50
+ const title = node.getTitle() || node.getTextContent() || url;
51
+ const schema = linkService.parseSchemaUrl(url);
52
+ const rule = linkService.getSchemaRule(url, {
53
+ ...createRuleContext(editor, node.getTextContent(), title),
54
+ schema
55
+ });
56
+ if (!rule) return null;
57
+ const parsed = rule.parse?.(url, schema);
58
+ const payload = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? normalizeSchemaPayload(parsed) : { payload: parsed };
59
+ const schemaNode = $createSchemaNode({
60
+ payload: payload.payload,
61
+ schemaType: payload.schemaType || rule.id,
62
+ title: payload.title || title,
63
+ url: payload.url || url
64
+ });
65
+ node.replace(schemaNode);
66
+ return schemaNode;
67
+ }
68
+ function convertLinkNodeByKeyToSchema(editor, key, linkService) {
69
+ editor.update(() => {
70
+ const node = $getNodeByKey(key);
71
+ if (!$isLinkNode(node)) return;
72
+ convertLinkNodeToSchema(node, editor, linkService)?.selectNext();
73
+ }, { discrete: true });
74
+ }
75
+ function replaceWithResolvedCardNode(node, payload, fallback, layout) {
76
+ const cardPayload = {
77
+ description: payload?.description,
78
+ icon: payload?.icon,
79
+ openTarget: payload?.openTarget || ($isLinkNode(node) ? node.getTarget() : $isLinkCardNode(node) ? node.getOpenTarget() : null) || "_blank",
80
+ title: payload?.title || fallback.title,
81
+ url: payload?.url || fallback.url
82
+ };
83
+ const cardNode = layout === "block" ? $createLinkBlockCardNode(cardPayload) : $createLinkCardNode(cardPayload);
84
+ if (layout === "block") replaceWithBlockNode(node, cardNode);
85
+ else replaceWithInlineNode(node, cardNode);
86
+ return cardNode;
87
+ }
88
+ async function replaceNodeByKeyWithCardNode(editor, key, linkService, layout = "inline") {
89
+ let request;
90
+ editor.getEditorState().read(() => {
91
+ const node = $getNodeByKey(key);
92
+ if (!$isLinkNode(node) && !$isLinkCardNode(node) && !$isLinkIframeNode(node)) return;
93
+ const url = getNodeUrl(node);
94
+ const title = getNodeTitle(node);
95
+ const context = createRuleContext(editor, title, title);
96
+ const rule = linkService.getEmbedRule(url, context);
97
+ let payload;
98
+ try {
99
+ payload = $isLinkCardNode(node) ? getExistingCardPayload(node) : rule?.getCardPayload?.(url, context);
100
+ } catch {
101
+ payload = void 0;
102
+ }
103
+ request = {
104
+ payload,
105
+ title,
106
+ url
107
+ };
108
+ });
109
+ if (!request) return;
110
+ const resolvedRequest = request;
111
+ let replacementKey = null;
112
+ const replaceCurrentNode = (payload) => {
113
+ const node = $getNodeByKey(key);
114
+ if (!$isLinkNode(node) && !$isLinkCardNode(node) && !$isLinkIframeNode(node)) return;
115
+ if (getNodeUrl(node) !== resolvedRequest.url) return;
116
+ const replacement = replaceWithResolvedCardNode(node, payload, resolvedRequest, layout);
117
+ replacementKey = replacement.getKey();
118
+ replacement.selectNext();
119
+ };
120
+ const initialPayload = resolvedRequest.payload;
121
+ if (!isPromiseLike(initialPayload)) {
122
+ editor.update(() => replaceCurrentNode(initialPayload), { discrete: true });
123
+ return;
124
+ }
125
+ editor.update(() => replaceCurrentNode(void 0), { discrete: true });
126
+ if (!replacementKey) return;
127
+ const cardKey = replacementKey;
128
+ linkService.setCardMetadataLoading(cardKey, true);
129
+ try {
130
+ const resolvedPayload = await initialPayload;
131
+ if (!resolvedPayload) return;
132
+ editor.update(() => {
133
+ const cardNode = $getNodeByKey(cardKey);
134
+ if (!$isLinkCardNode(cardNode) || getNodeUrl(cardNode) !== resolvedRequest.url) return;
135
+ cardNode.setURL(resolvedPayload.url || resolvedRequest.url).setTitle(resolvedPayload.title || resolvedRequest.title).setIcon(resolvedPayload.icon).setDescription(resolvedPayload.description).setOpenTarget(resolvedPayload.openTarget || cardNode.getOpenTarget() || "_blank");
136
+ }, { discrete: true });
137
+ } catch {
138
+ return;
139
+ } finally {
140
+ linkService.setCardMetadataLoading(cardKey, false);
141
+ }
142
+ }
143
+ function replaceNodeByKeyWithBlockCardNode(editor, key, linkService) {
144
+ return replaceNodeByKeyWithCardNode(editor, key, linkService, "block");
145
+ }
146
+ function replaceWithIframeNode(node, editor, linkService) {
147
+ const url = getNodeUrl(node);
148
+ const title = getNodeTitle(node);
149
+ const context = createRuleContext(editor, title, title);
150
+ const payload = linkService.getEmbedRule(url, context)?.getIframePayload?.(url, context);
151
+ const iframeNode = $createLinkIframeNode({
152
+ src: payload?.src || url,
153
+ title: payload?.title || title,
154
+ url: payload?.url || url
155
+ });
156
+ replaceWithBlockIframeNode(node, iframeNode);
157
+ return iframeNode;
158
+ }
159
+ function replaceNodeByKeyWithIframeNode(editor, key, linkService) {
160
+ editor.update(() => {
161
+ const node = $getNodeByKey(key);
162
+ if (!$isLinkNode(node) && !$isLinkCardNode(node)) return;
163
+ replaceWithIframeNode(node, editor, linkService).selectNext();
164
+ }, { discrete: true });
165
+ }
166
+ function replaceWithInlineNode(node, inlineNode) {
167
+ if (node.isInline()) {
168
+ node.replace(inlineNode);
169
+ return;
170
+ }
171
+ const paragraph = $createParagraphNode();
172
+ paragraph.append(inlineNode);
173
+ node.replace(paragraph);
174
+ }
175
+ function replaceWithBlockIframeNode(node, iframeNode) {
176
+ replaceWithBlockNode(node, iframeNode);
177
+ }
178
+ function replaceWithBlockNode(node, blockNode) {
179
+ const parent = node.getParent();
180
+ if (parent && !$isRootNode(parent) && !parent.isInline() && parent.getChildrenSize() === 1) {
181
+ parent.replace(blockNode);
182
+ return;
183
+ }
184
+ if (parent && $isParagraphNode(parent)) {
185
+ const previousSiblings = node.getPreviousSiblings();
186
+ const nextSiblings = node.getNextSiblings();
187
+ if (previousSiblings.length === 0) {
188
+ parent.insertBefore(blockNode);
189
+ node.remove();
190
+ if (parent.getChildrenSize() === 0) parent.remove();
191
+ return;
192
+ }
193
+ if (nextSiblings.length === 0) {
194
+ parent.insertAfter(blockNode);
195
+ node.remove();
196
+ return;
197
+ }
198
+ const nextParagraph = $createParagraphNode();
199
+ nextParagraph.setFormat(parent.getFormatType());
200
+ nextParagraph.setIndent(parent.getIndent());
201
+ nextParagraph.setDirection(parent.getDirection());
202
+ nextParagraph.append(...nextSiblings);
203
+ parent.insertAfter(blockNode);
204
+ blockNode.insertAfter(nextParagraph);
205
+ node.remove();
206
+ return;
207
+ }
208
+ node.replace(blockNode);
209
+ }
210
+ function $isLinkToolbarNode(node) {
211
+ return $isLinkNode(node) || $isLinkCardNode(node) || $isLinkIframeNode(node) || $isSchemaNode(node);
212
+ }
213
+ function createRuleContext(editor, text, title) {
214
+ return {
215
+ editor,
216
+ text,
217
+ title
218
+ };
219
+ }
220
+ function isPromiseLike(value) {
221
+ return Boolean(value && typeof value.then === "function");
222
+ }
223
+ function getExistingCardPayload(node) {
224
+ return {
225
+ description: node.getDescription(),
226
+ icon: node.getIcon(),
227
+ openTarget: node.getOpenTarget(),
228
+ title: node.getTitle(),
229
+ url: node.getURL()
230
+ };
231
+ }
232
+ function normalizeSchemaPayload(payload) {
233
+ if ("payload" in payload || "schemaType" in payload || "title" in payload || "url" in payload) return payload;
234
+ return { payload };
235
+ }
236
+ //#endregion
237
+ export { $isLinkToolbarNode, convertLinkNodeByKeyToSchema, convertLinkNodeToSchema, convertLinkToolbarNodeByKeyToLink, convertLinkToolbarNodeToLink, getLinkToolbarCapabilities, replaceNodeByKeyWithBlockCardNode, replaceNodeByKeyWithCardNode, replaceNodeByKeyWithIframeNode, replaceWithBlockIframeNode, replaceWithBlockNode, replaceWithIframeNode, replaceWithInlineNode };
@@ -1,7 +1,9 @@
1
1
  import { INSERT_LINK_COMMAND } from "./command/index.js";
2
+ import { ILinkService, LinkEmbedRule, LinkLabels, LinkToolbarAction, LinkToolbarItem, LinkToolbarItemIcon, LinkToolbarRenderContext, ParsedSchemaUrl, SchemaRule, getNodeTitle, getNodeUrl, normalizeProtocol } from "./service/i-link-service.js";
3
+ import { normalizeSchemaLinkNode } from "./normalization/index.js";
2
4
  import { LinkPlugin, LinkPluginOptions } from "./plugin/index.js";
3
- import { ReactLinkPluginProps } from "./react/type.js";
5
+ import { LinkCardRenderer, LinkCardRendererProps, LinkIframeRenderer, LinkIframeRendererProps, ReactSchemaRule, SchemaLinkRenderer, SchemaLinkRendererConfig, SchemaLinkRendererProps, SchemaRenderer, SchemaRendererProps } from "./react/renderer-registry.js";
6
+ import { ReactLinkDefaultToolbarItemKey, ReactLinkDefaultToolbarItems, ReactLinkPluginProps } from "./react/type.js";
4
7
  import { ReactLinkPlugin } from "./react/ReactLinkPlugin.js";
5
8
  import "./react/index.js";
6
- import { ILinkService } from "./service/i-link-service.js";
7
- export { ILinkService, INSERT_LINK_COMMAND, LinkPlugin, LinkPluginOptions, ReactLinkPlugin, type ReactLinkPluginProps };
9
+ export { ILinkService, INSERT_LINK_COMMAND, type LinkCardRenderer, type LinkCardRendererProps, type LinkEmbedRule, type LinkIframeRenderer, type LinkIframeRendererProps, type LinkLabels, LinkPlugin, LinkPluginOptions, type LinkToolbarAction, type LinkToolbarItem, type LinkToolbarItemIcon, type LinkToolbarRenderContext, type ParsedSchemaUrl, type ReactLinkDefaultToolbarItemKey, type ReactLinkDefaultToolbarItems, ReactLinkPlugin, type ReactLinkPluginProps, type ReactSchemaRule, type SchemaLinkRenderer, type SchemaLinkRendererConfig, type SchemaLinkRendererProps, type SchemaRenderer, type SchemaRendererProps, type SchemaRule, getNodeTitle, getNodeUrl, normalizeProtocol, normalizeSchemaLinkNode };
@@ -0,0 +1,16 @@
1
+ import { LinkCardNode, SerializedLinkCardNode } from "./LinkCardNode.js";
2
+ import { DOMConversionMap, DOMExportOutput, EditorConfig, LexicalEditor, LexicalNode, LexicalUpdateJSON, NodeKey } from "lexical";
3
+ //#region src/plugins/link/node/LinkBlockCardNode.d.ts
4
+ declare class LinkBlockCardNode extends LinkCardNode {
5
+ static getType(): string;
6
+ static clone(node: LinkBlockCardNode): LinkBlockCardNode;
7
+ static importJSON(serializedNode: SerializedLinkCardNode): LinkBlockCardNode;
8
+ static importDOM(): DOMConversionMap | null;
9
+ constructor(url: string, title?: string, icon?: string, description?: string, openTarget?: null | string, key?: NodeKey);
10
+ createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement;
11
+ exportDOM(): DOMExportOutput;
12
+ updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedLinkCardNode>): this;
13
+ isInline(): false;
14
+ }
15
+ //#endregion
16
+ export { LinkBlockCardNode };