@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,16 +1,41 @@
1
1
  import { IEditorPluginConstructor } from "../../../types/kernel.js";
2
2
  import "../../../types/index.js";
3
+ import { LinkCardNode } from "../node/LinkCardNode.js";
4
+ import { LinkIframeNode } from "../node/LinkIframeNode.js";
3
5
  import { LinkAttributes } from "../node/LinkNode.js";
6
+ import { SchemaNode } from "../node/SchemaNode.js";
7
+ import { LinkEmbedRule, LinkLabels, LinkToolbarAction, SchemaLinkRendererConfig, SchemaRule } from "../service/i-link-service.js";
8
+ import { normalizeSchemaLinkNode } from "../normalization/index.js";
9
+ import { LexicalEditor } from "lexical";
4
10
  //#region src/plugins/link/plugin/index.d.ts
5
11
  interface LinkPluginOptions {
12
+ allowedProtocols?: string[];
13
+ /** Registration-time option. Recreate LinkPlugin to apply changes. */
6
14
  attributes?: LinkAttributes;
15
+ decoratorCard?: (node: LinkCardNode, editor: LexicalEditor) => any;
16
+ decoratorIframe?: (node: LinkIframeNode, editor: LexicalEditor) => any;
17
+ decoratorSchema?: (node: SchemaNode, editor: LexicalEditor) => any;
18
+ /** Registration-time option. Recreate LinkPlugin to apply changes. */
7
19
  enableHotkey?: boolean;
20
+ labels?: Partial<LinkLabels>;
21
+ linkEmbedRules?: LinkEmbedRule[];
22
+ /** Registration-time option. Recreate LinkPlugin to apply changes. */
8
23
  linkRegex?: RegExp;
24
+ /** Registration-time normalization strategy. Defaults to true for legacy compatibility. */
25
+ normalizeSchemaLinks?: boolean;
26
+ schemaLinkRenderers?: SchemaLinkRendererConfig[];
27
+ schemaRules?: SchemaRule[];
28
+ /** Registration-time option. Recreate LinkPlugin to apply changes. */
9
29
  theme?: {
10
30
  link?: string;
31
+ linkCard?: string;
32
+ linkIframe?: string;
33
+ schemaLink?: string;
11
34
  };
35
+ toolbarActions?: LinkToolbarAction[];
36
+ /** Registration-time option. Recreate LinkPlugin to apply changes. */
12
37
  validateUrl?: (url: string) => boolean;
13
38
  }
14
39
  declare const LinkPlugin: IEditorPluginConstructor<LinkPluginOptions>;
15
40
  //#endregion
16
- export { LinkPlugin, LinkPluginOptions };
41
+ export { LinkPlugin, LinkPluginOptions, normalizeSchemaLinkNode };
@@ -2,9 +2,14 @@ import { INodeHelper, init_helper } from "../../../editor-kernel/inode/helper.js
2
2
  import { KernelPlugin, init_plugin } from "../../../editor-kernel/plugin.js";
3
3
  import { ILitexmlService } from "../../litexml/service/litexml-service.js";
4
4
  import { IMarkdownShortCutService } from "../../markdown/service/shortcut.js";
5
+ import { ILinkService, LinkService } from "../service/i-link-service.js";
5
6
  import { $createLinkNode, $isLinkNode, AutoLinkNode, LinkNode } from "../node/LinkNode.js";
6
7
  import { INSERT_LINK_COMMAND, registerLinkCommand } from "../command/index.js";
7
- import { ILinkService, LinkService } from "../service/i-link-service.js";
8
+ import { $isLinkCardNode, LinkCardNode } from "../node/LinkCardNode.js";
9
+ import { $isLinkBlockCardNode, LinkBlockCardNode } from "../node/LinkBlockCardNode.js";
10
+ import { $isLinkIframeNode, LinkIframeNode } from "../node/LinkIframeNode.js";
11
+ import { $isSchemaNode, SchemaNode } from "../node/SchemaNode.js";
12
+ import { normalizeSchemaLinkNode } from "../normalization/index.js";
8
13
  import { registerLinkCommands } from "./registry.js";
9
14
  import { $createTextNode, COMMAND_PRIORITY_NORMAL, PASTE_COMMAND } from "lexical";
10
15
  //#region src/plugins/link/plugin/index.ts
