核心编辑器架构
The @ckeditor/ckeditor5-core
包比较简单。它只包含几个类。您需要了解的类如下所示。
# 编辑器类
The Editor
类表示编辑器的基础。它是应用程序的入口点,将所有其他组件粘合在一起。它提供了一些您需要了解的属性。
config
– 配置对象。plugins
和commands
– 加载的插件和命令的集合。model
– 编辑器数据模型的入口点。data
– 数据控制器。它控制如何从文档中检索数据并将其设置在文档中。editing
– 编辑控制器。它控制如何将模型渲染给用户进行编辑。keystrokes
– 按键处理程序。它允许将按键绑定到操作。
除此之外,编辑器还公开了一些方法。
create()
– 静态create()
方法。编辑器构造函数是受保护的,您应该使用此静态方法创建编辑器。它允许初始化过程是异步的。destroy()
– 销毁编辑器。execute()
– 执行给定的命令。setData()
和getData()
– 从编辑器中检索数据和将数据设置到编辑器中的方法。数据格式由数据控制器的 数据处理器 控制。它不需要是字符串(例如,如果您实现了这样的 数据处理器,它可以是 JSON)。例如,请查看如何 生成 Markdown 输出。
有关方法的完整列表,请查看您使用的编辑器类的 API 文档。特定的编辑器实现可能提供其他方法。
The Editor
类是实现您自己的编辑器的基础。CKEditor 5 框架带有一些编辑器类型(例如,经典、内联、气球 和 解耦),但您可以自由地实现工作和外观完全不同的编辑器。唯一的要求是您必须实现 Editor
接口。
# 插件
插件是引入编辑器功能的一种方式。在 CKEditor 5 中,即使 输入 也是一个插件。更重要的是,Typing
插件依赖于 Input
和 Delete
插件,它们分别负责处理插入文本和删除内容的方法。同时,一些插件需要在某些情况下自定义 Backspace 行为并自行处理。这使得基本插件无需任何非通用知识。
现有 CKEditor 5 插件的实现方式的另一个重要方面是将引擎和 UI 部分分离。例如,BoldEditing
插件引入了模式定义、渲染 <strong>
标签的机制、应用和删除文本粗体的命令,而 BoldUI
插件添加了该功能的 UI(即按钮)。此功能分离旨在实现更大的重用(可以采用引擎部分并为功能实现自己的 UI),以及在服务器端运行 CKEditor 5。最后,有 Bold
插件,它为完整的体验带来了这两个插件。
总结一下,
- 每个功能都是由插件实现或至少启用的。
- 插件非常细粒度。
- 插件了解编辑器的所有内容。
- 插件应该尽可能少地了解其他插件。
这些是实现官方插件所基于的规则。在实现自己的插件时,如果您不打算发布它们,可以将此列表缩减到第一点。
在进行了冗长的介绍(旨在让您更容易理解现有插件)之后,可以解释插件 API。
所有插件都需要实现 PluginInterface
。最简单的方法是继承自 Plugin
类。插件初始化代码应位于 init()
方法中(该方法可以返回一个 promise)。如果某些代码需要在其他插件初始化后执行,可以将其放在 afterInit()
方法中。插件之间的依赖关系是使用静态 requires
属性实现的。
import MyDependency from 'some/other/plugin';
class MyPlugin extends Plugin {
static get requires() {
return [ MyDependency ];
}
init() {
// Initialize your plugin here.
this.editor; // The editor instance which loaded this plugin.
}
}
您可以在 分步教程 中看到如何实现简单的插件。
# 命令
命令是动作(回调)和状态(一组属性)的组合。例如,bold
命令对选定文本应用或删除粗体属性。如果放置选定内容的文本已经应用了粗体,则该命令的值为 true
,否则为 false
。如果 bold
命令可以在当前选定内容上执行,则它处于启用状态。如果不是(例如,由于此处的粗体不允许),则它处于禁用状态。
我们建议您使用官方 CKEditor 5 检查器 进行开发和调试。它将为您提供有关编辑器状态的大量有用信息,例如内部数据结构、选定内容、命令等等。
所有命令都需要继承自 Command
类。命令需要添加到编辑器的 命令集合 中,以便可以使用 Editor#execute()
方法执行它们。
举个例子
class MyCommand extends Command {
execute( message ) {
console.log( message );
}
}
class MyPlugin extends Plugin {
init() {
const editor = this.editor;
editor.commands.add( 'myCommand', new MyCommand( editor ) );
}
}
调用 editor.execute( 'myCommand', 'Foo!' )
将在控制台中记录 Foo!
。
要查看典型命令(如 bold
)的状态管理是如何实现的,请查看 AttributeCommand
类的部分代码,该类是 bold
的基础。
首先要注意的是 refresh()
方法。
refresh() {
const doc = this.editor.document;
this.value = doc.selection.hasAttribute( this.attributeKey );
this.isEnabled = doc.schema.checkAttributeInSelection(
doc.selection, this.attributeKey
);
}
当 对模型应用任何更改 时,此方法会自动(由命令本身)调用。这意味着,只要编辑器中的任何内容发生更改,该命令就会自动刷新其自身状态。
命令的重要一点是,它们的每个状态更改以及调用 execute()
方法都会触发事件。这些事件的一些示例包括 #set:value
和 #change:value
(当您更改 #value
属性时)以及 #execute
(当您执行该命令时)。
在 可观察对象 深入研究指南中阅读有关此机制的更多信息。
这些事件使得可以从外部控制命令。例如,如果您想在某些条件为真时阻止特定命令(例如,根据您的应用程序逻辑,它们应该暂时不可用),并且没有其他更干净的方法,则可以手动阻止该命令。
function disableCommand( cmd ) {
cmd.on( 'set:isEnabled', forceDisable, { priority: 'highest' } );
cmd.isEnabled = false;
// Make it possible to enable the command again.
return () => {
cmd.off( 'set:isEnabled', forceDisable );
cmd.refresh();
};
function forceDisable( evt ) {
evt.return = false;
evt.stop();
}
}
// Usage:
// Disabling the command.
const enableBold = disableCommand( editor.commands.get( 'bold' ) );
// Enabling the command again.
enableBold();
只要您不 关闭 此侦听器,该命令就会被阻止,而不管 someCommand.refresh()
调用了多少次。
默认情况下,当编辑器处于 只读 模式时,编辑器命令会被阻止。但是,如果您的命令不会更改编辑器数据,并且您希望它在只读模式下保持启用状态,则可以将 affectsData
标志设置为 false
。
class MyAlwaysEnabledCommand extends Command {
constructor( editor ) {
super( editor );
// This command will remain enabled even when the editor is read-only.
this.affectsData = false;
}
}
The affectsData
标志也会影响 其他限制用户写入权限的编辑器模式 中的命令。
默认情况下,affectsData
标志对所有编辑器命令都设置为 true
,并且除非您的命令应该在编辑器处于只读模式时启用,否则您无需更改它。该标志在编辑器的整个生命周期中是不可变的。
# 事件系统和可观察对象
CKEditor 5 具有基于事件的架构,因此您可以在任何地方找到 Emitter
和 Observable
的混合。这两种机制都允许代码解耦并使其可扩展。
大多数已经提到的类要么是发射器,要么是可观察对象(可观察对象也是发射器)。发射器可以发射(触发)事件以及监听事件。
class MyPlugin extends Plugin {
init() {
// Make MyPlugin listen to someCommand#execute.
this.listenTo( someCommand, 'execute', () => {
console.log( 'someCommand was executed' );
} );
// Make MyPlugin listen to someOtherCommand#execute and block it.
// You listen with a high priority to block the event before
// someOtherCommand's execute() method is called.
this.listenTo( someOtherCommand, 'execute', evt => {
evt.stop();
}, { priority: 'high' } );
}
// Inherited from Plugin:
destroy() {
// Removes all listeners added with this.listenTo();
this.stopListening();
}
}
第二个监听'execute'
的监听器展示了 CKEditor 5 代码中常见的做法之一。基本上,'execute'
的默认操作(即调用execute()
方法)被注册为该事件的监听器,并具有默认优先级。因此,通过使用'low'
或'high'
优先级监听事件,您可以在execute()
真正被调用之前或之后执行一些代码。如果您停止了事件,那么execute()
方法将完全不会被调用。在本例中,Command#execute()
方法使用ObservableMixin#decorate()
函数用该事件进行了装饰。
import { ObservableMixin, mix } from 'ckeditor5';
class Command {
constructor() {
this.decorate( 'execute' );
}
// Will now fire the #execute event automatically.
execute() {}
}
// Mix ObservableMixin into Command.
mix( Command, ObservableMixin );
除了用事件装饰方法之外,可观察对象还允许观察其选定的属性。例如,Command
类通过调用set()
使它的#value
和#isEnabled
可观察。
class Command {
constructor() {
this.set( 'value', undefined );
this.set( 'isEnabled', undefined );
}
}
mix( Command, ObservableMixin );
const command = new Command();
command.on( 'change:value', ( evt, propertyName, newValue, oldValue ) => {
console.log(
`${ propertyName } has changed from ${ oldValue } to ${ newValue }`
);
} )
command.value = true; // -> 'value has changed from undefined to true'
可观察对象还有一个广泛用于编辑器(尤其是在 UI 库中)的功能——能够将一个对象属性的值绑定到其他属性的值(一个或多个对象)。当然,这也可以通过回调来处理。
假设target
和source
是可观察对象,并且使用的属性是可观察的
target.bind( 'foo' ).to( source );
source.foo = 1;
target.foo; // -> 1
// Or:
target.bind( 'foo' ).to( source, 'bar' );
source.bar = 1;
target.foo; // -> 1
您还可以在UI 库架构指南中找到有关用户界面中数据绑定的更多信息。
# 下一步阅读
在您了解了如何创建插件和命令之后,您可以阅读编辑引擎指南,了解如何实现真正的编辑功能。
我们每天都在努力使我们的文档保持完整。您是否发现过时信息?是否缺少什么内容?请通过我们的问题追踪器进行报告。
随着 42.0.0 版本的发布,我们重写了大部分文档以反映新的导入路径和功能。我们感谢您的反馈,以帮助我们确保其准确性和完整性。