Skip to content
bug010101Public

About

用NodeJS写的一个markdown编辑器,并支持导出pdf

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

M2P

Markdown → PDF 桌面工具,Electron + 内置 Chromium 排版。

platform license electron

左边写 Markdown,右边所见即所得地预览 A4 纸张,一键导出与浏览器排版一致的 PDF。零外部依赖,三端安装包由 GitHub Actions 在原生 runner 上构建。

目录

特性

写作体验

  • 实时预览:预览与导出共用同一套渲染管线(src/shared/markdown.js + src/shared/themes.js),纸张按“适应宽度”自动缩放。
  • 编辑器:CodeMirror 6,Markdown 语法高亮、行号、查找面板。
  • 会话:自动保存草稿、最近文档、侧边栏文件夹浏览、可拖拽分栏、界面缩放、拖拽文件或文件夹到窗口直接打开。

排版能力

  • 四种主题:GitHub(代码 / 表格 / 引用)、学术论文(宋体、首行缩进、居中标题)、商务报告(深蓝标题、蓝系表格)、极简(无边框、紧凑留白)。
  • 页面设置:A4 / A5 / Letter / Legal,纵向 / 横向,三档页边距(约 12 / 19 / 25 mm)。
  • 页眉页脚:页眉取文档标题或自定义文案(支持 {title}、{date}),页脚“第 X 页 / 共 Y 页”。
  • 文档结构:自动目录(H1–H3,带内部跳转,可单独占页)、一级标题另起一页、PDF 书签大纲、Tagged PDF 结构标签;预览内点击目录条目或脚注引用可平滑跳转。
  • Markdown 能力:CommonMark + 表格、任务清单、脚注([^1])、代码高亮(highlight.js,带语言标签条)、KaTeX 公式($...$、$$...$$)、相对路径图片。

导出

  • PDF:直接调用 Chromium 的 webContents.printToPDF,不需要 Chrome / Puppeteer / wkhtmltopdf。
  • HTML:单文件产出,KaTeX 字体以 base64 内嵌,换机器打开不掉字。
  • 批量:整个文件夹(含子目录,最多 4 层)一次导出,带进度显示。
  • 命令行:无界面导出,结果一行 JSON,便于脚本消费。

安装与运行

环境要求

项 要求
系统 macOS 11+(Apple Silicon / Intel)、Windows 10+(x64)、Linux(x64、glibc 2.28+)
Node.js 18+(开发用,实测 22.23.2)
桌面环境 Linux 需要 X11 / Wayland 与中文字体,见常见问题

从源码运行

git clone <repo> M2P && cd M2P
npm install
npm start            # 构建 + 启动桌面应用

开发时开 watch 模式,改源码自动重建,之后在应用里按 ⌘R(Windows / Linux 为 Ctrl+R)重载窗口:

npm run dev

国内网络

首次 npm install 要下载 Electron 二进制,建议走镜像:

ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ npm install

应用本身的源码与导出逻辑不依赖平台特性,差异只在打包配置里。

打包与发布

打包交给 CI,本地只写代码。工作流在 .github/workflows/ci.yml。

三端产物

平台 运行器 产物
macOS(Apple Silicon) macos-15 M2P-<version>-arm64.dmg、M2P-<version>-arm64.zip
macOS(Intel) macos-15-intel M2P-<version>-x64.dmg、M2P-<version>-x64.zip
Windows(x64) windows-latest M2P-<version>-win-x64-setup.exe(NSIS,可选安装目录)
Linux(x64) ubuntu-latest M2P-<version>-x64.AppImage、M2P-<version>-x64.deb

CI 工作流

Job 触发 做什么
smoke push / PR Ubuntu 上用 Xvfb 无头导出 demo PDF,断言页数、A4 尺寸、目录文案与 /Outlines 书签;产物 demo-pdf
package v* tag 或手动 四个矩阵项并行打包,各自上传 artifact
release v* tag 汇总三端安装包,建 GitHub Release 并生成说明

发版流程

# 1. 改 package.json 的 version(CI 会按 tag 覆盖,保持一致更省心)
# 2. 打 tag 并推送
git tag v1.0.1
git push origin v1.0.1

推送后 Actions 自动产出上表文件并挂到 Release 页。也可以到 Actions 页面手动 Run workflow,只出 artifact 不发 Release。

未签名安装包的后果

仓库没有 Apple Developer ID,也没有 Windows 代码签名证书,所以安装包未签名、未公证:

平台 首次打开的现象 处理
macOS “无法验证开发者”或“已损坏,无法打开” xattr -cr /Applications/M2P.app,详见下文
Windows SmartScreen“Windows 已保护你的电脑” 更多信息 → 仍要运行
Linux AppImage 无执行位 / 缺少 FUSE chmod +x M2P-*.AppImage;sudo apt install libfuse2

