@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
@@ -1,148 +1,477 @@
1
+ import { $getNearestNodeFromDOMNode as $getNearestNodeFromDOMNode$1, init_utils } from "../../../../editor-kernel/utils.js";
1
2
  import { useLexicalEditor } from "../../../../editor-kernel/react/useLexicalEditor.js";
2
3
  import { useTranslation } from "../../../../editor-kernel/react/useTranslation.js";
3
- import { $isLinkNode, HOVER_LINK_COMMAND, HOVER_OUT_LINK_COMMAND, TOGGLE_LINK_COMMAND } from "../../node/LinkNode.js";
4
+ import { getNodeUrl } from "../../service/i-link-service.js";
5
+ import { $isLinkNode, HOVER_LINK_COMMAND, HOVER_OUT_LINK_COMMAND } from "../../node/LinkNode.js";
4
6
  import { getSelectedNode } from "../../utils/index.js";
5
7
  import { useEditable } from "../../../../editor-kernel/react/useEditable.js";
6
8
  import { cleanPosition, updatePosition } from "../../../../utils/updatePosition.js";
7
9
  import { styles } from "../style.js";
8
- import { EDIT_LINK_COMMAND } from "./LinkEdit.js";
9
- import { ActionIconGroup } from "@lobehub/ui";
10
- import { EditIcon, ExternalLinkIcon, UnlinkIcon } from "lucide-react";
11
- import { memo, useCallback, useRef, useState } from "react";
10
+ import { $isLinkToolbarNode, convertLinkNodeByKeyToSchema, convertLinkToolbarNodeByKeyToLink, getLinkToolbarCapabilities, replaceNodeByKeyWithBlockCardNode, replaceNodeByKeyWithCardNode, replaceNodeByKeyWithIframeNode } from "../../conversion/index.js";
11
+ import { Flexbox, Icon } from "@lobehub/ui";
12
+ import { CopyIcon, EditIcon, ExternalLinkIcon, GalleryHorizontalEndIcon, LinkIcon, PanelTopOpenIcon, PanelsTopLeftIcon, UnlinkIcon } from "lucide-react";
13
+ import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
12
14
  import { jsx } from "react/jsx-runtime";
13
- import { $createRangeSelection, $getNodeByKey, $getSelection, $isRangeSelection, $isTextNode, $setSelection, COMMAND_PRIORITY_NORMAL } from "lexical";
15
+ import { $getNodeByKey, $getSelection, $isRangeSelection, COMMAND_PRIORITY_NORMAL } from "lexical";
14
16
  import { mergeRegister } from "@lexical/utils";
15
17
  //#region src/plugins/link/react/components/LinkToolbar.tsx
