ai newsDo., 29. Okt.
EilmeldungNVIDIA Releases Nemotron 3.5 Transcribe: An Open 30B MoE Model for 40-Language Transcription with ~4B Active Parameters

AI 时代一群人在卷笔记软件,我想先把 Markdown 编辑器的底层做好

AI 时代,一群人在卷编辑器:AI 写作、AI 笔记、AI 知识库,一个比一个炫。 可打开底层看,大家用的还是那几套东西,遇到的也还是那几个老问题:内容存成 JSON,来回转 Markdown 时把你的原文改掉;所见即所得和源码模式是两套编辑器,切一次丢一点;协同一上来,结构就开始出怪事。AI 生成得再快,落到这些框架里照样会丢格式、错位置。 我做 ProseMirror 相关的产品三年了,这些坑基本都踩过。这次我不想再做一个编辑器应用,我想先把编辑器框架这件事做对。这篇讲其中一条线: MSFE(Markdown Source-First Editor),一个源码优先的 Markdown 编

dev.toDonnerstag 04:47 · 29.10.2 Min. Lesezeit
Bild: dev.to

AI 时代,一群人在卷编辑器:AI 写作、AI 笔记、AI 知识库,一个比一个炫。

可打开底层看,大家用的还是那几套东西,遇到的也还是那几个老问题:内容存成 JSON,来回转 Markdown 时把你的原文改掉;所见即所得和源码模式是两套编辑器,切一次丢一点;协同一上来,结构就开始出怪事。AI 生成得再快,落到这些框架里照样会丢格式、错位置。

我做 ProseMirror 相关的产品三年了,这些坑基本都踩过。这次我不想再做一个编辑器应用,我想先把编辑器框架这件事做对。这篇讲其中一条线: MSFE(Markdown Source-First Editor),一个源码优先的 Markdown 编辑器框架 。

先看看最近几年流行的笔记软件和编辑器,底层都是什么。开源的看了仓库源码,闭源的只采用官方文档和工程博客,官方没说的写“未公开”(截至 2026 年 10 月):

以上仅收集公开信息,方便对照技术路线,不评价产品好坏。每个产品的取舍都有它的场景。

不少主打本地、离线的产品,内容存的是数据库或 JSON,Markdown 主要用在导入导出上。

富文本派要读写 Markdown,就得在 ProseMirror 的 node、mark 和 Markdown 源码之间来回转换。问题是,这个转换只能“按规定转”。

社区的 tiptap-markdown 已经停止维护。它遇到表达不了的节点,会输出一个 [nodeName] 占位;表格一有合并单元格就降级成 HTML,列对齐信息也丢了。

这些不是 bug,是这个架构必然的结果: 文档的权威是那棵 JSON 树,Markdown 只是它的一种“打印方式” 。打印方式只有一种,所以你写的那份源码,早在第一次保存时就已经不在了。

常听到一种说法:Markdown 表达不了复杂结构,像多维表格、数据库视图,所以只能存 JSON。Logseq 的联合创始人在 2024 年解释为什么做数据库版本时,也提到 Markdown 的结构化数据支持有限,缺少持久 ID、时间戳这些东西。

Markdown 本来就有一个非常好用的容器: 代码围栏 。在 MSFE 里,多维表格、流程图、思维导图、白板都存成一个代码围栏,里面放的是可以反复打开编辑的数据:

``` quirekit-data-view {...表格的字段、记录、视图... } ``` Enter fullscreen mode Exit fullscreen mode 在 MSFE 里,它显示成一个能直接编辑的多维表格; 在 GitHub、VS Code 或者任何普通的 Markdown 工具里,它就是一段代码,不会坏,也不会丢; 用户点“确定”之后,才把新数据写回同一个围栏。其他行一个字节都不动。 复杂块的问题不在 Markdown,而在你有没有给它设计一个好的落点。

说 Markdown 无所不能也不诚实。它真正的限制在语法本身,我用 micromark 4.0.2 加 GFM 扩展逐条实测过:

