介绍
ProseMirror 提供了一套用于构建富文本编辑器的工具和概念,使用受“所见即所得”启发的用户界面,但试图避免这种编辑风格的缺陷。
ProseMirror 的主要原则是您的代码可以完全控制文档及其内容。此文档不是 HTML 块,而是一个自定义数据结构,它仅包含您明确允许其包含的元素,并且与您指定的关系有关。所有更新都经过一个点,您可以在此检查它们并对其做出反应。
核心库不是简单的嵌入式组件——我们优先考虑模块化和可定制性,而不是简单性,希望将来人们能够基于 ProseMirror 分发嵌入式编辑器。因此,这更像是乐高积木,而不是火柴盒汽车。
有四个基本模块,是进行任何编辑所必需的,还有许多由核心团队维护的扩展模块,它们的状态与第三方模块相似 - 它们提供了有用的功能,但您可以省略它们或将它们替换为实现类似功能的其他模块。
基本模块包括:
此外,
- 基本编辑命令
prosemirror-commands、 - 绑定键
prosemirror-keymap、 - 撤消历史记录
prosemirror-history、 - 输入宏
prosemirror-inputrules、 - 协作编辑
prosemirror-collab、 - 简单文档模式
prosemirror-schema-basic
第一个demo
上面的名词和概念有点多, 我们从MVC模式来先讲解下
prosemirror mvc
传统MVC
prosemirror mvc
从设计上,设计理念是一样的
prosemirror-model定义编辑器的文档模型,用于描述编辑器内容的数据结构。prosemirror-state提供描述编辑器整个状态(包括选择)的数据结构以及从一个状态移动到下一个状态的事务系统。prosemirror-view实现一个用户界面组件,将给定的编辑器状态显示为浏览器中的可编辑元素,并处理用户与该元素的交互。prosemirror-transform包含以可记录和重放的方式修改文档的功能,这是模块中交易的基础state,并使撤消历史记录和协作编辑成为可能。
初始化代码框架
npm create vite
// 建议选择vanilla和typescript
npm install
// 安装prosemirror package
npm i prosemirror-model prosemirror-state prosemirror-view
// 安装一个basic schema
npm i prosemirror-schema-basic
npm run dev
编写第一个案例
import { EditorView } from "prosemirror-view";
import { EditorState } from "prosemirror-state";
import { schema } from "prosemirror-schema-basic";
import './style.css';
const id = 'prosemirror-editor';
export const mount = () => {
const el = document.querySelector(`#${id}`);
// 1. 提供schema (文档结构, 这里暂时用现成的)
// 2. 创建一个editor state数据实例
const editorState = EditorState.create({
schema
});
// 3. 创建editor view编辑器视图实例
const editorView = new EditorView(el, {
state: editorState
});
}
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `<div id="${id}"></div>`
mount();
看element元素渲染成这样,就意味着第一个案例成功跑起来了
model
由上面的流程,可以看出,schema是一切的开始
那schema是什么东西呢?
schema定义了编辑器的元素结构, 包含nodes和marks
你能指定编辑器可以有哪些节点,节点元素的具体表现,复制黏贴时候怎样解析成对应的节点
上文使用schema-basic, 不方便理解,我们编写一个自己的schema
import { EditorView } from "prosemirror-view";
import { EditorState } from "prosemirror-state";
// import { schema } from "prosemirror-schema-basic";
import { Schema, DOMParser } from "prosemirror-model";
import './style.css';
import './prosemirror.css';
const id = 'prosemirror-editor';
const schema = new Schema({
nodes: {
/// 定义最顶层doc节点
doc: {
content: "block+" // 子节点为多个block
},
/// 定义最底层text节点
text: {
group: "inline"
},
/// 定义段落节点
paragraph: {
content: "inline*",
group: "block",
parseDOM: [{ tag: "p" }],
toDOM() { return ['p', 0] }
},
/// heading节点
heading: {
attrs: {level: {default: 1}},
content: "inline*",
group: "block",
defining: true,
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}}],
toDOM(node) { return ["h" + node.attrs.level, 0] }
}
},
marks: {}
});
// 初始化一个p段落
const content = new window.DOMParser().parseFromString(
`<p>this is paragraph</p><h1>this is h1</h1>`,
"text/html"
).body;
export const mount = () => {
const el = document.querySelector(`#${id}`);
// 1. 提供schema (文档结构, 这里暂时用现成的)
// 2. 创建一个editor state数据实例
const editorState = EditorState.create({
// schema
doc: DOMParser.fromSchema(schema).parse(content)
});
// 3. 创建editor view编辑器视图实例
const editorView = new EditorView(el, {
state: editorState
});
}
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `<div id="${id}"></div>`;
mount();
如果我们不定义schema nodes里面的heading(删除schema nodes heading), 会发现h1表现没能正确渲染
所以,在这里主要表述一个概念。ProseMirror会根据你给它的元素标签内容来渲染对应的节点
heading: {
attrs: {level: {default: 1}}, // 代表定义当前节点有哪些属性
content: "inline*", // 该节点可以是0或多个inline的节点
group: "block", // 属于group分组
defining: true, // 复制黏贴后,是否保持原节点
// 黏贴到编辑器内时,匹配tag h1/2/3/4/5/6的标签,设置上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}}],
// 转换为html标签时,生成h1/2/3/4/5/6/的标签
toDOM(node) { return ["h" + node.attrs.level, 0] }
}
可以将{tag: "h1", attrs: {level: 1} 改为 {tag: "h1", attrs: {level: 7}
你会发现,元素渲染成了 h7
所以
parseDOM 定义了解析数据源dom的行为。
heading节点将h1解析后,根据parseDOM上的设定{tag: "h1", attrs: {level: 7},设置attrs上的level值为7。
最后通过toDOM定义了最终要渲染出的dom结构, h + attrs.level,即 h7
可以理解
schema的意义是,通过json配置化的形式,让开发者自己定义解析和渲染的行为
至于里面的字段,下一章会详细讲schema
state
State 是Prosemirror 的数据结构对象。包含了schema, selection, tr等一些对象
-
doc 是整个文档的doc tree结构
-
selection 选区系统
- plugins 插件相关 目前我们还没有定义插件,所以这里没有数据
- tr transaction 操作类相关
view
view就是视图对象,可以访问到顶层dom对象,也有state等
在原型上还挂了一些view操作方法
总结
至此,你已经成功跑起了第一个案例。
希望本文能让你对prosemirror的model/state/view有初步的理解
但我相信你一定有很多疑问,带着疑问,可以持续关注后续专栏文章。