@lobehub/editor 4.24.0 → 4.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/codemirror/style.js +42 -0
- package/es/editor-kernel/kernel.js +3 -0
- package/es/editor-kernel/react/useDecorators.js +2 -4
- package/es/headless/collapsible-plugin.js +74 -0
- package/es/headless/index.js +2 -0
- package/es/headless.d.ts +18 -0
- package/es/headless.js +2479 -71
- package/es/index.d.ts +26 -3
- package/es/index.js +17 -2
- package/es/locale/index.d.ts +14 -0
- package/es/locale/index.js +14 -0
- package/es/plugins/block/command/index.js +24 -0
- package/es/plugins/block/plugin/index.js +7 -5
- package/es/plugins/block/react/ReactBlockPlugin.js +43 -1
- package/es/plugins/block/react/core/menu-position.js +20 -0
- package/es/plugins/block/react/drag/drag-session.js +3 -2
- package/es/plugins/block/react/drag/drag-utils.js +13 -1
- package/es/plugins/codemirror-block/index.d.ts +2 -1
- package/es/plugins/codemirror-block/plugin/index.js +2 -0
- package/es/plugins/codemirror-block/react/CodemirrorNode.js +95 -5
- package/es/plugins/codemirror-block/react/useCodemirrorEditLock.js +33 -0
- package/es/plugins/codemirror-block/service/index.d.ts +40 -0
- package/es/plugins/codemirror-block/service/index.js +59 -0
- package/es/plugins/collapsible/command/index.d.ts +17 -0
- package/es/plugins/collapsible/command/index.js +72 -0
- package/es/plugins/collapsible/index.d.ts +7 -0
- package/es/plugins/collapsible/node/CollapsibleNode.d.ts +30 -0
- package/es/plugins/collapsible/node/CollapsibleNode.js +138 -0
- package/es/plugins/collapsible/plugin/index.d.ts +11 -0
- package/es/plugins/collapsible/plugin/index.js +535 -0
- package/es/plugins/collapsible/react/ReactCollapsiblePlugin.d.ts +6 -0
- package/es/plugins/collapsible/react/ReactCollapsiblePlugin.js +19 -0
- package/es/plugins/collapsible/react/index.d.ts +3 -0
- package/es/plugins/collapsible/react/style.js +67 -0
- package/es/plugins/collapsible/react/type.d.ts +6 -0
- package/es/plugins/common/plugin/index.js +3 -2
- package/es/plugins/common/plugin/progressive-select-all.js +81 -0
- package/es/plugins/common/plugin/register.js +73 -4
- package/es/plugins/link/command/index.js +26 -3
- package/es/plugins/link/conversion/index.js +237 -0
- package/es/plugins/link/index.d.ts +5 -3
- package/es/plugins/link/node/LinkBlockCardNode.d.ts +16 -0
- package/es/plugins/link/node/LinkBlockCardNode.js +66 -0
- package/es/plugins/link/node/LinkCardNode.d.ts +42 -0
- package/es/plugins/link/node/LinkCardNode.js +161 -0
- package/es/plugins/link/node/LinkIframeNode.d.ts +33 -0
- package/es/plugins/link/node/LinkIframeNode.js +131 -0
- package/es/plugins/link/node/LinkNode.d.ts +48 -2
- package/es/plugins/link/node/LinkNode.js +14 -6
- package/es/plugins/link/node/SchemaNode.d.ts +36 -0
- package/es/plugins/link/node/SchemaNode.js +149 -0
- package/es/plugins/link/normalization/index.d.ts +7 -0
- package/es/plugins/link/normalization/index.js +38 -0
- package/es/plugins/link/plugin/index.d.ts +26 -1
- package/es/plugins/link/plugin/index.js +124 -4
- package/es/plugins/link/react/ReactLinkPlugin.js +165 -7
- package/es/plugins/link/react/components/LinkCard.js +235 -0
- package/es/plugins/link/react/components/LinkEdit.js +93 -14
- package/es/plugins/link/react/components/LinkIframe.js +128 -0
- package/es/plugins/link/react/components/LinkToolbar.js +440 -111
- package/es/plugins/link/react/components/SchemaLink.js +49 -0
- package/es/plugins/link/react/index.d.ts +2 -2
- package/es/plugins/link/react/renderer-registry.d.ts +81 -0
- package/es/plugins/link/react/renderer-registry.js +80 -0
- package/es/plugins/link/react/style.js +63 -1
- package/es/plugins/link/react/type.d.ts +24 -1
- package/es/plugins/link/service/i-link-service.d.ts +156 -1
- package/es/plugins/link/service/i-link-service.js +258 -5
- package/es/plugins/list/plugin/index.js +9 -2
- package/es/plugins/litexml/command/diffCommand.js +94 -14
- package/es/plugins/litexml/command/index.js +187 -6
- package/es/plugins/litexml/diff-validation.js +237 -0
- package/es/plugins/litexml/node/DiffContentNode.js +67 -0
- package/es/plugins/litexml/node/DiffNode.d.ts +1 -1
- package/es/plugins/litexml/node/DiffNode.js +5 -2
- package/es/plugins/litexml/node/TableCellDiffNode.js +77 -0
- package/es/plugins/litexml/node/TableRowDiffNode.js +73 -0
- package/es/plugins/litexml/plugin/index.d.ts +2 -0
- package/es/plugins/litexml/plugin/index.js +62 -9
- package/es/plugins/litexml/react/DiffNodeToolbar/index.js +10 -12
- package/es/plugins/litexml/react/TableRowDiffToolbar.js +123 -0
- package/es/plugins/litexml/react/hooks/useHasDiffNode.js +5 -2
- package/es/plugins/litexml/react/index.js +6 -2
- package/es/plugins/litexml/react/style.js +18 -5
- package/es/plugins/litexml/table-cell-diff.js +153 -0
- package/es/plugins/litexml/table-row-diff.js +82 -0
- package/es/plugins/markdown/data-source/markdown/parse.js +25 -1
- package/es/plugins/markdown/plugin/index.js +5 -3
- package/es/plugins/slash/react/ReactSlashPlugin.js +22 -43
- package/es/plugins/slash/react/components/DefaultSlashMenu.js +76 -16
- package/es/plugins/slash/react/menu-utils.js +84 -0
- package/es/plugins/slash/service/i-slash-service.d.ts +3 -0
- package/es/plugins/table/plugin/index.js +4 -1
- package/es/plugins/table/plugin/single-cell-paste.js +79 -0
- package/es/plugins/table/react/TableController/hooks.js +7 -4
- package/es/plugins/table/react/hooks.js +2 -2
- package/es/plugins/table/react/index.js +2 -0
- package/es/plugins/table/react/style.js +15 -5
- package/es/plugins/table/react/useScrollFocusedTableCellIntoView.js +46 -0
- package/es/plugins/toc/index.d.ts +10 -0
- package/es/plugins/toc/plugin/index.d.ts +8 -0
- package/es/plugins/toc/plugin/index.js +56 -0
- package/es/plugins/toc/react/ReactTocPlugin.d.ts +6 -0
- package/es/plugins/toc/react/ReactTocPlugin.js +36 -0
- package/es/plugins/toc/react/getNearestScrollContainer.d.ts +6 -0
- package/es/plugins/toc/react/getNearestScrollContainer.js +18 -0
- package/es/plugins/toc/react/hooks/useActiveHeading.js +59 -0
- package/es/plugins/toc/react/hooks/useTocItems.js +50 -0
- package/es/plugins/toc/react/index.d.ts +5 -0
- package/es/plugins/toc/react/type.d.ts +30 -0
- package/es/plugins/toc/react/useToc.d.ts +5 -0
- package/es/plugins/toc/react/useToc.js +41 -0
- package/es/plugins/toc/service/i-toc-service.d.ts +39 -0
- package/es/plugins/toc/service/i-toc-service.js +125 -0
- package/es/plugins/toc/service/index.d.ts +3 -0
- package/es/plugins/toc/types.d.ts +23 -0
- package/es/plugins/toc/utils/index.d.ts +6 -0
- package/es/plugins/toc/utils/index.js +30 -0
- package/es/plugins/toolbar/react/index.js +26 -12
- package/es/plugins/toolbar/react/style.js +3 -2
- package/es/plugins/virtual-block/plugin/register.js +34 -12
- package/es/plugins/yjs/index.d.ts +6 -0
- package/es/plugins/yjs/plugin/index.d.ts +7 -0
- package/es/plugins/yjs/plugin/index.js +191 -0
- package/es/plugins/yjs/plugin/types.d.ts +16 -0
- package/es/plugins/yjs/plugin/utils/awareness.js +9 -0
- package/es/plugins/yjs/plugin/utils/editor-state.js +47 -0
- package/es/plugins/yjs/plugin/utils/history.js +31 -0
- package/es/plugins/yjs/plugin/utils/node-properties.js +39 -0
- package/es/plugins/yjs/plugin/utils/sync.js +24 -0
- package/es/plugins/yjs/react/codemirrorEditLockProvider.js +59 -0
- package/es/plugins/yjs/react/index.d.ts +23 -0
- package/es/plugins/yjs/react/index.js +200 -0
- package/es/plugins/yjs/service/index.d.ts +47 -0
- package/es/plugins/yjs/service/index.js +84 -0
- package/es/plugins/yjs/websocket-provider.d.ts +78 -0
- package/es/plugins/yjs/websocket-provider.js +263 -0
- package/es/renderer/LexicalRenderer.js +9 -2
- package/es/renderer/engine/render-builtin-node.js +17 -8
- package/es/types/kernel.d.ts +4 -0
- package/es/utils/updatePosition.js +5 -2
- package/package.json +3 -1
- package/patches/@lexical__yjs@0.42.0.patch +42 -0
- package/scripts/postinstall-lexical-patch.cjs +62 -36
|
@@ -2,13 +2,35 @@
|
|
|
2
2
|
import { flattenSlashOptions, isSlashDividerOption, isSlashSectionOption } from "../../service/i-slash-service.js";
|
|
3
3
|
import { shouldShowLoadingPlaceholder } from "./menuLoading.js";
|
|
4
4
|
import { Icon, LOBE_THEME_APP_ID, menuSharedStyles } from "@lobehub/ui";
|
|
5
|
-
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
5
|
+
import { isValidElement, useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { createStaticStyles } from "antd-style";
|
|
8
8
|
import { createPortal } from "react-dom";
|
|
9
9
|
import { autoUpdate, flip, offset, shift, size, useFloating } from "@floating-ui/react";
|
|
10
10
|
//#region src/plugins/slash/react/components/DefaultSlashMenu.tsx
|
|
11
11
|
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
12
|
+
compact: css`
|
|
13
|
+
grid-column: span 1;
|
|
14
|
+
justify-content: center;
|
|
15
|
+
min-width: 0;
|
|
16
|
+
|
|
17
|
+
> :not(:first-child) {
|
|
18
|
+
display: none;
|
|
19
|
+
}
|
|
20
|
+
`,
|
|
21
|
+
divider: css`
|
|
22
|
+
grid-column: 1 / -1;
|
|
23
|
+
`,
|
|
24
|
+
items: css`
|
|
25
|
+
display: grid;
|
|
26
|
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
27
|
+
gap: 4px 6px;
|
|
28
|
+
`,
|
|
29
|
+
menuItem: css`
|
|
30
|
+
box-sizing: border-box;
|
|
31
|
+
width: 100%;
|
|
32
|
+
margin: 0;
|
|
33
|
+
`,
|
|
12
34
|
popup: css`
|
|
13
35
|
scrollbar-width: none;
|
|
14
36
|
|
|
@@ -39,6 +61,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
|
39
61
|
z-index: 1100;
|
|
40
62
|
`,
|
|
41
63
|
section: css`
|
|
64
|
+
grid-column: 1 / -1;
|
|
42
65
|
margin-block-start: 6px;
|
|
43
66
|
`,
|
|
44
67
|
sectionFirst: css`
|
|
@@ -82,15 +105,23 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
|
82
105
|
font-weight: 500;
|
|
83
106
|
color: ${cssVar.colorText};
|
|
84
107
|
white-space: nowrap;
|
|
108
|
+
`,
|
|
109
|
+
tile: css`
|
|
110
|
+
grid-column: span 3;
|
|
111
|
+
`,
|
|
112
|
+
wide: css`
|
|
113
|
+
grid-column: 1 / -1;
|
|
85
114
|
`
|
|
86
115
|
}));
|
|
87
116
|
const renderMenuItem = (item, activeKey, onSelect) => {
|
|
88
117
|
const isHighlighted = item.key === activeKey;
|
|
89
118
|
const isDisabled = Boolean(item.disabled);
|
|
90
|
-
const description = item.metadata?.description;
|
|
119
|
+
const description = item.description ?? item.metadata?.description;
|
|
120
|
+
const layoutClass = styles[item.layout ?? "wide"];
|
|
121
|
+
const shortcut = item.shortcut ?? item.extra;
|
|
91
122
|
return /* @__PURE__ */ jsxs("div", {
|
|
92
123
|
"aria-disabled": isDisabled || void 0,
|
|
93
|
-
className: menuSharedStyles.item
|
|
124
|
+
className: `${menuSharedStyles.item} ${styles.menuItem} ${layoutClass}`,
|
|
94
125
|
"data-disabled": isDisabled ? "" : void 0,
|
|
95
126
|
"data-highlighted": isHighlighted ? "" : void 0,
|
|
96
127
|
onClick: () => {
|
|
@@ -102,7 +133,7 @@ const renderMenuItem = (item, activeKey, onSelect) => {
|
|
|
102
133
|
children: [
|
|
103
134
|
item.icon ? /* @__PURE__ */ jsx("span", {
|
|
104
135
|
className: menuSharedStyles.icon,
|
|
105
|
-
children: /* @__PURE__ */ jsx(Icon, { icon: item.icon })
|
|
136
|
+
children: isValidElement(item.icon) ? item.icon : /* @__PURE__ */ jsx(Icon, { icon: item.icon })
|
|
106
137
|
}) : null,
|
|
107
138
|
/* @__PURE__ */ jsxs("span", {
|
|
108
139
|
className: styles.text,
|
|
@@ -114,9 +145,9 @@ const renderMenuItem = (item, activeKey, onSelect) => {
|
|
|
114
145
|
children: description
|
|
115
146
|
}) : null]
|
|
116
147
|
}),
|
|
117
|
-
|
|
148
|
+
shortcut ? /* @__PURE__ */ jsx("span", {
|
|
118
149
|
className: menuSharedStyles.extra,
|
|
119
|
-
children:
|
|
150
|
+
children: shortcut
|
|
120
151
|
}) : null
|
|
121
152
|
]
|
|
122
153
|
}, String(item.key));
|
|
@@ -126,24 +157,50 @@ const renderItems = (options, activeKey, loading, onSelect) => {
|
|
|
126
157
|
className: menuSharedStyles.empty,
|
|
127
158
|
children: "Loading..."
|
|
128
159
|
});
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
className: `${
|
|
133
|
-
|
|
134
|
-
className: styles.
|
|
135
|
-
children:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
160
|
+
return /* @__PURE__ */ jsx("div", {
|
|
161
|
+
className: styles.items,
|
|
162
|
+
children: options.map((opt, index) => {
|
|
163
|
+
if (isSlashDividerOption(opt)) return /* @__PURE__ */ jsx("div", { className: `${menuSharedStyles.separator} ${styles.divider}` }, `__divider_${index}`);
|
|
164
|
+
if (isSlashSectionOption(opt)) return /* @__PURE__ */ jsxs("div", {
|
|
165
|
+
className: `${styles.section} ${index === 0 ? styles.sectionFirst : ""}`,
|
|
166
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
167
|
+
className: styles.sectionLabel,
|
|
168
|
+
children: opt.label
|
|
169
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
170
|
+
className: styles.items,
|
|
171
|
+
children: opt.items.map((item) => renderMenuItem(item, activeKey, onSelect))
|
|
172
|
+
})]
|
|
173
|
+
}, String(opt.key ?? `__section_${index}`));
|
|
174
|
+
return renderMenuItem(opt, activeKey, onSelect);
|
|
175
|
+
})
|
|
139
176
|
});
|
|
140
177
|
};
|
|
178
|
+
function useKeepActiveItemVisible(open, activeKey, options) {
|
|
179
|
+
const popupRef = useRef(null);
|
|
180
|
+
useLayoutEffect(() => {
|
|
181
|
+
if (!open || !activeKey) return;
|
|
182
|
+
const popup = popupRef.current;
|
|
183
|
+
const selectedItem = popup?.querySelector("[data-highlighted]");
|
|
184
|
+
if (!popup || !selectedItem) return;
|
|
185
|
+
const padding = 8;
|
|
186
|
+
const popupRect = popup.getBoundingClientRect();
|
|
187
|
+
const selectedRect = selectedItem.getBoundingClientRect();
|
|
188
|
+
if (selectedRect.top < popupRect.top + padding) popup.scrollTop -= popupRect.top + padding - selectedRect.top;
|
|
189
|
+
else if (selectedRect.bottom > popupRect.bottom - padding) popup.scrollTop += selectedRect.bottom - (popupRect.bottom - padding);
|
|
190
|
+
}, [
|
|
191
|
+
activeKey,
|
|
192
|
+
open,
|
|
193
|
+
options
|
|
194
|
+
]);
|
|
195
|
+
return popupRef;
|
|
196
|
+
}
|
|
141
197
|
const resolvePortalContainer = () => {
|
|
142
198
|
if (typeof document === "undefined") return null;
|
|
143
199
|
return document.getElementById(LOBE_THEME_APP_ID) ?? document.body;
|
|
144
200
|
};
|
|
145
201
|
const FullWidthSlashMenu = ({ activeKey, anchor, loading, onSelect, open, options, placement }) => {
|
|
146
202
|
const resolvedPlacement = placement === "bottom" ? "bottom-start" : "top-start";
|
|
203
|
+
const popupRef = useKeepActiveItemVisible(open, activeKey, options);
|
|
147
204
|
const { refs, floatingStyles, isPositioned } = useFloating({
|
|
148
205
|
elements: { reference: anchor },
|
|
149
206
|
middleware: [
|
|
@@ -171,12 +228,14 @@ const FullWidthSlashMenu = ({ activeKey, anchor, loading, onSelect, open, option
|
|
|
171
228
|
},
|
|
172
229
|
children: /* @__PURE__ */ jsx("div", {
|
|
173
230
|
className: styles.popup,
|
|
231
|
+
ref: popupRef,
|
|
174
232
|
children: renderItems(options, activeKey, loading, onSelect)
|
|
175
233
|
})
|
|
176
234
|
}), portalContainer);
|
|
177
235
|
};
|
|
178
236
|
const CaretSlashMenu = ({ activeKey, loading, onSelect, open, options, placement: forcePlacement, position }) => {
|
|
179
237
|
const resolvedPlacement = forcePlacement ? `${forcePlacement}-start` : "top-start";
|
|
238
|
+
const popupRef = useKeepActiveItemVisible(open, activeKey, options);
|
|
180
239
|
const middleware = useMemo(() => [
|
|
181
240
|
offset(8),
|
|
182
241
|
...!forcePlacement ? [flip()] : [],
|
|
@@ -226,6 +285,7 @@ const CaretSlashMenu = ({ activeKey, loading, onSelect, open, options, placement
|
|
|
226
285
|
},
|
|
227
286
|
children: /* @__PURE__ */ jsx("div", {
|
|
228
287
|
className: styles.popup,
|
|
288
|
+
ref: popupRef,
|
|
229
289
|
children: renderItems(options, activeKey, loading, onSelect)
|
|
230
290
|
})
|
|
231
291
|
}), portalContainer);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { flattenSlashOptions, isSlashMenuOption as isSlashMenuOption$1, isSlashSectionOption } from "../service/i-slash-service.js";
|
|
2
|
+
//#region src/plugins/slash/react/menu-utils.ts
|
|
3
|
+
function isSlashMenuOption(option) {
|
|
4
|
+
return isSlashMenuOption$1(option) && Boolean(option.key);
|
|
5
|
+
}
|
|
6
|
+
function getSelectableSlashOptions(options) {
|
|
7
|
+
return flattenSlashOptions(options).filter((item) => Boolean(item.key) && !item.disabled);
|
|
8
|
+
}
|
|
9
|
+
function getNextSlashActiveKey(options, activeKey, direction) {
|
|
10
|
+
const selectableOptions = getSelectableSlashOptions(options);
|
|
11
|
+
if (selectableOptions.length === 0) return null;
|
|
12
|
+
const currentIndex = activeKey === null ? -1 : selectableOptions.findIndex((option) => option.key === String(activeKey));
|
|
13
|
+
const nextIndex = direction === "forward" ? currentIndex === -1 ? 0 : (currentIndex + 1) % selectableOptions.length : currentIndex === -1 ? selectableOptions.length - 1 : (currentIndex - 1 + selectableOptions.length) % selectableOptions.length;
|
|
14
|
+
return String(selectableOptions[nextIndex].key);
|
|
15
|
+
}
|
|
16
|
+
function findSlashOptionByKey(options, key) {
|
|
17
|
+
if (key === null) return null;
|
|
18
|
+
for (const option of flattenSlashOptions(options)) if (option.key === String(key)) return option;
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function getLayoutColumnCount(option) {
|
|
22
|
+
if (option.layout === "compact") return 6;
|
|
23
|
+
if (option.layout === "tile") return 2;
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
function getOptionRows(options) {
|
|
27
|
+
const rows = [];
|
|
28
|
+
let pendingLayout = null;
|
|
29
|
+
let pendingColumns = 1;
|
|
30
|
+
let pendingRow = [];
|
|
31
|
+
const flushPendingRow = () => {
|
|
32
|
+
if (pendingRow.length === 0) return;
|
|
33
|
+
rows.push(pendingRow);
|
|
34
|
+
pendingRow = [];
|
|
35
|
+
};
|
|
36
|
+
const expandedOptions = options.flatMap((option) => {
|
|
37
|
+
if (!isSlashSectionOption(option)) return [option];
|
|
38
|
+
return [
|
|
39
|
+
{ type: "divider" },
|
|
40
|
+
...option.items,
|
|
41
|
+
{ type: "divider" }
|
|
42
|
+
];
|
|
43
|
+
});
|
|
44
|
+
for (const option of expandedOptions) {
|
|
45
|
+
if (!isSlashMenuOption(option) || option.disabled) {
|
|
46
|
+
flushPendingRow();
|
|
47
|
+
pendingLayout = null;
|
|
48
|
+
pendingColumns = 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const columns = getLayoutColumnCount(option);
|
|
52
|
+
const layout = option.layout ?? "wide";
|
|
53
|
+
if (columns === 1) {
|
|
54
|
+
flushPendingRow();
|
|
55
|
+
rows.push([option]);
|
|
56
|
+
pendingLayout = null;
|
|
57
|
+
pendingColumns = 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (pendingLayout !== layout || pendingColumns !== columns || pendingRow.length >= columns) {
|
|
61
|
+
flushPendingRow();
|
|
62
|
+
pendingLayout = layout;
|
|
63
|
+
pendingColumns = columns;
|
|
64
|
+
}
|
|
65
|
+
pendingRow.push(option);
|
|
66
|
+
}
|
|
67
|
+
flushPendingRow();
|
|
68
|
+
return rows;
|
|
69
|
+
}
|
|
70
|
+
function getNextSlashSpatialActiveKey(options, activeKey, direction) {
|
|
71
|
+
if (direction === "left") return getNextSlashActiveKey(options, activeKey, "backward");
|
|
72
|
+
if (direction === "right") return getNextSlashActiveKey(options, activeKey, "forward");
|
|
73
|
+
const rows = getOptionRows(options);
|
|
74
|
+
if (rows.length === 0) return null;
|
|
75
|
+
if (activeKey === null) return String(rows[0][0].key);
|
|
76
|
+
const activeRowIndex = rows.findIndex((row) => row.some((option) => option.key === String(activeKey)));
|
|
77
|
+
if (activeRowIndex === -1) return String(rows[0][0].key);
|
|
78
|
+
const activeColumnIndex = rows[activeRowIndex].findIndex((option) => option.key === String(activeKey));
|
|
79
|
+
const nextRow = rows[direction === "down" ? (activeRowIndex + 1) % rows.length : (activeRowIndex - 1 + rows.length) % rows.length];
|
|
80
|
+
const nextColumnIndex = Math.min(activeColumnIndex, nextRow.length - 1);
|
|
81
|
+
return String(nextRow[nextColumnIndex].key);
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
export { findSlashOptionByKey, getNextSlashActiveKey, getNextSlashSpatialActiveKey, getSelectableSlashOptions, isSlashMenuOption };
|
|
@@ -9,8 +9,11 @@ type ISlashDividerOption = {
|
|
|
9
9
|
type: 'divider';
|
|
10
10
|
};
|
|
11
11
|
interface ISlashMenuOption extends DropdownMenuItemType {
|
|
12
|
+
description?: ReactNode;
|
|
13
|
+
layout?: 'compact' | 'tile' | 'wide';
|
|
12
14
|
metadata?: Record<string, any>;
|
|
13
15
|
onSelect?: (editor: IEditor, matchingString: string) => void;
|
|
16
|
+
shortcut?: ReactNode;
|
|
14
17
|
}
|
|
15
18
|
interface ISlashSectionOption {
|
|
16
19
|
items: ISlashMenuOption[];
|
|
@@ -8,6 +8,7 @@ import { createDefaultTableColWidths } from "../utils/index.js";
|
|
|
8
8
|
import { AUTO_FIT_TABLE_COLUMN_WIDTH_COMMAND, DISTRIBUTE_TABLE_COLUMN_WIDTH_COMMAND, INSERT_TABLE_COLUMN_COMMAND, INSERT_TABLE_ROW_COMMAND, SYNC_TABLE_COLUMN_WIDTH_COMMAND, registerTableCommand } from "../command/index.js";
|
|
9
9
|
import { TableNode as TableNode$1, patchTableNode } from "../node/index.js";
|
|
10
10
|
import { ITableControllerMenuService, TableControllerMenuService } from "../service/i-table-controller-menu-service.js";
|
|
11
|
+
import { registerSingleCellTablePaste } from "./single-cell-paste.js";
|
|
11
12
|
import { $computeTableMapSkipCellCheck, $createTableSelection, $deleteTableColumnAtSelection, $deleteTableRowAtSelection, $isTableNode, TableCellNode, TableRowNode, registerTableCellUnmergeTransform, registerTablePlugin, registerTableSelectionObserver, setScrollableTablesActive } from "@lexical/table";
|
|
12
13
|
import { $setSelection } from "lexical";
|
|
13
14
|
//#region src/plugins/table/plugin/index.ts
|
|
@@ -16,6 +17,7 @@ init_plugin();
|
|
|
16
17
|
const tableCellProcessor = (before, content, after) => {
|
|
17
18
|
return before + content.replace(/\n+$/, "").replaceAll(/\n+/g, "<br />") + after;
|
|
18
19
|
};
|
|
20
|
+
const isRemovedTableRowDiff = (node) => node.getType() === "table-row-diff" && "getDiffType" in node && typeof node.getDiffType === "function" && node.getDiffType() === "remove";
|
|
19
21
|
function isHeadlessEditor(editor) {
|
|
20
22
|
return editor._headless === true;
|
|
21
23
|
}
|
|
@@ -64,6 +66,7 @@ const TablePlugin = class extends KernelPlugin {
|
|
|
64
66
|
}
|
|
65
67
|
onInit(editor) {
|
|
66
68
|
this.register(registerTableCellUnmergeTransform(editor));
|
|
69
|
+
this.register(registerSingleCellTablePaste(editor));
|
|
67
70
|
if (!isHeadlessEditor(editor)) {
|
|
68
71
|
setScrollableTablesActive(editor, true);
|
|
69
72
|
this.register(registerTablePlugin(editor));
|
|
@@ -310,7 +313,7 @@ const TablePlugin = class extends KernelPlugin {
|
|
|
310
313
|
markdownService.registerMarkdownWriter(TableRowNode.getType(), (ctx, node) => {
|
|
311
314
|
const parent = node.getParent();
|
|
312
315
|
if (!$isTableNode(parent)) return;
|
|
313
|
-
if (!node.
|
|
316
|
+
if (!node.getPreviousSiblings().some((sibling) => !isRemovedTableRowDiff(sibling))) ctx.wrap("", `\n${parent.getColWidths()?.map(() => {
|
|
314
317
|
return "|:--";
|
|
315
318
|
}).join("")}|\n`);
|
|
316
319
|
else ctx.wrap("", "\n");
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { COMMAND_PRIORITY_HIGH, CONTROLLED_TEXT_INSERTION_COMMAND, PASTE_COMMAND } from "lexical";
|
|
2
|
+
//#region src/plugins/table/plugin/single-cell-paste.ts
|
|
3
|
+
const LEXICAL_CLIPBOARD_TYPE = "application/x-lexical-editor";
|
|
4
|
+
const countNodesByType = (node, type) => {
|
|
5
|
+
return (node.type === type ? 1 : 0) + (Array.isArray(node.children) ? node.children.reduce((count, child) => count + countNodesByType(child, type), 0) : 0);
|
|
6
|
+
};
|
|
7
|
+
const parseLexicalTableClipboard = (serializedClipboard) => {
|
|
8
|
+
if (!serializedClipboard) return void 0;
|
|
9
|
+
try {
|
|
10
|
+
const clipboard = JSON.parse(serializedClipboard);
|
|
11
|
+
if (!Array.isArray(clipboard.nodes) || clipboard.nodes.length !== 1) return { isSingleCell: false };
|
|
12
|
+
const [rootNode] = clipboard.nodes;
|
|
13
|
+
if (rootNode?.type !== "table") return { isSingleCell: false };
|
|
14
|
+
const cells = [];
|
|
15
|
+
const collectCells = (node) => {
|
|
16
|
+
if (node.type === "tablecell") cells.push(node);
|
|
17
|
+
node.children?.forEach(collectCells);
|
|
18
|
+
};
|
|
19
|
+
collectCells(rootNode);
|
|
20
|
+
const isSingleCell = countNodesByType(rootNode, "table") === 1 && cells.length === 1;
|
|
21
|
+
return {
|
|
22
|
+
cell: isSingleCell ? cells[0] : void 0,
|
|
23
|
+
isSingleCell
|
|
24
|
+
};
|
|
25
|
+
} catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const getSerializedNodeText = (node) => {
|
|
30
|
+
if (node.type === "text") return node.text || "";
|
|
31
|
+
if (node.type === "linebreak") return "\n";
|
|
32
|
+
if (node.type === "tab") return " ";
|
|
33
|
+
return node.children?.map(getSerializedNodeText).join("") || "";
|
|
34
|
+
};
|
|
35
|
+
const getSerializedCellText = (cell) => {
|
|
36
|
+
return cell.children?.map(getSerializedNodeText).join("\n") || "";
|
|
37
|
+
};
|
|
38
|
+
const getSingleCellHTMLText = (html) => {
|
|
39
|
+
if (!isSingleCellHTMLTable(html) || typeof DOMParser === "undefined") return void 0;
|
|
40
|
+
const cell = new DOMParser().parseFromString(html, "text/html").body.querySelector("td, th");
|
|
41
|
+
if (!cell) return void 0;
|
|
42
|
+
cell.querySelectorAll("br").forEach((node) => node.replaceWith("\n"));
|
|
43
|
+
return Array.from(cell.children).map((node) => node.textContent || "").join("\n") || cell.textContent || "";
|
|
44
|
+
};
|
|
45
|
+
const isSingleCellHTMLTable = (html) => {
|
|
46
|
+
if (!html || typeof DOMParser === "undefined") return false;
|
|
47
|
+
const document = new DOMParser().parseFromString(html, "text/html");
|
|
48
|
+
const tables = document.body.querySelectorAll("table");
|
|
49
|
+
if (tables.length !== 1 || tables[0].querySelectorAll("td, th").length !== 1) return false;
|
|
50
|
+
const remainder = document.body.cloneNode(true);
|
|
51
|
+
remainder.querySelector("table")?.remove();
|
|
52
|
+
remainder.querySelectorAll("link, meta, script, style").forEach((node) => node.remove());
|
|
53
|
+
return !remainder.querySelector("audio, canvas, iframe, img, object, svg, video") && remainder.textContent?.trim() === "";
|
|
54
|
+
};
|
|
55
|
+
const isSingleCellTableClipboard = (clipboardData) => {
|
|
56
|
+
const lexicalResult = parseLexicalTableClipboard(clipboardData.getData(LEXICAL_CLIPBOARD_TYPE));
|
|
57
|
+
if (lexicalResult !== void 0) return lexicalResult.isSingleCell;
|
|
58
|
+
return isSingleCellHTMLTable(clipboardData.getData("text/html"));
|
|
59
|
+
};
|
|
60
|
+
const getSingleCellPlainText = (clipboardData) => {
|
|
61
|
+
const lexicalResult = parseLexicalTableClipboard(clipboardData.getData(LEXICAL_CLIPBOARD_TYPE));
|
|
62
|
+
if (lexicalResult?.cell) return getSerializedCellText(lexicalResult.cell);
|
|
63
|
+
const htmlText = getSingleCellHTMLText(clipboardData.getData("text/html"));
|
|
64
|
+
if (htmlText !== void 0) return htmlText;
|
|
65
|
+
return clipboardData.getData("text/plain").replace(/(?:\r\n|\n)$/, "");
|
|
66
|
+
};
|
|
67
|
+
const handleSingleCellTablePaste = (editor, event) => {
|
|
68
|
+
if (!("clipboardData" in event)) return false;
|
|
69
|
+
const clipboardData = event.clipboardData;
|
|
70
|
+
if (!clipboardData || !isSingleCellTableClipboard(clipboardData)) return false;
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
editor.dispatchCommand(CONTROLLED_TEXT_INSERTION_COMMAND, getSingleCellPlainText(clipboardData));
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
75
|
+
const registerSingleCellTablePaste = (editor) => {
|
|
76
|
+
return editor.registerCommand(PASTE_COMMAND, (event) => handleSingleCellTablePaste(editor, event), COMMAND_PRIORITY_HIGH);
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
export { getSingleCellPlainText, handleSingleCellTablePaste, isSingleCellTableClipboard, registerSingleCellTablePaste };
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { syncTableWidthDOM } from "../../utils/index.js";
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { $isTableNode } from "@lexical/table";
|
|
4
|
+
import { $getNodeByKey } from "lexical";
|
|
3
5
|
//#region src/plugins/table/react/TableController/hooks.ts
|
|
4
6
|
const CONTROL_SIZE = 14;
|
|
5
7
|
const ROW_BORDER_HEIGHT = .5;
|
|
6
8
|
const sum = (values) => {
|
|
7
9
|
return values.reduce((total, value) => total + value, 0);
|
|
8
10
|
};
|
|
9
|
-
const getTableElement = (element) => {
|
|
11
|
+
const getTableElement$1 = (element) => {
|
|
10
12
|
if (element instanceof HTMLTableElement) return element;
|
|
11
13
|
return element?.querySelector("table.editor_table, table") || null;
|
|
12
14
|
};
|
|
13
15
|
const readNodeColWidths = (editor, node) => {
|
|
14
16
|
return editor.getEditorState().read(() => {
|
|
15
|
-
const latestNode = node.
|
|
17
|
+
const latestNode = $getNodeByKey(node.getKey());
|
|
18
|
+
if (!$isTableNode(latestNode)) return [];
|
|
16
19
|
const columnCount = latestNode.getColumnCount();
|
|
17
20
|
const colWidths = latestNode.getColWidths();
|
|
18
21
|
return Array.from({ length: columnCount }, (_, index) => colWidths?.[index] || 92);
|
|
@@ -33,7 +36,7 @@ const useTableColumnMetrics = (editor, node) => {
|
|
|
33
36
|
const [tableHeight, setTableHeight] = useState(0);
|
|
34
37
|
const refreshColumnMetrics = useCallback(() => {
|
|
35
38
|
const nodeColWidths = readNodeColWidths(editor, node);
|
|
36
|
-
const tableElement = getTableElement(editor.getElementByKey(node.getKey()));
|
|
39
|
+
const tableElement = getTableElement$1(editor.getElementByKey(node.getKey()));
|
|
37
40
|
setColWidths((currentWidths) => {
|
|
38
41
|
if (currentWidths.length === nodeColWidths.length && currentWidths.every((width, index) => width === nodeColWidths[index])) return currentWidths;
|
|
39
42
|
return nodeColWidths;
|
|
@@ -44,7 +47,7 @@ const useTableColumnMetrics = (editor, node) => {
|
|
|
44
47
|
}
|
|
45
48
|
}, [editor, node]);
|
|
46
49
|
useEffect(() => {
|
|
47
|
-
const tableElement = getTableElement(editor.getElementByKey(node.getKey()));
|
|
50
|
+
const tableElement = getTableElement$1(editor.getElementByKey(node.getKey()));
|
|
48
51
|
let frame = null;
|
|
49
52
|
const scheduleMeasure = () => {
|
|
50
53
|
if (frame !== null) cancelAnimationFrame(frame);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getTableSelectionIndexes, isTableFullySelected } from "../utils/index.js";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import { $findTableNode, $isTableSelection } from "@lexical/table";
|
|
3
|
+
import { $findTableNode, $isTableNode, $isTableSelection } from "@lexical/table";
|
|
4
4
|
import "es-toolkit/compat";
|
|
5
5
|
import { $getNodeByKey, $getSelection, $isRangeSelection } from "lexical";
|
|
6
6
|
//#region src/plugins/table/react/hooks.ts
|
|
@@ -14,7 +14,7 @@ const readTableControllerSelection = (editor, tableKey) => {
|
|
|
14
14
|
return editor.getEditorState().read(() => {
|
|
15
15
|
const selection = $getSelection();
|
|
16
16
|
const tableNode = $getNodeByKey(tableKey);
|
|
17
|
-
if (
|
|
17
|
+
if (!$isTableNode(tableNode)) return {
|
|
18
18
|
isTableFocused: false,
|
|
19
19
|
isTableSelected: false,
|
|
20
20
|
selectedColumns: [],
|
|
@@ -13,6 +13,7 @@ import TableHoverActions from "./TableHoverActions/index.js";
|
|
|
13
13
|
import TableResize_default from "./TableResize/index.js";
|
|
14
14
|
import TableRowController from "./TableRowController.js";
|
|
15
15
|
import { useAutoFitPastedTable } from "./useAutoFitPastedTable.js";
|
|
16
|
+
import { useScrollFocusedTableCellIntoView } from "./useScrollFocusedTableCellIntoView.js";
|
|
16
17
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react";
|
|
17
18
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
18
19
|
import { cx } from "antd-style";
|
|
@@ -34,6 +35,7 @@ const ReactTablePlugin = ({ className, locale, resizeMode = "realtime" }) => {
|
|
|
34
35
|
return new EventEmitter();
|
|
35
36
|
}, []);
|
|
36
37
|
useAutoFitPastedTable(lexicalEditor);
|
|
38
|
+
useScrollFocusedTableCellIntoView(lexicalEditor);
|
|
37
39
|
const selectionOutlineStyle = useMemo(() => {
|
|
38
40
|
if (!selectionOutlineRect) return;
|
|
39
41
|
const style = {
|
|
@@ -49,9 +49,17 @@ const styles = createStaticStyles(({ css, cssVar }) => css`
|
|
|
49
49
|
);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
/*
|
|
53
|
+
* These hosts belong to the table controller only. Keep the selector scoped
|
|
54
|
+
* to the table root / scroll wrapper so a nested editor feature using the
|
|
55
|
+
* generic toolbar class (for example a LiteXML DiffNode inside a cell) is
|
|
56
|
+
* not made click-through or collapsed to zero height.
|
|
57
|
+
*/
|
|
58
|
+
> .toolbar,
|
|
59
|
+
> .toolbar-col,
|
|
60
|
+
> .toolbar-row,
|
|
61
|
+
> .lobe-editor-table-scroll-wrapper > .toolbar-col,
|
|
62
|
+
> .lobe-editor-table-scroll-wrapper > .toolbar-row {
|
|
55
63
|
pointer-events: none;
|
|
56
64
|
|
|
57
65
|
position: absolute;
|
|
@@ -63,8 +71,10 @@ const styles = createStaticStyles(({ css, cssVar }) => css`
|
|
|
63
71
|
height: 0;
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
.toolbar-col,
|
|
67
|
-
.toolbar-row
|
|
74
|
+
> .toolbar-col,
|
|
75
|
+
> .toolbar-row,
|
|
76
|
+
> .lobe-editor-table-scroll-wrapper > .toolbar-col,
|
|
77
|
+
> .lobe-editor-table-scroll-wrapper > .toolbar-row {
|
|
68
78
|
inset-inline-start: var(--lobe-block-anchor-padding, 54px);
|
|
69
79
|
}
|
|
70
80
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { $getTableCellNodeFromLexicalNode, $isTableSelection } from "@lexical/table";
|
|
3
|
+
import { $getSelection, $isRangeSelection } from "lexical";
|
|
4
|
+
//#region src/plugins/table/react/useScrollFocusedTableCellIntoView.ts
|
|
5
|
+
const $getSelectedTableCellKey = () => {
|
|
6
|
+
const selection = $getSelection();
|
|
7
|
+
if (!$isRangeSelection(selection) && !$isTableSelection(selection)) return null;
|
|
8
|
+
return $getTableCellNodeFromLexicalNode(selection.anchor.getNode())?.getKey() ?? null;
|
|
9
|
+
};
|
|
10
|
+
/** UI-only behavior that keeps keyboard-focused table cells inside the visible scroll area. */
|
|
11
|
+
const useScrollFocusedTableCellIntoView = (editor) => {
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!editor) return;
|
|
14
|
+
let frame = null;
|
|
15
|
+
let selectedCellKey = null;
|
|
16
|
+
const cancelScheduledScroll = () => {
|
|
17
|
+
if (frame === null) return;
|
|
18
|
+
cancelAnimationFrame(frame);
|
|
19
|
+
frame = null;
|
|
20
|
+
};
|
|
21
|
+
const handleEditorState = (editorState) => {
|
|
22
|
+
const nextCellKey = editorState.read($getSelectedTableCellKey);
|
|
23
|
+
if (nextCellKey === selectedCellKey) return;
|
|
24
|
+
selectedCellKey = nextCellKey;
|
|
25
|
+
cancelScheduledScroll();
|
|
26
|
+
if (!nextCellKey) return;
|
|
27
|
+
frame = requestAnimationFrame(() => {
|
|
28
|
+
frame = null;
|
|
29
|
+
editor.getElementByKey(nextCellKey)?.scrollIntoView({
|
|
30
|
+
block: "nearest",
|
|
31
|
+
inline: "nearest"
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
handleEditorState(editor.getEditorState());
|
|
36
|
+
const unregister = editor.registerUpdateListener(({ editorState }) => {
|
|
37
|
+
handleEditorState(editorState);
|
|
38
|
+
});
|
|
39
|
+
return () => {
|
|
40
|
+
cancelScheduledScroll();
|
|
41
|
+
unregister();
|
|
42
|
+
};
|
|
43
|
+
}, [editor]);
|
|
44
|
+
};
|
|
45
|
+
//#endregion
|
|
46
|
+
export { useScrollFocusedTableCellIntoView };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TocHeadingTag, TocItem, TocJumpOptions, TocPluginOptions, TocScrollBehavior, TocScrollLogicalPosition } from "./types.js";
|
|
2
|
+
import { ITocService, TocService } from "./service/i-toc-service.js";
|
|
3
|
+
import "./service/index.js";
|
|
4
|
+
import { TocPlugin } from "./plugin/index.js";
|
|
5
|
+
import { ReactTocPluginProps, UseTocOptions, UseTocResult } from "./react/type.js";
|
|
6
|
+
import ReactTocPlugin from "./react/ReactTocPlugin.js";
|
|
7
|
+
import { useToc } from "./react/useToc.js";
|
|
8
|
+
import "./react/index.js";
|
|
9
|
+
import { buildTocTree, flattenTocTree } from "./utils/index.js";
|
|
10
|
+
export { ITocService, ReactTocPlugin, type ReactTocPluginProps, TocHeadingTag, TocItem, TocJumpOptions, TocPlugin, TocPluginOptions, TocScrollBehavior, TocScrollLogicalPosition, TocService, type UseTocOptions, type UseTocResult, buildTocTree, flattenTocTree, useToc };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IEditorPluginConstructor } from "../../../types/kernel.js";
|
|
2
|
+
import "../../../types/index.js";
|
|
3
|
+
import { TocPluginOptions } from "../types.js";
|
|
4
|
+
import "../service/index.js";
|
|
5
|
+
//#region src/plugins/toc/plugin/index.d.ts
|
|
6
|
+
declare const TocPlugin: IEditorPluginConstructor<TocPluginOptions>;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { TocPlugin };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { KernelPlugin, init_plugin } from "../../../editor-kernel/plugin.js";
|
|
2
|
+
import { ITocService, TocService } from "../service/i-toc-service.js";
|
|
3
|
+
import { $getNodeByKey } from "lexical";
|
|
4
|
+
import { $isHeadingNode } from "@lexical/rich-text";
|
|
5
|
+
//#region src/plugins/toc/plugin/index.ts
|
|
6
|
+
init_plugin();
|
|
7
|
+
function $hasHeadingAncestor(node) {
|
|
8
|
+
let current = node;
|
|
9
|
+
while (current) {
|
|
10
|
+
if ($isHeadingNode(current)) return true;
|
|
11
|
+
current = current.getParent();
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
function hasHeadingUpdate(editorState, prevEditorState, dirtyElements, dirtyLeaves) {
|
|
16
|
+
const dirtyKeys = /* @__PURE__ */ new Set([...dirtyElements.keys(), ...dirtyLeaves]);
|
|
17
|
+
if (dirtyKeys.size === 0) return false;
|
|
18
|
+
if (dirtyElements.get("root") === true) return true;
|
|
19
|
+
let hasHeading = false;
|
|
20
|
+
const findHeading = () => {
|
|
21
|
+
for (const key of dirtyKeys) if ($hasHeadingAncestor($getNodeByKey(key))) {
|
|
22
|
+
hasHeading = true;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
editorState.read(findHeading);
|
|
27
|
+
if (hasHeading) return true;
|
|
28
|
+
prevEditorState.read(findHeading);
|
|
29
|
+
return hasHeading;
|
|
30
|
+
}
|
|
31
|
+
const TocPlugin = class extends KernelPlugin {
|
|
32
|
+
static {
|
|
33
|
+
this.pluginName = "TocPlugin";
|
|
34
|
+
}
|
|
35
|
+
constructor(kernel, config = {}) {
|
|
36
|
+
super();
|
|
37
|
+
this.kernel = kernel;
|
|
38
|
+
this.config = config;
|
|
39
|
+
this.service = new TocService();
|
|
40
|
+
this.service.setDepthRange(config);
|
|
41
|
+
kernel.registerServiceHotReload(ITocService, this.service);
|
|
42
|
+
}
|
|
43
|
+
onInit(editor) {
|
|
44
|
+
this.service.bindEditor(editor);
|
|
45
|
+
this.service.refresh();
|
|
46
|
+
const refreshOnDocumentChange = this.service.refresh.bind(this.service);
|
|
47
|
+
this.kernel.on("documentChange", refreshOnDocumentChange);
|
|
48
|
+
this.register(() => this.kernel.off("documentChange", refreshOnDocumentChange));
|
|
49
|
+
this.register(editor.registerUpdateListener(({ dirtyElements, dirtyLeaves, editorState, prevEditorState }) => {
|
|
50
|
+
if (!hasHeadingUpdate(editorState, prevEditorState, dirtyElements, dirtyLeaves)) return;
|
|
51
|
+
this.service.refresh();
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
//#endregion
|
|
56
|
+
export { TocPlugin };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useLexicalComposerContext } from "../../../editor-kernel/react/react-context.js";
|
|
3
|
+
import { TocPlugin } from "../plugin/index.js";
|
|
4
|
+
import { useInsertionEffect } from "react";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
//#region src/plugins/toc/react/ReactTocPlugin.tsx
|
|
7
|
+
const HeadlessTocPlugin = ({ editor, maxDepth = 6, minDepth = 1 }) => {
|
|
8
|
+
useInsertionEffect(() => {
|
|
9
|
+
editor.registerPlugin(TocPlugin, {
|
|
10
|
+
maxDepth,
|
|
11
|
+
minDepth
|
|
12
|
+
});
|
|
13
|
+
}, [
|
|
14
|
+
editor,
|
|
15
|
+
maxDepth,
|
|
16
|
+
minDepth
|
|
17
|
+
]);
|
|
18
|
+
return null;
|
|
19
|
+
};
|
|
20
|
+
const TocPluginWithContext = (props) => {
|
|
21
|
+
const [editor] = useLexicalComposerContext();
|
|
22
|
+
return /* @__PURE__ */ jsx(HeadlessTocPlugin, {
|
|
23
|
+
...props,
|
|
24
|
+
editor
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
const ReactTocPlugin = ({ editor, ...props }) => {
|
|
28
|
+
if (editor) return /* @__PURE__ */ jsx(HeadlessTocPlugin, {
|
|
29
|
+
...props,
|
|
30
|
+
editor
|
|
31
|
+
});
|
|
32
|
+
return /* @__PURE__ */ jsx(TocPluginWithContext, { ...props });
|
|
33
|
+
};
|
|
34
|
+
ReactTocPlugin.displayName = "ReactTocPlugin";
|
|
35
|
+
//#endregion
|
|
36
|
+
export { ReactTocPlugin as default };
|