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 抽象类不直接实例化,要先被继承,给出父类的抽象方法 apply、invert、map、getMap 和 fromJSON 的具体实现,构建出各种子类再使用
它给出了一些抽象方法和具体的方法:
- 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 节点对象(根节点,表示转换成功后的文档);否则该属性值为nullfailed属性:如果步骤没有执行成功,则该属性值为错误信息(一个字符串);否则该属性值为null
- static
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 interfaceMappable,该类的实例化对象要包含以下属性或方法map(pos, assoc?)方法:该方法将旧文档中的位置pos(用数字表示)映射到(经过 step 步骤转换后的)新文档中,该方法返回一个数字表示新文档中的相应位置。
如果正好在所需映射的位置pos处插入了内容,则可以通过设置第二个(可选)参数assoc是-1或1(默认值)来决定这个旧的位置pos应该映射到新插入内容的哪一侧。如果assoc=-1则表示将旧位置映射到插入内容的左侧/前面;如果assoc=1则表示将旧位置映射到插入内容的右侧/后面mapResult(pos, assoc?)方法:该方法也和上一个方法map()类似,也是将旧文档中的位置pos(用数字表示)映射到(经过 step 步骤转换后的)新文档中,但返回的值不再是一个数字,而是一个mapResult对象,它具有一些属性可以更详细地描述位置的相关信息。同样支持设置第二个(可选)参数assoc来决定映射到哪一侧mapResult
class
mapResult类用于表示映射的结果,除了位置信息(用数字表示),还有其他额外的信息,该类的实例化对象称为「映射结果」pos属性:一个数字,表示映射到新文档中的位置deleted属性:一个布尔值,表示所映射的位置是否在相应的 step 步骤中删除了映射位置正好落入在删除内容的一侧
如果所需映射的位置
pos正好处于删除内容的边缘一侧时,则根据assoc参数来判断该位置是否视为被删掉。当
assoc=1(默认值)时,表示保留右侧,如果pos在删除内容的左侧就会被视为删掉,即属性deleted为true;如果pos在删除内容的右侧就不会被视为删掉,即属性deleted为false。当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类表示一系列的stepMappipeline(在该管道内可以包含零个或多个位置映射器),该类的实例化对象称为「位置映射管道」它也需要 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 以获取新的位置信息,参数from与to规定了要遍历哪些 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对象,它只包含当前位置映射管道的一部分(只包含from到to这部分的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来决定映射到哪一侧
- static
- abstract
invert(doc)抽象方法:创建一个步骤。它的作用与当前的步骤相反,所接收的参数doc是一个 node 节点对象(根节点),它是在应用当前步骤(转换)之前的旧文档 - abstract
map(mappable)抽象方法:创建一个步骤。根据传入的对象(它符合一种 TypeScript interfaceMappable)对当前的步骤进行调整,而得到一个新的步骤对象。如果在mapping所记录的操作中,当前步骤被完全删除,则返回null应用场景
如果基于一个 doc 创建了当前步骤对象之后,在它被应用之前,又有另外一个 Step 被先应用了,如果直接应用当前步骤,则它所包含的一些位置信息可能是错误的(并不是对应到最新的文档中),所以需要更新当前步骤以重新获取到正确的
from和to信息 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?) 进行实例化,各参数的具体说明如下:
- 参数
from和to是原文档的替换范围的起始位置和结束位置(一个数字,基于 index schema 计算而得,表示文档的位置) - 参数
slice是切片对象,用于替换原文档指定范围的内容注意
切片
slice需要「契合」替换的位置,即切片两侧的开放深度和替换位置的深度需要相同,而且可以与包围它的节点 join 接起来 - (可选)参数
structure一个布尔值(默认值为false),以表示该替换步骤是否与协作编辑相关。
如果为true表示该操作只进行结构替换,而不进行内容覆盖;如果为false则表示该操作只是简单内容替换。提示
这主要是处理 rebase step 可能会覆盖原文档内容的场景。
在 ProseMirror 的多人协作中,当收到远程其他用户的更改时,collab 模块会将这些更改表示为一系列的 steps 步骤,然后基于当前用户操作的文档来应用这些远程步骤,这个过程就是 rebase。
在 rebase 过程中,如果远程替换步骤中的
from和to范围与当前文档中的内容重叠,可能会导致新内容被覆盖。为了避免这种情况,使用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 过程中,不会不经意地覆盖其他用户或当前用户的修改内容。
class ReplaceStep 实例化 replaceStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 ReplaceStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类
ReplaceAroundStep 子类
ReplaceAroundStep 类继承自 Step 类,它和 ReplaceStep 类似,也是表示使用一个 slice 切片对文档的特定范围的内容进行替换操作,但它可以保留原文档(被替代范围中的)部分/全部内容(将这些内容插入到 slice 切片的指定位置中),一般用作 wrap around 包裹选中的内容
通过方法 new ReplaceStep(from, to, gapFrom, gapTo, slice, insert, structure?) 进行实例化,各参数的具体说明如下:
- 参数
from和to都是一个数字(基于 index schema 计算而得,表示文档的位置),是需要替换的文档范围 - 参数
gapFrom和gapTo是一个数字(基于 index schema 计算而得,表示文档的位置),分别需要保留的原始内容范围的起始位置和结束位置,最终的效果是在插入新内容时这部分的内容不会被替换掉注意
参数
gapFrom和gapTo所表示的位置需要在一个节点的两侧,而不能像 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) 进行实例化,各参数的具体说明如下:
- 参数
from和to分别表示一个文档范围的起始位置和结束位置(一个数字,基于 index schema 计算而得,表示文档的位置) - 参数
mark是一个 mark 样式标记对象
class AddMarkStep 实例化 addMarkStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 AddMarkStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类
RemoveMarkStep 子类
RemoveMarkStep 类继承自 Step 类,表示移除指定范围的 inline content(inline 内联类型的节点的片段/内容的特定范围)上原有的 mark 样式标记的操作
通过方法 new RemoveMarkStep(from, to, mark) 进行实例化,各参数的具体说明如下:
- 参数
from和to分别表示一个文档范围的起始位置和结束位置(一个数字,基于 index schema 计算而得,表示文档的位置) - 参数
mark是一个 mark 样式标记对象
class RemoveMarkStep 实例化 removeMarkStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 RemoveMarkStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类
AddNodeMarkStep 子类
AddNodeMarkStep 类继承自 Step 类,表示为目标节点添加给定的 mark 样式标记的操作
通过方法 new AddNodeMarkStep(pos, mark) 进行实例化,各参数的具体说明如下:
- 参数
pos是目标节点的位置(一个数字,基于 index schema 计算而得,用于寻找该目标节点) - 参数
mark是一个 mark 样式标记对象
class AddNodeMarkStep 实例化 addNodeMarkStep 对象,它具有 Step 父类所定义的一些属性和方法(由于 AddNodeMarkStep 继承自 Step 父类,并对其中的抽象方法提供了具体的实现),具体请参考 Step 父类
RemoveNodeMarkStep 子类
RemoveNodeMarkStep 类继承自 Step 类,表示移除目标节点上原有的 mark 样式标记的操作
通过方法 new RemoveNodeMarkStep(pos, mark) 进行实例化,各参数的具体说明如下:
- 参数
pos是目标节点的位置(一个数字,基于 index schema 计算而得,用于寻找该目标节点) - 参数
mark是一个 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各步骤所对应的 stepMapdoc属性:一个 node 节点对象,表示经过转换后的文档before属性:一个 node 节点对象,表示转换前的文档step(step)方法:为当前的文档转换操作对象添加一个 step 步骤,然后保存结果。如果新增的步骤应用失败,则抛出一个TransformError错误。maybeStep(step)方法:尝试在当前文档转换操作对象添加一个 step 步骤,如果失败则忽略,否则返回 stepResultdocChanged属性:一个布尔值,表示应用该转换后文档是否改变(实际上是基于transform.steps.length是否大于0作出的判定)replace(from, to?, slice?)方法:使用给定的 slice 替换文档的指定范围from到to的内容replaceWith(from, to, content)方法:使用给定的content(可以是fragment片段,node节点对象或[nodes]一系列节点对象构成的数组)替换文档的指定范围from到to的内容replaceRange(from, to, slice)方法:用给定的slice切片替换文档从from到to范围的内容。
ProseMirror 可能会自动调整替换范围(from和to作为参考值,而不是作为固定的起始点和结束点)以及插入的内容(根据slice的openStart属性)。
所以该方法可能会使替换的范围变大,或者使插入的内容减少(截取slice的部分内容以让结尾的开放节点得以关闭),来满足 WYSIWYG 的结构预期。
如果父级节点对象设置了 non-defining 或 defining 是否作为具有特殊语义的上下文,则会影响复制粘贴的替换操作提示
该方法会在内容替换时进行自动调整。如果需要对替换操作有精准的控制,可以使用前面提到的方法
replace()replaceRangeWith(from, to, node)方法:使用给定的node节点对象替换文档的指定范围from到to的内容
ProseMirror 可能会自动调整替换范围(from和to作为参考值,而不是作为固定的起始点和结束点)
如果当from和to相同且都位于父级节点的起始或结尾位置,而给定的node并不适合此位置时,该方法可能扩大from和to的范围到超出父级节点,以允许给定的node被放置;如果给定的范围(从from和to)完全覆盖了一个父级节点,则该方法可能完全替换掉这个父级节点。delete(from, to)方法:删除文档的指定范围from到to的内容deleteRangeWith(from, to)方法:删除给定的文档范围的内容,但是 ProseMirror 可能会自动调整(扩大)该范围,可能完全覆盖父级节点直到找到一个有效的替换位置为止。insert(pos, content)方法:在文档的位置pos插入给定的的content(可以是fragment片段,node节点对象或[nodes]一系列节点对象构成的数组)lift(range, target)方法:该方法的作用是将一个给定的rangenodeRange 节点范围移动到一个更浅的层级。
如果它的前后有同级的内容,那么需要将该范围的内容从其父节点里分割出来(否则直接提升),并将它们沿着树级结构向上提升至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?)方法:将介于from和to范围之间的所有的文本块(部分地,有些文本块或许不能被改变类型)设置成带有属性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其值为valueaddNodeMark(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样式标记添加到在范围从from到to之间的内联节点上removeMark(from, to, mark?)方法:移除范围从from到to之间的内联节点的mark样式标记。第三个(可选)参数mark可以是 mark 样式标记对象(移除具体的某个样式标记)、markType 样式标记类型(移除某种类型的所有样式标记)、null(移除所有样式标记)clearIncompatible(pos, parentType, match?)方法:移除在位置pos与给定的父级节点类型parentType不兼容的 marks 样式标记和 node 节点对象。第三个(可选)参数match是一个 contentMatch 对象(它描述了节点可以容纳哪些内容/子节点,起作用类似于正则表达式)用于对起始位置的节点进行判断 ❓
此外该模块还提供了一些 helper 辅助函数,用于快速创建 transform 文档转换操作对象,或判断一些转换是否可以实现
replaceStep(doc, from, to?, slice?)方法:用给定的slice切片替换doc文档特定范围从from到to的内容(如果第三个可选参数to省略,则是在from位置插入切片)。如果可以成功替换就返回一个 step 操作步骤;如果没有找到一个有意义的途径来插入该切片,或者插入是没有意义的(比如用一个空的 slice 切片替换一个空的范围,即插入空的 slice 切片)则返回nullliftTarget(range)方法:为给定的rangeNodeRange 节点范围寻找一个恰当的提升层级。返回的是一个数字,表示需要提升的层级(例如它不会跨越出设置为 isolating 的祖先节点之外,因为它是独立/隔离的节点)findWrapping(range, nodeType, attrs?, innerRange?)方法:返回一个数组,它的元素都是一个对象{type: NodeType, attrs: Attrs}[]表示包装容器节点。如果没有可用的包裹方式则返回null
该方法尝试找到一个合法的方式来用特定类型nodeType的节点作为容器,来包裹给定的范围range(所囊括的内容)说明
如需必要的话,ProseMirror 会自动在指定类型
nodeType的容器节点的内部和周围添加额外的节点,让包裹生成的结构符合 schema 的约束所以该方法返回的值是一个数组,它的元素都是一个对象
{type: NodeType, attrs: Attrs}[]表示包装容器节点,从左往右的元素,依次表示从外到里的嵌套关系可以参考代码如下,通过遍历该数组,即可构建出完整的容器节点
tslet 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所在的是节点的开头或结尾,而且不是合法的插入位置,则沿着文档的层级结构向上寻找)。如果找到,返回一个数字,表示可插入的位置;如果没找到,则返回nulldropPoint(doc, pos, slice)方法:在位置pos附近寻找可以插入slice切片的地方。如果找到,返回一个数字,表示可连接的位置
即使原始的位置并不恰好在父级节点的起始或者结束位置,也会在父级节点最近的边界位置尝试插入切片。如果找不到合适的位置,则返回null