import { defaultExportConfig, getPaperDimensionsMm, lengthToMillimeters, paperFormatLabels, supportedMermaidLayouts, supportedMermaidLooks, supportedMermaidThemes, supportedPaperFormats, type ExportConfig, type FooterConfig, type HeaderConfig, type MermaidExportConfig } from "@md-to-pdf/core"; import { useEffect, useState } from "react"; import type { ThemeSummary } from "./application-backend"; interface ExportSettingsDrawerProps { config: ExportConfig; theme: ThemeSummary | undefined; onChange: (config: ExportConfig) => void; onClose: () => void; onReset: () => void; } interface LengthInputProps { label: string; value: string; maximum: number; disabled?: boolean; precision?: number; step?: number; onChange: (value: string) => void; } const pageNumberFormatLabels: Record = { page: "1", "page-total": "1 / 10", "chinese-page-total": "第 1 页 / 共 10 页", "dash-page": "- 1 -", "official-page": "— 1 —(标准公文)", 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 fitMarginPair( first: number, second: number, available: number ) { const total = first + second; if (total <= available || total === 0) { return [first, second] as const; } const scale = available / total; const scaledFirst = Math.floor(first * scale * 10) / 10; return [ scaledFirst, Math.floor((available - scaledFirst) * 10) / 10 ] as const; } function LengthInput({ label, value, maximum, disabled = false, precision = 1, step = 0.1, onChange }: LengthInputProps) { const [draft, setDraft] = useState( String(lengthToMillimeters(value)) ); useEffect(() => { setDraft(String(lengthToMillimeters(value))); }, [value]); function commit() { const number = Number.parseFloat(draft); if (!Number.isFinite(number)) { setDraft(String(lengthToMillimeters(value))); return; } const normalized = Math.min(Math.max(number, 0), maximum); const factor = 10 ** precision; const rounded = Math.round(normalized * factor) / factor; setDraft(String(rounded)); onChange(`${rounded}mm`); } return ( ); } function PageDecorationFontInput({ label, value, disabled, onChange }: { label: string; value: string; disabled: boolean; onChange: (value: string) => void; }) { const [draft, setDraft] = useState(value); useEffect(() => { setDraft(value); }, [value]); function commit() { const normalized = draft.trim(); if ( !normalized || !/^[\p{L}\p{N}\s,"'-]+$/u.test(normalized) ) { setDraft(value); return; } setDraft(normalized); onChange(normalized); } return ( ); } 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 ( ); } function updateHeaderSlot( header: HeaderConfig, slot: "left" | "center" | "right", value: Partial ) { return { ...header, [slot]: { ...header[slot], ...value } }; } export function ExportSettingsDrawer({ config, theme, onChange, onClose, onReset }: ExportSettingsDrawerProps) { const dimensions = getPaperDimensionsMm( config.paper.format, config.paper.orientation ); const margins = config.paper.margins; const horizontalGap = 1; const verticalGap = 1; function updatePaper(paper: Partial) { const nextPaper = { ...config.paper, ...paper }; const nextDimensions = getPaperDimensionsMm( nextPaper.format, nextPaper.orientation ); const [left, right] = fitMarginPair( lengthToMillimeters(nextPaper.margins.left), lengthToMillimeters(nextPaper.margins.right), nextDimensions.width - 1 ); const [top, bottom] = fitMarginPair( lengthToMillimeters(nextPaper.margins.top), lengthToMillimeters(nextPaper.margins.bottom), nextDimensions.height - 1 ); onChange({ ...config, paper: { ...nextPaper, margins: { top: `${top}mm`, right: `${right}mm`, bottom: `${bottom}mm`, left: `${left}mm` } } }); } function updateMargin( side: keyof ExportConfig["paper"]["margins"], value: string ) { updatePaper({ marginMode: "custom", margins: { ...margins, [side]: value } }); } function followThemeMargins() { updatePaper({ marginMode: "theme", margins: theme?.pageDefaults?.margins ?? { top: "16mm", right: "16mm", bottom: "16mm", left: "16mm" } }); } function updateHeader(header: Partial) { onChange({ ...config, pageDecorationsMode: "custom", header: { ...config.header, ...header } }); } function updateFooter(footer: Partial) { onChange({ ...config, pageDecorationsMode: "custom", footer: { ...config.footer, ...footer } }); } function followThemePageDecorations() { onChange({ ...config, pageDecorationsMode: "theme", header: structuredClone( theme?.pageDefaults?.header ?? defaultExportConfig.header ), footer: structuredClone( theme?.pageDefaults?.footer ?? defaultExportConfig.footer ) }); } function updateMermaid(mermaid: Partial) { onChange({ ...config, mermaid: { ...config.mermaid, ...mermaid } }); } return (

纸张

方向

当前纸张:{dimensions.width} × {dimensions.height}mm

页边距

页边距来源

{config.paper.marginMode === "theme" ? theme?.pageDefaults ? `当前采用“${theme.name}”推荐值` : "当前主题未设置推荐值,采用全局默认 16mm" : "当前采用自定义值;修改任意页边距会自动进入此模式"}

updateMargin("top", value)} /> updateMargin("right", value)} /> updateMargin("bottom", value)} /> updateMargin("left", value)} />
{config.paper.marginMode === "custom" ? ( ) : null}

Mermaid 图表

updateMermaid({ fontFamily })} />

单个 Mermaid 代码块中的 config frontmatter 优先于这里的全局设置;style、classDef 和 themeVariables 会继续按 Mermaid 原生语法生效。

页眉与页码来源

页面装饰来源

{config.pageDecorationsMode === "theme" ? theme?.pageDefaults?.header || theme?.pageDefaults?.footer ? `当前采用“${theme.name}”推荐的页眉与页码` : "当前主题未设置推荐值,采用全局默认配置" : "当前采用自定义配置;修改页眉或页码会自动进入此模式"}

{config.pageDecorationsMode === "custom" ? ( ) : null}

页眉

支持 {"${title}"}、{"${author}"} 和 {"${filename}"}。

updateHeader({ fontFamily })} /> updateHeader({ fontSize })} /> {(["left", "center", "right"] as const).map((slot) => { const labels = { left: "左侧", center: "中间", right: "右侧" }; return (
updateHeader( updateHeaderSlot(config.header, slot, { content: event.target.value }) ) } />
); })}

页脚页码

updateFooter({ fontFamily })} /> updateFooter({ fontSize })} /> {config.footer.format === "custom" ? ( ) : null}

页眉和页码会实时显示在分页预览中。

); }