新增能力:内置 4 套红头、3 套正式文档和 3 套标书主题,支持主题推荐页边距、页面装饰、页眉页脚和页码;增加结构化公文、项目报告及标书 Front Matter,内置 Fandol 中文字体、两份教程和 14 份主题示例。 桌面工作流:重组更多菜单,增加新建、保存、另存为快捷键和未保存确认;另存为后跟随新路径,主题示例自动切换主题,Desktop 发行包完整携带教程、示例与字体。 问题修复:修正红头标题居中与正式文档字体;围栏代码按正文宽度自动换行,长内容安全断行,至少两行即可在当前页分页,并统一保留 8px 左侧内容留白;Docker Web 镜像正确打包 samples。 兼容与部署:未声明主题推荐设置时继续使用 16mm 默认页边距;Web 隐藏不适用的另存为。正式镜像 yixiong/md-to-pdf:v0.5.1 内容 ID 为 sha256:72f519a69bfd6cb2f6df30c2be938e58ceb6f20e4748a32551874de3d436b276,Compose 健康运行。 验证结果:最终修复前 65 个测试文件、286 项测试通过,最终代码块与主题定向测试 27 项通过;全项目类型检查、生产构建和 git diff --check 通过。容器检出 14 套主题、17 份 Markdown,代码块回归 PDF 为 2 页且无越界。NSIS SHA-256 为 676CCE73D782B0B15AC6BC68F253CFBC4740383583E4DAA98F746EDF9999C5CC,ZIP SHA-256 为 82BF6ADF1200604F145CC86FA3ED193955CF6741EBEE3DF8953483515CFF621F。
176 lines
4.5 KiB
TypeScript
176 lines
4.5 KiB
TypeScript
import {
|
|
defaultExportConfig,
|
|
exportConfigSchema,
|
|
type ExportConfig
|
|
} from "@md-to-pdf/core";
|
|
|
|
export const EXPORT_CONFIG_STORAGE_KEY = "md-to-pdf.export-config.v4";
|
|
export const LEGACY_EXPORT_CONFIG_V3_STORAGE_KEY =
|
|
"md-to-pdf.export-config.v3";
|
|
export const LEGACY_EXPORT_CONFIG_V2_STORAGE_KEY =
|
|
"md-to-pdf.export-config.v2";
|
|
|
|
type SettingsStorage = Pick<Storage, "getItem" | "setItem">;
|
|
|
|
function cloneDefaultExportConfig() {
|
|
return structuredClone(defaultExportConfig);
|
|
}
|
|
|
|
function pickLegacyPageDecorations(value: unknown) {
|
|
if (typeof value !== "object" || value === null) {
|
|
return value;
|
|
}
|
|
const source = value as Record<string, unknown>;
|
|
if ("left" in source) {
|
|
return {
|
|
enabled: source.enabled,
|
|
height: source.height,
|
|
showDivider: source.showDivider,
|
|
fontSize: source.fontSize,
|
|
color: source.color,
|
|
left: source.left,
|
|
center: source.center,
|
|
right: source.right
|
|
};
|
|
}
|
|
return {
|
|
enabled: source.enabled,
|
|
height: source.height,
|
|
showDivider: source.showDivider,
|
|
fontSize: source.fontSize,
|
|
color: source.color,
|
|
alignment: source.alignment,
|
|
format: source.format,
|
|
template: source.template,
|
|
startFrom: source.startFrom
|
|
};
|
|
}
|
|
|
|
function usesLegacyPageDecorationDefaults(
|
|
header: unknown,
|
|
footer: unknown
|
|
) {
|
|
return (
|
|
JSON.stringify(pickLegacyPageDecorations(header)) ===
|
|
JSON.stringify(
|
|
pickLegacyPageDecorations(defaultExportConfig.header)
|
|
) &&
|
|
JSON.stringify(pickLegacyPageDecorations(footer)) ===
|
|
JSON.stringify(
|
|
pickLegacyPageDecorations(defaultExportConfig.footer)
|
|
)
|
|
);
|
|
}
|
|
|
|
function migrateStoredExportConfig(value: unknown): unknown {
|
|
if (
|
|
typeof value !== "object" ||
|
|
value === null ||
|
|
!("version" in value) ||
|
|
!("paper" in value)
|
|
) {
|
|
return value;
|
|
}
|
|
|
|
if (
|
|
value.version !== 2 &&
|
|
value.version !== 3 &&
|
|
value.version !== defaultExportConfig.version
|
|
) {
|
|
return value;
|
|
}
|
|
|
|
const source = value as Record<string, unknown>;
|
|
const hasPageDecorationsMode =
|
|
typeof source.pageDecorationsMode === "string";
|
|
|
|
const paper =
|
|
typeof value.paper === "object" && value.paper !== null
|
|
? value.paper
|
|
: {};
|
|
const margins =
|
|
"margins" in paper &&
|
|
typeof paper.margins === "object" &&
|
|
paper.margins !== null
|
|
? paper.margins
|
|
: {};
|
|
const usesLegacyDefaults =
|
|
JSON.stringify(margins) ===
|
|
JSON.stringify(defaultExportConfig.paper.margins);
|
|
const hasMarginMode =
|
|
typeof (paper as Record<string, unknown>).marginMode === "string";
|
|
|
|
return {
|
|
...value,
|
|
...(value.version === 2 || value.version === 3
|
|
? { version: defaultExportConfig.version }
|
|
: {}),
|
|
...(value.version === 2
|
|
? { mermaid: structuredClone(defaultExportConfig.mermaid) }
|
|
: {}),
|
|
...(!hasPageDecorationsMode
|
|
? {
|
|
pageDecorationsMode: usesLegacyPageDecorationDefaults(
|
|
source.header,
|
|
source.footer
|
|
)
|
|
? "theme"
|
|
: "custom"
|
|
}
|
|
: {}),
|
|
paper: {
|
|
...paper,
|
|
...(!hasMarginMode
|
|
? {
|
|
marginMode: usesLegacyDefaults ? "theme" : "custom"
|
|
}
|
|
: {})
|
|
}
|
|
};
|
|
}
|
|
|
|
export function parseStoredExportConfig(rawValue: string | null): ExportConfig {
|
|
if (!rawValue) {
|
|
return cloneDefaultExportConfig();
|
|
}
|
|
|
|
try {
|
|
const value: unknown = JSON.parse(rawValue);
|
|
const migratedValue = migrateStoredExportConfig(value);
|
|
const parsed = exportConfigSchema.safeParse(migratedValue);
|
|
return parsed.success ? parsed.data : cloneDefaultExportConfig();
|
|
} catch {
|
|
return cloneDefaultExportConfig();
|
|
}
|
|
}
|
|
|
|
export function loadExportConfig(
|
|
storage: SettingsStorage = window.localStorage
|
|
) {
|
|
const currentValue = storage.getItem(EXPORT_CONFIG_STORAGE_KEY);
|
|
if (currentValue) {
|
|
return parseStoredExportConfig(currentValue);
|
|
}
|
|
|
|
const legacyValue =
|
|
storage.getItem(LEGACY_EXPORT_CONFIG_V3_STORAGE_KEY) ??
|
|
storage.getItem(LEGACY_EXPORT_CONFIG_V2_STORAGE_KEY);
|
|
const config = parseStoredExportConfig(legacyValue);
|
|
if (legacyValue) {
|
|
storage.setItem(EXPORT_CONFIG_STORAGE_KEY, JSON.stringify(config));
|
|
}
|
|
return config;
|
|
}
|
|
|
|
export function saveExportConfig(
|
|
config: ExportConfig,
|
|
storage: SettingsStorage = window.localStorage
|
|
) {
|
|
const parsed = exportConfigSchema.parse(config);
|
|
storage.setItem(EXPORT_CONFIG_STORAGE_KEY, JSON.stringify(parsed));
|
|
}
|
|
|
|
export function resetExportConfig() {
|
|
return cloneDefaultExportConfig();
|
|
}
|