实现块状小部件
在本教程中,您将学习如何实现更复杂的 CKEditor 5 插件。
您将构建一个“简单框”功能,允许用户将带有标题和正文字段的自定义框插入文档中。您将使用小部件实用程序并使用模型-视图转换来正确设置此功能的行为。稍后,您将创建一个 UI,允许使用工具栏按钮将新的简单框插入文档中。
如果您想在深入研究之前查看本教程的最终产品,请查看演示。
# 开始之前
本教程将参考您在学习过程中遇到的CKEditor 5 架构部分的各个部分。虽然阅读这些部分不是完成本教程的必要条件,但建议您在某些时候阅读这些指南,以更好地理解本教程中使用的机制。
如果您想为小部件触发的事件使用自己的事件处理程序,您必须将其包装在具有data-cke-ignore-events
属性的容器中,以将其从编辑器的默认处理程序中排除。有关更多详细信息,请参阅从默认处理程序中排除 DOM 事件。
# 让我们开始
最简单的入门方法是使用以下命令获取入门项目。
这将在名为block-widget
的新目录中创建必要的文件。npm install
命令将安装所有依赖项,npm run dev
将启动开发服务器。
带有基本插件的编辑器是在main.js
文件中创建的。
打开终端中显示的 URL。如果一切正常,您应该在浏览器中看到一个像这样的 CKEditor 5 实例
# 插件结构
编辑器启动并运行后,您可以开始实现插件。您可以将整个插件代码保存在单个文件中,但是建议将它的“编辑”和“UI”层拆分,并创建一个加载两者的主插件。这样,您可以确保更好地分离关注点,并允许重新组合功能(例如,选择现有功能的编辑部分,但编写自己的 UI)。所有官方 CKEditor 5 插件都遵循这种模式。
此外,您将命令、按钮和其他“自包含”组件的代码拆分到单独的文件中。为了避免将这些文件与项目的main.js
文件混淆,请创建以下目录结构
现在定义 3 个插件。
首先是主(粘合)插件。它的作用只是加载“编辑”和“UI”部分。
现在,剩下的两个插件
最后,您需要在main.js
文件中加载SimpleBox
插件
您的页面将刷新,您应该看到SimpleBoxEditing
和SmpleBoxUI
插件已加载
# 模型和视图层
CKEditor 5 实现了一个 MVC 架构,它的自定义数据模型虽然仍然是树结构,但并没有与 DOM 一对一映射。您可以将模型视为编辑器内容的语义表示,而 DOM 是其可能的表示之一。
了解有关编辑引擎架构的更多信息。
由于您的简单框功能旨在成为一个带有标题和描述字段的框,因此请像这样定义它的模型表示
# 定义模式
您需要从定义模型的模式开始。您需要定义 3 个元素及其类型,以及允许的父/子元素。
了解有关模式的更多信息。
使用此定义更新SimpleBoxEditing
插件。
定义模式目前不会对编辑器有任何影响。这是一个信息,插件和编辑器引擎可以使用它来了解如何处理按下Enter键、单击元素、键入文本、插入图像等操作的行为。
为了使简单框插件开始执行任何操作,您需要定义模型-视图转换器。现在就做吧!
# 定义转换器
转换器告诉编辑器如何将视图转换为模型(例如,加载数据到编辑器或处理粘贴内容)以及如何将模型渲染到视图(用于编辑目的,或者检索编辑器数据时)。
了解有关编辑器中的转换的更多信息。
现在您需要考虑如何将<simpleBox>
元素及其子元素渲染到 DOM(用户将看到的内容)和数据中。CKEditor 5 允许将模型转换为不同的结构以供编辑,并转换为不同的结构以存储为“数据”或在复制粘贴内容时与其他应用程序交换。但是,为了简单起见,现在在两个管道中都使用相同的表示。
您要实现的视图中的结构
使用conversion.elementToElement()
方法定义所有转换器。
您需要为 3 个模型元素定义转换器。使用此代码更新SimpleBoxEditing
插件
有了转换器后,您可以尝试查看简单框的实际效果。您还没有定义将新简单框插入文档的方法,因此请通过编辑器数据加载它。为此,您需要修改index.html
文件
瞧!这是您的第一个简单框实例
# 模型中有什么?
您添加到index.html
文件中的 HTML 是您的编辑器数据。这是editor.getData()
将返回的内容。此外,目前,这也是 CKEditor 5 引擎在可编辑区域中渲染的 DOM 结构
但是,模型中有什么?
要了解这一点,请使用官方的CKEditor 5 检查器。在安装后,您需要在main.js
文件中加载它
刷新页面后,您将看到检查器
您将看到以下 HTML 类字符串
如您所见,此结构与 HTML 输入/输出完全不同。如果您仔细观察,您还会注意到第一段中的[]
字符 - 这是选择位置。
使用编辑器功能(粗体、斜体、标题、列表、选择)进行一些操作,以查看模型结构如何变化。
您还可以使用一些有用的助手,如getData()
和setData()
来了解有关编辑器模型状态的更多信息,或在测试中编写断言。
# 将简单框转换为小部件之前的行为
现在该检查简单框是否按您期望的方式运行了。您可以观察到以下情况
- 您可以在标题中键入文本。按下Enter不会拆分它,Backspace不会完全删除它。这是因为它在模式中被标记为
isLimit
元素。 - 您无法在标题中应用列表,也无法将其转换为标题(除了
<h1 class="simple-box-title">
,它已经是这样了)。这是因为它只允许其他块元素(如段落)中允许的内容。但是,您可以在标题中应用斜体(因为斜体在其他块中是允许的)。 - 描述的行为与标题类似,但它允许更多内容在其中 - 列表和其他标题。
- 如果您尝试选择整个简单框实例并按下Delete,它将作为一个整体被删除。复制粘贴它时也是如此。这是因为它在模式中被标记为
isObject
元素。 - 您无法通过单击轻松地选择整个简单框实例。此外,当您将鼠标悬停在它上面时,光标指针不会改变。换句话说,它看起来有点“死”了。这是因为您还没有定义视图行为。
到目前为止,非常棒,对吧?只需少量代码,您就可以定义简单框插件的行为,该插件可以维护这些元素的完整性。引擎确保用户不会破坏这些实例。
看看您还能改进什么。
# 将简单框转换为小部件
在 CKEditor 5 中,小部件系统主要由引擎处理。其中一些包含在 (@ckeditor/ckeditor5-widget
) 包中,而其他一些则必须由 CKEditor 5 框架提供的其他实用程序处理。
因此,CKEditor 5 的实现对扩展和重新组合是开放的。您可以选择您想要的行为(就像您在本教程中通过定义模式所做的那样),并跳过其他行为或自己实现它们。
您定义的转换器将模型<simpleBox*>
元素转换为视图中的普通 ContainerElement
(以及在向上转换期间返回)。
您想稍微更改此行为,以便在编辑视图中创建的结构使用 toWidget()
和 toWidgetEditable()
实用程序进行增强。但是,您不想影响数据视图。因此,您需要分别为编辑和数据向下转换定义转换器。
如果您发现向下转换和向上转换的概念令人困惑,请阅读 转换简介。
现在是时候重新审视您之前定义的 _defineConverters()
方法了。您将使用 elementToElement()
向上转换助手 和 elementToElement()
向下转换助手,而不是双向 elementToElement()
转换器助手。
此外,您需要确保 Widget
插件已加载。如果您省略它,视图中的元素将具有所有类(如 ck-widget
),但不会加载任何“行为”(例如,单击小部件不会选择它)。
如您所见,代码变得更加冗长且更长。这是因为您使用了较低级别的转换器。我们计划在将来提供更多方便的小部件转换实用程序。阅读更多信息(和 👍) 此票证。
# 将简单框转换为小部件后的行为
现在,您应该看到您的简单框插件发生了什么变化。
您应该观察到
<section>
、<h1>
和<div>
元素具有contentEditable
属性(以及一些类)。此属性告诉浏览器元素是否被视为可编辑的。将元素通过toWidget()
传递将使其内容不可编辑。相反,通过toWidgetEditable()
传递它将使其内容再次可编辑。- 您现在可以单击小部件(灰色区域)来选择它。一旦选中,它就更容易进行复制粘贴。
- 小部件及其嵌套的可编辑区域对悬停、选择和焦点(轮廓)做出反应。
换句话说,简单框实例变得更具响应性。
此外,如果您调用 editor.getData()
,您将获得与将简单框转换为小部件之前相同的 HTML。这是由于仅在 editingDowncast
管道中使用 toWidget()
和 toNestedEditable()
。
现在,您只需要从模型和视图层中获得这些。就可编辑性和数据输入/输出而言,它完全可以工作。现在找到一种方法将新的简单框插入文档!
# 创建命令
一个 命令 是一个动作和一个状态的组合。您可以通过它们公开的命令与大多数编辑器功能进行交互。这不仅允许执行这些功能(例如,使文本片段变为粗体),还可以检查此操作是否可以在选择的当前位置执行,以及观察其他状态属性(例如,当前选定的文本是否已变为粗体)。
对于简单框,情况很简单
- 您需要一个“插入新简单框”动作。
- 您需要一个“您可以在此处(在当前选择位置)插入新的简单框”检查。
在 simplebox/
目录中创建一个名为 insertsimpleboxcommand.js
的新文件。您将使用 model.insertObject()
方法,该方法将能够(例如,如果您尝试在段落的中间插入一个简单框,则拆分段落(这在模式中不允许)。
导入命令并在 SimpleBoxEditing
插件中注册它
您现在可以执行此命令以插入一个新的简单框。调用
它应该导致
您也可以尝试检查 isEnabled
属性值(或只是在 CKEditor 5 检查器中检查它)
它始终为 true
,除非选择在一个地方 - 在另一个简单框的标题中。您还可以观察到,当选择在该位置时执行命令不会产生任何影响。
在您继续前进之前,再更改一件事 - 也不允许 simpleBox
在 simpleBoxDescription
中。这可以通过 定义自定义子级检查 来完成
现在,当选择在另一个简单框实例的描述中时,命令也应该被禁用。
# 创建按钮
现在该允许编辑器用户将小部件插入内容中了。最好的方法是通过工具栏中的 UI 按钮。您可以使用 ButtonView
类(由 CKEditor 5 的 UI 框架 提供)快速创建一个。
按钮应该在单击时执行 命令,如果小部件无法插入选择的某个特定位置(如模式中所定义),则按钮将变为非活动状态。
看看它在实践中的样子,并扩展之前 创建的 SimpleBoxUI
插件
您需要做的最后一件事是告诉编辑器在工具栏中显示按钮。为此,您需要稍微修改运行编辑器实例的代码,并将按钮包含在 工具栏配置 中
刷新网页并自己尝试一下
# 演示
您可以在下面的编辑器中看到块小部件的实现。如果您想开发自己的块小部件,您还可以查看本教程的完整 源代码。
# 最终解决方案
如果您在教程中的任何地方迷路了,或者想直接获得解决方案,则有一个包含 最终项目 的存储库。
我们每天都在努力使我们的文档保持完整。您是否发现过时信息?是否缺少什么东西?请通过我们的 问题跟踪器 报告。
随着 42.0.0 版的发布,我们重新编写了我们的许多文档以反映新的导入路径和功能。感谢您的反馈,帮助我们确保文档的准确性和完整性。