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

Posted

技术标签:

【中文标题】如何从 CKEditor 5 中的插入事件中获取文本?【英文标题】:How to get the text from an Insert event in CKEditor 5? 【发布时间】:2018-07-01 18:32:53 【问题描述】:

我正在尝试处理来自 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 字段吗?我们是否应该假设路径中的最后一项始终是范围开始和结束的偏移量并将其用于子字符串?我认为插入事件也会被触发以插入非文本类型的东西(例如元素)。我们怎么知道这确实是一个文本类型的事件?

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

【问题讨论】:

【参考方案1】:

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

编辑:1.0.0-beta.1 于 3 月 15 日发布,因此您可以跳转到“自 2018 年 3 月以来”部分。

至 2018 年 3 月(截至 1.0.0-alpha.2

(如果您需要了解有关某些类 API 或事件的更多信息,please check out the docs。)

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

let data = '';

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

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

(您可以在CKEditor5 & Angular2 - Getting exact position of caret on click inside editor to grab data 中阅读有关字符串化范围的更多信息。)

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

Document#change 进行更改

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

协作服务可以同时带来更多变化, 不同的功能可能已经对相同的更改做出反应并将其更改排队,这可能会使模型不同。

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

如果您希望此解决方案防弹,您可能必须这样做:

    在迭代data.range 子节点时,如果您找到TextProxy,请创建一个跨越该节点的LiveRange。 然后,在 enqueueChanges() 块中,遍历存储的 LiveRanges 及其子代。 为每个找到的TextProxy 实例执行逻辑。 之后记得destroy()LiveRanges。

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

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

1.0.0-beta.1 中的重大变化将是引入 model.Differ 类、改进的事件结构和用于大部分模型的新 API。

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

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

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

除此之外,重构中还将进行许多其他更改,下面的代码 sn-p 也将反映它们。所以,在新世界中,它会是这样的:

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.
        
    
 );

在代码方面,可能比第一个sn-p要多一点,但是:

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

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

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

【讨论】:

感谢您的回答!【参考方案2】:

@Szymon Cofalik 的回答指向“如何根据更改侦听器应用一些更改”的方向。这使得它比从 Document#change 事件中获取文本所需的复杂得多,归结为以下 sn-p:

let data = '';

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

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

【讨论】:

好像在 API 1.0.0-alpha.2 的版本中,我有方法 getItems() 而不是 getChildren()。

以上是关于如何从 CKEditor 5 中的插入事件中获取文本?的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式在CKEditor 5中的当前位置插入链接

如何从 CKEditor 5 实例中获取数据

如何从CKEditor 5实例获取数据

CKEditor 5 - 如何插入一些 HTML(又名源模式在哪里)?

如何以编程方式在 CKEditor 5 的当前位置插入链接

使用 Jquery 从 CKEditor 检测 onChange 事件