这个模块定义了 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