@@ -18,10 +23,22 @@ const LinkPlugin = class extends KernelPlugin {
18
23
  super();
19
24
  this.kernel = kernel;
20
25
  this.config = config;
21
- this.linkRegex = /^https?:\/\/\S+$/;
26
+ this.linkRegex = /^[a-z][\d+.a-z-]*:\/\/\S+$/i;
22
27
  this.service = new LinkService();
23
- kernel.registerNodes([LinkNode, AutoLinkNode]);
28
+ kernel.registerNodes([
29
+ LinkNode,
30
+ AutoLinkNode,
31
+ LinkCardNode,
32
+ LinkBlockCardNode,
33
+ LinkIframeNode,
34
+ SchemaNode
35
+ ]);
36
+ this.service.updateConfig(config);
24
37
  kernel.registerService(ILinkService, this.service);
38
+ this.registerDecorator(kernel, LinkCardNode.getType(), (node, editor) => config?.decoratorCard ? config.decoratorCard(node, editor) : null);
39
+ this.registerDecorator(kernel, LinkBlockCardNode.getType(), (node, editor) => config?.decoratorCard ? config.decoratorCard(node, editor) : null);
40
+ this.registerDecorator(kernel, LinkIframeNode.getType(), (node, editor) => config?.decoratorIframe ? config.decoratorIframe(node, editor) : null);
41
+ this.registerDecorator(kernel, SchemaNode.getType(), (node, editor) => config?.decoratorSchema ? config.decoratorSchema(node, editor) : null);
25
42
  if (config?.theme) kernel.registerThemes(config.theme);
26
43
  if (config?.linkRegex) this.linkRegex = config.linkRegex;
27
44
  }
@@ -45,6 +62,9 @@ const LinkPlugin = class extends KernelPlugin {
45
62
  }
46
63
  return false;
47
64
  }, COMMAND_PRIORITY_NORMAL));
65
+ if (this.config?.normalizeSchemaLinks !== false) this.register(editor.registerNodeTransform(LinkNode, (node) => {
66
+ normalizeSchemaLinkNode(node, editor, this.service);
67
+ }));
48
68
  this.registerMarkdown();
49
69
  this.registerLiteXml();
50
70
  }
@@ -69,6 +89,74 @@ const LinkPlugin = class extends KernelPlugin {
69
89
  version: 1
70
90
  })];
71
91
  });
92
+ litexmlService.registerXMLWriter(LinkCardNode.getType(), (node, ctx) => {
93
+ if (!$isLinkCardNode(node)) return false;
94
+ return ctx.createXmlNode("link-card", {
95
+ description: node.getDescription(),
96
+ href: node.getURL(),
97
+ icon: node.getIcon(),
98
+ openTarget: node.getOpenTarget() || "_blank",
99
+ title: node.getTitle()
100
+ });
101
+ });
102
+ litexmlService.registerXMLWriter(LinkBlockCardNode.getType(), (node, ctx) => {
103
+ if (!$isLinkBlockCardNode(node)) return false;
104
+ return ctx.createXmlNode("link-card", {
105
+ description: node.getDescription(),
106
+ href: node.getURL(),
107
+ icon: node.getIcon(),
108
+ layout: "block",
109
+ openTarget: node.getOpenTarget() || "_blank",
110
+ title: node.getTitle()
111
+ });
112
+ });
113
+ litexmlService.registerXMLReader("link-card", (xmlNode) => {
114
+ return [INodeHelper.createTypeNode(xmlNode.getAttribute("layout") === "block" ? LinkBlockCardNode.getType() : LinkCardNode.getType(), {
115
+ description: xmlNode.getAttribute("description") || "",
116
+ icon: xmlNode.getAttribute("icon") || "",
117
+ openTarget: xmlNode.getAttribute("openTarget") || "_blank",
118
+ title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
119
+ type: xmlNode.getAttribute("layout") === "block" ? LinkBlockCardNode.getType() : LinkCardNode.getType(),
120
+ url: xmlNode.getAttribute("href") || "",
121
+ version: 1
122
+ })];
123
+ });
124
+ litexmlService.registerXMLWriter(LinkIframeNode.getType(), (node, ctx) => {
125
+ if (!$isLinkIframeNode(node)) return false;
126
+ return ctx.createXmlNode("link-iframe", {
127
+ href: node.getURL(),
128
+ src: node.getSrc(),
129
+ title: node.getTitle()
130
+ });
131
+ });
132
+ litexmlService.registerXMLReader("link-iframe", (xmlNode) => {
133
+ return [INodeHelper.createTypeNode(LinkIframeNode.getType(), {
134
+ src: xmlNode.getAttribute("src") || xmlNode.getAttribute("href") || "",
135
+ title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
136
+ type: LinkIframeNode.getType(),
137
+ url: xmlNode.getAttribute("href") || "",
138
+ version: 1
139
+ })];
140
+ });
141
+ litexmlService.registerXMLWriter(SchemaNode.getType(), (node, ctx) => {
142
+ if (!$isSchemaNode(node)) return false;
143
+ return ctx.createXmlNode("schema-link", {
144
+ href: node.getURL(),
145
+ payload: JSON.stringify(node.getPayload() ?? null),
146
+ schemaType: node.getSchemaType(),
147
+ title: node.getTitle()
148
+ });
149
+ });
150
+ litexmlService.registerXMLReader("schema-link", (xmlNode) => {
151
+ return [INodeHelper.createTypeNode(SchemaNode.getType(), {
152
+ payload: parseJsonAttribute(xmlNode.getAttribute("payload")),
153
+ schemaType: xmlNode.getAttribute("schemaType") || "",
154
+ title: xmlNode.getAttribute("title") || xmlNode.getAttribute("href") || "",
155
+ type: SchemaNode.getType(),
156
+ url: xmlNode.getAttribute("href") || "",
157
+ version: 1
158
+ })];
159
+ });
72
160
  }