16
- const LinkToolbar = memo(({ editor, enable }) => {
18
+ init_utils();
19
+ const HOVER_OPEN_DELAY = 180;
20
+ const HOVER_CLOSE_DELAY = 500;
21
+ const toolbarIconMap = {
22
+ copy: CopyIcon,
23
+ edit: EditIcon,
24
+ open: ExternalLinkIcon,
25
+ unlink: UnlinkIcon
26
+ };
27
+ const LinkToolbar = memo(({ editor, enable, linkService }) => {
17
28
  const divRef = useRef(null);
18
- const LinkRef = useRef(null);
19
- const [linkNode, setLinkNode] = useState(null);
20
- const state = useRef({ isLink: false });
21
- const t = useTranslation();
29
+ const linkDomRef = useRef(null);
30
+ const [toolbarNodeKey, setToolbarNodeKey] = useState(null);
31
+ const [menuVersion, setMenuVersion] = useState(0);
32
+ const selectedLinkKeyRef = useRef(null);
33
+ const toolbarActionGestureRef = useRef({ pressHandled: false });
34
+ const toolbarActionResetTimerRef = useRef(-1);
35
+ const toolbarHoverRef = useRef(false);
36
+ const visibleLinkKeyRef = useRef(null);
22
37
  const clearTimerRef = useRef(-1);
38
+ const rebindFrameRef = useRef(-1);
39
+ const showTimerRef = useRef(-1);
23
40
  const { editable } = useEditable();
24
- const handleEdit = useCallback(() => {
25
- if (!linkNode) return;
26
- editor.dispatchCommand(EDIT_LINK_COMMAND, {
27
- linkNode,
28
- linkNodeDOM: editor.getElementByKey(linkNode.getKey())
29
- });
30
- }, [editor, linkNode]);
41
+ const t = useTranslation();
31
42
  const handleCancel = useCallback(() => {
32
43
  clearTimeout(clearTimerRef.current);
44
+ clearTimeout(showTimerRef.current);
45
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rebindFrameRef.current);
46
+ linkDomRef.current?.classList.remove("hover");
47
+ linkDomRef.current = null;
48
+ toolbarHoverRef.current = false;
49
+ visibleLinkKeyRef.current = null;
33
50
  cleanPosition(divRef.current);
51
+ setToolbarNodeKey(null);
34
52
  }, []);
35
- const handleRemove = useCallback(() => {
36
- if (!linkNode) return;
37
- editor.update(() => {
38
- const node = $getNodeByKey(linkNode.getKey());
39
- if (!$isLinkNode(node)) return;
40
- let selection = $getSelection();
41
- if (!selection || !$isRangeSelection(selection)) {
42
- $setSelection($createRangeSelection());
43
- selection = $getSelection();
44
- }
45
- const first = node.getFirstDescendant();
46
- const last = node.getLastDescendant();
47
- if (selection && $isRangeSelection(selection) && $isTextNode(first) && $isTextNode(last)) {
48
- selection.anchor.set(first.getKey(), 0, "text");
49
- selection.focus.set(last.getKey(), last.getTextContentSize(), "text");
50
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
53
+ const updateToolbarPosition = useCallback(() => {
54
+ updatePosition({
55
+ floating: divRef.current,
56
+ offset: 4,
57
+ placement: "top-start",
58
+ reference: linkDomRef.current
59
+ });
60
+ }, []);
61
+ const showToolbar = useCallback((nextNodeKey, reference) => {
62
+ clearTimeout(clearTimerRef.current);
63
+ linkDomRef.current?.classList.remove("hover");
64
+ linkDomRef.current = reference;
65
+ linkDomRef.current.classList.add("hover");
66
+ visibleLinkKeyRef.current = nextNodeKey;
67
+ setToolbarNodeKey(nextNodeKey);
68
+ }, []);
69
+ const rebindToolbarToNode = useCallback((nextNodeKey) => {
70
+ selectedLinkKeyRef.current = nextNodeKey;
71
+ const bind = () => {
72
+ const dom = editor.getElementByKey(nextNodeKey);
73
+ if (!dom) {
74
+ handleCancel();
51
75
  return;
52
76
  }
53
- const children = node.getChildren();
54
- for (const child of children) node.insertBefore(child);
55
- node.remove();
77
+ showToolbar(nextNodeKey, dom);
78
+ };
79
+ const dom = editor.getElementByKey(nextNodeKey);
80
+ if (dom) {
81
+ showToolbar(nextNodeKey, dom);
82
+ return;
83
+ }
84
+ if (typeof requestAnimationFrame === "function") rebindFrameRef.current = requestAnimationFrame(bind);
85
+ else handleCancel();
86
+ }, [
87
+ editor,
88
+ handleCancel,
89
+ showToolbar
90
+ ]);
91
+ const scheduleShowToolbar = useCallback((nextNodeKey, reference) => {
92
+ clearTimeout(clearTimerRef.current);
93
+ clearTimeout(showTimerRef.current);
94
+ showTimerRef.current = setTimeout(() => {
95
+ showToolbar(nextNodeKey, reference);
96
+ }, HOVER_OPEN_DELAY);
97
+ }, [showToolbar]);
98
+ const scheduleHideToolbar = useCallback((delay = HOVER_CLOSE_DELAY) => {
99
+ clearTimeout(clearTimerRef.current);
100
+ clearTimerRef.current = setTimeout(() => {
101
+ if (!toolbarHoverRef.current) handleCancel();
102
+ }, delay);
103
+ }, [handleCancel]);
104
+ const scheduleToolbarActionGestureReset = useCallback(() => {
105
+ clearTimeout(toolbarActionResetTimerRef.current);
106
+ toolbarActionResetTimerRef.current = setTimeout(() => {
107
+ toolbarActionGestureRef.current.pressHandled = false;
108
+ }, 1e3);
109
+ }, []);
110
+ useEffect(() => () => {
111
+ clearTimeout(toolbarActionResetTimerRef.current);
112
+ }, []);
113
+ useEffect(() => {
114
+ if (!enable || !editable) handleCancel();
115
+ }, [
116
+ editable,
117
+ enable,
118
+ handleCancel
119
+ ]);
120
+ useEffect(() => {
121
+ if (!linkService) return;
122
+ return linkService.subscribe(() => {
123
+ setMenuVersion((version) => version + 1);
56
124
  });
57
- }, [editor, linkNode]);
58
- const handleOpenLink = useCallback(() => {
59
- if (!linkNode) return;
60
- const url = editor.getEditorState().read(() => linkNode.getURL());
61
- window.open(url, "_blank");
62
- }, [editor, linkNode]);
63
- useLexicalEditor((editor) => {
64
- if (!editable) return;
65
- return mergeRegister(editor.registerUpdateListener(() => {
66
- const selection = editor.getEditorState().read(() => $getSelection());
67
- if (!selection) return;
68
- if ($isRangeSelection(selection)) editor.getEditorState().read(() => {
69
- const node = getSelectedNode(selection);
70
- const parent = node.getParent();
71
- const isLink = $isLinkNode(parent) || $isLinkNode(node);
72
- if (isLink === state.current.isLink) return;
73
- state.current.isLink = isLink;
74
- if (isLink) {
75
- const linkNode = $isLinkNode(parent) ? parent : node;
76
- editor.dispatchCommand(EDIT_LINK_COMMAND, {
77
- linkNode,
78
- linkNodeDOM: editor.getElementByKey(linkNode.getKey())
125
+ }, [linkService]);
126
+ const resolveToolbarNodeKey = useCallback(() => {
127
+ return editor.getEditorState().read(() => {
128
+ const currentNode = linkDomRef.current?.isConnected ? $getNearestLinkToolbarNodeFromDOMNode(linkDomRef.current, editor) : null;
129
+ if (currentNode) return currentNode.getKey();
130
+ const selection = $getSelection();
131
+ if ($isRangeSelection(selection)) {
132
+ const selectedNode = getSelectedNode(selection);
133
+ const parent = selectedNode.getParent();
134
+ const selectedToolbarNode = $isLinkToolbarNode(selectedNode) ? selectedNode : $isLinkToolbarNode(parent) ? parent : null;
135
+ if (selectedToolbarNode) return selectedToolbarNode.getKey();
136
+ }
137
+ const fallbackKey = visibleLinkKeyRef.current || toolbarNodeKey;
138
+ if (!fallbackKey) return null;
139
+ const fallbackNode = $getNodeByKey(fallbackKey);
140
+ return $isLinkToolbarNode(fallbackNode) ? fallbackNode.getKey() : null;
141
+ });
142
+ }, [editor, toolbarNodeKey]);
143
+ const items = useMemo(() => {
144
+ if (!toolbarNodeKey) return [];
145
+ return readToolbarNode(editor, toolbarNodeKey, (toolbarNode) => {
146
+ const result = [];
147
+ const labels = linkService?.getLabels();
148
+ const capabilities = getLinkToolbarCapabilities(toolbarNode, editor, linkService);
149
+ const context = $isLinkNode(toolbarNode) ? {
150
+ close: handleCancel,
151
+ editor,
152
+ linkDom: linkDomRef.current,
153
+ linkNode: toolbarNode
154
+ } : null;
155
+ if (context && linkService) result.push(...linkService.getToolbarItems(context).map((item) => ({
156
+ icon: toolbarIconMap[item.icon],
157
+ key: item.key,
158
+ label: typeof item.label === "function" ? item.label(context) : t(item.label),
159
+ onClick: () => {
160
+ const nodeKey = resolveToolbarNodeKey();
161
+ if (!nodeKey) return;
162
+ readToolbarNode(editor, nodeKey, (currentNode) => {
163
+ if (!$isLinkNode(currentNode)) return;
164
+ item.onClick({
165
+ close: handleCancel,
166
+ editor,
167
+ linkDom: linkDomRef.current,
168
+ linkNode: currentNode
169
+ });
79
170
  });
80
- } else editor.dispatchCommand(EDIT_LINK_COMMAND, {
81
- linkNode: null,
82
- linkNodeDOM: null
171
+ }
172
+ })));
173
+ else {
174
+ const url = getNodeUrl(toolbarNode);
175
+ result.push({
176
+ icon: ExternalLinkIcon,
177
+ key: "open",
178
+ label: t("link.open"),
179
+ onClick: () => window.open(url, "_blank")
83
180
  });
181
+ }
182
+ if (capabilities.canConvertToCard) result.push({
183
+ icon: GalleryHorizontalEndIcon,
184
+ key: "convertToCard",
185
+ label: labels?.convertToCard || "Convert to card",
186
+ onClick: () => {
187
+ if (!linkService) return;
188
+ const nodeKey = resolveToolbarNodeKey();
189
+ if (!nodeKey) return;
190
+ replaceNodeByKeyWithCardNode(editor, nodeKey, linkService);
191
+ handleCancel();
192
+ }
84
193
  });
85
- else state.current.isLink = false;
86
- }), editor.registerCommand(HOVER_LINK_COMMAND, (payload) => {
87
- if (!enable) return false;
88
- if (!payload.event.target || divRef.current === null) return false;
89
- clearTimeout(clearTimerRef.current);
90
- setLinkNode(payload.linkNode);
91
- updatePosition({
92
- callback: () => {
93
- LinkRef.current = payload.event.target;
94
- },
95
- floating: divRef.current,
96
- offset: 4,
97
- placement: "top-start",
98
- reference: payload.event.target
194
+ if (capabilities.canConvertToBlockCard) result.push({
195
+ icon: PanelsTopLeftIcon,
196
+ key: "convertToBlockCard",
197
+ label: labels?.convertToBlockCard || "Convert to block card",
198
+ onClick: () => {
199
+ if (!linkService) return;
200
+ const nodeKey = resolveToolbarNodeKey();
201
+ if (!nodeKey) return;
202
+ replaceNodeByKeyWithBlockCardNode(editor, nodeKey, linkService);
203
+ handleCancel();
204
+ }
99
205
  });
100
- return false;
101
- }, COMMAND_PRIORITY_NORMAL), editor.registerCommand(HOVER_OUT_LINK_COMMAND, () => {
102
- clearTimerRef.current = setTimeout(handleCancel, 300);
103
- return true;
104
- }, COMMAND_PRIORITY_NORMAL));
105
- }, [enable, editable]);
106
- return /* @__PURE__ */ jsx(ActionIconGroup, {
107
- className: styles.linkToolbar,
108
- items: [
109
- {
110
- icon: EditIcon,
111
- key: "edit",
112
- label: t("link.edit"),
113
- onClick: handleEdit
114
- },
115
- {
116
- icon: ExternalLinkIcon,
117
- key: "openLink",
118
- label: t("link.open"),
119
- onClick: handleOpenLink
120
- },
121
- {
122
- icon: UnlinkIcon,
123
- key: "unlink",
124
- label: t("link.unlink"),
206
+ if (capabilities.canConvertToIframe) result.push({
207
+ icon: PanelTopOpenIcon,
208
+ key: "convertToIframe",
209
+ label: labels?.convertToIframe || "Convert to iframe",
210
+ onClick: () => {
211
+ if (!linkService) return;
212
+ const nodeKey = resolveToolbarNodeKey();
213
+ if (!nodeKey) return;
214
+ replaceNodeByKeyWithIframeNode(editor, nodeKey, linkService);
215
+ handleCancel();
216
+ }
217
+ });
218
+ if (capabilities.canConvertToSchema && $isLinkNode(toolbarNode)) result.push({
219
+ icon: LinkIcon,
220
+ key: "convertToSchema",
221
+ label: labels?.convertToSchema || "Convert to schema",
125
222
  onClick: () => {
126
- handleRemove();
223
+ if (!linkService) return;
224
+ const nodeKey = resolveToolbarNodeKey();
225
+ if (!nodeKey) return;
226
+ convertLinkNodeByKeyToSchema(editor, nodeKey, linkService);
127
227
  handleCancel();
128
228
  }
229
+ });
230
+ if (capabilities.canConvertToLink) result.push({
231
+ icon: LinkIcon,
232
+ key: "convertToLink",
233
+ label: labels?.convertToLink || "Convert to link",
234
+ onClick: () => {
235
+ const nodeKey = resolveToolbarNodeKey();
236
+ if (!nodeKey) return;
237
+ const replacementKey = convertLinkToolbarNodeByKeyToLink(editor, nodeKey);
238
+ if (!replacementKey) {
239
+ handleCancel();
240
+ return;
241
+ }
242
+ rebindToolbarToNode(replacementKey);
243
+ }
244
+ });
245
+ if (linkService) result.push(...linkService.getToolbarActions({
246
+ editor,
247
+ node: toolbarNode
248
+ }).map((action) => ({
249
+ icon: action.icon || LinkIcon,
250
+ key: action.key,
251
+ label: action.label,
252
+ onClick: () => {
253
+ const nodeKey = resolveToolbarNodeKey();
254
+ if (!nodeKey) return;
255
+ readToolbarNode(editor, nodeKey, (currentNode) => {
256
+ action.onClick({
257
+ editor,
258
+ node: currentNode
259
+ });
260
+ });
261
+ handleCancel();
262
+ }
263
+ })));
264
+ return result;
265
+ }) || [];
266
+ }, [
267
+ editor,
268
+ handleCancel,
269
+ linkService,
270
+ menuVersion,
271
+ rebindToolbarToNode,
272
+ resolveToolbarNodeKey,
273
+ t,
274
+ toolbarNodeKey
275
+ ]);
276
+ useLayoutEffect(() => {
277
+ if (!toolbarNodeKey || items.length === 0) return;
278
+ updateToolbarPosition();
279
+ }, [
280
+ items.length,
281
+ menuVersion,
282
+ toolbarNodeKey,
283
+ updateToolbarPosition
284
+ ]);
285
+ useEffect(() => {
286
+ if (!toolbarNodeKey || typeof window === "undefined") return;
287
+ const handleViewportChange = () => updateToolbarPosition();
288
+ window.addEventListener("resize", handleViewportChange, { passive: true });
289
+ window.addEventListener("scroll", handleViewportChange, {
290
+ capture: true,
291
+ passive: true
292
+ });
293
+ return () => {
294
+ window.removeEventListener("resize", handleViewportChange);
295
+ window.removeEventListener("scroll", handleViewportChange, { capture: true });
296
+ };
297
+ }, [toolbarNodeKey, updateToolbarPosition]);
298
+ useLexicalEditor((lexicalEditor) => {
299
+ let rootElement = lexicalEditor.getRootElement();
300
+ const handleMouseOver = (event) => {
301
+ if (!enable || !editable || !rootElement) return;
302
+ const reference = getLinkReferenceElement(event.target);
303
+ if (!reference || !rootElement.contains(reference)) return;
304
+ if (event.relatedTarget instanceof Node && reference.contains(event.relatedTarget)) return;
305
+ const node = lexicalEditor.getEditorState().read(() => $getNearestLinkToolbarNodeFromDOMNode(reference, lexicalEditor));
306
+ if (!node) return;
307
+ const nodeKey = node.getKey();
308
+ if (visibleLinkKeyRef.current) showToolbar(nodeKey, reference);
309
+ else scheduleShowToolbar(nodeKey, reference);
310
+ };
311
+ const handleMouseOut = (event) => {
312
+ const reference = getLinkReferenceElement(event.target);
313
+ if (!reference || !rootElement?.contains(reference)) return;
314
+ if (event.relatedTarget instanceof Node && reference.contains(event.relatedTarget)) return;
315
+ if (event.relatedTarget instanceof Node && divRef.current?.contains(event.relatedTarget)) return;
316
+ clearTimeout(showTimerRef.current);
317
+ if (visibleLinkKeyRef.current !== selectedLinkKeyRef.current) scheduleHideToolbar();
318
+ };
319
+ if (typeof document !== "undefined") {
320
+ document.addEventListener("mouseover", handleMouseOver, true);
321
+ document.addEventListener("mouseout", handleMouseOut, true);
322
+ }
323
+ return mergeRegister(lexicalEditor.registerRootListener((nextRootElement) => {
324
+ rootElement = nextRootElement;
325
+ }), lexicalEditor.registerUpdateListener(() => {
326
+ if (!enable || !editable) {
327
+ selectedLinkKeyRef.current = null;
328
+ return;
129
329
  }
130
- ],
330
+ const selectedLinkKey = lexicalEditor.getEditorState().read(() => {
331
+ const selection = $getSelection();
332
+ if (!$isRangeSelection(selection)) return null;
333
+ const selectedNode = getSelectedNode(selection);
334
+ const parent = selectedNode.getParent();
335
+ return ($isLinkToolbarNode(selectedNode) ? selectedNode : $isLinkToolbarNode(parent) ? parent : null)?.getKey() || null;
336
+ });
337
+ if (!selectedLinkKey) {
338
+ selectedLinkKeyRef.current = null;
339
+ scheduleHideToolbar();
340
+ return;
341
+ }
342
+ if (selectedLinkKey === selectedLinkKeyRef.current) return;
343
+ selectedLinkKeyRef.current = selectedLinkKey;
344
+ const dom = lexicalEditor.getElementByKey(selectedLinkKey);
345
+ if (dom) showToolbar(selectedLinkKey, dom);
346
+ }), lexicalEditor.registerCommand(HOVER_LINK_COMMAND, (payload) => {
347
+ if (!enable || !editable || !payload.event.target) return false;
348
+ const reference = getLinkReferenceElement(payload.event.target);
349
+ if (!reference || !$isLinkToolbarNode(payload.node)) return false;
350
+ const nodeKey = payload.node.getKey();
351
+ if (visibleLinkKeyRef.current) showToolbar(nodeKey, reference);
352
+ else scheduleShowToolbar(nodeKey, reference);
353
+ return false;
354
+ }, COMMAND_PRIORITY_NORMAL), lexicalEditor.registerCommand(HOVER_OUT_LINK_COMMAND, () => {
355
+ clearTimeout(showTimerRef.current);
356
+ if (visibleLinkKeyRef.current !== selectedLinkKeyRef.current) scheduleHideToolbar();
357
+ return true;
358
+ }, COMMAND_PRIORITY_NORMAL), () => {
359
+ if (typeof document !== "undefined") {
360
+ document.removeEventListener("mouseover", handleMouseOver, true);
361
+ document.removeEventListener("mouseout", handleMouseOut, true);
362
+ }
363
+ clearTimeout(clearTimerRef.current);
364
+ if (typeof cancelAnimationFrame === "function") cancelAnimationFrame(rebindFrameRef.current);
365
+ clearTimeout(showTimerRef.current);
366
+ });
367
+ }, [
368
+ editable,
369
+ enable,
370
+ scheduleHideToolbar,
371
+ scheduleShowToolbar,
372
+ showToolbar
373
+ ]);
374
+ if (items.length === 0) return /* @__PURE__ */ jsx("div", {
375
+ className: styles.linkToolbar,
376
+ ref: divRef
377
+ });
378
+ return /* @__PURE__ */ jsx("div", {
379
+ className: styles.linkToolbar,
131
380
  onMouseEnter: () => {
381
+ toolbarHoverRef.current = true;
132
382
  clearTimeout(clearTimerRef.current);
133
383
  },
134
384
  onMouseLeave: () => {
135
- handleCancel();
385
+ toolbarHoverRef.current = false;
386
+ scheduleHideToolbar(120);
136
387
  },
137
388
  ref: divRef,
138
- shadow: true,
139
- size: {
140
- blockSize: 32,
141
- size: 16
142
- },
143
- variant: "outlined"
389
+ children: /* @__PURE__ */ jsx(Flexbox, {
390
+ align: "center",
391
+ gap: 8,
392
+ horizontal: true,
393
+ children: items.map((item) => /* @__PURE__ */ jsx("button", {
394
+ "aria-label": item.label,
395
+ className: styles.popoverActionItem,
396
+ onClick: (event) => {
397
+ const shouldRun = shouldRunToolbarActionFromEvent(toolbarActionGestureRef.current, "click");
398
+ clearTimeout(toolbarActionResetTimerRef.current);
399
+ if (!shouldRun) {
400
+ event.preventDefault();
401
+ event.stopPropagation();
402
+ return;
403
+ }
404
+ event.preventDefault();
405
+ event.stopPropagation();
406
+ item.onClick();
407
+ },
408
+ onMouseDown: (event) => {
409
+ if (event.button !== 0) return;
410
+ if (!shouldRunToolbarActionFromEvent(toolbarActionGestureRef.current, "mousedown")) {
411
+ event.preventDefault();
412
+ event.stopPropagation();
413
+ return;
414
+ }
415
+ scheduleToolbarActionGestureReset();
416
+ event.preventDefault();
417
+ event.stopPropagation();
418
+ item.onClick();
419
+ },
420
+ onPointerDown: (event) => {
421
+ if (!shouldRunToolbarActionFromPointer(event)) return;
422
+ shouldRunToolbarActionFromEvent(toolbarActionGestureRef.current, "pointerdown");
423
+ scheduleToolbarActionGestureReset();
424
+ event.preventDefault();
425
+ event.stopPropagation();
426
+ item.onClick();
427
+ },
428
+ title: item.label,
429
+ type: "button",
430
+ children: /* @__PURE__ */ jsx(Icon, {
431
+ icon: item.icon,
432
+ size: { size: 18 }
433
+ })
434
+ }, item.key))
435
+ })
144
436
  });
145
437
  });
438
+ function $getNearestLinkToolbarNodeFromDOMNode(startingDOM, editor) {
439
+ let node = $getNearestNodeFromDOMNode$1(startingDOM, editor);
440
+ while (node) {
441
+ if ($isLinkToolbarNode(node)) return node;
442
+ node = node.getParent();
443
+ }
444
+ return null;
445
+ }
446
+ function readToolbarNode(editor, key, reader) {
447
+ return editor.getEditorState().read(() => {
448
+ const node = $getNodeByKey(key);
449
+ return $isLinkToolbarNode(node) ? reader(node) : null;
450
+ });
451
+ }
452
+ function shouldRunToolbarActionFromPointer(event) {
453
+ return event.button <= 0;
454
+ }
455
+ function shouldRunToolbarActionFromEvent(state, eventType) {
456
+ if (eventType === "pointerdown") {
457
+ state.pressHandled = true;
458
+ return true;
459
+ }
460
+ if (eventType === "mousedown") {
461
+ if (state.pressHandled) return false;
462
+ state.pressHandled = true;
463
+ return true;
464
+ }
465
+ if (state.pressHandled) {
466
+ state.pressHandled = false;
467
+ return false;
468
+ }
469
+ return true;
470
+ }
471
+ function getLinkReferenceElement(target) {
472
+ if (!(target instanceof Element)) return null;
473
+ return target.closest("a, [data-link-card=\"true\"], [data-link-iframe=\"true\"], [data-schema-link=\"true\"]");
474
+ }
146
475
  LinkToolbar.displayName = "LinkToolbar";
147
476
  //#endregion
148
- export { LinkToolbar as default };
477
+ export { LinkToolbar as default, readToolbarNode, shouldRunToolbarActionFromEvent, shouldRunToolbarActionFromPointer };
@@ -0,0 +1,49 @@
1
+ import { getKernelFromEditor, init_utils } from "../../../../editor-kernel/utils.js";
2
+ import { ILinkService } from "../../service/i-link-service.js";
3
+ import { useEffect, useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/plugins/link/react/components/SchemaLink.tsx
6
+ init_utils();
7
+ const SchemaLink = ({ editor, node, rendererRegistry }) => {
8
+ const service = getKernelFromEditor(editor)?.requireService(ILinkService);
9
+ const [, setRendererVersion] = useState(0);
10
+ const url = node.getURL();
11
+ const schema = service?.parseSchemaUrl(url) || null;
12
+ useEffect(() => {
13
+ const handleChange = () => {
14
+ setRendererVersion((version) => version + 1);
15
+ };
16
+ rendererRegistry.on("change", handleChange);
17
+ return () => {
18
+ rendererRegistry.off("change", handleChange);
19
+ };
20
+ }, [rendererRegistry]);
21
+ const props = {
22
+ editor,
23
+ node,
24
+ payload: node.getPayload(),
25
+ schema,
26
+ schemaType: node.getSchemaType(),
27
+ title: node.getTitle(),
28
+ url
29
+ };
30
+ return rendererRegistry.renderLegacySchemaLink(props) || rendererRegistry.renderSchemaNode(props) || /* @__PURE__ */ jsx(DefaultSchemaLink, { ...props });
31
+ };
32
+ SchemaLink.displayName = "SchemaLink";
33
+ function DefaultSchemaLink(props) {
34
+ return /* @__PURE__ */ jsxs("a", {
35
+ href: props.url,
36
+ style: {
37
+ border: "1px solid rgba(0,0,0,0.12)",
38
+ borderRadius: 8,
39
+ color: "inherit",
40
+ display: "inline-flex",
41
+ gap: 8,
42
+ padding: "8px 10px",
43
+ textDecoration: "none"
44
+ },
45
+ children: [/* @__PURE__ */ jsx("span", { children: props.schemaType || "schema" }), /* @__PURE__ */ jsx("strong", { children: props.title })]
46
+ });
47
+ }
48
+ //#endregion
49
+ export { SchemaLink as default };
@@ -1,3 +1,3 @@
1
- import { ReactLinkPluginProps } from "./type.js";
1
+ import { ReactLinkDefaultToolbarItemKey, ReactLinkDefaultToolbarItems, ReactLinkPluginProps } from "./type.js";
2
2
  import { ReactLinkPlugin } from "./ReactLinkPlugin.js";
3
- export { ReactLinkPlugin, type ReactLinkPluginProps };
3
+ export { type ReactLinkDefaultToolbarItemKey, type ReactLinkDefaultToolbarItems, ReactLinkPlugin, type ReactLinkPluginProps };