1. 表格单元格里只能放行内内容。 想在单元格里放一个列表或者一段代码?表格当场被拆坏:

| A | B | | --- | --- | | - 一 - 二 | x | Enter fullscreen mode Exit fullscreen mode 解析结果:第一行变成 - 一 和一个空单元格, - 二 | x | 被挤出表格,成了表格外面的一个列表项。代码块放进单元格也一样,整张表都乱了。单元格里想分段,只能写 <br> 。

这是 **“加粗”** 后面的字 注意 **(重要)** 这里 Enter fullscreen mode Exit fullscreen mode 这两行都不会加粗,渲染出来就是原样的星号。原因是 CommonMark 判断 ** 能不能开始或结束强调,要看它两边是不是标点和空白。中文引号、括号是标点,后面又紧跟着汉字,规则就判它不成立。英文写作几乎碰不到这个问题,中文写作却天天碰到。

Markdown 派想做所见即所得,最常见的做法是:一套富文本编辑器,加一套源码编辑器,切换时互相转换。

Joplin 桌面端就是 TinyMCE 加 CodeMirror。它的官方帮助页很诚实,直接列出了在富文本编辑器里编辑 Markdown 笔记会出的问题:大多数插件格式会丢;表格没有表头会被补一个空表头;同一个列表里不能混用复选框和普通列表项;Markdown 里的 HTML 可能丢;引用式链接会被改成行内链接。

MarkText 是自研的 Muya 加 CodeMirror 5,读入用 marked 转成 HTML,写出用 turndown 再转回 Markdown。

富文本派做协同,基本都走 Yjs 加 y-prosemirror(Tiptap 的协同也是这套)。我们做 Word 级文档时专门测过,也翻了上游的文档和 issue:

上游自己承认的问题 (y-prosemirror 的 CAVEATS 和 issue):

我们自己实测的 (Yjs 13.6.33 + y-prosemirror 1.3.7、Tiptap 的 y-tiptap 3.0.9、Yjs 14 预发布版,三组结果一致):

这些不是部署问题,换个服务器、多加几台机器都解决不了。根子在“树形文档怎么映射成 CRDT”这件事上。

MSFE 的协同为什么没有这类问题 :MSFE 协同的是 Markdown 源码本身,一个 Yjs 文本。任何一段文本都是合法的 Markdown,所以不存在“结构不合法要删节点”;结构是从文本重新解析出来的,所以也不存在“拆段合段没有表示”。两个人在同一个地方改,结果可能需要人看一眼,比如把同一行表格改成了不同的样子,但内容不会因为结构冲突凭空消失。撤销是协同撤销,只撤你自己的修改。

很多人以为 CodeMirror 只能做源码编辑器,要做所见即所得就得上 ProseMirror。其实 CodeMirror 6 的装饰和 widget 足够做出“看起来就是富文本”的效果:标题变大、标记符号藏起来、表格画成表格、公式渲染成公式、代码围栏里的数据渲染成流程图和多维表格。可底下的文本一个字都没变。

MSFE 本身就提供三种可以编辑的呈现: 文档视图 (标记符号全部隐藏,看起来就是富文本)、 实时预览 (编辑到哪里,哪里露出 Markdown 标记)、 源码视图 。它们是同一个 CodeMirror 实例上的不同呈现,不是三套编辑器。切换时不做任何转换,选区和撤销历史都还在,改的永远是同一份 Markdown。

QuireKit 的 MD Editor 就是在 MSFE 上面拼出来的成品:加上工具栏、斜杠菜单这些界面,再加一个只读的阅读视图,用来渲染同一份源码。

实时预览 :光标在哪一行,哪一行就露出 Markdown 源码,比如这里的公式行变成了 LaTeX,其他地方照常渲染。

