CKEditor5——Conversion理解

选中文字可对指定文章内容进行评论啦,→和←可快速切换按钮,绿色背景文字可以点击查看评论额。

大家知道,在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的转化逻辑,感兴趣的可以一起讨论。

 

版权声明:著作权归作者所有。

相关推荐

CKEditor5事件系统(代理事件)

emitter接口提供了事件代理机制。也就是说指定选择的事件能够被其他的emitter触发。 1、代理指定的事件到另一个emitterlet anyClass = new AnyClass(); let anotherClass = new AnyClass(); let oneClass = new AnyClass(); anotherClass.on('bar',(evt,data

RxJs——Subject理解二

上一节我们对subject有了初步理解,今天我们继续学习subjectReplaySubject介绍这个ReplaySubject之前,我们说下一种使用场景1、我们创建一个subject2、在应用的某个地方,我们向subject推送值,但此时没有订阅3、在某个时点,有第一个观察者开始订阅4、我们期望观察者能接收之前主题推送的值(可能是全部值或者时其中一个)5、实际上啥都没发生,因为主题不能存储记忆

CKEditor5——模型理解(一)

我们知道,CK5实现了一个MVC的架构,从今天开始,我们一步一步深入学习模型,视图,以及模型和视图之间的转换。今天我们开始模型的学习。首先,我们看模型的定义:The model is implemented by a DOM-like tree structure of elements and text nodes.模型由两类节点构成,分别是元素节点和文本节点,模型是一种类Dom树结构。我们知道

CKEditor5——模型理解(二:Node)

上一节我们理解了基本的CK5的模型基本信息,今天我们来学习一些模型的API。节点说明首先,需要理解的就是模型的节点。在这一点上,CK5的模型节点和dom的节点有点类似,也有一些不同。我会在文章中一一介绍。节点是模型树的基本结构。它是模型中不同节点类型的一种抽象。这里需要指出的一点是:如果一个节点从模型树中分离出来,你可以使用它的 API 来操作它。但是,非常重要的是,已经附加到模型树的节点只能通过

CKEditor5——模型理解(三:Element Text)

在上一节,我们学习了CK5中模型节点Node的API,今天我们学习另一个常用的API:Element。元素节点说明element表示模型的元素节点类型,它包含一个拥有名称和子节点的节点类型,继承自Node类。元素属性说明1、name,元素的名称举个例子哈,段落的名称是paragraph,代码块的名称是codeBlock等等。2、childCount, 子元素的数目这里指的是此元素节点包含的子元素的

javascript——generator理解

最近在学习CK5的时候,遇到了一个函数:function* _getAllUpcastDefinitions( definition ) { if ( definition.model.values ) { for ( const value of definition.model.values ) { const model = { key: definition.model.key

CKEditor5——模型理解(五:Position, Range, Selection)

今天我们继续学习CK5中模型的一些知识,主要包括:Position, Range, Selection首先,我们需要知道:position表示模型树中的一个位置。模型的位置有两部分组成:root,path。即位置由其根和该根中的路径表示。位置基于偏移量,而不是索引。这意味着两个文本节点 foo 和 bar 之间的位置偏移为 3,而不是 1。由于模型中的位置由位置根和位置路径表示,因此可以创建不存在

CKEditor5——模型理解(六:Range)

上一节我们主要介绍了模型中的Position这个关键的类,今天我们开始学习Range这个类。简单来说的话,如果Position表示一个点的话,那么Range是不是可以理解为一条线段呢?这个线段有一个startPostion,endPosition以及线段的长度等属性,我们暂且这么认为,那么我们可以看看Range官方的文档。从文档中看到,Range类有五个属性:Range属性start:Positi

CKEditor5——模型理解(七:Selection)

昨天我们学习了Range的一些API使用,今天我们看看另一个重要的类Selection的API:Selection的作用是记录鼠标在文档上的选择区域,如果是单个用户在编辑一份文档的时候,选择应该就是一个Range,如果是多个用户在编辑一份文档的时候,那么选择的区域就应该是多个range。因此,我大胆的猜测,Selection中应该有Range数组。我们来看看吧。Selection属性anchor

RxJs——Subscription理解

我們前段時間學習了Observable相關的知識,今天我們學習另一個重要的概念:Subscription。首先,我們看看Subscription官方文檔的介紹如下:What is a Subscription? A Subscription is an object that represents a disposable resource, usually the execution of an