@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,149 @@
1
+ import { getKernelFromEditor, init_utils } from "../../../editor-kernel/utils.js";
2
+ import { HOVER_LINK_COMMAND, HOVER_OUT_LINK_COMMAND } from "./LinkNode.js";
3
+ import { $applyNodeReplacement, DecoratorNode } from "lexical";
4
+ import { addClassNamesToElement } from "@lexical/utils";
5
+ //#region src/plugins/link/node/SchemaNode.ts
6
+ init_utils();
7
+ var SchemaNode = class SchemaNode extends DecoratorNode {
8
+ static getType() {
9
+ return "schema-link";
10
+ }
11
+ static clone(node) {
12
+ return new SchemaNode(node.__url, node.__schemaType, node.__payload, node.__title, node.__key);
13
+ }
14
+ static importJSON(serializedNode) {
15
+ return $createSchemaNode({
16
+ payload: serializedNode.payload,
17
+ schemaType: serializedNode.schemaType,
18
+ title: serializedNode.title,
19
+ url: serializedNode.url
20
+ }).updateFromJSON(serializedNode);
21
+ }
22
+ static importDOM() {
23
+ return { a: (node) => {
24
+ if (node instanceof HTMLAnchorElement && node.dataset.schemaLink === "true") return {
25
+ conversion: $convertSchemaElement,
26
+ priority: 2
27
+ };
28
+ return null;
29
+ } };
30
+ }
31
+ constructor(url, schemaType, payload, title, key) {
32
+ super(key);
33
+ this.__url = url;
34
+ this.__schemaType = schemaType || "";
35
+ this.__payload = payload;
36
+ this.__title = title || url;
37
+ }
38
+ createDOM(config, editor) {
39
+ const element = document.createElement("div");
40
+ element.dataset.schemaLink = "true";
41
+ addClassNamesToElement(element, config.theme.schemaLink);
42
+ element.addEventListener("mouseenter", (event) => {
43
+ if (event.target instanceof HTMLElement) {
44
+ event.target.classList.add("hover");
45
+ editor.dispatchCommand(HOVER_LINK_COMMAND, {
46
+ event,
47
+ node: this
48
+ });
49
+ }
50
+ });
51
+ element.addEventListener("mouseleave", (event) => {
52
+ if (event.target instanceof HTMLElement) {
53
+ event.target.classList.remove("hover");
54
+ editor.dispatchCommand(HOVER_OUT_LINK_COMMAND, { event });
55
+ }
56
+ });
57
+ return element;
58
+ }
59
+ updateDOM() {
60
+ return false;
61
+ }
62
+ exportDOM() {
63
+ const element = document.createElement("a");
64
+ element.dataset.schemaLink = "true";
65
+ element.dataset.schemaType = this.getSchemaType();
66
+ element.dataset.payload = JSON.stringify(this.getPayload() ?? null);
67
+ element.href = this.getURL();
68
+ element.textContent = this.getTitle();
69
+ return { element };
70
+ }
71
+ exportJSON() {
72
+ return {
73
+ ...super.exportJSON(),
74
+ payload: this.getPayload(),
75
+ schemaType: this.getSchemaType(),
76
+ title: this.getTitle(),
77
+ url: this.getURL()
78
+ };
79
+ }
80
+ updateFromJSON(serializedNode) {
81
+ return super.updateFromJSON(serializedNode).setURL(serializedNode.url).setSchemaType(serializedNode.schemaType).setPayload(serializedNode.payload).setTitle(serializedNode.title);
82
+ }
83
+ getTextContent() {
84
+ return this.getTitle();
85
+ }
86
+ getURL() {
87
+ return this.getLatest().__url;
88
+ }
89
+ setURL(url) {
90
+ const writable = this.getWritable();
91
+ writable.__url = url;
92
+ return writable;
93
+ }
94
+ getSchemaType() {
95
+ return this.getLatest().__schemaType;
96
+ }
97
+ setSchemaType(schemaType) {
98
+ const writable = this.getWritable();
99
+ writable.__schemaType = schemaType || "";
100
+ return writable;
101
+ }
102
+ getPayload() {
103
+ return this.getLatest().__payload;
104
+ }
105
+ setPayload(payload) {
106
+ const writable = this.getWritable();
107
+ writable.__payload = payload;
108
+ return writable;
109
+ }
110
+ getTitle() {
111
+ return this.getLatest().__title || this.getURL();
112
+ }
113
+ setTitle(title) {
114
+ const writable = this.getWritable();
115
+ writable.__title = title || this.getURL();
116
+ return writable;
117
+ }
118
+ decorate(editor) {
119
+ const decorator = getKernelFromEditor(editor)?.getDecorator(SchemaNode.getType());
120
+ if (!decorator) return null;
121
+ if (typeof decorator === "function") return decorator(this, editor);
122
+ return decorator.render(this, editor);
123
+ }
124
+ };
125
+ function $createSchemaNode(payload) {
126
+ return $applyNodeReplacement(new SchemaNode(payload.url, payload.schemaType, payload.payload, payload.title));
127
+ }
128
+ function $convertSchemaElement(domNode) {
129
+ const element = domNode;
130
+ return { node: $createSchemaNode({
131
+ payload: parsePayload(element.dataset.payload),
132
+ schemaType: element.dataset.schemaType,
133
+ title: element.textContent || element.href,
134
+ url: element.getAttribute("href") || ""
135
+ }) };
136
+ }
137
+ function parsePayload(value) {
138
+ if (!value) return void 0;
139
+ try {
140
+ return JSON.parse(value);
141
+ } catch {
142
+ return value;
143
+ }
144
+ }
145
+ function $isSchemaNode(node) {
146
+ return node instanceof SchemaNode || node?.getType?.() === SchemaNode.getType();
147
+ }
148
+ //#endregion
149
+ export { $createSchemaNode, $isSchemaNode, SchemaNode };
@@ -0,0 +1,7 @@
1
+ import { LinkNode } from "../node/LinkNode.js";
2
+ import { LinkService } from "../service/i-link-service.js";
3
+ import { LexicalEditor } from "lexical";
4
+ //#region src/plugins/link/normalization/index.d.ts
5
+ declare function normalizeSchemaLinkNode(node: LinkNode, editor: LexicalEditor, linkService: LinkService): boolean;
6
+ //#endregion
7
+ export { normalizeSchemaLinkNode };
@@ -0,0 +1,38 @@
1
+ import { $createSchemaNode } from "../node/SchemaNode.js";
2
+ //#region src/plugins/link/normalization/index.ts
3
+ function normalizeSchemaLinkNode(node, editor, linkService) {
4
+ const url = node.getURL();
5
+ const title = node.getTitle() || node.getTextContent() || url;
6
+ const schema = linkService.parseSchemaUrl(url);
7
+ const rule = linkService.getSchemaRule(url, {
8
+ editor,
9
+ schema,
10
+ text: node.getTextContent(),
11
+ title
12
+ });
13
+ if (!rule) return false;
14
+ const parsed = rule.parse?.(url, schema);
15
+ const payload = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? normalizeSchemaPayload(parsed) : { payload: parsed };
16
+ if (linkService.hasSchemaLinkRenderer(url)) payload.payload = {
17
+ ...payload.payload && typeof payload.payload === "object" ? payload.payload : {},
18
+ __legacyLink: {
19
+ rel: node.getRel(),
20
+ target: node.getTarget(),
21
+ text: node.getTextContent(),
22
+ title: node.getTitle()
23
+ }
24
+ };
25
+ node.replace($createSchemaNode({
26
+ payload: payload.payload,
27
+ schemaType: payload.schemaType || rule.id,
28
+ title: payload.title || title,
29
+ url: payload.url || url
30
+ }));
31
+ return true;
32
+ }
33
+ function normalizeSchemaPayload(payload) {
34
+ if ("payload" in payload || "schemaType" in payload || "title" in payload || "url" in payload) return payload;
35
+ return { payload };
36
+ }
37
+ //#endregion
38
+ export { normalizeSchemaLinkNode };
@@ -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 };