转换

Slate 的数据结构不可变,所以不能直接修改或者删除节点。不过,Slate 带有一组“转换”函数用于转换编辑器的值。

Slate 的转换函数设计为非常灵活,可以对编辑器进行各种更改。然而,这种灵活可能刚开始难以理解。

通常,会将单个操作用于单个或者多个节点。例如,通过对每个块级元素的父级应用 unwrapNodes 来展平语法树:

Transforms.unwrapNodes(editor, {
  at: [], // 编辑器的路径
  match: node =>
    !Editor.isEditor(node) &&
    node.children?.every(child => Editor.isBlock(editor, child)),
  mode: 'all', // 还有编辑器的子元素
})

非标准操作(或者调试/追踪哪些节点会受到 NodeOptions 的影响)可能需要使用 Editor.nodes 创建 NodeEntries JavaScript 迭代器且用 for..of 执行。 例如,用对应的 alt 文本替换所有图像元素:

const imageElements = Editor.nodes(editor, {
  at: [], // 编辑器的路径
  match: (node, path) => 'image' === node.type,
  // 模式默认是 “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 }])
}

🤖 查看转换 参考获取 Slate 转换的完整列表。

选区转换

选区关联转换是一些更简单的转换。例如,以下是将选区设置为新范围的方法:

Transforms.select(editor, {
  anchor: { path: [0, 0], offset: 0 },
  focus: { path: [1, 0], offset: 2 },
})

但也可以更复杂。

例如,通常需要将光标按照字符,单词,行来向前/后移动不同的距离。以下是如何将光标向后移动三个单词:

Transforms.move(editor, {
  distance: 3,
  unit: 'word',
  reverse: true,
})

🤖 请查看选区转换 API 参考获取更多信息

文本转换

文本转换用于编辑器的文本内容。例如,以下是在指定位置插入文本字符串的方法:

Transforms.insertText(editor, 'some words', {
  at: { path: [0, 0], offset: 3 },
})

或者从编辑器中删除整个范围内的所有内容:

Transforms.delete(editor, {
  at: {
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [1, 0], offset: 2 },
  },
})

🤖 请查看文本转换 API 参考获取更多信息

节点转换

节点转换作用于构成编辑器值的单个元素和文本节点。例如,在指定位置插入新文本节点:

Transforms.insertNodes(
  editor,
  {
    text: 'A new string of text.',
  },
  {
    at: [0, 1],
  }
)

或者将节点从该路径移动到其它路径:

Transforms.moveNodes(editor, {
  at: [0, 0],
  to: [0, 1],
})

🤖 请查看节点转换 API 参考获取更多信息

at 选项

许多转换作用于文档的指定位置。默认情况下使用用户的当前选区。但是可以使用 at 选项覆盖。

例如在插入文本时,会在用户的当前光标处插入字符串:

Transforms.insertText(editor, 'some words')

而这将会在指定位置插入:

Transforms.insertText(editor, 'some words', {
  at: { path: [0, 0], offset: 3 },
})

at 选项的用途非常广泛,很容易的用于实现更复杂的装换。因为它是 Location 所以它可以是 PathPointRange。每种类型的位置都会导致略有不同的转换。

例如,在插入文本时,如果指定 Range 位置,首先删除范围,在插入文本的位置折叠为单个点。

所以要用新字符串替换文本,可以执行如下操作:

Transforms.insertText(editor, 'some words', {
  at: {
    anchor: { path: [0, 0], offset: 0 },
    focus: { path: [0, 0], offset: 3 },
  },
})

如果指定 Path 位置,将会扩大到范围,覆盖该路径的整个节点。然后,使用基于范围的行为,删除整个节点的内容且使用文本替换。

所以使用新字符串替换整个节点的文本,可以执行如下操作:

Transforms.insertText(editor, 'some words', {
  at: [0, 0],
})

基于位置的行为适用于所有采用 at 选项的转换。可能刚开始很难理解,但它让 API 更强大且能够表达细微差别的转换。

match 选项

许多基于节点的转换采用 match 函数选项,限制转换仅应用于函数返回 true 的节点。当跟 at 一起使用时, match 也会变得非常强大。

例如,考虑一个基础的转换,将节点从一个路径移动到另一个路径:

Transforms.moveNodes(editor, {
  at: [2],
  to: [5],
})

虽然看起来就像是将路径移动到另一个地方。在底层发生了两件事。。。

首先, at 选项扩展为范围代表 [2] 节点内的所有内容。看起来就像是:

at: {
  anchor: { path: [2, 0], offset: 0 },
  focus: { path: [2, 2], offset: 19 }
}

其次,match 选项默认仅为匹配指定路径的函数,在这里是 [2]

match: (node, path) => Path.equals(path, [2])

然后 Slate 遍历范围,移动所有通过了匹配器函数的节点到新位置。在本例中,因为 match 默认仅匹配精确的 [2] 路径,因此该节点被移动。

但是想要移动 [2] 的子节点呢?

可能会循环子节点并依次移动,但会变得非常复杂,因为当移动节点时,所引用的路径也会过时。

不过,可以使用 atmatch 选项来匹配所有子节点:

Transforms.moveNodes(editor, {
  // 这将会扩展到 `[2]` 的整个节点的范围。
  at: [2],
  // 匹配路径较长的节点,即子节点。
  match: (node, path) => path.length === 2,
  to: [5],
})

这是我们使用相同的 at 路径(扩展到范围),但是默认情况下不仅匹配路径,而是提供恰好匹配指定子节点的 match 函数。

使用 match 可以使复杂的逻辑变得很简单。

例如,考虑为任何不是斜体的文本节点添加粗体标记:

Transforms.setNodes(
  editor,
  { bold: true },
  {
    // 路径引用编辑器,且扩展为
    // 包含编辑器所有内容的范围。
    at: [],
    // 仅匹配不是斜体的文本节点。
    match: (node, path) => Text.isText(node) && node.italic !== true,
  }
)

当执行转换时,如果曾经遍历节点且依次转换,请考虑 match 是否可以解决该用例,并将管理循环的复杂性转移给 Slate。 match 函数可以使用 node.children 检查子节点或使用 Node.parent 检查父节点。

转换和标准化

如果节点树应该在转换中标准化,则可能需要将转换序列使用 Editor.withoutNormalizing 包裹起来。参阅 标准化 —— 对其它代码的影响

results matching ""

    No results matching ""