release: 发布 v0.4.5
新增能力:Web 与 Desktop 支持新建和保存 Markdown;桌面端注册 .md/.markdown 打开程序,支持单实例文件转交、自定义主题目录以及窗口位置、尺寸和最大化状态的延迟原子持久化与屏幕越界修正。 界面与兼容性:保留打开 Markdown 为独立主操作,将新建、保存和主题目录收纳到更多菜单;Web 移除本地素材目录入口并明确默认不支持本地素材;新文档默认使用一级标题或首行合法化生成文件名,可不保存直接导出 PDF。 渲染修复:图片、Mermaid 和 ECharts 按文档顺序串行执行媒体分页回填,每个媒体元素至多重排一次;图片以限宽后的高度作为缩放基准,只缩放媒体主体并保持标题尺寸;ECharts 冻结为 SVG 图片后参与统一分页。 示例与文档:默认示例仅保留一张网络图片,增加有效 Base64 横图、竖图、边界图片及多尺寸 Mermaid/ECharts;更新根 README、Desktop README、部署文档、四个 packages README、PROGRESS 和 AGENTS 发布规范;登记 v0.4.6 超链接导航问题。 部署与验证:构建并运行 yixiong/md-to-pdf:v0.4.5 正式镜像,Compose 健康且 PDF 冒烟无 Mermaid/ECharts 错误;生成 NSIS 安装包和免安装 ZIP,本机已升级到 NSIS v0.4.5 并清理旧 Squirrel 安装;202 项测试、类型检查、生产构建和 git diff --check 全部通过。 发布产物:Setup.exe SHA-256 9CBD6362E15AA745185805E753D5A7749434E0E142E6CE0F66463E6A19468F1D;ZIP SHA-256 97B2D44E72F626A3396E5AB5B330FFB7F9BECAF945A1707808C876940A82E850;当前 Windows 产物未配置代码签名。
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
const fallbackBaseName = "未命名文档";
|
||||
const windowsReservedName =
|
||||
/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/iu;
|
||||
|
||||
function stripMarkdownFormatting(value: string) {
|
||||
return value
|
||||
.replace(/!\[([^\]]*)\]\([^)]*\)/gu, "$1")
|
||||
.replace(/\[([^\]]+)\]\([^)]*\)/gu, "$1")
|
||||
.replace(/<\/?[A-Za-z][^>]*>/gu, " ")
|
||||
.replace(/[`*_~]/gu, "")
|
||||
.replace(/^\s{0,3}(?:>\s*|[-+*]\s+|\d+[.)]\s+)/u, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
export function legalizeMarkdownBaseName(value: string) {
|
||||
const normalized = stripMarkdownFormatting(value)
|
||||
.normalize("NFKC")
|
||||
.replace(/[^\p{L}\p{N}\s_-]+/gu, " ")
|
||||
.replace(/\s+/gu, " ")
|
||||
.trim()
|
||||
.replace(/[ .]+$/u, "");
|
||||
const shortened = Array.from(normalized).slice(0, 100).join("").trim();
|
||||
if (!shortened) {
|
||||
return fallbackBaseName;
|
||||
}
|
||||
return windowsReservedName.test(shortened)
|
||||
? `文档 ${shortened}`
|
||||
: shortened;
|
||||
}
|
||||
|
||||
export function deriveMarkdownBaseName(markdown: string) {
|
||||
const lines = markdown.replace(/^\uFEFF/u, "").split(/\r?\n/u);
|
||||
let inFrontMatter = lines[0]?.trim() === "---";
|
||||
let inFence = false;
|
||||
let fenceMarker = "";
|
||||
let fenceLength = 0;
|
||||
let firstBodyLine = "";
|
||||
|
||||
for (let index = inFrontMatter ? 1 : 0; index < lines.length; index += 1) {
|
||||
const line = lines[index] ?? "";
|
||||
const trimmed = line.trim();
|
||||
if (inFrontMatter) {
|
||||
if (trimmed === "---" || trimmed === "...") {
|
||||
inFrontMatter = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFence) {
|
||||
const closingFence = trimmed.match(/^(`+|~+)\s*$/u)?.[1];
|
||||
if (
|
||||
closingFence?.[0] === fenceMarker &&
|
||||
closingFence.length >= fenceLength
|
||||
) {
|
||||
inFence = false;
|
||||
fenceMarker = "";
|
||||
fenceLength = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const fence = trimmed.match(/^(`{3,}|~{3,})/u)?.[1];
|
||||
if (fence) {
|
||||
if (!inFence) {
|
||||
inFence = true;
|
||||
fenceMarker = fence[0] ?? "";
|
||||
fenceLength = fence.length;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (inFence || !trimmed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const heading = line.match(/^\s{0,3}#\s+(.+?)\s*#*\s*$/u)?.[1];
|
||||
if (heading) {
|
||||
return legalizeMarkdownBaseName(heading);
|
||||
}
|
||||
if (!firstBodyLine) {
|
||||
firstBodyLine = line;
|
||||
}
|
||||
}
|
||||
|
||||
return legalizeMarkdownBaseName(firstBodyLine);
|
||||
}
|
||||
|
||||
export function createMarkdownFileName(markdown: string) {
|
||||
return `${deriveMarkdownBaseName(markdown)}.md`;
|
||||
}
|
||||
|
||||
export function ensureMarkdownFileName(fileName: string, markdown: string) {
|
||||
const trimmed = fileName.trim();
|
||||
if (!trimmed) {
|
||||
return createMarkdownFileName(markdown);
|
||||
}
|
||||
return /\.(?:md|markdown)$/iu.test(trimmed)
|
||||
? trimmed
|
||||
: `${trimmed}.md`;
|
||||
}
|
||||
|
||||
export function downloadMarkdown(markdown: string, fileName: string) {
|
||||
const url = URL.createObjectURL(
|
||||
new Blob([markdown], { type: "text/markdown;charset=utf-8" })
|
||||
);
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = fileName;
|
||||
anchor.click();
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 0);
|
||||
}
|
||||
Reference in New Issue
Block a user