API 参考手册

本文档手册上次更新日期为:2021年6月13日,文档更新总览请查看:X_CHANGELOG

注:自 2020年9月29日全部翻译完成 以来,之后的每一次对应模块的更新都是与上游仓库进行合并,以保持翻译文档的最新版本,因此可能存在版本落后的问题,如果发现版本落后,请查看 原始文档,同时在 电报群 里找我合并一下上游仓库,我会尽快更新中文版。

译者前言:
  1. 鼠标悬浮在中文上会出现英文原文,方便读者在觉得翻译质量不行的时候直接查看原文。
  2. 专有名词不翻译,如 Schema、State、View 等,以让读者能在阅读源码或者在社区提问的时候对应上相应的概念。
  3. 因为有些接口需要上下文,因此译者的增加了注释以对此进行额外的说明,以灰色背景块显示出来,代表了译者对某个接口的理解。
  4. 本文档与 官网 保持同步(译者不定期检查原仓库,然后 Merge 最新的代码,最后翻译完成后更新本文档)。
  5. 本文档翻译工作是作者业余时间完成,你如果觉得有帮助可以 赏杯咖啡钱

本页面是富文本编辑器 ProseMirror 的 API 手册,它列出和描述了该库导出的全部接口。想了解更多关于它的介绍,请访问:指南

ProseMirror 由多个单独的模块构成。这个手册描述了每个模块导出的 API。例如,如果你想使用 prosemirror-state 模块,你可以像下面这样导入即可:

var EditorState = require("prosemirror-state").EditorState
var state = EditorState.create({schema: mySchema})

或者使用 ES6 的语法:

import {EditorState} from "prosemirror-state"
let state = EditorState.create({schema: mySchema})

prosemirror-state module

本模块实现了 ProseMirror 编辑器的 state 对象,以及关于选区 selection 和 插件 plugin 的抽象。

Editor State

ProseMirror 使用一个单独的大 对象 来保持对编辑器所有 state 的引用(基本上来说,需要创建一个与当前编辑器相同的编辑器)。 这个对象通过应用一个 transactions 来更新(即创建一个新的 state)。

注: transactions 按惯例在写代码的或者看源码的时候被缩写成 tr

EditorState class

ProseMirror 编辑器状态由此对象表示。一个 state 是一个持久化的数据结构--它本身并不更新,旧的 state 通过 apply 方法产生一个新的 state。

一个 state 有很多内建的字段,同时可以通过 plugins 来 定义 额外的字段。

doc: Node

当前文档

selection: Selection

当前选区。

storedMarks: ?⁠[Mark]

即将要应用到下一次输入的 marks。如果没有显式的设置 marks,此字段将会是 null。

schema: Schema

state 所表示的文档的 schema。

plugins: [Plugin]

在当前 state 中激活的 plugins。

apply(tr: Transaction) → EditorState

对旧的 state 应用给定的 transaction 以产生一个新的 state。

applyTransaction(rootTr: Transaction) → {state: EditorState, transactions: [Transaction]}

apply 的复杂版。该接口返回将应用到旧 state 以产生新 state 的每一个 transactions (其返回解构可能被插件的 transaction hooks 影响。)

tr: Transaction

从当前 state 生成一个新的 transaction 以对当前 state 进行修改。

注: 该 transaction 是一个 getter 函数,每次调用都会 new 一个新的 transaction。

reconfigure(config: Object) → EditorState

基于当前的 state 新建一个新的 state,只是新的 state 的中的字段会由传入的 plugins 重新配置。新旧两组 plugins 中的 state 字段中都存在的字段保持不变。 (相比于旧的 plugins 中)不再存在的字段将会被丢弃,新增的字段将会使用 plugin 的 state 对象的 init 方法进行初始化后作为新的 state 字段。

注: plugin 配置对象有一个 state 字段,其有两个方法,一个是 init 用来初始化 state;一个是 apply,用来决定如何更新 state。此 create 方法对于新增的 plugin 会调用其 state 的 init 方法进行初始化,以生成编辑器的 state。

config: Object

配置选项,之前还有一个 schema 属性可以配置,后来没;之前返回的 Plugin 是可选的,即可能什么也不返回,后来去掉了可选符号 ? 不知道什么原因,有兴趣的可以看下变更历史。

plugins: [Plugin]

新的激活的插件集合。

注: plugins 上的 state 构成新的编辑器的 state。

toJSON(pluginFields: ?⁠Object<Plugin> | string | number) → Object

将 state 对象序列化成 JSON 对象。如果你想序列化 plugin 的 state,则需要传递一个有着属性名-插件的映射关系的对象,该对象的属性名就会出现在返回值结果对象中。 参数也可以是字符串或者数字,但这种情况下参数会被忽略,以支持以 JSON.stringify 的方式调用 toString 方法。

注: 如果想序列化 plugin 的 state,需要 plugin 的 state 对象有提供 toJSON 方法,该方法的参数是 plugin 的 key。docselection 是保留字段,不能作为参数对象的属性名。

static create(config: Object) → EditorState

创建一个新的 state。

config: Object

state 配置选项。必须包含 schemadoc (或者两者都有)。

schema: ?⁠Schema

当前编辑器所使用的 schema。

doc: ?⁠Node

初始文档。

selection: ?⁠Selection

文档中可用的选区。

storedMarks: ?⁠[Mark]

stored marks 的初始集合。

plugins: ?⁠[Plugin]

state 中激活的 plugins。

static fromJSON(config: Object, json: Object, pluginFields: ?⁠Object<Plugin>) → EditorState

反序列化一个 state 的 JSON 表示。config 至少应该有一个 schema 字段,并且应该包含用来初始化 state 的 plugin 数组。 pluginField 参数通过在 JSON 对象中的属性名与 plugin 实例对应的方式来反序列化 plugin 的 state。

注: pluginFields 中的属性名如果对应到了某个 plugin 的 key(string),则会调用对应 plugin 的 state 的 fromJSON 方法, 如果没有对应到任一个 plugin 的 key,则会直接调 plugin 的 state 的 init 方法,前者参数是 config、插件对应的 json 和根据 config 生成的编辑器 state;后者参数是 config 和根据 config 生成的编辑器的 state。

config: Object

配置选项

schema: Schema

反序列化用到的 schema。

plugins: ?⁠[Plugin]

激活插件的集合。

Transaction class extends Transform

一个编辑器 state 的 transaction 可以被用来应用到 state 以创建一个更新的 state。 使用 EditorState.tr 来创建一个 transaction 实例。

注: EditorState.tr 是一个 getter 函数,每次调用都会 new 一个新的。

Transactions (它是 Transform 的子类)不仅会追踪对文档的修改,还能追踪 state 的其他变化, 比如选区更新以及 storedmarks 的调整。此外,你还可以在 transaction 中储存 metadata 信息, metadata 信息是一种很有用的信息形式以告诉客户端代码或者该 transaction 所代表的含义,然后它们以此来相应的更新它们 自己的 state

编辑器的 view 使用下面几个 metadata 属性:它会在 tr 上附加上 "pointer" 属性,值 true 表示由鼠标或者触摸点击触发的选区 transaction, 以及一个 "uiEvent" 属性,值可能是 "paste", "cut" 或者 "drop"

time: number

与当前 transaction 关联的时间戳,与 Date.now() 格式相同。

storedMarks: ?⁠[Mark]

当前 transaction 设置的 stored marks,如果有的话。

selection: Selection

该 transaction 的选区。默认是编辑器当前选区经过该 transaction mapped 后的选区,不过也会被 setSelection 方法给手动设置。

setSelection(selection: Selection) → Transaction

更新当前 transaction 的选区。其会决定 transaction 应用后编辑器的选区。

selectionSet: bool

选区是否被该 transaction 显式更新过。

注: 即在当前 transaction 中是否显式调用过 setSelection,一个 tr 在应用到 state 之前会 流过 所有的 plugin 的 apply 方法,因此这对于判断其他插件是否显式设置过选区很有用。

setStoredMarks(marks: ?⁠[Mark]) → Transaction

设置 stored marks。

ensureMarks(marks: [Mark]) → Transaction

确保 transaction 设置的 stored marks 或者如果 transaction 没有设置 stored marks 的话,确保光标位置的 marks,与参数给定的 marks 一致。 如果一致的话什么也不做。

注: 如果不一致的话,就让它们一致--这也是「确保」的含义。

addStoredMark(mark: Mark) → Transaction

在已经设置的 stored marks 集合中增加一个 mark。

removeStoredMark(mark: Mark | MarkType) → Transaction

在已经设置的 stored marks 集合中移除一个 mark 或者移除一种 mark。

storedMarksSet: bool

当前 transaction 是否显式设置了 stored marks。

setTime(time: number) → Transaction

更新该 transaction 的时间戳。

replaceSelection(slice: Slice) → Transaction

用给定的 slice 替换当前选区。

replaceSelectionWith(node: Node, inheritMarks: ?⁠bool) → Transaction

用给定的 node 替换当前选区。如果 inheritMarks 是 true 并且 node 的内容是 inline 的话,插入的内容将会继承插入点位置的 marks。

deleteSelection() → Transaction

删除选区。

注: 选区被删除了,其内容也一起被删除。

insertText(text: string, from: ?⁠number, to: ?⁠number = from) → Transaction

用包含给定文本的文本节点替换给定的 range,如果没有给定 range 的话则替换选区。

注: range 就是用 from 和 to 表示的一个范围。

setMeta(key: string | Plugin | PluginKey, value: any) → Transaction

在该 transaction 上储存一个 metadata 信息,可以以 name 或者 plugin 来区分。

注: 因为一个 transaction 可能会被不同的 plugin 设置不同的 metadata 信息,因此需要区分。key 可以传 PluginKey,或者简单一个字符串。

getMeta(key: string | Plugin | PluginKey) → any

用给定的 name 或者 plugin key 来获取设置的 metadata 信息。

注: 给定的 name 或者 plugin key 就是上面 setMeta 设置的 key,获取的就是 setMeta 设置的 value。

isGeneric: bool

如果该 transaction 没有包含任何 metadata 信息则返回 true,如此以来就可以被安全的扩展。

注: 有些场景需要对 transaction 做一些额外处理,如合并多个 step,此时如果某个 step 有 metadata 信息,则说明该 step 对某个 plugin 可能有其他的用途,就不能简单的合并 step。

scrollIntoView() → Transaction

当该 transaction 更新完 state 后,让编辑器将选区滚动到视图窗口之内。

注: 类似 chrome devtools 中,Elements 下对某个元素右键的 「Scroll into view」

Selection

一个 ProseMirror selection 可以是多种不同类型的选区。这个模块定义了一个基础文本选区 text selections(当然,光标是其中的一个特殊状态,即 selection 的内容为空) 和节点选区 node selections ,表示一个文档节点被选中。可以通过扩展 selection 父类来实现自定义的 selection 类型。

Selection class

编辑器选区的超类。所有的选区类型都扩展自它。不应该直接实例化。

new Selection($anchor: ResolvedPos, $head: ResolvedPos, ranges: ?⁠[SelectionRange])

用给定的 head 和 anchor 和 ranges 初始化一个选区。如果没有 ranges 给定,则构造一个包含 $anchor$head 位置的 range。

ranges: [SelectionRange]

选区覆盖到的 ranges。

$anchor: ResolvedPos

选区 resolved 过的 anchor 位置(即当选区变化的时候,其不动的一侧)。

$head: ResolvedPos

选区 resolved 过的 head 位置(即当选区变化时,移动的一侧)。

注: 「选区变化时」可能是用户造成的,如用户用鼠标从左到右选择,则选区起始(左侧)是 anchor,即「锚点」;选区右侧(鼠标所在位置)是 head,即动点。

anchor: number

选区的 anchor 的位置。

head: number

选区的 head 的位置。

from: number

选区位置较小一侧的位置。

注: 无论选区是如何选的,一般情况下 from 是选区的左侧起始位置。

to: number

选区位置较大的一侧。

注: 无论选区是如何选的,一般情况下 to 是选区的右侧结束位置。

注: 均不考虑多个选区的情况,而且似乎 chrome 等浏览器也不支持多选区,只是在一些编辑器中为了编辑方便,有多个选区的存在。

$from: ResolvedPos

resolve 过的选区的位置较小的一侧。

$to: ResolvedPos

resolve 过的选区的位置较大的一侧。

empty: bool

表示选区是否包含任何内容。

eq(Selection) → bool

测试当前选区与另一个选区是否相等。

map(doc: Node, mapping: Mappable) → Selection

通过一个 mappable 对象来 map 当前选区。 doc 参数应该是我们正在 mapping 的新的 document。

注: 一般通过 tr.doc 拿到将要 mapping 到的新的 document。

content() → Slice

获取选区内容的 slice 形式。

replace(tr: Transaction, content: ?⁠Slice = Slice.empty)

用给定的 slice 替换当前选区,如果没有给 slice,则删除选区。该操作会附加到给定 transaction 最后。

注: 替换后会将新的选区(光标)放到插入的内容的右侧。如果插入的内容是一个 inline 节点,则向右寻找该节点后面的位置。 如果不是 inline 节点,则向左寻找。

注: 英文原文档有多处使用了「backward」、「forward」、「back」之类的字眼,但是在不同的上下文中,其含义是不同的,因此此处意译为了「向左」或者「向右」, 不习惯的可以鼠标悬浮查看原英文文档。

replaceWith(tr: Transaction, node: Node)

用给定的 node 替换当前选区,该操作会附加到给定的 transaction 最后。

toJSON() → Object

将当前选区转换成 JSON 表示的格式。当在自己实现的 selection 类中实现此方法的时候,需要确保给这个返回的对象一个 type 属性, 属性值是你 注册 selection 时候的 ID。

getBookmark() → SelectionBookmark

获取一个选区的 bookmark,它是一个无需访问当前 document 即可被 mapped 然后再在 mapped 后通过给定一个 document 再解析成一个真实选区的值。(这个方法最可能被用在 history 中,以进行 选区追踪和恢复旧选区)该方法的默认实现仅仅是转换当前选区为一个文本选区,然后返回文本选区的 bookmark。

visible: bool

控制该选区类型在浏览器中被激活的时候是否对用户可见。默认是 true

static findFrom($pos: ResolvedPos, dir: number, textOnly: ?⁠bool) → ?⁠Selection

在给定的位置寻找一个可用的光标或叶节点选区,如果 dir 参数是负的则往左寻找,如果是正的则向右寻找。当 textOnly 是 true 的时候,则只考虑光标选区。 如果没有可用的选区位置,则返回 null。

注: 此方法对在粘贴或者一番操作后,不知道应该将光标放到哪个合适的位置时的情况尤为有用,它会自动寻找一个合适的位置,而不用手动 setSelection,对此种情况还有用的一个方法是下面的 near 方法。

static near($pos: ResolvedPos, bias: ?⁠number = 1) → Selection

在给定的位置寻找一个可用的光标或者叶节点选区。默认向右搜索,如果 bias 是负,则会优先向左搜索。

static atStart(doc: Node) → Selection

寻找一个给定文档最开始的光标或叶节点选区。如果没有可用的位置存在,则返回 AllSelection

static atEnd(doc: Node) → Selection

寻找一个给定文档最末尾的光标或者叶节点选区。

static fromJSON(doc: Node, json: Object) → Selection

反序列化一个选区的 JSON 表示。必须在自定义的 selection 类中实现该方法(作为一个静态类方法)。

static jsonID(id: string, selectionClass: constructor<Selection>)

为了能够从 JSON 中反序列化一个选区,自定义的 selection 类必须用一个字符串 ID 来注册自己,以消除歧义。 尽量要用一个不会与其他模块的类名冲突的字符串。

TextSelection class extends Selection

一个文本选区代表一个典型的编辑器选区,其有一个 head(移动的一侧)和一个 anchor(不动的一侧),二者都 指向一个文本块节点。它可以是空的(此时表示一个正常的光标位置)。

注: 文本块节点,即文本节点的直接父节点。如定义了 doc > p > text,则文本块节点即 p 节点。

new TextSelection($anchor: ResolvedPos, $head: ?⁠ResolvedPos = $anchor)

构造一个包含给定两点的文本选区。

$cursor: ?⁠ResolvedPos

如果当前选区是一个光标选区(一个空的文本选区),则返回其 resolved 过的位置,否则返回 null。

static create(doc: Node, anchor: number, head: ?⁠number = anchor) → TextSelection

用一个非 resolved 过的位置作为参数来创建一个文本选区。

static between($anchor: ResolvedPos, $head: ResolvedPos, bias: ?⁠number) → Selection

返回一个跨越给定 anchor 和 head 位置的选区,如果它们不是一个文本位置,则调用 findFrom 就近寻找一个可用的文本选区。 bias 决定就近向哪个方向寻找,默认是向左,值为负时是向右。如果文档不包含一个可用的文本位置, 则调用 Selection.near 方法。

NodeSelection class extends Selection

一个 node (节点)选区是一个指向单独节点的选区。所有的配置为 selectable 的 node 节点都可以是一个 node 选区的目标。在这个类型的选区中,fromto 直接指向选择节点的前面和后面, anchor 等于 fromhead 等于 to

注: node 选区就是当选中一个节点的时候的选区类型。

new NodeSelection($pos: ResolvedPos)

新建一个 node 选区。不会验证参数的可用性。

注: 因为不会验证参数的可用性,所以需要保证参数 $pos 是一个 resolved 过的可用 pos。

node: Node

当前选择的 node。