要正式分发,就在仓库 Secrets 里放证书,并在 build.mac 补 identity / notarize、build.win 补 signtool。

本地手动打包(一般不需要)

npm run pack         # 生成 .app(release/mac-<arch>/)
npm run dist         # 生成 dmg + zip(release/)

打包配置在 package.json 的 build 字段:appId: com.m2p.desktop,图标 mac / Linux 用 assets/icon.png(1024×1024,自动转 icns 与多尺寸 png),Windows 用 assets/icon.ico(16–256)。

重新生成图标(一次产出 icon.png 与 icon.ico):

npm run icon         # 需要 Python 3 + Pillow

界面与快捷键

顶栏

侧边栏开关 · 文档名 · 主题 · 纸张 · 缩放 · 设置 · 导出 PDF。

快捷键

下表按 macOS 标注;Windows / Linux 上 ⌘ 对应 Ctrl(悬停提示会按平台自动切换)。

快捷键 功能
⌘N 新建文档
⌘O / ⇧⌘O 打开文件 / 打开文件夹
⌘S / ⇧⌘S 保存 / 另存为
⌘E 导出 PDF
⌘F 编辑器内查找
⌘B 显示 / 隐藏侧边栏
⌘⌥P 显示 / 隐藏预览
⌘+ / ⌘- / ⌘0 放大 / 缩小 / 实际大小
⌘, 打开设置
Esc 关闭弹窗

设置文件

设置项改动即时生效并自动保存:

平台 配置文件
macOS ~/Library/Application Support/M2P/settings.json
Windows %APPDATA%\M2P\settings.json
Linux ~/.config/M2P/settings.json

Markdown 语法

完整示例见 examples/demo.md,覆盖以下语法:

类别 支持
基础 标题(H1–H6)、段落、加粗、斜体、删除线、行内代码、引用块、分割线
列表 无序列表、有序列表、嵌套列表、任务清单(- [x] / - [ ])
表格 GFM 表格,跨页自动重复表头
代码 围栏代码块、语言标签条、highlight.js 高亮(JS / Python / Bash / CSS 等)
数学 KaTeX 行内公式 $E=mc^2$、块级公式 $$...$$
脚注 [^1] 引用标记 + [^1]: 定义,文末自动收集,附回链
链接与图片 外部链接(系统浏览器打开)、相对路径图片(以文档所在目录为基准)
目录 开启 --toc 后自动生成 H1–H3 目录,带内部跳转链接

命令行导出

适合脚本、CI 与批量处理。

用法

# 单个文件
npm start -- --export ./doc.md --out ./doc.pdf --theme academic --toc

# 指定纸张与分页
npm start -- --export ./doc.md --out ./doc.pdf --page-size A5 --chapter-break

# 导出 HTML(输出名用 .html)
npm start -- --export ./doc.md --out ./doc.html --html

# 整个文件夹(递归,含子目录)
npm start -- --export-folder ./docs --theme report --toc

也可以直接调 Electron 可执行文件,避开 npm 的 -- 转发(Windows 上用 npx electron .):

./node_modules/.bin/electron . --export ./doc.md --out ./doc.pdf --theme github --toc

参数

参数 说明
--export <file.md> 输入 Markdown
--out <file.pdf> 输出路径(带 --html 时为 .html)
--export-folder <dir> 批量导出目录
--theme <id> github / academic / report / minimal
--page-size <size> A4 / A5 / Letter / Legal
--landscape 横向
--toc 生成目录
--chapter-break 一级标题另起一页
--no-header 关闭页眉与页码
--html 导出 HTML 而非 PDF

输出

结果一行 JSON 写到 stdout,例如 {"ok":true,"path":"/tmp/demo.pdf","pages":5,"bytes":854210},失败时 {"ok":false,"error":"..."} 并以退出码 1 结束。

开发辅助参数:--open <file> 启动时载入文档、--modal 启动即打开设置、--capture <png> 截图后退出。

项目结构

M2P/
├── .github/workflows/ci.yml    # CI:无头冒烟 + 三端打包 + Release
├── build.mjs                   # esbuild 构建:main / preload / renderer + 资源拷贝
├── package.json                # 依赖、脚本、electron-builder 三端配置
├── package-lock.json            # 锁定依赖版本,CI 用 npm ci 安装
├── LICENSE                     # MIT
├── assets/
│   ├── icon.png                # 应用图标(1024×1024,mac / Linux 用)
│   └── icon.ico                # Windows 图标(16–256 多尺寸)
├── examples/demo.md            # 功能演示文档(验收样本)
├── tools/
│   ├── make-icon.py            # 用 Pillow 生成 icon.png
│   └── make-ico.py             # 由 icon.png 生成多尺寸 icon.ico
└── src/
    ├── main/
    │   ├── main.js             # 主进程:窗口、菜单、设置持久化、IPC、CLI 分支
    │   └── exporter.js         # 导出管线:HTML → 隐藏窗口 → printToPDF → 写文件
    ├── preload/preload.js      # contextBridge 暴露 window.m2p
    ├── renderer/
    │   ├── index.html          # 界面骨架(CSP 已开启)
    │   ├── style.css           # 界面样式
    │   └── app.js              # 渲染进程:编辑器、预览、设置、导出、拖拽、快捷键
    └── shared/
        ├── markdown.js         # markdown-it 实例、目录生成、整篇 HTML 拼装
        ├── themes.js           # 基础排版 CSS + 四套主题 + 代码高亮配色
        └── defaults.js         # 纸张尺寸、主题清单、默认设置

