ProseMirror View 模块

prosemirror

ProseMirror View 模块

prosemirror-view 模块主要用于将编辑器状态对象 state 展示在页面(渲染为 DOM),并处理用户与编辑器的交互事件。

提示

如果使用该模块,请确保在页面中引入了 style/prosemirror.css 样式表,以提供必要的外观设置

EditorView 类

该类用于管理页面上的一个 editable DOM(并让编辑器状态对象 state 与这个 DOM 的更新保持同步)

通过方法 new EditorView(place, props) 进行实例化,得到一个 view 对象,以下称作「编辑器视图对象」,它对(用于表示/反映编辑器状态的)editable DOM 进行管理,相关参数的具体说明如下:

编辑器状态与 editable DOM 保持同步

编辑器视图对象 view 将 editable DOM 的变动同步到编辑器状态 state 上,这样就可以确保 editable DOM 实时反映了编辑器当前的状态

其流程大致如下:

  1. 用户与 DOM 进行交互
  2. 利用浏览器原生的引擎对 editable DOM 进行修改
  3. 编辑器视图对象 view 基于 DOM 的变动修改编辑器状态,以实现编辑器与页面的 DOM 相同步

所以一般先利用浏览器原始的编辑器行为 editing action 响应用户的操作(对 editable DOM 进行修改),然后 ProseMirror 再基于 DOM 的变动分发 transaction 更新编辑器状态(这一步一般是不需要再去改变 DOM,仅更新编辑器状态对象即可;只有遇到特殊的需求而导致不应用浏览器默认的编辑行为时,才需要再去改变 DOM,例如 transaction 被取消,则需要将 DOM 恢复为用户操作之前的样子

对于选区也是利用浏览器原生的引擎先进行更改,然后 ProseMirror 再分发 transaction 更新编辑器状态,让(存储在编辑器状态里)selection 与 editable DOM 的选区实现同步

虽然 ProseMirror 支持通过监听(editable 编辑相关的)事件 ❓ 对操作进行劫持(取消浏览器的默认行为),以实现定制化的响应,但是 ProseMirror 一般使用浏览器的默认行为来响应用户的操作,因为原生引擎就已经可以很好地处理许多场景(而无需编写复杂的代码)

例如在两个较长的段落中间夹着一个较短的段落,通过键盘的上下键进行光标导航,光标移到较短的段落时光标会置于末尾,而光标再移到较长的段落时浏览器原生引擎可以准确地恢复光标的水平位置(而如果对按键操作事件进行监听,阻止默认的行为,则会丢失这个功能)

光标导航
光标导航

  • 第一个参数 place 用于指定编辑器会在页面的哪个位置,其值有多种类型,以分别适用不同的场景:
    • 可以是一个 DOMNode 对象 它作为编辑器的容器
      ProseMirror 会自动使用方法 document.createElement("div") 创建一个 editable DOM,作为编辑器的根元素,并将它追加 append 到 place 参数所指定的 DOMNode 对象里,作为它的子节点(但是保留其原有的子节点)
    • 可以是一个函数 fn(editor: HTMLElement) 提供更灵活的方式将编辑器插入到页面
      该函数会接受一个参数 editor 它是一个 editable HTMLElement 元素(由 ProseMirror 自动使用方法 document.createElement("div") 创建的),。如果要在创建编辑器视图时执行额外的逻辑操作则可以使用该函数。
    • 可以是一个对象 {mount: HTMLElement}
      即该对象包含一个属性 mount,其值是一个 editable HTMLElement 元素,直接将其作为编辑器的根元素(可以理解为 ProseMirror 接管了这个 DOM 元素作为编辑器,而不再另外创建一个 editable div 元素来作为编辑器的根元素)
    • 可以是 null,则表示编辑器还没有添加 mounted 到页面上,即该方法所创建的编辑器视图 view 还没有与页面上的任何 editable DOM 相关联
  • 第二个参数 props 是一个配置对象(它需要符合一种 TypeScript interface DirectEditorProps),用于设置编辑器视图相关的状态和行为
    props

    props 是单词 properties 的缩写,常见于现代前端框架中,一般是指父组件向子组件传递的数据或属性,ProseMirror 借鉴了这个概念

    在这里的参数 props 用于对编辑器视图(可以将其类比为 UI 组件)进行配置

    在现代前端框架中,一般遵循单向数据流的模式向子组件传递的数据,即 props 不能在子组件中直接进行修改(只能将 props 的值作为初始值,赋值给子组件内部逻辑中变量,再对该变量进行修改),只能在使用组件的上下文代码(即父组件)中进行修改

    对于这里的参数 props 也是一样的,不能在编辑器视图中对该参数对象的属性直接进行修改,只能在传递/配置参数 props 的上下文代码中对它的属性进行更新。 ⚠️ 但是属性 props.state 除外,因为编辑器状态对象是 immutable 不能直接通过变更 prop 来修改 state ❓ 可以通过方法 editorView.updateState() 来更新 state

    另外可以在实例化插件时,通过配置对象的属性 props(符合 TypeScript interface EditorProps)来设置视图的 props 参数(这些插件在编辑器视图实例化时进行注册

    ts
    // 该插件的作用是基于文档内容字数是否超过 max 阈值,来判断编辑器是否可以继续编辑
    function maxSizePlugin(max) {
      return new Plugin({
        props: {
          // 属性 editable 基于文档内容字数进行变动
          editable(state) { return state.doc.content.size < max }
        }
      })
    }
    
    DirectEditorProps

    TypeScript interface DirectEditorProps 描述了编辑器视图的配置对象

    • state 属性:一个编辑器状态对象 state,用于设置该编辑器视图与哪个编辑器状态相关联/绑定
    • plugins(可选)属性:一个数组(其元素是 plugin 插件对象),用于将一系列插件的 viewprops 应用到视图对象上的
      注意

      以上所设置的插件需要应用到编辑器视图上,所以这些插件(其配置对象)不能具有 state 字段filterTransaction 字段appendTransaction 字段,否则会抛出错误

      具有上述字段的插件只能应用到编辑器状态上

    • dispatchTransaction 属性:一个函数 fn(tr: Transaction) 用以设置当视图对象分发事务时,需要执行的一些额外处理
      该属性值是一个函数 fn(tr) 其入参 tr 是一个事务对象。视图对象分发一个事务 dispatch transaction 时,在事务应用到编辑器的状态对象,会先执行该回调函数。
      该回调函数中 this 指向当前视图对象
      注意

      请保证在该回调函数里最后执行了方法 this.updateState(state) 以更新编辑器状态,该方法的入参 state 是(应用了视图所分发的事务 tr 后所生成的)新的编辑器状态

      ts
      // 应用了视图所分发的事务 `tr` 后生成新的编辑器状态对象 state
      const newState = view.state.apply(tr);
      // 也可以使用 applyTransaction 分发事务 `tr`,但需要解构来获取新的编辑器状态对象
      // const {state, transactions} = view.state.applyTransaction(tr);
      // 更新编辑器状态
      view.updateState(newState);
      

    它其实继承自另一个 TypeScript interface EditorProps,即除了上述两个属性,还可以用 interface EditorProps 所定义的属性和方法来配置编辑器视图

    EditorProps(它也可用于配置 plugin 插件)相比,DirectEditorProps 接口额外添加了的一些属性和方法,只能直接用于设置编辑器视图 view(而不能用于配置 plugin 插件)

    EditorProps

    TypeScript interface EditorProps<P=any> 描述了编辑器视图或插件的配置对象

    提示

    EditorProps<P=any> 是一个泛型接口,可以设置类型变量 P(默认值是 any),它表示该接口所约束的配置对象的方法(例如以 handler 为前缀的一系列属性)里的this 所指向的数据类型

    如果 EditorProps 用于描述插件的配置对象,则该类型变量传入的是插件对象

    说明

    其中以 handler 为前缀的属性,是用于为相应事件设置处理函数(例如 handleKeyDown 用于响应按键事件),当事件分发时每次只能有一个处理函数在执行,会先执行在视图对象中设置的相应的事件处理函数,然后是在插件中设置的相应的事件处理函数(会根据插件注册的先后顺序,依次执行),直到其中一个事件处理函数返回 true 为止(表示该事件已响应/处理)

    而对于其他非函数型的属性,如果在多个插件中都进行了配置,会采用第一个设置值

    以下属性和方法与事件监听处理相关

    • handleDOMEvents(可选)属性:它是一个对象,以键值对的方式为不同的 DOM 事件设置处理函数
      ts
      // 属性值是一个对象
      // 其中属性名是一个表示 DOM 事件类型的字符串,值是响应函数
      {
        [event in keyof DOMEventMap]: fn(view: EditorView, event: DOMEventMap[event]) → boolean | undefined
      }
      

      该对象的属性名是 DOM 事件名称(具体而言是 interface DOMEventMap 所列出的键名,而该接口实际上继承自 TypeScript 的内置 interface HTMLElementEventMap,所以可以查看这个列表来看看该对象的属性名有哪些备选项)
      属性值是事件处理函数 fn(view: EditorView, event: DOMEventMap[event]) 第一个参数 view 是编辑器视图对象;第二个参数 event 是当前分发的事件对象。最后应该返回一个布尔值,以表示该处理函数是否已经响应/处理完成了该事件
      注意

      虽然后文列出的一系列 handle 为前缀的属性也是用于设置事件处理函数,但是与该属性 handleDOMEvents 所设置的事件处理函数有所不同

      属性 handleDOMEvents 灵活性更大,即可以为各种不同类型的事件设置处理函数,而后文列出的一系列 handle 为前缀的属性一般只能针对某一种类型的事件设置处理函数

      而在相应的事件分发时,通过以上属性 handleDOMEvents 所设置的处理函数会先调用,(假如还通过后文列出的一系列 handle 为前缀的属性设置了事件处理函数,它们之后才会被调用)

      通过以上属性 handleDOMEvents 所设置的事件处理函数,并没有针对事件的默认行为进行处理,如果需要阻止事件的默认行为,需要在事件处理函数中调用方法 event.preventDefault();而通过后文列出的一系列 handle 为前缀的属性所设置的事件处理函数,被调用前视图对象会自动先调用 event.preventDefault() 以阻止事件触发浏览器的相关默认行为(即让编辑器在接收到用户的交互时,仅通过 Prosemirror 来处理)

    • handleKeyDown(可选)属性:一个事件处理函数 ⁠fn(view, event) 当编辑器接收到 keydown 事件时会调用该处理函数,第一个参数 view 是编辑器视图对象,第二个参数 event 是 KeyboardEvent 键盘事件对象。最后返回一个布尔值或 undefined,以表示该处理函数是否已经响应/处理完成了该事件
    • handleKeyPress(可选)属性:一个事件处理函数 ⁠fn(view, event) 当编辑器接收到 keypress 事件时会调用该处理函数,第一个参数 view 是编辑器视图对象,第二个参数 event 是 KeyboardEvent 键盘事件对象。最后返回一个布尔值或 undefined,以表示该处理函数是否已经响应/处理完成了该事件
    • handleTextInput(可选)属性:一个事件处理函数 fn(view, from, to, text) 当编辑器接收到 input 事件时会调用该处理函数,第一个参数 view 是视图对象;第二、三个参数 fromto 是数值(符合 index schema 规则,表示文档的位置),分别表示在进行文本输入时编辑器选区的所覆盖的文档范围的 lower boundary 下界(在文档中较前的位置)和 upper boundary 上界(在文档中较后的位置);第四个参数 text 是字符串,表示用户输入的文本。
      注意

      该函数会在输入内容应用/显示到页面之前先执行。

      函数最后应该返回一个布尔值或 undefined,如果返回 true 则 Prosemirror 会阻止 input 事件的默认行为。💡 该方法一般用于拦截输入,以便根据用户原始输入的内容生成相应的内容,例如自动转换 markdown 语法,自动填充等。

    • handleClickOn(可选)属性:一个事件处理函数 ⁠fn(view, pos, node, nodePos, event, direct) 当编辑器接收到 click 事件时,该事件从内往外冒泡时,其路径上所经过的节点(编辑器的节点)都调用一遍该处理函数,各参数的具体说明如下:
      • 第一个参数 view 是编辑器视图对象
      • 第二个参数 pos 是一个数值,表示点击的位置(符合 index schema 规则,表示文档的位置)
      • 第三个参数 node 是事件冒泡过程当前所到达的节点对象
      • 第四个参数 nodePos 是一个数值,表示当前节点在编辑器中的位置(符合 index schema 规则,表示文档的位置)
      • 第五个参数 event 是 MouseEvent 事件对象
      • 第六个参数 direct 是一个布尔值,表示当前节点是否为正好被点击的(即最内层的)节点

      最后返回一个布尔值或 undefined,以表示该处理函数是否已经响应/处理完成了该事件
    • handleClick(可选)属性:一个事件处理函数 fn(view, pos, event) 当编辑器接收到 click 事件时调用该处理函数,各参数的具体说明如下:
      • 第一个参数 view 是编辑器视图对象
      • 第二个参数 pos 是一个数值,表示点击的位置(符合 index schema 规则,表示文档的位置)
      • 第三个参数 event 是 MouseEvent 事件对象

      最后返回一个布尔值或 undefined,以表示该处理函数是否已经响应/处理完成了该事件
      区别

      该属性所设置的处理函数也是在编辑器接收到 click 事件后执行的,但与 handleClickOn 属性所设置的事件处理函数有点不同

      该属性的事件处理函数是在编辑器被点击后执行一次,且在 handleClickOn 属性所设置的事件处理函数调用完之后才执行。

    • handleDoubleClickOn(可选)属性:一个事件处理函数 fn(view, pos, node, nodePos, event, direct),与属性 handleClickOn 类似,不过它是针对 dblclick 事件的。最后返回一个布尔值或 undefined,以表示该处理函数是否已经响应/处理完成了该事件
    • handleDoubleClick(可选)属性:一个事件处理函数 fn(view, pos, event),与属性 handlerClick 类似,不过它是针对 dblclick 事件的。最后返回一个布尔值或 undefined,以表示该处理函数是否已经响应/处理完成了该事件
      区别

      该属性的事件处理函数是在编辑器被点击后执行一次,且在 handleDoubleClickOn 属性所设置的事件处理函数调用完之后才执行。

    • handleTripleClickOn(可选)属性:一个事件处理函数 fn(view, pos, node, nodePos, event, direct),与属性 handleClickOn 类似,不过它是针对鼠标连续点击三次的场景(一般默认行为是选中所点击的整个段落)。最后返回一个布尔值或 undefined,以表示该处理函数是否已经响应/处理完成了该事件
      提示

      DOM 并没有提供与 triple click 相关的原生事件,根据 ProseMirror 的源码通过点击位置和间隔时间(判定为连续点击的最大间隔阈值是 500ms)来判定用户是否连续点击鼠标三次

      另外根据 StackOverflow 的一个回答,其实可以基于鼠标事件对象的属性 event.detail 来判断是点击的次数,它是一个数字,表示 current click count

      但是可能由于不同浏览器对于连续点击的判断(最大间隔阈值)有所不同,ProseMirror 在 prosemirror-view 模块内部自己实现了判断单击、双击、三击的逻辑,以确保编辑器在不同浏览器上提供一样的交互体验

    • handleTripleClick(可选)属性:一个事件处理函数 fn(view, pos, event) ,与属性 handlerClick 类似,不过它是针对鼠标连续点击三次的场景(一般默认行为是选中所点击的整个段落)。最后返回一个布尔值或 undefined,以表示该处理函数是否已经响应/处理完成了该事件
      区别

      该属性的事件处理函数是在编辑器被点击后执行一次,且在 handleTripleClickOn 属性所设置的事件处理函数调用完之后才执行。

    • handlePaste(可选)属性:一个事件处理函数 fn(view, event, slice) 当编辑器接收到 paste 事件时调用该处理函数,各参数的具体说明如下:
      • 第一个参数 view 是编辑器视图对象
      • 第二个参数 event 是 ClipboardEvent 事件对象
      • 第三参数 slice 是一个 slice 对象(它是 ProseMirror 对粘贴内容解析后生成的)
      提示

      如果希望获取(剪切板)粘贴的原始内容,可以调用粘贴事件对象的方法 event.clipboardData.getData() 来获取


      最后返回一个布尔值或 undefined,以表示该处理函数是否已经响应/处理完成了该事件
    • handleDrop(可选)属性:一个事件处理函数 fn(view, event, slice, moved) 当编辑器接收到 drop 事件时调用该处理函数,各参数的具体说明如下:
      • 第一个参数 view 是编辑器视图对象
      • 第二个参数 event 是 DragEvent 事件对象
      • 第三参数 slice 是一个 slice 对象(它是 ProseMirror 对粘贴内容解析后生成的)
      • 第四个参数 moved 是一个布尔值,以表示拖放的内容是否源于编辑器内,如果值为 true 即表示拖拽移动的内容是来自编辑器当前选区,在移动后原来的内容默认应该被移除(以实现编辑器的内容从一个位置移动到另一个位置的效果)

      最后返回一个布尔值或 undefined,以表示该处理函数是否已经响应/处理完成了该事件
    • handleScrollToSelection(可选)属性:一个事件处理函数 fn(view) 当视图对象尝试将编辑器的选区滚动到视窗中时调用该处理函数(一般会通过调用事务的方法 tr.scrollIntoView() 在编辑器状态更新完成后,将选区滚动到视图窗口中),参数 view 是编辑器的视图对象
      函数最后应该返回一个布尔值,如果为 true 就表示阻止默认的滚动行为,具体如何响应就可以由该处理函数来决定;如果为 false 表示它不阻止默认的滚动(或让其他事件处理函数来进行处理)
    • createSelectionBetween(可选)属性:一个函数 ⁠fn(view, anchor, head) 用于覆写/自定义编辑器(根据 DOM 选区)创建选区的行为。各参数的说明如下:
      • 第一个参数 view 是视图对象
      • 第二、三个参数 anchorhead 都是一个 resolvedPos 对象,分别表示 DOM 选区的锚点和动点经过 ProseMirror 解析后的结果

      最后返回一个 selection 选区对象(以在编辑器状态对象中表示/保留当前 DOM 选区)
    • domParser(可选)属性:一个 domParser 对象,用于为编辑器设置自定义的 DOM 解析器(将 DOM 解析为编辑器可识别的格式)
      提示

      当 editable DOM 改变时,编辑器就会使用该属性所设置的 DOM 解析器读取页面内容,将页面的 DOM 转换为编辑器可识别的格式,如 node 节点对象、slice 切片对象等

      默认的 DOM 解释器是通过调用 DomParser 类的静态方法 DomParser.fromSchema(schema)(基于 schema 数据结构的约束对象)所生成的

    以下属性和方法与剪切板相关

    • transformCopied⁠(可选)属性:一个函数 fn(slice, view) 当往剪切板复制内容时,会先调用该函数。第一个参数 slice 是一个 slice 切片对象(表示需要复制进剪切板的内容),第二个参数 view 是编辑器视图对象
      最后返回一个(经过转换处理)slice 切片对象
      一个常见的应用场景是将 HTML 内容粘贴到编辑器(进行解析前)先进行「清洗」操作
    • transformPastedHTML(可选)属性:一个函数 fn(html, view) 当往编辑器粘贴 HTML 格式的文本时,在 domParser 解析这些内容之前会先调用该函数。各参数的具体说明如下:
      • 第一个参数 html 是一段 HTML 字符串
      • 第二个参数 view 是编辑器视图对象

      最后返回一个(经过转换处理)表示 HTML 的字符串
      一个常见的应用场景是将 HTML 内容粘贴到编辑器(进行解析前)先进行「清洗」操作
    • transformPastedText(可选)属性:与前一个属性 transformPastedHTML 类似也是对粘贴的内容进行预处理,但它针对的(粘贴内容)是纯文本。一个函数 fn(text, plain, view) 当往编辑器粘贴纯文本时,在编辑器使用后述属性 clipboardTextParser 所设置的方法解析这些内容(成为一个 slice 切片对象)之前会先调用该函数。其中第二个参数 plain 是一个布尔值,表示是否将所粘贴的内容强制视为纯文本 ❓ 最后返回一个(经过转换处理的)表示纯文本的字符串
    • transformPasted(可选)属性:一个函数 fn(slice, view) 当通过复制粘贴或拖放的方式将内容插入到编辑器之前会先调用该函数。
      各参数的具体说明如下:
      • 第一个参数 slice 是一个 slice 切片对象(表示需要插入的内容)
      • 第二个参数 view 是编辑器视图对象

      最后返回一个(经过转换处理)slice 切片对象
      提示

      可以将其看作是一个通用型的粘贴内容转换器

      而属性 transformPastedHTML 或属性 transformPastedText 则分别只针对 HTML 或纯文本设置粘贴内容转换器

    • clipboardParser(可选)属性:一个 domParser 对象,用于为剪切板设置自定义的 DOM 解析器
      当编辑器需要从剪切板读取内容(且内容是 HTML 格式)时,例如执行粘贴操作,会使用该解析器(将 DOM 解析为编辑器可识别的格式)
      如果不设置该属性,则默认采用前述属性 domParser 所设置的 DOM 解析器来作为剪切板的 DOM 解析器
    • clipboardTextParser(可选)属性:一个函数 fn(text, $context, plain, view) 用于为剪切板设置自定义的纯文本解析器
      当编辑器需要从剪切板读取内容(且内容是纯文本)时,例如执行粘贴操作,会使用该解释器,将纯文本解析为 slice 切片对象
      各参数的具体说明如下:
      • 第一个参数 text 是字符串,剪切板中需要解析的内容
      • 第二个参数 $context 是一个 resolvedPos 对象(表示光标的位置 ❓)
      • 第三个参数 plain 是一个布尔值,如果要将所粘贴的内容强制视为纯文本 ❓ 则该值设置为 true
      • 第四个参数 view 是编辑器视图对象

      最后返回一个 slice 对象
      提示

      该解析器会在前述属性 transformPastedText 所设置的转换器(对纯文本进行转换处理)执行完成后再运行

      默认的剪切板纯文本解析器的行为是将每个纯文本段落包裹到一个 <p> 标签内,将它们转换为 HTML 格式(实际是返回一个 slice 对象)

      然后 ProseMirror 再调用前一个属性 clipboardParser 所设置的 DOM 解析器来解析这些 HTML 文本

    • clipboardSerializer(可选)属性:一个 domSerializer 对象,用于为剪切板设置自定义的 DOM 生成器/序列化器
      当从编辑器复制内容到剪切板时,会调用该 DOM 序列化器对内容进行序列化(将编辑器数据,如 node 节点对象或 fragment 片段对象,序列化为 HTML),再添加到剪切板中
      默认的 DOM 序列化器是通过调用 DOMSerializer 类的静态方法 DOMSerializer.fromSchema(基于 schema 数据结构的约束对象)所生成的
      提示

      由于序列化只会调用该 DOM 序列化器的一个方法 domSerializer.serializedFragment() 即可完成,所以配置该属性时(除了提供一个 DOMSerializer 类实例)也可以提供一个只具有属性 serializedFragment 的对象,就可以实现类似 DOM 序列化器的功能

    • clipboardTextSerializer(可选)属性:一个函数 fn(slice, view) 用于为剪切板设置自定义的纯文本序列化
      当需要将编辑器选区的内容以纯文本的形式复制到剪切板时,会调用该纯文本序列化器,将选区内容转换为字符串
      第一个参数 slice 是 slice 切片对象(表示选区内容),第二个参数 view 是编辑器视图对象
      提示

      如果没有设置该属性,则默认采用选区所属的父节点 ❓ 的方法 parentNode.textBetween() 获取选区范围的文本内容

    以下属性和方法与自定义视图相关

    • nodeViews(可选)属性:它是一个对象 {nodeName: NodeViewConstructor} 以键值对的形式列出一系列不同类型的节点的视图构建函数(这些节点采用自定义的渲染展示方式和交互行为方式),其中键是节点的名称,值是对应的视图构建函数
      视图构建函数 NodeViewConstructor(node, view, getPos, decorations, innerDecorations) 各参数的具体说明如下:
      • 第一个参数 node节点对象
      • 第二个参数 view 是编辑器视图对象
      • 第三个参数 getPos 是一个方法,通过调用它可以获取节点在文档中的位置(返回一个数值,符合 index schema 规则;如果节点脱离了文档,则该方法返回 undefined),如果需要分发 transaction 更新节点时知道它的位置很有用
      • 第四个参数 decorations 是一个数组,它的元素是 node decoration 节点装饰器对象或 inline decoration 内联装饰器对象,以表示应用到该节点的一系列装饰器
        提示

        节点视图一般视为「隔离独立」在编辑器文档里,所以这些装饰器通常并不对其有影响,可以忽略该参数

        但是有时候它们也可以看作是提供额外的上下文语境,例如在 plugin.propsdecoration 属性上,可以通过构造 decoration 的时候添加一些额外的信息,然后在这个参数中拿到这些信息

      • 第五个参数 innerDecorations 是一个对象(符合 TypeScript interface DecorationSource,规定了一些方法用于处理装饰器),表示节点内容中包含的装饰器
        提示

        如果节点并没有内容或属性 contentDOM,则该参数可以忽略不考虑,因为编辑器会将装饰器自动绘制到节点视图中;如果节点具有内容或属性 contentDOM,例如在节点视图中内嵌一个编辑器,则需要考虑如何处理这些装饰器


      视图构建函数返回值是一个对象(符合 TypeScript interface NodeView)称为「节点视图」
      NodeView

      节点对象一般使用(在 schema 数据约束对象中)相应节点类型 nodeType 的属性 toDOM)在页面上渲染出 DOM 元素,默认由 ProseMirror 来控制节点如何在页面显示和响应用户的操作

      但有时候场景需要对节点的视图有更高的控制自由度,例如为用户提供特殊的交互界面,可以使用该属性为特定类型的节点设置自定义的 UI 渲染形式和交互方式(而不采用 schema 中所定义的方法来构建节点视图)

      TypeScript interface NodeView 描述了一个节点的视图对象

      • dom 属性:一个 DOMNode,用于设置该节点渲染到页面的形式(相当于节点配置对象 nodeSpec 的属性 toDOM 的作用,如果节点设置了 nodeView 就会使用该属性进行覆盖)
      • contentDOM(可选)属性:一个 HTMLElement,表示节点内容渲染到页面的哪个 DOM 内
        注意

        该属性需要在设置了上一个属性 dom,且该节点视图所对应的节点不是叶子节点才生效


        当设置了该属性,Prosemirror 会自动将节点内容(即它的子节点)渲染到该指定的 DOM 内;如果没有设置该属性,则需要开发者(构建节点视图时)手动设置如何渲染该节点的内容
      提示

      为了兼容旧版本,也支持在该属性中为不同类型的样式标记设置视图构建函数,但是更推荐通过下文所介绍的属性 markViews 来设置

      但是对于样式标记视图的构建函数,它的返回的对象只能包含两个属性 {dom: DOMNode, contentDOM?: HTMLElement},以下列出的其他属性/方法都不支持配置

      • update(可选)属性:一个函数 fn(node, decorations, innerDecorations) 以更精细地控制 nodeView 如何更新(对应的 DOM 元素),该函数最后返回一个布尔值以表示是否更新完成
        该函数的各参数的具体说明如下:
        • 第一个参数 node 是该节点视图所对应的 node 节点对象(具有最新状态的,甚至可能和更新前的节点类型不一样)
        • 第二参数 decorations 是一个数组,它的元素是 decoration 装饰器对象,以表示应用到该节点的一系列装饰器(它们通常并不影响节点视图的更新,一般可以忽略它们)
        • 第三个参数 innerDecorations 是一个对象(符合 TypeScript interface DecorationSource),表示应用于节点内容(子节点)上的装饰器(通常不需要考虑它们对节点视图更新的影响)

        该函数最后返回值一个布尔值,以表示节点视图所对应的节点是否可以进行更新
        提示

        如果该节点视图设置了属性 contentDOM(或没有设置属性 dom),则该节点视图所对应的节点的子节点的更新由 Prosemirror 自动处理


        通过该方法可以按需复用当前的 nodeView 实例,以提高编辑器的性能,该函数最后返回一个布尔值以表示是否更新完成
        • 如果该方法返回 true 就指当前的 nodeView 可以通过更新,来表示它所对应的节点的新状态,即该 nodeView 实例可以复用(一般该 nodeView 在页面所对应的 DOM 元素也得以复用)
        • 如果返回 false 表示无法通过更新当前的 nodeView 来表示它所对应的节点的新状态(则需要重新调用构造函数创建一个新的 nodeView 实例来表示它所对应的节点的新状态,一般该 nodeView 在页面所对应的 DOM 元素也需要重绘)
        注意

        应该避免update 函数里 dispatch 事务,这可能触发 infinite loop

      • selectNode(可选)属性:一个函数 fn(),用于自定义该节点选中时的状态(即节点选区的展示方式)
      • deselectNode(可选)属性:一个函数 fn(),用于自定义该节点取消选中时的状态。一般与上一个属性 selectNode 配合使用(以移除所设置的渲染效果)
      • setSelection(可选)属性:一个函数 fn(anchor, head, root),用于自定义在节点里进行框选(创建选区)的交互行为
        提示

        当用户框选节点内容时,默认情况下会根据参数 anchorhead 所表示的位置创建一个 DOM 选区,但可以通过该属性覆盖这个默认行为


        该函数的各参数的具体说明如下:
        • 第一、二个参数 anchorhead 是数值,分别表示选区相对于该节点开头位置的锚点和动点位置(符合 index schema 规则,表示文档的位置)
        • 第三个参数 root 是该选区所在的 DOM 节点
      • stopEvent(可选)属性:一个函数 fn(event)(参数 event 是在该节点视图上所触发的的事件对象),用于阻止某些/全部来自该节点视图的事件,让它们不被编辑器所处理。最后返回值是一个布尔值,如果为 true 则阻止事件冒泡
      • ignoreMutation(可选)属性:一个函数 fn(dom.MutationRecord),用于设置编辑器是否忽略该节点视图所对应的 DOM 的变化(包括选区的变化)
        函数 fn(dom.MutationRecord) 的参数是一个 DOM MutationObserver 变化记录。当 DOM 变化时,或节点视图层内的选区变化时(此时 DOM 的变化记录对象的属性 type 其值为 selection),会调用该函数
        该函数最后返回一个布尔值,如果为 false 则编辑器会响应 DOM 变化,重新读取选区,或重新解析发生变化的 DOM 节点;如果为 true 则编辑器会忽略 DOM 变化
      • destroy(可选)属性:一个函数 fn(),当节点视图(或整个编辑器)销毁时会执行该函数
    • markViews(可选)属性:它是一个对象 {markName: MarkViewConstructor} 以键值对的形式列出一系列不同类型的样式标记的视图构建函数(和前一个属性 nodeViews 类似,为这些样式标记提供自定义的渲染展示方式,但是不能定制交互行为),其中键是样式标记的名称,值是对应的视图构建函数
      视图构建函数 MarkViewConstructor(mark, view, inline) 各参数的具体说明如下:
      • 第一个参数 mark样式标记对象
      • 第二个参数 view 是编辑器视图对象
      • 第三个参数 inline 是一个布尔值,表示该样式标记的内容是否为 inline 内联类型

      视图构建函数返回值是一个对象,只能包含两个属性 {dom: HTMLElement, contentDOM⁠?: HTMLElement}(具体介绍可以查看 interface NodeView 的相应属性 dom属性 contentDOM,在样式标记视图中它们的含义是一致的)
    • decorations(可选)属性:一个函数 fn(state)(参数 state 是编辑器的状态对象),返回一个对象(符合 TypeScript interface DecorationSource),为编辑器视图设置所需应用的装饰器的合集
    • editable(可选)属性:一个函数 fn(state)(参数 state 是编辑器的状态对象),返回一个布尔值,用于设置编辑器的视图是否可以操作,即是否接受用户的交互修改编辑器的内容,如果为 false 则表示编辑器的内容不可以通过与视图交互直接修改
    • attributes(可选)属性:有多种形式的属性值,用于设置编辑器所对应的 editable DOM 元素的属性 attributes
      该属性值可以是一个对象,各属性名为 DOM 的 attribute 名称,属性值为 attribute 的值;也可以是一个一个函数 fn(state)(参数 state 是编辑器的状态对象),返回一个对象,也是以键值对的形式为 DOM 元素设置属性 attributes
      注意

      页面的 editable DOM 元素默认会具有 ProseMirror 类名,并且当上一个属性 editabletrue 时会为 editable DOM 元素添加 contentEditable attribute

      通过该属性所设置的 class 类名将以追加的方式添加到 DOM 元素上,对于其他的 attribute,则会采用第一个设定的值(例如不同插件设置了同一个 attribute,则优先注册的插件所设定的值会被采用)

    • scrollThreshold(可选)属性:设置触发滚动的阈值(以像素为单位,默认值为 0),当光标(文本选区的一种状态)与 viewport 边缘相距小于该阈值时,会触发编辑器页面滚动以确保光标可见
      该属性值可以是一个数值,设置光标与视窗底部相距的阈值;或一个对象 {top, right, bottom, left} 设置光标与视窗的四周相距的阈值。
      说明

      该阈值的默认值是 0,所以在使用键盘的上下键在编辑器中导航时,或编辑时按下 enter 键换行时,当光标已经抵达视窗的底部,编辑器页面就会滚动,确保光标始终在视窗内

    • scrollMargin(可选)属性:设置当编辑器页面滚动以让光标置于视窗内,究竟滚动到何种程度(以像素为单位,默认值为 5)才停止滚动
      该属性值可以是一个数值,设置光标与视窗底部的距离;也可以是一个对象 {top, right, bottom, left} 分别设置光标与视窗四周的距离

editorView 编辑器视图对象包含一些属性和方法:

  • state 属性:编辑器状态对象,表示当前编辑器视图与哪个编辑器状态相绑定
  • dom 属性:一个 HTMLElement,它是一个 editable DOM 元素, ⚠️ 它在页面显示出编辑器文档,一般不应该直接操作该 DOM 及其内容
  • editable 属性:一个布尔值,表示编辑器目前是否可编辑
  • dragging 属性:一个对象 {slice: Slice, move: boolean} 或为 null,表示拖拽相关信息
    在视图中进行拖拽交互时,该属性值是一个对象 {slice: Slice, move: boolean} 其中属性 slice 是一个 slice 切片对象,它包含拖拽的内容;另一个属性是 move 是一个布尔值,表示拖拽的方式(如果为 true 则以 moved 的方式将原有的内容移动到其他位置,即拖拽结束时原来位置的内容会删除掉;如果为 false 则以 copied 的方式将内容移动到其他位置,例如从外部拖拽导入内容)
    当视图没有进行拖拽交互时,该属性是 null
  • composing 属性:一个布尔值,以表示 compositionEvent 事件是否在激活(该事件表示用户正在间接输入文本,即在键盘的输入和最终输出到页面的值并不一致,例如使用 CJK 中日韩输入法时)
  • props 属性:一个对象(符合 TypeScript interface DirectEditorProps),表示编辑器视图的配置对象
  • update(props) 方法:用于更新整个编辑器视图的配置对象。其中入参 prop 是新的配置对象(符合 TypeScript interface DirectEditorProps),该操作会立即触发(编辑器视图在页面所对应)DOM 进行更新
  • setProps(props) 方法:用于更新部分配置参数。其中参数 props 是一个对象(含有 TypeScript interface DirectEditorProps 所描述的部分属性)
    提示

    该方法可用于更新配置对象的部分属性值,对于其他属性则使用原值

    该方法相当于前一个方法 view.update(Object.assign({}, view.props, props))

  • someProp(propName, f?) 方法:遍历编辑器视图配置对象中的某个属性 propName 的值
    视图配置对象具有多个值

    可以在使用方法 new EditorView(place, props) 初始化编辑器时设置配置对象。此外由于 Prosemirror 支持插件系统,而且可以通过插件对编辑器视图进行配置,即可以在多处进行编辑器视图配置,所以配置对象的某个属性可能「具有多个值」(虽然最终采取优先级最高的值)

    在遍历编辑器视图配置对象的某个属性的值时,该值可能是从视图的配置对象中获取的,也可能是从插件的配置参数中获取的(而且插件可能有两个来源,可以是应用到 state 编辑器状态对象上的,也可以是应用到 view 编辑器视图对象上的)

    通过各种方法设置属性值的优先级:

    1. 在初始化编辑器视图时 new EditorView(place, props) 配置对象 props 中的属性值优先级最高
    2. 应用到编辑器视图上的插件所设置的属性值优先级次之(如果有多个插件配置同一个属性,则根据插件的注册顺序来判断优先级)
    3. 应用到编辑器状态上的插件所设置的属性值优先级最低

    该方法的各参数的具体说明如下:
    • 第一个参数 propName 是需要遍历值的属性
    • 第二个(可选)参数 f(value) 是一个函数。如果当前所遍历的属性值不是 undefined 则调用该函数,其入参 value 就是当前所遍历到的属性值。如果该函数最后返回的是一个 truthy 的值,则遍历结束并返回该值;如果函数返回的是一个 falsely 的值,则继续遍历下一个属性值。如果没有设置该函数,则直接返回第一个属性值(优先级最高)
  • updateState(state) 方法:用于更新前文所述的属性 state(而并不触及其他属性),入参 state 是一个 state 编辑器对象
  • hasFocus() 方法:一个布尔值,查看编辑器的视图是否获得焦点
  • focus() 方法:调用该方法让编辑器的视图获得焦点
  • root 属性:编辑器所在页面的 document 对象,或一个 shadow DOM(如果编辑器是内嵌在页面中)
  • updateRoot() 方法:当将一个已存在的编辑器视图挂载/应用到页面的另一个 DOM 元素(或 shadow DOM)上时,调用该方法以更新前一个属性 root
  • posAtCoords(coords) 方法:根据给定的 viewport 视窗位置 coords(它是一个对象 {left: number, top: number} 表示视窗中的一个位置,单位是像素),获取编辑器文档中与之距离最近的位置
    该方法的返回值有多种类型,与给定的 viewport 位置相关:
    • 如果给定的 viewport 位置不在编辑器内部,则返回 null
    • 如果给定的 viewport 位置在编辑器内部,则返回一个对象 {pos: number, inside: number} 以描述文档中的位置,其中属性 pos 是一个数值(符合 index schema 规则)表示文档的位置;如果该位置在一个节点里,那么属性 inside(一个数值)表示距离该节点开头的偏移量(所以它是 0 或正数,符合 index schema 规则 ❓ ),如果该位置是在文档顶级节点上(而不在任何子节点内部),则属性 inside-1
    提示

    该方法一般用在事件处理函数中,可以通过事件对象 event 的属性 clientXclientY 得出事件发生在 viewport 的哪个位置,然后获取编辑器文档中与之距离最近的位置

  • coordsAtPos(pos, side?) 方法:根据给定的文档位置 pos,获取该位置在 viewport 所对应的矩形框(包含坐标信息)
    第一个参数 pos 是一个数值(符合 index schema 规则)表示文档的位置;当该位置正好处于前后元素不连续的(例如在该位置使用了 widget decoration ❓),则可以通过第二个(可选)参数 side(一个数值,默认值为 1)设置应该获取哪一侧的坐标信息,当该参数值小于 0 时则采用靠前的一侧;否则采用靠近的一侧
    该方法最后返回一个对象 {left: number, right: number, top: number, bottom: number} 表示一个 viewport 的矩形框,其中属性 leftright 总是相等的,因为该矩形框描述的是类似光标的位置
  • nodeDOM(pos) 方法:根据给定的文档位置 pos,获取所对应的 DOM 元素
    参数 pos 是一个数值(符合 index schema 规则,表示文档的位置)
    该方法的返回值有两种类型,与给定的文档位置相关:
    • 如果给定的文档位置正好是在一个节点前,则返回该位置后面的节点在页面所对应的 DOMNode
    • 如果给定的文档位置在节点里(而不是正好在节点前面),或在一个 opaque node view 不透明的节点视图里,则返回 null
      opaque node view

      opaque node view 不透明的节点这里的「不透明」是指该节点视图不包含属性 contentDOM,则 ProseMirror 无法控制其内容的渲染,所以该节点视图对于 ProseMirror 而言是「不透明」的

    注意

    虽然通过该方法可以获取 DOM 元素,但不应该直接更改 DOM 元素(包括其内容、属性、样式等可以触发 DOM 更新的变动),因为修改可能随着节点(更新所触发的)重绘而覆盖掉

    可以使用该方法读取一些关于该位置所对应的 DOM 元素的信息,例如通过 dom.getBoundingClientRect 获取 DOM 元素在页面的大小,和前述的方法 coordsAtPos(pos) 类似

  • domAtPos(pos, side?) 方法:根据给定的文档位置 pos,获取相应的 DOM 元素
    第一个参数 pos 是一个数值(符合 index schema 规则,表示文档的位置);第二个(可选)参数 side 是一个数值(默认值为 0),表示偏向于获取哪一侧的 DOM 元素,如果该参数小于 0 则偏向于获取前侧的 DOM 元素(左侧),如果该参数大于 0 则偏向于获取后侧的 DOM 元素(右侧),如果该参数为 0 则获取距离文档位置最近的 DOM 元素
    该方法最后返回值是一个对象 {node: DOMNode, offset: number} 属性 node 是所获取到的 DOM 元素,另一个属性 offset 是一个数值,表示该 DOM 元素(在网页的结构树中,相对于其父元素)的索引值
    注意

    虽然通过该方法可以获取 DOM 元素,但不应该直接更改 DOM 元素(包括其内容、属性、样式等可以触发 DOM 更新的变动),只应该使用该方法读取一些关于该位置所对应的 DOM 元素的信息

  • posAtDOM(node, offset, bias?) 方法:根据给定的(编辑器在页面上的)node DOM 元素,获取相应的文档位置,返回一个数值符合 index schema 规则,表示文档的位置)
    该方法的各参数的具体说明如下:
    • 第一个参数 node 是 DOM 元素
    • 第二个参数 offset 是数值,表示在 DOM 元素里的偏移量(如果该 DOM 元素的内容是文本,则表示字符偏移量;如果是包含一系列的子元素,则表示相对于其父节元素的索引值),用于设置具体对 DOM 元素里的哪一个位置进行查询/转换
    • 第三个(可选)参数 bias 是一个数值(默认值为 -1),用于指定查询/获取的位置偏向 DOM 的哪一侧。主要针对叶子元素(即不包含内容的 DOM 元素,例如 <img> 图像元素),如果参数值大于 0 则获取/查询叶子元素的的右侧在文档中的相应位置;否则获取/查询叶子元素的左侧
    提示

    更推荐基于编辑器文档结构(树形结构或扁平结构)来查询/获取位置,更准确且性能会更好

    但是在一些特殊的场景,例如在与编辑器交互时只知道事件对象 event,则可以通过属性 event.target 获取到页面上的 DOM 元素,再通过该方法「逆向」查询/获取到编辑器文档的相应位置信息,但是该方式需要 ProseMirror 对编辑器在页面上的所有 DOM 依次进行检索,比较消耗性能

  • endOfTextblock(dir, state?) 方法:用于判断光标(选区)是否在文本块 textblock 的边界处
    提示

    文本块 textblock 一般是指以文本作为内容的节点,例如 paragraph 段落节点


    该方法的各参数的具体说明如下:
    • 第一个参数 dir 可以是 updownleftrightforwardbackward 这六个值之一,表示应该把光标往哪个方向上移动一个单位进行测试
    • 第二个(可选)参数 state 是一个编辑器状态对象(默认传入的是当前编辑器的状态对象,但也可以传入其他的编辑器状态对象,以判断该状态下的选区情况)

    该方法最后返回一个布尔值,以表示如果光标移动一个单位后,是否会移出 textblock 所在的父节点,如果为 true 则表示光标在该方向上处于 textblock 的边界;否则返回 false
  • parseHTML(html, event?) 方法:对给定的 html(一个字符串,表示 HTML 格式的文本)执行编辑器与粘贴相关的逻辑(默认的粘贴流程:从剪切板读取内容,解析为 slice 切片对象,替换当前编辑器选区的内容)。如果设置了第二个(可选)参数 event(一个 ClipboardEvent 剪切板事件对象),则会将它传递给 handlePaste 处理函数(该函数用于覆盖前述的默认粘贴流程,以自定义粘贴行为)。最后返回一个布尔值表示是否粘贴成功
  • pasteText(text, event?) 方法:与前一个方法 parseHTML 类似,但是该方法是针对纯文本的,对给定的 text(一个字符串,表示纯文本)执行编辑器与粘贴相关的逻辑。最后返回一个布尔值表示是否粘贴成功
  • destroy() 方法:将编辑器从页面移除,并销毁/清除编辑器中所有的 node views 节点视图对象
  • isDestroyed 属性:一个布尔值,表示编辑器是否被销毁
  • dispatchEvent(event) 方法:手动分发事件,用于调试
  • dispatch(tr) 方法:分发一个事务(参数 tr 是需要分发的 transaction 事务对象)
    提示

    如果在编辑器视图的配置对象中设置了属性 dispatchTransaction,则会执行该属性所设置的函数 fn(tr)(可以手动地、更精细地控制事务如何应用到编辑器的状态对象上,⚠️ 请保证该函数最后执行了 this.updateState(state) 方法来更新编辑器的视图);如果视图对象没有配置属性 dispatchTransaction,则 ProseMirror 会自动将事务应用到编辑器的状态对象上,并自动调用 updateState() 方法来更新编辑器的状态

