feat: 完善 Mermaid 配置与预览缩放
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
import {
|
||||
getPaperDimensionsMm,
|
||||
paperFormatLabels,
|
||||
supportedMermaidLayouts,
|
||||
supportedMermaidLooks,
|
||||
supportedMermaidThemes,
|
||||
supportedPaperFormats,
|
||||
type ExportConfig,
|
||||
type FooterConfig,
|
||||
type HeaderConfig
|
||||
type HeaderConfig,
|
||||
type MermaidExportConfig
|
||||
} from "@md-to-pdf/core";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -30,6 +34,48 @@ const pageNumberFormatLabels: Record<FooterConfig["format"], string> = {
|
||||
custom: "自定义模板"
|
||||
};
|
||||
|
||||
const mermaidLayoutLabels: Record<
|
||||
MermaidExportConfig["layout"],
|
||||
string
|
||||
> = {
|
||||
dagre: "Dagre",
|
||||
elk: "ELK"
|
||||
};
|
||||
|
||||
const mermaidThemeLabels: Record<
|
||||
MermaidExportConfig["theme"],
|
||||
string
|
||||
> = {
|
||||
default: "Default",
|
||||
base: "Base(可定制基础主题)",
|
||||
dark: "Dark",
|
||||
forest: "Forest",
|
||||
neutral: "Neutral",
|
||||
neo: "Neo",
|
||||
"neo-dark": "Neo Dark",
|
||||
redux: "Redux",
|
||||
"redux-dark": "Redux Dark",
|
||||
"redux-color": "Redux Color",
|
||||
"redux-dark-color": "Redux Dark Color"
|
||||
};
|
||||
|
||||
const mermaidLookLabels: Record<
|
||||
MermaidExportConfig["look"],
|
||||
string
|
||||
> = {
|
||||
classic: "Classic",
|
||||
handDrawn: "Hand-drawn",
|
||||
neo: "Neo"
|
||||
};
|
||||
|
||||
const mermaidFontFamilies = [
|
||||
"Segoe UI, Microsoft YaHei, sans-serif",
|
||||
"Microsoft YaHei, sans-serif",
|
||||
"Arial, Helvetica, sans-serif",
|
||||
"Georgia, Times New Roman, serif",
|
||||
"Consolas, Microsoft YaHei, monospace"
|
||||
] as const;
|
||||
|
||||
function millimeters(value: string) {
|
||||
return Number.parseFloat(value.replace(/mm$/i, ""));
|
||||
}
|
||||
@@ -99,6 +145,54 @@ function LengthInput({
|
||||
);
|
||||
}
|
||||
|
||||
function MermaidFontInput({
|
||||
value,
|
||||
onChange
|
||||
}: {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
}) {
|
||||
const [draft, setDraft] = useState(value);
|
||||
|
||||
useEffect(() => {
|
||||
setDraft(value);
|
||||
}, [value]);
|
||||
|
||||
function commit() {
|
||||
const normalized = draft.trim();
|
||||
if (!normalized) {
|
||||
setDraft(value);
|
||||
return;
|
||||
}
|
||||
setDraft(normalized);
|
||||
onChange(normalized);
|
||||
}
|
||||
|
||||
return (
|
||||
<label className="setting-field stacked">
|
||||
<span>图表字体</span>
|
||||
<input
|
||||
type="text"
|
||||
list="mermaid-font-family-options"
|
||||
maxLength={300}
|
||||
value={draft}
|
||||
onBlur={commit}
|
||||
onChange={(event) => setDraft(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<datalist id="mermaid-font-family-options">
|
||||
{mermaidFontFamilies.map((fontFamily) => (
|
||||
<option key={fontFamily} value={fontFamily} />
|
||||
))}
|
||||
</datalist>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
function updateHeaderSlot(
|
||||
header: HeaderConfig,
|
||||
slot: "left" | "center" | "right",
|
||||
@@ -193,6 +287,16 @@ export function ExportSettingsDrawer({
|
||||
});
|
||||
}
|
||||
|
||||
function updateMermaid(mermaid: Partial<MermaidExportConfig>) {
|
||||
onChange({
|
||||
...config,
|
||||
mermaid: {
|
||||
...config.mermaid,
|
||||
...mermaid
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="settings-layer">
|
||||
<button
|
||||
@@ -307,6 +411,73 @@ export function ExportSettingsDrawer({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="settings-section">
|
||||
<h3>Mermaid 图表</h3>
|
||||
<label className="setting-field">
|
||||
<span>布局算法</span>
|
||||
<select
|
||||
value={config.mermaid.layout}
|
||||
onChange={(event) =>
|
||||
updateMermaid({
|
||||
layout: event.target
|
||||
.value as MermaidExportConfig["layout"]
|
||||
})
|
||||
}
|
||||
>
|
||||
{supportedMermaidLayouts.map((layout) => (
|
||||
<option key={layout} value={layout}>
|
||||
{mermaidLayoutLabels[layout]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="setting-field">
|
||||
<span>图表主题</span>
|
||||
<select
|
||||
value={config.mermaid.theme}
|
||||
onChange={(event) =>
|
||||
updateMermaid({
|
||||
theme: event.target
|
||||
.value as MermaidExportConfig["theme"]
|
||||
})
|
||||
}
|
||||
>
|
||||
{supportedMermaidThemes.map((theme) => (
|
||||
<option key={theme} value={theme}>
|
||||
{mermaidThemeLabels[theme]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="setting-field">
|
||||
<span>图表外观</span>
|
||||
<select
|
||||
value={config.mermaid.look}
|
||||
onChange={(event) =>
|
||||
updateMermaid({
|
||||
look: event.target
|
||||
.value as MermaidExportConfig["look"]
|
||||
})
|
||||
}
|
||||
>
|
||||
{supportedMermaidLooks.map((look) => (
|
||||
<option key={look} value={look}>
|
||||
{mermaidLookLabels[look]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<MermaidFontInput
|
||||
value={config.mermaid.fontFamily}
|
||||
onChange={(fontFamily) => updateMermaid({ fontFamily })}
|
||||
/>
|
||||
<p className="setting-hint">
|
||||
单个 Mermaid 代码块中的 config frontmatter
|
||||
优先于这里的全局设置;style、classDef 和 themeVariables
|
||||
会继续按 Mermaid 原生语法生效。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="settings-section">
|
||||
<div className="setting-section-title">
|
||||
<h3>页眉</h3>
|
||||
|
||||
Reference in New Issue
Block a user