前言
prosemirror的插件系统,提供了丰富的能力,让开发者可以轻松地开发出各种插件,是prosemirror变得强大的一个重要模块
相信大家都了解或知道webpack的插件原理,webpack在构建的各个阶段会提供出钩子,方便插件模块介入去做一些代码逻辑。例如 clean-webpack-html(清理构建物), html-webpack-plugin(构建后处理html)等webpack插件
prosemirror的插件系统也是类似的设计,但是prosemirror和webpack不一样的地方是,prosemirror提供的plugin钩子更多帮助开发者去实现更丰富的功能和视图操作
plugin
我们先使用官方提供的plugin,感受下plugin的作用
// 安装三个包
// npm install prosemirror-keymap prosemirror-commands prosemirror-history
import { keymap } from 'prosemirror-keymap'
import { baseKeymap } from 'prosemirror-commands'
import { history, undo, redo } from 'prosemirror-history';
const editorState = EditorState.create({
// schema
doc: DOMParser.fromSchema(schema).parse(content),
plugins: [
// 支持enter delete backspace等基本键盘操作
keymap(baseKeymap),
// 支持操作历史
history(),
// 支持键盘undo和redo
keymap({ "Mod-z": undo, "Mod-y": redo }),
]
});
可以看到prosemirror的插件,提供监听dom event事件的钩子
通过plugin的注册,可以看到编辑器已经支持了基本的enter delete backspace redo undo的操作了
下面我们具体讲下plugin的几个重要模块
plugin key
在给定状态下,只能有一个插件,可以通过这个key访问插件的配置和状态
import { Plugin, PluginKey } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
// new PluginKey得到
const pluginKey = new PluginKey('Test-Plugin');
export const testPlugin = () => {
return new Plugin({
// 传入key
key: pluginKey,
props: {
handleKeyDown: (view: EditorView, event: KeyboardEvent) => {
// 获取plugin state对象
const pluginState = pluginKey.getState(view.state);
return false;
}
},
});
}
pluginKey对象有getState和get方法,其实主要是用来获取plugin state
plugin props
官方文档 plugin props
ProseMirror 对可编辑 DOM 元素上触发的事件进行处理之前,会调用这些plugin props上注册的函数
如果return true, 则会阻止后续的监听响应,有点类似preventDefault
handleDONEvents为对象,其它为事件,支持的evnet比较多,大家可以查看源码
实现一个handleKeyDown的监听
可以看到每次keydownDown都会收到响应,就类似我们的dom.addEventListener, 只是这个是基于prosemirror编辑器的每一次keydown
import { Plugin, PluginKey } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
const pluginKey = new PluginKey('Test-Plugin');
export const testPlugin = () => {
return new Plugin({
key: pluginKey,
props: {
handleKeyDown: (view: EditorView, event: KeyboardEvent) => {
console.log(view, event);
return false;
}
}
});
}
plugin state
定义plugin内部使用的state,用于记录一些内部状态,方便内部和外部使用
- init: 初始化一个state对象
- apply: 每次有事务变更,都会触发apply。有点像react的didupdate
- toJSON
- fromJSON
return new Plugin({
key: pluginKey,
state:{
init: (config: EditorStateConfig, instance: EditorState) => {
return {
isShowXXX: false,
}
},
apply(tr: Transaction, value: T, oldState: EditorState, newState: EditorState) {
let isShowXXX: boolean = !!value.isShowXXX;
// do something else
if (false) {
isShowXXX = true;
}
return { isShowXXX };
},
},
props: {
handleKeyDown: (view: EditorView, event: KeyboardEvent) => {
const pluginState = pluginKey.getState(view.state);
if (pluginState.isShowXXX) {
console.log('do something');
}
return false;
}
},
});
plugin view
当插件需要与编辑器视图交互或在 DOM 中设置某些内容时,请使用此字段。
- update: 编辑器view有更新时
- destory: view destory时
declare type PluginView = {
/**
Called whenever the view's state is updated.
*/
update?: (view: EditorView, prevState: EditorState) => void;
/**
Called when the view is destroyed or receives a state
with different plugins.
*/
destroy?: () => void;
};
实际例子
PluginView定义了这个plugin视图相关的逻辑,获取到prosemirror的view并添加一个div。
结合Plugin State属性isShowXXX, 去显示不同的内容
import { Plugin, PluginKey, PluginView } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { EditorState } from 'prosemirror-state';
const pluginKey = new PluginKey('Test-Plugin');
class TestPluginView implements PluginView {
container: HTMLElement;
view: EditorView;
constructor(view: EditorView) {
// 初始化添加一个div
this.container = document.createElement('div');
this.view = view;
this.view.dom.parentNode?.appendChild(this.container);
}
// view update时,更改innerText
update(view: EditorView, state: EditorState) {
const { isShowXXX } = pluginKey.getState(state);
this.container.innerText = isShowXXX ? 'xxxx' : 'yyyy';
}
destroy() {
this.container.remove();
}
}
export const testPlugin = () => {
return new Plugin({
key: pluginKey,
state: {
init: () => {
return {
isShowXXX: false,
}
},
apply(tr, value) {
let isShowXXX: boolean = !!value.isShowXXX;
// 有事务更新就会将isShowXXX设置为true
if (true) {
isShowXXX = true;
}
return { isShowXXX };
},
},
props: {
handleKeyDown: (view: EditorView, event: KeyboardEvent) => {
const pluginState = pluginKey.getState(view.state);
if (pluginState.isShowXXX) {
console.log('do something');
}
return false;
}
},
view: (view) => {
return new TestPluginView(view);
}
});
}
上面例子讲述的是plugin提供的plugin view的实现方式,可以拓展出的场景是非常多的,例如字数统计/工具栏/右键菜单等
filterTransaction
filterTransaction?: (tr: Transaction, state: EditorState) => boolean;
在事务之前调用,允许插件取消事务
retrun true 则取消后续的事务
appendTransaction
在调用该事务后追加了一个事务
代码
总结
本文主要讲述了plugin的key/props/state/view的基础用法,以及几个对象之间的联动和应用场景
实际开发过程中,并不是plugin的props/state/view都是全部都一起用上,需要大家看需要选择
另外,Plugin nodeViews/markViews/Decorations,这些都是可以修改编辑器视图,后续会重点讲述这部分的内容