feat: 升级 CodeMirror 编辑器并新增 Markdown 工具栏

This commit is contained in:
SkyJourney
2026-07-30 12:39:35 +08:00
parent f7d4efeafc
commit 7831bc23f0
11 changed files with 1603 additions and 35 deletions
+40 -14
View File
@@ -9,6 +9,7 @@ import {
useRef,
useState
} from "react";
import type { EditorView } from "@codemirror/view";
import {
createPagedDocumentPayload,
getPaperDimensionsMm,
@@ -71,6 +72,11 @@ import {
applyThemeDefaults,
selectTheme
} from "./theme-margins";
import {
MarkdownEditor,
type MarkdownEditorHandle
} from "./MarkdownEditor";
import { MarkdownToolbar } from "./MarkdownToolbar";
const PrecisePdfPreview = lazy(async () => {
const module = await import("./PrecisePdfPreview");
@@ -107,6 +113,11 @@ const initialDocumentKind: DocumentKind = startsWithBlankMarkdown
export function App() {
const [markdown, setMarkdown] = useState(initialMarkdown);
const [editorDocumentVersion, setEditorDocumentVersion] =
useState(0);
const [editorView, setEditorView] = useState<EditorView | null>(
null
);
const [fileName, setFileName] = useState(initialFileName);
const [documentKind, setDocumentKind] =
useState<DocumentKind>(initialDocumentKind);
@@ -141,7 +152,7 @@ export function App() {
current: 0,
total: 0
});
const editorRef = useRef<HTMLTextAreaElement>(null);
const editorRef = useRef<MarkdownEditorHandle>(null);
const markdownFileInputRef = useRef<HTMLInputElement>(null);
const moreMenuRef = useRef<HTMLDivElement>(null);
const previewScrollRef = useRef<HTMLDivElement>(null);
@@ -158,6 +169,11 @@ export function App() {
const themeRefreshTargetRef = useRef<number | undefined>(undefined);
documentDirtyRef.current = documentDirty;
function replaceMarkdownDocument(content: string) {
setMarkdown(content);
setEditorDocumentVersion((version) => version + 1);
}
useEffect(() => {
const bridge = window.mdToPdfDesktop;
if (!bridge) {
@@ -191,7 +207,7 @@ export function App() {
return;
}
setAppError("");
setMarkdown(opened.markdown);
replaceMarkdownDocument(opened.markdown);
setFileName(opened.fileName);
setDocumentKind("file");
setSavedMarkdown(opened.markdown);
@@ -658,9 +674,13 @@ export function App() {
function synchronizeScroll(origin: "editor" | "preview") {
const source =
origin === "editor" ? editorRef.current : getPreviewScroller();
origin === "editor"
? editorRef.current?.getScrollElement()
: getPreviewScroller();
const target =
origin === "editor" ? getPreviewScroller() : editorRef.current;
origin === "editor"
? getPreviewScroller()
: editorRef.current?.getScrollElement();
if (!source || !target) {
return;
}
@@ -866,7 +886,7 @@ export function App() {
try {
setAppError("");
const content = await file.text();
setMarkdown(content);
replaceMarkdownDocument(content);
setFileName(file.name);
setDocumentKind("file");
setSavedMarkdown(content);
@@ -885,7 +905,7 @@ export function App() {
if (!opened) {
return;
}
setMarkdown(opened.markdown);
replaceMarkdownDocument(opened.markdown);
setFileName(opened.fileName);
setDocumentKind("file");
setSavedMarkdown(opened.markdown);
@@ -923,7 +943,7 @@ export function App() {
try {
setAppError("");
await window.mdToPdfDesktop?.startNewMarkdown();
setMarkdown("");
replaceMarkdownDocument("");
setFileName("");
setDocumentKind("new");
setSavedMarkdown("");
@@ -1034,7 +1054,7 @@ export function App() {
try {
setAppError("");
await window.mdToPdfDesktop?.startNewMarkdown();
setMarkdown(sample.markdown);
replaceMarkdownDocument(sample.markdown);
setFileName(sample.fileName);
setDocumentKind("sample");
setSavedMarkdown(sample.markdown);
@@ -1078,7 +1098,7 @@ export function App() {
try {
setAppError("");
await window.mdToPdfDesktop?.startNewMarkdown();
setMarkdown(tutorial.markdown);
replaceMarkdownDocument(tutorial.markdown);
setFileName(tutorial.fileName);
setDocumentKind("sample");
setSavedMarkdown(tutorial.markdown);
@@ -1522,12 +1542,13 @@ export function App() {
</button>
</div>
<textarea
<MarkdownToolbar editorView={editorView} />
<MarkdownEditor
ref={editorRef}
aria-label="Markdown 内容"
value={markdown}
spellCheck={false}
onChange={(event) => setMarkdown(event.target.value)}
documentVersion={editorDocumentVersion}
onChange={setMarkdown}
onEditorReady={setEditorView}
onScroll={() => synchronizeScroll("editor")}
/>
</div>
@@ -1537,7 +1558,12 @@ export function App() {
className="source-panel-restore"
aria-controls="markdown-source-content"
aria-expanded="false"
onClick={() => setSourcePanelCollapsed(false)}
onClick={() => {
setSourcePanelCollapsed(false);
window.requestAnimationFrame(() =>
editorRef.current?.focus()
);
}}
>
</button>