static create(doc: Node, from: number) → NodeSelection

以一个未 resolved 过的位置来新建一个 node 选区。

static isSelectable(node: Node) → bool

判断给的节点是否可以被选中作为一个 node 选区。

AllSelection class extends Selection

代表了选中整个文档的选区类型(此时可能用文本选区类型来表示不是必要的,比如当一个文档开头或者结尾有一个叶节点的时候)。

new AllSelection(doc: Node)

创建一个覆盖给定文档的 AllSelection 选区类型。

SelectionRange class

表示文档中的一个选区范围。

new SelectionRange($from: ResolvedPos, $to: ResolvedPos)
$from: ResolvedPos

选区范围位置较小的一侧。

$to: ResolvedPos

选区范围位置较大的一侧。

SelectionBookmark interface

一个轻量的,文档无关的选区形式。你可以对一个自定义选区类来自定义一个 bookmark 类型,使 history 正确处理它(自定义选区的 bookmark)。

map(mapping: Mapping) → SelectionBookmark

在一系列的文档修改后 map 该 bookmark 到一个新的 bookmark。

resolve(doc: Node) → Selection

将该 bookmark 再解析成一个真实选区。可能需要做一些错误检查,并且如果 mapping 后该 bookmark 变得不可用的话,则会回滚到 默认行为(通常是 TextSelection.between)。

Plugin System

为了让打包和扩展编辑器功能变得更容易,ProseMirror 提供了一个 Plugin 系统。

PluginSpec interface

这是一个传递给 Plugin 构造函数的类型。它提供了插件的配置。

props: ?⁠EditorProps

该插件设置的 视图属性。属性如果是函数,则函数的 this 将绑定到当前实例。

注: 对象属性是函数的话一般叫做对象的方法。

state: ?⁠StateField<any>

允许插件定义一个 state 字段,一个在编辑器整体 state 对象上的额外的插槽,其可以持有自己的插件 state。

key: ?⁠PluginKey

可以被用来唯一确定一个 plugin。在一个给定的 state 你只能有一个给定 key 的 plugin。 你可以通过这个 key 而不用访问插件实例来访问该插件的配置和 state。

view: ?⁠fn(EditorView) → Object

当插件需要与编辑器视图交互的时候,或者需要在 DOM 上设置一些东西的时候,使用这个字段。 当插件的 state 与编辑器 view 有关联的时候将会调用该函数。

returns: Object

应该返回有下列可选属性的对象:

update: ?⁠fn(view: EditorView, prevState: EditorState)

编辑器 view 一更新就调用该函数。

注: 编辑器 view 更新可能是用户的操作如输入内容,或者编辑器的操作,如由事件触发的 transaction 更新视图, 此处可以拿到编辑器的 view 和应用 transaction 之前的 state。

destroy: ?⁠fn()

当 state 对象被重新配置而不再含该插件或者编辑器视图被销毁的时候调用该函数。

注: 页面重载等情况会销毁编辑器的 view。

filterTransaction: ?⁠fn(Transaction, EditorState) → bool

如果有该函数,则该函数会在一个 transaction 被应用到 state 之前调用,以允许插件有机会取消该 transaction(通过返回 false)

appendTransaction: ?⁠fn(transactions: [Transaction], oldState: EditorState, newState: EditorState) → ?⁠Transaction

允许这个插件附加另一个 transaction 到将要被应用的 transactions 数组的末尾上去。 当另一个 plugin 又附加了一个 transaction 且其在当前 plugin 之后调用, 则当前 plugin 的该函数会再调用一次。但是仅含新的 transaction 和新的 state。也即是,它不会再将之前处理过的 transaction 再处理一次。

StateField interface

插件可能会提供一个该配置类型的 state 字段(在它的 state 属性上)。 它描述了插件想要持有的 state。该字段下的方法调用的时候,其 this 指向插件实例。

init(config: Object, instance: EditorState) → T

初始化插件的 state。config 是传递给 EditorState.create 的对象。 记住:instance 是一个半初始化的 state 实例,在当前插件之后初始化的插件在此时将不会有值。

注: 因此在新建 state 的时候,插件的顺序至关重要。

apply(tr: Transaction, value: T, oldState: EditorState, newState: EditorState) → T

应用给定的 transaction 到插件的 state 字段,以产生一个新的 state。 记住,newState 参数再一次的,是一个部分构造的 state,它不会包含当前插件之后还未初始化的插件的 state。

toJSON: ?⁠fn(value: T) → any

将当前字段值转换成 JSON。当然,你也可以留空以禁用当前插件 state 的序列化。

注: 所谓转成 JSON,在该文档所有对象的 toJSON 方法都是转成一个 plain object,而不是 JSON.stringify 得到的对象。

fromJSON: ?⁠fn(config: Object, value: any, state: EditorState) → T

反序列化给定的该字段的 JSON 表示对象。记住:state 参数还是一个半序列化的 state 对象。

Plugin class

Plugins 可以被添加到 editor 中,它们是 编辑器 state 的一部分,并且能够影响包含它的 state 和 view。

new Plugin(spec: PluginSpec)

创建一个 plugin。

props: EditorProps

当前插件导出的 属性

spec: Object

当前插件的 配置对象

getState(state: EditorState) → any

从编辑器的 state 上获取当前插件的 state。

PluginKey class

一个插件 key 用来 [标记]tag 一个插件,以能够在通过搜索编辑器的 state 来方便的找到它。

new PluginKey(name: ?⁠string = "key")

新建一个 plugin key

get(state: EditorState) → ?⁠Plugin

用 key 从 state 获取到 key 对应的激活的插件。

getState(state: EditorState) → ?⁠any

从编辑器的 state 中获取插件的 state。

prosemirror-view module

ProseMirror 的 view 模块用来在 DOM 中展示给定的 编辑器的 state,同时处理用户触发的事件。

当使用该模块的时候,要首先确认下已经加载了 style/prosemirror.css 模块作为样式表。

EditorView class

一个编辑器视图负责整个可编辑文档。它的 state 和行为由 props 决定。

注: 新建编辑器的第一步就是 new 一个 EditorView。

new EditorView(place: ?⁠dom.Node | fn(dom.Node) | {mount: dom.Node}, props: DirectEditorProps)

新建一个 view 视图,place 参数可能是一个 DOM 节点,表示编辑器的挂载点,或者一个函数,则编辑器将会被挂载在文档根节点 或者一个对象,它的 mount 属性的值表示编辑器的挂载 DOM,而如果是 null,编辑器将不会被放到文档中。

注: place 是一个函数的时候,函数的参数是通过 document.createElement('div') 新建的一个 DOM 节点, 该节点将会作为函数的唯一参数传入,该节点还未被放入真实文档中,需要你手动放入。

state: EditorState

编辑器当前的 state

dom: dom.Element

一个包含编辑器文档的可编辑 DOM 节点。(你不应该直接操作该节点的内容)

editable: bool

指示当前编辑器是否 可编辑

dragging: ?⁠{slice: Slice, move: bool}

当编辑器的内容被拖拽的时候,这个对象包含有拖拽内容相关的信息及该内容是否被复制还是被移动。在其他时候,该对象是 null。

composing: boolean

composition 事件触发的时候,该值为 true。

注: composition 事件与 CJK 输入法有关,也与浏览器实现有关,Safari 和 Chrome 中的 composition 触发顺序就不一样,以及一些其他差异。 这导致了一些使用 ProseMirror 的编辑器在 Safari 上的表现比较诡异,论坛中也有很多针对 Safari 反馈的 bug,大多跟 composition 有关。

props: DirectEditorProps

编辑器 view 的 props(属性)

注: props 是一个 getter 属性,每次通过 view.props 访问到的 props 带的一定是最新的 state。

update(props: DirectEditorProps)

更新 view 的 props。将会立即引起 DOM 的更新。

setProps(props: DirectEditorProps)

用给定的参数来更新已有的 props 对象,以达到更新 view 的目的。等同于 view.update(Object.assign({}, view.props, props))

updateState(state: EditorState)

单独更新编辑器 props 的 state 属性。

someProp(propName: string, f: ?⁠fn(prop: any) → any) → any

遍历给定属性名所有的值,在编辑器 props 中的属性优先,然后按照插件书写的顺序遍历插件的 props 上的该属性,获取它的值, 若遇到该属性的值不是 undefined 的话就调用 f 函数。当 f 函数返回一个真值,那么该 somePorp 函数则立即返回该属性值。如果 f 函数 未提供,则将其当成是一个拥有固定返回值的函数(即遍历到第一个给定属性且有值的话则直接返回该值)

注: 若提供了 f 函数,则 f 函数执行的时候,参数即为遍历到的 prop 的值(一般是个函数), 若 f 函数返回了真值,则 someProp 函数的返回值即为 f 函数本身,并停止遍历;若 f 函数返回了非真值, 则继续遍历,直到遇到真值才返回。 若 f 函数未提供,则如果 prop 的值不为 undefined,则直接返回该值。

注: 一般用法是 view.someProp('handleResize', v => v(view, state, slice, other)),这里的 v 即为你写的 prop 属性值。

hasFocus() → bool

查询当前 view 是否被 focus。

focus()

focus 编辑器。

注: 这个过程会用到特性检测,即检查 dom.focus({preventScroll: true}) 是否支持。

root: dom.Document | dom.DocumentFragment

获取编辑器所在的根节点。通常情况下是顶级节点 document,但是也可能是一个 shadow DOM 根节点,如果编辑器在它内部的话。

posAtCoords(coords: {left: number, top: number}) → ?⁠{pos: number, inside: number}

给定一对儿视口坐标信息,返回该坐标在文档中的位置。如果给定的坐标不在编辑器中,则会返回 null。 当返回一个对象时,pos 属性是离坐标最近的位置,inside 属性指示坐标落在的节点的内部节点的位置, 或者未为 -1,表示该坐标落在了顶级节点的位置,不在任何节点之内。

注: inside 属性举例:如果 table 结构是 table > td+ > p* > text*,则若 pos 落在了 text 内部,则 inside 就是 text 开头的位置;如果落在了 p 之前的位置(before),那就是 td 起始的位置(start)。

注: 这个方法非常有用,实际开发的时候会被大量用到,尤其是在处理事件相关的时候,需要 event 的 clientX 和 clientY 的坐标信息,以获得元素的位置信息。

注: 位置信息指的是编辑器内部的位置计数系统,是一个数字,如果想获取位置信息的更多内容,需要 resolve 一下,将其变成 resolvedPos,详见指南。

coordsAtPos(pos: number, side: ?⁠number = 1) → {left: number, right: number, top: number, bottom: number}

返回给定文档位置的相对于视口的坐标及大小信息。leftright 总是相同,因为该函数返回的是一个光标的的位置和大小信息。 如果该位置在两个并不直接相邻的元素之间,则 side 参数决定了这两个元素哪个元素被使用,当 side 小于 0 的时候,使用位置前面(左边)的元素,否则是后面的元素。

注: 光标只有高度没有宽度,因此只有 top 和 bottom 及 height 信息;left 和 right 总是一样的,width 总是 0.

注: 这个方法也很常用,不过一般情况下你不会用到 DOM 的坐标信息。

domAtPos(pos: number, side: ?⁠number = 0) → {node: dom.Node, offset: number}

返回给定位置的 DOM 节点。如果 side 参数是负的,则寻找离 position 最近的前面(左边的元素) 否则就寻找离位置最近的右边的元素。如果是 0,则会更倾向于返回一个较浅(?)的位置。

记住:你 绝对不应该 直接修改编辑器内部的 DOM,而只能查看它(虽然即使是检查它也是不必要的)

注: 查看它 的意思是只能获取 DOM 的信息,而不要设置。

nodeDOM(pos: number) → ?⁠dom.Node

寻找给定位置的 DOM 节点。如果位置不指向一个 node 前面或者该 node 是一个不透明的 node view 的话,则返回 null。

该方法设计的目的是让你能够在 DOM 上调用类似 getBoundingClientRect 方法。绝对不要 直接修改编辑器的 DOM 元素,也不要通过这种方式添加样式之类的,因为你的修改可能随着节点的重绘被立即覆盖掉。

注: domAtPos 获取的是给定位置的 DOM 宽高和坐标信息,nodeDOM 获取的是给定位置的 DOM。你可以通过 nodeDOM 获取到 DOM 后再手动获取位置信息。

posAtDOM(node: dom.Node, offset: number, bias: ?⁠number = -1) → number

返回给定 DOM 的位置信息。(它会尽可能的优先选择直接检查文档结构来获取位置信息,而不是用四处寻找逐个探测的方式,但是有些情况下,比如给定的是一个事件 target,那你别无选择只能逐个 target 的进行测试)

注: 这句话的意思是,如果你直接通过 ProseMirror 的接口,如 nodeDOM,通过 pos 获取到了 DOM,然后通过该方法相当于是一个逆过程,以获取到 pos。然而,如果你传给该函数的参数是来自于 event.target 那么 ProseMirror 只能通过挨个节点检查的方式,来确定它在 ProseMirror 的位置。

如果位置落在了一个叶子节点,那么 bias 参数可以用来决定使用叶子节点的哪一侧。

注: bias > 0 是右侧,否则是左侧,默认是左侧。

endOfTextblock(dir: "up" | "down" | "left" | "right" | "forward" | "backward", state: ?⁠EditorState) → bool

返回如果光标往给定方向移动的话,当前光标是否是一个文本 block 的末尾。例如,当给定方向为 「left」 的话,如果光标向左移动一个单位的距离将会离开文本 block,则会返回 true。 默认使用的是view 当前的 state,也可以传入一个不同的 state。

注: 文本 block,一般情况下指的是 paragraph 这种的,以 text 为直接子元素的节点。该方法的移动给定方向后检测的位置是 state.selection.$head。

destroy()

从 DOM 中移除编辑器,并销毁所有的 node views

dispatch(tr: Transaction)

派发一个 transaction。会调用 dispatchTransaction (如果设置了的话),否则默认应用该 transaction 到当前 state, 然后将其结果(新的 state)作为参数,传入 updateState 方法。该方法被绑定在 view 对象上,因此可以容易地被调用。

注: 必须调用 view.dispatch(transaction) 才可以触发一个更改。该方法一般情况下用在事件响应函数里面,但是你也可以用在任何能访问到 view 的地方。 反过来说,比如在 plugin 的 state 的 apply 内,你访问不到 view,也就不能 dispatch 一个 tr。如果你强行在其内 dispatch 了一个 tr(如通过将 view 放到 window 作为全局访问的方法),那么会导致循环调用以致内存溢出。

Props

EditorProps interface

Props 就是一些可以传递给编辑器的 view,或者用在插件中的值。这个接口列出了支持的 props。

不同的事件处理函数可能都返回 true 表示它们处理了相应的事件。view 将会在事件发生时帮你调用 preventDefault。但是 handleDOMEvents 中的事件需要你负责去手动调用。

不同的 prop 有不同的处理方式。prop 是函数的话则会在某个时刻调用:最开始的时候是寻找在 view 上的 prop,然后按照 plugin 书写的顺序查找其上的 prop,按顺序调用,直到它们中的某一个返回了 true 才终止。 而对于其他一些 porps,会使用遇到的第一个 prop 返回的值。

handleDOMEvents: ?⁠Object<fn(view: EditorView, event: dom.Event) → bool>

其是一个对象,键是 DOM 事件名,值是事件处理函数。事件处理函数将会先于 ProseMirror 处理任何发生在可编辑 DOM 元素上的事件之前调用。 与其他事件处理函数(此处指的是下面这些 ProseMirror 自己的事件处理函数)相反的是,当该函数返回 true 的时候,你需要手动调用 preventDefault(或者不调用,如果你想允许默认行为发生的话)

注: 可以理解为,handleDOMEvents 中定义的事件比较原始,一切都需要你自己来掌控。之所以其内定义的事件处理函数会发生于 ProseMirror 事件处理之前,一个原因我猜是因为 如果 ProseMirror 事件处理完了之后再调用用户定义的事件处理函数,则需要再处理一遍 DOM 的更新。

handleKeyDown: ?⁠fn(view: EditorView, event: dom.KeyboardEvent) → bool

当编辑器接收到一个 keydown 事件的时候调用。

handleKeyPress: ?⁠fn(view: EditorView, event: dom.KeyboardEvent) → bool

当编辑器接收到一个 keypress 事件的时候调用。

handleTextInput: ?⁠fn(view: EditorView, from: number, to: number, text: string) → bool

无论何时用户直接输入了文字的时候,该处理函数将会在输入内容应用到 DOM 之前调用。如果该函数返回 true,则用户输入文本到编辑器的默认行为将会被阻止。

注: 该方法通常用来拦截输入,然后生成新的输入,如自动转换 markdown 语法,或者按下某个键执行特殊操作的时候比较有用。

handleClickOn: ?⁠fn(view: EditorView, pos: number, node: Node, nodePos: number, event: dom.MouseEvent, direct: bool) → bool

为每一个点击事件冒泡路径上的节点从内到外都调用一遍该函数。如果是内部节点,则 direct 将会是 true。

handleClick: ?⁠fn(view: EditorView, pos: number, event: dom.MouseEvent) → bool

当编辑器被点击的时候调用,函数执行顺序位于 handleClickOn 函数之后。

handleDoubleClickOn: ?⁠fn(view: EditorView, pos: number, node: Node, nodePos: number, event: dom.MouseEvent, direct: bool) → bool

handleClickOn,只是针对双击事件。

