@lobehub/editor 4.20.2 → 4.21.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/components/Toolbar.js +2 -1
- package/es/codemirror/constants.js +6 -0
- package/es/codemirror/types.d.ts +4 -0
- package/es/headless.d.ts +1 -0
- package/es/headless.js +7 -0
- package/es/locale/index.d.ts +1 -0
- package/es/locale/index.js +1 -0
- package/es/plugins/codemirror-block/react/CodemirrorNode.js +45 -22
- package/es/plugins/codemirror-block/react/MermaidPreview.js +47 -0
- package/es/plugins/toolbar/react/index.js +59 -5
- package/es/plugins/toolbar/react/style.js +8 -0
- package/es/plugins/toolbar/react/type.d.ts +4 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import { ActionIcon, Flexbox } from "@lobehub/ui";
|
|
|
6
6
|
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/codemirror/components/Toolbar.tsx
|
|
9
|
-
const Toolbar = ({ selectedLang, onLanguageChange, onCopy, tabSize, onTabSizeChange, useTabs, onUseTabsChange, showLineNumbers, onShowLineNumbersChange, onClick, expand, toggleExpand, labels, languageOptions }) => {
|
|
9
|
+
const Toolbar = ({ selectedLang, onLanguageChange, onCopy, tabSize, onTabSizeChange, useTabs, onUseTabsChange, showLineNumbers, onShowLineNumbersChange, onClick, expand, toggleExpand, labels, languageOptions, extra }) => {
|
|
10
10
|
return /* @__PURE__ */ jsxs(Flexbox, {
|
|
11
11
|
align: "center",
|
|
12
12
|
className: "cm-header-toolbar",
|
|
@@ -24,6 +24,7 @@ const Toolbar = ({ selectedLang, onLanguageChange, onCopy, tabSize, onTabSizeCha
|
|
|
24
24
|
horizontal: true,
|
|
25
25
|
onClick: (e) => e.stopPropagation(),
|
|
26
26
|
children: [
|
|
27
|
+
extra,
|
|
27
28
|
/* @__PURE__ */ jsx(MoreOptions, {
|
|
28
29
|
labels,
|
|
29
30
|
onShowLineNumbersChange,
|
package/es/codemirror/types.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
|
|
1
3
|
//#region src/codemirror/types.d.ts
|
|
2
4
|
interface CodeMirrorMode {
|
|
3
5
|
ext?: string[];
|
|
@@ -7,6 +9,7 @@ interface CodeMirrorMode {
|
|
|
7
9
|
}
|
|
8
10
|
interface CodeMirrorLabels {
|
|
9
11
|
copy?: string;
|
|
12
|
+
preview?: string;
|
|
10
13
|
selectLanguage?: string;
|
|
11
14
|
showLineNumbers?: string;
|
|
12
15
|
tabSize?: string;
|
|
@@ -35,6 +38,7 @@ interface MoreOptionsProps {
|
|
|
35
38
|
}
|
|
36
39
|
interface ToolbarProps {
|
|
37
40
|
expand?: boolean;
|
|
41
|
+
extra?: ReactNode;
|
|
38
42
|
labels?: CodeMirrorLabels;
|
|
39
43
|
languageOptions?: CodeMirrorMode[];
|
|
40
44
|
onClick?: () => void;
|
package/es/headless.d.ts
CHANGED
package/es/headless.js
CHANGED
|
@@ -28,6 +28,7 @@ var locale_default = {
|
|
|
28
28
|
cancel: "Cancel",
|
|
29
29
|
codemirror: {
|
|
30
30
|
copySuccess: "Code copied to clipboard",
|
|
31
|
+
preview: "Preview",
|
|
31
32
|
selectLanguage: "Select language",
|
|
32
33
|
selectTheme: "Select theme",
|
|
33
34
|
showLineNumbers: "Show Line Numbers",
|
|
@@ -5449,6 +5450,12 @@ const LANGUAGES = [
|
|
|
5449
5450
|
syntax: "markdown",
|
|
5450
5451
|
value: "markdown"
|
|
5451
5452
|
},
|
|
5453
|
+
{
|
|
5454
|
+
ext: ["mermaid", "mmd"],
|
|
5455
|
+
name: "Mermaid",
|
|
5456
|
+
syntax: "simplemode",
|
|
5457
|
+
value: "mermaid"
|
|
5458
|
+
},
|
|
5452
5459
|
{
|
|
5453
5460
|
name: "MATLAB",
|
|
5454
5461
|
syntax: "octave",
|
package/es/locale/index.d.ts
CHANGED
package/es/locale/index.js
CHANGED
|
@@ -6,7 +6,9 @@ import { lobeTheme } from "../../../codemirror/theme.js";
|
|
|
6
6
|
import { useTranslation } from "../../../editor-kernel/react/useTranslation.js";
|
|
7
7
|
import { SELECT_AFTER_CODEMIRROR_COMMAND, SELECT_BEFORE_CODEMIRROR_COMMAND } from "../command/index.js";
|
|
8
8
|
import { useLexicalNodeSelection } from "../../../editor-kernel/react/useLexicalNodeSelection.js";
|
|
9
|
-
import
|
|
9
|
+
import MermaidPreview from "./MermaidPreview.js";
|
|
10
|
+
import { ActionIcon, Block } from "@lobehub/ui";
|
|
11
|
+
import { CodeXml, Eye } from "lucide-react";
|
|
10
12
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
11
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
14
|
import { cx } from "antd-style";
|
|
@@ -26,6 +28,8 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
26
28
|
const [useTabs, setUseTabs] = useState(node.options.indentWithTabs ?? false);
|
|
27
29
|
const [showLineNumbers, setShowLineNumbers] = useState(node.options.lineNumbers ?? false);
|
|
28
30
|
const [expand, setExpand] = useState(true);
|
|
31
|
+
const [preview, setPreview] = useState(false);
|
|
32
|
+
const [code, setCode] = useState(node.code);
|
|
29
33
|
const handleCopy = useCallback(async () => {
|
|
30
34
|
if (instanceRef.current) {
|
|
31
35
|
const code = instanceRef.current.getValue();
|
|
@@ -35,6 +39,7 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
35
39
|
}
|
|
36
40
|
}, []);
|
|
37
41
|
const labels = useMemo(() => ({
|
|
42
|
+
preview: t("codemirror.preview"),
|
|
38
43
|
selectLanguage: t("codemirror.selectLanguage"),
|
|
39
44
|
showLineNumbers: t("codemirror.showLineNumbers"),
|
|
40
45
|
tabSize: t("codemirror.tabSize"),
|
|
@@ -42,6 +47,7 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
42
47
|
}), [t]);
|
|
43
48
|
const handleLanguageChange = useCallback((value) => {
|
|
44
49
|
setSelectedLang(value);
|
|
50
|
+
if (value !== "mermaid") setPreview(false);
|
|
45
51
|
if (instanceRef.current) instanceRef.current.setOption("mode", value);
|
|
46
52
|
editor.update(() => {
|
|
47
53
|
node.setLang(value);
|
|
@@ -145,7 +151,9 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
145
151
|
});
|
|
146
152
|
});
|
|
147
153
|
instance.on("change", () => {
|
|
148
|
-
|
|
154
|
+
const currentValue = instance.getValue();
|
|
155
|
+
isEmptyRef.current = !currentValue.trim();
|
|
156
|
+
setCode(currentValue);
|
|
149
157
|
});
|
|
150
158
|
instance.on("change", debounce(() => {
|
|
151
159
|
const currentValue = instance.getValue();
|
|
@@ -191,27 +199,42 @@ const ReactCodemirrorNode = ({ node, className, editor }) => {
|
|
|
191
199
|
onMouseUp: (e) => e.stopPropagation(),
|
|
192
200
|
onSelect: (e) => e.stopPropagation(),
|
|
193
201
|
variant: "filled",
|
|
194
|
-
children: [
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
202
|
+
children: [
|
|
203
|
+
/* @__PURE__ */ jsx(Toolbar, {
|
|
204
|
+
expand,
|
|
205
|
+
extra: selectedLang === "mermaid" ? /* @__PURE__ */ jsx(ActionIcon, {
|
|
206
|
+
className: "cm-hidden-actions",
|
|
207
|
+
icon: preview ? CodeXml : Eye,
|
|
208
|
+
onClick: () => setPreview(!preview),
|
|
209
|
+
size: "small",
|
|
210
|
+
title: labels.preview
|
|
211
|
+
}) : void 0,
|
|
212
|
+
labels,
|
|
213
|
+
onClick: () => setExpand(!expand),
|
|
214
|
+
onCopy: handleCopy,
|
|
215
|
+
onLanguageChange: handleLanguageChange,
|
|
216
|
+
onShowLineNumbersChange: handleShowLineNumbersChange,
|
|
217
|
+
onTabSizeChange: handleTabSizeChange,
|
|
218
|
+
onUseTabsChange: handleUseTabsChange,
|
|
219
|
+
selectedLang,
|
|
220
|
+
showLineNumbers,
|
|
221
|
+
tabSize,
|
|
222
|
+
toggleExpand: () => setExpand(!expand),
|
|
223
|
+
useTabs
|
|
224
|
+
}),
|
|
225
|
+
/* @__PURE__ */ jsx("div", {
|
|
226
|
+
className: cx("cm-container", (!expand || preview && selectedLang === "mermaid") && "cm-container-collapsed"),
|
|
227
|
+
children: /* @__PURE__ */ jsx("textarea", {
|
|
228
|
+
className: "cm-textarea",
|
|
229
|
+
ref
|
|
230
|
+
})
|
|
231
|
+
}),
|
|
232
|
+
expand && preview && selectedLang === "mermaid" && /* @__PURE__ */ jsx("div", {
|
|
233
|
+
className: "cm-container",
|
|
234
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
235
|
+
children: /* @__PURE__ */ jsx(MermaidPreview, { code })
|
|
213
236
|
})
|
|
214
|
-
|
|
237
|
+
]
|
|
215
238
|
});
|
|
216
239
|
};
|
|
217
240
|
ReactCodemirrorNode.displayName = "ReactCodemirrorNode";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Mermaid } from "@lobehub/ui";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { cssVar } from "antd-style";
|
|
6
|
+
//#region src/plugins/codemirror-block/react/MermaidPreview.tsx
|
|
7
|
+
const MermaidPreview = ({ code }) => {
|
|
8
|
+
const [error, setError] = useState(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!code.trim()) {
|
|
11
|
+
setError(null);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
let cancelled = false;
|
|
15
|
+
import("mermaid").then(async (mod) => {
|
|
16
|
+
try {
|
|
17
|
+
await mod.default.parse(code);
|
|
18
|
+
if (!cancelled) setError(null);
|
|
19
|
+
} catch (error_) {
|
|
20
|
+
if (!cancelled) setError(error_ instanceof Error ? error_.message : String(error_));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return () => {
|
|
24
|
+
cancelled = true;
|
|
25
|
+
};
|
|
26
|
+
}, [code]);
|
|
27
|
+
if (!code.trim()) return null;
|
|
28
|
+
if (error) return /* @__PURE__ */ jsx("div", {
|
|
29
|
+
style: {
|
|
30
|
+
color: cssVar.colorTextDescription,
|
|
31
|
+
fontFamily: cssVar.fontFamilyCode,
|
|
32
|
+
fontSize: 12,
|
|
33
|
+
padding: 16,
|
|
34
|
+
whiteSpace: "pre-wrap"
|
|
35
|
+
},
|
|
36
|
+
children: error
|
|
37
|
+
});
|
|
38
|
+
return /* @__PURE__ */ jsx(Mermaid, {
|
|
39
|
+
animated: false,
|
|
40
|
+
theme: "lobe-theme",
|
|
41
|
+
variant: "borderless",
|
|
42
|
+
children: code
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
MermaidPreview.displayName = "MermaidPreview";
|
|
46
|
+
//#endregion
|
|
47
|
+
export { MermaidPreview as default };
|
|
@@ -6,21 +6,36 @@ import { HIDE_TOOLBAR_COMMAND, registerToolbarCommand } from "../command/index.j
|
|
|
6
6
|
import { getDOMRangeRect } from "../utils/getDOMRangeRect.js";
|
|
7
7
|
import { setFloatingElemPosition } from "../utils/setFloatingElemPosition.js";
|
|
8
8
|
import { styles } from "./style.js";
|
|
9
|
-
import { Block } from "@lobehub/ui";
|
|
10
|
-
import { useCallback, useRef } from "react";
|
|
9
|
+
import { Block, LOBE_THEME_APP_ID } from "@lobehub/ui";
|
|
10
|
+
import { useCallback, useRef, useState } from "react";
|
|
11
11
|
import { jsx } from "react/jsx-runtime";
|
|
12
12
|
import { cx, useThemeMode } from "antd-style";
|
|
13
13
|
import { $getSelection, COMMAND_PRIORITY_LOW, SELECTION_CHANGE_COMMAND, getDOMSelection } from "lexical";
|
|
14
14
|
import { mergeRegister } from "@lexical/utils";
|
|
15
|
+
import { createPortal } from "react-dom";
|
|
15
16
|
//#region src/plugins/toolbar/react/index.tsx
|
|
16
17
|
init_debug();
|
|
17
|
-
const
|
|
18
|
+
const resolveDefaultPortalContainer = () => {
|
|
19
|
+
if (typeof document === "undefined") return null;
|
|
20
|
+
return document.getElementById(LOBE_THEME_APP_ID) ?? document.body;
|
|
21
|
+
};
|
|
22
|
+
const ReactToolbarPlugin = ({ className, children, getPopupContainer, usePortal = true, zIndex }) => {
|
|
18
23
|
const popupCharStylesEditorRef = useRef(null);
|
|
19
24
|
const anchorElemRef = useRef(null);
|
|
25
|
+
const [portalContainer, setPortalContainer] = useState(null);
|
|
20
26
|
const [kernelEditor] = useLexicalComposerContext();
|
|
21
27
|
const { isDarkMode } = useThemeMode();
|
|
22
28
|
const isMouseDownRef = useRef(false);
|
|
23
29
|
const logger = createDebugLogger("plugin", "toolbar");
|
|
30
|
+
const resolvePortalContainer = useCallback(() => {
|
|
31
|
+
if (!usePortal) return null;
|
|
32
|
+
if (getPopupContainer) return getPopupContainer();
|
|
33
|
+
return resolveDefaultPortalContainer();
|
|
34
|
+
}, [getPopupContainer, usePortal]);
|
|
35
|
+
const syncPortalContainer = useCallback(() => {
|
|
36
|
+
const nextContainer = resolvePortalContainer();
|
|
37
|
+
setPortalContainer((current) => current === nextContainer ? current : nextContainer);
|
|
38
|
+
}, [resolvePortalContainer]);
|
|
24
39
|
const $updateTextFormatFloatingToolbar = useCallback((editor) => {
|
|
25
40
|
if (!anchorElemRef.current) return;
|
|
26
41
|
const selection = $getSelection();
|
|
@@ -65,6 +80,14 @@ const ReactToolbarPlugin = ({ className, children }) => {
|
|
|
65
80
|
};
|
|
66
81
|
}
|
|
67
82
|
}, []);
|
|
83
|
+
useLexicalEditor((editor) => {
|
|
84
|
+
if (!usePortal) {
|
|
85
|
+
setPortalContainer(null);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
syncPortalContainer();
|
|
89
|
+
return editor.registerRootListener(syncPortalContainer);
|
|
90
|
+
}, [syncPortalContainer, usePortal]);
|
|
68
91
|
useLexicalEditor((editor) => {
|
|
69
92
|
const handleMouseDown = handleMouseDownFactory(() => {
|
|
70
93
|
editor.dispatchCommand(HIDE_TOOLBAR_COMMAND, void 0);
|
|
@@ -75,10 +98,32 @@ const ReactToolbarPlugin = ({ className, children }) => {
|
|
|
75
98
|
});
|
|
76
99
|
});
|
|
77
100
|
const rootElement = editor.getRootElement();
|
|
101
|
+
const editorWindow = editor._window ?? rootElement?.ownerDocument.defaultView ?? globalThis.window ?? null;
|
|
102
|
+
let animationFrameId = null;
|
|
103
|
+
const updateToolbarPosition = () => {
|
|
104
|
+
if (!editorWindow || isMouseDownRef.current || animationFrameId !== null) return;
|
|
105
|
+
animationFrameId = editorWindow.requestAnimationFrame(() => {
|
|
106
|
+
animationFrameId = null;
|
|
107
|
+
editor.getEditorState().read(() => {
|
|
108
|
+
$updateTextFormatFloatingToolbar(editor);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
const scrollListenerOptions = {
|
|
113
|
+
capture: true,
|
|
114
|
+
passive: true
|
|
115
|
+
};
|
|
116
|
+
const scrollCleanupOptions = { capture: true };
|
|
117
|
+
const resizeListenerOptions = { passive: true };
|
|
78
118
|
if (rootElement) {
|
|
79
119
|
rootElement.addEventListener("mousedown", handleMouseDown);
|
|
80
120
|
document.addEventListener("mouseup", handleMouseUp);
|
|
81
121
|
}
|
|
122
|
+
if (editorWindow) {
|
|
123
|
+
editorWindow.addEventListener("scroll", updateToolbarPosition, scrollListenerOptions);
|
|
124
|
+
editorWindow.document.addEventListener("scroll", updateToolbarPosition, scrollListenerOptions);
|
|
125
|
+
editorWindow.addEventListener("resize", updateToolbarPosition, resizeListenerOptions);
|
|
126
|
+
}
|
|
82
127
|
return mergeRegister(registerToolbarCommand(editor, { onHide: $hideFloatingToolbar }), editor.registerUpdateListener(({ editorState }) => {
|
|
83
128
|
if (!isMouseDownRef.current) editorState.read(() => {
|
|
84
129
|
$updateTextFormatFloatingToolbar(editor);
|
|
@@ -91,11 +136,18 @@ const ReactToolbarPlugin = ({ className, children }) => {
|
|
|
91
136
|
rootElement.removeEventListener("mousedown", handleMouseDown);
|
|
92
137
|
document.removeEventListener("mouseup", handleMouseUp);
|
|
93
138
|
}
|
|
139
|
+
if (editorWindow) {
|
|
140
|
+
editorWindow.removeEventListener("scroll", updateToolbarPosition, scrollCleanupOptions);
|
|
141
|
+
editorWindow.document.removeEventListener("scroll", updateToolbarPosition, scrollCleanupOptions);
|
|
142
|
+
editorWindow.removeEventListener("resize", updateToolbarPosition);
|
|
143
|
+
if (animationFrameId !== null) editorWindow.cancelAnimationFrame(animationFrameId);
|
|
144
|
+
}
|
|
94
145
|
});
|
|
95
146
|
});
|
|
96
|
-
|
|
97
|
-
className: styles.anchor,
|
|
147
|
+
const toolbarNode = /* @__PURE__ */ jsx("div", {
|
|
148
|
+
className: usePortal ? styles.portalAnchor : styles.anchor,
|
|
98
149
|
ref: anchorElemRef,
|
|
150
|
+
style: usePortal && zIndex !== void 0 ? { zIndex } : void 0,
|
|
99
151
|
children: /* @__PURE__ */ jsx(Block, {
|
|
100
152
|
className: cx(isDarkMode ? styles.toolbarDark : styles.toolbarLight, className),
|
|
101
153
|
padding: 4,
|
|
@@ -104,6 +156,8 @@ const ReactToolbarPlugin = ({ className, children }) => {
|
|
|
104
156
|
children
|
|
105
157
|
})
|
|
106
158
|
});
|
|
159
|
+
if (usePortal) return portalContainer ? createPortal(toolbarNode, portalContainer) : null;
|
|
160
|
+
return toolbarNode;
|
|
107
161
|
};
|
|
108
162
|
//#endregion
|
|
109
163
|
export { ReactToolbarPlugin };
|
|
@@ -3,8 +3,15 @@ import { createStaticStyles } from "antd-style";
|
|
|
3
3
|
const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
4
4
|
anchor: css`
|
|
5
5
|
position: relative;
|
|
6
|
+
`,
|
|
7
|
+
portalAnchor: css`
|
|
8
|
+
pointer-events: none;
|
|
9
|
+
position: fixed;
|
|
10
|
+
z-index: 1100;
|
|
11
|
+
inset: 0;
|
|
6
12
|
`,
|
|
7
13
|
toolbarDark: css`
|
|
14
|
+
pointer-events: auto;
|
|
8
15
|
will-change: transform;
|
|
9
16
|
|
|
10
17
|
position: absolute;
|
|
@@ -28,6 +35,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
|
|
|
28
35
|
transition: opacity 0.12s ${cssVar.motionEaseOut};
|
|
29
36
|
`,
|
|
30
37
|
toolbarLight: css`
|
|
38
|
+
pointer-events: auto;
|
|
31
39
|
will-change: transform;
|
|
32
40
|
|
|
33
41
|
position: absolute;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { ReactNode } from "react";
|
|
1
|
+
import { CSSProperties, ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
//#region src/plugins/toolbar/react/type.d.ts
|
|
4
4
|
interface ReactToolbarPluginProps {
|
|
5
5
|
children?: ReactNode;
|
|
6
6
|
className?: string;
|
|
7
|
+
getPopupContainer?: () => HTMLElement | null;
|
|
8
|
+
usePortal?: boolean;
|
|
9
|
+
zIndex?: CSSProperties['zIndex'];
|
|
7
10
|
}
|
|
8
11
|
//#endregion
|
|
9
12
|
export { ReactToolbarPluginProps };
|
package/package.json
CHANGED