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

时间:2018-01-23 02:25:26

标签: javascript ckeditor ckeditor5

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

是否有我遗漏的东西,或者只有不同的方式一起完成这一切?

2 个答案:

答案 0 :(得分:2)

首先,让我描述您目前是如何做到的(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.item中没有其他data或类似属性的原因。可能有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
  5. 正如您所看到的,这似乎不必要地复杂化。提供一个开放灵活的框架(如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实例,该实例将在第一次更改之前的模型状态与您希望对更改做出反应的时刻的模型状态之间存储差异。您将遍历所有差异项并检查确切的位置和更改的位置。

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

    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. 新方法更容易掌握 - 在完成所有更改后,您可以使用所有更改,而不是在其他更改排队时对更改作出反应,并且可能会弄乱模型。
    4. writer是一个对象,用于对模型进行更改(而不是Document#batch API)。它将包含insertText()insertElement()remove()等方法

      您可以检查model.Differ API和测试,因为它们已在master branch上提供。 (内部代码将更改,但API将保持不变。)

答案 1 :(得分:1)

@Szymon Cofalik的答案走向了一个方向"如何根据变更听众应用一些变化"。这使得它比从Document#change事件获取文本所需的内容复杂得多,后者归结为以下代码段:

let data = '';

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

然而,对变化做出反应是一项棘手的任务,因此,如果您打算这样做,请务必阅读Szymon的深刻见解。