前言
到目前为止,我们都是通过toDOM的规则去渲染dom。
对于某些用例,节点需要特定的界面,我们可以通过Plugin EditorProps.nodeViews去自定义节点视图
nodeViews
我们先定义一个新的节点
schema节点定义
custom_block: {
group: 'block', // block节点
selectable: true, // NodeSelection
dragable: true, // 支持拖拽
atom: true, // 独立的一个单元
content: 'inline+', // 子节点类型
// 三个属性值
attrs: {
id: { default: '' },
version: { default: '' },
fileName: { default: '' }
},
// 渲染成div class=node-view的形式
toDOM: () => {
// node view时, 不能加
// return ['div', { class: 'node-view' }]
// toDOM规则时,需加hole
return ['div', { class: 'node-view' }, 0]
},
// 解析内容规则
parseDOM: [{
tag: 'div.node-view', getAttrs(dom: HTMLElement) {
return {
id: dom.getAttribute('id'),
version: dom.getAttribute('version'),
fileName: dom.getAttribute('fileName'),
}
}
}]
}
在nodeViews还没接入之前,我们会看到正常的toDOM渲染
具体实现
现在我们开始编写nodeViews
- 去掉toDOM的hole
toDOM: () => {
// node view时, 不能加hole
return ['div', { class: 'node-view' }]
// toDOM规则时,需加hole
// return ['div', { class: 'node-view' }, 0]
},
- 编写nodeViews
- dom: 需要呈现到文档的dom节点
- contentDOM: 定义子content dom
- selectNode: 选中节点时触发
- disselectNode: 取消选中节点时触发
- update: node有更新时触发
- destory: 节点删除时触发或文档被移除时触发
- setSelection:节点中内容被选中时触发
- stopEvent:可以阻止容器的dom事件
- ignoreMutation:可忽略editor mutation或selection change
class TestNodeViews implements NodeView {
public dom: HTMLElement;
private node: Node;
private view: EditorView;
private getPos: () => number | undefined;
constructor(node: Node, view: EditorView, getPos: () => number | undefined) {
this.node = node;
this.view = view;
this.getPos = getPos;
// 初始化构造时,根据节点上的属性去创建dom节点
const attrs = node?.attrs || {};
this.dom = document.createElement('nodeview');
const container = document.createElement('div');
container.innerText = `id ${attrs.id}, version ${attrs.version} fileName ${attrs.fileName}`;
this.dom.append(container);
}
// 选中节点
selectNode() {
this.dom.classList.add("active");
console.log('node view select');
}
// 取消选中节点
deselectNode() {
this.dom.classList.remove("active")
console.log('node view dis select');
}
// node节点更新时触发
update(node: Node, decoration) {
console.log('node view update', node);
// return true则阻止默认的修改
return false;
}
// 节点删除时触发或文档被移除时触发
destroy() {
this.dom.remove();
}
}
注册到plugin 上
export const testPlugin = () => {
return new Plugin({
key: pluginKey,
props: {
nodeViews: {
custom_block: (node, view, getPos) => new TestNodeViews(node, view, getPos)
}
}
});
}
实际dom渲染出来会和我们在nodeViews里面构造的一致
我们触发一个node节点更新事件来理解nodeViews update的时机
const nodeViewsButton = document.querySelector('#nodeViewsButton');
nodeViewsButton?.addEventListener('click', () => {
const { state, dispatch } = editorView;
const { schema, tr, selection } = state;
// 选中该节点
const node = selection.$from.nodeAfter;
// 修改该节点的attrs的fileName
if (node?.type.name === 'custom_block') {
tr.setNodeAttribute(selection.from, 'fileName', 'newFileName.txt');
}
// 发布更新
dispatch(tr);
});
代码链接
总结
plugin nodeViews在复杂的block上应用很广泛,还可以结合react/vue等框架去做dom渲染部分,这部分后续实战的时候再给大家讲解