@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
@@ -0,0 +1,235 @@
1
+ import { getKernelFromEditor, init_utils } from "../../../../editor-kernel/utils.js";
2
+ import { ILinkService } from "../../service/i-link-service.js";
3
+ import { EDIT_LINK_CARD_COMMAND } from "../../node/LinkCardNode.js";
4
+ import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { $createNodeSelection, $getNodeByKey, $setSelection } from "lexical";
7
+ //#region src/plugins/link/react/components/LinkCard.tsx
8
+ init_utils();
9
+ const LinkCard = ({ description, editor, icon, node, openTarget, rendererRegistry, title, url }) => {
10
+ const key = node.getKey();
11
+ const linkService = getKernelFromEditor(editor)?.requireService(ILinkService);
12
+ const subscribeCardMetadata = useCallback((listener) => linkService?.subscribeCardMetadata(listener) || (() => {}), [linkService]);
13
+ const getCardMetadataSnapshot = useCallback(() => linkService?.isCardMetadataLoading(key) ?? false, [key, linkService]);
14
+ const isLoading = useSyncExternalStore(subscribeCardMetadata, getCardMetadataSnapshot, getCardMetadataSnapshot);
15
+ const [isSelected, setIsSelected] = useState(() => isNodeSelected(editor, key));
16
+ const [, setRendererVersion] = useState(0);
17
+ useEffect(() => {
18
+ return editor.registerUpdateListener(() => {
19
+ setIsSelected(isNodeSelected(editor, key));
20
+ });
21
+ }, [editor, key]);
22
+ useEffect(() => {
23
+ const handleChange = () => {
24
+ setRendererVersion((version) => version + 1);
25
+ };
26
+ rendererRegistry.on("change", handleChange);
27
+ return () => {
28
+ rendererRegistry.off("change", handleChange);
29
+ };
30
+ }, [rendererRegistry]);
31
+ const onClickCapture = useCallback((event) => {
32
+ event.preventDefault();
33
+ event.stopPropagation();
34
+ if (editor.isEditable()) return;
35
+ if (openTarget === "_self") {
36
+ window.location.href = url;
37
+ return;
38
+ }
39
+ window.open(url, openTarget || "_blank");
40
+ }, [
41
+ editor,
42
+ openTarget,
43
+ url
44
+ ]);
45
+ const onMouseDownCapture = useCallback((event) => {
46
+ if (!editor.isEditable()) return;
47
+ event.preventDefault();
48
+ event.stopPropagation();
49
+ editor.focus();
50
+ editor.update(() => {
51
+ const selection = $createNodeSelection();
52
+ selection.add(key);
53
+ $setSelection(selection);
54
+ });
55
+ editor.dispatchCommand(EDIT_LINK_CARD_COMMAND, {
56
+ cardNode: node,
57
+ cardNodeDOM: editor.getElementByKey(key)
58
+ });
59
+ }, [
60
+ editor,
61
+ key,
62
+ node
63
+ ]);
64
+ const props = {
65
+ description,
66
+ editor,
67
+ icon,
68
+ isLoading,
69
+ isSelected,
70
+ layout: node.isInline() ? "inline" : "block",
71
+ node,
72
+ onClickCapture,
73
+ onMouseDownCapture,
74
+ openTarget,
75
+ title,
76
+ url
77
+ };
78
+ return rendererRegistry.renderCardNode(props) || /* @__PURE__ */ jsx(DefaultLinkCard, { ...props });
79
+ };
80
+ LinkCard.displayName = "LinkCard";
81
+ function isNodeSelected(editor, key) {
82
+ return editor.getEditorState().read(() => {
83
+ return $getNodeByKey(key)?.isSelected() ?? false;
84
+ });
85
+ }
86
+ function DefaultLinkCard(props) {
87
+ if (props.layout === "block") return /* @__PURE__ */ jsxs("a", {
88
+ "aria-busy": props.isLoading,
89
+ href: props.url,
90
+ onClickCapture: props.onClickCapture,
91
+ onMouseDownCapture: props.onMouseDownCapture,
92
+ rel: "noreferrer",
93
+ style: {
94
+ alignItems: "center",
95
+ border: "1px solid rgba(0,0,0,0.12)",
96
+ borderRadius: 12,
97
+ color: "inherit",
98
+ display: "grid",
99
+ gap: 12,
100
+ gridTemplateColumns: "40px minmax(0, 1fr)",
101
+ maxWidth: 520,
102
+ outline: props.isSelected ? "2px solid rgba(22,119,255,0.45)" : void 0,
103
+ outlineOffset: props.isSelected ? 1 : void 0,
104
+ padding: 12,
105
+ textDecoration: "none",
106
+ width: "100%"
107
+ },
108
+ target: props.openTarget || "_blank",
109
+ children: [props.isLoading ? /* @__PURE__ */ jsx(LinkCardLoadingIndicator, { size: 18 }) : props.icon ? /* @__PURE__ */ jsx("img", {
110
+ alt: "",
111
+ src: props.icon,
112
+ style: {
113
+ borderRadius: 8,
114
+ height: 40,
115
+ objectFit: "cover",
116
+ width: 40
117
+ }
118
+ }) : null, /* @__PURE__ */ jsxs("span", {
119
+ style: {
120
+ display: "grid",
121
+ gap: 4,
122
+ minWidth: 0
123
+ },
124
+ children: [/* @__PURE__ */ jsx("strong", {
125
+ style: {
126
+ overflow: "hidden",
127
+ textOverflow: "ellipsis",
128
+ whiteSpace: "nowrap"
129
+ },
130
+ children: props.title
131
+ }), props.isLoading ? /* @__PURE__ */ jsx("span", {
132
+ style: {
133
+ fontSize: 12,
134
+ opacity: .65
135
+ },
136
+ children: "Loading preview..."
137
+ }) : props.description ? /* @__PURE__ */ jsx("span", {
138
+ style: {
139
+ opacity: .65,
140
+ overflow: "hidden",
141
+ textOverflow: "ellipsis",
142
+ whiteSpace: "nowrap"
143
+ },
144
+ children: props.description
145
+ }) : null]
146
+ })]
147
+ });
148
+ return /* @__PURE__ */ jsxs("a", {
149
+ "aria-busy": props.isLoading,
150
+ href: props.url,
151
+ onClickCapture: props.onClickCapture,
152
+ onMouseDownCapture: props.onMouseDownCapture,
153
+ rel: "noreferrer",
154
+ style: {
155
+ alignItems: "center",
156
+ color: "#1677ff",
157
+ display: "inline-flex",
158
+ gap: 4,
159
+ lineHeight: 1,
160
+ maxWidth: 320,
161
+ outline: props.isSelected ? "2px solid rgba(22,119,255,0.45)" : void 0,
162
+ outlineOffset: props.isSelected ? 1 : void 0,
163
+ padding: "0 2px",
164
+ textDecoration: "none",
165
+ verticalAlign: "baseline"
166
+ },
167
+ target: props.openTarget || "_blank",
168
+ children: [props.isLoading ? /* @__PURE__ */ jsx(LinkCardLoadingIndicator, { size: "1.1em" }) : props.icon ? /* @__PURE__ */ jsx("img", {
169
+ alt: "",
170
+ src: props.icon,
171
+ style: {
172
+ borderRadius: 5,
173
+ display: "block",
174
+ height: "1.1em",
175
+ objectFit: "cover",
176
+ position: "relative",
177
+ top: "0.06em",
178
+ width: "1.1em"
179
+ }
180
+ }) : /* @__PURE__ */ jsx("span", {
181
+ style: {
182
+ alignItems: "center",
183
+ background: "rgba(0,0,0,0.06)",
184
+ borderRadius: 5,
185
+ display: "inline-flex",
186
+ fontSize: 11,
187
+ height: "1.1em",
188
+ justifyContent: "center",
189
+ lineHeight: 1,
190
+ position: "relative",
191
+ top: "0.06em",
192
+ width: "1.1em"
193
+ },
194
+ children: props.title.slice(0, 1).toUpperCase()
195
+ }), /* @__PURE__ */ jsx("span", {
196
+ style: {
197
+ display: "inline-block",
198
+ lineHeight: 1,
199
+ minWidth: 0,
200
+ overflow: "hidden",
201
+ textOverflow: "ellipsis",
202
+ whiteSpace: "nowrap"
203
+ },
204
+ children: /* @__PURE__ */ jsx("span", {
205
+ style: {
206
+ fontSize: "1em",
207
+ fontWeight: 400,
208
+ overflow: "hidden",
209
+ textOverflow: "ellipsis",
210
+ whiteSpace: "nowrap"
211
+ },
212
+ children: props.title
213
+ })
214
+ })]
215
+ });
216
+ }
217
+ function LinkCardLoadingIndicator({ size }) {
218
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("style", { children: "@keyframes lobe-link-card-spin{to{transform:rotate(360deg)}}" }), /* @__PURE__ */ jsx("span", {
219
+ "aria-label": "Loading link preview",
220
+ role: "status",
221
+ style: {
222
+ animation: "lobe-link-card-spin 1s linear infinite",
223
+ border: "2px solid rgba(0,0,0,0.12)",
224
+ borderRadius: "50%",
225
+ borderTopColor: "#1677ff",
226
+ boxSizing: "border-box",
227
+ display: "inline-block",
228
+ flex: "0 0 auto",
229
+ height: size,
230
+ width: size
231
+ }
232
+ })] });
233
+ }
234
+ //#endregion
235
+ export { DefaultLinkCard, LinkCard as default };
@@ -1,13 +1,13 @@
1
1
  import { useLexicalEditor } from "../../../../editor-kernel/react/useLexicalEditor.js";
