React富文本编辑器开发(十)变换

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 来创建 NodeEntriesJavaScript 迭代器和 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),所以它可能是 PathPointRang。 这些类型的位置中的每一种转换都有些差异。例如,在插入文本的情况下,如果指定了一个范围(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]怎么办?

您可以考虑遍历节点的子节点并一次移动一个节点,但这会变得非常复杂,因为当您移动节点时,您引用的路径会过时。相反,您可以利用 atmatch 选项来匹配所有子项:

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将复杂的转换转换为低级操作,并自动将它们应用于编辑器。因此,除非您实施协作编辑,否则您很少需要考虑操作。

相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
懒人咖9 小时前
缺料分析时携带用料清单的二开字段
c#·金蝶云星空
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端