Procházet zdrojové kódy

Added: Introduced Mapper#registerViewToModelLength.

Szymon Cofalik před 9 roky
rodič
revize
03c9b666c4

+ 36 - 1
packages/ckeditor5-engine/src/conversion/mapper.js

@@ -215,6 +215,39 @@ export default class Mapper {
 	}
 
 	/**
+	 * Registers a callback that evaluates the length in the model of a view element with given name.
+	 *
+	 * The callback is fired with one argument, which is a view element instance. The callback is expected to return
+	 * a number representing the length of view element in model.
+	 *
+	 *		// List item in view may contain nested list, which have other list items. In model though,
+	 *		// the lists are represented by flat structure. Because of those differences, length of list view element
+	 *		// may be greater than one. In the callback it's checked how many nested list items are in evaluated list item.
+	 *
+	 *		function getViewListItemLength( element ) {
+	 *			let length = 1;
+	 *
+	 *			for ( let child of element.getChildren() ) {
+	 *				if ( child.name == 'ul' || child.name == 'ol' ) {
+	 *					for ( let item of child.getChildren() ) {
+	 *						length += getViewListItemLength( item );
+	 *					}
+	 *				}
+	 *			}
+	 *
+	 *			return length;
+	 *		}
+	 *
+	 *		mapper.registerViewToModelLength( 'li', getViewListItemLength );
+	 *
+	 * @param {String} viewElementName Name of view element for which callback is registered.
+	 * @param {Function} lengthCallback Function return a length of view element instance in model.
+	 */
+	registerViewToModelLength( viewElementName, lengthCallback ) {
+		this._viewToModelLengthCallbacks.set( viewElementName, lengthCallback );
+	}
+
+	/**
 	 * Calculates model offset based on the view position and the block element.
 	 *
 	 * Example:
@@ -279,7 +312,9 @@ export default class Mapper {
 	 */
 	_getModelLength( viewNode ) {
 		if ( this._viewToModelMapping.has( viewNode ) ) {
-			return viewNode.modelLength;
+			const callback = this._viewToModelLengthCallbacks.get( viewNode.name );
+
+			return callback ? callback( viewNode ) : 1;
 		} else if ( viewNode instanceof ViewText ) {
 			return viewNode.data.length;
 		} else {

+ 0 - 11
packages/ckeditor5-engine/src/view/element.js

@@ -123,17 +123,6 @@ export default class Element extends Node {
 	}
 
 	/**
-	 * Length of this view element in model if it is mapped. This value can be overwritten in classes extending
-	 * {@link engine.view.Element Element} to provide correct position mapping in {@link engine.conversion.Mapper Mapper}.
-	 *
-	 * @readonly
-	 * @type {Number}
-	 */
-	get modelLength() {
-		return 1;
-	}
-
-	/**
 	 * Clones provided element.
 	 *
 	 * @param {Boolean} deep If set to `true` clones element and all its children recursively. When set to `false`,

+ 70 - 0
packages/ckeditor5-engine/tests/conversion/mapper.js

@@ -470,4 +470,74 @@ describe( 'Mapper', () => {
 			it( 'should transform modelCaption 3', () => createToViewTest( modelCaption, 3, viewTextFOO, 3 ) );
 		} );
 	} );
+
+	describe( 'List mapping (test registerViewToModelLengthCallback)', () => {
+		function createToModelTest( viewElement, viewOffset, modelElement, modelOffset ) {
+			const viewPosition = new ViewPosition( viewElement, viewOffset );
+			const modelPosition = mapper.toModelPosition( viewPosition );
+			expect( modelPosition.parent ).to.equal( modelElement );
+			expect( modelPosition.offset ).to.equal( modelOffset );
+		}
+
+		let mapper;
+
+		let modelRoot;
+		let modelListItem1, modelListItem2;
+		let modelListItem11, modelListItem12;
+
+		let viewRoot;
+		let viewList, viewListNested;
+		let viewListItem1, viewListItem2;
+		let viewListItem11, viewListItem12;
+
+		before( () => {
+			modelListItem1 = new ModelElement( 'listItem', null, new ModelText( 'aaa' ) );
+			modelListItem11 = new ModelElement( 'listItem', null, new ModelText( 'bbb' ) );
+			modelListItem12 = new ModelElement( 'listItem', null, new ModelText( 'ccc' ) );
+			modelListItem2 = new ModelElement( 'listItem', null, new ModelText( 'xxx' ) );
+
+			modelRoot = new ModelRootElement( 'root', null, [ modelListItem1, modelListItem11, modelListItem12, modelListItem2 ] );
+
+			viewListItem11 = new ViewElement( 'li', null, new ViewText( 'bbb' ) );
+			viewListItem12 = new ViewElement( 'li', null, new ViewText( 'ccc' ) );
+			viewListNested = new ViewElement( 'ul', null, [ viewListItem11, viewListItem12 ] );
+
+			viewListItem1 = new ViewElement( 'li', null, [ new ViewText( 'aaa' ), viewListNested ] );
+			viewListItem2 = new ViewElement( 'li', null, new ViewText( 'ddd' ) );
+			viewList = new ViewElement( 'ul', null, [ viewListItem1, viewListItem2 ] );
+
+			viewRoot = new ViewElement( 'div', null, viewList );
+
+			mapper = new Mapper();
+			mapper.bindElements( modelRoot, viewRoot );
+			mapper.bindElements( modelListItem1, viewListItem1 );
+			mapper.bindElements( modelListItem11, viewListItem11 );
+			mapper.bindElements( modelListItem12, viewListItem12 );
+			mapper.bindElements( modelListItem2, viewListItem2 );
+
+			function getViewListItemLength( element ) {
+				let length = 1;
+
+				for ( let child of element.getChildren() ) {
+					if ( child.name == 'ul' || child.name == 'ol' ) {
+						for ( let item of child.getChildren() ) {
+							length += getViewListItemLength( item );
+						}
+					}
+				}
+
+				return length;
+			}
+
+			mapper.registerViewToModelLength( 'li', getViewListItemLength );
+		} );
+
+		describe( 'toModelPosition', () => {
+			it( 'should transform viewRoot 0', () => createToModelTest( viewRoot, 0, modelRoot, 0 ) );
+			it( 'should transform viewRoot 1', () => createToModelTest( viewRoot, 1, modelRoot, 4 ) );
+			it( 'should transform viewList 0', () => createToModelTest( viewList, 0, modelRoot, 0 ) );
+			it( 'should transform viewList 1', () => createToModelTest( viewList, 1, modelRoot, 3 ) );
+			it( 'should transform viewList 2', () => createToModelTest( viewList, 2, modelRoot, 4 ) );
+		} );
+	} );
 } );