@lobehub/editor 4.23.0 → 4.23.2
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/editor-kernel/index.js +1 -0
- package/es/editor-kernel/kernel.js +47 -40
- package/es/editor-kernel/react/useLexicalNodeSelection.js +4 -2
- package/es/headless.js +201 -175
- package/es/index.js +5 -2
- package/es/plugins/auto-complete/plugin/index.js +4 -2
- package/es/plugins/block/react/ReactBlockPlugin.js +5 -4
- package/es/plugins/codeblock/command/index.js +8 -6
- package/es/plugins/codeblock/plugin/index.js +4 -2
- package/es/plugins/codeblock/react/ReactCodeblockPlugin.js +2 -1
- package/es/plugins/codemirror-block/command/index.js +8 -6
- package/es/plugins/common/data-source/json-data-source.js +5 -4
- package/es/plugins/common/plugin/index.js +1 -3
- package/es/plugins/common/plugin/paste-handler.js +2 -1
- package/es/plugins/hr/command/index.js +2 -1
- package/es/plugins/image/react/components/BrokenImage.js +2 -1
- package/es/plugins/image/react/components/Image.js +18 -15
- package/es/plugins/image/react/components/ImageEditPopover.js +42 -41
- package/es/plugins/image/react/components/ResizeHandle.js +1 -3
- package/es/plugins/link/node/LinkNode.js +4 -2
- package/es/plugins/link/plugin/registry.js +17 -12
- package/es/plugins/link/react/components/LinkEdit.js +9 -8
- package/es/plugins/list/plugin/index.js +1 -4
- package/es/plugins/list/utils/index.js +4 -2
- package/es/plugins/litexml/command/index.js +69 -62
- package/es/plugins/litexml/node/DiffNode.js +14 -10
- package/es/plugins/litexml/plugin/index.js +6 -12
- package/es/plugins/markdown/data-source/markdown-data-source.js +5 -4
- package/es/plugins/markdown/plugin/index.js +2 -1
- package/es/plugins/markdown/service/transformers.js +4 -2
- package/es/plugins/markdown/utils/index.js +10 -8
- package/es/plugins/math/command/index.js +4 -2
- package/es/plugins/math/react/components/MathInline.js +14 -13
- package/es/plugins/math/react/style.js +2 -2
- package/es/plugins/slash/plugin/index.js +13 -4
- package/es/plugins/slash/react/components/DefaultSlashMenu.js +2 -1
- package/es/plugins/slash/react/components/menuLoading.js +4 -0
- package/es/plugins/slash/service/i-slash-service.d.ts +6 -1
- package/es/plugins/slash/service/i-slash-service.js +16 -2
- package/es/plugins/slash/utils/utils.js +1 -1
- package/es/plugins/table/react/TableActionMenu/ActionMenu.js +69 -67
- package/es/plugins/table/react/TableColController.js +29 -26
- package/es/plugins/table/react/TableHoverActions/index.js +0 -2
- package/es/plugins/table/react/TableRowController.js +29 -26
- package/es/plugins/table/utils/index.js +4 -2
- package/es/react/Editor/Editor.js +24 -23
- package/es/react/hooks/useEditorState/index.js +28 -21
- package/es/react/hooks/useEditorState/utils.js +2 -1
- package/es/renderer/engine/render-builtin-node.js +26 -18
- package/es/renderer/engine/render-tree.js +4 -1
- package/es/renderer/renderers/mermaid.js +2 -1
- package/package.json +1 -1
|
@@ -70,12 +70,9 @@ const LitexmlPlugin = class extends KernelPlugin {
|
|
|
70
70
|
nodeToXML(diffNode.getChildAtIndex(0), lines, indent);
|
|
71
71
|
break;
|
|
72
72
|
case "remove": break;
|
|
73
|
-
case "listItemModify":
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
break;
|
|
78
|
-
default: break;
|
|
73
|
+
case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
|
|
74
|
+
nodeToXML(child, lines, indent);
|
|
75
|
+
});
|
|
79
76
|
}
|
|
80
77
|
return { lines };
|
|
81
78
|
});
|
|
@@ -91,12 +88,9 @@ const LitexmlPlugin = class extends KernelPlugin {
|
|
|
91
88
|
ctx.processChild(ctx, diffNode.getChildAtIndex(0));
|
|
92
89
|
break;
|
|
93
90
|
case "remove": break;
|
|
94
|
-
case "listItemModify":
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
});
|
|
98
|
-
break;
|
|
99
|
-
default: break;
|
|
91
|
+
case "listItemModify": diffNode.getChildAtIndex(1).getChildren().forEach((child) => {
|
|
92
|
+
ctx.processChild(ctx, child);
|
|
93
|
+
});
|
|
100
94
|
}
|
|
101
95
|
return true;
|
|
102
96
|
});
|
|
@@ -83,10 +83,11 @@ var MarkdownDataSource = class extends DataSource {
|
|
|
83
83
|
...node.exportJSON(),
|
|
84
84
|
$key: node.getKey()
|
|
85
85
|
};
|
|
86
|
-
if (node === firstNode)
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
if (node === firstNode) {
|
|
87
|
+
if (node === lastNode) {
|
|
88
|
+
if (anchor.type !== "element" || focus.type !== "element" || focus.offset === anchor.offset) sNode.text = anchorOffset < focusOffset ? sNode.text.slice(anchorOffset, focusOffset) : sNode.text.slice(focusOffset, anchorOffset);
|
|
89
|
+
} else sNode.text = isBefore ? sNode.text.slice(anchorOffset) : sNode.text.slice(focusOffset);
|
|
90
|
+
} else if (node === lastNode) sNode.text = isBefore ? sNode.text.slice(0, focusOffset) : sNode.text.slice(0, anchorOffset);
|
|
90
91
|
let hasPush = false;
|
|
91
92
|
for (let i = lastElement.length - 1; i >= 0; i--) if ($getNodeByKey(lastElement[i].$key)?.isParentOf(node)) {
|
|
92
93
|
lastElement[i].children.push(sNode);
|
|
@@ -212,7 +212,8 @@ const MarkdownPlugin = class extends KernelPlugin {
|
|
|
212
212
|
const markdown = this.markdownDataSource.write(editor, { selection: true });
|
|
213
213
|
if (!markdown) return false;
|
|
214
214
|
event.preventDefault();
|
|
215
|
-
|
|
215
|
+
const data = $getClipboardDataFromSelection(selection);
|
|
216
|
+
setLexicalClipboardDataTransfer(clipboardData, data);
|
|
216
217
|
clipboardData.setData("text/plain", markdown);
|
|
217
218
|
clipboardData.setData("text/markdown", markdown);
|
|
218
219
|
this.logger.debug("copy with text/markdown:", { markdownLength: markdown.length });
|
|
@@ -2,7 +2,8 @@ import { PUNCTUATION_OR_SPACE, getOpenTagStartIndex, isEqualSubString } from "..
|
|
|
2
2
|
import { $createRangeSelection, $getNodeByKey, $getSelection, $isLineBreakNode, $isRangeSelection, $isRootOrShadowRoot, $isTextNode, $setSelection } from "lexical";
|
|
3
3
|
//#region src/plugins/markdown/service/transformers.ts
|
|
4
4
|
function testElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers, fromTrigger) {
|
|
5
|
-
|
|
5
|
+
const grandParentNode = parentNode.getParent();
|
|
6
|
+
if (!$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) return false;
|
|
6
7
|
const textContent = anchorNode.getTextContent();
|
|
7
8
|
if (fromTrigger !== "enter" && textContent[anchorOffset - 1] !== " ") return false;
|
|
8
9
|
for (const { regExp, trigger } of elementTransformers) {
|
|
@@ -12,7 +13,8 @@ function testElementTransformers(parentNode, anchorNode, anchorOffset, elementTr
|
|
|
12
13
|
return false;
|
|
13
14
|
}
|
|
14
15
|
function runElementTransformers(parentNode, anchorNode, anchorOffset, elementTransformers, fromTrigger) {
|
|
15
|
-
|
|
16
|
+
const grandParentNode = parentNode.getParent();
|
|
17
|
+
if (!$isRootOrShadowRoot(grandParentNode) || parentNode.getFirstChild() !== anchorNode) return false;
|
|
16
18
|
const textContent = anchorNode.getTextContent();
|
|
17
19
|
if (fromTrigger !== "enter" && textContent[anchorOffset - 1] !== " ") return false;
|
|
18
20
|
for (const { regExp, replace, trigger } of elementTransformers) {
|
|
@@ -34,7 +34,7 @@ function indexBy(list, callback) {
|
|
|
34
34
|
}
|
|
35
35
|
let Punctuation = /[!"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~\u00A1\u2010-\u2027-]/;
|
|
36
36
|
try {
|
|
37
|
-
Punctuation =
|
|
37
|
+
Punctuation = new RegExp("[\\p{Pc}|\\p{Pd}|\\p{Pe}|\\p{Pf}|\\p{Pi}|\\p{Po}|\\p{Ps}]", "u");
|
|
38
38
|
} catch {}
|
|
39
39
|
/**
|
|
40
40
|
* Checks if a character is a punctuation character.
|
|
@@ -49,13 +49,15 @@ function $updateSelectionOnInsert(selection) {
|
|
|
49
49
|
const anchor = selection.anchor;
|
|
50
50
|
let nodeToInspect = null;
|
|
51
51
|
const anchorCaret = $caretFromPoint(anchor, "previous");
|
|
52
|
-
if (anchorCaret)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
if (anchorCaret) {
|
|
53
|
+
if ($isTextPointCaret(anchorCaret)) nodeToInspect = anchorCaret.origin;
|
|
54
|
+
else {
|
|
55
|
+
const range = $getCaretRange(anchorCaret, $getChildCaret($getRoot(), "next").getFlipped());
|
|
56
|
+
for (const caret of range) if ($isTextNode(caret.origin)) {
|
|
57
|
+
nodeToInspect = caret.origin;
|
|
58
|
+
break;
|
|
59
|
+
} else if ($isElementNode(caret.origin) && !caret.origin.isInline()) break;
|
|
60
|
+
}
|
|
59
61
|
}
|
|
60
62
|
if (nodeToInspect && $isTextNode(nodeToInspect)) {
|
|
61
63
|
const newFormat = nodeToInspect.getFormat();
|
|
@@ -28,8 +28,10 @@ function registerMathCommand(editor) {
|
|
|
28
28
|
const { key, prev } = payload;
|
|
29
29
|
editor.update(() => {
|
|
30
30
|
const mathNode = $getNodeByKey(key);
|
|
31
|
-
if (mathNode)
|
|
32
|
-
|
|
31
|
+
if (mathNode) {
|
|
32
|
+
if (prev) mathNode.selectPrevious();
|
|
33
|
+
else mathNode.selectNext();
|
|
34
|
+
}
|
|
33
35
|
});
|
|
34
36
|
return true;
|
|
35
37
|
}, COMMAND_PRIORITY_HIGH));
|
|
@@ -63,21 +63,22 @@ const MathInline = ({ editor, node, className }) => {
|
|
|
63
63
|
});
|
|
64
64
|
});
|
|
65
65
|
}, [node]);
|
|
66
|
+
const handleClick = useCallback((e) => {
|
|
67
|
+
logger.debug("📊 Math click event:", e.target === ref.current);
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
e.stopPropagation();
|
|
70
|
+
if (isSelected) clearSelected();
|
|
71
|
+
setIsSelected(true);
|
|
72
|
+
editor.focus();
|
|
73
|
+
}, [
|
|
74
|
+
clearSelected,
|
|
75
|
+
editor,
|
|
76
|
+
isSelected,
|
|
77
|
+
setIsSelected
|
|
78
|
+
]);
|
|
66
79
|
return /* @__PURE__ */ jsx("span", {
|
|
67
80
|
className,
|
|
68
|
-
onClick:
|
|
69
|
-
logger.debug("📊 Math click event:", e.target === ref.current);
|
|
70
|
-
e.preventDefault();
|
|
71
|
-
e.stopPropagation();
|
|
72
|
-
if (isSelected) clearSelected();
|
|
73
|
-
setIsSelected(true);
|
|
74
|
-
editor.focus();
|
|
75
|
-
}, [
|
|
76
|
-
clearSelected,
|
|
77
|
-
editor,
|
|
78
|
-
isSelected,
|
|
79
|
-
setIsSelected
|
|
80
|
-
]),
|
|
81
|
+
onClick: handleClick,
|
|
81
82
|
ref,
|
|
82
83
|
children: node.code ? node.code : /* @__PURE__ */ jsx(Placeholder, { mathBlock: isMathBlock })
|
|
83
84
|
});
|
|
@@ -14,12 +14,12 @@ const styles = createStaticStyles(({ css, cssVar }) => {
|
|
|
14
14
|
overflow: auto hidden;
|
|
15
15
|
padding: 3px;
|
|
16
16
|
|
|
17
|
-
.base {
|
|
17
|
+
.katex-base {
|
|
18
18
|
margin-block: 0;
|
|
19
19
|
margin-inline: auto;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
.tag {
|
|
22
|
+
.katex-tag {
|
|
23
23
|
position: relative !important;
|
|
24
24
|
display: inline-block;
|
|
25
25
|
padding-inline-start: 0.5rem;
|
|
@@ -109,7 +109,7 @@ const SlashPlugin = class extends KernelPlugin {
|
|
|
109
109
|
const range = editorWindow.document.createRange();
|
|
110
110
|
const selection = $getSelection();
|
|
111
111
|
const text = getQueryTextForSearch(editor);
|
|
112
|
-
if (!$isRangeSelection(selection) || !selection.isCollapsed() || selection.hasFormat("code") || text === null || range === null
|
|
112
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed() || selection.hasFormat("code") || text === null || range === null) {
|
|
113
113
|
this.triggerClose();
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
@@ -119,8 +119,13 @@ const SlashPlugin = class extends KernelPlugin {
|
|
|
119
119
|
}
|
|
120
120
|
let triggerText = this.currentSlashTrigger;
|
|
121
121
|
if (triggerText === null) {
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
const resolution = this.service?.resolveTrigger(text);
|
|
123
|
+
if (!resolution) {
|
|
124
|
+
this.triggerClose();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
triggerText = resolution.trigger;
|
|
128
|
+
this.currentSlashTriggerIndex = resolution.leadOffset;
|
|
124
129
|
}
|
|
125
130
|
const lastIndex = text.lastIndexOf(triggerText);
|
|
126
131
|
if (lastIndex < this.currentSlashTriggerIndex) {
|
|
@@ -137,11 +142,15 @@ const SlashPlugin = class extends KernelPlugin {
|
|
|
137
142
|
const fuse = this.service?.getSlashFuse(triggerText);
|
|
138
143
|
const isRangePositioned = tryToPositionRange(this.currentSlashTriggerIndex, range, editorWindow);
|
|
139
144
|
const match = triggerFn?.(text.slice(this.currentSlashTriggerIndex));
|
|
145
|
+
if (!match) {
|
|
146
|
+
this.triggerClose();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
140
149
|
if (text.slice(this.currentSlashTriggerIndex).includes(" ") && !slashOptions?.allowWhitespace) {
|
|
141
150
|
this.triggerClose();
|
|
142
151
|
return;
|
|
143
152
|
}
|
|
144
|
-
const finalItems = fuse && match
|
|
153
|
+
const finalItems = fuse && match.matchingString.length > 0 ? fuse.search(match.matchingString).map((result) => result.item) : slashOptions.items;
|
|
145
154
|
if (isRangePositioned !== null && finalItems.length > 0) {
|
|
146
155
|
this.currentSlashTrigger = triggerText;
|
|
147
156
|
this.config?.triggerOpen({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { flattenSlashOptions, isSlashDividerOption, isSlashSectionOption } from "../../service/i-slash-service.js";
|
|
3
|
+
import { shouldShowLoadingPlaceholder } from "./menuLoading.js";
|
|
3
4
|
import { Icon, LOBE_THEME_APP_ID, menuSharedStyles } from "@lobehub/ui";
|
|
4
5
|
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
|
5
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -121,7 +122,7 @@ const renderMenuItem = (item, activeKey, onSelect) => {
|
|
|
121
122
|
}, String(item.key));
|
|
122
123
|
};
|
|
123
124
|
const renderItems = (options, activeKey, loading, onSelect) => {
|
|
124
|
-
if (loading) return /* @__PURE__ */ jsx("div", {
|
|
125
|
+
if (shouldShowLoadingPlaceholder(loading, options)) return /* @__PURE__ */ jsx("div", {
|
|
125
126
|
className: menuSharedStyles.empty,
|
|
126
127
|
children: "Loading..."
|
|
127
128
|
});
|
|
@@ -53,10 +53,15 @@ interface SlashOptions {
|
|
|
53
53
|
searchKeys?: string[];
|
|
54
54
|
trigger: string;
|
|
55
55
|
}
|
|
56
|
+
interface SlashTriggerResolution {
|
|
57
|
+
leadOffset: number;
|
|
58
|
+
trigger: string;
|
|
59
|
+
}
|
|
56
60
|
interface ISlashService {
|
|
57
61
|
registerSlash(options: SlashOptions): void;
|
|
62
|
+
resolveTrigger(text: string): SlashTriggerResolution | null;
|
|
58
63
|
updateOptions(options: SlashOptions[]): void;
|
|
59
64
|
}
|
|
60
65
|
declare const ISlashService: IServiceID<ISlashService>;
|
|
61
66
|
//#endregion
|
|
62
|
-
export { ISlashDividerOption, ISlashMenuOption, ISlashOption, ISlashSectionOption, ISlashService, SlashOptions, flattenSlashOptions, isSlashDividerOption, isSlashMenuOption, isSlashSectionOption };
|
|
67
|
+
export { ISlashDividerOption, ISlashMenuOption, ISlashOption, ISlashSectionOption, ISlashService, SlashOptions, SlashTriggerResolution, flattenSlashOptions, isSlashDividerOption, isSlashMenuOption, isSlashSectionOption };
|
|
@@ -23,8 +23,10 @@ var SlashService = class {
|
|
|
23
23
|
this.logger = createDebugLogger("service", "slash");
|
|
24
24
|
}
|
|
25
25
|
registerSlash(options, update = false) {
|
|
26
|
-
if (this.triggerMap.has(options.trigger) && !update)
|
|
27
|
-
|
|
26
|
+
if (this.triggerMap.has(options.trigger) && !update) {
|
|
27
|
+
if (this.kernel.isHotReloadMode()) this.logger.warn(`🔄 Overriding slash trigger "${options.trigger}"`);
|
|
28
|
+
else throw new Error(`Slash trigger "${options.trigger}" is already registered.`);
|
|
29
|
+
}
|
|
28
30
|
this.triggerMap.set(options.trigger, options);
|
|
29
31
|
this.triggerFnMap.set(options.trigger, getBasicTypeaheadTriggerMatch(options.trigger, {
|
|
30
32
|
allowWhitespace: options.allowWhitespace,
|
|
@@ -44,6 +46,18 @@ var SlashService = class {
|
|
|
44
46
|
this.triggerFnMap.clear();
|
|
45
47
|
this.triggerFuseMap.clear();
|
|
46
48
|
}
|
|
49
|
+
resolveTrigger(text) {
|
|
50
|
+
let resolution = null;
|
|
51
|
+
for (const [trigger, triggerFn] of this.triggerFnMap) {
|
|
52
|
+
const match = triggerFn(text);
|
|
53
|
+
if (!match || resolution && match.leadOffset <= resolution.leadOffset) continue;
|
|
54
|
+
resolution = {
|
|
55
|
+
leadOffset: match.leadOffset,
|
|
56
|
+
trigger
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return resolution;
|
|
60
|
+
}
|
|
47
61
|
updateOptions(options) {
|
|
48
62
|
this.removeAllOptions();
|
|
49
63
|
options.forEach((option) => {
|
|
@@ -44,7 +44,7 @@ function tryToPositionRange(leadOffset, range, editorWindow) {
|
|
|
44
44
|
}
|
|
45
45
|
return true;
|
|
46
46
|
}
|
|
47
|
-
const PUNCTUATION = "\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]
|
|
47
|
+
const PUNCTUATION = "\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\!%'\"~=<>_:;";
|
|
48
48
|
function getBasicTypeaheadTriggerMatch(trigger, { minLength = 0, maxLength = 75, punctuation = PUNCTUATION, allowWhitespace = false }) {
|
|
49
49
|
return (text) => {
|
|
50
50
|
const validCharsSuffix = allowWhitespace ? "" : "\\s";
|
|
@@ -134,7 +134,8 @@ const TableActionMenu = memo(({ editor, tableCellNode: _tableCellNode, cellMerge
|
|
|
134
134
|
editor.update(() => {
|
|
135
135
|
const selection = $getSelection();
|
|
136
136
|
if (!$isTableSelection(selection)) return;
|
|
137
|
-
const
|
|
137
|
+
const tableCells = selection.getNodes().filter($isTableCellNode);
|
|
138
|
+
const targetCell = $mergeCells(tableCells);
|
|
138
139
|
if (targetCell) $selectLastDescendant(targetCell);
|
|
139
140
|
});
|
|
140
141
|
};
|
|
@@ -221,73 +222,74 @@ const TableActionMenu = memo(({ editor, tableCellNode: _tableCellNode, cellMerge
|
|
|
221
222
|
tableCellNode,
|
|
222
223
|
clearTableSelection
|
|
223
224
|
]);
|
|
225
|
+
const menuItems = useMemo(() => {
|
|
226
|
+
return [
|
|
227
|
+
{
|
|
228
|
+
icon: PanelTopCloseIcon,
|
|
229
|
+
key: "table-insert-row-above",
|
|
230
|
+
label: t(`table.insertRowAbove`, { count: selectionCounts.rows }),
|
|
231
|
+
onClick: () => insertTableRowAtSelection(false)
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
icon: PanelBottomCloseIcon,
|
|
235
|
+
key: "table-insert-row-below",
|
|
236
|
+
label: t(`table.insertRowBelow`, { count: selectionCounts.rows }),
|
|
237
|
+
onClick: () => insertTableRowAtSelection(true)
|
|
238
|
+
},
|
|
239
|
+
{ type: "divider" },
|
|
240
|
+
{
|
|
241
|
+
icon: PanelLeftCloseIcon,
|
|
242
|
+
key: "table-insert-column-before",
|
|
243
|
+
label: t(`table.insertColumnLeft`, { count: selectionCounts.columns }),
|
|
244
|
+
onClick: () => insertTableColumnAtSelection(false)
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
icon: PanelRightCloseIcon,
|
|
248
|
+
key: "table-insert-column-after",
|
|
249
|
+
label: t(`table.insertColumnRight`, { count: selectionCounts.columns }),
|
|
250
|
+
onClick: () => insertTableColumnAtSelection(true)
|
|
251
|
+
},
|
|
252
|
+
{ type: "divider" },
|
|
253
|
+
{
|
|
254
|
+
icon: TableColumnsSplitIcon,
|
|
255
|
+
key: "table-delete-columns",
|
|
256
|
+
label: renderDeleteMenuLabel(t(`table.deleteColumn`), "columns"),
|
|
257
|
+
onClick: () => deleteTableColumnAtSelection()
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
icon: TableRowsSplitIcon,
|
|
261
|
+
key: "table-delete-rows",
|
|
262
|
+
label: renderDeleteMenuLabel(t(`table.deleteRow`), "rows"),
|
|
263
|
+
onClick: () => deleteTableRowAtSelection()
|
|
264
|
+
},
|
|
265
|
+
{ type: "divider" },
|
|
266
|
+
{
|
|
267
|
+
icon: Grid2X2XIcon,
|
|
268
|
+
key: "table-delete",
|
|
269
|
+
label: renderDeleteMenuLabel(t(`table.delete`), "table"),
|
|
270
|
+
onClick: () => deleteTableAtSelection()
|
|
271
|
+
}
|
|
272
|
+
];
|
|
273
|
+
}, [
|
|
274
|
+
cellMerge,
|
|
275
|
+
canMergeCells,
|
|
276
|
+
canUnmergeCell,
|
|
277
|
+
selectionCounts.rows,
|
|
278
|
+
selectionCounts.columns,
|
|
279
|
+
tableCellNode.__headerState,
|
|
280
|
+
mergeTableCellsAtSelection,
|
|
281
|
+
unmergeTableCellsAtSelection,
|
|
282
|
+
insertTableRowAtSelection,
|
|
283
|
+
insertTableColumnAtSelection,
|
|
284
|
+
deleteTableColumnAtSelection,
|
|
285
|
+
deleteTableRowAtSelection,
|
|
286
|
+
deleteTableAtSelection,
|
|
287
|
+
renderDeleteMenuLabel,
|
|
288
|
+
toggleTableRowIsHeader,
|
|
289
|
+
toggleTableColumnIsHeader
|
|
290
|
+
]);
|
|
224
291
|
return /* @__PURE__ */ jsx(DropdownMenu, {
|
|
225
|
-
items:
|
|
226
|
-
return [
|
|
227
|
-
{
|
|
228
|
-
icon: PanelTopCloseIcon,
|
|
229
|
-
key: "table-insert-row-above",
|
|
230
|
-
label: t(`table.insertRowAbove`, { count: selectionCounts.rows }),
|
|
231
|
-
onClick: () => insertTableRowAtSelection(false)
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
icon: PanelBottomCloseIcon,
|
|
235
|
-
key: "table-insert-row-below",
|
|
236
|
-
label: t(`table.insertRowBelow`, { count: selectionCounts.rows }),
|
|
237
|
-
onClick: () => insertTableRowAtSelection(true)
|
|
238
|
-
},
|
|
239
|
-
{ type: "divider" },
|
|
240
|
-
{
|
|
241
|
-
icon: PanelLeftCloseIcon,
|
|
242
|
-
key: "table-insert-column-before",
|
|
243
|
-
label: t(`table.insertColumnLeft`, { count: selectionCounts.columns }),
|
|
244
|
-
onClick: () => insertTableColumnAtSelection(false)
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
icon: PanelRightCloseIcon,
|
|
248
|
-
key: "table-insert-column-after",
|
|
249
|
-
label: t(`table.insertColumnRight`, { count: selectionCounts.columns }),
|
|
250
|
-
onClick: () => insertTableColumnAtSelection(true)
|
|
251
|
-
},
|
|
252
|
-
{ type: "divider" },
|
|
253
|
-
{
|
|
254
|
-
icon: TableColumnsSplitIcon,
|
|
255
|
-
key: "table-delete-columns",
|
|
256
|
-
label: renderDeleteMenuLabel(t(`table.deleteColumn`), "columns"),
|
|
257
|
-
onClick: () => deleteTableColumnAtSelection()
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
icon: TableRowsSplitIcon,
|
|
261
|
-
key: "table-delete-rows",
|
|
262
|
-
label: renderDeleteMenuLabel(t(`table.deleteRow`), "rows"),
|
|
263
|
-
onClick: () => deleteTableRowAtSelection()
|
|
264
|
-
},
|
|
265
|
-
{ type: "divider" },
|
|
266
|
-
{
|
|
267
|
-
icon: Grid2X2XIcon,
|
|
268
|
-
key: "table-delete",
|
|
269
|
-
label: renderDeleteMenuLabel(t(`table.delete`), "table"),
|
|
270
|
-
onClick: () => deleteTableAtSelection()
|
|
271
|
-
}
|
|
272
|
-
];
|
|
273
|
-
}, [
|
|
274
|
-
cellMerge,
|
|
275
|
-
canMergeCells,
|
|
276
|
-
canUnmergeCell,
|
|
277
|
-
selectionCounts.rows,
|
|
278
|
-
selectionCounts.columns,
|
|
279
|
-
tableCellNode.__headerState,
|
|
280
|
-
mergeTableCellsAtSelection,
|
|
281
|
-
unmergeTableCellsAtSelection,
|
|
282
|
-
insertTableRowAtSelection,
|
|
283
|
-
insertTableColumnAtSelection,
|
|
284
|
-
deleteTableColumnAtSelection,
|
|
285
|
-
deleteTableRowAtSelection,
|
|
286
|
-
deleteTableAtSelection,
|
|
287
|
-
renderDeleteMenuLabel,
|
|
288
|
-
toggleTableRowIsHeader,
|
|
289
|
-
toggleTableColumnIsHeader
|
|
290
|
-
]),
|
|
292
|
+
items: menuItems,
|
|
291
293
|
children
|
|
292
294
|
});
|
|
293
295
|
});
|
|
@@ -47,7 +47,8 @@ const TableColController = memo((props) => {
|
|
|
47
47
|
clearDeletePreview();
|
|
48
48
|
if (!showMenu) return;
|
|
49
49
|
editor.getEditorState().read(() => {
|
|
50
|
-
const
|
|
50
|
+
const tableNode = node.getLatest();
|
|
51
|
+
const [tableMap] = $computeTableMapSkipCellCheck(tableNode, null, null);
|
|
51
52
|
const previewCellKeys = /* @__PURE__ */ new Set();
|
|
52
53
|
for (const row of tableMap) for (const columnIndex of selectedColumns) {
|
|
53
54
|
const cell = row[columnIndex]?.cell;
|
|
@@ -110,32 +111,34 @@ const TableColController = memo((props) => {
|
|
|
110
111
|
}
|
|
111
112
|
}, INSERT_BUTTON_HIDE_DELAY);
|
|
112
113
|
};
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
insertAfter: false
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
return {
|
|
127
|
-
index: columns.length - 1,
|
|
128
|
-
insertAfter: true
|
|
114
|
+
const getColumnDropTarget = useCallback((event) => {
|
|
115
|
+
const controller = colTopRef.current;
|
|
116
|
+
if (!controller) return null;
|
|
117
|
+
const columns = Array.from(controller.querySelectorAll(".col"));
|
|
118
|
+
if (columns.length === 0) return null;
|
|
119
|
+
for (const [index, column] of columns.entries()) {
|
|
120
|
+
const rect = column.getBoundingClientRect();
|
|
121
|
+
if (event.clientX <= rect.left + rect.width / 2) return {
|
|
122
|
+
index,
|
|
123
|
+
insertAfter: false
|
|
129
124
|
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
index: columns.length - 1,
|
|
128
|
+
insertAfter: true
|
|
129
|
+
};
|
|
130
|
+
}, []);
|
|
131
|
+
const moveColumns = useCallback((target, selectedIndexes) => {
|
|
132
|
+
editor.dispatchCommand(MOVE_TABLE_COLUMN_COMMAND, {
|
|
133
|
+
columnIndex: target.index,
|
|
134
|
+
insertAfter: target.insertAfter,
|
|
135
|
+
selectedColumns: selectedIndexes,
|
|
136
|
+
table: node.getKey()
|
|
137
|
+
});
|
|
138
|
+
}, [editor, node]);
|
|
139
|
+
const { clearDragState, dragTarget, finishDrag: finishColumnDrag, isDragging, startDrag } = useTableAxisDrag({
|
|
140
|
+
getDropTarget: getColumnDropTarget,
|
|
141
|
+
onMove: moveColumns
|
|
139
142
|
});
|
|
140
143
|
const startColumnDrag = useCallback((event, selectedIndexes) => {
|
|
141
144
|
event.dataTransfer.effectAllowed = "move";
|
|
@@ -47,7 +47,8 @@ const TableRowController = memo((props) => {
|
|
|
47
47
|
clearDeletePreview();
|
|
48
48
|
if (!showMenu) return;
|
|
49
49
|
editor.getEditorState().read(() => {
|
|
50
|
-
const
|
|
50
|
+
const tableNode = node.getLatest();
|
|
51
|
+
const [tableMap] = $computeTableMapSkipCellCheck(tableNode, null, null);
|
|
51
52
|
const previewCellKeys = /* @__PURE__ */ new Set();
|
|
52
53
|
for (const rowIndex of selectedRows) {
|
|
53
54
|
const row = tableMap[rowIndex];
|
|
@@ -108,32 +109,34 @@ const TableRowController = memo((props) => {
|
|
|
108
109
|
if (!insertButtonHoveredRef.current) setInsertTarget(null);
|
|
109
110
|
}, INSERT_BUTTON_HIDE_DELAY);
|
|
110
111
|
};
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
insertAfter: false
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
return {
|
|
125
|
-
index: rows.length - 1,
|
|
126
|
-
insertAfter: true
|
|
112
|
+
const getRowDropTarget = useCallback((event) => {
|
|
113
|
+
const controller = rowLeftRef.current;
|
|
114
|
+
if (!controller) return null;
|
|
115
|
+
const rows = Array.from(controller.querySelectorAll(".row"));
|
|
116
|
+
if (rows.length === 0) return null;
|
|
117
|
+
for (const [index, row] of rows.entries()) {
|
|
118
|
+
const rect = row.getBoundingClientRect();
|
|
119
|
+
if (event.clientY <= rect.top + rect.height / 2) return {
|
|
120
|
+
index,
|
|
121
|
+
insertAfter: false
|
|
127
122
|
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
index: rows.length - 1,
|
|
126
|
+
insertAfter: true
|
|
127
|
+
};
|
|
128
|
+
}, []);
|
|
129
|
+
const moveRows = useCallback((target, selectedIndexes) => {
|
|
130
|
+
editor.dispatchCommand(MOVE_TABLE_ROW_COMMAND, {
|
|
131
|
+
insertAfter: target.insertAfter,
|
|
132
|
+
rowIndex: target.index,
|
|
133
|
+
selectedRows: selectedIndexes,
|
|
134
|
+
table: node.getKey()
|
|
135
|
+
});
|
|
136
|
+
}, [editor, node]);
|
|
137
|
+
const { clearDragState, dragTarget, finishDrag: finishRowDrag, isDragging, startDrag } = useTableAxisDrag({
|
|
138
|
+
getDropTarget: getRowDropTarget,
|
|
139
|
+
onMove: moveRows
|
|
137
140
|
});
|
|
138
141
|
const startRowDrag = useCallback((event, selectedIndexes) => {
|
|
139
142
|
event.dataTransfer.effectAllowed = "move";
|
|
@@ -63,12 +63,14 @@ function $forEachTableCell(grid, cb) {
|
|
|
63
63
|
function $addHighlightToDOM(editor, cell) {
|
|
64
64
|
const element = cell.elem;
|
|
65
65
|
const editorThemeClasses = editor._config.theme;
|
|
66
|
-
|
|
66
|
+
const node = $getNearestNodeFromDOMNode(element);
|
|
67
|
+
assert($isTableCellNode(node), "Expected to find LexicalNode from Table Cell DOMNode");
|
|
67
68
|
addClassNamesToElement(element, editorThemeClasses.tableCellSelected);
|
|
68
69
|
}
|
|
69
70
|
function $removeHighlightFromDOM(editor, cell) {
|
|
70
71
|
const element = cell.elem;
|
|
71
|
-
|
|
72
|
+
const node = $getNearestNodeFromDOMNode(element);
|
|
73
|
+
assert($isTableCellNode(node), "Expected to find LexicalNode from Table Cell DOMNode");
|
|
72
74
|
const editorThemeClasses = editor._config.theme;
|
|
73
75
|
removeClassNamesFromElement(element, editorThemeClasses.tableCellSelected);
|
|
74
76
|
}
|