Slate
的数据结构是不可变的,因此你不能直接修改或删除节点。相反,Slate
带有一系列的 "transform" 函数,可实现更改编辑器的值的目的。
Slate
的变换函数被设计得非常灵活,可以表示可能需要对编辑器进行的各种更改。然而,这种灵活性一开始可能很难理解。
通常,您将对零个或多个节点应用单个操作。例如,下面介绍如何通过unwrapNodes
应用于块元素的每个父级来展平语法树:
javascript
Transforms.unwrapNodes(editor, {
at: [], // Path 或 Editor, 此处为整个文档
match: node =>
!Editor.isEditor(node) &&
node.children?.every(child => Editor.isBlock(editor, child)), // 所有子元素
mode: 'all', // 或者是 Editor的子元素
})
非标准操作(或调试/跟踪哪些节点将受到一组节点选项的影响)可能需要使用 Editor.nodes
来创建 NodeEntries
的 JavaScript
迭代器和 for
的循环来行动。例如,要将所有图像元素替换为其替代文本,请执行以下操作:
javascript
const imageElements = Editor.nodes(editor, {
at: [], // Path 或 Editor, 此处为整个文档
match: (node, path) => 'image' === node.type,
// mode 默认为 "all"
})
for (const nodeEntry of imageElements) {
const altText =
nodeEntry[0].alt ||
nodeEntry[0].title ||
/\/([^/]+)$/.exec(nodeEntry[0].url)?.[1] ||
'☹︎'
Transforms.select(editor, nodeEntry[1])
Editor.insertFragment(editor, [{ text: altText }])
}
选择转换
我们可以指定选择范围,如:从第一个节点开始到第二节点的第二个字符处。
javascript
Transforms.select(editor, {
anchor: { path: [0, 0], offset: 0 },
focus: { path: [1, 0], offset: 2 },
})
还可以按不同距离向前或向后移动光标,这个距离可以以字符为单位、单词为单位、以行为单位(character、 word, line)。
javascript
Transforms.move(editor, {
distance: 3,
unit: 'word',
reverse: true,
})
节点转换
节点转换作用于单个元素和文本节点。例如,您可以在特定路径处插入新的文本节点:
javascript
Transforms.insertNodes(
editor,
{
text: 'A new string of text.',
},
{
at: [0, 1],
}
)
或者,您可以将节点从一条路径移动到另一条路径:
javascript
Transforms.moveNodes(editor, {
at: [0, 0],
to: [0, 1],
})
at选项
许多转换作用于文档中的特定位置。默认情况下,他们将使用用户的当前选择。但这可以通过at
覆盖。
例如,在插入文本时,这会在用户的当前光标处插入字符串:
javascript
Transforms.insertText(editor, 'some words')
或指定插入点:
javascript
Transforms.insertText(editor, 'some words', {
at: { path: [0, 0], offset: 3 },
})
at
选项用途广泛,可非常轻松地实现更复杂的转换。由于它是一个位置(Location),所以它可能是 Path
、Point
或 Rang
。 这些类型的位置中的每一种转换都有些差异。例如,在插入文本的情况下,如果指定了一个范围(Range),则首先删除该范围,然后折叠,再插入文本。因此,要用新字符串替换文本范围,您可以执行以下操作:
javascript
Transforms.insertText(editor, 'some words', {
at: {
anchor: { path: [0, 0], offset: 0 },
focus: { path: [0, 0], offset: 3 },
},
})
或者,你指定了一个位置 Path
,它将扩展到覆盖该路径上整个节点的范围。然后,使用基于范围(Range)的行为,它将删除节点的所有内容,并将其替换为您的文本。
javascript
Transforms.insertText(editor, 'some words', {
at: [0, 0],
})
这个功能很灵活,也很强大,在转换中非常实用。
选项match
许多匹配(match)节点的转换都采用函数选项,但这种转换仅适用于函数返回true
的节点。当与at
结合使用时,match
功能也非常强大。
例如,考虑将节点从一条路径移动到另一条路径的基本转换:
javascript
Transforms.moveNodes(editor, {
at: [2],
to: [5],
})
看似简单的动作,其实在强大的引擎下相当于下面的事件发生过程:
javascript
at: {
anchor: { path: [2, 0], offset: 0 },
focus: { path: [2, 2], offset: 19 }
}
该选项的匹配函数相当于下面的操作,配置指定路径的节点:
javascript
match: (node, path) => Path.equals(path, [2])
然后,Slate遍历整个文档树节点,并将匹配上面这个路径的所有节点移动到新位置。
但是,如果您想将节点的子节点移至[2]
怎么办?
您可以考虑遍历节点的子节点并一次移动一个节点,但这会变得非常复杂,因为当您移动节点时,您引用的路径会过时。相反,您可以利用 at
和match
选项来匹配所有子项:
javascript
Transforms.moveNodes(editor, {
// 将扩展范围到整个[2]这个节点
at: [2],
// 匹配所有子节点路径。
match: (node, path) => path.length === 2,
to: [5],
})
在这里我们使用相同的 at
路径(扩展到一个范围),但是默认情况下,我们提供了自己的match
函数,而不是让它匹配该路径,该函数恰好只匹配节点的子节点。
使用match
使得复杂逻辑变得更加简单。
例如,要向任何尚未斜体的文本节点添加粗体标记:
javascript
Transforms.setNodes(
editor,
{ bold: true },
{
at: [],
match: (node, path) => Text.isText(node) && node.italic !== true,
}
)
在执行转换时,如果您曾经循环使用节点并一次转换一个节点,请考虑看看match
是否可以解决您的用例,并将管理循环的复杂性转移到 Slate。该match
函数可以检查节点的子节点,在node.children
中,或使用Node.parent
检查其父节点。
操作
操作是在调用转换时发生的精细的低级别操作。单个转换可能会导致对编辑器应用许多低级别操作。Slate的核心定义了富文本文档上可能发生的所有可能的操作。例如:
javascript
editor.apply({
type: 'insert_text',
path: [0, 0],
offset: 15,
text: 'A new string of text to be inserted.',
})
editor.apply({
type: 'remove_node',
path: [0, 0],
node: {
text: 'A line of text!',
},
})
editor.apply({
type: 'set_selection',
properties: {
anchor: { path: [0, 0], offset: 0 },
},
newProperties: {
anchor: { path: [0, 0], offset: 15 },
},
})
在幕后,Slate将复杂的转换转换为低级操作,并自动将它们应用于编辑器。因此,除非您实施协作编辑,否则您很少需要考虑操作。