ProseMirror Schema Basic 模块

prosemirror

ProseMirror Schema Basic 模块

prosemirror-schema-basic 模块提供了一个 schema 数据结构的约束对象,以及它的配置对象(符合一种 TypeScript interface SchemaSpec)的属性 nodes 和属性 marks 的值(分别表示该文档所支持的节点类型和样式标记类型)

schema 对象

该模块导出一个 schema 数据结构的约束对象,它支持的节点类型和样式标记类型参考 CommonMark(一种 Markdown 格式)

ts
schema: Schema<
  // 该 schema 支持多种 nodes 节点类型
  "blockquote" |
  "doc" |
  "paragraph" |
  "horizontal_rule" |
  "heading" |
  "code_block" |
  "text" |
  "image" |
  "hard_break"
  ,
  // 也支持一些 marks 样式标记类型
  "code" | "em" | "strong" | "link"
>
注意

该 schema 并不包含列表节点,可以查看官方所提供的另一个模块 prosemirror-schema-list

提示

可以直接使用该 schema 对象来创建编辑器,也可以基于它进行扩展,定制出符合自己编辑器使用场景的数据结构

具体支持哪些节点和样式标记可以通过该对象的属性 schema.spec.nodes 或 schema.spec.marks 来查看

nodes 配置对象

该模块导出了一个 nodes 对象,它包含一些属性(其值需要符合一种 TypeScript interface NodeSpec,规定了节点可接受哪些配置参数)对不同类型的节点进行描述

如何使用

在使用方法 new Schema(spec) 实例化 Schema 类时,该对象作为配置参数 spec(它是一个对象,符合一种 TypeScript interface SchemaSpec)的属性 spec.nodes 的值

