引言
ProseMirror是代码编辑器[CodeMirror](codemirror.net/) "https://codemirror.net/)")的作者Marijn Haverbeke开发的一款开源的富文本编辑器。 提供了一组工具和设计概念用来构建富文本编辑器,像乐高积木一样,需要开发者自己组装,并不是开箱即用。
ProseMirror基础知识
ProseMirror提供了四个核心模块,编辑器任何操作都需要他们。
四个核心模块如下:
-
prosemirror-model
定义编辑器文档模型,用来描述编辑器内容 -
prosemirror-state
描述整个编辑器状态的数据结构 -
prosemirror-view
将编辑器状态state 展示成对应视图 -
prosemirror-transform
提供了可以撤销和恢复的修改文档的能力
第一个简单的编辑器
1. 创建一个react项目
js
// 使用react脚手架创建一个react项目
create-react-app + 项目名称
2. 安装prosemirror相关依赖包
js
npm i prosemirror-view prosemirror-model prosemirror-state
3. 创建Editor编辑器对象
js
// Editor.js
import React from 'react';
import { EditorView } from 'prosemirror-view';
import { EditorState } from 'prosemirror-state';
import { Schema } from 'prosemirror-model';
import './Editor.css';
class Editor extends React.PureComponent {
constructor() {
super();
this.editorRef = React.createRef(null);
}
componentDidMount() {
this.createEditor();
}
createSchema() {
// 创建schema
this.schema = new Schema({
nodes: {
doc: {
content: 'block'
},
paragraph: {
content: 'inline*',
group: 'block',
toDOM: () => ['p', 0]
},
text: {
group: 'inline'
}
},
})
}
createPlugins() {
// 编辑器插件,暂时不使用任何插件
return [];
}
createView() {
const { current: place } = this.editorRef;
// 创建编辑器视图对象
this.view = new EditorView(place, {
state: EditorState.create({
schema: this.schema
})
});
// 创建编辑器数据对象
const newState = this.view.state.reconfigure({
plugins: this.createPlugins()
});
// 更新编辑器数据到视图
this.view.updateState(newState);
}
createEditor() {
this.createSchema();
this.createView();
}
render() {
return (
<div className='editor-container' ref={this.editorRef}></div>
)
}
}
export default Editor;
// Editor.css
.ProseMirror {
outline-width: 0px;
margin: 100px 60px;
}
4. 入口文件中引入编辑器
js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import Editor from './Editor';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Editor />);

schema
描述编辑器中nodes类型和nodes之间嵌套关系,文档节点与dom节点之间的转化关系。例如定义一个最简单的文档schema结构,由文本节点构建的文档。
js
new Schema({
nodes: {
// 文档顶层节点,必须有
doc: {
// 内容由0个或者多个text节点组成
content: 'text*'
},
// text 节点
text: {}
}
})
上面schema结构应用到编辑器后对应dom结构如下:

给上面shema增加一个段落节点,即文档doc顶层节点由多个段落节点paragraph组成,段落节点由多个text节点组成。
css
new Schema({
nodes: {
// 文档顶层节点,必须有
doc: {
// 内容由0个或者多个paragraph节点组成
content: 'paragraph*'
},
paragraph: {
// 内容由0个或者多个text节点组成
content: 'text*',
// 定义当前节点的默认序列化成 DOM/HTML 的方式, 子节点内容在p标签中。下面详细解释下toDOM应该返回的结构
toDOM: () => ['p', 0]
},
// text 节点
text: {}
}
})
schema中增加paragraph后,对应dom节点增加了一层p标签。

上面schema结构中有使用到toDOM,这里详细说明下。toDOM是一个函数,定义了当前节点渲染到dom中的序列化方式,需要返回DOMOutputSpec结构。
toDOM: (node) -> DOMOutputSpec
DOMOutputSpec 是DOM结构的描述方式,具体有三种形式:
- 字符串,表示文本节点
- dom节点,表示自身
- 一个数组,数组中第一个值表示DOM元素对应的标题名,第二个值是一个对象表示当前节点的attributes属性。数组第二个值之后的任何值(包括第二个值,如果它不是一个普通属性对象的话) 都被认为是该 DOM 元素的子元素,后面的值必须是有一个有效的
DOMOutputSpec
值,或者是数字 0。例如:
less
['div', {style:'background:green'}, 0], 表示<div style="color:red">子元素<div>
['div', {style:'color:red'}, ['p', 0]],表示 <div style="color:red"><p>子元素</p><div>
schema还有很多属性,在后面的文档中一点点介绍具体使用方法。
plugin
ProseMirror中提供了插件系统,用来扩展编辑行为和编辑状态。ProseMirror官方提供了很多插件,例如快捷键 keymap 插件,undo/redo功能的 history 插件。下面我们实现一个统计编辑次数的插件,感受下插件是什么。
javascript
const EditCountPlugin = new Plugin({
state: {
init() {
reutrn 0;
},
apply(tr, value) {
console.log('editCount:', vlaue + 1);
return value + 1;
}
}
})
插件中可以定义自己的数据state,插件初始化时会调用init方式,文档数据发生变化时会执行插件中apply方案。类似react中组件渲染的生命周期,后面写一篇详细的文章介绍ProseMirror中插件系统的设计原理。