ProseMirror Transform 模块

prosemirror

ProseMirror Transform 模块

prosemirror-transform 模块主要与文档的修改相关,让修改文档的步骤可以被记录下来,使得操作可以 replayed 重放/重新执行,或对使得变更可以 reordered 重新排序。

Step 类

Step 步骤是对文档的原子操作(即每次操作都只完成特定的一项任务,不能再拆分),例如替代内容、添加样式标记或设置属性等,其结果是生成一个新的文档。

注意

Step 步骤一般只会应用到创建它的那个文档上,因为存储在该操作步骤中的位置信息只有对那个文档来说才有意义

Step 也可以 invert 反转/撤销(这个操作也需要通过创建一个 step 步骤来实现),以恢复修改前的文档

每个 step 步骤都提供了一个 stepMap 对象,它具有相关的方法(例如 stepMap.map(number))实现将旧文档中的位置映射到(经过步骤转换后的)新文档中,可以用于保持选区在文档修改前后的正确位置

step 与 transform 关系

将多个 steps 步骤链接/集合在一起就形成一个名为 transform 对象,即 transform 可以将多个操作应用到文档上

Step 是一个 TypeScript abstract class 抽象类

抽象类

TypeScript abstract class 不直接实例化,由于它一般只定义了抽象的方法(虽然也可以包含非抽象的方法),即针对这些方法只是对入参和输出值的类型进行了约束,而没有给出这些方法的具体实现

抽象类可以被其他类继承,子类需要实现抽象类中的抽象方法,所以一般是创建子类再使用(才能进行实例化)

所以这里的 abstract class Step 抽象类不直接实例化,要先被继承,给出父类的抽象方法 applyinvertmapgetMapfromJSON 的具体实现,构建出各种子类再使用