ts
export const nodes = {
  // 文档的根节点
  doc: {
    // 其内容(子节点)需要是 block group 中所包含的节点
    // 具体包括 5 种类型的节点:
    // * paragraph
    // * blockquote
    // * horizontal_rule
    // * heading
    // * code_block
    // 而数量是一个或多个
    content: "block+"
  } as NodeSpec,
  // 文本节点
  text: {
    // 将它归到 inline group 组别中
    group: "inline"
  } as NodeSpec,
  // 段落节点
  paragraph: {
    // 其内容(子节点)需要是 inline group 组别中所包含的节点
    // 具体包括 text、image、hard_break 这 3 种类型的节点
    // 而数量是零个或多个
    content: "inline*",
    // 将它归到 block group 组别中
    group: "block",
    // 定义了如何将 DOM 元素解析为该类型节点的一些规则
    // 这里只设置了一条规则
    parseDOM: [
      // 基于 DOM 元素的名称进行匹配,即将 HTML 中的 `<p>` 元素解析为段落节点
      {tag: "p"}
    ],
    // 定义了如何将该类型的节点转换为相应的 DOM 元素
    toDOM() { return pDOM }
  } as NodeSpec,
  // 引文节点
  blockquote: {
    // 其内容(子节点)需要是 block group 中所包含的节点
    // 具体包括 5 种类型的节点:
    // * paragraph
    // * blockquote
    // * horizontal_rule
    // * heading
    // * code_block
    // 它可以使用自身类型的节点作为子节点
    // 而数量是一个或多个
    content: "block+",
    // 将它归到 block group 组别中
    group: "block",
    // 表示该类型的节点是否具有特殊的上下文语义,用于控制与该节点相关的复制粘贴行为
    // 如果设置为 true 时
    // 会同时将该节点的 definingAsContext 属性和 definingForContent 属性都设置为 true
    defining: true,
    // 定义了如何将 DOM 元素解析为该类型节点的一些规则
    // 这里只设置了一条规则
    parseDOM: [
      // 基于 DOM 元素的名称进行匹配,即将 HTML 中的 `<blockquote>` 元素解析为引文节点
      {tag: "blockquote"}
    ],
    // 定义了如何将该类型的节点转换为相应的 DOM 元素
    toDOM() { return blockquoteDOM }
  } as NodeSpec,
  // 水平线节点
  horizontal_rule: {
    // 将它归到 block group 组别中
    group: "block",
    // 定义了如何将 DOM 元素解析为该类型节点的一些规则
    // 这里只设置了一条规则
    parseDOM: [
      // 基于 DOM 元素的名称进行匹配,即将 HTML 中的 `<hr>` 元素解析为水平线节点
      {tag: "hr"}
    ],
    // 定义了如何将该类型的节点转换为相应的 DOM 元素
    toDOM() { return hrDOM }
  } as NodeSpec,
  // 标题节点
  heading: {
    // 设置该类型的节点所拥有的 attributes 以添加额外的信息
    attrs: {
      // 属性 level 表示标题节点的层级
      // (可以是数字 1 至 6 任一值,分别对应 DOM 元素 <h1> 到 <h6>)
      // 默认值是 1
      // (即实例化标题节点时,如果省略配置 level 属性,则默认创建一个 1 级标题节点)
      level: {default: 1}
    },
    // 其内容(子节点)需要是 inline group 组别中所包含的节点
    // (具体包括 text、image、hard_break 这 3 种类型的节点)
    // 而数量是零个或多个
    content: "inline*",
    // 将它归到 block group 组别中
    group: "block",
    // 表示该类型的节点是否具有特殊的上下文语义,用于控制与该节点相关的复制粘贴行为
    // 如果设置为 true 时
    // 会同时将该节点的 definingAsContext 属性和 definingForContent 属性都设置为 true
    defining: true,
    // 定义了如何将 DOM 元素解析为该类型节点的一些规则
    // 这里设置了 6 条规则
    // 分别对应将 <h1> 到 <h6> 元素转换为标题节点
    // (需要为节点的属性 attrs.level 设置对应的值)
    parseDOM: [
      {tag: "h1", attrs: {level: 1}},
      {tag: "h2", attrs: {level: 2}},
      {tag: "h3", attrs: {level: 3}},
      {tag: "h4", attrs: {level: 4}},
      {tag: "h5", attrs: {level: 5}},
      {tag: "h6", attrs: {level: 6}}
    ],
    // 定义了如何将该类型的节点转换为相应的 DOM 元素
    // 根据节点的属性 attrs.level 合成 DOM 元素的名称
    toDOM(node) { return ["h" + node.attrs.level, 0] }
  } as NodeSpec,
  // 代码块节点
  // Represented as a `<pre>` element with a `<code>` element inside of it.
  code_block: {
    // 其内容只允许文本节点
    // 而数量是零个或多个
    content: "text*",
    // 而且不允许任何类型的 marks 样式标记对文本进行修饰
    marks: "",
    // 将它归到 block group 组别中
    group: "block",
    // 表示该节点内包含代码文字
    // 以告诉 ProseMirror 采用不同的处理方式
    // 例如粘贴内容到节点内时,格式化的方式不同,需要保留空格和缩进等
    code: true,
    // 表示该类型的节点是否具有特殊的上下文语义,用于控制与该节点相关的复制粘贴行为
    // 如果设置为 true 时
    // 会同时将该节点的 definingAsContext 属性和 definingForContent 属性都设置为 true
    defining: true,
    // 定义了如何将 DOM 元素解析为该类型节点的一些规则
    // 这里只设置了一条规则
    parseDOM: [
      // 基于 DOM 元素的名称进行匹配,即将 HTML 中的 `<pre>` 元素解析为代码块节点
      // 而且配置属性 preserveWhitespace 为 `full` 表示完全保留空格键和换行符
      {tag: "pre", preserveWhitespace: "full"}
    ],
    // 定义了如何将该类型的节点转换为相应的 DOM 元素
    toDOM() { return preDOM }
  } as NodeSpec,
  // 图像节点
  image: {
    // 表示该节点为行内类型
    inline: true,
    // 设置该类型的节点所拥有的 attributes 以添加额外的信息
    attrs: {
      // 属性 src 是图片的路径
      src: {},
      // 属性 alt 是对图像的文本描述,默认为空
      alt: {default: null},
      // 属性 title 是作为 tooltip 提示文本呈现给用户,默认为空
      title: {default: null}
    },
    // 将它归到 inline group 组别中
    group: "inline",
    // 表示该节点(在非选中的情况下)允许拖拽移动。
    draggable: true,
    // 定义了如何将 DOM 元素解析为该类型节点的一些规则
    // 这里只设置了一条规则
    parseDOM: [
      // 基于 DOM 元素的名称进行匹配
      // 即将 HTML 中的 `<img>` 元素(需要具有 src 属性)解析为图像节点
      {
        tag: "img[src]",
        // 使用 getAttrs 属性更精细地设置 attributes
        // 💡 也有些节点(不是当前示例)使用该属性(基于特定的 attributes)匹配 DOM 元素
        //    如果该函数返回 false,表示该 DOM 元素无法满足规则,即无法匹配
        getAttrs(dom: HTMLElement) {
          // 从 DOM 元素的相应属性获取值,以设置节点的一系列 attributes
          return {
            src: dom.getAttribute("src"),
            title: dom.getAttribute("title"),
            alt: dom.getAttribute("alt")
          }
        }
      }
    ],
    // 定义了如何将该类型的节点转换为相应的 DOM 元素
    toDOM(node) {
      let {src, alt, title} = node.attrs;
      return ["img", {src, alt, title}]
    }
  } as NodeSpec,
  // 强制换行节点
  hard_break: {
    // 表示该节点为行内类型
    inline: true,
    // 将它归到 inline group 组别中
    group: "inline",
    // 表示该节点不允许被选中
    selectable: false,
    // 定义了如何将 DOM 元素解析为该类型节点的一些规则
    // 这里只设置了一条规则
    parseDOM: [
      // 基于 DOM 元素的名称进行匹配
      // 即将 HTML 中的 `<br>` 元素解析为强制换行节点
      {tag: "br"}
    ],
    // 定义了如何将该类型的节点转换为相应的 DOM 元素
    toDOM() { return brDOM }
  } as NodeSpec
}

