@lobehub/editor 4.24.1 → 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 (143) 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/types/kernel.d.ts +4 -0
  140. package/es/utils/updatePosition.js +5 -2
  141. package/package.json +3 -1
  142. package/patches/@lexical__yjs@0.42.0.patch +42 -0
  143. package/scripts/postinstall-lexical-patch.cjs +62 -36
@@ -0,0 +1,535 @@
1
+ import { INodeHelper, init_helper } from "../../../editor-kernel/inode/helper.js";
2
+ import { KernelPlugin, init_plugin } from "../../../editor-kernel/plugin.js";
3
+ import { $getNearestNodeFromDOMNode as $getNearestNodeFromDOMNode$1, init_utils } from "../../../editor-kernel/utils.js";
4
+ import { ILitexmlService } from "../../litexml/service/litexml-service.js";
5
+ import { IMarkdownShortCutService } from "../../markdown/service/shortcut.js";
6
+ import { INSERT_TABLE_COMMAND } from "../../table/command/index.js";
7
+ import { $isCollapsibleNode, CollapsibleNode } from "../node/CollapsibleNode.js";
8
+ import { registerCollapsibleCommand } from "../command/index.js";
9
+ import { IYjsService } from "../../yjs/service/index.js";
10
+ import { $createParagraphNode, $getNodeByKey, $getSelection, $isDecoratorNode, $isElementNode, $isRangeSelection, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_EDITOR, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_BACKSPACE_COMMAND, SELECTION_CHANGE_COMMAND } from "lexical";
11
+ import { getAnchorAndFocusCollabNodesForUserState } from "@lexical/yjs";
12
+ //#region src/plugins/collapsible/plugin/index.ts
13
+ init_helper();
14
+ init_plugin();
15
+ init_utils();
16
+ const COLLAPSIBLE_SELECTOR = "[data-collapsible=\"true\"]";
17
+ const CONTENT_SELECTOR = "[data-collapsible-content=\"true\"]";
18
+ const TOGGLE_SELECTOR = "[data-collapsible-toggle=\"true\"]";
19
+ const CollapsiblePlugin = class extends KernelPlugin {
20
+ static {
21
+ this.pluginName = "CollapsiblePlugin";
22
+ }
23
+ constructor(kernel, config) {
24
+ super();
25
+ this.kernel = kernel;
26
+ this.config = config;
27
+ kernel.registerNodes([CollapsibleNode]);
28
+ if (config?.theme) kernel.registerThemes(config.theme);
29
+ }
30
+ onInit(editor) {
31
+ const canCollapse = (node) => !hasRemoteSelectionInsideCollapsible(this.kernel, node);
32
+ this.register(registerCollapsibleCommand(editor, canCollapse));
33
+ this.register(registerCollaborativeCollapseGuard(editor, this.kernel));
34
+ this.register(registerCollapsibleDomBehavior(editor));
35
+ this.register(registerCollapsedSelectionGuard(editor));
36
+ this.register(registerEmptyCollapsibleBackspace(editor));
37
+ this.register(registerTableInsertionBoundary(editor));
38
+ this.registerMarkdown();
39
+ this.registerLiteXml();
40
+ }
41
+ registerLiteXml() {
42
+ const litexmlService = this.kernel.requireService(ILitexmlService);
43
+ if (!litexmlService) return;
44
+ litexmlService.registerXMLWriter(CollapsibleNode.getType(), (node, ctx) => {
45
+ if (!$isCollapsibleNode(node)) return false;
46
+ return ctx.createXmlNode("collapsible", {
47
+ collapsed: String(node.isCollapsed()),
48
+ title: node.getTitle()
49
+ });
50
+ });
51
+ litexmlService.registerXMLReader("collapsible", (xmlNode, children) => {
52
+ const title = xmlNode.getAttribute("title") || "Details";
53
+ return INodeHelper.createElementNode(CollapsibleNode.getType(), {
54
+ children: ensureTitleChildren(children, title),
55
+ collapsed: xmlNode.getAttribute("collapsed") === "true",
56
+ title
57
+ });
58
+ });
59
+ }
60
+ registerMarkdown() {
61
+ const markdownService = this.kernel.requireService(IMarkdownShortCutService);
62
+ if (!markdownService) return;
63
+ markdownService.registerMarkdownWriter(CollapsibleNode.getType(), (ctx, node) => {
64
+ if (!$isCollapsibleNode(node)) return false;
65
+ ctx.appendLine(`<details${node.isCollapsed() ? "" : " open"}>\n`);
66
+ ctx.appendLine(`<summary>${escapeHtml(node.getTitle())}</summary>\n\n`);
67
+ node.getChildren().slice(1).forEach((child) => ctx.processChild(ctx, child));
68
+ ctx.appendLine("\n</details>\n");
69
+ return true;
70
+ });
71
+ markdownService.registerMarkdownReader("html", (node, children) => {
72
+ const tag = getHtmlTagName(node.value);
73
+ if (tag === "summary") return INodeHelper.createTypeNode("__collapsible_summary", { text: getNodeText(children) });
74
+ if (tag !== "details") return false;
75
+ const summaryNode = children.find((child) => child.type === "__collapsible_summary");
76
+ const contentChildren = children.filter((child) => child.type !== "__collapsible_summary");
77
+ const title = summaryNode && "text" in summaryNode && typeof summaryNode.text === "string" ? summaryNode.text : extractSummaryTitle(node.value) || "Details";
78
+ return INodeHelper.createElementNode(CollapsibleNode.getType(), {
79
+ children: ensureTitleChildren(contentChildren, title),
80
+ collapsed: !/\sopen(?:\s|>|$)/i.test(node.value),
81
+ title
82
+ });
83
+ });
84
+ }
85
+ };
86
+ function registerCollaborativeCollapseGuard(editor, kernel) {
87
+ const handleClick = (event) => {
88
+ if (event.defaultPrevented) return;
89
+ const target = event.target;
90
+ if (!(target instanceof HTMLElement)) return;
91
+ const section = target.closest(TOGGLE_SELECTOR)?.closest(COLLAPSIBLE_SELECTOR);
92
+ if (!section) return;
93
+ let shouldBlock = false;
94
+ editor.getEditorState().read(() => {
95
+ const node = $getCollapsibleNodeFromSection(section, editor);
96
+ shouldBlock = Boolean(node && !node.isCollapsed() && hasRemoteSelectionInsideCollapsible(kernel, node));
97
+ });
98
+ if (!shouldBlock) return;
99
+ event.preventDefault();
100
+ event.stopImmediatePropagation();
101
+ };
102
+ return editor.registerRootListener((rootElement, prevRootElement) => {
103
+ prevRootElement?.removeEventListener("click", handleClick, true);
104
+ rootElement?.addEventListener("click", handleClick, true);
105
+ });
106
+ }
107
+ function hasRemoteSelectionInsideCollapsible(kernel, collapsible) {
108
+ const collaborationState = kernel.requireService(IYjsService)?.getState();
109
+ if (!collaborationState) return false;
110
+ const awarenessStates = collaborationState.provider.awareness.getStates();
111
+ for (const [clientId, userState] of awarenessStates) {
112
+ if (clientId === collaborationState.binding.clientID) continue;
113
+ if (getRemoteSelectionNodes(collaborationState, clientId, userState).some((node) => isDescendantOf(node, collapsible))) return true;
114
+ }
115
+ return false;
116
+ }
117
+ function getRemoteSelectionNodes(collaborationState, clientId, userState) {
118
+ if (!userState) return [];
119
+ const nodes = [];
120
+ try {
121
+ const { anchorCollabNode, focusCollabNode } = getAnchorAndFocusCollabNodesForUserState(collaborationState.binding, userState);
122
+ const anchorNode = anchorCollabNode?.getNode();
123
+ const focusNode = focusCollabNode?.getNode();
124
+ nodes.push(...[anchorNode, focusNode].filter((node) => Boolean(node)));
125
+ } catch {}
126
+ const selection = collaborationState.binding.cursors.get(clientId)?.selection;
127
+ if (selection) nodes.push(...[$getNodeByKey(selection.anchor.key), $getNodeByKey(selection.focus.key)].filter((node) => Boolean(node)));
128
+ return nodes.filter((node) => Boolean(node));
129
+ }
130
+ function isDescendantOf(node, ancestor) {
131
+ let current = node;
132
+ while (current) {
133
+ if (current.is(ancestor)) return true;
134
+ current = current.getParent();
135
+ }
136
+ return false;
137
+ }
138
+ function getHtmlTagName(value) {
139
+ return value.match(/^<\/?\s*([\da-z-]+)/i)?.[1]?.toLowerCase() || "";
140
+ }
141
+ function escapeHtml(value) {
142
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
143
+ }
144
+ function extractSummaryTitle(value) {
145
+ return value.match(/<summary\b[^>]*>([\S\s]*?)<\/summary>/i)?.[1]?.trim() || "";
146
+ }
147
+ function getNodeText(children) {
148
+ return children.map((child) => {
149
+ if (!child || typeof child !== "object") return "";
150
+ if ("text" in child && typeof child.text === "string") return child.text;
151
+ if ("children" in child && Array.isArray(child.children)) return getNodeText(child.children);
152
+ return "";
153
+ }).join("");
154
+ }
155
+ function getCollapsedAncestor(node) {
156
+ const collapsible = getCollapsibleAncestor(node);
157
+ return collapsible?.isCollapsed() ? collapsible : null;
158
+ }
159
+ function getCollapsibleAncestor(node) {
160
+ let current = node;
161
+ while (current) {
162
+ if ($isCollapsibleNode(current)) return current;
163
+ current = current.getParent();
164
+ }
165
+ return null;
166
+ }
167
+ function getTopLevelNode(node) {
168
+ let current = node;
169
+ let parent = current.getParent();
170
+ while (parent && parent.getKey() !== "root") {
171
+ current = parent;
172
+ parent = current.getParent();
173
+ }
174
+ return current;
175
+ }
176
+ function getDirectChildOf(parent, node) {
177
+ let current = node;
178
+ let child = null;
179
+ while (current && current !== parent) {
180
+ child = current;
181
+ current = current.getParent();
182
+ }
183
+ return current === parent ? child : null;
184
+ }
185
+ function isInsideHiddenChild(collapsible, node) {
186
+ const directChild = getDirectChildOf(collapsible, node);
187
+ return Boolean(directChild && directChild !== collapsible.getFirstChild());
188
+ }
189
+ function getDirectChildIndexOf(parent, node) {
190
+ const directChild = getDirectChildOf(parent, node);
191
+ return directChild ? directChild.getIndexWithinParent() : -1;
192
+ }
193
+ function getDirectChildIndexFromSelection(collapsible, selection) {
194
+ const focusNode = selection.focus.getNode();
195
+ const directChildIndex = getDirectChildIndexOf(collapsible, focusNode);
196
+ if (directChildIndex >= 0) return directChildIndex;
197
+ if (focusNode === collapsible) return Math.min(selection.focus.offset, collapsible.getChildrenSize() - 1);
198
+ return -1;
199
+ }
200
+ function selectAfterCollapsedBlock(collapsible) {
201
+ const next = collapsible.getNextSibling();
202
+ if (next) {
203
+ next.selectStart();
204
+ return true;
205
+ }
206
+ const paragraph = $createParagraphNode();
207
+ collapsible.insertAfter(paragraph);
208
+ paragraph.selectStart();
209
+ return true;
210
+ }
211
+ function selectBeforeBlock(collapsible, temporaryParagraphKeys) {
212
+ const previous = collapsible.getPreviousSibling();
213
+ if (previous) {
214
+ if ($isDecoratorNode(previous) || previous.getType() === "code") {
215
+ const paragraph = $createParagraphNode();
216
+ previous.insertAfter(paragraph);
217
+ temporaryParagraphKeys?.add(paragraph.getKey());
218
+ paragraph.selectStart();
219
+ return true;
220
+ }
221
+ previous.selectEnd();
222
+ return true;
223
+ }
224
+ const paragraph = $createParagraphNode();
225
+ collapsible.insertBefore(paragraph);
226
+ paragraph.selectStart();
227
+ return true;
228
+ }
229
+ function selectCollapsedTitle(collapsible, end = true) {
230
+ const firstChild = collapsible.getFirstChild();
231
+ if (!firstChild) return false;
232
+ if (end) firstChild.selectEnd();
233
+ else firstChild.selectStart();
234
+ return true;
235
+ }
236
+ function selectFirstVisibleChild(collapsible) {
237
+ const firstChild = collapsible.getFirstChild();
238
+ if (!firstChild) return false;
239
+ firstChild.selectStart();
240
+ return true;
241
+ }
242
+ function selectLastVisibleChild(collapsible) {
243
+ if (collapsible.isCollapsed()) return selectCollapsedTitle(collapsible);
244
+ const lastChild = collapsible.getLastChild();
245
+ if (!lastChild) return false;
246
+ lastChild.selectEnd();
247
+ return true;
248
+ }
249
+ function selectExpandedAdjacentChildOrBlock(collapsible, childIndex, isDown, temporaryParagraphKeys) {
250
+ if (childIndex < 0) return false;
251
+ const nextIndex = childIndex + (isDown ? 1 : -1);
252
+ const nextChild = collapsible.getChildAtIndex(nextIndex);
253
+ if (nextChild) {
254
+ if (isDown) nextChild.selectStart();
255
+ else nextChild.selectEnd();
256
+ return true;
257
+ }
258
+ return isDown ? selectAfterCollapsedBlock(collapsible) : selectBeforeBlock(collapsible, temporaryParagraphKeys);
259
+ }
260
+ function moveCollapsibleSelection(direction, temporaryParagraphKeys) {
261
+ const selection = $getSelection();
262
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
263
+ const focusNode = selection.focus.getNode();
264
+ const collapsible = getCollapsibleAncestor(focusNode);
265
+ if (collapsible) {
266
+ const childIndex = getDirectChildIndexFromSelection(collapsible, selection);
267
+ if (childIndex < 0) return false;
268
+ if (direction === "down") return collapsible.isCollapsed() ? selectAfterCollapsedBlock(collapsible) : selectExpandedAdjacentChildOrBlock(collapsible, childIndex, true, temporaryParagraphKeys);
269
+ if (collapsible.isCollapsed()) return childIndex > 0 ? selectCollapsedTitle(collapsible) : selectBeforeBlock(collapsible, temporaryParagraphKeys);
270
+ return selectExpandedAdjacentChildOrBlock(collapsible, childIndex, false, temporaryParagraphKeys);
271
+ }
272
+ const topLevelNode = getTopLevelNode(focusNode);
273
+ const adjacent = direction === "down" ? topLevelNode.getNextSibling() : topLevelNode.getPreviousSibling();
274
+ if (!$isCollapsibleNode(adjacent)) return false;
275
+ return direction === "down" ? selectFirstVisibleChild(adjacent) : selectLastVisibleChild(adjacent);
276
+ }
277
+ function moveCollapsibleSelectionFromDOM(editor, ownerDocument, direction, temporaryParagraphKeys) {
278
+ const selection = ownerDocument.getSelection();
279
+ const section = getSelectionCollapsibleSection(selection);
280
+ if (!section) return false;
281
+ const collapsible = $getCollapsibleNodeFromSection(section, editor);
282
+ if (!$isCollapsibleNode(collapsible)) return false;
283
+ const childIndex = getDOMSelectionChildIndex(section, selection);
284
+ if (childIndex < 0) return false;
285
+ if (direction === "down") return collapsible.isCollapsed() ? selectAfterCollapsedBlock(collapsible) : selectExpandedAdjacentChildOrBlock(collapsible, childIndex, true, temporaryParagraphKeys);
286
+ if (collapsible.isCollapsed()) return childIndex > 0 ? selectCollapsedTitle(collapsible) : selectBeforeBlock(collapsible, temporaryParagraphKeys);
287
+ return selectExpandedAdjacentChildOrBlock(collapsible, childIndex, false, temporaryParagraphKeys);
288
+ }
289
+ function getSelectionCollapsibleSection(selection) {
290
+ const anchorNode = selection?.anchorNode;
291
+ if (!anchorNode) return null;
292
+ return (anchorNode instanceof HTMLElement ? anchorNode : anchorNode.parentElement)?.closest(COLLAPSIBLE_SELECTOR) || null;
293
+ }
294
+ function getDOMSelectionChildIndex(section, selection) {
295
+ const content = section.querySelector(CONTENT_SELECTOR);
296
+ const anchorNode = selection?.anchorNode;
297
+ if (!content || !anchorNode) return -1;
298
+ const anchorElement = anchorNode instanceof HTMLElement ? anchorNode : anchorNode.parentElement;
299
+ if (!anchorElement || !content.contains(anchorElement)) return -1;
300
+ const directChild = anchorElement.closest(`${CONTENT_SELECTOR} > *`);
301
+ if (!directChild) return 0;
302
+ return Array.from(content.children).indexOf(directChild);
303
+ }
304
+ function $getCollapsibleNodeFromSection(section, editor) {
305
+ const blockId = section.dataset.blockId;
306
+ const nodeFromBlockId = blockId ? $getNodeByKey(blockId) : null;
307
+ if ($isCollapsibleNode(nodeFromBlockId)) return nodeFromBlockId;
308
+ const nodeFromDOM = $getNearestNodeFromDOMNode$1(section, editor);
309
+ return $isCollapsibleNode(nodeFromDOM) ? nodeFromDOM : null;
310
+ }
311
+ function registerCollapsibleDomBehavior(editor) {
312
+ const handleMouseDown = (event) => {
313
+ if (event.defaultPrevented) return;
314
+ const target = event.target;
315
+ if (!(target instanceof HTMLElement)) return;
316
+ if (target.closest(TOGGLE_SELECTOR)) return;
317
+ const section = target.closest(COLLAPSIBLE_SELECTOR);
318
+ if (!section) return;
319
+ const content = section.querySelector(CONTENT_SELECTOR);
320
+ if (!content) return;
321
+ const directChild = target.closest(`${CONTENT_SELECTOR} > *`);
322
+ let childIndex = 0;
323
+ if (directChild) {
324
+ if (hasNativeCaretTarget(event, directChild)) return;
325
+ childIndex = Array.from(content.children).indexOf(directChild);
326
+ if (childIndex < 0) return;
327
+ }
328
+ event.preventDefault();
329
+ editor.update(() => {
330
+ const node = $getNearestNodeFromDOMNode$1(section, editor);
331
+ if (!$isCollapsibleNode(node)) return;
332
+ if (node.isCollapsed() && childIndex > 0) return;
333
+ node.getChildAtIndex(childIndex)?.selectStart();
334
+ });
335
+ editor.focus();
336
+ };
337
+ const unregisterRootListener = editor.registerRootListener((rootElement, prevRootElement) => {
338
+ prevRootElement?.removeEventListener("mousedown", handleMouseDown);
339
+ rootElement?.addEventListener("mousedown", handleMouseDown);
340
+ });
341
+ return () => {
342
+ unregisterRootListener();
343
+ };
344
+ }
345
+ function registerTableInsertionBoundary(editor) {
346
+ return editor.registerCommand(INSERT_TABLE_COMMAND, () => {
347
+ const selection = $getSelection();
348
+ if (!$isRangeSelection(selection)) return false;
349
+ const collapsible = getCollapsibleAncestor(selection.anchor.getNode());
350
+ if (!collapsible) return false;
351
+ const insertionPoint = $createParagraphNode();
352
+ collapsible.insertAfter(insertionPoint);
353
+ insertionPoint.selectStart();
354
+ return false;
355
+ }, COMMAND_PRIORITY_CRITICAL);
356
+ }
357
+ function registerEmptyCollapsibleBackspace(editor) {
358
+ const removeCollapsible = (collapsible, event) => {
359
+ if (collapsible.getTextContent().trim().length > 0) return false;
360
+ const paragraph = $createParagraphNode();
361
+ collapsible.replace(paragraph);
362
+ paragraph.selectStart();
363
+ event.preventDefault();
364
+ event.stopPropagation();
365
+ event.stopImmediatePropagation();
366
+ return true;
367
+ };
368
+ const unregisterCommand = editor.registerCommand(KEY_BACKSPACE_COMMAND, (event) => {
369
+ const selection = $getSelection();
370
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) return false;
371
+ const collapsible = getCollapsibleAncestor(selection.anchor.getNode());
372
+ return collapsible ? removeCollapsible(collapsible, event) : false;
373
+ }, COMMAND_PRIORITY_CRITICAL);
374
+ const handleKeyDown = (event) => {
375
+ if (event.key !== "Backspace" || event.defaultPrevented) return;
376
+ const domSelection = (event.currentTarget?.ownerDocument)?.getSelection();
377
+ if (!domSelection?.isCollapsed) return;
378
+ const section = getSelectionCollapsibleSection(domSelection);
379
+ if (!section) return;
380
+ let handled = false;
381
+ editor.update(() => {
382
+ const collapsible = $getCollapsibleNodeFromSection(section, editor);
383
+ if (collapsible) handled = removeCollapsible(collapsible, event);
384
+ }, { discrete: true });
385
+ if (handled) event.preventDefault();
386
+ };
387
+ const unregisterRootListener = editor.registerRootListener((rootElement, prevRootElement) => {
388
+ prevRootElement?.removeEventListener("keydown", handleKeyDown, true);
389
+ rootElement?.addEventListener("keydown", handleKeyDown, true);
390
+ });
391
+ return () => {
392
+ unregisterCommand();
393
+ unregisterRootListener();
394
+ };
395
+ }
396
+ function hasNativeCaretTarget(event, directChild) {
397
+ const node = getCaretRangeFromPoint(event.clientX, event.clientY)?.startContainer;
398
+ return Boolean(node && directChild.contains(node) && node.textContent);
399
+ }
400
+ function getCaretRangeFromPoint(x, y) {
401
+ if (document.caretRangeFromPoint) return document.caretRangeFromPoint(x, y);
402
+ const caretPosition = document.caretPositionFromPoint?.(x, y);
403
+ if (!caretPosition) return null;
404
+ const range = document.createRange();
405
+ range.setStart(caretPosition.offsetNode, caretPosition.offset);
406
+ range.collapse(true);
407
+ return range;
408
+ }
409
+ function cleanupTemporaryParagraphs(temporaryParagraphKeys) {
410
+ if (temporaryParagraphKeys.size === 0) return;
411
+ const selection = $getSelection();
412
+ const selectedKeys = /* @__PURE__ */ new Set();
413
+ if ($isRangeSelection(selection)) {
414
+ const anchorNode = selection.anchor.getNode();
415
+ const focusNode = selection.focus.getNode();
416
+ selectedKeys.add(anchorNode.getKey());
417
+ selectedKeys.add(focusNode.getKey());
418
+ const anchorParent = anchorNode.getParent();
419
+ const focusParent = focusNode.getParent();
420
+ if (anchorParent) selectedKeys.add(anchorParent.getKey());
421
+ if (focusParent) selectedKeys.add(focusParent.getKey());
422
+ }
423
+ for (const key of Array.from(temporaryParagraphKeys)) {
424
+ const node = $getNodeByKey(key);
425
+ if (!node || !$isElementNode(node)) {
426
+ temporaryParagraphKeys.delete(key);
427
+ continue;
428
+ }
429
+ if (node.getTextContent().trim().length > 0) {
430
+ temporaryParagraphKeys.delete(key);
431
+ continue;
432
+ }
433
+ if (selectedKeys.has(key)) continue;
434
+ node.remove();
435
+ temporaryParagraphKeys.delete(key);
436
+ }
437
+ }
438
+ function registerCollapsedSelectionGuard(editor) {
439
+ const temporaryParagraphKeys = /* @__PURE__ */ new Set();
440
+ let cleanupScheduled = false;
441
+ const scheduleTemporaryParagraphCleanup = () => {
442
+ if (cleanupScheduled) return;
443
+ cleanupScheduled = true;
444
+ queueMicrotask(() => {
445
+ cleanupScheduled = false;
446
+ editor.update(() => {
447
+ cleanupTemporaryParagraphs(temporaryParagraphKeys);
448
+ }, { discrete: true });
449
+ });
450
+ };
451
+ const unregisterSelection = editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
452
+ const selection = $getSelection();
453
+ if (!$isRangeSelection(selection) || !selection.isCollapsed()) {
454
+ cleanupTemporaryParagraphs(temporaryParagraphKeys);
455
+ return false;
456
+ }
457
+ const focusNode = selection.focus.getNode();
458
+ const collapsible = getCollapsedAncestor(focusNode);
459
+ if (!collapsible || !isInsideHiddenChild(collapsible, focusNode)) {
460
+ cleanupTemporaryParagraphs(temporaryParagraphKeys);
461
+ return false;
462
+ }
463
+ selectCollapsedTitle(collapsible);
464
+ cleanupTemporaryParagraphs(temporaryParagraphKeys);
465
+ return true;
466
+ }, COMMAND_PRIORITY_EDITOR);
467
+ const unregisterArrowDown = editor.registerCommand(KEY_ARROW_DOWN_COMMAND, (event) => {
468
+ if (event.shiftKey) return false;
469
+ if (moveCollapsibleSelection("down", temporaryParagraphKeys)) {
470
+ cleanupTemporaryParagraphs(temporaryParagraphKeys);
471
+ scheduleTemporaryParagraphCleanup();
472
+ event.preventDefault();
473
+ return true;
474
+ }
475
+ return false;
476
+ }, COMMAND_PRIORITY_CRITICAL);
477
+ const unregisterArrowUp = editor.registerCommand(KEY_ARROW_UP_COMMAND, (event) => {
478
+ if (event.shiftKey) return false;
479
+ if (moveCollapsibleSelection("up", temporaryParagraphKeys)) {
480
+ cleanupTemporaryParagraphs(temporaryParagraphKeys);
481
+ scheduleTemporaryParagraphCleanup();
482
+ event.preventDefault();
483
+ return true;
484
+ }
485
+ return false;
486
+ }, COMMAND_PRIORITY_CRITICAL);
487
+ const handleRootKeyDown = (event) => {
488
+ if (event.shiftKey || event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
489
+ if (!isEventForEditorRoot(editor, event)) return;
490
+ let moved = false;
491
+ const ownerDocument = getEventOwnerDocument(event);
492
+ editor.update(() => {
493
+ const direction = event.key === "ArrowDown" ? "down" : "up";
494
+ moved = moveCollapsibleSelection(direction, temporaryParagraphKeys) || moveCollapsibleSelectionFromDOM(editor, ownerDocument, direction, temporaryParagraphKeys);
495
+ }, { discrete: true });
496
+ if (!moved) return;
497
+ editor.update(() => {
498
+ cleanupTemporaryParagraphs(temporaryParagraphKeys);
499
+ }, { discrete: true });
500
+ event.preventDefault();
501
+ event.stopImmediatePropagation();
502
+ };
503
+ const unregisterRootListener = editor.registerRootListener((rootElement, prevRootElement) => {
504
+ prevRootElement?.ownerDocument.removeEventListener("keydown", handleRootKeyDown, true);
505
+ rootElement?.ownerDocument.addEventListener("keydown", handleRootKeyDown, true);
506
+ });
507
+ return () => {
508
+ unregisterRootListener();
509
+ unregisterSelection();
510
+ unregisterArrowDown();
511
+ unregisterArrowUp();
512
+ };
513
+ }
514
+ function getEventOwnerDocument(event) {
515
+ const target = event.target;
516
+ return target instanceof Node ? target.ownerDocument || document : document;
517
+ }
518
+ function isEventForEditorRoot(editor, event) {
519
+ const rootElement = editor.getRootElement();
520
+ if (!rootElement) return false;
521
+ const target = event.target;
522
+ if (target instanceof Node && rootElement.contains(target)) return true;
523
+ const anchorNode = getEventOwnerDocument(event).getSelection()?.anchorNode;
524
+ if (anchorNode && rootElement.contains(anchorNode)) return true;
525
+ const activeElement = getEventOwnerDocument(event).activeElement;
526
+ return Boolean(activeElement && rootElement.contains(activeElement));
527
+ }
528
+ function ensureTitleChildren(children, title) {
529
+ const trimmedTitle = title.trim();
530
+ if (!trimmedTitle) return children;
531
+ if (getNodeText(children.slice(0, 1)).trim() === trimmedTitle) return children;
532
+ return [INodeHelper.createParagraph({ children: [INodeHelper.createTextNode(title)] }), ...children];
533
+ }
534
+ //#endregion
535
+ export { CollapsiblePlugin };
@@ -0,0 +1,6 @@
1
+ import { ReactCollapsiblePluginProps } from "./type.js";
2
+ import { FC } from "react";
3
+ //#region src/plugins/collapsible/react/ReactCollapsiblePlugin.d.ts
4
+ declare const ReactCollapsiblePlugin: FC<ReactCollapsiblePluginProps>;
5
+ //#endregion
6
+ export { ReactCollapsiblePlugin as default };
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { useLexicalComposerContext } from "../../../editor-kernel/react/react-context.js";
3
+ import { MarkdownPlugin } from "../../markdown/plugin/index.js";
4
+ import { CollapsiblePlugin } from "../plugin/index.js";
5
+ import { styles } from "./style.js";
6
+ import { useLayoutEffect } from "react";
7
+ import { cx } from "antd-style";
8
+ //#region src/plugins/collapsible/react/ReactCollapsiblePlugin.tsx
9
+ const ReactCollapsiblePlugin = ({ className }) => {
10
+ const [editor] = useLexicalComposerContext();
11
+ useLayoutEffect(() => {
12
+ editor.registerPlugin(MarkdownPlugin);
13
+ editor.registerPlugin(CollapsiblePlugin, { theme: { collapsible: cx(styles, className) } });
14
+ }, [className, editor]);
15
+ return null;
16
+ };
17
+ ReactCollapsiblePlugin.displayName = "ReactCollapsiblePlugin";
18
+ //#endregion
19
+ export { ReactCollapsiblePlugin as default };
@@ -0,0 +1,3 @@
1
+ import { ReactCollapsiblePluginProps } from "./type.js";
2
+ import ReactCollapsiblePlugin from "./ReactCollapsiblePlugin.js";
3
+ export { ReactCollapsiblePlugin, type ReactCollapsiblePluginProps };
@@ -0,0 +1,67 @@
1
+ import { css } from "antd-style";
2
+ //#region src/plugins/collapsible/react/style.ts
3
+ const styles = css`
4
+ position: relative;
5
+ margin: 12px 0;
6
+ padding: 14px 16px 14px 42px;
7
+ border: 1px solid rgba(51, 103, 153, 0.72);
8
+ border-radius: 8px;
9
+
10
+ &[data-collapsible-collapsed='true'] {
11
+ padding-block: 10px;
12
+ }
13
+
14
+ [data-collapsible-toggle='true'] {
15
+ cursor: pointer;
16
+ position: absolute;
17
+ inset-block-start: 20px;
18
+ inset-inline-start: 16px;
19
+ width: 14px;
20
+ height: 14px;
21
+ padding: 0;
22
+ border: 0;
23
+ color: currentColor;
24
+ background: transparent;
25
+ }
26
+
27
+ &[data-collapsible-collapsed='true'] > [data-collapsible-toggle='true'] {
28
+ inset-block-start: 15px;
29
+ }
30
+
31
+ [data-collapsible-toggle='true']::before {
32
+ content: '';
33
+ display: block;
34
+ width: 0;
35
+ height: 0;
36
+ margin: 3px 0 0 2px;
37
+ border-style: solid;
38
+ }
39
+
40
+ &[data-collapsible-collapsed='false'] > [data-collapsible-toggle='true']::before {
41
+ border-width: 7px 5px 0 5px;
42
+ border-color: currentColor transparent transparent transparent;
43
+ }
44
+
45
+ &[data-collapsible-collapsed='true'] > [data-collapsible-toggle='true']::before {
46
+ border-width: 5px 0 5px 7px;
47
+ border-color: transparent transparent transparent currentColor;
48
+ }
49
+
50
+ [data-collapsible-content='true'] > *:first-child {
51
+ margin-block-start: 0;
52
+ }
53
+
54
+ [data-collapsible-content='true'] > *:last-child {
55
+ margin-block-end: 0;
56
+ }
57
+
58
+ &[data-collapsible-collapsed='true'] > [data-collapsible-content='true'] > *:first-child {
59
+ margin-block-end: 0;
60
+ }
61
+
62
+ &[data-collapsible-collapsed='true'] > [data-collapsible-content='true'] > *:not(:first-child) {
63
+ display: none !important;
64
+ }
65
+ `;
66
+ //#endregion
67
+ export { styles };
@@ -0,0 +1,6 @@
1
+ //#region src/plugins/collapsible/react/type.d.ts
2
+ interface ReactCollapsiblePluginProps {
3
+ className?: string;
4
+ }
5
+ //#endregion
6
+ export { ReactCollapsiblePluginProps };
@@ -12,7 +12,8 @@ import TextDataSource from "../data-source/text-data-source.js";
12
12
  import { registerBreakLineClick } from "../node/ElementDOMSlot.js";