73
161
  registerMarkdown() {
74
162
  this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownShortCut({
@@ -88,6 +176,30 @@ const LinkPlugin = class extends KernelPlugin {
88
176
  this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkNode.getType(), (ctx, node) => {
89
177
  if ($isLinkNode(node)) ctx.wrap("[", `](${node.getURL()})`);
90
178
  });
179
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkCardNode.getType(), (ctx, node) => {
180
+ if ($isLinkCardNode(node)) {
181
+ ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
182
+ return true;
183
+ }
184
+ });
185
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkBlockCardNode.getType(), (ctx, node) => {
186
+ if ($isLinkBlockCardNode(node)) {
187
+ ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
188
+ return true;
189
+ }
190
+ });
191
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(LinkIframeNode.getType(), (ctx, node) => {
192
+ if ($isLinkIframeNode(node)) {
193
+ ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
194
+ return true;
195
+ }
196
+ });
197
+ this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownWriter(SchemaNode.getType(), (ctx, node) => {
198
+ if ($isSchemaNode(node)) {
199
+ ctx.appendLine(`[${node.getTitle()}](${node.getURL()})\n`);
200
+ return true;
201
+ }
202
+ });
91
203
  this.kernel.requireService(IMarkdownShortCutService)?.registerMarkdownReader("link", (node, children) => {
92
204
  return [INodeHelper.createElementNode("link", {
93
205
  children,
@@ -102,5 +214,13 @@ const LinkPlugin = class extends KernelPlugin {
102
214
  });
103
215
  }
104
216
  };
217
+ function parseJsonAttribute(value) {
218
+ if (!value) return void 0;
219
+ try {
220
+ return JSON.parse(value);
221
+ } catch {
222
+ return value;
223
+ }
224
+ }
105
225
  //#endregion
106
- export { LinkPlugin };
226
+ export { LinkPlugin, normalizeSchemaLinkNode };
@@ -2,49 +2,207 @@
2
2
  import { useLexicalComposerContext } from "../../../editor-kernel/react/react-context.js";
3
3
  import { useLexicalEditor } from "../../../editor-kernel/react/useLexicalEditor.js";
4
4
  import { MarkdownPlugin } from "../../markdown/plugin/index.js";
5
+ import { useTranslation } from "../../../editor-kernel/react/useTranslation.js";
5
6
  import { ILinkService } from "../service/i-link-service.js";
7
+ import { UNLINK_LINK_COMMAND } from "../command/index.js";
6
8
  import { LinkPlugin } from "../plugin/index.js";
9
+ import { useEditable } from "../../../editor-kernel/react/useEditable.js";
7
10
  import PortalAnchor from "../../../editor-kernel/react/PortalAnchor.js";
11
+ import LinkCard from "./components/LinkCard.js";
8
12
  import { styles } from "./style.js";
9
- import LinkEdit from "./components/LinkEdit.js";
13
+ import LinkEdit, { EDIT_LINK_COMMAND } from "./components/LinkEdit.js";
14
+ import LinkIframe from "./components/LinkIframe.js";
10
15
  import LinkToolbar from "./components/LinkToolbar.js";
11
- import { useLayoutEffect, useState } from "react";
16
+ import SchemaLink from "./components/SchemaLink.js";
17
+ import { LinkReactRendererRegistry, splitReactSchemaRules } from "./renderer-registry.js";
18
+ import { useLayoutEffect, useMemo, useRef, useState } from "react";
12
19
  import { jsx, jsxs } from "react/jsx-runtime";
20
+ import { message } from "antd";
13
21
  //#region src/plugins/link/react/ReactLinkPlugin.tsx
14
- const ReactLinkPlugin = ({ theme, enableHotkey = true, validateUrl, attributes }) => {
15
- const [enableToolbar, setEnableToolbar] = useState(false);
22
+ async function copyTextToClipboard(text) {
23
+ try {
24
+ await navigator.clipboard.writeText(text);
25
+ } catch {
26
+ const textarea = document.createElement("textarea");
27
+ textarea.value = text;
28
+ document.body.append(textarea);
29
+ textarea.select();
30
+ document.execCommand("copy");
31
+ textarea.remove();
32
+ }
33
+ }
34
+ function isDefaultToolbarItemEnabled(defaultToolbarItems, key) {
35
+ if (defaultToolbarItems === false) return false;
36
+ if (defaultToolbarItems === true || defaultToolbarItems === void 0) return true;
37
+ return defaultToolbarItems[key] !== false;
38
+ }
39
+ const ReactLinkPlugin = ({ allowedProtocols, theme, enableHotkey = true, validateUrl, attributes, defaultToolbarItems, labels, linkEmbedRules, normalizeSchemaLinks, renderLinkCard, renderLinkIframe, renderSchema, schemaLinkRenderers, schemaRules, toolbarActions }) => {
40
+ const [enableToolbar, setEnableToolbar] = useState(true);
41
+ const [linkService, setLinkService] = useState(null);
16
42
  const [editor] = useLexicalComposerContext();
43
+ const { editable } = useEditable();
44
+ const t = useTranslation();
45
+ const registeredRef = useRef(false);
46
+ const rendererRegistryRef = useRef(new LinkReactRendererRegistry());
47
+ const splitSchemaRules = useMemo(() => splitReactSchemaRules(schemaRules), [schemaRules]);
17
48
  useLayoutEffect(() => {
49
+ if (registeredRef.current) return;
50
+ registeredRef.current = true;
18
51
  editor.registerPlugin(MarkdownPlugin);
19
52
  editor.registerPlugin(LinkPlugin, {
53
+ allowedProtocols,
20
54
  attributes,
55
+ decoratorCard: (node, editor) => /* @__PURE__ */ jsx(LinkCard, {
56
+ description: node.getDescription(),
57
+ editor,
58
+ icon: node.getIcon(),
59
+ node,
60
+ openTarget: node.getOpenTarget(),
61
+ rendererRegistry: rendererRegistryRef.current,
62
+ title: node.getTitle(),
63
+ url: node.getURL()
64
+ }),
65
+ decoratorIframe: (node, editor) => /* @__PURE__ */ jsx(LinkIframe, {
66
+ editor,
67
+ node,
68
+ rendererRegistry: rendererRegistryRef.current,
69
+ src: node.getSrc(),
70
+ title: node.getTitle(),
71
+ url: node.getURL()
72
+ }),
73
+ decoratorSchema: (node, editor) => /* @__PURE__ */ jsx(SchemaLink, {
74
+ editor,
75
+ node,
76
+ rendererRegistry: rendererRegistryRef.current
77
+ }),
21
78
  enableHotkey,
79
+ labels,
80
+ linkEmbedRules,
81
+ normalizeSchemaLinks,
82
+ schemaLinkRenderers: getSchemaLinkRendererProtocols(schemaLinkRenderers),
83
+ schemaRules: splitSchemaRules.coreRules,
22
84
  theme: theme || styles,
85
+ toolbarActions,
23
86
  validateUrl
24
87
  });
25
88
  }, [
89
+ allowedProtocols,
26
90
  attributes,
27
91
  enableHotkey,
92
+ labels,
93
+ linkEmbedRules,
94
+ normalizeSchemaLinks,
95
+ schemaLinkRenderers,
96
+ schemaRules,
28
97
  styles,
29
98
  theme,
99
+ toolbarActions,
30
100
  validateUrl
31
101
  ]);
102
+ useLayoutEffect(() => {
103
+ rendererRegistryRef.current.update({
104
+ renderLinkCard,
105
+ renderLinkIframe,
106
+ renderSchema,
107
+ schemaLinkRenderers,
108
+ schemaRenderers: splitSchemaRules.schemaRenderers
109
+ });
110
+ }, [
111
+ renderLinkCard,
112
+ renderLinkIframe,
113
+ renderSchema,
114
+ schemaLinkRenderers,
115
+ splitSchemaRules.schemaRenderers
116
+ ]);
117
+ useLayoutEffect(() => {
118
+ if (!registeredRef.current) return;
119
+ editor.requireService(ILinkService)?.updateConfig({
120
+ allowedProtocols,
121
+ labels,
122
+ linkEmbedRules,
123
+ schemaLinkRenderers: getSchemaLinkRendererProtocols(schemaLinkRenderers),
124
+ schemaRules: splitSchemaRules.coreRules,
125
+ toolbarActions
126
+ });
127
+ }, [
128
+ allowedProtocols,
129
+ editor,
130
+ labels,
131
+ linkEmbedRules,
132
+ schemaLinkRenderers,
133
+ splitSchemaRules.coreRules,
134
+ toolbarActions
135
+ ]);
32
136
  useLexicalEditor(() => {
33
137
  const linkService = editor.requireService(ILinkService);
138
+ setLinkService(linkService);
34
139
  setEnableToolbar(linkService.enableLinkToolbar);
35
140
  const handleChange = () => {
36
141
  setEnableToolbar(linkService.enableLinkToolbar);
37
142
  };
38
143
  linkService.on("linkToolbarChange", handleChange);
144
+ const unregisterItems = [];
145
+ const registerDefaultToolbarItem = (key, item) => {
146
+ if (!isDefaultToolbarItemEnabled(defaultToolbarItems, key)) return;
147
+ unregisterItems.push(linkService.registerToolbarItem(item));
148
+ };
149
+ registerDefaultToolbarItem("open", {
150
+ icon: "open",
151
+ key: "open",
152
+ label: "link.open",
153
+ onClick: ({ editor, linkNode }) => {
154
+ const linkUrl = editor.getEditorState().read(() => linkNode.getURL());
155
+ window.open(linkUrl, "_blank");
156
+ },
157
+ order: 10
158
+ });
159
+ registerDefaultToolbarItem("edit", {
160
+ icon: "edit",
161
+ key: "edit",
162
+ label: "link.edit",
163
+ onClick: ({ close, editor, linkDom, linkNode }) => {
164
+ close();
165
+ editor.dispatchCommand(EDIT_LINK_COMMAND, {
166
+ linkNode,
167
+ linkNodeDOM: linkDom
168
+ });
169
+ },
170
+ order: 20
171
+ });
172
+ registerDefaultToolbarItem("copy", {
173
+ icon: "copy",
174
+ key: "copy",
175
+ label: "link.copy",
176
+ onClick: async ({ editor, linkNode }) => {
177
+ await copyTextToClipboard(editor.getEditorState().read(() => linkNode.getURL()));
178
+ message.success(t("link.copySuccess"));
179
+ },
180
+ order: 30
181
+ });
182
+ registerDefaultToolbarItem("unlink", {
183
+ icon: "unlink",
184
+ key: "unlink",
185
+ label: "link.unlink",
186
+ onClick: ({ close, editor, linkNode }) => {
187
+ editor.dispatchCommand(UNLINK_LINK_COMMAND, { key: linkNode.getKey() });
188
+ close();
189
+ },
190
+ order: 40
191
+ });
39
192
  return () => {
40
193
  linkService.off("linkToolbarChange", handleChange);
194
+ unregisterItems.forEach((unregister) => unregister());
41
195
  };
42
- }, []);
196
+ }, [defaultToolbarItems, t]);
43
197
  return /* @__PURE__ */ jsxs(PortalAnchor, { children: [/* @__PURE__ */ jsx(LinkToolbar, {
44
198
  editor: editor.getLexicalEditor(),
45
- enable: enableToolbar
46
- }), /* @__PURE__ */ jsx(LinkEdit, { editor: editor.getLexicalEditor() })] });
199
+ enable: enableToolbar && editable,
200
+ linkService
201
+ }), editable && /* @__PURE__ */ jsx(LinkEdit, { editor: editor.getLexicalEditor() })] });
47
202
  };
48
203
  ReactLinkPlugin.displayName = "ReactLinkPlugin";
204
+ function getSchemaLinkRendererProtocols(renderers) {
205
+ return renderers?.map(({ protocol }) => ({ protocol }));
206
+ }
49
207
  //#endregion
50
208
  export { ReactLinkPlugin, ReactLinkPlugin as default };
@@ -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 };