Răsfoiți Sursa

Added MoveDelta.

Szymon Cofalik 10 ani în urmă
părinte
comite
d888a8d598

+ 75 - 0
packages/ckeditor5-engine/src/treemodel/delta/movedelta.js

@@ -0,0 +1,75 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+'use strict';
+
+CKEDITOR.define( [
+	'treemodel/delta/delta',
+	'treemodel/delta/register',
+	'treemodel/operation/moveoperation',
+	'treemodel/position'
+], ( Delta, register, MoveOperation, Position ) => {
+	/**
+	 * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#move} method
+	 * uses the `MoveDelta` class which inherits from the `Delta` class and may overwrite some methods.
+	 *
+	 * @class treeModel.delta.MoveDelta
+	 */
+	class MoveDelta extends Delta {}
+
+	function addMoveOperation( batch, delta, sourcePosition, howMany, targetPosition ) {
+		const operation = new MoveOperation( sourcePosition, howMany, targetPosition, batch.doc.version );
+		batch.doc.applyOperation( operation );
+		delta.addOperation( operation );
+	}
+
+	/**
+	 * Moves given node to given target position. Use this only when you have to move one node (it may contain children).
+	 * If you want to move multiple nodes, use {@link treeModel.Batch#move move} instead.
+	 *
+	 * @chainable
+	 * @method moveNode
+	 * @memberOf treeModel.Batch
+	 * @param {treeModel.Node} node Node to move.
+	 * @param {treeModel.Position} targetPosition Position where moved nodes will be inserted.
+	 */
+	register( 'moveNode', function( node, targetPosition ) {
+		const delta = new MoveDelta();
+
+		addMoveOperation( this, delta, Position.createBefore( node ), 1, targetPosition );
+
+		this.addDelta( delta );
+
+		return this;
+	} );
+
+	/**
+	 * Moves given range of nodes to target position.
+	 *
+	 * @chainable
+	 * @method move
+	 * @memberOf treeModel.Batch
+	 * @param {treeModel.Range} range Range to move.
+	 * @param {treeModel.Position} targetPosition Position where moved nodes will be inserted.
+	 */
+	register( 'move', function( range, targetPosition ) {
+		const delta = new MoveDelta();
+
+		// The array is reversed, so the furthest ranges get moved first.
+		// This will keep the order of nodes in ranges correct.
+		// It's like using .pop() + .unshift() when moving elements between arrays.
+		let ranges = range.getMinimalFlatRanges().reverse();
+
+		for ( let flat of ranges ) {
+			addMoveOperation( this, delta, flat.start, flat.end.offset - flat.start.offset, targetPosition );
+		}
+
+		this.addDelta( delta );
+
+		return this;
+	} );
+
+	return MoveDelta;
+} );

+ 99 - 0
packages/ckeditor5-engine/tests/treemodel/delta/movedelta.js

@@ -0,0 +1,99 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: treemodel, delta */
+
+'use strict';
+
+const modules = bender.amd.require(
+	'treemodel/document',
+	'treemodel/position',
+	'treemodel/range',
+	'treemodel/element'
+);
+
+describe( 'Batch', () => {
+	let Document, Position, Range, Element;
+
+	let doc, root, div, p, batch, chain;
+
+	before( () => {
+		Document = modules[ 'treemodel/document' ];
+		Position = modules[ 'treemodel/position' ];
+		Range = modules[ 'treemodel/range' ];
+		Element = modules[ 'treemodel/element' ];
+	} );
+
+	beforeEach( () => {
+		doc = new Document();
+		root = doc.createRoot( 'root' );
+
+		div = new Element( 'div', [], 'foobar' );
+		p = new Element( 'p', [], 'abcxyz' );
+
+		root.insertChildren( 0, [ div, p ] );
+
+		batch = doc.batch();
+	} );
+
+	function getNodesAndText( element ) {
+		let range = Range.createFromElement( element );
+		let txt = '';
+
+		for ( let step of range ) {
+			let node = step.node;
+
+			if ( node.character ) {
+				txt += node.character.toLowerCase();
+			} else if ( node.name ) {
+				txt += node.name.toUpperCase();
+			}
+		}
+
+		return txt;
+	}
+
+	describe( 'moveNode', () => {
+		beforeEach( () => {
+			chain = batch.moveNode( div, new Position( root, [ 2 ] ) );
+		} );
+
+		it( 'should move specified node', () => {
+			expect( root.getChildCount() ).to.equal( 2 );
+			expect( getNodesAndText( root.getChild( 0 ) ) ).to.equal( 'abcxyz' );
+			expect( getNodesAndText( root.getChild( 1 ) ) ).to.equal( 'foobar' );
+		} );
+
+		it( 'should be chainable', () => {
+			expect( chain ).to.equal( batch );
+		} );
+	} );
+
+	describe( 'move', () => {
+		beforeEach( () => {
+			div.insertChildren( 4, [ new Element( 'p', [], 'gggg' ) ] );
+			div.insertChildren( 2, [ new Element( 'p', [], 'hhhh' ) ] );
+
+			// Range starts in ROOT > DIV > P > gg|gg.
+			// Range ends in ROOT > DIV > ...|ar.
+			let range = new Range( new Position( root, [ 0, 2, 2 ] ), new Position( root, [ 0, 6 ] ) );
+			chain = batch.move( range, new Position( root, [ 1, 3 ] ) );
+		} );
+
+		it( 'should move any range of nodes', () => {
+			expect( getNodesAndText( root.getChild( 0 ) ) ).to.equal( 'foPhhPar' );
+			expect( getNodesAndText( root.getChild( 1 ) ) ).to.equal( 'abchhobPggggPxyz' );
+		} );
+
+		it( 'should create minimal number of operations', () => {
+			expect( batch.deltas.length ).to.equal( 1 );
+			expect( batch.deltas[ 0 ].operations.length ).to.equal( 2 );
+		} );
+
+		it( 'should be chainable', () => {
+			expect( chain ).to.equal( batch );
+		} );
+	} );
+} );