handleDoubleClick: ?⁠fn(view: EditorView, pos: number, event: dom.MouseEvent) → bool

handleClick 只是针对双击事件。

handleTripleClickOn: ?⁠fn(view: EditorView, pos: number, node: Node, nodePos: number, event: dom.MouseEvent, direct: bool) → bool

handleClickOn,只是针对三击事件。

handleTripleClick: ?⁠fn(view: EditorView, pos: number, event: dom.MouseEvent) → bool

handleClick 只是针对三击事件

handlePaste: ?⁠fn(view: EditorView, event: dom.ClipboardEvent, slice: Slice) → bool

可以用来覆盖默认的粘贴行为。slice 是被编辑器格式化后的粘贴内容,不过你也可以通过直接访问事件对象来获取原始的粘贴内容。

注: 粘贴事件中的数据位于 event.dataTransfer 对象上。

handleDrop: ?⁠fn(view: EditorView, event: dom.Event, slice: Slice, moved: bool) → bool

当有东西被放入编辑器的时候调用。如果是从当前编辑器选区放入的,则 moved 参数会是 true(因此选区的内容应该被删除)。

handleScrollToSelection: ?⁠fn(view: EditorView) → bool

当 view 更新了 state 之后,尝试将选区滚动到视图中的时候调用该函数。该函数可能返回 false,表示它不处理滚动;或者返回 true,表示让默认行为发生。

createSelectionBetween: ?⁠fn(view: EditorView, anchor: ResolvedPos, head: ResolvedPos) → ?⁠Selection

在给定的起点和终点新建一个选区。

domParser: ?⁠DOMParser

parser 用来从 DOM 中读取编辑器的变化。默认情况下(如果不设置的话)调用 DOMParser.fromSchema 方法,参数是编辑器的 schema。

transformPastedHTML: ?⁠fn(html: string) → string

可以被用来在 HTML 文本被 parser 之前 转换一下。

clipboardParser: ?⁠DOMParser

用来从粘贴板中读取内容后 parser 。如果没有给,则使用 domParser 属性。

transformPastedText: ?⁠fn(text: string, plain: bool) → string

转换粘贴的纯文本。如果粘贴的文本是纯文本的话,plain 将会是 true。

clipboardTextParser: ?⁠fn(text: string, $context: ResolvedPos, plain: bool) → Slice

将粘贴板中的文本 parse 成文档 slice。将会在 transformPastedText 之后调用。 默认行为是将文本分割成多行,然后使用 <p> 标签包裹之,然后再对其调用 [clipboardParser](#view.EditorProps.clipboardParser)。如果粘贴的内容是纯文本,则plain` 将会是 true。

transformPasted: ?⁠fn(Slice) → Slice

可以用来在将粘贴的内容应用到文档之前转换一下。

nodeViews: ?⁠Object<fn(node: Node, view: EditorView, getPos: fn() → number, decorations: [Decoration], innerDecorations: DecorationSource) → NodeView>

允许你为 node 或者 marks 自定义渲染和行为逻辑。该对象键是 node 或者 mark 名,值是对应的构造函数。 构造函数返回一个 NodeView 对象,来实现节点的展示逻辑。 对于 nodes 来说,第三个参数 getPos 是一个函数,调用它可以获取 node 当前的位置,这对于创建一个 transaction 然后更新它很有用。 对于 marks 来说,第三个参数是一个 boolean 值,指示 mark 的内容是否是 inline 的。

decoration 是一个在当前 node 周围激活的 node decoration 或者 inline decoration 数组。 他们会自动绘制,通常情况下你可以忽略它们,不过它们也可以用来为 node view 提供上下文信息,而不是将它们添加到文档中。

注: 最后一句话的意思是,在 plugin.props 的 decoration 属性上,你可以通过构造 decoration 的时候添加一些额外的信息,然后在 node view 中拿到这些信息来搞事情。

innerDecorations 指向节点内容的装饰器。如果你的 view 没有内容或者没有 contentDOM 属性的话,你可以安全的忽略它,因为 编辑器将会把该 decorations 绘制到其内容上。不过如果你的 view 有内容,比如,用该内容创建一个嵌套的 editor,那么提供给其内部元素的内部 decorations 就是有用的。

注: 这个 innerDecorations 是新增的参数,之前是没有这个参数的,可以看下原仓库的修改记录。

clipboardSerializer: ?⁠DOMSerializer

该函数用来序列化 DOM,然后将其放入粘贴板。如果没有给定,则会使用 DOMSerializer.fromSchema 方法返回的结果。

clipboardTextSerializer: ?⁠fn(Slice) → string

当复制内容到粘贴板的时候,该方法将会被调用以用来获取选区内的文本。默认情况下,编辑器会在选区范围使用 textBetween 方法。

decorations: ?⁠fn(state: EditorState) → ?⁠DecorationSource

一个展示在 view 上的 document decorations(文档装饰器) 集合。

注: 之前该方法返回 DecorationSet,现在返回 DecorationSource,有兴趣的可以研究下区别。

editable: ?⁠fn(state: EditorState) → bool

当它返回 false,那么 view 的内容不能直接编辑。

注: 不能直接编辑的意思就是用户不能将光标放入进去,然后编辑。但是仍然可以通过 dispatch transaction 进行编辑。

attributes: ?⁠Object<string> | fn(EditorState) → ?⁠Object<string>

控制可编辑元素上的 DOM attributes。可以是一个对象,或者是一个函数接收编辑器的 state,然后返回一个对象。 默认情况下,元素将会被设置一个 「ProseMirror」 类名,以及一个由 editable prop 决定的 contentEditable attributes。 在此处提供的其他类名将会被附加上去。对于其他 attributes,最先提供的将会被使用(就像 someProp 一样)。

注: 原文中的 prop 和 attribute 我个人觉得应该分开翻译而不能都翻译成 属性,但是找不到合适的中文进行区分, 部分翻译资料(如上古时期的 jQuery),将 attr 和 prop 分别翻译成「属性」和「特性」,在此处感觉也不是很妥当,因此索性不翻译了。

scrollThreshold: ?⁠number | {top: number, right: number, bottom: number, left: number}

当滚动光标位置到视口的时候,决定光标与视口尾部的距离(单位是像素)多大才开始滚动。默认是 0。

scrollMargin: ?⁠number | {top: number, right: number, bottom: number, left: number}

当光标滚动到视口中的时候,决定光标离视口上下方的距离,默认是 5(像素为单位)。

DirectEditorProps interface extends EditorProps

直接在 view 中使用的 props 对象有两个字段不能被用在 plugin 的 props 字段上:

state: EditorState

编辑器当前的 state。

注: plugin 有自己的 state 字段,其与 props 平级,因此不作为 props 的属性。

dispatchTransaction: ?⁠fn(tr: Transaction)

view dispatch 一个 transaction 后(更新 state 前),transaction 会先经过此回调函数。 如果你设置了该函数,你应该需要保证该函数以调用 view 的 updateState 方法结束。 updateState 方法接受一个 applied 过该 transaction 的 state 作为参数。回调的 this 绑定到 view 实例上。

NodeView interface

默认情况下,文档节点使用它们 schema 配置对象中的 toDOM 方法来渲染,然后完全由编辑器管理状态。而对于一些使用场景,比如为特定节点嵌入编辑界面等情况, 你可能想要更加细粒度的控制一个节点在编辑器中的表现形式,因此,你需要 define 一个自定义的 node view。

Mark 的 view 仅仅支持 domcontentDOM,而且不支持任何 node view 的方法。

node views 们返回的对象必须保证有以下接口:

dom: ?⁠dom.Node

表示该文档节点的外层 DOM 节点。如果没有给出,那么默认的策略是创建一个 DOM 节点。

contentDOM: ?⁠dom.Node

应该持有节点的内容的 DOM 节点。只有当定义了 dom 属性且节点类型不是叶子节点的时候才有意义。 当它设置的时候,ProseMirror 将会将节点的子节点渲染到该 DOM 中作为它的子节点; 如果没有设置,node view 本身有责任渲染(或者决定不渲染)它的子节点。

update: ?⁠fn(node: Node, decorations: [Decoration], innerDecorations: DecorationSource) → bool

当 node view 更新自身的时候会调用该节点的此方法。它接受一个 node(可能是与当前不同的类型)、 一个激活的 decorations 数组(它会自行渲染,如果 node view 对它的新的不感兴趣的话可以忽略)作为参数。 如果 node 需要被更新,则返回 true,否则返回 false。如果 node view 有一个 contentDOM 属性(或者 dom 属性),则它的子节点的更新将交给 ProseMirror 来控制。

selectNode: ?⁠fn()

可以用来覆盖节点选中的展示状态(作为一个节点选区)。

deselectNode: ?⁠fn()

当定义一个 selectNode 方法,你应该同时提供一个 deselectNode 方法去移除前者所做的效果。

setSelection: ?⁠fn(anchor: number, head: number, root: dom.Document)

该方法将会被调用以用来处理节点内部选区的设置。anchorhead 位置相对于节点的起始位置。 默认情况下,将会在与这些位置相对应的 DOM 位置之间创建 DOM 选区,不过你可以通过覆盖该行为来做一些其他的事情。

stopEvent: ?⁠fn(event: dom.Event) → bool

可以用来阻止编辑器处理一些或者所有的源自 node view 的 DOM 事件。返回 true 表示编辑器不处理该事件。

ignoreMutation: ?⁠fn(dom.MutationRecord) → bool

当一个 DOM mutation(突变) 的时候调用,或者在 node view 内一个选区改变的时候调用。当是选区改变的时候,record 将会有一个值为 「selection」type 属性(原生的 mutation records 没有)。返回 false 表示编辑器应该重新读取选区或者重新 parse 突变附近的 DOM, 返回 true 表示该突变可以被安全的忽略掉。

destroy: ?⁠fn()

当整个编辑器被销毁或者当前 node view 被移除的时候调用(对 marks 的 view 不可用)。

View descriptions are data structures that describe the DOM that is used to represent the editor's content. They are used for:

  • Incremental redrawing when the document changes

  • Figuring out what part of the document a given DOM position corresponds to

  • Wiring in custom implementations of the editing interface for a given node

They form a doubly-linked mutable tree, starting at view.docView.

Decorations

装饰器是用来影响文档的展现但是又不实际改变文档内容的一种方式。

展现指的是视图层的东西如对话框等不是用户输入的内容,文档内容是指用户输入的内容--译者注)

Decoration class

Decoration(装饰器)对象可以通过 decoration 属性提供给 view。 它们有多个不同的变体,有关的详细信息,参见此类的静态成员。

注: Decoration 有三种,widget 挂件装饰器;inline 行内装饰器;node 节点装饰器;

from: number

decoration 开始的位置

to: number

decoration 结束的位置。如果是 widget decorations 的话,该值将会和 from 一致。

spec: Object

当创建 decoration 的时候提供的配置。用来存储一些额外的信息非常有用。

static widget(pos: number, toDOM: fn(view: EditorView, getPos: fn() → number) → dom.Node | dom.Node, spec: ?⁠Object) → Decoration

创建一个 widget decorations,它是一个显示在给定位置的 DOM 节点。推荐的方式是通过传递一个函数来返回 decoration,以实现当该 decoration 绘制在 view 的时候延迟渲染的目的,不过你也可以直接传递一个 DOM 节点。getPos 方法用来获取 widget 在当前文档的位置。

spec: ?⁠Object

支持以下可选参数:

side: ?⁠number

控制该 widget 与文档位置的哪一侧相关。当是负数的时候,它绘制在给定位置光标的之前, 并且在该位置插入的内容在 widget 之后。当非负(默认)的时候,widget 绘制在给定位置光标之后,用户输入的内容会插入到该位置之前。

当在同一个位置有多个 widget 的时候,他们的 side 决定了它们出现的顺序。较小的值出现在前面。 相同 side 值的话先后位置不确定。

注: 相同 side 值 widget 出现的先后位置不确定,原因跟某些算法排序的 稳定 概念类似。

marks 是 null 的时候,side 同样决定 widget 包裹的 marks。节点之前的是负的,节点之后的是正的。

marks: ?⁠[Mark]

绘制在 widget 周围的 marks。

stopEvent: ?⁠fn(event: dom.Event) → bool

可以用来控制编辑器应该忽略从 widget 冒泡出来的哪些 DOM 事件。

ignoreSelection: ?⁠bool

当设置的时候(默认是 false),在 widget 内的选区变化将被忽略, 这样的话该变化就不会让 ProseMirror 尝试重新同步该选区和 state 的选区。

key: ?⁠string

当比较此种类型的 decorations 的时候(以决定它是否应该被重绘),ProseMirror 将会默认通过 widget DOM 节点来识别。如果你传递了一个 key,那它就会用 key 来对比。 这对于你仅仅想在内存中创建 decorations 而不真正绘制 DOM 结构很有用。确保任何具有相同 key 的 widget 是可互换的--比如,如果 widget 的一些事件处理函数不一样,即使 DOM 结构相同,也应该有不同的 key。

static inline(from: number, to: number, attrs: DecorationAttrs, spec: ?⁠Object) → Decoration

创建一个内联的 decoration,它会在 fromto 之间的每一个内联节点上添加给定的 attributes。

spec: ?⁠Object

支持一下可选参数:

inclusiveStart: ?⁠bool

决定如果内容直接插入在这个位置的时候,decoration 的左侧如何 mapped。 默认情况下,decoration 不会包括新的内容,不过你可以设置为 true 来让它影响新内容。

inclusiveEnd: ?⁠bool

决定 decoration 的右侧如何被 mapped。具体看 inclusiveStart

static node(from: number, to: number, attrs: DecorationAttrs, spec: ?⁠Object) → Decoration

创建一个 node decoration。fromto 应该精确的指向在文档中的某个节点的前面和后面。该节点,也只有该节点,会受到给定的 attributes。

spec: ?⁠Object

decoration 存储的可选的信息。也用来比较 decorators(装饰器们)是否相等。

DecorationAttrs interface

一个被用来添加到被装饰的节点附近的 attributes 集合。大多数 properties 的名字与同名的 DOM attributes 一样,以用来被设置为属性值。下面几个是特例:

class: ?⁠string

会被 添加 到节点已有的类名上的 CSS 的类名,或者用空格分隔的 css 类名集合。

style: ?⁠string

会被 添加 到节点已有的 style 属性上的 CSS 字符串。

nodeName: ?⁠string

如果该值为非 null,则目标节点将会用该类型的节点包裹住(同时其他的属性会被应用到该元素上)。

DecorationSet class extends DecorationSource

一个 decorations 集合,用这种数据结构组织它们可以让绘制算法高效的对比和渲染它们。 它是一个不可突变的数据结构,它不改变,更新会产生新的值。

find(start: ?⁠number, end: ?⁠number, predicate: ?⁠fn(spec: Object) → bool) → [Decoration]

找到给定范围涉及到的所有的 decoration 集合(包括开始或结束位置在边界的 decorations), 然后用给定的 predicate 函数来检测是否匹配,该函数参数是 decoration 的配置对象。 若 startend 省略,则集合中所有的 decoration 将会被检测。如果 predicate 没有给出,则所有的 decorations 将会 match。

map(mapping: Mapping, doc: Node, options: ?⁠Object) → DecorationSet

Map decorations 的集合以响应文档修改。

options: ?⁠Object

有如下参数可选:

onRemove: ?⁠fn(decorationSpec: Object)

当设置该函数的时候,该函数会对在 mapping 过程中被移除的 decoration 调用该函数,传递 decoration 的配置对象。

add(doc: Node, decorations: [Decoration]) → DecorationSet

在当前 decorations 集合中增加给定数组中的 decorations,以产生一个新的集合。 需要传递当前文档 doc 以创建合适的树状结构。

remove(decorations: [Decoration]) → DecorationSet

用当前的 decorations 集合减去给定数组中的 decorations,得到一个新的 decorations 集合。

static create(doc: Node, decorations: [Decoration]) → DecorationSet

用给定文档的结构,创建一个 decorations 集合。

static empty: DecorationSet

decorations 的空集合。

DecorationSource interface

一个可以提供 decorations 的对象。被 DecorationSet 实现,会被传给 node views 方法。

prosemirror-model module

这个模块定义了 ProseMirror 的内容模型,它的数据结构被用来表示文档和其内的节点并让它们按预期工作。

Document Structure

一个 ProseMirror 的文档是一个树状结构。在每个层级中,一个 node 描述了内容的类型,同时通过 fragment 来保持对其子节点的引用。

Node class

这个类表示 ProseMirror 中组成文档树的节点,因此一个文档就是一个 Node 的实例,以及它的子节点同样是 Node 的实例。

节点都是一些持久化的数据结构。每次更新会创建一个新的节点与一些你想要的内容,而不是改变旧的节点。旧的节点始终保持旧文档的引用。 这使得在旧的和新的数据之间共享结构变得容易,因为像这样的树状结构没有「向后的指针」(?)

不要 直接修改 Node 对象的属性。查看 中文指南获取更多信息。

type: NodeType

当前节点的类型。

attrs: Object

一个键值对。允许的和需要的 attributes 取决于 节点类型。

content: Fragment

一个持有节点子元素的容器。

注: 该容器是 Fragment 类型

marks: [Mark]

应用到当前节点的 marks(marks 是一些类似于加粗或者链接一样的节点)

text: ?⁠string

对于文本节点,它包含了节点的文本内容。

nodeSize: number

表示该节点的大小,由基于整数的 indexing scheme 决定。 对于文本节点,它是字符数,对于其他叶子节点,是 1。对于非叶子节点,它是其内容的大小加上 2(开始和结束标签)。