Decoration 类

Decoration 类可以影响节点在页面的渲染结果,但是不会修改节点在编辑器文档中的数据表示形式

装饰器用在哪里

使用方法 new EditorView(place, props) 创建编辑器视图时,在配置对象 props(符合的 TypeScript interface DirectEditorProps,继承自 interface EditorProps)的属性 decorations 中使用到装饰器(在属性 nodeView 中也有涉及

Decoration 与 NodeView 区别

NodeView 节点视图和 Decoration 装饰器都可以对节点在页面的渲染形式进行自定义

NodeView 对于视图层的配置灵活度更高,还可以对视图层的交互方式进行自定义;而 Decoration 则适用于进行轻量级的 UI 定制,所以它被称作装饰器,它只是为页面添加一些「点缀物」,例如语法高亮,它只影响渲染输出效果而不会修改编辑器的文档内容/数据

该类提供了三种静态方法进行实例化,分别创建三种装饰器以适用于不同的场景:

  • Decoration.widget(pos, toDOM, spec?) 静态方法:在给定位置 pos 创建一个挂件装饰器,它在页面渲染为一个 的 DOM 元素
    实例化的参数说明
    • 第一个参数 pos 是一个数值(符合 index schema 规则,表示文档的位置),用以设置该挂件装饰器应该放置在文档哪个位置
    • 第二个参数可以是 toDom 可以是一个函数 fn(view, getPos)(它返回一个 DOMNode)或直接传递一个 DOM 元素,用于设置该挂件装饰器在页面如何渲染。
      如果设置为函数 fn(view, getPos) 则它接受两个参数,第一个参数 view 是编辑器的视图对象,第二个参数 getPos 是一个方法,通过调用它可以获取该挂件装饰器在文档中的位置(返回一个数值,符合 index schema 规则)
      延迟渲染

      更推荐使用函数的形式来指定挂件装饰器所对应的 DOM 元素,因为它会等待该 decoration 真正绘制到视图层时才被调用,以实现按需渲染

    • 第三个(可选)参数 spec 是一个对象,包括挂件装饰器的其他配置项
      • side(可选)属性:一个数值,用于设置挂件装饰器与给定位置的哪一侧相关,以便处理涉及光标相关的操作
        如果是负值则将挂件装饰器绘制在指定位置(前文所述的参数 pos)光标的前侧,如果用户继续在该位置输入内容,会置于该装饰器的后面;如果是 0(默认值)或正值则将挂件装饰器绘制在指定位置光标的后侧,如果用户继续在该位置输入内容,则会置于装饰器的前面
        如果后一个属性 marks 设置为 null 时,则该属性也决定挂件装饰器会继承哪一侧节点所具有的 mark 样式标记。如果该属性值是负值,则会继承/应用位于挂件前面的节点所具有的样式标记;如果该属性值是正数,则会继承/应用位于挂件后面的节点所具有的样式标记
        多个挂件装饰器插入同一个位置

        如果在给定位置插入多个挂件装饰器,则根据该属性 side 的数值的大小来排序依次插入页面,即数值较小的挂件装饰器先插入页面。而对于该属性值相同的两个挂件装饰器,其插入页面的先后顺序不确定

      • marks(可选)属性:一个数组,其元素是 mark 样式标记对象,用于设置应用在挂件装饰器周围的样式标记,即在挂件装饰器所在位置继续输入内容时,需要应用什么样式标记
      • stopEvent(可选)属性:一个函数 fn(event)(参数 event 是该挂件装饰器接收到的事件对象),用于阻止某些/全部来自该挂件装饰器的事件,让它们不被编辑器所处理。最后返回值是一个布尔值,如果为 true 则阻止事件冒泡
      • ignoreSelection(可选)属性:一个布尔值(默认为 false),用于设置是否忽略挂件装饰器里的选区变化。如果为 true 则 Prosemirror 会忽略挂件装饰器里的选区变化;否则 ProseMirror 会读取挂件装饰器的 DOM 选区,并更新编辑器状态的选区以实现同步
      • key(可选)属性:一个字符串,为挂件装饰器设置唯一标识符
        提示

        在判断装饰器是否需要重新渲染时,ProseMirror 默认以装饰器所对应的 DOM 元素作为标识符,将同一种类型的装饰器进行区分比较,以决定需要对哪一个进行重绘

        也可以通过属性 key 为装饰器设置一个字符串作为标识符,ProseMirror 就会以这个 key 值区分不同的装饰器(而不是它们的 DOM 元素),这在需要动态生成装饰器(且不想重用旧的 DOM 节点 ❓ )的场景中非常有用

        但请确保具有相同 key 的装饰器是可以互换的,例如不同装饰器对事件处理行为有所不同,那它们就应该使用不同的 key

      • destroy(可选)属性:一个函数 fn(node)(参数 nodeDOMNode,挂件装饰器所对应的 DOM 节点),当挂件装饰器(或整个编辑器)销毁时会执行该函数
      • [string] 自定义属性:还可以为挂件装饰器设置其他属性(属性名是字符串,属性值可以是任何数据类型),以添加额外的信息
  • Decoration.inline(from, to, attrs, spec?) 静态方法:在给定范围创建一个行内装饰器,其作用是为给定范围中的行内节点添加特定的属性
    实例化的参数说明
    • 第一、二个参数 fromto 是一个数值(符合 index schema 规则,表示文档的位置),分别设置行内装饰器的起点和终点
    • 第三个参数 attrs 是一个对象(符合 TypeScript type DecorationAttrs),用于为给定范围内的每个 inline 行内类型的节点(所对应的 DOM 元素)添加属性
      DecorationAttrs

      TypeScript type DecorationAttrs 以键值对的方式描述要添加到装饰器(所对应的 DOM 元素)的 attributes 合集

      如果键名与某个 DOM attributes 一样,则会将它设置为 DOMNode 相应的 property 值,但有一些特例它们的处理方式有所不同:

      • nodeName(可选)属性:一个字符串(非 null 值),以指定 DOM 节点的类型,创建一个容器,用以包裹装饰器所指定范围内的节点(一般是针对节点装饰器而言 ❓ ),然后其他属性 attrs 的设置会应用于该容器上
      • class(可选)属性:一个字符串,用以设置类名(可以设置多个 class 名,并用空格进行分隔),会以追加的方式添加到 DOM 元素上
      • style(可选)属性:一个字符串,用以设置行内样式,会以追加的方式添加到 DOM 元素上
    • 第四个(可选)参数 spec 是一个对象,包括行内装饰器的其他配置项
      • inclusiveStart(可选)属性:一个布尔值(默认为 false),用于设置在装饰器的左侧/开始位置的映射规则,当在该位置插入新的内容时,是否将这些内容包括在行内装饰器的范围内。如果为 true 则行内装饰器会包含新增的内容
      • inclusiveEnd(可选)属性:与前一个属性 inclusiveStart 类似,也是一个布尔值,用于设置在装饰器的右侧/结尾位置的映射规则
      • [string] 自定义属性:还可以为行内装饰器设置其他属性(属性名是字符串,属性值可以是任何数据类型),以添加额外的信息
  • Decoration.node(from, to, attrs, spec?) 静态方法:在给定范围创建一个节点装饰器,其作用是为给定范围内的一个节点(即该范围与该目标节点在文档中的范围一致)添加特定的属性
    实例化的参数说明
    • 第一、二个参数 fromto 是一个数值(符合 index schema 规则,表示文档的位置),分别设置节点装饰器的起点和终点(它们需要正好精准地定位到一个目标节点的开始和结束位置)
    • 第三个参数 attrs 是一个对象(符合 TypeScript type DecorationAttrs,具体介绍可查看前文 inline decoration 行内装饰器相关部分),为目标节点(所对应的 DOM 元素)添加的属性
    • 第四个(可选)参数 spec 是一个对象,用于为节点装饰器添加一些附加的信息,该属性也用于节点装饰器之间的比较,以判断它们是否相同

decoration 装饰器对象包含一些属性和方法:

  • from 属性:一个数值,表示该装饰器在文档中的开始位置(符合 index schema 规则,表示文档的位置)
  • to 属性:一个数值,表示该装饰器在文档中的结束位置(符合 index schema 规则,表示文档的位置)
  • spec 属性:一个对象,它表示在创建装饰器时所传递的配置参数 spec。因此可以在创建装饰器时,设置一些额外的信息,然后在使用该装饰器时,可以通过该属性再获取这些附加的信息

DecorationSet 类

该模块还提供一个 DecorationSet 类,表示一个装饰器的合集,使用这种数据结构来组织装饰器,让 ProseMirror 的绘制算法可以更高效地对比和渲染它们(它实现了 TypeScript interface DecorationSource,即具有该类型约束所指定的属性和方法,还添加了额外的一些属性和方法)

注意

它是一个 persistent data structure 不可变的数据结构,即它不能直接改变,只能通过生成一个新的值来进行更新

DecorationSource

TypeScript interface DecorationSource 描述了两个方法:

  • map(mapping, node) 方法:它根据文档发生的变更,对装饰器进行重新映射(更新它们的位置)。第一个参数 mapping 是一个 Mapping 对象;第二个参数 node 是一个 node 节点对象(根节点 ❓ 表示编辑器的文档)。最后返回一个经过映射更新的对象(它也符合 TypeScript interface DecorationSource
  • forChild(offset, child) 方法:获取应用于给定节点 child(内容里)的装饰器,第一个参数 offset 是开始检索的位置(符合 index schema 规则,表示文档的位置)

该接口由 class DecorationSet 实现,并应用在 nodeView

通过该类所提供的一些静态方法或属性进行实例化,得到一个 decorationSet 对象,以下称作「装饰器合集对象」

  • DecorationSet.create(doc, decorations) 静态方法:通过给定的 doc 一个 node 节点(根节点,表示编辑器文档)和一系列的装饰器 decorations(一个数组,元素是 decoration 装饰器对象)来创建一个装饰器合集
  • DecorationSet.empty 属性:一个空的装饰器合集。

decorationSet 装饰器合集对象包含一些属性和方法:

  • find(start, end, predicate) 方法:从装饰器合集中,筛选出在给定范围内,且满足特定条件的装饰器。
    各参数的具体说明如下:
    • 第一、二个(可选)参数 startend 都是一个数值(符合 index schema 规则,表示文档的位置),用以设置查找的范围(在该范围边界上的装饰器也会纳入考虑)。如果没有设置这两个参数,则默认查找整个文档里的装饰器
    • 第三个(可选)参数 predicate 是一个函数 fn(spec) 用以设置过滤条件。装饰器都会分别调用该函数,并将它的配置对象 spec 作为入参,这样就可以根据装饰器的信息对它们进行过滤筛选。该函数最后返回一个布尔值,表示该装饰器是否符合条件。如果没有设置该参数,则默认所有指定范围内的装饰器都符合条件。

    该方法最后返回一个数组,其元素是符合条件的装饰器对象
  • map(mapping, doc, options) 方法:根据文档发生的变更,对装饰器进行重新映射(更新它们的位置)
    各参数的具体说明如下:
    • 第一个参数 mapping 是一个 Mapping 对象
    • 第二个参数 doc 是一个 node 节点对象(根节点 ❓ 表示编辑器的文档)
    • 第三个(可选)参数 options 是一个对象,它包含一个(可选)属性 onRemove 用以设置当合集中任意一个装饰器被移除时,执行一些额外的操作,属性值是一个函数 fn(decorationSpec) 在映射过程中被移除的装饰器会分别调用该函数,入参是该装饰器的配置对象 spec

    该方法返回映射后的装饰器合集
  • add(doc, decorations) 方法:往合集中添加其他给定的一系列装饰器 decorations(一个数值,元素是装饰器对象),构成一个新的合集。第一个参数 doc 传入的是一个 node 节点对象(根节点 ❓ 表示编辑器的文档),由于需要获取访问当前文档以正确地创建树形结构
  • remove(decorations) 方法:从合集中移除指定的一系列装饰器 decorations(一个数值,元素是装饰器对象),构成一个新的合集。

Copyright © 2025 Ben

Theme BlogiNote

Icons from Icônes