ProseMirror 入门 - 从 contentEditable 到一次编辑闭环

返回 🗂 富文本编辑专题 · 相关私有笔记

适合谁:你已经熟悉浏览器、React/Vue 和应用状态,但还没有建立富文本编辑器的系统心智模型。

读完能回答:为什么 contentEditable 不该直接当数据源;Schema、Node、Mark、State、Transaction、View 各自做什么;一次输入如何回到可保存的文档。

先区分两棵树:浏览器 DOM 与编辑器文档

contentEditable 能让用户直接在 DOM 中输入,但浏览器同时会处理选区、输入法组合态、拼写检查、撤销栈和跨浏览器差异。它适合作为编辑界面,不适合作为应用唯一的数据源。

ProseMirror 因此维护另一棵树:文档模型。它是应用真正保存、校验、协同和导出的内容;浏览器 DOM 只是这份内容在编辑时的投影。

可以先记成:

浏览器 DOM:让人输入,保留浏览器瞬时状态
ProseMirror document:让程序保存、校验、比较和同步

这不是“DOM 完全不可信”。编辑器会尊重浏览器正在进行的输入,再把可接受的结果同步进文档模型;EditorView 与 NodeView 会解释这条边界。

六个名词,各自只负责一件事

名词现在只需理解的职责不要把它当成
Schema文档的运行时语法:允许哪些内容、怎样嵌套TypeScript 类型声明或 HTML 模板
Node要保存的结构单元,如段落、标题、图片浏览器的 Node
Mark附着在行内内容上的语义,如粗体、链接一层包裹内容的 DOM 标签
EditorState某一时刻的完整编辑快照只装文档字符串的 state
Transaction基于当前 state 提出的“一次变更”数据库事务或直接改 DOM 的命令
EditorView把 state 呈现到可编辑 DOM,并把输入转回变更React 的普通受控组件

先不用记 API。重要的是分清:内容结构由 Schema、Node、Mark 表达;某一时刻的编辑状态放在 EditorState;变化通过 Transaction;浏览器交互由 EditorView 协调。

用一份最小文档建立参照物

下面这份 JSON 表示“一个标题加一段含粗体文本的正文”:

{
  "type": "doc",
  "content": [
    {
      "type": "heading",
      "attrs": { "level": 2 },
      "content": [{ "type": "text", "text": "设计记录" }]
    },
    {
      "type": "paragraph",
      "content": [
        { "type": "text", "text": "Hello " },
        {
          "type": "text",
          "text": "world",
          "marks": [{ "type": "strong" }]
        }
      ]
    }
  ]
}
  • headingparagraphtext 都是 Node;它们组成文档树。
  • strongMark;它描述 world 的行内语义,不是一个新的块级结构。
  • levelattrs;它把同一种 Node 的具体数据放在内容之外。
  • 这份形状能否存在,由 Schema 决定。下一篇会从这份最小语言开始,而不是从字段清单开始。

一次输入如何闭环

假设光标在段落末尾,用户输入 !

浏览器把字符暂时放进 contentEditable DOM

EditorView 解释这次输入,构造 Transaction

EditorState.apply(transaction) 产生新的不可变 state

EditorView 用新 state 最小化同步 DOM,同时尽量保留选区和 IME 状态

这里最重要的边界是:任何持久内容变化最终都要落回 Transaction 和新的 EditorState,而不是把 DOM 当成最终事实。后续的撤销、协同、插件和序列化都建立在这条闭环上。

现在可以先跳过的术语

第一次阅读时,遇到下面术语只需知道它们属于后续层次,不必立刻理解:

  • StepMapping:把一次变更拆细,并让旧位置跟随文档变化。
  • ResolvedPosSliceNodeRange:在树中定位、复制粘贴和做结构变换的工具。
  • NodeView、Decoration、Plugin:默认渲染或文档内容不够用时的扩展点。

它们不是基础概念的例外;它们是基础概念在“位置、交互、表现、扩展”问题上的自然延伸。

接下来按目标阅读

参考