注: indexing scheme 链接指向中文翻译指南,请搜索 Document 一节 下的 Indexing 一节。

childCount: number

该节点拥有的子节点个数。

child(index: number) → Node

获取给定 index 处的子节点。如果 index 超出范围,则返回错误。

maybeChild(index: number) → ?⁠Node

获取给定 index 处的子节点,如果存在的话。

注: 不存在返回 undefined。

forEach(f: fn(node: Node, offset: number, index: number))

对每个子节点调用 f 函数,参数是子节点、子节点相对于当前节点的偏移以及它的 index。

nodesBetween(from: number, to: number, f: fn(node: Node, pos: number, parent: Node, index: number) → ?⁠bool, startPos: ?⁠number = 0)

在相对于当前节点内容开始位置的两个位置之间对所有的后代节点递归的调用 f 回调。 回调的参数是后代节点、后代节点开始位置相对于当前节点的偏移、后代节点的父节点、以及它在父节点中的 index。

descendants(f: fn(node: Node, pos: number, parent: Node) → ?⁠bool)

对每一个后代节点调用给定的回调函数 f。当回调处理一个节点的时候返回 false ,则后续不会继续对该节点的子节点再调用该回调了。

注: 上述递归都是深度优先。

textContent: string

该节点的所有文本内容连接为一个字符串返回。

textBetween(from: number, to: number, blockSeparator: ?⁠string, leafText: ?⁠string) → string

获取 fromto 之间的所有文本内容。当 blockSeparator 给定的时候,它将会插入到每一个新的块级节点开始的地方。 当 leafText 给定的时候,它将会插入到遇到的每一个非文本叶子节点后面。

firstChild: ?⁠Node

返回节点的第一个子节点,如果没有则是 null

lastChild: ?⁠Node

返回节点的最后一个子节点,如果没有则是 null

eq(other: Node) → bool

测试两个节点是否表示的是文档中相同的部分。

注: 比较的手段是先比较节点的引用,如果相等直接为 true;否则比较 markup 是否相等,如果不是则返回 false,如果是再递归比较二者的子节点。

注: markup 指的是节点类型、节点 attributes、和其上的 marks。

sameMarkup(other: Node) → bool

比较当前与给定节点的 markup(包含类型、attributes 和 marks)是否相等。如果相同返回 true

hasMarkup(type: NodeType, attrs: ?⁠Object, marks: ?⁠[Mark]) → bool

检查节点是否有给定的类型、attributes 和 marks。

copy(content: ?⁠Fragment = null) → Node

新建一个与当前节点有相同 markup 的节点,包含给定的内容(如果没有给定内容则为空)。

mark(marks: [Mark]) → Node

新建一个当前节点的副本,包含给定的 marks,而不是当前节点原始的 marks。

cut(from: number, to: ?⁠number) → Node

创建一个当前节点的副本,该节点仅包含给定位置范围的内容。如果 to 没有给定,则默认是当前节点的结束位置。

slice(from: number, to: ?⁠number = this.content.size) → Slice

剪切文档给定位置范围的部分,然后作为 Slice 对象返回。

replace(from: number, to: number, slice: Slice) → Node

用给定的 slice 替换给定位置范围的文档内容。slice 必须「适合」该位置范围,也就是说,slice 打开的两侧必须能够正确的连接它两侧被切开的周围的内容, 同时它的子节点也必须符合放入位置的祖先节点的 scheme 约束。如果有任何违背,那么将会抛出一个 ReplaceError

nodeAt(pos: number) → ?⁠Node

返回给定位置右侧的节点。

@commetn 「右侧」为紧挨着给定位置的右侧节点,不存在则为 null。

childAfter(pos: number) → {node: ?⁠Node, index: number, offset: number}

如果有的话,返回给定偏移量后面的直接子节点,同时返回它的 index 以及相对于当前节点的偏移。

childBefore(pos: number) → {node: ?⁠Node, index: number, offset: number}

如果有的话,返回给定偏移量前面的直接子节点,同时返回它的 index 以及相对于当前节点的偏移。

resolve(pos: number) → ResolvedPos

resolve 文档中给定的位置,返回一个关于此位置上下文信息的 object

rangeHasMark(from: number, to: number, type: Mark | MarkType) → bool

测试文档中给定的位置范围内是否有给定的 mark 或者 mark 类型。

isBlock: bool

是否是一个块级节点(非内联节点的都是块级节点)。

isTextblock: bool

是否是一个文本块节点(textblock),即有内联内容的块级节点。

inlineContent: bool

节点是否允许内联内容。

isInline: bool

节点是否是内联节点(文本节点或者能够出现在文本之间的节点都是内联节点)。

isText: bool

是否是文本节点。

isLeaf: bool

是否是一个叶子节点。

isAtom: bool

是否是一个原子节点,例如,它没有一个直接可编辑的内容。它的值通常与 isLeaf 一样,不过可以通过节点配置对象上的 atom 属性 进行设置。 (典型的使用场景是节点展示成一个不可编辑的 node view)。

toString() → string

为了 debug 目的获取当前节点的字符串表示。

contentMatchAt(index: number) → ContentMatch

获取当前节点给定 index 的 content match。

注: content match 在 ProseMirror 中也是一个专有名词。

canReplace(from: number, to: number, replacement: ?⁠Fragment = Fragment.empty, start: ?⁠number = 0, end: ?⁠number) → bool

测试用给定的 fragment(默认是空的 fragment) 替换 fromto(from 和 to 是子节点位置 index) 之间的内容是否合法(即符合 schema 约束)。 你可以可选的传入 startend(start 和 end 都是子节点的位置 index)以只用 fragment 的一部分替换。

canReplaceWith(from: number, to: number, type: NodeType, marks: ?⁠[Mark]) → bool

测试用给定的节点类型替换当前节点 fromto index 之间的子元素是否合法。

canAppend(other: Node) → bool

测试给定节点的内容可以被附加到当前节点最后。如果给定节点是空的,那么只有当至少一个节点类型能够出现在这两个节点之内的时候才会返回 true(以避免合并完全不兼容的节点)。

check()

检查当前节点和节点的所有后代是否符合当前节点的 schema,如果不符合的话会抛出一个错误。

toJSON() → Object

返回一个当前节点 JSON 序列化的表示。

注: 不像我们认为的 JSON 序列化后与 stringify 过一样是个字符串,这里的序列化后是个对象。

static fromJSON(schema: Schema, json: Object) → Node

从一个节点 JSON 序列化的对象中反序列化出 Node 节点。

Fragment class

一个 fragment 表示了节点的子节点集合。

像 nodes 一样,fragment 也是一个持久化数据结构,你不应该直接修改他们或者他们的内容,而应该创建一个新的实例。下面的 API 就是用来试图将这件事变得容易。

size: number

fragment 的大小,也即它的内容节点大小的总和。

nodesBetween(from: number, to: number, f: fn(node: Node, start: number, parent: Node, index: number) → ?⁠bool, nodeStart: ?⁠number = 0)

对相对于 fragment 开始位置的两个位置范围内的节点调用 f 回调。如果某个节点的回调返回 false,则不会对该节点的内部节点再调用该回调了。

descendants(f: fn(node: Node, pos: number, parent: Node) → ?⁠bool)

对所有的后代元素递归调用给定的回调。如果某个节点回调返回 false 表示阻止再对该节点的子节点调用回调。

textBetween(from: number, to: number, blockSeparator: ?⁠string, leafText: ?⁠string) → string

Extract the text between from and to. See the same method on Node.

append(other: Fragment) → Fragment

创建一个包含当前 fragment 内容和给定 fragment 内容的新的 fragment。

cut(from: number, to: ?⁠number) → Fragment

从 fragment 剪切出给定范围的一个子 fragment。

replaceChild(index: number, node: Node) → Fragment

将 fragment 中的给定 index 位置的节点用给定节点替换掉后,创建一个新的 fragment。

eq(other: Fragment) → bool

将当前 fragment 与另一个 fragment 比较,看是否相等。。

注: 先比较 fragment 的内容大小,再逐个对内容节点调用节点的 eq 方法进行比较,一旦发现不一样的则返回 false,否则返回 true。

firstChild: ?⁠Node

返回当前 fragment 的第一个子节点,如果是空则为 null

lastChild: ?⁠Node

返回当前 fragment 的最后一个节点,如果是空则为 null

childCount: number

当前 fragment 的子节点数量。

child(index: number) → Node

获取 fragment 在给定 index 的子节点。如果 index 超出范围则抛出一个错误。

maybeChild(index: number) → ?⁠Node

获取给定 index 的子节点,如果存在的话。

forEach(f: fn(node: Node, offset: number, index: number))

为每一个子节点调用 f 函数,参数是子节点、子节点相对于当前节点的偏移、以及子节点的 index。

findDiffStart(other: Fragment) → ?⁠number

寻找当前 fragment 和给定 fragment 的第一个不同的位置,如果它们相同的话返回 null

findDiffEnd(other: Fragment) → ?⁠{a: number, b: number}

从后往前搜索,寻找当前 fragment 和给定 fragment 的第一个不同的位置,如果相同则返回 null。 因为该位置在两个节点中可能是不同的,因此该函数返回的是一个对象,带有两个不同的位置。

注: 对象是 {a: number, b: number}。

toString() → string

返回一个用来 debug 的 string 以描述该 fragment。

toJSON() → ?⁠Object

返回该 fragment 序列化后的 JSON 表示。

static fromJSON(schema: Schema, value: ?⁠Object) → Fragment

Deserialize a fragment from its JSON representation.

从该 fragment 的 JSON 表示中反序列化(parse)一个 fragment。

static fromArray(array: [Node]) → Fragment

用一个节点数组构建一个 fragment。带有相同 marks 的相邻文本节点会被合并到一起。

static from(nodes: ?⁠Fragment | Node | [Node]) → Fragment

用给定的类节点集合的对象中创建一个 fragment。如果是 null 则返回空 fragment。 如果是 fragment 则返回该 fragment 自身。如果是一个节点或者一个节点数组,则返回一个包含这些节点的 fragment。

static empty: Fragment

一个空的 fragment。没有包含任何节点的 fragment 都指向该对象(而不是为每个 fragment 都创建一个空的 fragment)。

Mark class

Mark 是可以被附加到节点上的一小段信息,比如加粗行内代码或者加粗链接字体。它有一个可选的 attributes 集合以提供更多信息 (比如链接的 target 信息等)。Marks 通过 Schema 创建,它控制哪些 marks 存在于哪些节点以及拥有哪些 attributes。

type: MarkType

当前 mark 的 type。

attrs: Object

与此 mark 相关的 attributes。

addToSet(set: [Mark]) → [Mark]

将当前 marks 加入到给定 marks 集合的右侧(后面)后返回新的 marks 集合。如果当前 marks 已经存在于给定集合当中 那么给定集合自身会被返回。如果给定集合中有任何 marsk 配置对象的 exclusive 属性值中有当前 mark,那么它会被用当前 mark 替换掉。

removeFromSet(set: [Mark]) → [Mark]

从给定的 marks 集合中移除当前 mark。如果当前 mark 不在集合中,那么给定集合本身会被返回。

isInSet(set: [Mark]) → bool

测试是否当前 mark 在给定 marks 集合中。

eq(other: Mark) → bool

测试当前 mark 与给定 mark 是否有相同的类型和 attributes。

toJSON() → Object

返回当前 mark 的 JSON 序列化的表示。

static fromJSON(schema: Schema, json: Object) → Mark
static sameSet(a: [Mark], b: [Mark]) → bool

测试两个 marks 集合是否一样。

注: marks 集合是否相同的比较是是先测试 marks 集合中的 mark 数量,然后逐个调用 mark 的 eq 进行比较。

static setFrom(marks: ?⁠Mark | [Mark]) → [Mark]

用给定的参数,新建一个 stored marks 集合,该参数可能是 null、单独一个 mark或者一个未排序的 marks 数组。

static none: [Mark]

marks 的空集合。

Slice class

一个 slice 表示从大的文档中切出去的一小段片段。它不仅存储着 fragment,还有它两侧的节点「开放」的深度(切割节点产生的)。

new Slice(content: Fragment, openStart: number, openEnd: number)

新建一个 slice。当指定一个非零的开放深度的时候,你必须保证该 slice 的 fragment 至少在这个深度存在节点。 例如,如果节点是一个空的段落节点,那么 openStartopenEnd 不能大于 1。

开放节点的内容无需一定要符合 schema 的内容限制,因为对于开放节点来说,它的内容应该被在一个有效的开始/结束/中间位置开放,而这具体取决于节点的哪一侧被打开。

注: 这句话举个例子比较好理解,如果 schema 内容限制 li 不能包含 p,但如果一个 slice 的 fragment 结构是 <li><p>123</p><p>456</p></li>,openStart 是 2,openEnd 也是 2 那么该 slice 切割(打开/开放)出来的节点就会形如 123</p><p>456, 因此也是一个有效的 slice,可以被放到文档中需要的地方去(如另个一 p 标签内)。

content: Fragment

该 slice 的内容片段。

注: 该内容片段以 Fragment 的实例形式存在。

openStart: number

开始位置的开放深度。

openEnd: number

结束位置的开放深度。

size: number

当将插入 slice 到文档中时,插入的内容大小。

eq(other: Slice) → bool

测试当前 slice 是否与另一个 slice 相等。

注: 相等比较是比较 slice 的内容,也即调用 fragment 的 eq 方法比较的,而且需要满足 openStart 相等和 openEnd 相等。

toJSON() → ?⁠Object

返回当前 slice 的 JSON 序列化的表示。

static fromJSON(schema: Schema, json: ?⁠Object) → Slice

从 slice 的 JSON 表示形式反序列化出一个 slice。

static maxOpen(fragment: Fragment, openIsolating: ?⁠bool = true) → Slice

从一个 fragment 新建一个 slice,该 slice 两侧的的打开值尽可能的大。

static empty: Slice

空的 Slice。

ReplaceError class extends Error

一种调用 Node.replace 方法时如果给定替换内容不可用的话会返回的错误类型。

Resolved Positions

在文档中的位置可以表示为一个整数的 offsets。不过你经常会想要使用一种更方便表达形式来使用位置信息。

ResolvedPos class

你可以 resolve 一个位置以得到该位置的更多信息。该类的对象就是表示这样一种 resolve 过的位置, 它提供一些上下文信息,以及一些有用的工具函数。

通过这个接口,对于那些接受可选参数 depth 的方法来说,如果没有传入该参数则默认会是 this.depth,如果是负数则会是 this.depth + value

pos: number

被 resolve 的位置。

depth: number

从根节点开始算,它的父节点的深度。如果位置直接指向根节点,则是 0。如果它指向一个顶级节点如段落,则是 1,以此类推。

parentOffset: number

该位置相对于父节点的偏移量。

parent: Node

该位置指向的父级节点。记住,即使一个位置指向的是文本节点,那该节点也不认为是父级,因为文本节点在 ProseMirror 世界里是 「扁平」的,它没有内容。

doc: Node

该位置被 resolve 的根节点。

node(depth: ?⁠number) → Node

