ProseMirror Schema Basic 模块
prosemirror-schema-basic 模块提供了一个 schema 数据结构的约束对象,以及它的配置对象(符合一种 TypeScript interface SchemaSpec)的属性 nodes 和属性 marks 的值(分别表示该文档所支持的节点类型和样式标记类型)
schema 对象
该模块导出一个 schema 数据结构的约束对象,它支持的节点类型和样式标记类型参考 CommonMark(一种 Markdown 格式)
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 的值
export const nodes = {
// 文档的根节点
doc: {
// 其内容(子节点)需要是 block group 中所包含的节点
// 具体包括 paragraph、blockquote、horizontal_rule、heading、code_block 这 5 种类型的节点
// 而数量是一个或多个
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 中所包含的节点
// 具体包括 paragraph、blockquote、horizontal_rule、heading、code_block 这 5 种类型的节点,它可以使用自身类型的节点作为子节点
// 而数量是一个或多个
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 元素
const pDOM: DOMOutputSpec = ["p", 0], // pDOM 描述了段落节点如何转换为 DOM 元素
blockquoteDOM: DOMOutputSpec = ["blockquote", 0], // blockquoteDOM 描述了引文节点如何转换为 DOM 元素
hrDOM: DOMOutputSpec = ["hr"], // hrDOM 描述了将水平线节点如何转换为 DOM 元素
// preDOM 描述了代码块节点如何转换为 DOM 元素
// 转换为 <pre><code>content</code></pre> 的形式
preDOM: DOMOutputSpec = ["pre", ["code", 0]],
brDOM: DOMOutputSpec = ["br"] // brDOM 描述了强制换行节点如何转换为 DOM 元素
marks 配置对象
该模块导出了一个 marks 对象,它包含一些属性(其值需要符合一种 TypeScript interface MarkSpec,规定了样式标记可接受哪些配置参数)对不同类型的样式标记进行描述
如何使用
在使用方法 new Schema(spec) 实例化 Schema 类时,该对象作为配置参数 spec(它是一个对象,符合一种 TypeScript interface SchemaSpec)的属性 spec.marks 的值
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 元素
const emDOM: DOMOutputSpec = ["em", 0], // emDOM 描述了斜体式强调样式标记如何转换为 DOM 元素
strongDOM: DOMOutputSpec = ["strong", 0], // strongDOM 描述了加粗式强调样式标记如何转换为 DOM 元素
codeDOM: DOMOutputSpec = ["code", 0] // codeDOM 描述了代码样式标记如何转换为 DOM 元素