最後终於来到了我们最後一个章节:『 Transforms 』。
Transform 在 slate package 里头也是占了举足轻重的地位,它提供了最 high-level 操作 Slate editor 的方法,让开发者在了解 Slate 的基本运作概念以後就能直接透过这些 apis 无痛开发。
但 High-level 的 Transform 与 Low-level 的 Operation 之间还是存在着差距的,即便名义上是说一组 Transform 是由复数个 Operations 所组成,我们仍不难猜想在各个 Transform methods 里一定有事先为我们挡掉了各种 edge-cases 甚至下了不少优化操作效能的功夫。
如果我们选择每个 Transform methods 都深入探讨里头的程序码的话 ... 另一个 30 天可能又过去了吧 ...
经过笔者缜密的思考(其实就是想省点事而已XD)後,决定让这一整个章节以偏向 reference 的形式进行,刚好官方 document 上对各个 methods 的介绍也不太完整,因此这边会尽可能补足官方 document 遗漏解释的内容,会以介绍各个 Transform methods 的功用以及传入的参数的用途为主。所以除非解释上必较,否则我们会尽可能不过度深入程序码的内容。
以下我们先从整个 NodeTrasforms
的通用 Options
开始介绍起。
interface NodeOptions {
at?: Location
match?: NodeMatch<T>
mode?: ('highest' | 'lowest') | ('all' | 'highest' | 'lowest')
voids?: boolean
}
at
: 欲将节点插入编辑器的 Location ,预设值为编辑器的 selection
value
match
: 自定义的 match
function ,详细的解释请看 Day 12
mode
:这个参数主要用於 Editor.nodes
method 的 mode
option ,以 insertNodes
为例:
insertNodes(...) {
// ...
const [entry] = Editor.nodes(editor, {
at: at.path,
match,
mode,
voids,
})
// ...
}
决定 Editor.nodes
method 要以哪种模式遍历 Slate node tree 。它分成三种模式:
'all'
从 at
出发,以正常的『垂直遍历』的方式 yield
出查找到的节点
'highest'
只会查找并 yield
出最浅层的节点
'lowest'
只会查找并 yield
出 at
所涵盖的 branch 最底层的节点
voids
决定在这个 Transform method 有呼叫到的所有操作行为中,是否要略过或避开 void nodes 的出现 。
insertNodes
用途:『插入单一/复数节点进编辑器』
参数:
editor: Editor
nodes: Node | Node[]
options: InsertNodesOptions
options :
interface InsertNodesOptions extends NodeOptions {
hanging?: boolean
select?: boolean
}
hanging
如果传入的 at
为 Range type 的话,这个 value 会决定 Range 是否要另外修正为 unhanging
type 。
hanging 在 Slate 里头的意思代表『这段 Range 涵盖到了不存在的节点』。
我们假设目前的 Slate Document 如下:
[{text: 'one '}, {text: 'two', bold: true}, {text: ' three'}]
这时使用者看到的显示方式应该如下:
one two three
假设使用者选取了 "two" ,此时的 selection
会有几种 anchor
与 focus
points 的可能性出现
// 1 -- no hanging
{
anchor: { path: [1], offset: 0 },
focus: { path: [1], offset: 3 }
}
// 2 -- anchor hanging
{
anchor: { path: [0], offset: 4 },
focus: { path: [1], offset: 3 }
}
// 3 -- focus hanging
{
anchor: { path: [1], offset: 0 },
focus: { path: [2], offset: 0 }
}
// 4 -- both hanging
{
anchor: { path: [0], offset: 4 },
focus: { path: [2], offset: 0 }
}
当我们传入 hanging: false
时, Slate 就会将这组 Range 传入 Editor.unhangRange
method 里确保 Range 维持在第一种情形。
select
决定是否更新编辑器的 selection
,如果没有传入 at
参数,以编辑器的 selection
位置去新增节点的话则会强制将 select
设为 true
。
liftNodes
用途:将特定 Location 指向的内容,於 Document tree 里向上提升一个层级。如果有必要的话会将它的父层节点一分为二。
参数
editor: Editor
options: LiftNodesOptions
options :
interface LiftNodesOptions extends NodeOptions {}
这个 method 限制无法提升路径长度小於 2 的节点(路径长度为 1 的节点上层就是 Editor root 了)
if (path.length < 2) {
throw new Error(
`Cannot lift node at a path [${path}] because it has a depth of less than \`2\`.`
)
}
这个 method 又可以分成四种可能性:
if (length === 1) {
const toPath = Path.next(parentPath)
Transforms.moveNodes(editor, { at: path, to: toPath, voids })
Transforms.removeNodes(editor, { at: parentPath, voids })
} else if (index === 0) {
Transforms.moveNodes(editor, { at: path, to: parentPath, voids })
} else if (index === length - 1) {
const toPath = Path.next(parentPath)
Transforms.moveNodes(editor, { at: path, to: toPath, voids })
} else {
const splitPath = Path.next(path)
const toPath = Path.next(parentPath)
Transforms.splitNodes(editor, { at: splitPath, voids })
Transforms.moveNodes(editor, { at: path, to: toPath, voids })
}
mergeNodes
用途:将特定 Location 指向的内容,与它同层的前一个 sibling node 做合并。并会移除合并过後所产生的空节点。
参数:
editor: Editor
options: MergeNodesOptions
options :
interface MergeNodesOptions extends NodeOptions {
hanging?: boolean
}
hanging
与 insertNodes
的 hanging
option 一样,决定是否将 at
为 Range 时的 value 修正成为 unhanging
type 。
moveNodes
用途:将单个/复数个节点从旧的 Location 搬迁到新的 Path
参数:
editor: Editor
options: MoveNodesOptions
options :
interface MoveNodesOptions extends NodeOptions {
to: Path
}
to
欲将 at
指向的节点搬迁到的新路径( Path )
removeNodes
用途:将 at
Location 指向的单个/复数个节点从 Document 中移除
参数:
editor: Editor
options: RemoveNodesOptions
options :
interface RemoveNodesOptions extends NodeOptions {
hanging?: boolean
}
hanging
与 insertNodes
的 hanging
option 一样,决定是否将 at
为 Range 时的 value 修正成为 unhanging
type 。
setNodes
用途:为 at
Location 指向的节点设置新属性
参数:
editor: Editor
props: Partial<Node>
欲设置的新属性
options: SetNodesOptions
options :
interface SetNodesOptions extends NodeOptions {
hanging?: boolean
split?: boolean
}
hanging
与 insertNodes
的 hanging
option 一样,决定是否将 at
为 Range 时的 value 修正成为 unhanging
type 。
split
当 at
为 Range type 时,决定是否将节点拆分开来。
splitNodes
用途:拆分 Location 指向的节点。
参数:
editor: Editor
options: SplitNodesOptions
options :
interface SplitNodesOptions extends NodeOptions {
always?: boolean
height?: number
}
always
这个布林值会决定,如:作为基准的子节点的顺位为整个子层节点的边境(第一或最後顺位)。这类实际上不需要拆分父层节点的状况是否仍要强行拆分。
height
欲拆分的父层节点与 at
Location 指向的节点所相差的层级高度
unsetNodes
用途:取消 at
Location 指向的节点属性设置
参数:
editor: Editor
props: Partial<Node>
欲取消设置的属性
options: UnsetNodesOptions
options :
interface UnsetNodesOptions extends NodeOptions {
split?: boolean
}
split
这个 method 基本上就是多经过一层简单的处理後,呼叫 setNodes
method ,因此这里的 split
option 只是原封不动地作为传给 setNodes
method 的参数而已。
unwrapNodes
用途:将 at
Location 指向的节点内容展开并提升至上一层的位置,如果传入的 at
为 Range type 则会拆分父层节点,为了确保只有展开 Range 涵盖的内容
参数:
editor: Editor
options: UnwrapNodesOptions
options :
interface UnwrapNodesOptions extends NodeOptions {
split?: boolean
}
split
当 at
为 Range type 时,决定是否将节点拆分开来。
这个 method 主要的工作内容是因应各种传入的 at
与 match
参数来决定要丢入进 Transforms.liftNodes
method 的内容。
如果传入的 at
为 Path ,要提升的内容则为 Path 指向的节点涵盖到的所有文字作为 Range 传入到 Transforms.liftNodes
。
如果传入的 at
为 Range type 同时 split
参数设为 true
,才会去寻找 at
Range 与欲展开的节点之间的文字交集,并丢入到 Transforms.liftNodes
由它来展开 Range 内的文字内容并拆分父层节点,否则传入的 Range 仍会以要提升的节点为单位去涵盖节点内的所有文字。
wrapNodes
用途:将 element
节点里的 at
Location 指向的内容包装进一个新的 container 节点
参数:
editor: Editor
element: Element
涵盖了 at
Location 的父层 container 节点,因应不同的 Block-type 会决定後续遍历节点所传入的 match
参数:
if (match == null) {
if (Path.isPath(at)) {
match = matchPath(editor, at)
} else if (editor.isInline(element)) {
match = n => Editor.isInline(editor, n) || Text.isText(n)
} else {
match = n => Editor.isBlock(editor, n)
}
}
// ...
const matches = Array.from(
Editor.nodes(editor, { at: a, match, mode, voids })
)
if (matches.length > 0) {
// wrapNodes implementation
}
options: WrapNodesOptions
options :
interface WrapNodesOptions extends NodeOptions {
split?: boolean
}
split
当 at
为 Range type 时,决定是否将节点拆分开来。
如果传入的 at 为 Range type 同时 split 参数设为 true ,则会先将 Range 所涵盖到的文字范围与其之外的文字边界先做节点拆分,确保只有 at 涵盖到的文字集合被包装进新的 container 节点:
if (split && Range.isRange(at)) {
const [start, end] = Range.edges(at)
const rangeRef = Editor.rangeRef(editor, at, {
affinity: 'inward',
})
Transforms.splitNodes(editor, { at: end, match, voids })
Transforms.splitNodes(editor, { at: start, match, voids })
at = rangeRef.unref()!
if (options.at == null) {
Transforms.select(editor, at)
}
}
>>: Day 28 - 发表作品 - 输出与使用至不同平台
当我们有很多重复的架构,内容却不一样,以旧有无框架的开发,我们可能就需要手动一笔一笔的刻出来,更进步...
接下来让我们开始熟悉 Git 的操作流程。 使用 Git 的时候,我们会常看见以下四个指令: git...
第15天~~~完成了一半的铁人赛,之後也要继续加油! 今天要讲的内容是Spinbox,後面有几个实例...
#16. Quiz App 所谓Quiz App就是提供给用户答题的小应用,包含数个选择题,选完一个...
今天的目标: 要怎麽针对特定资料,固定地创造图表?现在用到图表的机会越来越多,很多时候我们会需要创造...