这个问题我其实不是第一次碰到。之前我做过一个开源项目 Incremark,专门解决 AI 流式输出时 Markdown 的增量渲染:模型每吐出一段文字,不重新解析整篇。做 MSFE 时我发现,编辑器的增量解析和流式渲染其实是同一个问题,而且在解析器这一层能做得更彻底。于是我直接改了 Lezer 的增量解析,在它上面重写了新版 Incremark。现在 MSFE 编辑、MD Editor 的阅读视图、AI 流式回复的显示,用的都是同一套解析。Incremark 的来龙去脉,以及我为什么放弃了第一版,后面单独写一篇细讲。

具体的优化是给 Lezer 的 Markdown 增量解析加了“复用证明”,解析器会记下哪些区间是原样复用的,编辑器据此直接跳过没变的部分。1 MiB 的文档连续输入 20 次:

上面这些,单独做一个编辑器应用也能做。我把它做成框架,是因为这些问题每个做编辑器的人都会碰到,没必要每人重新踩一遍。

import { createMsfeEditor } from ' @quirekit/msfe-core ' import { StarterKit } from ' @quirekit/msfe-starter-kit ' const editor = createMsfeEditor ({ parent, documentId: ' note-1 ', markdown: ' # Hello \n\n **Markdown** stays the source. \n ', mode: ' live ', extensions: [ StarterKit ], }) editor. getMarkdown () // 永远是那份源码 Enter fullscreen mode Exit fullscreen mode 内核不依赖 Vue 或 React,也提供 Vue、React 组件; 现成的扩展有:标题、加粗、链接、图片、代码块、公式和 Mermaid 等扩展语法、HTML 和 HTML 沙箱、流程图、思维导图、白板、多维表格; 协同(含 Hocuspocus 服务端)也是扩展。 这些都是 MIT 开源。AI 读写文档、改动以“待审阅”的形式显示,以及批注,是商业组件,开发和评估免费。

不想自己拼的,直接用 MD Editor:工具栏、四种视图、斜杠菜单、表格、公式、图表都配好了,几行代码就能放进 Vue 项目。

< script setup lang= "ts" > import { MdEditor } from ' @quirekit/md-editor/vue ' import ' @quirekit/md-editor/styles.css ' </ script > < template > <MdEditor markdown= "# Hello" label= "Document" /> </ template > Enter fullscreen mode Exit fullscreen mode 周边的编辑器也都是独立的包:公式编辑器(LaTeX 和 Word 公式互转)、流程图、思维导图、白板、Mermaid / PlantUML / Graphviz 文本绘图、多维表格,以及给 AI 边生成边显示用的流式 Markdown 渲染(Incremark)。所有界面都来自同一套 UI 组件库,亮色暗黑一键切换。

开源方案( @quirekit/md-editor-basic-export ,MIT) :用成熟的开源库来转。Markdown 转 Word 用 md-to-docx,Word 转 Markdown 用 Mammoth 加 Turndown,PDF 用 pdfmake,全在浏览器里完成。MSFE 特有的内容会降级:流程图、思维导图、白板导出成预览图,其他嵌入块变成占位。 每一处降级都会写进导出报告,不会悄悄丢掉。

商业方案 :不走“Markdown 转 HTML 再转 Word”这条路,而是把 Markdown 转进和我们分页文档编辑器 Classic 同一套文档模型,再交给 Classic 的排版引擎输出 Word、PDF 和 OFD:

Classic 就是上面提到的那个分页文档编辑器,是 QuireKit 的另一条线:基于 ProseMirror,自己排版、真正分页,画在 Canvas 或 SVG 上。它解决的是另一类问题,有机会单独写一篇。官网首屏就是它,可以直接上手试: https://quirekit.com

QuireKit 不是某一个编辑器,而是一整套编辑器生态:两套 Tiptap 风格的编辑器框架(ProseMirror 线和这篇讲的 CodeMirror 线 MSFE),上面是分页文档、块编辑器、Markdown 编辑器,以及一堆周边编辑器,共用一套 UI 组件库。开源部分全部 MIT,闭源商用也可以。

觉得这个方向有意思的话,点个 Star 就是对我最大的支持。也很想听听:你们做 Markdown 编辑器时,被哪个坑卡得最久?

Quelle: dev.to (dev.to). Original öffnen