Skip to content

Commit e9fc19c

Browse files
committed
feat: md update v5
1 parent 73a177c commit e9fc19c

File tree

9 files changed

+406
-43
lines changed

9 files changed

+406
-43
lines changed

bun.lockb

2.06 KB
Binary file not shown.

client/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"@radix-ui/themes": "^2.0.3",
4848
"@tailwindcss/typography": "^0.5.13",
4949
"@types/react-modal": "^3.16.3",
50-
"@vavt/rt-extension": "^1.2.4",
50+
"@vavt/rt-extension": "^1.3.3",
5151
"class-variance-authority": "^0.7.0",
5252
"clsx": "^2.1.1",
5353
"colorthief": "^2.4.0",
@@ -59,9 +59,10 @@
5959
"i18next-http-backend": "^2.5.2",
6060
"jotai": "^2.9.0",
6161
"kbar": "^0.1.0-beta.45",
62+
"lodash": "^4.17.21",
6263
"lodash.template": "^4.5.0",
6364
"lucide-react": "^0.408.0",
64-
"md-editor-rt": "^4.18.1",
65+
"md-editor-rt": "^5.1.1",
6566
"primereact": "^10.6.6",
6667
"react": "^18.2.0",
6768
"react-dom": "^18.2.0",

client/src/components/markdown/MarkDownEditor.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import {MdEditor} from "md-editor-rt";
1+
import {ExposeParam, MdEditor} from "md-editor-rt";
22
import "md-editor-rt/lib/style.css";
3-
import {useEffect, useState} from "react";
3+
import {useEffect, useRef, useState} from "react";
44
import {toolbars, codeThemeOptions, previewThemeOptions} from "./config";
55
import {Emoji, ExportPDF, Mark} from "@vavt/rt-extension";
66
import i18n from 'i18next';
@@ -10,6 +10,7 @@ import {useConfig} from "../../store/useConfig";
1010
import {TimeNow} from "../time-now";
1111
import {client} from "../../main";
1212
import {headersWithAuth} from "../../utils/auth";
13+
import useIsMobile from "../../hooks/use-is-mobile";
1314