13
13
  import { registerMDReader } from "./mdReader.js";
14
14
  import { handleFilePaste, handlePlainTextPaste, handleVSCodePaste, runPasteHandlers } from "./paste-handler.js";
15
- import { registerHeaderBackspace, registerLastElement, registerRichKeydown } from "./register.js";
15
+ import { registerProgressiveSelectAll } from "./progressive-select-all.js";
16
+ import { registerBlockBackspace, registerLastElement, registerRichKeydown } from "./register.js";
16
17
  import { registerHistory } from "@lexical/history";
17
18
  import { $createLineBreakNode, $createParagraphNode, $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_CRITICAL, COMMAND_PRIORITY_HIGH, INSERT_LINE_BREAK_COMMAND, INSERT_PARAGRAPH_COMMAND, PASTE_COMMAND, ParagraphNode, TEXT_TYPE_TO_FORMAT, TextNode } from "lexical";
18
19
  import { $createHeadingNode, $createQuoteNode, $isHeadingNode, $isQuoteNode, HeadingNode, QuoteNode, registerRichText } from "@lexical/rich-text";
@@ -231,7 +232,7 @@ const CommonPlugin = class extends KernelPlugin {
231
232
  if (this.config?.pasteAsPlainText) return runPasteHandlers(ctx, [handleFilePaste, handlePlainTextPaste]);
232
233
  return false;
233
234
  }, COMMAND_PRIORITY_CRITICAL));
234
- this.registerClears(registerRichText(editor), CAN_USE_DOM ? registerDragonSupport(editor) : noop, registerHistory(editor, this.kernel.getHistoryState(), 300), registerHeaderBackspace(editor), registerRichKeydown(editor, this.kernel, { enableHotkey: this.config?.enableHotkey }), registerCommands(editor), registerBreakLineClick(editor), registerCursorNode(editor), registerLastElement(editor), editor.registerCommand(INSERT_LINE_BREAK_COMMAND, () => {
235
+ this.registerClears(registerRichText(editor), registerProgressiveSelectAll(editor), CAN_USE_DOM ? registerDragonSupport(editor) : noop, registerHistory(editor, this.kernel.getHistoryState(), 300), registerBlockBackspace(editor), registerRichKeydown(editor, this.kernel, { enableHotkey: this.config?.enableHotkey }), registerCommands(editor), registerBreakLineClick(editor), registerCursorNode(editor), registerLastElement(editor), editor.registerCommand(INSERT_LINE_BREAK_COMMAND, () => {
235
236
  const selection = $getSelection();
236
237
  if (!$isRangeSelection(selection)) return false;
237
238
  const { inCell, inTable } = $isCursorInTable(selection);