大家知道,在CKEditor5中,Conversion(转化器)是最重要的一个组件之一,为了深入的理解转化器,我们先从大的层面来掌握一下,以后再分别从细节入手。
我们从上面的图中不难看出,总的来说有三个converter,那么这三个converter在代码中具体在哪里呢?
我们在ckeditor5-engine包下的controller包中有两个类,而这两个类才是真正存放转化控制器的代码的地方:
这里我贴出代码:
datacontroller.js
this.downcastDispatcher = new DowncastDispatcher( {
mapper: this.mapper,
schema: model.schema
} );
this.downcastDispatcher.on( 'insert:$text', insertText(), { priority: 'lowest' } );
this.upcastDispatcher = new UpcastDispatcher( {
schema: model.schema
} );
this.upcastDispatcher.on( 'text', convertText(), { priority: 'lowest' } );
this.upcastDispatcher.on( 'element', convertToModelFragment(), { priority: 'lowest' } );
this.upcastDispatcher.on( 'documentFragment', convertToModelFragment(), { priority: 'lowest' } );
editingcontroller.js
this.downcastDispatcher = new DowncastDispatcher( {
mapper: this.mapper,
schema: model.schema
} );
// Attach default model converters.
this.downcastDispatcher.on( 'insert:$text', insertText(), { priority: 'lowest' } );
this.downcastDispatcher.on( 'remove', remove(), { priority: 'low' } );
// Attach default model selection converters.
this.downcastDispatcher.on( 'selection', clearAttributes(), { priority: 'high' } );
this.downcastDispatcher.on( 'selection', convertRangeSelection(), { priority: 'low' } );
this.downcastDispatcher.on( 'selection', convertCollapsedSelection(), { priority: 'low' } );
大家注意没有,在datacontroller(数据控制器)中又两个转化器,分别对应着向上和向下两个方向。在向下的方向,注册了一个插入文本模型节点的事件,一旦我们向我们的模型树中插入文本节点,那么就会执行insertText(),这个方法用于向数据视图插入数据。其实在editingcontroller(编辑控制器)中也有一个这样的事件,它对文本节点的插入和数据控制器的处理是一致的。
同时,在datacontroller中初始化了一个向上的转化器,它主要处理文本,元素和文档段的转化,将它们转化成模型或者模型的属性。
同理,在editingcontroller(编辑控制器)中还处理其他一些事件,比如移除事件,选择事件等,这些都是在模型节点有移除或者模型节点被选择的时候触发的模型转视图。
记住,我们这里可以暂时不用管具体是怎么处理的,只需要知道有三个转化器,每个转化器处理不同的情况。
有了上面的知识,我们对conversion的理解有近了一步,那么这里有个问题,conversion是editor的一个属性,那么这个属性是怎么初始化的呢?我在editor.js中找到如下代码:
this.data = new DataController( this.model, stylesProcessor );
this.editing = new EditingController( this.model, stylesProcessor );
this.conversion = new Conversion( [ this.editing.downcastDispatcher, this.data.downcastDispatcher ], this.data.upcastDispatcher );
this.conversion.addAlias( 'dataDowncast', this.data.downcastDispatcher );
this.conversion.addAlias( 'editingDowncast', this.editing.downcastDispatcher );
大家看到了吧,在editor初始化的时候,将数据控制器的两个转化器和编辑控制器的转化器作为构造参数传递到了conversion这个类中。下面我们看看conversion.js是怎么实现的:
conversion.js
export default class Conversion {
constructor( downcastDispatchers, upcastDispatchers ) {
this._helpers = new Map();
this._downcast = toArray( downcastDispatchers );
this._createConversionHelpers( { name: 'downcast', dispatchers: this._downcast, isDowncast: true } );
this._upcast = toArray( upcastDispatchers );
this._createConversionHelpers( { name: 'upcast', dispatchers: this._upcast, isDowncast: false } );
}
}
从这里的代码我们可以知道,这三个转化器都被传递到了Conversion这个类中,分别存储在_downcast和_upcast属性中,同时,我们看看这个私有方法:_createConversionHelpers()
_createConversionHelpers( { name, dispatchers, isDowncast } ) {
if ( this._helpers.has( name ) ) {
throw new CKEditorError( 'conversion-group-exists', this );
}
const helpers = isDowncast ? new DowncastHelpers( dispatchers ) : new UpcastHelpers( dispatchers );
this._helpers.set( name, helpers );
}
从上面我们可以看出,这个方法实际上就是将转化器包装成不同的helper后进行分组,一个组的名字叫做downcast,另一个叫做upcast。
一般我们在使用的时候调用方法都是:
editor.conversion.for( 'downcast' ).elementToElement( config ) );
我们知道editor.conversion.for( 'downcast' )
这个方法调用后返回的是DowncastHelpers
的实例,而这个类的elementToElement()
是这样的:
elementToElement( config ) {
return this.add( downcastElementToElement( config ) );
}
这里的add是父类的一个方法:
conversionhelpers.js
export default class ConversionHelpers {
constructor( dispatchers ) {
this._dispatchers = dispatchers;
}
add( conversionHelper ) {
for ( const dispatcher of this._dispatchers ) {
conversionHelper( dispatcher );
}
return this;
}
}
这里的add方法的参数是一个函数,且这个函数接收一个dispatcher作为参数,因此我们可以推测:
downcastElementToElement( config )
应该返回一个函数,同时这个函数的参数是dispatcher
下面我们看看这个方法是怎么实现的:
function downcastElementToElement( config ) {
config = cloneDeep( config );
config.view = normalizeToElementConfig( config.view, 'container' );
return dispatcher => {
dispatcher.on( 'insert:' + config.model, insertElement( config.view ), { priority: config.converterPriority || 'normal' } );
if ( config.triggerBy ) {
if ( config.triggerBy.attributes ) {
for ( const attributeKey of config.triggerBy.attributes ) {
dispatcher._mapReconversionTriggerEvent( config.model, `attribute:${ attributeKey }:${ config.model }` );
}
}
if ( config.triggerBy.children ) {
for ( const childName of config.triggerBy.children ) {
dispatcher._mapReconversionTriggerEvent( config.model, `insert:${ childName }` );
dispatcher._mapReconversionTriggerEvent( config.model, `remove:${ childName }` );
}
}
}
};
}
不出所料,这个方法的确返回一个函数,而这个函数的执行逻辑就是绑定当某个模型插入的时候,我们应该进行的模型转化视图的逻辑,同时还附加一些其他操作,这里的其他操作我们以后分析。当然,这里还有一个逻辑就是normalizeToElementConfig()
这个方法,它是对视图的一些处理。感兴趣的可以具体分析一下。好了,我在这里就大概分析了ck5的conversion的转化逻辑,感兴趣的可以一起讨论。