一个所见即所得的 Markdown 编辑器,类 Typora。同一套编辑器内核,三种形态:
| 形态 | 怎么跑 | 适合 |
|---|---|---|
| 桌面 App | npm start |
日常写作,原生打开/保存、拖拽文件、导出 PDF |
| 网页版 | npm run web → http://localhost:4321 |
随手改代码、跨平台 |
| 双击即用 | 直接打开 app/index.html |
零依赖零服务,拷到 U 盘就能用 |
网页版在 Chrome / Edge 里还能从地址栏装成 PWA,离线可用、有独立窗口。
到 Releases 下载对应的 DMG:
- Apple 芯片(M1/M2/M3/M4) →
Inkmark-x.y.z-arm64.dmg - Intel 芯片 →
Inkmark-x.y.z-x64.dmg
打开 DMG,把 Inkmark 拖进「应用程序」。
首次打开会被拦下来。 这个应用没有 Apple 开发者证书签名(一年 99 美元), 所以 macOS 会提示「无法验证开发者」。两种办法:
- 右键点图标 → 打开,在弹窗里再点一次「打开」。之后就正常了。
- 或者在终端里跑一行:
xattr -dr com.apple.quarantine /Applications/Inkmark.app应用做过 ad-hoc 自签名,所以不会出现「应用已损坏」那种更吓人的报错。 不放心的话可以自己从源码构建 —— 见下面的「自己构建」。
不想装应用的话,直接用网页版也是完整功能。
npm install # 依赖只有 electron(桌面版)和 electron-builder(打包)
npm start # 桌面版npm run web # 网页版,http://localhost:4321npm run icons # SVG → .icns(用 Electron 自带的 Chromium 渲染,不需要图形库)
npm run dist # 出 dist/Inkmark-x.y.z-{arm64,x64}.{dmg,zip}打包时 scripts/adhoc-sign.js 会给 .app 做一次 ad-hoc 自签名。
这一步不能省:没有开发者证书时 electron-builder 会整个跳过签名,
包里留着 Electron 原始签名却对不上被替换过的资源,macOS 会直接判定「应用已损坏」。
Typora 的手感来自一个很朴素的想法:语法标记一直都在 DOM 里,只是被 CSS 藏起来了。
非激活块: <h2><span class="mk">## </span>标题</h2> mk 是 display:none → 看到「标题」
光标进来: 同一个 DOM,加个 .active 类 mk 变可见 → 看到「## 标题」
于是:
- 切换渲染态只是加个 class,不重新解析、不重建 DOM
- 光标偏移量天然一致 —— 源码字符始终在 DOM 里,不需要任何位置映射
- 读回源码只要
textContent - 中文输入法不会被打断 —— 组字期间完全不动 DOM
整个编辑器都建立在这一条不变式上:
块元素.textContent === 该块的 Markdown 源码(逐字符相等)
#、>、- 这类块级前缀用绝对定位悬挂到正文左侧的边槽里,所以光标移进移出时
正文一个像素都不会跳,这也是 Typora 手感好的关键细节。
代码结构:
app/ ← 三种形态共用的全部界面与逻辑
js/markdown.js 块解析 / 行内渲染(不变式在这里)
js/editor.js 编辑内核:光标、输入、分块、撤销、查找
js/highlight.js 零依赖代码高亮
js/exporter.js 导出独立 HTML(摘掉标记、还原 ul/ol 嵌套)
js/html2md.js 富文本粘贴 → Markdown
js/platform.js 宿主适配层(浏览器 / Electron 两套实现)
js/app.js 界面装配,所有能力注册进 CMD 表
electron/main.js 窗口、原生菜单、文件对话框、导出 PDF
scripts/serve.js 零依赖静态服务器
platform.js 是唯一知道「自己跑在哪」的文件。编辑器内核对宿主一无所知,
这就是三种形态能共用一套代码的原因。
标题 1-6、加粗、斜体、删除线、行内代码、==高亮==、链接、图片、
自动链接、脚注引用、$公式$(按等宽显示,不做排版)、
引用、无序/有序列表、任务列表(可直接点勾)、多级缩进、
代码围栏(20+ 语言高亮)、表格(带对齐)、分割线、
以及白名单行内 HTML:<u> <mark> <kbd> <sub> <sup> <b> <i> <s> <del> <ins> <small> <br>。
和 Typora 基本一致。桌面版由原生菜单提供,网页版由页面接管。
| ⌘N / ⌘O / ⌘S / ⇧⌘S | 新建 / 打开 / 保存 / 另存为 |
| ⇧⌘E / ⌘P | 导出 HTML / 导出 PDF |
| ⌘B ⌘I ⌘U | 加粗 / 斜体 / 下划线 |
| ⇧⌘D / ⇧⌘H / ⌘E | 删除线 / 高亮 / 行内代码 |
| ⌘1…⌘6 / ⌘0 | 标题 1-6 / 转为正文 |
| ⇧⌘8 / ⇧⌘7 / ⇧⌘X / ⇧⌘9 | 无序 / 有序 / 任务 / 引用 |
| ⌘K / ⇧⌘I / ⇧⌘T / ⇧⌘K | 链接 / 图片 / 表格 / 代码块 |
| ⌘F | 查找替换 |
| ⌘/ | 整篇源码模式 |
| ⌘\ | 侧边栏(大纲 / 最近打开) |
| ⇧⌘F / ⇧⌘Y / ⇧⌘L | 专注模式 / 打字机模式 / 换主题 |
| Tab / ⇧Tab | 列表缩进 / 退回 |
| ⇧⏎ | 段内软换行 |
注意 ⌘0 是「转为正文」(跟 Typora 一致),恢复字号是 ⌥⌘0。
| 桌面版 | 网页版 | |
|---|---|---|
| 原生打开/保存对话框 | ✅ | Chrome/Edge ✅,其他浏览器退化为上传/下载 |
| 最近打开真能重新打开 | ✅ | ✅(文件句柄存在 IndexedDB,首次会要一次授权) |
拖拽 .md 进窗口 |
✅ | ✅ |
| 粘贴/拖入图片 | 存到 文档名.assets/ 目录 |
内嵌成 data URL |
| 导出 PDF | 隐藏窗口渲染后 printToPDF,干净 | 走系统打印对话框「存储为 PDF」 |
| 崩溃/刷新不丢内容 | 靠系统 + 关窗询问 | 草稿自动存 localStorage |
| Finder 双击 .md 打开 | ✅(打包后) | PWA 装好后可注册为 .md 处理器 |
这些是有意为之的简化,不是没写完:
- 表格光标移进去会摊平成源码行,移出来恢复表格。Typora 是保持表格形态另给一个工具条; 摊平的做法更直白,也不会出现「单元格里的换行往哪去」这类歧义。
- 块级 HTML(
<div>之类)按字面文本显示,不渲染。渲染它会破坏 「textContent === 源码」这条不变式,而整个编辑器都靠它。行内 HTML 走白名单,是安全的。 - 数学公式只做等宽着色,没有排版引擎(要排版得引入 KaTeX,那就不是零依赖了)。
- 有序列表编号不自动重排 —— 写
1. / 1. / 1.就显示 1/1/1。 源码怎么写就怎么显示,回车时会自动递增。 - Mermaid 图表没做。
- 桌面版
.app图标用的是 Electron 默认图标,app/icons/icon.svg需要转成.icns才能在打包时生效。
网页版注册了 Service Worker 做离线缓存,改完代码刷新看不到变化就是它。 调试时用:
http://localhost:4321/?nosw
npm run web 起的服务器已经强制 Cache-Control: no-store,所以只需要绕开 SW。