1415
interface IMdEditorState {
1516
lang?: string;
@@ -37,6 +38,8 @@ interface IMdEditorState {
3738
export default function MdEditorEx({text, theme, previewTheme, codeTheme, onContentChange}) {
3839
const [config] = useConfig();
3940
const {t, i18n} = useTranslation();
41+
const editorRef = useRef<ExposeParam>();
42+
4043
const initState = {
4144
lang: i18n.language as string,
4245
text: "",
@@ -144,7 +147,8 @@ export default function MdEditorEx({text, theme, previewTheme, codeTheme, onCont
144147
return (
145148
<div className="h-screen w-full relative z-1">
146149
<MdEditor
147-
modelValue={text}
150+
ref={editorRef}
151+
value={text}
148152
theme={theme}
149153
onUploadImg={onUploadImg}
150154
onChange={onContentChange}
@@ -153,7 +157,7 @@ export default function MdEditorEx({text, theme, previewTheme, codeTheme, onCont
153157
codeTheme={codeTheme}
154158
toolbars={toolbars}
155159
defToolbars={[
156-
<Emoji key={1}/>,
160+
<Emoji theme={theme} key={1}/>,
157161
<Mark key={2}/>,
158162
<ExportPDF key={3} modelValue={text} height="100vh"/>,
159163
<CodeTheme key={4}/>,
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import {ExposeParam, MdEditor, Footers} from "md-editor-rt";
2+
import React, {useCallback, useEffect, useMemo, useRef, useState} from "react";
3+
import {toolbars, codeThemeOptions, previewThemeOptions} from "./config";
4+
import {Emoji, ExportPDF, Mark} from "@vavt/rt-extension";
5+
import i18n from 'i18next';
6+
import {useTranslation} from "react-i18next";
7+
import {useConfig} from "../../store/useConfig";
8+
import {TimeNow} from "../time-now";
9+
import {client} from "../../main";
10+
import {headersWithAuth} from "../../utils/auth";
11+
import useIsMobile from "../../hooks/use-is-mobile";
12+
import {Helmet} from "react-helmet";
13+
import 'md-editor-rt/lib/style.css';
14+
15+
interface IMdEditorState {
16+
lang?: string;
17+
md?: string;
18+
theme?: "light" | "dark";
19+
previewTheme?:
20+
| "default"
21+
| "github"
22+
| "vuepress"
23+
| "mk-cute"
24+
| "smart-blue"
25+
| "cyanosis"
26+
| "arknights";
27+
codeTheme?:
28+
| "atom"
29+
| "a11y"
30+
| "github"
31+
| "gradient"
32+
| "kimbie"
33+
| "paraiso"
34+
| "qtcreator"
35+
| "stackoverflow";
36+
}
37+
38+
const editorId = 'editor-preview';
39+
40+
export default function MdEditorExV5({ mdText, onContentChange }: { mdText: string; onContentChange: (val: string) => void }) {
41+
const [config] = useConfig();
42+
const {t, i18n} = useTranslation();
43+
const editorRef = useRef<ExposeParam>();
44+
const [md, setMd] = useState(mdText||"");
45+
const isMobile = useIsMobile();
46+
47+
const [state, setState] = useState({
48+
lang: i18n.language === "en" ? "en-US" : i18n.language,
49+
md: "",
50+
theme: config?.mode || 'light',
51+
codeTheme: "",
52+
previewTheme: "",
53+
});
54+
const [isDebug, setIsDebug] = useState(() => {
55+
return false;
56+
});
57+
const [ufToolbars, setToolbars] = useState(toolbars);
58+
const [inputBoxWidth, setInputBoxWidth] = useState('50%');
59+
60+
const { SITE_NAME, KEYWORDS, DESCRIPTION } = useMemo(() => {
61+
return {
62+
SITE_NAME: `prop's blog`,
63+
KEYWORDS: 'md',
64+
DESCRIPTION: 'writing md',
65+
};
66+
}, []);
67+
68+
const changeLayout = useCallback(() => {
69+
if (isMobile) {
70+
// 在移动端不现实分屏预览,要么编辑,要么仅预览
71+
setToolbars(() => {
72+
const t = toolbars.filter(
73+
(item) =>
74+
!(['preview', 'previewOnly'] as Array<string | number>).includes(
75+
item
76+
)
77+
);
78+
79+
return ['previewOnly', ...t];
80+
});
81+
setInputBoxWidth('100%');
82+
editorRef.current?.togglePreview(false);
83+
} else {
84+
setToolbars(toolbars);
85+
setInputBoxWidth('50%');
86+
editorRef.current?.togglePreview(true);
87+
}
88+
}, []);
89+
90+
useEffect(() => {
91+
setState({
92+
...state,
93+
theme: config?.mode,
94+
lang: i18n.language === "en" ? "en-US" : i18n.language,
95+
});
96+
}, [i18n.language, config?.mode]);
97+
98+
let closrRatio = () => {};
99+
let updateRatio: ((str: string) => void) | undefined;
100+
101+
const onProgress = ({ ratio }: { ratio: number }) => {
102+
if (updateRatio) {
103+
updateRatio(`Progress: ${ratio * 100}%`);
104+
} else {
105+
// `Progress: ${ratio * 100}%`
106+
}
107+
};
108+
109+
const onSuccess = () => {
110+
closrRatio();
111+
112+
setTimeout(() => {
113+
updateRatio = undefined;
114+
}, 100);
115+
116+
// message.success('Export successful.', {
117+
// zIndex: 999999,
118+
// });
119+
};
120+
121+
const PreviewTheme = () => {
122+
return (
123+
<>
124+
<select
125+
value={state.previewTheme}
126+
onChange={(event) => {
127+
setState({
128+
...state,
129+
previewTheme: event.target
130+
.value as IMdEditorState["previewTheme"],
131+
});
132+
}}
133+
>
134+
{previewThemeOptions.map((preview) => (
135+
<option key={preview.value} value={preview.value}>
136+
{preview.label}
137+
</option>
138+
))}
139+
</select>
140+
</>
141+
);
142+
};
143+
144+
const CodeTheme = () => {
145+
return (
146+
<>
147+
<select
148+
value={state.codeTheme}
149+
onChange={(event) => {
150+
setState({
151+
...state,
152+
codeTheme: event.target.value as IMdEditorState["codeTheme"],
153+
});
154+
}}
155+
>
156+
{codeThemeOptions.map((code) => (
157+
<option key={code.value} value={code.value}>
158+
{code.label}
159+
</option>
160+
))}
161+
</select>
162+
</>
163+
);
164+
};
165+
166+
const onUploadImg = async (files, callback) => {
167+
const res = await Promise.all(
168+
files.map((file) => {
169+
return new Promise((rev, rej) => {
170+
client.storage.index
171+
.post(
172+
{
173+
key: file.name,
174+
file: file,
175+
},
176+
{
177+
headers: headersWithAuth(),
178+
}
179+
)
180+
.then(({data, error}) => {
181+
if (error) {
182+
rej(error)
183+
}
184+
if (data) {
185+
rev(data);
186+
}
187+
})
188+
.catch((e: any) => {
189+
rej(e)
190+
});
191+
});
192+
})
193+
);
194+
195+
callback(res.map((item) => item));
196+
197+
};
198+
199+
useEffect(() => {
200+
if (isDebug) {
201+
editorRef.current?.on('catalog', (v) => {
202+
console.log('catalog', v);
203+
});
204+
editorRef.current?.on('fullscreen', (v) => {
205+
console.log('fullscreen', v);
206+
});
207+
editorRef.current?.on('htmlPreview', (v) => {
208+
console.log('htmlPreview', v);
209+
});
210+
editorRef.current?.on('pageFullscreen', (v) => {
211+
console.log('pageFullscreen', v);
212+
});
213+
editorRef.current?.on('preview', (v) => {
214+
console.log('preview', v);
215+
});
216+
217+
// window.editorInstance = editorRef.current;
218+
}
219+
220+
editorRef.current?.on('previewOnly', (v) => {
221+
if (isMobile) {
222+
if (!v) {
223+
editorRef.current?.togglePreview(false);
224+
}
225+
}
226+
});
227+
228+
changeLayout();
229+
230+
window.addEventListener('resize', changeLayout);
231+
}, [changeLayout, isDebug]);
232+
233+
const footers: Footers[] = ['markdownTotal', '=', 0, 'scrollSwitch'];
234+
235+
const defFooters = useMemo(() => {
236+
return [<TimeNow key="time-now" />];
237+
}, []);
238+
239+
const onSave = useCallback((v: string, h: Promise<string>) => {
240+
console.log('v', v);
241+
242+
h.then((html) => {
243+
console.log('h', html);
244+
});
245+
}, []);
246+
247+
return (
248+
<div className="h-screen w-full relative z-1">
249+
<Helmet>
250+
<title>{SITE_NAME}</title>
251+
<meta name="keywords" content={KEYWORDS} />
252+
<meta name="description" content={DESCRIPTION} />
253+
<meta name="viewport" content="width=device-width, initial-scale=1" />
254+
</Helmet>
255+
<MdEditor
256+
language={state?.lang}
257+
ref={editorRef}
258+
theme={state?.theme}
259+
previewTheme={state?.previewTheme}
260+
inputBoxWidth={inputBoxWidth}
261+
codeTheme={state?.codeTheme}
262+
value={md}
263+
id={editorId}
264+
autoDetectCode
265+
defToolbars={[
266+
<Mark key="mark-extension" />,
267+
<Emoji key="emoji-extension" />,
268+
<ExportPDF
269+
key="ExportPDF"
270+
modelValue={md}
271+
height="700px"
272+
onProgress={onProgress}
273+
onSuccess={onSuccess}
274+
/>,
275+
<CodeTheme key="code-theme"/>,
276+
<PreviewTheme key="preview-theme"/>,
277+
]}
278+
onSave={onSave}
279+
toolbars={ufToolbars}
280+
onChange={val => {
281+
setMd(val);
282+
onContentChange(val);
283+
}}
284+
onUploadImg={onUploadImg}
285+
footers={footers}
286+
defFooters={defFooters}
287+
/>
288+
</div>
289+
);
290+
}

0 commit comments

Comments
 (0)