ProseMirror Dino Example For Command

prosemirror

ProseMirror Dino Example For Command

ProseMirror 在官方文档中演示了如何创建自定义 node 节点,并创建自定义指令(以实现点击按钮往编辑器中插入该节点的功能),完整的源码可以查看相关的 Github 仓库

一些值得注意的细节:

  • 通过 schema.spec.nodes 获取所有节点类型,它是一个 OrderMap 映射对象,以键值对(键名是字符串)的形式表示 schema 数据约束对象支持的节点类型
    与普通的对象的主要不同的是 OrderMap 是有顺序
    注意

    OrderMap 是 ProseMirror 作者所创建的一种数据结构,但是它并没有针对大量的键值对的数据进行优化,所以这种数据结构在处理大量数据时可能并不那么高效

    由于编辑器的节点类型(或样式标记类型)一般是有限的小型键值对数据集,所以在 ProseMirror 使用 OrderMap 性能应该是足够的


    OrderMap 内置了一些属性和方法以更方便地操作数据
    在该编辑器示例中,使用 orderMap.addBefore(place, key, string) 方法将给定的键值对(键名为 key)放置在 place(键值对的键名)之前(如果键名 place 未能在原 orderMap 里找到,则会将给定的键值对添加到末尾)
    ts
    const dinoSchema = new Schema({
      // 这里的 schema 是一个对象,来自 prosemirror-schema-basic 模块(所导出的变量)
      // schema 包含了一些预设的简单节点
      // 将 `dino` 恐龙节点放置在 `img` 图像节点前面
      nodes: schema.spec.nodes.addBefore("image", "dino", dinoNodeSpec),
      marks: schema.spec.marks
    })
    

    通过 schema.spec.marks 获取的所有样式标记类型,也是一个 OrderMap 映射
  • 根据前面对 OrderMap 的介绍,节点类型在 schema 中的是有先后顺序的(先出现/注册的优先级更高,对于样式标记类型也一样),由于编辑器中涉及对 DOM 的匹配和转换,而优先级别对此有重要影响
    在该示例中 dino 恐龙节点和 img 图像节点都是对应于 <img> 元素。但是 dino 节点还需要 <img> 元素带有特定的属性 img[dino-type] 才可以匹配;而 img 节点仅需要 <img> 元素带有图片通用的属性 img[src] 即可匹配
    在 schema 注册节点类型时,需要确保 dino 节点优先级更高(出现/排在 img 节点之前),这样才可以确保 <img dino-type="..."> 元素可以转换为 dino 节点,而不会都转换为 img 节点
    priority

    除了在 schema 注册节点类型时将 dino 节点置于 img 节点之前,还可以通过设置 priority 属性以提升恐龙节点的优先级

    dino 节点配置对象中 parseDOM(一个数组)包含一些将 DOM 元素解析为该节点的规则,可以在这些规则中设置属性 priority 以提高该节点解析的优先级,该属性值的默认值为 50,设置解析规则的优先级顺序,数值越大优先级越高,匹配时该规则会先执行设置

  • 创建自定义 command 指令,可以通过点击按钮在编辑器中插入 dino 节点,根据官方示例编写的简化版本的相关代码如下
    ts
    // command to insert dino node
    // refer to https://github.com/ProseMirror/website/blob/master/example/dino/index.js
    const dinoType = simpleSchemaWithDino.nodes.dino;
    function insertDino(type: any): Command {
      return (state, dispatch) => {
        const { $from } = state.selection;
        const index = $from.index(); // 光标所在的文本节点(在父节点里)的索引值
    
        // 这里通过方法 `node.canReplaceWith(from, to, nodeType)` 判断用类型为 nodeType 的节点替换(索引值)从 from 到 to 的节点是否可行
        // 由于这里假设选区处于光标状态,所以替换节点的索引值都是 index 即假设用 dino 节点替换光标所在的文本节点
        if(!$from.parent.canReplaceWith(index, index, dinoType)) return false;
    
        if(dispatch) {
          dispatch(state.tr.replaceSelectionWith(dinoType.create({type})));
        }
    
        return true;
      }
    }
    
    const dinoBtns = document.getElementsByTagName('button');
    const insertDinoCommands: {[key: string]: Command} = {};
    
    for(let i = 0; i < dinoBtns.length; i++) {
      const dinoType = dinoBtns[i].id;
      insertDinoCommands[dinoType] = insertDino(dinoType);
    
      dinoBtns[i].addEventListener('click', (event) => {
        if(event.target) {
          const type = (event.target as HTMLElement).id;
          insertDinoCommands[type](view.state, view.dispatch);
        }
      })
    }
    

    在官方示例中,还会使用 command(state) 的方式调用指令没有设置第二个参数 dispatch,表示该指令只是一个 dry run 「试运行」,即进行模拟或预演操作,但不对文档进行实际修改),根据返回的布尔值判断该指令在当前编辑器状态下是否可以执行

Copyright © 2025 Ben

Theme BlogiNote

Icons from Icônes