【文档编辑器专栏】6-ProseMirror Plugin插件系统之nodeViews

182 阅读2分钟

前言

到目前为止,我们都是通过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渲染

image.png

具体实现

现在我们开始编写nodeViews

  1. 去掉toDOM的hole
 toDOM: () => {
      // node view时, 不能加hole
      return ['div', { class: 'node-view' }]
      // toDOM规则时,需加hole
      // return ['div', { class: 'node-view' }, 0]
    },
  1. 编写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里面构造的一致

image.png

我们触发一个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);
});

1721745699261.gif

代码链接

github.com/pm-editor/d…

总结

plugin nodeViews在复杂的block上应用很广泛,还可以结合react/vue等框架去做dom渲染部分,这部分后续实战的时候再给大家讲解