如何从 CKEditor 5 中的 Insert 事件中获取文本?

How to get the text from an Insert event in CKEditor 5?

我正在尝试处理来自 CKEditor 5 的插入事件。

editor.document.on("change", (eventInfo, type, data) => {
  switch (type) {
    case "insert":
    console.log(type, data);
    break;
  }
});

在编辑器中输入时调用回调。事件回调中的 data 参数大致如下所示:

{
  range: {
    start: {
      root: { ... },
      path: [0, 14]
    },
    end: {
      root: { ... },
      path: [0, 15]
    }
  }
}

我没有找到一种方便的方法来确定实际插入的文本。我可以调用 data.range.root.getNodeByPath(data.range.start.path);,这似乎让我找到了插入文本的文本节点。然后我们应该查看文本节点的 data 字段吗?我们是否应该假设路径中的最后一项始终是范围开始和结束的偏移量并将其用于子字符串?我认为插入事件也会因插入非文本类型的东西(例如元素)而被触发。我们怎么知道这确实是事件的文本类型?

是不是我遗漏了什么,或者是否有不同的方法可以一起完成这一切?

首先,让我描述一下你目前(2018 年 1 月)会怎么做。请记住,CKEditor 5 现在正在进行重大重构,事情将会发生变化。最后,我将描述完成此重构后的外观。如果您不介意再等一段时间重构结束,您可以跳到后面的部分。

编辑: 1.0.0-beta.1 于 3 月 15 日发布,因此您可以跳转到 "Since March 2018" 部分。

2018 年 3 月之前(最多 1.0.0-alpha.2

(如果您需要了解更多 class API 或事件,please check out the docs。)

您最好的选择是简单地遍历插入的范围。

let data = '';

for ( const child of data.range.getItems() ) {
    if ( child.is( 'textProxy' ) ) {
        data += child.data;
    }
}

请注意,即使整个 Text 节点都包含在范围内,当您遍历范围时,总是会返回 TextProxy 实例。

(您可以在 中阅读有关字符串化范围的更多信息。)

请记住,InsertOperation 可能会插入多个不同类型的节点。大多数情况下,这些只是单个字符或元素,但可以提供更多节点。这就是为什么 data 中没有额外的 data.item 或类似的 属性。可能有 data.items,但它们与 Array.from( data.range.getItems() ).

相同

正在对 Document#change

进行更改

你没有提到你想用这些信息做什么。获取范围的内容很容易,但是如果您想以某种方式对这些更改做出反应并更改模型,那么您需要小心。当触发 change 事件时,可能已经有更多更改排队。例如:

  • 可以通过协作服务立即进行更多更改,
  • 不同的功能可能已经对相同的更改做出反应并将其更改排队,这可能会使模型不同。

如果您确切知道将使用哪一组功能,则可以坚持我的建议。请记住,您对模型所做的任何更改都应该在 Document#enqueueChanges() 块中完成(否则,它不会被渲染)。

如果你想让这个解决方案防弹,你可能必须这样做:

  1. 在遍历 data.range 个子节点时,如果找到 TextProxy,请创建一个跨越该节点的 LiveRange
  2. 然后,在 enqueueChanges() 块中,遍历存储的 LiveRange 及其子项。
  3. 为每个找到的 TextProxy 个实例做你的逻辑。
  4. 记得 destroy() 之后的所有 LiveRange

如您所见,这似乎不必要地复杂了。提供开放和灵活的框架(如 CKE5)有一些缺点,考虑到所有边缘情况就是其中之一。然而,它确实可以更简单,这就是我们首先开始重构的原因。

自 2018 年 3 月起(从 1.0.0-beta.1 开始)

1.0.0-beta.1 中的重大变化将是引入 model.Differ class、改进的事件结构和新的 API型号。

首先,Document#event:change 将在所有 enqueueChange 块完成后触发。这意味着您不必担心另一个更改是否会干扰您在回调中响应的更改。

此外,将添加 engine.Document#registerPostFixer() 方法,您将能够使用它来注册回调。 change 活动仍然可用,但 change 活动和 registerPostFixer 之间会有细微差别(我们将在指南和文档中介绍它们)。

其次,您将有权访问一个 model.Differ 实例,该实例将存储第一次更改之前的模型状态与您想要对更改做出反应时的模型状态之间的差异。您将遍历所有差异项并检查究竟发生了什么变化以及发生了什么变化。

除此之外,重构中将进行许多其他更改,下面的代码片段也将反映出来。所以,在新世界里,它会是这样的:

editor.document.registerPostFixer( writer => {
    const changes = editor.document.differ.getChanges();

    for ( const entry of changes ) {
        if ( entry.type == 'insert' && entry.name == '$text' ) {
            // Use `writer` to do your logic here.
            // `entry` also contains `length` and `position` properties.
        }
    }
} );

就代码而言,它可能比第一个片段中的要多一些,但是:

  1. 第一个片段不完整。
  2. 在新方法中需要考虑的边缘案例要少得多。
  3. 新方法更容易掌握 - 在所有更改都完成后您可以使用所有更改,而不是在其他更改排队时对更改做出反应并且可能会弄乱模型。

writer 是一个对象,将用于对模型进行更改(而不是 Document#batch API)。它将具有 insertText()insertElement()remove() 等方法

您可以检查 model.Differ API 和测试,因为它们已经在 master branch 上可用。 (内部代码会改变,但API会保持原样。)

@Szymon Cofalik 的回答转向了一个方向"How to apply some changes based on a change listener"。这使得它比从 Document#change 事件中获取文本所需的复杂得多,后者归结为以下代码片段:

let data = '';

for ( const child of data.range.getChildren() ) {
    if ( child.is( 'textProxy' ) ) {
        data += child.data;
    }
}

但是,对更改做出反应是一项棘手的任务,因此,如果您打算这样做,请务必阅读 Szymon 富有洞察力的回答。