161 lines
5.8 KiB
Markdown
161 lines
5.8 KiB
Markdown
# Markdown PDF 导出器
|
||
|
||
一个面向内网部署的 Markdown 排版与 PDF 导出工具。项目目标是使用同一份 HTML 和 CSS 完成网页预览与 Chromium PDF 渲染,并提供纸张、页边距、页眉页脚、页码及主题扩展能力。
|
||
|
||
## 当前状态
|
||
|
||
已经实现:
|
||
|
||
- React Markdown 编辑与本地文件读取;
|
||
- Fastify 预览接口;
|
||
- 共享导出配置模型;
|
||
- 动态主题清单、CSS 和静态资源接口;
|
||
- Markdown、Front Matter、安全过滤和扩展语法渲染核心;
|
||
- 表格、任务列表、脚注、代码高亮、KaTeX 和 Mermaid;
|
||
- iframe 隔离的真实分页预览、双向滚动同步与主题切换;
|
||
- Chromium PDF 下载、精确 PDF.js 预览与导出性能观测;
|
||
- 五种纸张、页边距、页眉、页码、Mermaid 风格和浏览器配置缓存;
|
||
- 快速预览与精确预览共用的 50%~400% 显示缩放;
|
||
- 内置 Typora 风格、GitHub、Pixyll 和 Whitey 四套主题;
|
||
- Web 素材目录上传、桌面同目录相对图片和受限公网图片下载;
|
||
- 图片标题、整块分页及超高图片单页等比例适配;
|
||
- 无本地 Server 的 Electron 桌面端与 Windows 安装包;
|
||
- 前端、Fastify、Nginx 和 Playwright Chromium 的 AIO 容器部署。
|
||
|
||
## 本地开发
|
||
|
||
要求 Node.js 22 或更高版本。
|
||
|
||
```powershell
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
默认地址:
|
||
|
||
- 前端:http://localhost:5173
|
||
- 后端:http://localhost:3001
|
||
- 健康检查:http://localhost:3001/api/health
|
||
|
||
页面支持直接编辑 Markdown,或选择本地 `.md` 文件。若 Markdown 使用
|
||
相对图片路径,再选择对应的素材目录即可;文件与图片只进入当前渲染请求,
|
||
不写入数据库。桌面端使用原生“打开 Markdown”对话框,并自动从 Markdown
|
||
所在目录安全解析相对图片。
|
||
|
||
独立一行的 `` 会显示图片标题。图片与标题作为一个整体
|
||
参与分页;超出单页内容区时,图片会在预留标题高度后等比例缩放,不跨页
|
||
切割。远程图片会先由应用服务受限下载并内嵌,失败时显示可诊断占位。
|
||
|
||
编辑区和分页预览使用双向比例滚动同步。在任意一侧滚动时,另一侧会跟随到相同的全文阅读进度,方便定位并修改较长文档。
|
||
|
||
预览工具栏支持滑块和手动百分比输入。缩放比例只改变浏览器显示,不改变纸张尺寸、分页结果或最终 PDF,并会单独保存在浏览器中。
|
||
|
||
## Mermaid 配置与样式
|
||
|
||
导出设置可以统一控制整篇文档的 Mermaid 布局、主题、外观和字体:
|
||
|
||
- 布局:Dagre、ELK;
|
||
- 主题:Mermaid 11.16.0 提供的 Default、Base、Dark、Forest、Neutral、Neo 和 Redux 系列;
|
||
- 外观:Classic、Hand-drawn、Neo;
|
||
- 字体:任意本地 CSS 字体族。
|
||
|
||
未设置时使用 `dagre + default + classic`。单个 Mermaid 区块的 `config` Front Matter 优先于全局导出设置:
|
||
|
||
````markdown
|
||
```mermaid
|
||
---
|
||
config:
|
||
layout: elk
|
||
theme: base
|
||
look: handDrawn
|
||
fontFamily: Microsoft YaHei
|
||
themeVariables:
|
||
primaryColor: "#dbeafe"
|
||
primaryBorderColor: "#2563eb"
|
||
flowchart:
|
||
wrappingWidth: 360
|
||
---
|
||
flowchart TB
|
||
classDef important fill:#fee2e2,stroke:#dc2626
|
||
A["较长的节点名称<br/>第二行说明"] --> B["下一个节点"]
|
||
style A fill:#dcfce7,stroke:#16a34a
|
||
class B important
|
||
```
|
||
````
|
||
|
||
`style`、`classDef`、`linkStyle`、`themeVariables` 和图表专属配置均由 Mermaid 官方渲染器处理。原始 `themeCSS`、`securityLevel` 和资源限制属于安全配置,不能由 Markdown 代码块覆盖。
|
||
|
||
ELK 使用官方 `@mermaid-js/layout-elk`,只有图表实际请求 ELK 时才加载核心布局代码。
|
||
|
||
### Mermaid 长标签
|
||
|
||
流程图节点标签的默认最大宽度为 320px,可以通过上例中的 `flowchart.wrappingWidth` 调整。
|
||
|
||
需要自动换行时可以使用 Mermaid Markdown String:
|
||
|
||
````markdown
|
||
```mermaid
|
||
flowchart LR
|
||
A["`这是一段可以由 Mermaid 自动换行的较长节点文字`"]
|
||
```
|
||
````
|
||
|
||
个别节点仍需缩小字体时,优先使用 Mermaid 自带的 `classDef`:
|
||
|
||
````markdown
|
||
```mermaid
|
||
flowchart LR
|
||
A["较长节点"] --> B["普通节点"]
|
||
classDef compact font-size:12px;
|
||
class A compact;
|
||
```
|
||
````
|
||
|
||
## 内置与本地 Typora 主题
|
||
|
||
公司内部版本已经内置 GitHub、Pixyll 和 Whitey 三套适合打印的白色
|
||
Typora 默认主题。如需从本机 Typora 安装目录刷新本地开发副本,可以运行:
|
||
|
||
```powershell
|
||
npm run theme:import-typora
|
||
```
|
||
|
||
默认从 Typora 安装目录的 `resources\style` 读取基础 CSS、主题和资源,写入 `.local\themes\typora-*`。启动开发服务后,这些主题会出现在预览页面的主题选择器中。
|
||
|
||
导入工具不会默认覆盖已存在的目标。确认替换本地副本时使用:
|
||
|
||
```powershell
|
||
npm run theme:import-typora -- --replace
|
||
```
|
||
|
||
旧副本会移动到 `.local\theme-backups`。
|
||
|
||
本地主题由动态主题注册器加载,CSS 中的相对字体和图片会通过受限资源接口提供。外部 URL、越界路径和符号链接逃逸会被拒绝。
|
||
|
||
自定义主题目录、`theme.json`、CSS 组合顺序、资源引用和安全限制详见 [主题开发指南](docs/THEMES.md)。
|
||
|
||
## Docker Desktop 部署
|
||
|
||
```powershell
|
||
docker compose -f deploy\compose.yaml up --build -d
|
||
```
|
||
|
||
默认访问地址为 http://localhost:8080。Compose 会将 `.local/themes`
|
||
作为只读主题目录挂载;端口、主题目录和 PDF 并发参数的配置方式详见
|
||
[AIO 容器部署说明](deploy/README.md)。
|
||
|
||
## 测试与构建
|
||
|
||
```powershell
|
||
npm test
|
||
npm run typecheck
|
||
npm run build
|
||
git diff --check
|
||
```
|
||
|
||
## 隐私原则
|
||
|
||
文档内容与图片仅用于当前渲染和转换请求,不接入数据库或文档历史存储。
|
||
相对路径会规范化并限制在所选目录内;路径穿越、符号链接越界、内网远程
|
||
地址、超出数量或大小上限的图片会被拒绝或替换为失败占位。
|