在以上代码中涉及到一些变量(可以是一个字符串、一个 DOM 元素、一个对象,或一个数组),它们作为 toDOM 方法的返回值(符合 TypeScript type DOMOutputSpec),它们描述了如何将节点转换为 DOM 元素

ts
const pDOM: DOMOutputSpec = ["p", 0], // pDOM 描述了段落节点如何转换为 DOM 元素
      // blockquoteDOM 描述了引文节点如何转换为 DOM 元素
      blockquoteDOM: DOMOutputSpec = ["blockquote", 0],
      // hrDOM 描述了将水平线节点如何转换为 DOM 元素
      hrDOM: DOMOutputSpec = ["hr"],
      // preDOM 描述了代码块节点如何转换为 DOM 元素
      // 转换为 <pre><code>content</code></pre> 的形式
      preDOM: DOMOutputSpec = ["pre", ["code", 0]],
      // brDOM 描述了强制换行节点如何转换为 DOM 元素
      brDOM: DOMOutputSpec = ["br"]

marks 配置对象

该模块导出了一个 marks 对象,它包含一些属性(其值需要符合一种 TypeScript interface MarkSpec,规定了样式标记可接受哪些配置参数)对不同类型的样式标记进行描述

如何使用

在使用方法 new Schema(spec) 实例化 Schema 类时,该对象作为配置参数 spec(它是一个对象,符合一种 TypeScript interface SchemaSpec)的属性 spec.marks 的值

