@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
@@ -50,6 +50,48 @@ const styles = createStaticStyles(({ css, cssVar }) => css`
50
50
  transition: all 0.3s;
51
51
  }
52
52
 
53
+ &.collab-locked {
54
+ cursor: not-allowed;
55
+ }
56
+
57
+ &.collab-locked::after {
58
+ pointer-events: none;
59
+ content: '';
60
+
61
+ position: absolute;
62
+ z-index: 11;
63
+ inset: 0;
64
+
65
+ background: ${cssVar.colorFillSecondary};
66
+ }
67
+
68
+ &.collab-locked .cm-container {
69
+ opacity: 0.72;
70
+ }
71
+
72
+ .cm-collab-lock {
73
+ position: absolute;
74
+ z-index: 12;
75
+ inset-block-start: 8px;
76
+ inset-inline-end: 8px;
77
+
78
+ overflow: hidden;
79
+
80
+ max-width: min(240px, calc(100% - 16px));
81
+ padding-block: 2px;
82
+ padding-inline: 6px;
83
+ border-radius: 6px;
84
+
85
+ font-size: 12px;
86
+ line-height: 18px;
87
+ color: ${cssVar.colorTextSecondary};
88
+ text-overflow: ellipsis;
89
+ white-space: nowrap;
90
+
91
+ background: ${cssVar.colorBgElevated};
92
+ box-shadow: ${cssVar.boxShadowTertiary};
93
+ }
94
+
53
95
  .cm-container {
54
96
  position: relative;
55
97
  width: 100%;
@@ -24,6 +24,11 @@ declare class Kernel extends EventEmitter implements IEditorKernel {
24
24
  private hotReloadMode;
25
25
  private logger;
26
26
  private historyState;
27
+ /**
28
+ * Root-scoped resources are attached to the Lexical editor's root listener
29
+ * so direct Lexical setRootElement(null) calls are safe as well.
30
+ */
31
+ private unregisterRootLifecycle?;
27
32
  private editor?;
28
33
  private headlessEditor;
29
34
  constructor();
@@ -44,6 +49,7 @@ declare class Kernel extends EventEmitter implements IEditorKernel {
44
49
  destroy(): void;
45
50
  getRootElement(): HTMLElement | null;
46
51
  setRootElement(dom: HTMLElement, editable?: boolean): LexicalEditor;
52
+ setRootElement(dom: null, editable?: boolean): LexicalEditor | null;
47
53
  setEditable(editable: boolean): void;
48
54
  initNodeEditor(): LexicalEditor;
49
55
  initHeadlessEditor(): LexicalEditor;
@@ -80,6 +86,9 @@ declare class Kernel extends EventEmitter implements IEditorKernel {
80
86
  registerNodeTransform(transform: INodeRegistrationTransform): () => void;
81
87
  registerBeforeEditorInit(hook: IBeforeEditorInitLifecycle): () => void;
82
88
  registerRootClassName(className: string): () => void;
89
+ registerRootListener(listener: (rootElement: HTMLElement | null) => void): () => void;
90
+ private getEditorId;
91
+ private registerRootLifecycle;
83
92
  private runBeforeEditorInitLifecycle;
84
93
  private applyRootClassNames;
85
94
  private resolveNodesForInitialization;
@@ -95,26 +95,46 @@ var init_kernel = __esmMin((() => {
95
95
  return this.editor || null;
96
96
  }
97
97
  destroy() {
98
- unregisterEditorKernel(this.themes[EDITOR_THEME_KEY]);
98
+ const editor = this.editor;
99
+ const editorId = this.getEditorId();
99
100
  this.logger.info(`🗑️ Destroying editor with ${this.pluginsInstances.length} plugins`);
101
+ if (editor) try {
102
+ editor.setRootElement(null);
103
+ } catch (error) {
104
+ this.logger.warn("Failed to detach editor root during destroy:", error);
105
+ }
106
+ unregisterEditorKernel(editorId);
100
107
  try {
101
- this.editor?.setEditorState(createEmptyEditorState());
108
+ editor?.setEditorState(createEmptyEditorState());
102
109
  } catch (error) {
103
110
  this.logger.warn("Failed to reset editor state during destroy:", error);
104
111
  }
105
- this.dataTypeMap.clear();
112
+ this.unregisterRootLifecycle?.();
113
+ this.unregisterRootLifecycle = void 0;
114
+ let destroyError;
106
115
  this.pluginsInstances.forEach((plugin) => {
107
- if (plugin.destroy) plugin.destroy();
116
+ try {
117
+ plugin.destroy?.();
118
+ } catch (error) {
119
+ destroyError ??= error;
120
+ }
108
121
  });
109
122
  this.pluginsInstances = [];
110
- this.beforeEditorInitHooks = [];
123
+ this.dataTypeMap.clear();
124
+ this.decorators = {};
111
125
  this.nodeTransforms = [];
126
+ this.nodes = [];
127
+ this.beforeEditorInitHooks = [];
112
128
  this.rootClassNames.clear();
113
129
  this.serviceMap.clear();
114
- this.decorators = {};
115
- this.themes = {};
130
+ this.themes = { [EDITOR_THEME_KEY]: generateEditorId() };
131
+ this.historyState = createEmptyHistoryState();
132
+ this._commands.clear();
133
+ this._commandsClean.clear();
134
+ this.editor = void 0;
116
135
  this.headlessEditor = false;
117
136
  this.logger.info("✅ Editor destroyed");
137
+ if (destroyError) throw destroyError;
118
138
  }
119
139
  getRootElement() {
120
140
  if (this.headlessEditor) return null;
@@ -122,12 +142,23 @@ var init_kernel = __esmMin((() => {
122
142
  }
123
143
  setRootElement(dom, editable = true) {
124
144
  if (this.editor) {
125
- if (this.headlessEditor) throw new Error("Headless editor cannot be attached to a root element.");
145
+ if (this.headlessEditor) {
146
+ if (dom === null) return this.editor;
147
+ throw new Error("Headless editor cannot be attached to a root element.");
148
+ }
126
149
  this.logger.warn("[Editor] Editor is already initialized, updating root element only");
150
+ if (dom) registerEditorKernel(this.getEditorId(), this);
127
151
  this.editor.setRootElement(dom);
128
- this.applyRootClassNames(dom);
152
+ if (dom) {
153
+ this.editor.setEditable(editable);
154
+ this.applyRootClassNames(dom);
155
+ }
129
156
  return this.editor;
130
157
  }
158
+ if (dom === null) {
159
+ unregisterEditorKernel(this.getEditorId());
160
+ return null;
161
+ }
131
162
  if (this.pluginsInstances.length === 0) {
132
163
  this.logger.info(`🔌 Initializing ${this.plugins.length} plugins`);
133
164
  for (const plugin of this.plugins) {
@@ -138,7 +169,7 @@ var init_kernel = __esmMin((() => {
138
169
  this.runBeforeEditorInitLifecycle();
139
170
  const resolvedNodes = this.resolveNodesForInitialization();
140
171
  this.logger.info(`📝 Creating editor with ${resolvedNodes.length} nodes`);
141
- registerEditorKernel(this.themes[EDITOR_THEME_KEY], this);
172
+ registerEditorKernel(this.getEditorId(), this);
142
173
  const editor = this.editor = createEditor({
143
174
  __kernel: this,
144
175
  editable,
@@ -151,9 +182,9 @@ var init_kernel = __esmMin((() => {
151
182
  theme: this.themes
152
183
  });
153
184
  this.headlessEditor = false;
185
+ this.registerRootLifecycle(editor);
154
186
  this.editor.setRootElement(dom);
155
187
  this.applyRootClassNames(dom);
156
- registerEvent(editor, dom);
157
188
  this.pluginsInstances.forEach((plugin) => {
158
189
  plugin.onInit?.(editor);
159
190
  });
@@ -193,6 +224,7 @@ var init_kernel = __esmMin((() => {
193
224
  theme: this.themes
194
225
  });
195
226
  this.headlessEditor = false;
227
+ this.registerRootLifecycle(editor);
196
228
  this.pluginsInstances.forEach((plugin) => {
197
229
  plugin.onInit?.(editor);
198
230
  });
@@ -246,6 +278,9 @@ var init_kernel = __esmMin((() => {
246
278
  this.historyState.current = null;
247
279
  }
248
280
  datasource.read(this.editor, content, options);
281
+ this.pluginsInstances.forEach((plugin) => {
282
+ plugin.onDocumentChange?.(type, content, options);
283
+ });
249
284
  this.emit("documentChange", type, content);
250
285
  this.logger.debug(`📥 Set ${type} document`);
251
286
  }
@@ -510,6 +545,34 @@ var init_kernel = __esmMin((() => {
510
545
  if (currentRootElement) currentRootElement.classList.remove(...classNames);
511
546
  };
512
547
  }
548
+ registerRootListener(listener) {
549
+ if (!this.editor || this.headlessEditor) return noop;
550
+ return this.editor.registerRootListener(listener);
551
+ }
552
+ getEditorId() {
553
+ return this.themes[EDITOR_THEME_KEY];
554
+ }
555
+ registerRootLifecycle(editor) {
556
+ this.unregisterRootLifecycle?.();
557
+ let rootEventCleanup;
558
+ const clearRootEvent = () => {
559
+ rootEventCleanup?.();
560
+ rootEventCleanup = void 0;
561
+ };
562
+ const unregisterRootListener = editor.registerRootListener((rootElement) => {
563
+ clearRootEvent();
564
+ if (!rootElement) {
565
+ unregisterEditorKernel(this.getEditorId());
566
+ return;
567
+ }
568
+ registerEditorKernel(this.getEditorId(), this);
569
+ rootEventCleanup = registerEvent(editor, rootElement);
570
+ });
571
+ this.unregisterRootLifecycle = () => {
572
+ clearRootEvent();
573
+ unregisterRootListener();
574
+ };
575
+ }
513
576
  runBeforeEditorInitLifecycle() {
514
577
  for (const hook of this.beforeEditorInitHooks) hook();
515
578
  }
@@ -1,7 +1,7 @@
1
1
  import { CAN_USE_DOM } from "../../common/canUseDOM.js";
2
2
  import { Suspense, useEffect, useLayoutEffect, useMemo, useState } from "react";
3
3
  import { jsx } from "react/jsx-runtime";
4
- import { createPortal, flushSync } from "react-dom";
4
+ import { createPortal } from "react-dom";
5
5
  //#region src/editor-kernel/react/useDecorators.tsx
6
6
  const useLayoutEffectImpl = CAN_USE_DOM ? useLayoutEffect : useEffect;
7
7
  function useDecorators(editor, ErrorBoundary) {
@@ -12,9 +12,7 @@ function useDecorators(editor, ErrorBoundary) {
12
12
  const initialDecorators = editor.getDecorators();
13
13
  setDecorators(initialDecorators);
14
14
  clears.push(editor.registerDecoratorListener((nextDecorators) => {
15
- flushSync(() => {
16
- setDecorators(nextDecorators);
17
- });
15
+ setDecorators(nextDecorators);
18
16
  }));
19
17
  };
20
18
  const lexicalEditor = editor.getLexicalEditor();
@@ -0,0 +1,74 @@
1
+ import { INodeHelper, init_helper } from "../editor-kernel/inode/helper.js";
2
+ import { KernelPlugin, init_plugin } from "../editor-kernel/plugin.js";
3
+ import { ILitexmlService } from "../plugins/litexml/service/litexml-service.js";
4
+ import { IMarkdownShortCutService } from "../plugins/markdown/service/shortcut.js";
5
+ import { $isCollapsibleNode, CollapsibleNode } from "../plugins/collapsible/node/CollapsibleNode.js";
6
+ //#region src/headless/collapsible-plugin.ts
7
+ init_helper();
8
+ init_plugin();
9
+ const HeadlessCollapsiblePlugin = class extends KernelPlugin {
10
+ static {
11
+ this.pluginName = "HeadlessCollapsiblePlugin";
12
+ }
13
+ constructor(kernel) {
14
+ super();
15
+ this.kernel = kernel;
16
+ kernel.registerNodes([CollapsibleNode]);
17
+ }
18
+ onInit(_editor) {
19
+ const markdownService = this.kernel.requireService(IMarkdownShortCutService);
20
+ markdownService?.registerMarkdownWriter(CollapsibleNode.getType(), (ctx, node) => {
21
+ if (!$isCollapsibleNode(node)) return false;
22
+ ctx.appendLine(`<details${node.isCollapsed() ? "" : " open"}>\n`);
23
+ ctx.appendLine(`<summary>${escapeHtml(node.getTitle())}</summary>\n\n`);
24
+ node.getChildren().slice(1).forEach((child) => ctx.processChild(ctx, child));
25
+ ctx.appendLine("\n</details>\n");
26
+ return true;
27
+ });
28
+ markdownService?.registerMarkdownReader("html", (node, children) => {
29
+ const tag = getHtmlTagName(node.value);
30
+ if (tag === "summary") return INodeHelper.createTypeNode("__collapsible_summary", { text: getNodeText(children) });
31
+ if (tag !== "details") return false;
32
+ const summaryNode = children.find((child) => child.type === "__collapsible_summary");
33
+ const contentChildren = children.filter((child) => child.type !== "__collapsible_summary");
34
+ const title = summaryNode && "text" in summaryNode && typeof summaryNode.text === "string" ? summaryNode.text : extractSummaryTitle(node.value) || "Details";
35
+ return INodeHelper.createElementNode(CollapsibleNode.getType(), {
36
+ children: ensureTitleChildren(contentChildren, title),
37
+ collapsed: !/\sopen(?:\s|>|$)/i.test(node.value),
38
+ title
39
+ });
40
+ });
41
+ const litexmlService = this.kernel.requireService(ILitexmlService);
42
+ litexmlService?.registerXMLWriter(CollapsibleNode.getType(), (node, ctx) => {
43
+ if (!$isCollapsibleNode(node)) return false;
44
+ return ctx.createXmlNode("collapsible", {
45
+ collapsed: String(node.isCollapsed()),
46
+ title: node.getTitle()
47
+ });
48
+ });
49
+ litexmlService?.registerXMLReader("collapsible", (xmlNode, children) => {
50
+ const title = xmlNode.getAttribute("title") || "Details";
51
+ return INodeHelper.createElementNode(CollapsibleNode.getType(), {
52
+ children: ensureTitleChildren(children, title),
53
+ collapsed: xmlNode.getAttribute("collapsed") === "true",
54
+ title
55
+ });
56
+ });
57
+ }
58
+ };
59
+ const getHtmlTagName = (value) => value.match(/^<\/?\s*([\da-z-]+)/i)?.[1]?.toLowerCase() || "";
60
+ const escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
61
+ const extractSummaryTitle = (value) => value.match(/<summary\b[^>]*>([\S\s]*?)<\/summary>/i)?.[1]?.trim() || "";
62
+ const getNodeText = (children) => children.map((child) => {
63
+ if (!child || typeof child !== "object") return "";
64
+ if ("text" in child && typeof child.text === "string") return child.text;
65
+ if ("children" in child && Array.isArray(child.children)) return getNodeText(child.children);
66
+ return "";
67
+ }).join("");
68
+ const ensureTitleChildren = (children, title) => {
69
+ const trimmedTitle = title.trim();
70
+ if (!trimmedTitle || getNodeText(children.slice(0, 1)).trim() === trimmedTitle) return children;
71
+ return [INodeHelper.createParagraph({ children: [INodeHelper.createTextNode(title)] }), ...children];
72
+ };
73
+ //#endregion
74
+ export { HeadlessCollapsiblePlugin };
@@ -15,6 +15,7 @@ import { ListPlugin } from "../plugins/list/plugin/index.js";
15
15
  import { MathPlugin } from "../plugins/math/plugin/index.js";
16
16
  import { MentionPlugin } from "../plugins/mention/plugin/index.js";
17
17
  import { TablePlugin } from "../plugins/table/plugin/index.js";
18
+ import { HeadlessCollapsiblePlugin } from "./collapsible-plugin.js";
18
19
  import { extractMediaFromEditorState } from "./extract-media-from-editor-state.js";
19
20
  //#region src/headless/index.ts
20
21
  init_utils();
@@ -118,6 +119,7 @@ const DEFAULT_HEADLESS_EDITOR_PLUGINS = [
118
119
  HRPlugin,
119
120
  ListPlugin,
120
121
  TablePlugin,
122
+ HeadlessCollapsiblePlugin,
121
123
  LitexmlPlugin
122
124
  ];
123
125
  var HeadlessEditor = class {
package/es/headless.d.ts CHANGED
@@ -79,6 +79,12 @@ declare const _default: {
79
79
  replace: string;
80
80
  };
81
81
  link: {
82
+ copy: string;
83
+ copySuccess: string;
84
+ convertToCard: string;
85
+ convertToIframe: string;
86
+ convertToLink: string;
87
+ convertToSchema: string;
82
88
  edit: string;
83
89
  editLinkTitle: string;
84
90
  editTextTitle: string;
@@ -112,6 +118,14 @@ declare const _default: {
112
118
  insertRowAbove: string;
113
119
  insertRowBelow: string;
114
120
  };
121
+ toc: {
122
+ ariaLabel: string;
123
+ empty: string;
124
+ expand: string;
125
+ pin: string;
126
+ title: string;
127
+ unpin: string;
128
+ };
115
129
  };
116
130
  //#endregion
117
131
  //#region src/types/locale.d.ts
@@ -343,6 +357,7 @@ interface IEditor {
343
357
  * @param dom
344
358
  */
345
359
  setRootElement(dom: HTMLElement, editable?: boolean): LexicalEditor;
360
+ setRootElement(dom: null, editable?: boolean): LexicalEditor | null;
346
361
  /**
347
362
  * set editor selection
348
363
  * @param selection
@@ -421,6 +436,17 @@ interface IEditorKernel extends IEditor {
421
436
  * @returns unregister function
422
437
  */
423
438
  registerRootClassName(className: string): () => void;
439
+ /**
440
+ * Register a callback for root attach/detach events.
441
+ *
442
+ * Call this after the kernel's Lexical editor has been initialized (usually
443
+ * from plugin onInit). Lexical immediately invokes a newly registered root
444
+ * listener with the current root, so listeners registered after the initial
445
+ * setRootElement still receive the first root attach. Headless kernels
446
+ * return a no-op cleanup because headless Lexical editors do not support
447
+ * root listeners.
448
+ */
449
+ registerRootListener(listener: (rootElement: HTMLElement | null) => void): () => void;
424
450
  /**
425
451
  * Register service
426
452
  * @param serviceId
@@ -458,6 +484,10 @@ interface IEditorPlugin<IConfig> {
458
484
  * Editor destruction
459
485
  */
460
486
  destroy(): void;
487
+ /**
488
+ * After editor document content is written by setDocument
489
+ */
490
+ onDocumentChange?(type: string, content: any, options?: IDocumentOptions): void;
461
491
  /**
462
492
  * After Lexical editor instantiation
463
493
  * @param editor Lexical editor instance