构建产物在 dist/,打包产物在 release/,两者都不入库。

实现要点

为什么不用 Puppeteer / md-to-pdf

printToPDF 复用应用内的 Chromium,零外部依赖、启动快;页眉页脚的 pageNumber / totalPages 模板与 generateDocumentOutline(PDF 书签)都是原生支持。

预览与 PDF 一致

buildHtmlDocument() 只拼一份 HTML:预览塞进 iframe.srcdoc,导出写成临时 HTML 交给隐藏窗口打印。唯一差异是预览把页面固定为纸张像素宽度,靠 transform: scale() 适应预览区,字号、边距、分页规则都不变。

KaTeX 字体

katex.min.css 内部是相对 fonts/ 的路径,临时 HTML 放在 /tmp 时会失效。导出前读取 CSS,把 url(./fonts/…) 改写成绝对 file:// 再内联;预览侧以 file:// 注入样式表。导出 HTML 时进一步转成 base64。

页边距与分页

边距以英寸存储(Electron margins 单位),开启页眉页脚时纵向至少 0.72in,避免页眉页脚被裁掉。chapter-break 让 h1 另起一页(首个除外),目录可独占一页;表格 thead 设 display: table-header-group 跨页重复表头,tr { break-inside: avoid } 防止行被切断。

安全

临时 HTML 会剥离 <script>、<iframe>、on*=、srcdoc;打印窗口 sandbox: true、nodeIntegration: false;主窗口开 contextIsolation,只经 preload 暴露白名单 IPC;界面设置 CSP,预览 iframe 为 allow-same-origin;外部链接走系统浏览器,主窗口拦截内部导航。

预览内链接处理

预览 iframe 是 sandbox="allow-same-origin",浏览器对 #锚点 的默认 fragment navigation 会清空整个 iframe 内容。所以在 onPreviewLoad 里统一拦截:外部链接(http/https/mailto)调系统浏览器打开;内部锚点先 decodeURIComponent 解码(目录链接的 href 是 URL 编码的中文),再用 getElementById 查找目标(不依赖 CSS 选择器,避免 id 以数字开头导致 querySelector 报错),最后 scrollIntoView 平滑滚动。

常见问题

macOS 解除 Gatekeeper 拦截

DMG 里的应用没有签名、也没公证,首次打开会被拦下,提示“无法验证开发者”或“M2P 已损坏,无法打开”。拖进 /Applications 后执行一行:

xattr -cr /Applications/M2P.app

在 DMG 挂载窗口里直接双击、或 DMG 本身报错,就先清 DMG 的隔离属性(换成你下载的文件名):

xattr -cr ~/Downloads/M2P-1.0.0-arm64.dmg

清完仍提示“已损坏”(Apple Silicon 上偶发),再补一次本地 ad-hoc 签名:

codesign --force --deep --sign - /Applications/M2P.app

提示 Operation not permitted 时加 sudo。xattr 只清除“来自网络下载”的隔离标记,不改应用内容;不建议用 sudo spctl --master-disable 关掉全局 Gatekeeper。

中文显示成方框

  • macOS 自带苹方 / 宋体,正常;
  • Windows 用系统自带的微软雅黑,正常;
  • Linux 需装中文字体:sudo apt install fonts-noto-cjk(Debian / Ubuntu)或 sudo dnf install google-noto-sans-cjk-fonts。

图片不显示

相对路径以 Markdown 文件所在目录为基准,未保存的新文档没有目录基准,先保存再预览或导出。

远程图片加载不出来

预览允许 https: 图片,但离线或需要登录的图床仍然加载不了;导出 PDF 走隐藏打印窗口,不受预览 CSP 限制。

批量导出很慢

每个文件都要走一次完整排版与打印,属预期行为,进度显示在状态栏。

Windows 版命令行看不到输出

GUI 子系统的 exe 没有标准输出流。CLI 导出建议用开发环境(./node_modules/.bin/electron .)或 Linux / macOS 版;Windows 上也可以用 --out 指定文件,直接看文件是否生成。

Linux 上 Electron 报 sandbox 错误

极少数发行版缺 SUID sandbox helper,启动加 --no-sandbox 可绕过(仅建议在受信环境使用)。

About

用NodeJS写的一个markdown编辑器,并支持导出pdf

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages