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

Slate的数据结构是不可变的,因此你不能直接修改或删除节点。相反,Slate带有一系列的 “transform” 函数,可实现更改编辑器的值的目的。
Slate的变换函数被设计得非常灵活,可以表示可能需要对编辑器进行的各种更改。然而,这种灵活性一开始可能很难理解。
通常,您将对零个或多个节点应用单个操作。例如,下面介绍如何通过unwrapNodes应用于块元素的每个父级来展平语法树:

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 的循环来行动。例如,要将所有图像元素替换为其替代文本,请执行以下操作:

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 }])
}

选择转换

我们可以指定选择范围,如:从第一个节点开始到第二节点的第二个字符处。

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

还可以按不同距离向前或向后移动光标,这个距离可以以字符为单位、单词为单位、以行为单位(character、 word, line)。

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

节点转换

节点转换作用于单个元素和文本节点。例如,您可以在特定路径处插入新的文本节点:

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

或者,您可以将节点从一条路径移动到另一条路径:

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

at选项

许多转换作用于文档中的特定位置。默认情况下,他们将使用用户的当前选择。但这可以通过at覆盖。
例如,在插入文本时,这会在用户的当前光标处插入字符串:

Transforms.insertText(editor, 'some words')

或指定插入点:

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

at选项用途广泛,可非常轻松地实现更复杂的转换。由于它是一个位置(Location),所以它可能是 PathPointRang。 这些类型的位置中的每一种转换都有些差异。例如,在插入文本的情况下,如果指定了一个范围(Range),则首先删除该范围,然后折叠,再插入文本。因此,要用新字符串替换文本范围,您可以执行以下操作:

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

或者,你指定了一个位置 Path,它将扩展到覆盖该路径上整个节点的范围。然后,使用基于范围(Range)的行为,它将删除节点的所有内容,并将其替换为您的文本。

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

这个功能很灵活,也很强大,在转换中非常实用。

选项match

许多匹配(match)节点的转换都采用函数选项,但这种转换仅适用于函数返回true的节点。当与at结合使用时,match功能也非常强大。
例如,考虑将节点从一条路径移动到另一条路径的基本转换:

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

看似简单的动作,其实在强大的引擎下相当于下面的事件发生过程:

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

该选项的匹配函数相当于下面的操作,配置指定路径的节点:

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

然后,Slate遍历整个文档树节点,并将匹配上面这个路径的所有节点移动到新位置。
但是,如果您想将节点的子节点移至[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检查其父节点。

操作

操作是在调用转换时发生的精细的低级别操作。单个转换可能会导致对编辑器应用许多低级别操作。Slate的核心定义了富文本文档上可能发生的所有可能的操作。例如:

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

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码蚁先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值