ts
export const marks = {
  // 链接样式标记 Has `href` and `title` attributes.
  // `title` defaults to the empty string.
  // Rendered and parsed as an `<a>` element.
  link: {
    // 设置该类型的样式标记所拥有的 attributes 以添加额外的信息
    attrs: {
      // 属性 href 是链接所指向的 URL
      href: {},
      // 属性 title 是作为 tooltip 提示文本呈现给用户,默认为空
      title: {default: null}
    },
    // 表示光标位于该样式标记结尾处时,该样式标记是否需要 active 激活
    // 设置为 false 时,表示用户继续输入文字不带有同样的样式标记
    // 💡 如果光标置于样式标记的开头时
    //    而(该样式标记所应用的)节点同时位于其父节点的开头
    //    则是否激活也同样受该属性控制
    inclusive: false,
    // 定义了如何将 DOM 元素解析为该类型的样式标记的一些规则
    // 这里只设置了一条规则
    parseDOM: [
      {
        // 基于 DOM 元素的名称进行匹配
        // 即将 HTML 中的 `<a>` 元素(需要具有 href 属性)解析为链接样式标记
        tag: "a[href]",
        // 使用 getAttrs 属性更精细地设置 attributes
        getAttrs(dom: HTMLElement) {
          // 从 DOM 元素的相应属性获取值,以设置该样式标记的一系列 attributes
          return {
            href: dom.getAttribute("href"),
            title: dom.getAttribute("title")
          }
        }
      }
    ],
    // 定义了如何将该类型的样式标记转换为相应的 DOM 元素
    toDOM(node) {
      let {href, title} = node.attrs;
      return ["a", {href, title}, 0]
    }
  } as MarkSpec,
  // 斜体式强调样式标记
  em: {
    // 定义了如何将 DOM 元素解析为该类型的样式标记的一些规则
    // 这里设置了 4 条规则
    parseDOM: [
      // 基于 DOM 元素的名称进行匹配
      // 即将 HTML 中的 `<i>` 元素解析为斜体式强调样式标记
      {tag: "i"},
      // 基于 DOM 元素的名称进行匹配
      // 即将 HTML 中的 `<em>` 元素解析为斜体式强调样式标记
      {tag: "em"},
      // 基于 inline style 行内样式进行匹配
      // 即将带有内联样式 "font-style=italic" 的 HTML 元素解析为斜体式强调样式标记
      {style: "font-style=italic"},
      // 基于 inline style 行内样式进行匹配
      // 即带有内联样式 "font-style=normal" 的 HTML 元素
      // 这里设置了 clearMark 属性
      // 所以该匹配规则并不是为了将 HTML 元素解析为特定类型的样式标记
      // 而是清除已激活的样式标记
      // (如果该 HTML 之前有匹配到其他规则,并激活了名为 `em` 的样式标记,在该规则要将它清除)
      {style: "font-style=normal", clearMark: m => m.type.name == "em"}
    ],
    // 定义了如何将该类型的样式标记转换为相应的 DOM 元素
    toDOM() { return emDOM }
  } as MarkSpec,
  // 加粗式强调样式标记
  strong: {
    // 定义了如何将 DOM 元素解析为该类型的样式标记的一些规则
    // 这里设置了 4 条规则
    parseDOM: [
      // 基于 DOM 元素的名称进行匹配
      // 即将 HTML 中的 `<strong>` 元素解析为加粗式强调样式标记
      {tag: "strong"},
      // 该解析规则为了处理 Google Docs 的奇异行为所造成的问题
      // 从 Google Docs 复制内容时,会使用 <b>(而不是 <span> 或 <div>)作为一个容器
      // (对文本内容进行包裹,并添加一个 id 属性)
      // 并添加 font-weight="normal"(以区分真正带有加粗语义的 `<b>` 标签)
      // 所以以下规则在基于 HTML 标签名称进行匹配后,还采用属性 getAttrs 进行更精细的判断
      // 避免错误地将作为容器的 `<b>` 标签解析为加粗
      // 当所匹配的 <b> 标签同时具有内联样式 font-weight="normal"
      // 则该函数返回 false(即 getAttrs 属性为 false)表示匹配失败
      // 如果 <b> 标签不具有内联样式 font-weight="normal"
      // 则该函数返回 null,表示匹配成功(但是该 mark 不具有 attrs)
      {
        tag: "b",
        getAttrs: (node: HTMLElement) => node.style.fontWeight != "normal" && null
      },
      // 基于 inline style 行内样式进行匹配
      // 即带有内联样式 "font-weight=400"(字重与 normal 一致)的 HTML 元素
      // 这里设置了 clearMark 属性
      // 所以该匹配规则并不是为了将 HTML 元素解析为特定类型的样式标记
      // 而是清除已激活的样式标记
      // (如果该 HTML 之前有匹配到其他规则,并激活了名为 `strong` 的样式标记,在该规则要将它清除)
      {
        style: "font-weight=400",
        clearMark: m => m.type.name == "strong"
      },
      // 基于 inline style 行内样式进行匹配
      // 即内联样式带有 "font-weight" 的 HTML 元素无法满足规则
      // 只有当 `font-weight` 的值为字符串 `bold` 或 `bolder` 或数值(位于 `5xx` 至 `9xx` 之间)
      // 才匹配成功
      {
        style: "font-weight",
        getAttrs: (value: string) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null
      },
    ],
    // 定义了如何将该类型的样式标记转换为相应的 DOM 元素
    toDOM() { return strongDOM }
  } as MarkSpec,
  // 代码样式标记
  /// Code font mark. Represented as a `<code>` element.
  code: {
    // 定义了如何将 DOM 元素解析为该类型的样式标记的一些规则
    // 这里只设置了一条规则
    parseDOM: [
      // 基于 DOM 元素的名称进行匹配
      // 即将 HTML 中的 `<code>` 元素解析为代码样式标记
      {tag: "code"}
    ],
    // 定义了如何将该类型的样式标记转换为相应的 DOM 元素
    toDOM() { return codeDOM }
  } as MarkSpec
}

在以上代码中涉及到一些变量(可以是一个字符串、一个 DOM 元素、一个对象,或一个数组),它们作为 toDOM 方法的返回值(符合 TypeScript type DOMOutputSpec),它们描述了如何将样式标记转换为相应的 DOM 元素

ts
const emDOM: DOMOutputSpec = ["em", 0], // emDOM 描述了斜体式强调样式标记如何转换为 DOM 元素
      // strongDOM 描述了加粗式强调样式标记如何转换为 DOM 元素
      strongDOM: DOMOutputSpec = ["strong", 0],
      // codeDOM 描述了代码样式标记如何转换为 DOM 元素
      codeDOM: DOMOutputSpec = ["code", 0]

Copyright © 2026 Ben

Theme BlogiNote

Icons from Icônes