它给出了一些抽象方法和具体的方法:

  • abstract apply(doc) 抽象方法:将该步骤应用到给定的文档 doc 上,返回一个 stepResult 对象(它表示步骤执行的结果,具有一些属性以描述该操作是否成功执行)
    StepResult

    class StepResult 类用于表示一个步骤的执行结果,其实例化对象称为「步骤执行结果对象」

    该类提供了一些静态方法进行实例化:

    • static ok(doc) 静态方法:返回一个 stepResult 步骤执行结果对象,它表示步骤成功执行,其中传入的参数 doc 是一个 node 节点对象(根节点)表示转换成功后的文档
    • static fail(message) 静态方法:返回一个 stepResult 步骤执行结果对象,它表示步骤没有执行成功,其中传入的参数 message 是一个字符串表示错误信息
    • static fromReplace(doc, from, to, slice) 静态方法:根据给定的参数调用 node.replace(from, to, slice) 方法对给定的文档 doc 进行处理,并返回一个 stepResult 步骤执行结果对象

    该类的实例化对象一般是由步骤对象的方法 step.apply() 产生的,即作为该方法的返回值

    stepResult 步骤执行结果对象包含一些属性和方法:

    • doc 属性:如果步骤执行成功,则该属性值为一个 node 节点对象(根节点,表示转换成功后的文档);否则该属性值为 null
    • failed 属性:如果步骤没有执行成功,则该属性值为错误信息(一个字符串);否则该属性值为 null
  • getMap() 方法:返回一个 stepMap 对象,称为位置映射器,可以实现旧文档中的位置与(转换后的)新文档中的位置的相互关联/映射
    StepMap

    class StepMap 类用于描述/记录 step 步骤的删除和插入操作,以实现将旧文档中的位置与(转换后的)新文档中的位置相互关联起来,该类的实例化对象称为「位置映射器」

    通过 new SteMap(ranges, inverted?) 进行实例化,第一个参数 ranges 是一个数组,以表示对应 step 步骤所做的修改;第二个(可选)参数 inverted 是一个布尔值,表示该位置映射器所对应的 step 步骤是否进行反转/撤销操作以恢复原文档(即位置是从新的文档映射到旧的文档上)

    change ranges

    在位置映射器中使用一个数组来记录对应步骤所做的修改,数组的每个元素都是数字,每三个元素一组表示对文档的一处修改记录 [start, oldSize, newSize] 其中 start 表示修改的开始位置oldSize 表示旧内容的大小newSize 表示新内容的大小

    另外该类还提供了一些静态属性和方法进行实例化

    • static StepMap.offset(n) 静态方法:创建一个位置映射器,它的作用是将旧文档的位置都偏移 n(作为新文档的位置)。
      当参数 n 是正数时,则在该静态方法内部会调用方法 new StepMap([0, 0, n]) 创建一个位置映射器实例,相当于在文档的开头 stat=0 处插入了一段长度为 n 的内容,所以旧文档的所有位置映射到新文档时,其位置都会增加(向右偏移)n
      当参数 n 是负数时,则在该静态方法内容会调用调用方法 new StepMap([0, -n, 0]) 创建一个位置映射器实例, 相当于在文档的开头 stat=0 处删除了一段长度为 n 的内容,所以所以旧文档的所有位置映射到新文档时,其位置都会减小(向左偏移)n
      n=0 时,则在该静态方法内部会返回 StepMap.empty 一个 change ranges 为空的位置映射器
      这对于实现将一个子文档应用到较大的父文档中的 step 步骤(或相反的操作)很有用
    • static StepMap.empty 静态属性:返回一个 change ranges 为空(空数组)的位置映射器(对应的 step 步骤则不对文档进行处理 ❓ )

    也可以由步骤对象的方法 step.getMap() 产生该类的实例化对象

    stepMap 位置映射器包含一些属性和方法:

    Mappable

    class StepMap 需要 implements 实现一种 TypeScript interface Mappable,该类的实例化对象要包含以下属性或方法

    • map(pos, assoc?) 方法:该方法将旧文档中的位置 pos(用数字表示)映射到(经过 step 步骤转换后的)新文档中,该方法返回一个数字表示新文档中的相应位置。
      如果正好在所需映射的位置 pos 处插入了内容,则可以通过设置第二个(可选)参数 assoc-11(默认值)来决定这个旧的位置 pos 应该映射到新插入内容的哪一侧。如果 assoc=-1 则表示将旧位置映射到插入内容的左侧/前面;如果 assoc=1 则表示将旧位置映射到插入内容的右侧/后面
    • mapResult(pos, assoc?) 方法:该方法也和上一个方法 map() 类似,也是将旧文档中的位置 pos(用数字表示)映射到(经过 step 步骤转换后的)新文档中,但返回的值不再是一个数字,而是一个 mapResult 对象,它具有一些属性可以更详细地描述位置的相关信息。同样支持设置第二个(可选)参数 assoc 来决定映射到哪一侧
      mapResult

      class mapResult 类用于表示映射的结果,除了位置信息(用数字表示),还有其他额外的信息,该类的实例化对象称为「映射结果」

      • pos 属性:一个数字,表示映射到新文档中的位置
      • deleted 属性:一个布尔值,表示所映射的位置是否在相应的 step 步骤中删除了
        映射位置正好落入在删除内容的一侧

        如果所需映射的位置 pos 正好处于删除内容的边缘一侧时,则根据 assoc 参数来判断该位置是否视为被删掉。

        assoc=1(默认值)时,表示保留右侧,如果 pos 在删除内容的左侧就会被视为删掉,即属性 deletedtrue;如果 pos 在删除内容的右侧就不会被视为删掉,即属性 deletedfalse。当 assoc=-1 时情况则相反。

        可以理解为当删除的内容是在 assoc「指向」(-1 表示指向左侧,1 表示指向右侧)的方向上时,则 pos 所在的那一侧才会被视为删掉

      • deletedBefore 属性:一个布尔值,表示所映射的位置的前面的 token 是否在相应的 step 步骤中删除了(即该位置的左侧是否有内容被删掉)
      • deletedAfter 属性:一个布尔值,表示所映射的位置的后面的 token 是否在相应的 step 步骤中删除了(即该位置的右侧是否有内容被删掉)
      • deletedAcross 属性:一个布尔值,表示所映射的位置的是否在相应的 step 步骤中删除了,而且它正好落入删除内容范围的中间(即只有当该位置的左侧和右侧都有内容被删掉,该属性才为 true

    (除了实现 TypeScript interface Mappable 的两个方法)还包含一些属性和方法

    • forEach(fn) 方法:遍历每一个修改记录(在 change ranges 数组中,每三个元素表示一个修改记录),并依次调用给定的函数
      参数 fn 是所需调用的函数,它接受 4 个参数:
      • 第一个参数 oldStart 是一个数字,表示当前所遍历的修改在旧文档范围中的开始位置
      • 第二个参数 oldEnd 是一个数字,表示当前所遍历的修改在旧文档范围中的结束位置
      • 第三个参数 newStart 是一个数字,表示当前所遍历的修改在新文档范围中的开始位置
      • 第四个参数 newEnd 是一个数字,表示当前所遍历的修改在新文档范围中的结束位置
    • invert() 方法:返回一个 stepMap 对象,它与当前的位置映射器的作用相反(如果原来的位置映射器的作用是将旧文档的位置映射到新文档里,那么通过这个方法生成的位置映射器,旧可以将位置是从新的文档映射到旧的文档上)
    Mapping

    class Mapping 类表示一系列的 stepMap pipeline(在该管道内可以包含零个或多个位置映射器),该类的实例化对象称为「位置映射管道」

    它也需要 implements 实现一种 TypeScript interface Mappable

    它具有特殊的规定,用于处理文档经过一系列 step 步骤操作后的位置映射,其中一些步骤可以是前面步骤的 invert 版本(这在协作编辑或历史管理中的 rebasing 步骤中出现)

    通过 new Mapping(map?, mirror?, from?, to?) 进行实例化

    各参数的具体说明如下:

    • 第一个(可选)参数 map 是一系列 stepMap 对象(数组形式,默认值为一个空数组)
    • 第二个(可选)参数 mirror 是一个数值数组(即数组的元素是数字),表示 map 中哪些是镜像操作 ❓
    • 第三和第四个(可选)参数 from(默认值为 0)和 to(默认值为 this.maps.length)都是一个数字,是参数 map(它是一个数组)的索引值,表示该位置映射管道是经过 map 数组中的哪些步骤(在调用方法 mapping.map()mapping.mapResult() 时会使用它们)
    提示

    进行映射获取新位置时,需要依次遍历 maps 的元素 stepMap 以获取新的位置信息,参数 fromto 规定了要遍历哪些 stepMap

    镜像操作

    镜像映射 mirror mapping 是 ProseMirror 中 Mapping 类用于处理协作编辑或历史管理时的一个特性

    在协作编辑或保存历史记录的场景中,可能会出现一些步骤(steps)是之前步骤的逆向操作,也就是"撤销"了之前的操作。这种情况下,简单地将新旧步骤组合在一起会导致位置映射出现问题

    为了解决这个问题, ProseMirror 引入了镜像映射的概念。每个步骤映射(StepMap)都可以关联一个"镜像"步骤映射,代表对该步骤进行逆向操作所对应的位置映射

    当 ProseMirror 检测到一个新的步骤是之前某个步骤的逆向操作时,它会使用该步骤映射的镜像映射,而不是直接应用该步骤映射。这样就可以保证即使在有"撤销"操作的情况下,位置映射依然是正确的

    例如,如果一个步骤是插入文本"abc",它的步骤映射是将原位置+3。如果后面有一个逆向步骤删除"abc",那么它的镜像映射就应该是将原位置-3。通过应用镜像映射,可以确保总体的位置映射是正确的

    总的来说,镜像映射是一种特殊的映射,用于在协作编辑和历史管理等场景下,正确地处理有"撤销"操作时的位置映射问题

    mapping 位置映射管道包含一些属性和方法:

    • maps 属性:一系列 stepMap 对象,它们构成了该位置映射管道
    • from 属性:一个数字,是参数 map(它是一个数组)的索引值,表示该位置映射管道是从 map 数组中的哪个步骤开始进行位置转换
    • to 属性:一个数字,是参数 map(它是一个数组)的索引值,表示该位置映射管道是直到 map 数组中的哪个步骤停止位置转换
    • slice(from?, to?) 方法:创建一个新的 mapping 对象,它只包含当前位置映射管道的一部分(只包含 fromto 这部分的 stepMap
    • appendMap(map, mirrors?) 方法:将给定的 stepMap 对象 map 添加到当前的位置映射管道的末尾。如果设置了 mirrors 参数(一个数字),则表示新增的 stepMap 对象是原有 map 数组中索引位置为 mirrors 的 stepMap 的镜像映射
    • appendMapping(mapping) 方法:将给定的位置映射管道 mapping 里的所有 stepMap 对象添加到当前的位置映射管道的末尾(而且保留镜像映射的信息)
    • getMirror(n) 方法:Finds the offset of the step map that mirrors the map at the given offset, in this mapping (as per the second argument to appendMap).
    • appendMappingInverted(mapping) 方法:将给定的位置映射管道 mapping 里的所有 stepMap 对象以相反顺序添加到当前的位置映射管道的末尾
    • invert() 方法:新建一个与当前位置映射管道作用相反的版本
    • map(pos, assoc?) 方法:将旧文档中的位置 pos(用数字表示)使用该位置映射管道转换,返回一个数字表示新文档中的相应位置
      如果文档进行一系列步骤的处理时,正好在所需映射的位置 pos 处插入了内容,则可以通过设置第二个(可选)参数 assoc 来决定这个旧的位置 pos 应该映射到新插入内容的哪一侧
      如果 assoc=-1 则表示将旧位置映射到插入内容的左侧/前面;如果 assoc=1(默认值)则表示将旧位置映射到插入内容的右侧/后面
    • mapResult(pos, assoc?) 方法:该方法也和上一个方法 map() 类似,也是将旧文档中的位置 pos(用数字表示)映射到(经过一系列 step 步骤转换后的)新文档中,但返回的值不再是一个数字,而是一个 mapResult 对象,它具有一些属性可以更详细地描述位置的相关信息。同样支持设置第二个(可选)参数 assoc 来决定映射到哪一侧
  • abstract invert(doc) 抽象方法:创建一个步骤。它的作用与当前的步骤相反,所接收的参数 doc 是一个 node 节点对象(根节点),它是在应用当前步骤(转换)之前的旧文档
  • abstract map(mappable) 抽象方法:创建一个步骤。根据传入的对象(它符合一种 TypeScript interface Mappable)对当前的步骤进行调整,而得到一个新的步骤对象。如果在 mapping 所记录的操作中,当前步骤被完全删除,则返回 null
    应用场景

    如果基于一个 doc 创建了当前步骤对象之后,在它被应用之前,又有另外一个 Step 被先应用了,如果直接应用当前步骤,则它所包含的一些位置信息可能是错误的(并不是对应到最新的文档中),所以需要更新当前步骤以重新获取到正确的 fromto 信息

    参考自《Prosemirror 高级篇(源码):文档修改的原子操作 Step

  • merge(other) 方法:创建一个步骤。该方法会将尝试当前步骤与传入的其他步骤 other 合并为一个步骤。如果两个步骤无法合并,则会返回 null
    该方法可以将个多个步骤合并为一个操作,但是合并是有限制的,步骤类型需要相同,修改的文档位置也需要是可以相连的
  • abstract toJSON() 静态方法:将步骤对象序列化为 JSON 对象。在子类中给出该方法的具体实现时,需要确保生成的 JSON 对象中具有属性 stepType 它的值是该步骤实例所对应的 Step 子类的 jsonID 以表示该 JSON 对象代表哪一种 Step 子类

该类还提供了一些静态方法用于将步骤序列化为 JSON 对象:

  • static Step.fromJSON(schema, jsonObj) 静态方法:基于 schema 数据结构约束对象将给定的 JSON 对象反序列化为一个步骤对象。
  • static Step.jsonID(id, stepSubClass) 静态方法:为 Step 子类 stepSubClass 注册一个唯一的标识符
    第一个参数 id 是字符串,以表示该 Step 子类;第二个参数 stepSubClass 是所对应的 Step 子类
    这个标识符会用于将步骤对象序列化为 JSON 对象时,作为 JSON 对象的属性 stepType 的值。这样在反序列化时,ProseMirror 会基于标识符将 JSON 对象反序列化为对应的步骤子类的实例

ReplaceStep 子类

ReplaceStep 类继承自 Step 类,它表示使用一个 slice 切片对文档的特定范围的内容进行替换操作。

通过方法 new ReplaceStep(from, to, slice, structure?) 进行实例化,各参数的具体说明如下:

  • 参数 fromto 是原文档的替换范围的起始位置和结束位置(一个数字,基于 index schema 计算而得,表示文档的位置)
  • 参数 slice切片对象,用于替换原文档指定范围的内容
    注意

    切片 slice 需要「契合」替换的位置,即切片两侧的开放深度和替换位置的深度需要相同,而且可以与包围它的节点 join 接起来

  • (可选)参数 structure 一个布尔值(默认值为 false),以表示该替换步骤是否与协作编辑相关。
    如果为 true 表示该操作只进行结构替换,而不进行内容覆盖;如果为 false 则表示该操作只是简单内容替换。
    提示

    这主要是处理 rebase step 可能会覆盖原文档内容的场景。

    在 ProseMirror 的多人协作中,当收到远程其他用户的更改时,collab 模块会将这些更改表示为一系列的 steps 步骤,然后基于当前用户操作的文档来应用这些远程步骤,这个过程就是 rebase。

    在 rebase 过程中,如果远程替换步骤中的 fromto 范围与当前文档中的内容重叠,可能会导致新内容被覆盖。为了避免这种情况,使用 structure 参数来标记步骤,以指示是否需要进行结构检查。

    官方文档的英文描述是 the step will fail if the content between from and to is not just a sequence of closing and then opening tokens 即 选中的标签不单纯是一堆结束标签或开始标签,还选中了实质的内容,那么这种替换就会失败。例如选中的范围是 </p></div><div> 就满足结构化替换的要求;而选中 </p></div><div><p>abc 包含实质的内容 abc 就不符合结构化替换的要求 ❓

    如果 structure 参数为 true,ProseMirror 会检查要替换的内容是否符合结构要求,即(选中)所需替换的范围只是涉及一些结构化的标签,以确保不会意外覆盖原文档的内容。

    因此 structure 参数的目的是确保在 rebase 过程中,不会不经意地覆盖其他用户或当前用户的修改内容。

    参考自《Prosemirror 高级篇(源码):文档修改的原子操作 Step

class ReplaceStep 实例化 replaceStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 ReplaceStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类

ReplaceAroundStep 子类

ReplaceAroundStep 类继承自 Step 类,它和 ReplaceStep 类似,也是表示使用一个 slice 切片对文档的特定范围的内容进行替换操作,但它可以保留原文档(被替代范围中的)部分/全部内容(将这些内容插入到 slice 切片的指定位置中),一般用作 wrap around 包裹选中的内容

通过方法 new ReplaceStep(from, to, gapFrom, gapTo, slice, insert, structure?) 进行实例化,各参数的具体说明如下:

  • 参数 fromto 都是一个数字(基于 index schema 计算而得,表示文档的位置),是需要替换的文档范围
  • 参数 gapFromgapTo 是一个数字(基于 index schema 计算而得,表示文档的位置),分别需要保留的原始内容范围的起始位置和结束位置,最终的效果是在插入新内容时这部分的内容不会被替换掉
    注意

    参数 gapFromgapTo 所表示的位置需要在一个节点的两侧,而不能像 slice 切片那样可能包含不完整的节点

  • 参数 insert 是一个数字,表示所保留的内容应该插入到 slice 切片的什么位置(也是遵循 index schema
  • 其他参数的含义和 ReplaceStep 类的构造函数的参数一样

class ReplaceAroundStep 实例化 replaceAroundStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 ReplaceAroundStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类

AddMarkStep 子类

AddMarkStep 类继承自 Step 类,表示为指定范围的 inline content(inline 内联类型的节点的片段/内容的特定范围)添加给定的 mark 样式标记的操作

通过方法 new AddMarkStep(from, to, mark) 进行实例化,各参数的具体说明如下:

  • 参数 fromto 分别表示一个文档范围的起始位置和结束位置(一个数字,基于 index schema 计算而得,表示文档的位置)
  • 参数 mark 是一个 mark 样式标记对象

class AddMarkStep 实例化 addMarkStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 AddMarkStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类

RemoveMarkStep 子类

RemoveMarkStep 类继承自 Step 类,表示移除指定范围的 inline content(inline 内联类型的节点的片段/内容的特定范围)上原有的 mark 样式标记的操作

通过方法 new RemoveMarkStep(from, to, mark) 进行实例化,各参数的具体说明如下:

  • 参数 fromto 分别表示一个文档范围的起始位置和结束位置(一个数字,基于 index schema 计算而得,表示文档的位置)
  • 参数 mark 是一个 mark 样式标记对象

class RemoveMarkStep 实例化 removeMarkStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 RemoveMarkStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类

AddNodeMarkStep 子类

AddNodeMarkStep 类继承自 Step 类,表示为目标节点添加给定的 mark 样式标记的操作

通过方法 new AddNodeMarkStep(pos, mark) 进行实例化,各参数的具体说明如下:

class AddNodeMarkStep 实例化 addNodeMarkStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 AddNodeMarkStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类

RemoveNodeMarkStep 子类

RemoveNodeMarkStep 类继承自 Step 类,表示移除目标节点上原有的 mark 样式标记的操作

通过方法 new RemoveNodeMarkStep(pos, mark) 进行实例化,各参数的具体说明如下:

class RemoveNodeMarkStep 实例化 removeNodeMarkStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 RemoveNodeMarkStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类

AttrStep 子类

AttrStep 类继承自 Step 类,表示更新目标节点上的特定属性 attribute 的值的操作

通过方法 new AttrStep(pos, attr, value) 进行实例化,各参数的具体说明如下:

  • 参数 pos 是目标节点的位置(一个数字,基于 index schema 计算而得,用于寻找该目标节点)
  • 参数 attr 是所需更新的属性 attribute 名称(字符串)
  • 参数 value 是新的属性值

class AttrStep 实例化 attrStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 AttrStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类

DocAttrStep 子类

DocAttrStep 类继承自 Step 类,表示更新根节点 doc 上的特定属性 attribute 的值的操作

通过方法 new DocAttrStep(attr, value) 进行实例化,各参数的具体说明如下:

  • 参数 attr 是所需更新的属性 attribute 名称(字符串)
  • 参数 value 是新的属性值

class DocAttrStep 实例化 docAttrStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 DocAttrStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类

Transform 类

Transform 转换是一系列 steps 步骤的集合,以便进行高效的文档更新(一次页面更新对应于多个 steps)。

Transaction

在 prosemirror-state 模块中 Transaction是该类的子类,除了继承了该类的属性和方法,transaction 还包括选区的变化,它提供了一些相关的方法来操作选区,例如方法 transaction.replaceSelection(slice)

通过 new Transform(doc) 进行实例化(其中参数 doc 是一个 node 节点对象,根节点,表示应用转换操作前的初始文档),得到一个 transform 对象,以下称作「文档转换操作对象」

transform 文档转换操作对象包含一些属性和方法:

链式调用

transform 对象的方法一般返回其自身,以便进行链式调用

  • steps 属性:一个数组,每个元素都是一个 step 操作步骤。该文档转换操作对象所包含的一系列步骤
  • docs 属性:一个数组,每个元素的都是一个 node 节点对象。它分别记录了执行 steps 各步骤之前的文档 ❓
  • mapping 属性:一个 mapping 位置映射管道对象,它包含了 steps 各步骤所对应的 stepMap
  • doc 属性:一个 node 节点对象,表示经过转换后的文档
  • before 属性:一个 node 节点对象,表示转换前的文档
  • step(step) 方法:为当前的文档转换操作对象添加一个 step 步骤,然后保存结果。如果新增的步骤应用失败,则抛出一个 TransformError 错误。
  • maybeStep(step) 方法:尝试在当前文档转换操作对象添加一个 step 步骤,如果失败则忽略,否则返回 stepResult
  • docChanged 属性:一个布尔值,表示应用该转换后文档是否改变(实际上是基于 transform.steps.length 是否大于 0 作出的判定)
  • replace(from, to?, slice?) 方法:使用给定的 slice 替换文档的指定范围 fromto 的内容
  • replaceWith(from, to, content) 方法:使用给定的 content(可以是 fragment 片段node 节点对象[nodes] 一系列节点对象构成的数组)替换文档的指定范围 fromto 的内容
  • replaceRange(from, to, slice) 方法:用给定的 slice 切片替换文档从 fromto 范围的内容。
    ProseMirror 可能会自动调整替换范围(fromto 作为参考值,而不是作为固定的起始点和结束点)以及插入的内容(根据 sliceopenStart 属性)。
    所以该方法可能会使替换的范围变大,或者使插入的内容减少(截取 slice 的部分内容以让结尾的开放节点得以关闭),来满足 WYSIWYG 的结构预期。
    如果父级节点对象设置了 non-definingdefining 是否作为具有特殊语义的上下文,则会影响复制粘贴的替换操作
    提示

    该方法会在内容替换时进行自动调整。如果需要对替换操作有精准的控制,可以使用前面提到的方法 replace()

  • replaceRangeWith(from, to, node) 方法:使用给定的 node 节点对象替换文档的指定范围 fromto 的内容
    ProseMirror 可能会自动调整替换范围(fromto 作为参考值,而不是作为固定的起始点和结束点)
    如果当 fromto 相同且都位于父级节点的起始或结尾位置,而给定的 node 并不适合此位置时,该方法可能扩大 fromto 的范围到超出父级节点,以允许给定的 node 被放置;如果给定的范围(从 fromto)完全覆盖了一个父级节点,则该方法可能完全替换掉这个父级节点。
  • delete(from, to) 方法:删除文档的指定范围 fromto 的内容
  • deleteRangeWith(from, to) 方法:删除给定的文档范围的内容,但是 ProseMirror 可能会自动调整(扩大)该范围,可能完全覆盖父级节点直到找到一个有效的替换位置为止。
  • insert(pos, content) 方法:在文档的位置 pos 插入给定的的 content(可以是 fragment 片段node 节点对象[nodes] 一系列节点对象构成的数组)
  • lift(range, target) 方法:该方法的作用是将一个给定的 range nodeRange 节点范围移动到一个更浅的层级。
    如果它的前后有同级的内容,那么需要将该范围的内容从其父节点里分割出来(否则直接提升),并将它们沿着树级结构向上提升至 target 层(一个数字,可以使用辅助函数 liftTarget 来计算获取合法值)
  • join(pos, depth?) 方法:将给定位置 pos 前后的节点连接起来。
    第二个(可选)参数 depth(默认值为 1)设置连接的深度,例如 depth=2 时表示不仅将该位置前面的节点连接,还会继续往下一层将它们的直接子节点连起来(前一个节点的最后一个直接子节点,以及后一个节点的第一个直接子节点)。如果 depth 更大,则以此类推将层级深度更大的后代节点也连接起来。
  • wrap(range, wrappers) 方法:使用 wrappers 所指定的一系列节点,将给定的范围 range(所囊括的内容)包装起来
    参数 wrappers 是一个数组,它的元素都是一个对象 { type: NodeType, attrs? Attrs} 以表示一种节点类型(它的实例化对象作为包装容器节点)
    作为容器的这些节点假定是适合 range 所在的位置,可以使用辅助函数 findWrapping 来计算合适的包装节点
  • setBlockType(from, to?, type, attrs?) 方法:将介于 fromto 范围之间的所有的文本块(部分地,有些文本块或许不能被改变类型)设置成带有属性 attrs 类型为 type 的节点
  • setNodeMarkup(pos, type?, attrs?, marks?) 方法:更改给定位置 pos 的节点的外观,可以包括其 type 节点类型(如果该参数省略,则采用原有的节点类型)、所包含的 attrs 属性、所拥有的 marks 样式标记
  • setNodeAttribute(pos, attr, value) 方法:将给定位置 pos 的节点的属性 attr 值设置为 value
    如果要为 doc 根节点设置属性,应该使用以下的 setDocAttribute() 方法
  • setDocAttribute(attr, value) 方法:为根节点 doc 设置属性 attr 其值为 value
  • addNodeMark(pos, mark) 方法:为给定位置 pos 的节点添加 mark 样式标记
  • removeNodeMark(pos, mark) 方法:将给定位置 pos 的节点的特定的样式标记 mark(它可以是一个 mark 样式标记对象,也可以是一个 markType 样式标记类型)移除
  • split(pos, depth?, typesAfter?) 方法:在给定的位置 pos 对节点进行拆分。
    第二个(可选)参数 depth(默认值为 1)设置拆分的层级,例如 depth=2 时表示不仅将所在位置的节点拆分为两个,还对其上一级的父节点(在该位置)拆分为两个节点
    一般拆分后的节点会继承原节点的类型和属性,也可以通过第三个(可选)参数 typesAfter 来设置,它是一个数组,它的元素都是一个对象 { type: NodeType, attrs? Attrs} 用于设置拆分后的节点的类型和属性
  • addMark(from, to, mark) 方法:将给定的 mark 样式标记添加到在范围从 fromto 之间的内联节点上
  • removeMark(from, to, mark?) 方法:移除范围从 fromto 之间的内联节点的 mark 样式标记。第三个(可选)参数 mark 可以是 mark 样式标记对象(移除具体的某个样式标记)、markType 样式标记类型(移除某种类型的所有样式标记)、null(移除所有样式标记)
  • clearIncompatible(pos, parentType, match?) 方法:移除在位置 pos 与给定的父级节点类型 parentType 不兼容的 marks 样式标记和 node 节点对象。第三个(可选)参数 match 是一个 contentMatch 对象(它描述了节点可以容纳哪些内容/子节点,起作用类似于正则表达式)用于对起始位置的节点进行判断 ❓

此外该模块还提供了一些 helper 辅助函数,用于快速创建 transform 文档转换操作对象,或判断一些转换是否可以实现

  • replaceStep(doc, from, to?, slice?) 方法:用给定的 slice 切片替换 doc 文档特定范围从 fromto 的内容(如果第三个可选参数 to 省略,则是在 from 位置插入切片)。如果可以成功替换就返回一个 step 操作步骤;如果没有找到一个有意义的途径来插入该切片,或者插入是没有意义的(比如用一个空的 slice 切片替换一个空的范围,即插入空的 slice 切片)则返回 null
  • liftTarget(range) 方法:为给定的 range NodeRange 节点范围寻找一个恰当的提升层级。返回的是一个数字,表示需要提升的层级(例如它不会跨越出设置为 isolating 的祖先节点之外,因为它是独立/隔离的节点)
  • findWrapping(range, nodeType, attrs?, innerRange?) 方法:返回一个数组,它的元素都是一个对象 {type: NodeType, attrs: Attrs}[] 表示包装容器节点。如果没有可用的包裹方式则返回 null
    该方法尝试找到一个合法的方式来用特定类型 nodeType 的节点作为容器,来包裹给定的范围 range(所囊括的内容)
    说明

    如需必要的话,ProseMirror 会自动在指定类型 nodeType 的容器节点的内部和周围添加额外的节点,让包裹生成的结构符合 schema 的约束

    所以该方法返回的值是一个数组,它的元素都是一个对象 {type: NodeType, attrs: Attrs}[] 表示包装容器节点,从左往右的元素,依次表示从外到里的嵌套关系

    可以参考代码如下,通过遍历该数组,即可构建出完整的容器节点

    ts
    let content = Fragment.empty
    // 假设 wrappers 是上述方法所返回的数组
    for (let i = wrappers.length - 1; i >= 0; i--) {
      content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content))
    }
    

    当传递了第四个(可选)参数 innerRange 时(它也是一个节点范围 NodeRange),表示插入到容器节点的是范围 innerRange 所囊括的内容(而不是范围 range 所囊括内容,即 range 在这种情况下,只是指定了包裹发在文档的什么位置,而它囊括的内容会被删除/替换掉)
  • canSplit(doc, pos, depth?, typesAfter?) 方法:返回一个布尔值,表示给定位置 pos 的节点是否可以被分割
  • canJoin(doc, pos) 方法:返回一个布尔值,表示给定位置 pos 前后的节点是否可以连起来
  • joinPoint(doc, pos, dir?) 方法:寻找一个给定位置 pos 的祖先节点,它可以与前面的块级节点相连接(或者与之后的块级节点,如果 dir 是正值)。如果找到,返回一个数字,表示可连接的位置
  • insertPoint(doc, pos, nodeType) 方法:在给定位置 pos 附近寻找可以插入给定类型 nodeType 的节点的地方(如果 pos 所在的是节点的开头或结尾,而且不是合法的插入位置,则沿着文档的层级结构向上寻找)。如果找到,返回一个数字,表示可插入的位置;如果没找到,则返回 null
  • dropPoint(doc, pos, slice) 方法:在位置 pos 附近寻找可以插入 slice 切片的地方。如果找到,返回一个数字,表示可连接的位置
    即使原始的位置并不恰好在父级节点的起始或者结束位置,也会在父级节点最近的边界位置尝试插入切片。如果找不到合适的位置,则返回 null

Copyright © 2025 Ben

Theme BlogiNote

Icons from Icônes