在给定深度的祖先节点。p.node(p.depth)p.parent` 相同。

index(depth: ?⁠number) → number

在给定深度的祖先节点的 index。例如,如果该位置指向顶级节点的第二个段落的第三个节点,那么 p.index(0) 是 1,p.index(1) 是 2。

indexAfter(depth: ?⁠number) → number

在给定深度的祖先节点后面节点的 index。

start(depth: ?⁠number) → number

给定深度的祖先节点的开始位置(绝对位置)。

注: 绝对位置是相对于 doc 根节点的位置,一般都是用它来定位。

end(depth: ?⁠number) → number

给定深度的祖先节点的结束位置(绝对位置)。

before(depth: ?⁠number) → number

在给定深度的祖先节点之前的(绝对)位置,或者,如果 depththis.depth + 1 的话,则是原始的位置。

after(depth: ?⁠number) → number

在给定深度的祖先节点之后的(绝对)位置,或者如果 depththis.depth + 1 的话则是原始的位置。

注: 「before 之前」、「start 开始」、「after 之后」、「end 结束」位置的区别:有以下结构 <p>123</p>,则(I表示「这个」位置) I<p>123</p> 表示 before<p>I123</p> 表示 start<p>123I</p> 表示 end<p>123</p>I 表示 after

textOffset: number

当位置指向一个文本节点,该函数返回当前位置到文本节点开始位置的距离。如果指向节点之间则是 0。

nodeAfter: ?⁠Node

获取紧挨着该位置后的节点,如果有的话。如果位置指向一个文本节点,则只有在文本节点中该位置之后的内容会被返回。

nodeBefore: ?⁠Node

获取紧挨着该位置前的节点,如果有的话。如果位置指向一个文本节点,则只有在文本节点中该位置之前的内容会被返回。

posAtIndex(index: number, depth: ?⁠number) → number

获取在给定深度的祖先节点的给定 index 的位置(深度默认是 this.depth)。

marks() → [Mark]

充分考虑 marks 们的 inclusive 属性后,获取当前位置的最终的 marks。如果该位置是在一个非空节点的起始位置,则会返回该位置之后节点的 marks(如果有的话)。

注: 如果位置在一个空元素内,则返回空的数组(即 Mark 的静态属性,Mark.none)。如果是在一个文本节点中,则简单返回文本节点的 marks。 如果在一个非空节点的起始位置(before 为空),则考虑该位置之后节点的 marks。最后(此时只剩一种情况,也即在一个非文本节点的末尾位置)考虑排除那些设置了 inclusive 属性为 false 的 marks 们。

marksAcross($end: ResolvedPos) → ?⁠[Mark]

获取在当前位置之后的 marks,如果有的话,会排除那些 inclusive 为 false 以及没有出现在 $end 位置的 marks 们。 这个方法最有用的场景是在执行删除操作后获取需要保留的 marks 集合。如果该位置在它的父级节点的结束的地方或者它的父级节点不是一个文本 block,则会返回 null(此时不应该有任何 marks 被保留)。

sharedDepth(pos: number) → number

返回在给定位置和当前位置拥有的相同父级节点所在的最大深度。

blockRange(other: ?⁠ResolvedPos = this, pred: ?⁠fn(Node) → bool) → ?⁠NodeRange

根据当前位置与给定位置围绕块级节点的周围看返回相应的 Range。例如,如果两个位置都指向一个文本 block,则文本 block 的 range 会被返回; 如果它们指向不同的块级节点,则包含这些块级节点的深度最大的共同祖先节点 range 将会被返回。你可以传递一个指示函数,来决定该祖先节点是否可接受。

sameParent(other: ResolvedPos) → bool

当前位置和给定位置是否具有相同的父级节点。

max(other: ResolvedPos) → ResolvedPos

返回当前位置和给定位置较大的那个。

min(other: ResolvedPos) → ResolvedPos

返回当前位置和给定位置较小的那个。

NodeRange class

表示一个内容的扁平范围(range),例如,一个开始和结束在相同节点的范围。

new NodeRange($from: ResolvedPos, $to: ResolvedPos, depth: number)

构造一个节点 range。至少深度在 depth 及更小的时候 $from$to 应该始终指向相同的节点,因为一个节点 range 表示具有相同父级节点的相邻节点的集合。

$from: ResolvedPos

range 的内容开始处 resolve 过的位置。它可能有一个大于该 range 的 depth 属性的深度,因为这些位置是用来计算 range 的,其不会直接在 range 的边界再次 resolve。

$to: ResolvedPos

range 的内容结束处 resolve 过的位置。看一下关于 $from 的警告。

注: 举个例子:有以下结构 <ul><li><p>abc</p></li><li><p>123</p><p>456</p></li></ul> 则构造一个 NodeRange 的时候,如果 $from 在 1 后面位置, $to 在 4 后面位置,则 depth 必须是在第二个 li 的开始位置的深度或者更小,因为如果再深的话,$from 和 $to 就没有共同的父级节点,就无法构建一个 NodeRange。 也因此,$from 和 $to 的 depth 属性是有可能大于 NodeRange 的 depth 属性的。

depth: number

该 range 指向的节点的深度。

start: number

该 range 开始的位置。

end: number

该 range 结束的位置。

parent: Node

该 range 所在的父级节点。

startIndex: number

该 range 在父级节点中的开始处的 index。

endIndex: number

该 range 在父级节点中结束处的 index。

Document Schema

每个 ProseMirror 文档都符合一个 schema 约束,它描述了节点的集合和 marks,以及它们之间的关系,比如哪些节点可以作为其他节点的子节点等。

Schema class

一个文档的 schema。对可能出现在文档中的 nodes 和 marks 提供相应的 node typemark type 对象, 以及提供相应创建和序列化这样一个文档的函数。

new Schema(spec: SchemaSpec)

构造一个 schema 从一个 schema specification(配置对象)中。

spec: SchemaSpec

当前 schema 所基于的 spec(配置对象),其中的 nodesmarks 属性可以保证是 OrderedMap 的实例(不是原始对象)。

nodes: Object<NodeType>

一个 schema 中节点名和节点类型对象的键值对映射。

marks: Object<MarkType>

一个 mark 名和 mark 类型对象的键值对映射。

topNodeType: NodeType

当前 schema 的 默认顶级节点 类型。

cached: Object

一个用于计算和缓存每个 schema 中的任何类型值的对象。(如果你想要在其上储存一些东西,要保证属性名不会冲突)

node(type: string | NodeType, attrs: ?⁠Object, content: ?⁠Fragment | Node | [Node], marks: ?⁠[Mark]) → Node

在 schema 中新建一个节点。type 参数可以是一个字符串或者一个 NodeType 的实例。Attributes 会被以默认值扩展,content 可能是一个 FragmentnullNode 或者一个节点数组。

text(text: string, marks: ?⁠[Mark]) → Node

在 schema 中新建一个文本节点。不允许创建空的文本节点。

注: 文本节点和文本块节点不同,注意区分。

mark(type: string | MarkType, attrs: ?⁠Object) → Mark

用给定的类型和 attributes 创建一个 mark。

nodeFromJSON(json: Object) → Node

从一个 JSON 表达式中反序列化出一个节点。该方法 this 已经绑定当前对象。

注: JSON 表达式其实并不是 JavaScript 中通常意义上的 JSON 字符串,而是一个普通对象,它及它的键值都是 plain object。该对象由相应的 Node.toJSON 生成。

markFromJSON(json: Object) → Mark

从一个 JSON 表达式中反序列化出一个 mark。该方法 this 已经绑定当前对象。

注: 该对象由相应的 Mark.toJSON 生成。

SchemaSpec interface

一个描述 schema 的对象,用来传递给 Schema 构造函数

注: 就是 schema 的配置对象,ProseMirror 中的 xxxSpec 都是 xxx 的配置对象,如 NodeSpec、MarkSpec 等。

nodes: Object<NodeSpec> | OrderedMap<NodeSpec>

当前 schema 中所有的 node 类型的对象。对象中,键是节点名,对象的键是对应的 NodeSpec。 节点们在该对象中出现的先后顺序是非常重要的,它决定了默认情况下哪个节点的 parse rules 优先进行, 以及哪个节点是一个 group 优先考虑的节点。

marks: ?⁠Object<MarkSpec> | OrderedMap<MarkSpec>

当前 schema 中的所有 mark 类型的对象。它们出现的顺序决定了在 mark sets 中的存储顺序,以及 parse rules 的处理顺序。

topNode: ?⁠string

当前 schema 顶级节点的名字,默认是 "doc"

NodeSpec interface

content: ?⁠string

就像在 schema guide 中描述的一样,为当前节点的内容表达式。 如果没有给定,则该节点不允许任何内容。

注: schema guide 链接指向中文翻译指南,请搜索 Schema 下的 Content Expressions 一节。

marks: ?⁠string

当前节点允许的 marks 类型。可能是一个空格分隔的字符串,内容是 mark 的名字或者 group 名。 "_" 表示明确允许所有的 marks,或者 "" 表示禁止所有的 marks。如果没有设置该字段,则节点含有的内联内容将会默认允许所有的 marks, 其他不含内联内容的节点将默认不允许所有的 marks。

group: ?⁠string

当前节点所属的 group,可以出现多个,用空格分隔,可以指向当前 schema 的内容表达式(content expressions)。

inline: ?⁠bool

对于内联节点,应该被设置为 true(文本节点隐式的被设置为 true)。

atom: ?⁠bool

可以被设置为 true,以表示即使当前节点不是一个 leaf node,但是其也没有直接可编辑内容, 因此在 view 中应该被当成是一个独立的单位对待。

注: 「独立单位对待」指的是,如在计数上,应该是 1;在事件上,内部元素触发的事件应该被视作是该节点触发的,等。

attrs: ?⁠Object<AttributeSpec>

当前节点拿到的 attributes。

selectable: ?⁠bool

控制当前类型的节点是否能够被作为 node selection 所选中。 对于非文本节点来说,默认是 true。

draggable: ?⁠bool

决定在未选中的情况下,当前类型的节点能否被拖拽。默认是 false。

code: ?⁠bool

指示当前节点包含 code,其会引起一些命令有特别的行为。

注: 「特别的行为」如,在 code 节点中的内容如果是 li 和 文档中的 li 是两个处理逻辑,前者针对 code 块处理;后者针对 li 进行处理。

defining: ?⁠bool

决定当前节点是否在替换操作中被认为是一个重要的父级节点(如粘贴操作)。当节点的内容被整个替换掉的时候, 若该节点的 defining 为 false(默认),则其会被移除,但是 defining 为 true 的节点会保留,然后包裹住替换进来的内容。 同样地,对于 插入的 内容,那些有着 defining 为 true 的父级节点会被尽可能的保留。一般来说,非默认段落的文本块节点类型及 li 元素,defining 应该是 true。

注: 最后一句话讲的是,例如,默认的 paragraph 中,文本块节点,粘贴的时候应该直接替换掉它的父节点,也即另一个文本块。 但是对非默认 paragraph(即你自己定制的 paragraph)的话,在替换内容的时候,就需要保留该 非默认 paragraph 的一些属性,不能直接替换。同理 li 元素, 因为首先选中 li 元素内容,然后粘贴内容是一个很常见的操作,用户的预期是将粘贴内容作为 li 的内容,而不是直接替换掉 li 而粘贴成 paragraph(或其他 block)。

isolating: ?⁠bool

当该属性设置为 true 时(默认是 false),当前类型的节点的两侧将会计算作为边界,于是对于正常的编辑操作如删除、或者提升,将不会被跨越过去。 举个例子,对于 table 的 cell 节点,该属性应该被设置为 true。

注: 「提升」操作指的是,如在一个二级 li 中,一般用户习惯下,按 shift + tab 会将该二级 li 提升到一级 li。

注: 「跨越」指的是,操作会跨过当前节点到达下一个(或者上一个)节点。如删除操作,在段落起始位置继续按删除键,光标会跑到上一个节点的尾部; 在 li 起始位置按删除键,光标会跑到上一个 li 结尾处或者直接删除整个 ul/ol;但是在 table 的 td 中,在 td 起始位置按删除键跑到上一个 td 结尾, 显然不是预期。

toDOM: ?⁠fn(node: Node) → DOMOutputSpec

定义当前节点的默认序列化成 DOM/HTML 的方式(被DOMSerializer.fromSchema使用)。 应该返回一个 DOM 节点或者一个描述 ODM 节点的 array structure,它带有可选的数字 0 (就是「洞」), 表示节点的内容应该被插在哪个位置。

对于文本节点,默认是创建一个文本 DOM 节点。虽然创建序列化器以将文本节点特殊渲染是可能的,但是当前编辑器并不支持这样做,因此你不应该覆盖文本节点中的该方法。

parseDOM: ?⁠[ParseRule]

当前节点相关的 DOM parser 信息,会被 DOMParser.fromSchema 使用以自动的衍生出一个 parser。Rule 中的 node 字段是隐式的(节点的名字会自动填充)。如果你在此处提供了自己的 parser,那你就不需要再在 schema 配置的时候提供 parser 了。

注: 配置 Editor view 的时候可以配置一个 Parser 和 Serializer,如果提供,则此处就不用写 parseDOM 了。

toDebugString: ?⁠fn(node: Node) → string

定义一个该类型节点被序列化成一个字符串形式的默认方法,以做 debugging 用途。

MarkSpec interface

attrs: ?⁠Object<AttributeSpec>

当前 mark 类型拿到的 attributes。

inclusive: ?⁠bool

当光标放到该 mark 的结尾处(或者如果该 mark 开始处同样是父级节点的开始处时,放到 mark 的开始处)时,该 marks 是否应该被激活。默认是 true/

注: 「被激活」的意思是,可以通过 API 获取光标所在的 resolvedPos 信息以查到相关的 marks,对用户来说被激活意味着在该地方输入内容会带上相应的 marks。

excludes: ?⁠string

决定当前 mark 是否能和其他 marks 共存。应该是由其他 marks 名或者 marks group 组成的以空格分隔的字符串。 当一个 marks 被 added 到一个集合中时,所有的与此 marks 排斥(excludes)的 marks 将会被在添加过程中移除。 如果当前集合包含任何排斥当前的新 mark 的 mark,但是该新 mark 却不排斥它,则当前新的 mark 不会被添加到集合中。你可以使用 "_" 来表明当前 marks 排斥所有的 schema 中的其他 marks。

注: 该段的主要意思是,第一:假设 A 、B 互斥,则 无论 A 添加到包含 B 的集合,还是 B 添加到 包含 A 的集合,已经在集合中的一方会被移除以添加新的 mark; 第二:若假设 A 排斥 B,B 却不排斥 A,则将 B 添加到包含 A 的集合中去的时候,将不会被添加进去。

默认是相同类型的 marks 会互斥。你可以将其设置为一个空的字符串(或者任何不包含 mark 自身名字的字符串) 以允许给定相同类型的多个 marks 共存(之哟啊他们有不同的 attributes)。

group: ?⁠string

当前 mark 所属的 一个 group 或者空格分隔的多个 groups。

spanning: ?⁠bool

决定当序列化为 DOM/HTML 的时候,当前类型的 marks 能否应用到相邻的多个节点上去。默认是 true。

toDOM: ?⁠fn(mark: Mark, inline: bool) → DOMOutputSpec

定义当前类型的 marks 序列化为 DOM/HTML 的默认方式。如果结果配置对象包含一个「洞」,则洞的位置就是 mark 内容所在的位置。否则,它会被附加到顶级节点之后。

注: 「否则,它会被附加到顶级节点之后」字面意思吗?有待实验,本人貌似没有印象了。

parseDOM: ?⁠[ParseRule]

当前 mark 的相关的 DOM parser 信息(具体请查看相应的 node spec field)。 在 Rules 中的 mark 字段是隐式的。

AttributeSpec interface

用来 define node 或者 marks 的 attributes。

default: ?⁠any

该 attribute 的默认值,当没有显式提供值的时候使用。如果 attributes 没有默认值,则必须在新建一个 node 或者 mark 的时候提供值。

NodeType class

每个 Node Type 只会被 Schema 初始化一次,然后使用它来tag(归类) Node 的实例。 这种对象包含了节点的类型信息,比如名称以及它表示那种节点。

name: string

该节点类型在 schema 中的名称。

schema: Schema

一个指向节点类型所属 Schema 的指针。

spec: NodeSpec

当前类型的配置对象。

contentMatch: ContentMatch

节点类型内容表达式的起始匹配。

注: sorry,这个 contentMatch 我用的比较少,所以也不知道是什么意思,貌似源码内部使用的比较多。

inlineContent: bool

如果当前节点类型有内联内容的话,即为 true。

isBlock: bool

当前节点是块级类型的话,即为 true。

注: 判断是否是块级类型是用排除法,如果不是内联类型(即 spec.inline 是 false)且节点类型的名称不是「text」,则该类型是块级类型。

isText: bool

如果是文本类型的节点,即为 true。

注: 也即节点名字是「text」。

isInline: bool

如果是一个内联类型,则为 true。

注: 同样使用排除法,即与 spec.isBlock 互斥。

isTextblock: bool

如果节点是文本块类型节点则为 true,即一个包含内联内容的块级类型节点。

注: 一个块级类型可能包含另一个块级类型,一个文本块类型则只会包含内联内容,哪些节点是内联元素由 schema 决定。

注: 文本块类型的判断需要同时满足 spec.isBlock 和 spec.inlineContent 同时为 true。

isLeaf: bool

如果节点不允许内容,则为 true。

注: 是否是叶节点使用的是 spec.contentMatch 是否为空判断的。

isAtom: bool

如果节点是一个原子节点则为 true,例如,一个没有直接可编辑的内容的节点。

hasRequiredAttrs() → bool

告诉你该节点类型是否有任何必须的 attributes。

create(attrs: ?⁠Object, content: ?⁠Fragment | Node | [Node], marks: ?⁠[Mark]) → Node

新建一个此种类型的节点。将会检查给定的 attributes,未给定的话即为默认值(如果该中类型的节点没有任何必须的 attributes,你可以直接传递 null 来使用全部 attributes 的默认值)。 content 可能是一个 Fragment、一个节点、一个节点数组或者 nullmarks 参数与之类似,默认是 null,表示空的 marks 集合。

createChecked(attrs: ?⁠Object, content: ?⁠Fragment | Node | [Node], marks: ?⁠[Mark]) → Node

create 类似,但是会检查给定的 content 是否符合节点类型的内容限制,如果不符的话会抛出一个错误。

注: 该自定义错误类型为 RangeError。

createAndFill(attrs: ?⁠Object, content: ?⁠Fragment | Node | [Node], marks: ?⁠[Mark]) → ?⁠Node

create 类似,不过该方法会查看是否有必要在给定的 fragment 开始和结尾的地方 添加一些节点,以让该 fragment 适应当前 node。如果没有找到合适的包裹节点,则返回 null。 记住,如果你传递 null 或者 Fragment.empty 作为内容会导致其一定会适合当前 node,因此该方法一定会成功。

注: 因为 nullFragment.empty 不用寻找任何「合适的包裹节点」就能适应当前节点。

validContent(content: Fragment) → bool

如果给定的 fragment 对当前带有 attributes 的节点是可用的,则返回 true。

allowsMarkType(markType: MarkType) → bool

检查当前节点类型是否允许给定的 mark 类型。

allowsMarks(marks: [Mark]) → bool

检查当前节点类型是否允许给定的 marks 集合。

allowedMarks(marks: [Mark]) → [Mark]

从给定的 marks 集合中移除不允许出现在当前 node 中的 marks。

MarkType class

和 nodes 类似,marks(与 node 关联的以表示诸如强调、链接等的内容)也被用类型对象进行 tagged(归类), 每个类型只会被 Schema 实例化一次。

name: string

mark 类型的名称。

schema: Schema

当前 mark 类型所属于的 schema。

spec: MarkSpec

当前 mark 类型的配置对象。

create(attrs: ?⁠Object) → Mark

创建一个当前类型的 mark。attrs 可能是 null 或者是一个仅包含部分 marks attributes 的对象。 其他未包含的 attributes,会使用它们的默认值添加上去。

removeFromSet(set: [Mark]) → [Mark]

如果当前 mark 类型存在与给定的 mark 集合,则将会返回不含有当前 mark 类型的 marks 集合。 否则,直接返回给定的 marks 集合。

注: 看函数名,顾名思义就是在给定 marks 集合中移除当前 mark 类型的 marks。

isInSet(set: [Mark]) → ?⁠Mark

检查当前类型的 marks 是否存在于给定 marks 集合。

excludes(other: MarkType) → bool

查询给定的 mark 类型是否与当前 mark 类型 excluded(互斥)

ContentMatch class

该类的实例表示一个节点类型的 content expression(内容表达式) 的匹配状态, 其可以用来寻找是否此处是否有更进一步的内容能够匹配到,以及判断一个位置是否是该节点的可用的结尾。

注: 本小节的方法和类我用的不多,可能在处理一些边缘 case 的情况才会用到,因此很多直译了。

validEnd: bool

当匹配状态表示该节点有一个可用的结尾时为 true。

matchType(type: NodeType) → ?⁠ContentMatch

匹配一个节点类型,如果成功则返回该 ContentMatch 匹配结果。

matchFragment(frag: Fragment, start: ?⁠number = 0, end: ?⁠number = frag.childCount) → ?⁠ContentMatch

尝试去匹配一个 fragment。如果成功则返回 ContentMatch 匹配结果。

defaultType: ?⁠NodeType

获取相应匹配位置的第一个可以被生成的匹配节点类型。

注: 「可以被生成的」指的是该位置不能是文本节点或者不能有必须存在的 attribute 才能被生成。

fillBefore(after: Fragment, toEnd: ?⁠bool = false, startIndex: ?⁠number = 0) → ?⁠Fragment

尝试匹配给定的 fragment,如果失败,则会查看是否可以通过在该 fragment 前面插入一些节点来使之匹配。 如果插入节点后匹配成功,则会返回一个插入的节点组成的 fragment(如果没有需要插入的节点,则可能是空的)。 当 toEnd 为 true 时,只有结果匹配到达了内容表达式的结尾之时,才会返回一个 fragment。

注: 否则返回 undefined。

findWrapping(target: NodeType) → ?⁠[NodeType]

寻找一个包裹给定节点的节点集合,该集合中的节点在包裹住给定类型的节点后才能出现在当前位置。 集合的内容可能是空(如果给定类型节点直接就适合当前位置而无需包裹任何节点时),若不存在相应的包裹节点,则集合也可能是 null。

edgeCount: number

在描述内容表达式的有限自动机中该节点拥有的外部边界的数量。

注: 没理解什么意思,需要看源码,我直译的,鼠标悬浮查看原始文档。

edge(n: number) → {type: NodeType, next: ContentMatch}

在描述内容表达式的有限自动机中获取该节点第 n 个外部的边界。

DOM Representation

由于用一颗 DOM 节点树来表示一个文档是 ProseMirror 进行各种操作的核心思想,因此 DOM parsingserializing 被集成进该模块中。

(不过记住,你 不需要 使用该模块来实现一个 DOM 操作接口。)

DOMParser class

一个为了让 ProseMirror 文档符合给定 schema 的 Parser。它的行为由一个 rules 数组定义。

new DOMParser(schema: Schema, rules: [ParseRule])

新建一个针对给定 schema 的 parser,使用给定的 parsing rules。

schema: Schema

parser 所 parses 的 schema。

注: 解析器所解析的 schema。

rules: [ParseRule]

parser 所使用的 parse rules,按顺序优先。

parse(dom: dom.Node, options: ?⁠ParseOptions = {}) → Node

parse 一个 DOM 节点的内容成一个文档。

parseSlice(dom: dom.Node, options: ?⁠ParseOptions = {}) → Slice

parses 给定的 DOM 节点,与 parse 类似,接受与之相同的参数。 不过与 parse 方法产生一整个节点不同的是,这个方法返回一个在节点两侧打开的 slice,这意味着 schema 的约束不适用于输入节点左侧节点的开始位置和末尾节点的结束位置。

注: 这表示该方法可能产生一个不受 schema 约束的 node,只是该 node 由于 openStart 和 openEnd 的存在而适合 schema (被 open 剪切掉以适合 schema,但是整体不适合 schema)。

static fromSchema(schema: Schema) → DOMParser

用给定的 schema 中的 node 配置对象 中的 parsing rule 来构造一个 DOM parser, 被按 优先级 重新排序。

ParseOptions interface

这是一个被 parseparseSlice 方法用到的参数配置对象。

preserveWhitespace: ?⁠bool | "full"

默认情况下,根据 HTML 的规则,空白符会被折叠起来不显示。传递 true 表示保留空白符,但会将换行符表示为空格。 "full" 表示完全保留所有的空白符。

findPositions: ?⁠[{node: dom.Node, offset: number}]

如果设置了该参数,则 parser 除了 parsing 内容外,还将记录给定位置 DOM 在文档中相应的位置。 它将通过写入对象,添加一个保存文档位置的 pos 属性来实现。不在 parsed 内容中的 DOM 的位置将不会被写入。

from: ?⁠number

从开始 parsing 位置计算的子节点的索引。

to: ?⁠number

从结束 parsing 位置计算的子节点的索引。

topNode: ?⁠Node

默认情况下,内容会被 parsed 到 schema 的默认 顶级节点 中。 你可以传递这个选项和 attributes 以使用一个不同的节点作为顶级容器。

topMatch: ?⁠ContentMatch

提供与 parsed 到顶级节点的内容匹配的起始内容匹配。

context: ?⁠ResolvedPos

在 parsing 的时候的一个额外的节点集合,其被算作给定 top node 之上的 context

ParseRule interface

一个描述了如何 parse 给定 DOM 节点及行内样式成 ProseMirror 节点及 mark 的对象。

tag: ?⁠string

一个描述了需要匹配那种 DOM 元素的 CSS 选择器。每个 rule 都应该有一个 tag 属性 或者 style 属性。

namespace: ?⁠string

需要匹配的命名空间。应该和 tag 一起使用。只有命名空间匹配之后或者为 null 表示没有命名空间,才会开始匹配节点。

style: ?⁠string

需要匹配的 CSS 属性名。如果给定的话,这个 rule 将会匹配包含该属性的行内样式。 也可以是 "property=value" 的形式,这种情况下 property 的值完全符合给定值时 rule 才会匹配。 (对于更复杂的过滤方式,使用 getAttrs,然后返回 false 表示匹配失败。)

priority: ?⁠number

可以使用它来提升 schema 中 parse rule 的优先级顺序。更高优先级的更先被 parse。 没有优先级设置的 rule 则被默认设置一个 50 的优先级。该属性只在 schema 中才有意义。 而在直接构造一个 parser 的时候使用的是 rule 数组的顺序。

consuming: ?⁠boolean

默认情况下,如果一个 rule 匹配了一个元素或者样式,那么就不会进一步的匹配接下来的 rule 了。 而通过设置该参数为 false,你可以决定即使当一个 rule 匹配了,在该 rule 之后的 rule 也依然会运行一次。

context: ?⁠string

如果设置了该属性,则限制 rule 只匹配给定的上下文表达式,该上下文即为被 parsed 的内容所在的父级节点。 应该包含一个或者多个节点名或者节点 group 名,用一个或者两个斜杠结尾。例如 "paragraph/" 表示只有当父级节点是段落的时候才会被匹配, "blockquote/paragraph/" 限制只有在一个 blockquote 中的一个段落中才会被匹配,"section//" 表示匹配在一个 section 中的任何位置--一个双斜线表示匹配 任何祖先节点序列。为了允许多个不同的上下文,它们可以用 | 分隔,比如 "blockquote/|list_item/"

node: ?⁠string

当 rule 匹配的时候,将要创建的节点类型的名字。仅对带有 tag 属性的 rules 可用,对样式 rule 无效。 每个 rule 应该有 nodemarkignore 属性的其中一个(除非是当 rule 出现在一个 node 或者 mark spec 中时,在这种情况下,node 或者 mark 属性将会从它的位置推断出来)。

mark: ?⁠string

包裹匹配内容的 mark 类型的名字。

ignore: ?⁠bool

如果是 true,则当前 rule 的内容会被忽略。

closeParent: ?⁠bool

如果是 true,则会在寻找匹配该 rule 的元素的时候关闭当前节点。

skip: ?⁠bool

如果是 true,则会忽略匹配当前规则的节点,但是会 parse 它的内容。

attrs: ?⁠Object

由该 rule 创建的节点或者 mark 的 attributes。如果 getAttrs 存在的话,getAttrs 优先。

getAttrs: ?⁠fn(dom.Node | string) → ?⁠Object | false

用来计算由当前 rule 新建的节点或者 mark 的 attributes。也可以用来描述进一步 DOM 元素或者行内样式匹配的话需要满足的条件。 当它返回 false,则 rule 不会匹配。当它返回 null 或者 undefined,则被当成是一个空的/默认的 attributes 集合。

对于 tag rule 来说该方法参数是一个 DOM 元素,对于 style rule 来说参数是一个字符串(即行内样式的值)

contentElement: ?⁠string | fn(dom.Node) → dom.Node

对于 tag rule 来说,其产生一个非叶子节点的 node 或者 marks,默认情况下 DOM 元素的内容被 parsed 作为该 mark 或者 节点的内容。如果子节点在一个子孙节点中,则这个可能是一个 CSS 选择器字符串, parser 必须使用它以寻找实际的内容元素,或者是一个函数, 为 parser 返回实际的内容元素。

getContent: ?⁠fn(dom.Node, schema: Schema) → Fragment

如果设置了该方法,则会使用函数返回的结果来作为匹配节点的内容,而不是 parsing 节点的子节点。

preserveWhitespace: ?⁠bool | "full"

控制当 parsing 匹配元素的内容的时候,空白符是否应该保留。false 表示空白符应该不显示, true 表示空白符应该不显示但是换行符会被换成空格,"full" 表示换行符也应该被保留。

DOMSerializer class

一个 DOM serializer 知道如何将不同类型的 ProseMirror 节点和 marks 转换成 DOM 节点。

new DOMSerializer(nodes: Object<fn(node: Node) → DOMOutputSpec>, marks: Object<?⁠fn(mark: Mark, inline: bool) → DOMOutputSpec>)

新建一个 serializer。nodes 应该是一个对象,键是节点名,值是一个函数,函数接受一个节点作为参数,返回相应的 DOM 描述。 marks 类似,键值 marks 名,值是一个函数,只不过函数参数表示的是 marks 的内容是否是 block 的或者是 inline 的(一般情况下应该是 inline 的)。 一个 mark serializer 可能是 null,表示这种类型的 mark 不应该被 serialized(序列化)。

nodes: Object<fn(node: Node) → DOMOutputSpec>

节点的 serialization 函数们。

marks: Object<?⁠fn(mark: Mark, inline: bool) → DOMOutputSpec>

mark 的 serialization 函数们。

serializeFragment(fragment: Fragment, options: ?⁠Object = {}) → dom.DocumentFragment

将给定的 fragment serialize 成 DOM fragment。如果该操作不是在浏览器中完成, 那么应该传递一个 document 参数,以让 serializer 能够新建 nodes 们。

注: option.document,如果没有传,默认使用的是 window.document

serializeNode(node: Node, options: ?⁠Object = {}) → dom.Node

将节点 serialize 成一个 DOM 节点。这对于当想要 serialize 文档的一部分而不是整个文档的时候很有用。 若要 serialize 整个文档,在它的 content 属性上调用 serializeFragment 来完成。

static renderSpec(doc: dom.Document, structure: DOMOutputSpec) → {dom: dom.Node, contentDOM: ?⁠dom.Node}

渲染一个 output 配置对象 到一个 DOM 节点。如果配置对象有一个洞(数字0), 则 contentDOM 将会指向该洞所代表的节点。

static fromSchema(schema: Schema) → DOMSerializer

使用 schema 中节点和 mark 配置对象的 toDOM 方法来构建一个 serializer。

DOMOutputSpec interface

一个 DOM 结构的描述。既可以是一个字符串,用来表示一个文本节点,也可以是一个 DOM 节点,表示它自身,亦或者是一个数组。

这个数组描述了一个 DOM 元素。数组中的第一个值应该是这个 DOM 元素名字字符串,可以允许带上命名空间 URL 的前缀或者空格。 如果数组第二个值是一个普通对象,则被当做是 DOM 元素的 attributes。在数组第二个值之后的任何值(包括第二个值,如果它不是一个普通属性对象的话) 都被认为是该 DOM 元素的子元素,因此这些后面的值必须是有一个有效的 DOMOutputSpec 值,或者是数字 0。

数字 0(念做「洞」)被用来指示子元素应该被放置的位置。如果子元素是一个会被放置内容的节点,那么 0 应该是它唯一子元素。

注: 举个例子: ['div', {style:'color:red'}, 0],表示的是 <div style="color:red">子元素<div>; ['div', {style:'color:red'}, ['p', 0]],表示的是 <div style="color:red"><p>子元素</p><div>; ['div', {style:'color:red'}, ['p'], 0] 非法,因为 0 作为一个放置子元素的容器,其并不是父节点 div 的唯一子元素,父节点还有个子元素是 p

prosemirror-transform module

这个模块定义了一种修改文档的方式,以允许修改被记录、回放、重新排序。你可以在 中文指南 了解更多。

Steps

Transforming 发生在一个或者多个 Step 中,step 是原子的及定义良好的一个修改文档的类。 Applying(应用) 一个 step 会产生一个新的文档。

每个 step 提供一个 change map(修改映射),它会在旧的文档和 transformed 后的文档之间映射。 Steps 可以被 inverted(反转) 以新建一个 step 来取消之前 step 所做的影响, 而且可以在一个叫做 Transform 的对象上方便的链式调用。

Step class

一个 step 对象表示对文档的一个原子修改。大体上讲,它只会应用到创建它的那个文档上去,因为其内的位置信息只有对那个文档来说才有意义。

新的 steps 通过创建扩展自 Step 的类来定义,其覆盖了 applyinvertmapgetMapfromJSON 方法, 此外注册类的时候还需要使用 Step.jsonID 来生成一个唯一的 JSON 序列化过的标识符。

apply(doc: Node) → StepResult

将当前 step 应用到给定的文档,返回一个结果对象,对象可能表示失败,如果 step 不能被应用到文档中; 也可能表示成功,此时它会包含一个转换后的文档。

getMap() → StepMap

获取由当前 step 产生的表示文档变化的 step map,可以用来在新旧两个文档之间转换位置。

invert(doc: Node) → Step

新建一个当前 step 相反的 step 版本,需要 step 之前的文档作为参数。

map(mapping: Mappable) → ?⁠Step

通过一个可 mappable 的东西来 map 当前 step,返回值可能是一个调整过位置的 step 版本, 或者 null,如果 step 完全被这个 mapping 删除的话。

merge(other: Step) → ?⁠Step

试着合并当前 step 与给定的 step,会被直接应用到当前 step 之后。如果可能的话,会返回合并之后的 step, 如果 step 不能被合并,则返回 null。

toJSON() → Object

新建一个当前 step JSON 序列化后的版本。如果为一个自定义的子类定义了该方法,则需要确保返回的结果对象的 stepType 属性值是 step 类型的 JSON id

static fromJSON(schema: Schema, json: Object) → Step

从一个 step 的 JSON 形式反序列化为一个 step。将会调用 step 类自己实现的此方法。

static jsonID(id: string, stepClass: constructor<Step>)

为了能够将 steps 序列化为 JSON 形式,每个 step 都需要一个字符串 ID 附加到它自己的 JSON 形式上去。 使用这个方法为你的 step 类注册一个 ID。需要避免与其他模块的 step 的命名冲突。

StepResult class

applying(应用) 一个 step 的结果。可能包含一个新的文档或者是一个失败的值。

doc: ?⁠Node

transform 后的文档。

failed: ?⁠string

提供失败信息的文本。

static ok(doc: Node) → StepResult

创建一个成功的 step 结果。

static fail(message: string) → StepResult

创建一个失败的 step 结果。

static fromReplace(doc: Node, from: number, to: number, slice: Slice) → StepResult

用给定的参数调用 Node.replace。如果成功就返回一个成功值, 如果它抛出一个 ReplaceError 则返回一个失败值。

ReplaceStep class extends Step

用含有新内容的 slice 来替换文档的一部分。

new ReplaceStep(from: number, to: number, slice: Slice, structure: ?⁠bool)

给定的 slice 应该适应这个介于 fromto 之间的 「gap」,即 slice 两侧的深度和各自所接起来的位置深度必须是相同的,且 slice 周围 的节点必须能够接起来。当 structure 为 true 的时候,如果介意 from 和 to 之间的内容不是连续的先闭合后开放的标签,则 step 将会失败(这是为了 保证避免 rebased 的 replace step 意外覆盖了一些东西)。

from: number

The start position of the replaced range.

to: number

The end position of the replaced range.

slice: Slice

The slice to insert.

ReplaceAroundStep class extends Step

用一个 slice 的内容替换文档的一部分,不过会通过将被替换内容移动到 slice 中的方式来保留它的一个 range。

new ReplaceAroundStep(from: number, to: number, gapFrom: number, gapTo: number, slice: Slice, insert: number, structure: ?⁠bool)

用给定的 range 和 gap 来新建一个 replace-around step。 insert 应该指向的是在 slice 中 gap 的内容应该被放置的位置。 structure 有与它在 ReplaceStep 类中相同的含义。

from: number

The start position of the replaced range.

to: number

The end position of the replaced range.

gapFrom: number

The start of preserved range.

gapTo: number

The end of preserved range.

slice: Slice

The slice to insert.

insert: number

The position in the slice where the preserved range should be inserted.

AddMarkStep class extends Step

在给定的两个位置中的所有内联元素上添加一个 mark。

new AddMarkStep(from: number, to: number, mark: Mark)
from: number

The start of the marked range.

to: number

The end of the marked range.

mark: Mark

The mark to add.

RemoveMarkStep class extends Step

在给定的两个位置中的所有内联元素上移除一个 mark。

new RemoveMarkStep(from: number, to: number, mark: Mark)
from: number

The start of the unmarked range.

to: number

The end of the unmarked range.

mark: Mark

The mark to remove.

Position Mapping

通过调用由 step 产生的 step maps 来从一个文档中映射位置到另一个文档中在 ProseMirror 中是一个非常重要的操作。 例如,它被用来当文档改变的时候更新选区。

Mappable interface

位置可以被好几个对象 map,这类对象都符合该接口。

map(pos: number, assoc: ?⁠number) → number

通过该对象 map 一个位置。如果给定该方法,则 assoc(应该是 -1 或者 1,默认是 1) 决定位置与哪一侧有关,这决定了当一块内容被插入到被 map 的位置的时候,该位置应该往哪个方向移动。

mapResult(pos: number, assoc: ?⁠number) → MapResult

map 一个位置,然后返回一个包含关于这个 mapping 附加信息的对象。结果的 deleted 字段会告诉你该位置在 map 期间是否被删除(在一个 replace 的 range 中完全闭合的位置,即两侧都删除),如果只有一侧被删除,则只有当 assoc 指向删除一侧的时候,这个位置才会被认为是删除了。

MapResult class

一个带有额外信息的表示一个 map 过的位置的对象。

pos: number

该位置 map 过的版本。

deleted: bool

告诉你该位置是否被删除了,也就是说,是否有 step 从文档中将该位置两侧(周围)的内容删除了。

StepMap class extends Mappable

一个 map 描述了由 step 产生的删除和插入操作,这可以用来找到应用 step 之前文档的位置和应用 step 之后文档的相同位置之间的对应关系。

new StepMap(ranges: [number])

新建一个位置 map。对文档的修改被表示为一个数字数组,在数组中每三个值表示一个修改区域,即 [开始,旧大小,新大小]

forEach(f: fn(oldStart: number, oldEnd: number, newStart: number, newEnd: number))

对该 map 中的每一个修改的 range 调用给定的函数。

invert() → StepMap

新建一个该 map 的反转版本。函数返回的结果可以被用来将 step 修改后的文档位置 map 回 step 修改前的文档位置。

static offset(n: number) → StepMap

新建一个将所有位置偏移 n (n 可能为负数)的一个 map。当将一个子文档的 step 应用于一个较大文档的时候,这可能会很有用,反之亦然。

Mapping class extends Mappable

一个 mapping 表示 0 个或者更多个 step maps 的管道。为了能够无损的处理通过一系列 step 而产生的位置 mapping, 其中一些 steps 很有可能是之前 step 的反转版本(这可能出现在为了协同编辑或者历史管理而 ‘rebasing’ step 的时候)因此有一些特殊的规定需要遵守。

new Mapping(maps: ?⁠[StepMap])

用给定的位置 maps 新建一个 mapping。

maps: [StepMap]

在当前 mapping 中的 step maps。

from: number

maps 数组中的起始位置,当 map 或者 mapResult 调用的时候会被使用。

to: number

maps 位置的结束位置。

slice(from: ?⁠number = 0, to: ?⁠number = this.maps.length) → Mapping

新建一个 mapping,其只 map 当前 mapping 的一部分。

appendMap(map: StepMap, mirrors: ?⁠number)

添加一个 step map 到当前 mapping 的末尾。如果设置了 mirrors 参数,则它应该是 step map 的索引,即第一个参数 step map 的镜像。

appendMapping(mapping: Mapping)

将给定 mapping 的所有 maps 添加到当前 mapping(保留镜像信息)。

getMirror(n: number) → ?⁠number

寻找给定偏移量位置的 map 的镜像 step map 的偏移量。

appendMappingInverted(mapping: Mapping)

将给定 mapping 的相反顺序的 mapping 附加到当前 mapping 上。

invert() → Mapping

新建一个当前 mapping 包含相反 map 顺序的版本。

Document transforms

由于你可能经常需要通过将一系列的 steps 合并到一起来修改文档,ProseMirror 提供了一个抽象来使这个过程简单化。 State transactions 就是这个抽象,它是 transforms 的子类。

注: transaction 通常被简写为 tr。

Transform class

为了构建和跟踪文档 transformation 的一系列 steps 的抽象。

大多数的 transforming 方法返回 Transform 对象本身,因此它们可以链式调用。

new Transform(doc: Node)

新建一个起始于给定文档的 transform。

doc: Node

当前文档(即应用了 transform 中 steps 后的结果)。

steps: [Step]

transform 中的 steps 们。

docs: [Node]

在每个 steps 开始之前的文档们。

mapping: Mapping

一个 maps 了 transform 中的每一个 steps 的 mapping。

before: Node

起始文档。

step(step: Step) → this

对当前 transform 应用一个新的 step,然后保存结果。如果应用失败则抛出一个错误。

注: 错误的类叫做「TransformError」。

maybeStep(step: Step) → StepResult

尝试在当前 transformation 中应用一个 step,如果失败则忽略,否则返回 step result。

docChanged: bool

如果文档被改变过(当有任何 step 的时候),则返回 true。

addMark(from: number, to: number, mark: Mark) → this

将给定的 mark 添加到 fromto 之间的内联节点中。

removeMark(from: number, to: number, mark: ?⁠Mark | MarkType = null) → this

fromto 之间的内联节点上给定的的 mark 移除。当 mark 是一个单独的 mark 时,则精确移除这个 mark。 如果是一个 mark 类型时,则移除所有的该类型的 mark。如果是 null,移除其内所有类型的 mark。

clearIncompatible(pos: number, parentType: NodeType, match: ?⁠ContentMatch) → this

从给定的 pos 移除与给定的新的父级节点类型不兼容的所有 marks 和节点们。 接受一个可选的起始 content match 作为第三个参数。

replace(from: number, to: ?⁠number = from, slice: ?⁠Slice = Slice.empty) → this

用给定的 slice 替换在 fromto 之间的这部分文档。

replaceWith(from: number, to: number, content: Fragment | Node | [Node]) → this

用给定的内容替换给定过的 range,该内容可能是一个 fragment、节点、或者节点数组。

delete(from: number, to: number) → this

删除给定位置之间的内容。

insert(pos: number, content: Fragment | Node | [Node]) → this

在给定的位置插入给定的内容。

replaceRange(from: number, to: number, slice: Slice) → this

用给定的 slice 替换文档的一个 range,用 fromto 以及 slice 的 openStart 属性 作为参照,而不是固定的起始和结束点。这个方法可能会使要替换的范围变大,或者会关闭在 slice 中开放的节点以更符合所谓 「WYSIWYG」的预期, 如果父级节点配置对象是 non-defining 的,则替换内容会完全覆盖被替换区域, 如果是 defining 的,则会包含一个来自于 slice 的打开的父级节点。

This is the method, for example, to handle paste. The similar replace method is a more primitive tool which will not move the start and end of its given range, and is useful in situations where you need more precise control over what happens.

replaceRangeWith(from: number, to: number, node: Node) → this

用给定的 node 替换一个由给定 fromto 模糊确定的 range,而不是一个精确的位置。 如果当 from 和 to 相同且都位于父级节点的起始或者结尾位置,而给定的 node 并不适合此位置的时候,该方法可能将 from 和 to 的范围 扩大 到 超出父级节点以允许给定的 node 被放置,如果给定的 range(from 和 to 形成的)完全覆盖了一个父级节点,则该方法可能完全替换掉这个父级节点。

deleteRange(from: number, to: number) → this

删除给定的 range,会将该 range 扩大到完全覆盖父级节点,直到找到一个有效的替换为止。

注: 有些 range 两侧在不同深度的节点中,因此会先将二者的值扩展到与二者中深度与较小的那个保持一致以形成完全覆盖一个父级节点的 range。

lift(range: NodeRange, target: number) → this

如果 range 内容前后有同级内容,则会将给定 range 从其父级节点中分割,然后将其沿树移动到由 target 指定的深度。你可能想要使用 liftTarget 来计算 target,以保证这个沿着树的提升是有效的。

wrap(range: NodeRange, wrappers: [{type: NodeType, attrs: ?⁠Object}]) → this

用规定的包裹节点集合来包裹给定的 range。包裹节点们会被假定是适合当前位置的,其应该被 findWrapping 方法合适的计算出来。

setBlockType(from: number, to: ?⁠number = from, type: NodeType, attrs: ?⁠Object) → this

将介于 fromto 之间的所有的文本块(部分地)设置成带有给定 attributes 的给定的节点类型。

注: 为什么是「部分的」是因为有些文本块或许不能被改变类型。

setNodeMarkup(pos: number, type: ?⁠NodeType, attrs: ?⁠Object, marks: ?⁠[Mark]) → this

在给定的 pos 处改变节点的类型、attributes、或者/和 marks。如果 type 没有给,则保留当前节点的类型。

split(pos: number, depth: ?⁠number = 1, typesAfter: ?⁠[?⁠{type: NodeType, attrs: ?⁠Object}]) → this

分割给定位置的节点,如果传入了 depth 参数,且其大于 1,则任何数量的节点在它之上(???)。 默认情况下,被分割部分的节点类型将会继承原始的节点类型,但是也可以通过传入一个类型数组和 attributes 来在分割后设置其上。

注: 这句英文原文也不通顺。

join(pos: number, depth: ?⁠number = 1) → this

将给定位置周围的块级元素连接起来。如果深度是 2,它们最后和第一个同级节点也会被连接,以此类推。

当新建一个 transform 或者决定能否新建一个 transform 的时候,下面几个工具函数非常有用。

replaceStep(doc: Node, from: number, to: ?⁠number = from, slice: ?⁠Slice = Slice.empty) → ?⁠Step

将一个 slice 「恰当的」放到文档中给定的位置,会生成一个进行插入操作的 step。如果没有一个有意义的途径来插入该 slice,或者插入的 slice 没有意义(比如一个空的 slice 带着空的 range)则返回 null。

liftTarget(range: NodeRange) → ?⁠number

尝试寻找一个目标深度以让给定的 range 内的内容可以被提升(深度)。不会考虑有属性 isolating 存在的父级节点。

findWrapping(range: NodeRange, nodeType: NodeType, attrs: ?⁠Object, innerRange: ?⁠NodeRange = range) → ?⁠[{type: NodeType, attrs: ?⁠Object}]

尝试找到一个有效的方式来用给定的节点类型包裹给定 range 的内容。如果必要的话,可能会在包裹节点的内部和周围生成额外的节点。 如果没有可用的包裹方式则返回 null。当 innerRange 给定的时候,该 range 的内容将会被作为被包裹的内容,而不是 range 的内容。

注: 需要研究一下 range 和 innerRange 的区别,若 range 包含多个同级节点和 range 只含有一个节点是否有区别?

canSplit(doc: Node, pos: number, depth: ?⁠number = 1, typesAfter: ?⁠[?⁠{type: NodeType, attrs: ?⁠Object}]) → bool

检查给定的位置是否允许分割。

canJoin(doc: Node, pos: number) → bool

测试在给定位置之前或者之后的块级节点是否可以被连接起来。

joinPoint(doc: Node, pos: number, dir: ?⁠number = -1) → ?⁠number

寻找一个给定位置的祖先节点,该节点可以被连接到块级节点之前(或者之后,如果 dir 是正的话)。 如果找到,返回这个可加入的位置。

insertPoint(doc: Node, pos: number, nodeType: NodeType) → ?⁠number

pos 本身不是有效的位置但位于节点的起始或结尾处时,通过搜索节点的层级结构,尝试找到一个可以在给定位置的节点附近插入给定节点类型的点。 如果找不到位置,则返回 null。

dropPoint(doc: Node, pos: number, slice: Slice) → ?⁠number

寻找一个位置在给定的位置或者附近,以让给定的 slice能够插入。即使原始的位置并不直接在父级节点的起始或者结束位置, 也会尝试查看父级节点最近的边界然后尝试插入。如果找不到位置,则返回 null。

prosemirror-commands module

本模块导出了一些 命令,它可以构建块级函数以封装一个编辑行为。一个命令函数接受一个编辑器的 state, 一个 可选的 dispatch 函数,用来 dispatch 一个 transaction,及一个 可选的 EditorView 实例作为参数。它应该返回一个布尔值以指示它是否可以执行任何行为。当没有 dispatch 回调被传入的时候, 该命令应该做一个 空运行,以决定它是否应该被运行,而不实际做任何事情。

这些命令大多数用来绑定按键以及定义菜单项。

chainCommands(...commands: [fn(EditorState, ?⁠fn(tr: Transaction), ?⁠EditorView) → bool]) → fn(EditorState, ?⁠fn(tr: Transaction), ?⁠EditorView) → bool

组合多个命令函数到一个单独的函数(一个一个的调用这些命令,直到其中一个返回 true)。

deleteSelection(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

删除选区,如果存在的话。

joinBackward(state: EditorState, dispatch: ?⁠fn(tr: Transaction), view: ?⁠EditorView) → bool

如果选区是空(光标)而且在一个文本块的起始位置,则试着减少光标所在的块级节点和该块级节点之前的节点之间的距离。如果存在一个块级节点直接位于 光标所在的块级节点之前而且能够被连接的话,则连接他们。如果不存在,则尝试通过将光标所在的块级节点从其父级节点中提升一级或者将其移动到父级节点之前的 块级节点中的方式来尝试将选择的块级节点(即光标所在的块级及诶单)移动的更接近文档中的下一个节点。如果给定 view 参数,则将会使用之以获取准确的(用来处理 bidi 的)文本块起始方向。

selectNodeBackward(state: EditorState, dispatch: ?⁠fn(tr: Transaction), view: ?⁠EditorView) → bool

当选区是空且在一个文本块的起始位置的时候,如果可以的话,选中位于文本块之前的节点。这个行为通常倾向于在 joinBackward 之后绑定向后删除键或者其他删除命令,以作为一个回退方案,如果 schema 不允许在选择的点进行删除操作的话。

注: 向后删除键在 Mac 上是 Ctrl + D,会删除光标右侧的内容。

joinForward(state: EditorState, dispatch: ?⁠fn(tr: Transaction), view: ?⁠EditorView) → bool

如果选区是空而且光标在一个文本块的结尾处,则尝试减少或者移除当前块级元素和它之后的块级元素之间边界。可以通过连接他们或者在树中移动挨着当前块级元素的 其他块级元素。将会使用给定的 view(如果有)以获取准确的文本块起始方向。

selectNodeForward(state: EditorState, dispatch: ?⁠fn(tr: Transaction), view: ?⁠EditorView) → bool

当选区是空且在一个文本块的结尾位置的时候,如果可以的话,选中位于文本块之后的节点。这个行为通常倾向于在 joinForward 之后绑定删除键或者其他类似的删除命令,以作为一个回退方案,如果 schema 不允许在选择的点进行删除操作的话。

joinUp(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

连接选择的块级节点,或者如果有文本选区,则连接与选区最接近的可连接的祖先节点和它之前的同级节点。

joinDown(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

连接选择的块级节点,或者连接与选区最接近的可连接的祖先节点和它之后的同级节点。

lift(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

从父级节点中提升选择的块级节点,或者提升与选区最接近的可提升的祖先节点。

newlineInCode(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

如果选区在一个配置对象中 code 属性为真值的节点类型中,则用一个换行符替换选区。

exitCode(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

如果选区在一个配置对象中 code 属性为真值的节点类型中,则在当前代码块之后新建一个默认的块级节点, 然后将光标移动到其内。

createParagraphNear(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

如果一个块级节点被选中,则在其前面(如果它是其父级节点的第一个子元素)新建一个段落,或者其后面。

liftEmptyBlock(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

如果光标在一个空的可以被提升的文本块中,那么提升这个文本块。

splitBlock(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

分割选区的父级节点。如果选区是一个文本选区,还会同时删除选区内容。

splitBlockKeepMarks(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

行为和 splitBlock 类似,不过不会重置光标处已经激活的 marks 集合。

selectParentNode(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

移动选区到包裹当前选区的节点中(如果有的话,不会选择文档根节点)。

selectAll(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

选择整个文档。

wrapIn(nodeType: NodeType, attrs: ?⁠Object) → fn(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

用带有给定 attributes 的给定类型的节点来包裹选区。

setBlockType(nodeType: NodeType, attrs: ?⁠Object) → fn(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

返回一个尝试将选中的文本块设置为带有给定 attributes 的给定节点类型的命令。

toggleMark(markType: MarkType, attrs: ?⁠Object) → fn(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

新建一个命令函数,控制带有给定 attributes 的给定 mark 的开关。如果当前选区不支持这个的 mark 将会返回 false。 这个过程将会移除在选区中存在的任何该种类型的 mark,或者如果没有的话则添加之。如果选区是空,则这将会应用到 stored marks](#state.EditorState.storedMarks) 中,而不是文档的某个范围。

autoJoin(command: fn(state: EditorState, ?⁠fn(tr: Transaction)) → bool, isJoinable: fn(before: Node, after: Node) → bool | [string]) → fn(state: EditorState, ?⁠fn(tr: Transaction)) → bool

封装一个命令,以便当它产生一个 transform 以引起两个可连接的节点彼此相邻时能够被连接。 当节点具有相同类型并且当 isJoinable 参数是函数时返回 true 或者参数是一个字符串数组而这些节点名在这个数组中时,节点将会被认为是可连接的。

baseKeymap: Object

取决于删除操作的平台,该值将指向 pcBasekeymap 或者 macBaseKeymap

pcBaseKeymap: Object

一个基本的按键映射,包含不特定于任何 schema 的按键绑定。绑定包含下列按键(当多个命令被列出来的时候,它们被 chainCommands 所链接起来)。

  • Enter 绑定到 newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock
  • Mod-Enter 绑定到 exitCode
  • BackspaceMod-Backspace 绑定到 deleteSelection, joinBackward, selectNodeBackward
  • DeleteMod-Delete 绑定到 deleteSelection, joinForward, selectNodeForward
  • Mod-Delete 绑定到 deleteSelection, joinForward, selectNodeForward
  • Mod-a 绑定到 selectAll
macBaseKeymap: Object

pcBaseKeymap 的复制版,和 Backspace 一样绑定了 Ctrl-h,和 Delete 一样绑定了 Ctrl-d, 和 Ctrl-Backspace 一样绑定了 Alt-Backspace,和 Ctrl-Delete 一样绑定了 Ctrl-Alt-Backspace, Alt-Delete, 和 Alt-d

prosemirror-history module

一个 ProseMirror 的撤销/重做历史的实现。撤销/重做的历史是 可选择的,这意味着它不仅仅是回滚到 之前的 state,而是可以在撤销部分修改的同时保留其他的修改,或者原封不动的保留之后的修改(这对于协同编辑来说 是很有必要的,在其他一些情况下也可能会发生)。

history(config: ?⁠Object) → Plugin

返回一个插件以使编辑器撤销历史可用。该插件将会追踪撤销和重做的操作栈,这可以和 撤销 and 重做 命令一同使用。

你可以在一个 transaction 上设置一个 "addToHistory"metadata 属性false,来阻止该 tr 被撤销回滚。

注: 设置了这个之后,该 tr 就相当于不会被加入到历史栈中。

config: ?⁠Object

支持下列的参数可选:

depth: ?⁠number

在最早的操作历史被丢弃之前,历史栈的最大长度,默认是 100。

注: 超过了就先丢弃最早的操作记录。

newGroupDelay: ?⁠number

操作从开始算起应该持续多久才会被算作一个操作。默认是 500(毫秒)。记住,如果多个修改不相邻的话,总是会被算作是新的操作。

注: 如果该值被设置为 500,则在 500 毫秒内输入 10 个字,这样会触发 10 个 tr,但是会被归为一「组」,撤销的时候直接撤销这 10 个字的输入记录。 如果 500 毫秒内输入 20 个字同理撤销这 20 个字的记录。如果在 500 毫秒内输入了 10 个字,在 501 毫秒内输入了第 11 个字,则撤销的时候,那第 501 毫秒输入的第 11 个字被算做是一组,先撤销那 1 个字的输入,再撤销那 10 个字的输入。这个逻辑是为了和系统输入保持一致,各位可以试试在系统的某个界面如搜索框变换输入速度来输入内容后 撤销,看会发生什么。

undo(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

一个可以撤销最后修改(如果有)的命令函数。

redo(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

一个可以重做最后一次撤销修改(如果有)的命令函数。

undoDepth(state: EditorState) → number

在给定的 state 中可以被撤销的操作的数量。

redoDepth(state: EditorState) → number

在给定的编辑器 state 中可以被重做的操作的数量。

closeHistory(tr: Transaction) → Transaction

在给定的 transaction 上设置一个标记,这将会阻止接下来的 steps 们被附加到一个已经存在的历史事件中(这样就需要一个单独的撤销命令来撤销了)。

注: 这个函数不能顾名思义的以为不会会将 tr 加入到历史栈中。正确理解应该是其会将本来能够一个 tr n 个 steps 完成的修改, 变成了 两个 tr,n/2 个 step。因此需要额外的撤销命令来撤销修改。「close」表示紧急中断该 tr,然后将剩余的 steps 放到一个新的 tr 中。

prosemirror-collab module

这个模块实现了一个 API,该 API 可以供协同编辑使用。查看 指南 以获取更多细节和示例。

collab(config: ?⁠Object = {}) → Plugin

创建一个能使编辑器支持协同编辑框架的插件。

config: ?⁠Object

可选参数对象。

version: ?⁠number

协同编辑的起始版本号,默认是 0.

clientID: ?⁠number | string

客户端 ID,用来分别哪些修改是自己做的哪些是其他客户端做的。默认是一个随机的 32 位数字。

getVersion(state: EditorState) → number

获取 collab 插件与鉴权中心同步的版本。

receiveTransaction(state: EditorState, steps: [Step], clientIDs: [number | string], options: ?⁠Object) → Transaction

创建一个接受自鉴权中心的表示新 steps 集合的 transaction。应用该 transaction 以将 state 向前移动来适应文档的鉴权中心的视图。

注: 「鉴权中心」指的就是协同处理的服务端,那里负责处理接受那些 tr,拒绝哪些 tr。

options: ?⁠Object

可选的配置参数。

mapSelectionBackward: ?⁠boolean

启用后(默认是 false),如果当前选区是一个 文本选区,则它的两侧位置会被这个 transaction 通过一个负向偏移 mapped,以便使插入光标处的内容会以光标所在的位置结尾。用户通常倾向于这样做,不过因为向后兼容的 原因,默认情况下不会这么做。

sendableSteps(state: EditorState) → ?⁠{version: number, steps: [Step], clientID: number | string, origins: [Transaction]}

提供编辑器未被确认的 steps 的数据描述,它会被发送给鉴权中心。如果没有需要发送的东西,返回 null。

origins 值是产生每个 steps 的 原始 transactions。对于寻找 steps 的时间戳和其他 metadata 信息很有用,不过记住,steps 可能会被 rebased, 因此原始的 transaction 仍然是旧的,未改变的对象。

prosemirror-keymap module

一个为了方便的定义按键绑定的插件。

keymap(bindings: Object) → Plugin

用给定的绑定集合来创建一个按键映射插件。

绑定应该将按键名和 命令 格式的函数对应起来,该函数将会传入 (EditorState, dispatch, EditorView) 作为参数来调用,如果它响应了该按键按下,则应该返回 true。记住,view 参数并不是命令协议的一部分,但是如果按键绑定需要直接与 UI 交互,则可以将其用来作为应急出口使用。

按键的名字可以是形如 "Shift-Ctrl-Enter" 的字符串,它是一个按键标识符,可以有 0 个或者多个修饰符做前缀。按键标识符的基础字符串基于这个 KeyEvent.key 中的按键而来。使用小写字母来表示字母键 (或者使用大写字母,如果你想要处理 shift 被同时按下的情况)。你也许会使用 "Space" 作为 " " 的别名。

修饰符可以以任何顺序给定。只允许 Shift-(或者 s-),Alt-(或者 a-),Ctrl-(或 c-Control-)及 Cmd-(或 m-Meta-) 这些修饰符出现。对于通过按下 shift 创建的字符,则 Shift- 前缀就是隐式的,不应该再显式添加了。

在 Mac 上你可以使用 Mod- 作为 Cmd- 的简称,在其他平台可以使用 Ctrl-

你可以在编辑器中添加多个按键映射插件。它们出现的顺序决定了它们的优先级(数组前面的优先被 dispatch)。

keydownHandler(bindings: Object) → fn(view: EditorView, event: dom.Event) → bool

给定一个按键绑定的集合(和 keymap 使用一样的格式),返回一个处理相应按键事件的 按键 处理函数

prosemirror-inputrules module

本模块定义了一个编辑器插件用来附加 input rules(输入规则),它可以响应或者转换用户输入的文字。 本模块还带有一些默认的规则,可以通过本插件启用。

InputRule class

输入规则是一些正则表达式,描述了输入何种文本会引起一些额外的变化。这个变化可能是将两个短斜杠变成一个长破折号,或者将以 "> " 开头的段落用 blockquote 包裹着, 亦或者其他完全不一样的事情。

new InputRule(match: RegExp, handler: string | fn(state: EditorState, match: [string], start: number, end: number) → ?⁠Transaction)

创建一个输入规则。规则会应用到当用户输入一些内容的时候,且直接在光标之前的文本会匹配 match 参数,该参数应该合适的用 $ 结尾。

handler 参数可以是一个字符串,这种情况下表示匹配的文本,或者在正则中匹配的第一个组,会被该字符串替换。

它也可以是一个函数,会将调用 RegExp.exec 后产生的 结果匹配数组传入作为参数来调用,以及匹配的起始和结束的范围。函数返回一个描述了规则影响的 transaction,或者如果输入没有被处理则返回 null。

inputRules(config: {rules: [InputRule]}) → Plugin

创建一个输入规则插件。启用的话,将会导致与任何给定规则匹配的文本输入都会触发该规则对应的行为。

undoInputRule(state: EditorState, dispatch: ?⁠fn(Transaction)) → bool

如果应用这个规则是用户做的最后一件事情的话,这是一个可以撤销输入规则的命令。

本模块还带有一些预定义的规则:

emDash: InputRule

转换两个短斜杠为一个长破折号的输入规则。

ellipsis: InputRule

转换三个点为一个省略号的输入规则。

openDoubleQuote: InputRule

「智能」打开双引号的输入规则。

closeDoubleQuote: InputRule

「智能」关闭双引号的输入规则。

openSingleQuote: InputRule

「智能」打开单引号的输入规则。

closeSingleQuote: InputRule

「智能」关闭单引号的输入规则。

smartQuotes: [InputRule]

自动打开/关闭 单/双 引号相关的输入规则。

下列这些工具函数接受一个特定于 schema 的参数,并创建一个特定于 schema 的输入规则。

wrappingInputRule(regexp: RegExp, nodeType: NodeType, getAttrs: ?⁠Object | fn([string]) → ?⁠Object, joinPredicate: ?⁠fn([string], Node) → bool) → InputRule

当给定字符串被输入的时候构建一个输入规则以自动包裹一个文本块。regexp 参数被直接传给 InputRule 构造函数。你也许想要正则表达式以 ^ 开头, 这样的话就只会从一个文本块起始位置开始匹配。

注: ^ 表示正则中的起始位置匹配,一般用来做类似于 markdown 的输入规则,例如在文本块开头输入 # + 空格后,生成一个 h 元素。

nodeType 是要被包裹进的节点类型。如果它需要 attributes,那么你既可以直接传入,也可以传入一个计算 attributes 的函数,该函数接受正则匹配的结果作为参数。

默认情况下,如果有新的包裹节点之前有一个与之相同类型的节点,那么这个规则将会尝试 join(连接) 这两个节点。 你可以传递一个连接指示函数,它接受一个正则表达式的结果和在包裹节点之前的节点作为参数,返回一个指示连接是否应该进行的布尔值。

textblockTypeInputRule(regexp: RegExp, nodeType: NodeType, getAttrs: ?⁠Object | fn([string]) → ?⁠Object) → InputRule

构建一个输入规则,以当匹配的文本输入的时候能够改变文本块的类型。你的正则通常应该以 ^ 开头,这样它就会只匹配文本块的起始位置。 可选参数 getAttrs 可以被用来计算新节点的 attributes,功能和 wrappingInputRule 中的该函数一样。

prosemirror-gapcursor module

本插件为那些不允许正常选区的聚焦的位置(比如说该位置有叶子节点、表格、或者文档的起始和结尾处)添加一种选区的类型。

你可能需要加载 style/gapcursor.css 这个文件,它包含了模拟光标的基本样式(即短的,闪烁的水平条纹)。

默认情况下,gap 光标只允许放置于默认内容节点(通过 schema 的 content 限制)是文本节点的地方。你可以通过在节点配置对象中添加 allowGapCursor 属性来自定义这个行为,如果该值是 true,则 gap 光标被允许放置到该节点的任何位置,如果是 false 则表示永远不允许放置该种类型的光标。

gapCursor() → Plugin

创建一个 gap 光标插件。如果启用的话,它将会捕获点击区域附近的和方向键经过的不允许有一个正常的可选择区域的地方,然后为它们创建一个 gap 光标选区。 光标元素的类名是 ProseMirror-gapcursor。你既可以从该包中直接引入 style/gapcursor.css 做样式文件,也可以添加你自己的样式以使它可见。

GapCursor class extends Selection

这个类是 Gap 光标选区的表现形式。它的 $anchor$head 属性都指向光标的位置。

prosemirror-schema-basic module

本模块定义了一个简单的 schema。你可以直接拿来使用,或者扩展它,亦或者仅仅是抄其中的一些节点和 mark 的配置对象然后应用到新的 schema 中。

schema: Schema

该 schema 大致对应于 CommonMark 使用的文档 schema,减去在 prosemirror-schema-list 模块中定义的里列表元素。

为了能够从该 schema 中重用元素,可以扩展和读取 spec.nodesspec.marks 属性

nodes: Object

定义在该 schema 中节点们的 Specs(配置对象)

doc: NodeSpec

文档顶级节点。

paragraph: NodeSpec

普通段落文本块。在 DOM 中表现为一个 <p> 元素。

blockquote: NodeSpec

一个引用块(<blockquote>)包裹一个或者多个块级节点。

horizontal_rule: NodeSpec

水平分隔线(<hr>)。

heading: NodeSpec

标题文本块,带有一个 level 属性,该属性的值应该在 1 到 6 的范围。会被格式化和序列化为 <h1><h6> 元素。

code_block: NodeSpec

代码块。默认情况下不允许 marks 和非文本行内节点。表现为一个包裹着 <code> 元素的 <pre> 元素。

text: NodeSpec

文本节点。

image: NodeSpec

行内图片节点。支持 srcalthref 属性。后两者默认的值是空字符串。

hard_break: NodeSpec

强制换行符,在 DOM 中表示为 <br> 元素。

marks: Object

schema 中 marks 们的 Specs(配置对象)

链接。有 hreftitle 属性。title 默认是空字符串。会被渲染和格式化为一个 <a> 元素。

em: MarkSpec

强调。渲染为一个 <em> 元素,格式化规则同样匹配 <i>font-style: italic

注: 这里可能是 em 加粗,或者 i/font-style: italic 斜体。

注: 「font-style: italic」写法中,样式名的冒号后面的空格要保持统一,要么都有,要么都无。

strong: MarkSpec

加粗。渲染为 <strong>,格式化规则同样匹配 <b>font-weight: bold

code: MarkSpec

行内代码。表现为 <code> 元素。

prosemirror-schema-list module

本模块导出了列表相关的 schema 元素和命令。命令假设列表是可以被嵌套的,以及限制列表项的第一个元素必须为普通段落元素。

下面这些是节点配置对象:

orderedList: NodeSpec

一个有序列表的 节点配置对象。有一个唯一的属性 order,它决定了列表从哪个数字开始计数,默认是 1。 表现形式是一个 <ol> 元素。

bulletList: NodeSpec

一个无序列表的节点配置对象,DOM 表示为 <ul>

listItem: NodeSpec

列表项(<li>)的配置对象。

注: li 父节点可以是 ol 也可以是 ul,高级用法还可以扩展为 todo

addListNodes(nodes: OrderedMap<NodeSpec>, itemContent: string, listGroup: ?⁠string) → OrderedMap<NodeSpec>

为 schema 方便的添加列表相关的节点类型到一个特定的节点类型的函数。orderedList 表示为 "ordered_list"bulletList 表示为 "bullet_list", 以及 listItem 表示为 "list_item"

itemContent 决定了列表项的内容表达式。如果你想要在本模块中定义的命令应用于你自己的列表结构,则它的值应该是诸如 "paragraph block*" 或者 "paragraph (ordered_list | bullet_list)*" 之类的。listGroup 可以将列表节点类型分配到一个组名,比如 "block"

本模块使用方式应该像下面这样:

const mySchema = new Schema({
  nodes: addListNodes(baseSchema.spec.nodes, "paragraph block*", "block"),
  marks: baseSchema.spec.marks
})

下列函数是 命令

wrapInList(listType: NodeType, attrs: ?⁠Object) → fn(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

返回一个命令函数,该函数用给定的类型和属性包裹位于 list 中的选区。如果 dispatch 参数是 null,则只返回一个指示该行为是否可能的值, 而不实际执行修改。

splitListItem(itemType: NodeType) → fn(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

构建一个命令,它会通过分割列表项的直接子元素的非空文本节点的方式来分割一个列表项。

liftListItem(itemType: NodeType) → fn(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

创建一个命令,该命令会提升选区所在的列表项到上一级列表中。

sinkListItem(itemType: NodeType) → fn(state: EditorState, dispatch: ?⁠fn(tr: Transaction)) → bool

创建一个命令,该命令会将选区所在的列表项缩进到一个内部列表中去。