Переглянути джерело

Fixed: engine.treeModel.TextProxy were unconsumable because every instance taken from tree model is a different object.

Szymon Cofalik 9 роки тому
батько
коміт
c25ed50661

+ 102 - 3
packages/ckeditor5-engine/src/treecontroller/modelconsumable.js

@@ -5,6 +5,8 @@
 
 'use strict';
 
+import TextProxy from '../treemodel/textproxy.js';
+
 /**
  * Manages a list of consumable values for {@link engine.treeModel.Item model items}.
  *
@@ -50,7 +52,7 @@
  *		//   ├─ <img />
  *		//   └─ <caption>
  *		//       └─ foo
- *		modelConversionDispatcher.on( 'insert:element:image', ( evt, data, consumable, conversionApi ) => {
+ *		modelConversionDispatcher.on( 'insert:image', ( evt, data, consumable, conversionApi ) => {
  *			// First, consume the `image` element.
  *			consumable.consume( data.item, 'insert' );
  *
@@ -67,7 +69,7 @@
  *				// It will not be converted by other converters, but it's children (probably some text) will be.
  *				// Through mapping, converters for text will know where to insert contents of `modelCaption`.
  *				if ( consumable.consume( modelCaption, 'insert' ) ) {
- *					const viewCaption = new ViewElement( 'figuaption' );
+ *					const viewCaption = new ViewElement( 'figcaption' );
  *
  *					const viewImageHolder = new ViewElement( 'figure', null, [ viewImage, viewCaption ] );
  *
@@ -94,9 +96,22 @@ export default class ModelConsumable {
 		 * Contains list of consumable values.
 		 *
 		 * @private
-		 * @member {Map} engine.treeController.ModelConsumable#_consumable.
+		 * @member {Map} engine.treeController.ModelConsumable#_consumable
 		 */
 		this._consumable = new Map();
+
+		/**
+		 * For each {@link engine.treeModel.TextProxy} added to `ModelConsumable`, this registry holds parent
+		 * of that `TextProxy` and start and end indices of that `TextProxy`. This allows identification of `TextProxy`
+		 * instances that points to the same part of the model but are different instances. Each distinct `TextProxy`
+		 * is given unique `Symbol` which is then registered as consumable. This process is transparent for `ModelConsumable`
+		 * API user because whenever `TextProxy` is added, tested, consumed or reverted, internal mechanisms of
+		 * `ModelConsumable` translates `TextProxy` to that unique `Symbol`.
+		 *
+		 * @private
+		 * @member {Map} engine.treeController.ModelConsumable#_textParentsRegistry
+		 */
+		this._textProxyRegistry = new Map();
 	}
 
 	/**
@@ -110,6 +125,10 @@ export default class ModelConsumable {
 	 * @param {String} type Consumable type.
 	 */
 	add( item, type ) {
+		if ( item instanceof TextProxy ) {
+			item = this._getSymbolForTextProxy( item );
+		}
+
 		if ( !this._consumable.has( item ) ) {
 			this._consumable.set( item, new Map() );
 		}
@@ -129,6 +148,10 @@ export default class ModelConsumable {
 	 * @returns {Boolean} `true` if consumable value was available and was consumed, `false` otherwise.
 	 */
 	consume( item, type ) {
+		if ( item instanceof TextProxy ) {
+			item = this._getSymbolForTextProxy( item );
+		}
+
 		if ( this.test( item, type ) ) {
 			this._consumable.get( item ).set( type, false );
 
@@ -151,6 +174,10 @@ export default class ModelConsumable {
 	 * already consumed or `true` if it was added and not consumed yet.
 	 */
 	test( item, type ) {
+		if ( item instanceof TextProxy ) {
+			item = this._getSymbolForTextProxy( item );
+		}
+
 		const itemConsumables = this._consumable.get( item );
 
 		if ( itemConsumables === undefined ) {
@@ -179,6 +206,10 @@ export default class ModelConsumable {
 	 * never been added.
 	 */
 	revert( item, type ) {
+		if ( item instanceof TextProxy ) {
+			item = this._getSymbolForTextProxy( item );
+		}
+
 		const test = this.test( item, type );
 
 		if ( test === false ) {
@@ -191,4 +222,72 @@ export default class ModelConsumable {
 
 		return null;
 	}
+
+	/**
+	 * Gets a unique symbol for passed {@link engine.treeModel.TextProxy} instance. All `TextProxy` instances that
+	 * have same parent, same start index and same end index will get the same symbol.
+	 *
+	 * Used internally to correctly consume `TextProxy` instances.
+	 *
+	 * @private
+	 * @param {engine.treeModel.TextProxy} textProxy `TextProxy` instance to get a symbol for.
+	 * @returns {Symbol} Symbol representing all equal instances of `TextProxy`.
+	 */
+	_getSymbolForTextProxy( textProxy ) {
+		let symbol = null;
+
+		const startIndex = textProxy.first.getIndex();
+		const endIndex = startIndex + textProxy.text.length;
+		const parent = textProxy.commonParent;
+
+		const startIndexMap = this._textProxyRegistry.get( startIndex );
+
+		if ( startIndexMap ) {
+			const endIndexMap = startIndexMap.get( endIndex );
+
+			if ( endIndexMap ) {
+				symbol = endIndexMap.get( parent );
+			}
+		}
+
+		if ( !symbol ) {
+			symbol = this._addSymbolForTextProxy( startIndex, endIndex, parent );
+		}
+
+		return symbol;
+	}
+
+	/**
+	 * Adds a symbol for given properties that characterizes a {@link engine.treeModel.TextProxy} instance.
+	 *
+	 * Used internally to correctly consume `TextProxy` instances.
+	 *
+	 * @private
+	 * @param {Number} startIndex Text proxy start index in it's parent.
+	 * @param {Number} endIndex Text proxy end index in it's parent.
+	 * @param {engine.treeModel.Element} parent Text proxy parent.
+	 * @returns {Symbol} Symbol generated for given properties.
+	 */
+	_addSymbolForTextProxy( startIndex, endIndex, parent ) {
+		const symbol = Symbol();
+		let startIndexMap, endIndexMap;
+
+		startIndexMap = this._textProxyRegistry.get( startIndex );
+
+		if ( !startIndexMap ) {
+			startIndexMap = new Map();
+			this._textProxyRegistry.set( startIndex, startIndexMap );
+		}
+
+		endIndexMap = startIndexMap.get( endIndex );
+
+		if ( !endIndexMap ) {
+			endIndexMap = new Map();
+			startIndexMap.set( endIndex, endIndexMap );
+		}
+
+		endIndexMap.set( parent, symbol );
+
+		return symbol;
+	}
 }