2
2
  import { useTranslation } from "../../../../editor-kernel/react/useTranslation.js";
3
3
  import { UPDATE_LINK_TEXT_COMMAND } from "../../command/index.js";
4
- import { useEditable } from "../../../../editor-kernel/react/useEditable.js";
4
+ import { EDIT_LINK_CARD_COMMAND } from "../../node/LinkCardNode.js";
5
5
  import { cleanPosition, updatePosition } from "../../../../utils/updatePosition.js";
6
6
  import { styles } from "../style.js";
7
7
  import { Block, Button, Flexbox, Hotkey, Icon, Input, Text } from "@lobehub/ui";
8
8
  import { BaselineIcon, LinkIcon } from "lucide-react";
9
9
  import { useCallback, useEffect, useRef, useState } from "react";
10
- import { jsx, jsxs } from "react/jsx-runtime";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
11
  import { cssVar } from "antd-style";
12
12
  import { COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_NORMAL, KEY_ESCAPE_COMMAND, KEY_TAB_COMMAND, createCommand } from "lexical";
13
13
  import { mergeRegister } from "@lexical/utils";
@@ -16,27 +16,43 @@ const EDIT_LINK_COMMAND = createCommand();
16
16
  const LinkEdit = ({ editor }) => {
17
17
  const divRef = useRef(null);
18
18
  const linkNodeRef = useRef(null);
19
+ const cardNodeRef = useRef(null);
19
20
  const linkInputRef = useRef(null);
20
21
  const linkTextInputRef = useRef(null);
21
22
  const [linkUrl, setLinkUrl] = useState("");
22
23
  const [linkText, setLinkText] = useState("");
24
+ const [openTarget, setOpenTarget] = useState("_blank");
23
25
  const [linkDom, setLinkDom] = useState(null);
24
- const { editable } = useEditable();
25
26
  const t = useTranslation();
26
27
  const handleCancel = useCallback(() => {
27
28
  if (!editor) return;
28
29
  editor.focus();
29
30
  cleanPosition(divRef.current);
30
31
  linkNodeRef.current = null;
32
+ cardNodeRef.current = null;
31
33
  setLinkUrl("");
32
34
  setLinkText("");
35
+ setOpenTarget("_blank");
33
36
  setLinkDom(null);
34
37
  }, [editor]);
35
38
  const handleSubmit = useCallback(() => {
36
- if (!linkNodeRef.current || !linkInputRef.current || !linkTextInputRef.current || !editor) return;
37
- const linkNode = linkNodeRef.current;
39
+ if (!editor.isEditable()) return;
40
+ if (!linkInputRef.current || !linkTextInputRef.current || !editor) return;
38
41
  const inputDOM = linkInputRef.current.input;
39
42
  const textInputDOM = linkTextInputRef.current.input;
43
+ if (cardNodeRef.current) {
44
+ const cardNode = cardNodeRef.current;
45
+ editor.update(() => {
46
+ cardNode.setTitle(textInputDOM.value);
47
+ cardNode.setURL(inputDOM.value);
48
+ cardNode.setOpenTarget(openTarget);
49
+ });
50
+ editor.focus();
51
+ handleCancel();
52
+ return;
53
+ }
54
+ if (!linkNodeRef.current) return;
55
+ const linkNode = linkNodeRef.current;
40
56
  if (editor.getEditorState().read(() => linkNode.getURL()) !== inputDOM.value) editor.update(() => {
41
57
  linkNode.setURL(inputDOM.value);
42
58
  });
@@ -51,10 +67,11 @@ const LinkEdit = ({ editor }) => {
51
67
  linkNodeRef,
52
68
  linkInputRef,
53
69
  linkTextInputRef,
70
+ openTarget,
54
71
  handleCancel
55
72
  ]);
56
73
  const handleKeyDown = useCallback((event) => {
57
- if (!linkNodeRef.current || !linkInputRef.current || !linkTextInputRef.current || !editor) return;
74
+ if (!linkNodeRef.current && !cardNodeRef.current || !linkInputRef.current || !linkTextInputRef.current || !editor) return;
58
75
  const linkNode = linkNodeRef.current;
59
76
  const inputDOM = linkInputRef.current.input;
60
77
  const textInputDOM = linkTextInputRef.current.input;
@@ -62,6 +79,11 @@ const LinkEdit = ({ editor }) => {
62
79
  case "Enter":
63
80
  event.preventDefault();
64
81
  if (event.currentTarget === textInputDOM) {
82
+ if (cardNodeRef.current) {
83
+ inputDOM.focus();
84
+ return;
85
+ }
86
+ if (!linkNode) return;
65
87
  if (editor.getEditorState().read(() => linkNode.getTextContent()) !== textInputDOM.value) {
66
88
  editor.dispatchCommand(UPDATE_LINK_TEXT_COMMAND, {
67
89
  key: linkNode.getKey(),
@@ -88,11 +110,18 @@ const LinkEdit = ({ editor }) => {
88
110
  handleCancel
89
111
  ]);
90
112
  useEffect(() => {
91
- if (linkDom) updatePosition({
92
- floating: divRef.current,
93
- reference: linkDom
94
- });
95
- else cleanPosition(divRef.current);
113
+ if (linkDom) {
114
+ updatePosition({
115
+ floating: divRef.current,
116
+ reference: linkDom
117
+ });
118
+ const focusTimer = setTimeout(() => {
119
+ linkInputRef.current?.focus();
120
+ });
121
+ return () => {
122
+ clearTimeout(focusTimer);
123
+ };
124
+ } else cleanPosition(divRef.current);
96
125
  }, [linkDom]);
97
126
  useEffect(() => {
98
127
  const handlePointerDown = (event) => {
@@ -111,16 +140,33 @@ const LinkEdit = ({ editor }) => {
111
140
  }, [linkDom]);
112
141
  useLexicalEditor((editor) => {
113
142
  return mergeRegister(editor.registerCommand(EDIT_LINK_COMMAND, (payload) => {
143
+ if (!editor.isEditable()) return false;
114
144
  if (!payload.linkNode || !payload.linkNodeDOM) {
115
145
  handleCancel();
116
146
  return false;
117
147
  }
118
148
  linkNodeRef.current = payload.linkNode;
149
+ cardNodeRef.current = null;
119
150
  setLinkUrl(payload.linkNode.getURL());
120
151
  setLinkText(payload.linkNode.getTextContent());
152
+ setOpenTarget(payload.linkNode.getTarget() || "_blank");
121
153
  setLinkDom(payload.linkNodeDOM);
122
154
  return true;
155
+ }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(EDIT_LINK_CARD_COMMAND, (payload) => {
156
+ if (!editor.isEditable()) return false;
157
+ if (!payload.cardNode || !payload.cardNodeDOM) {
158
+ handleCancel();
159
+ return false;
160
+ }
161
+ cardNodeRef.current = payload.cardNode;
162
+ linkNodeRef.current = null;
163
+ setLinkUrl(payload.cardNode.getURL());
164
+ setLinkText(payload.cardNode.getTitle());
165
+ setOpenTarget(payload.cardNode.getOpenTarget() || "_blank");
166
+ setLinkDom(payload.cardNodeDOM);
167
+ return true;
123
168
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(KEY_ESCAPE_COMMAND, () => {
169
+ if (!linkNodeRef.current && !cardNodeRef.current) return false;
124
170
  handleCancel();
125
171
  return true;
126
172
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(KEY_TAB_COMMAND, (payload) => {
@@ -133,7 +179,8 @@ const LinkEdit = ({ editor }) => {
133
179
  return false;
134
180
  }, COMMAND_PRIORITY_NORMAL));
135
181
  }, [editor]);
136
- if (!linkNodeRef.current || !editable) return null;
182
+ if (!linkNodeRef.current && !cardNodeRef.current) return null;
183
+ const isCardEdit = Boolean(cardNodeRef.current);
137
184
  return /* @__PURE__ */ jsxs(Block, {
138
185
  className: styles.linkEdit,
139
186
  ref: divRef,
@@ -145,7 +192,7 @@ const LinkEdit = ({ editor }) => {
145
192
  children: [
146
193
  /* @__PURE__ */ jsx(Text, {
147
194
  weight: 500,
148
- children: t("link.editTextTitle")
195
+ children: isCardEdit ? "Title" : t("link.editTextTitle")
149
196
  }),
150
197
  /* @__PURE__ */ jsx(Input, {
151
198
  onChange: (e) => {
@@ -175,7 +222,39 @@ const LinkEdit = ({ editor }) => {
175
222
  }),
176
223
  ref: linkInputRef,
177
224
  value: linkUrl
178
- })
225
+ }),
226
+ isCardEdit ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
227
+ weight: 500,
228
+ children: "Open target"
229
+ }), /* @__PURE__ */ jsxs("select", {
230
+ onChange: (e) => {
231
+ setOpenTarget(e.target.value);
232
+ },
233
+ onKeyDown: (e) => {
234
+ if (e.key === "Enter") {
235
+ e.preventDefault();
236
+ handleSubmit();
237
+ }
238
+ if (e.key === "Escape") {
239
+ e.preventDefault();
240
+ handleCancel();
241
+ }
242
+ },
243
+ style: {
244
+ border: `1px solid ${cssVar.colorBorder}`,
245
+ borderRadius: 6,
246
+ height: 32,
247
+ paddingInline: 8
248
+ },
249
+ value: openTarget,
250
+ children: [/* @__PURE__ */ jsx("option", {
251
+ value: "_blank",
252
+ children: "New page"
253
+ }), /* @__PURE__ */ jsx("option", {
254
+ value: "_self",
255
+ children: "Current page"
256
+ })]
257
+ })] }) : null
179
258
  ]
180
259
  }), /* @__PURE__ */ jsx(Flexbox, {
181
260
  className: styles.linkEditFooter,
@@ -0,0 +1,128 @@
1
+ import { useCallback, useEffect, useState } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { $createNodeSelection, $getNodeByKey, $setSelection } from "lexical";
4
+ //#region src/plugins/link/react/components/LinkIframe.tsx
5
+ const LinkIframe = ({ editor, node, rendererRegistry, src, title, url }) => {
6
+ const key = node.getKey();
7
+ const [isSelected, setIsSelected] = useState(() => isNodeSelected(editor, key));
8
+ const [loadedSrc, setLoadedSrc] = useState(null);
9
+ const isLoading = loadedSrc !== src;
10
+ const [, setRendererVersion] = useState(0);
11
+ useEffect(() => {
12
+ return editor.registerUpdateListener(() => {
13
+ setIsSelected(isNodeSelected(editor, key));
14
+ });
15
+ }, [editor, key]);
16
+ useEffect(() => {
17
+ const handleChange = () => {
18
+ setRendererVersion((version) => version + 1);
19
+ };
20
+ rendererRegistry.on("change", handleChange);
21
+ return () => {
22
+ rendererRegistry.off("change", handleChange);
23
+ };
24
+ }, [rendererRegistry]);
25
+ const onLoad = useCallback(() => {
26
+ setLoadedSrc(src);
27
+ }, [src]);
28
+ const onMouseDownCapture = useCallback((event) => {
29
+ if (!editor.isEditable()) return;
30
+ event.currentTarget.focus();
31
+ event.preventDefault();
32
+ event.stopPropagation();
33
+ editor.focus();
34
+ editor.update(() => {
35
+ const selection = $createNodeSelection();
36
+ selection.add(key);
37
+ $setSelection(selection);
38
+ });
39
+ }, [editor, key]);
40
+ const props = {
41
+ editor,
42
+ isEditable: editor.isEditable(),
43
+ isLoading,
44
+ isSelected,
45
+ node,
46
+ onLoad,
47
+ onMouseDownCapture,
48
+ src,
49
+ title,
50
+ url
51
+ };
52
+ return rendererRegistry.renderIframeNode(props) || /* @__PURE__ */ jsx(DefaultLinkIframe, { ...props });
53
+ };
54
+ LinkIframe.displayName = "LinkIframe";
55
+ function isNodeSelected(editor, key) {
56
+ return editor.getEditorState().read(() => {
57
+ return $getNodeByKey(key)?.isSelected() ?? false;
58
+ });
59
+ }
60
+ function DefaultLinkIframe(props) {
61
+ return /* @__PURE__ */ jsxs("div", {
62
+ style: {
63
+ border: "1px solid rgba(0,0,0,0.12)",
64
+ borderRadius: 8,
65
+ boxShadow: props.isSelected ? "0 0 0 2px rgba(22,119,255,0.18)" : void 0,
66
+ overflow: "hidden",
67
+ position: "relative",
68
+ width: "100%",
69
+ ...props.isSelected ? { borderColor: "#1677ff" } : {}
70
+ },
71
+ tabIndex: 0,
72
+ children: [
73
+ props.isLoading && /* @__PURE__ */ jsx("style", { children: "@keyframes lobe-link-iframe-spin{to{transform:rotate(360deg)}}" }),
74
+ /* @__PURE__ */ jsx("div", {
75
+ onMouseDownCapture: props.onMouseDownCapture,
76
+ style: {
77
+ borderBottom: "1px solid rgba(0,0,0,0.08)",
78
+ color: "rgba(0,0,0,0.55)",
79
+ cursor: props.isEditable ? "pointer" : "default",
80
+ fontSize: 12,
81
+ padding: "8px 10px"
82
+ },
83
+ children: props.title
84
+ }),
85
+ props.isLoading && /* @__PURE__ */ jsxs("div", {
86
+ style: {
87
+ alignItems: "center",
88
+ background: "rgba(0,0,0,0.03)",
89
+ bottom: 0,
90
+ color: "rgba(0,0,0,0.45)",
91
+ display: "flex",
92
+ fontSize: 13,
93
+ gap: 8,
94
+ height: 320,
95
+ justifyContent: "center",
96
+ left: 0,
97
+ position: "absolute",
98
+ right: 0,
99
+ zIndex: 1
100
+ },
101
+ children: [/* @__PURE__ */ jsx("span", { style: {
102
+ animation: "lobe-link-iframe-spin 1s linear infinite",
103
+ border: "2px solid rgba(0,0,0,0.12)",
104
+ borderRadius: "50%",
105
+ borderTopColor: "#1677ff",
106
+ display: "inline-block",
107
+ height: 14,
108
+ width: 14
109
+ } }), "Loading embed..."]
110
+ }),
111
+ /* @__PURE__ */ jsx("iframe", {
112
+ loading: "lazy",
113
+ onLoad: props.onLoad,
114
+ src: props.src,
115
+ style: {
116
+ border: 0,
117
+ display: "block",
118
+ height: 320,
119
+ visibility: props.isLoading ? "hidden" : "visible",
120
+ width: "100%"
121
+ },
122
+ title: props.title
123
+ })
124
+ ]
125
+ });
126
+ }
127
+ //#endregion
128
+ export { DefaultLinkIframe, LinkIframe as default };