@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
@@ -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 };
@@ -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 };
@@ -0,0 +1,66 @@
1
+ import { LinkCardNode } from "./LinkCardNode.js";
2
+ import { $applyNodeReplacement } from "lexical";
3
+ //#region src/plugins/link/node/LinkBlockCardNode.ts
4
+ var LinkBlockCardNode = class LinkBlockCardNode extends LinkCardNode {
5
+ static getType() {
6
+ return "link-block-card";
7
+ }
8
+ static clone(node) {
9
+ return new LinkBlockCardNode(node.__url, node.__title, node.__icon, node.__description, node.__openTarget, node.__key);
10
+ }
11
+ static importJSON(serializedNode) {
12
+ return $createLinkBlockCardNode({
13
+ description: serializedNode.description,
14
+ icon: serializedNode.icon,
15
+ openTarget: serializedNode.openTarget,
16
+ title: serializedNode.title,
17
+ url: serializedNode.url
18
+ }).updateFromJSON(serializedNode);
19
+ }
20
+ static importDOM() {
21
+ return { a: (node) => {
22
+ if (node instanceof HTMLAnchorElement && node.dataset.linkCard === "true" && node.dataset.linkCardLayout === "block") return {
23
+ conversion: $convertLinkBlockCardElement,
24
+ priority: 3
25
+ };
26
+ return null;
27
+ } };
28
+ }
29
+ constructor(url, title, icon, description, openTarget, key) {
30
+ super(url, title, icon, description, openTarget, key);
31
+ }
32
+ createDOM(config, editor) {
33
+ const element = super.createDOM(config, editor);
34
+ element.dataset.linkCardLayout = "block";
35
+ return element;
36
+ }
37
+ exportDOM() {
38
+ const output = super.exportDOM();
39
+ if (output.element instanceof HTMLElement) output.element.dataset.linkCardLayout = "block";
40
+ return output;
41
+ }
42
+ updateFromJSON(serializedNode) {
43
+ return super.updateFromJSON(serializedNode);
44
+ }
45
+ isInline() {
46
+ return false;
47
+ }
48
+ };
49
+ function $createLinkBlockCardNode(payload) {
50
+ return $applyNodeReplacement(new LinkBlockCardNode(payload.url, payload.title, payload.icon, payload.description, payload.openTarget));
51
+ }
52
+ function $convertLinkBlockCardElement(domNode) {
53
+ const element = domNode;
54
+ return { node: $createLinkBlockCardNode({
55
+ description: element.dataset.description,
56
+ icon: element.dataset.icon,
57
+ openTarget: element.getAttribute("target"),
58
+ title: element.textContent || element.href,
59
+ url: element.getAttribute("href") || ""
60
+ }) };
61
+ }
62
+ function $isLinkBlockCardNode(node) {
63
+ return node instanceof LinkBlockCardNode || node?.getType?.() === LinkBlockCardNode.getType();
64
+ }
65
+ //#endregion
66
+ export { $createLinkBlockCardNode, $isLinkBlockCardNode, LinkBlockCardNode };
@@ -0,0 +1,42 @@
1
+ import { DOMConversionMap, DOMExportOutput, DecoratorNode, EditorConfig, LexicalEditor, LexicalNode, LexicalUpdateJSON, NodeKey, SerializedLexicalNode, Spread } from "lexical";
2
+ //#region src/plugins/link/node/LinkCardNode.d.ts
3
+ type SerializedLinkCardNode = Spread<{
4
+ description: string;
5
+ icon: string;
6
+ openTarget: null | string;
7
+ title: string;
8
+ url: string;
9
+ }, SerializedLexicalNode>;
10
+ declare class LinkCardNode extends DecoratorNode<unknown> {
11
+ __url: string;
12
+ __title: string;
13
+ __icon: string;
14
+ __description: string;
15
+ __openTarget: null | string;
16
+ static getType(): string;
17
+ static clone(node: LinkCardNode): LinkCardNode;
18
+ static importJSON(serializedNode: SerializedLinkCardNode): LinkCardNode;
19
+ static importDOM(): DOMConversionMap | null;
20
+ constructor(url: string, title?: string, icon?: string, description?: string, openTarget?: null | string, key?: NodeKey);
21
+ createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement;
22
+ updateDOM(): false;
23
+ isInline(): boolean;
24
+ isKeyboardSelectable(): true;
25
+ exportDOM(): DOMExportOutput;
26
+ exportJSON(): SerializedLinkCardNode;
27
+ updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedLinkCardNode>): this;
28
+ getTextContent(): string;
29
+ getURL(): string;
30
+ setURL(url: string): this;
31
+ getTitle(): string;
32
+ setTitle(title?: string): this;
33
+ getIcon(): string;
34
+ setIcon(icon?: string): this;
35
+ getDescription(): string;
36
+ setDescription(description?: string): this;
37
+ getOpenTarget(): null | string;
38
+ setOpenTarget(openTarget?: null | string): this;
39
+ decorate(editor: LexicalEditor): unknown;
40
+ }
41
+ //#endregion
42
+ export { LinkCardNode, SerializedLinkCardNode };