Bläddra i källkod

Added WrapDelta.

Szymon Cofalik 10 år sedan
förälder
incheckning
4099775616

+ 2 - 1
packages/ckeditor5-engine/src/treemodel/batch.js

@@ -17,7 +17,8 @@ CKEDITOR.define( [
 	'treemodel/delta/removedelta',
 	'treemodel/delta/attributedelta',
 	'treemodel/delta/splitdelta',
-	'treemodel/delta/mergedelta'
+	'treemodel/delta/mergedelta',
+	'treemodel/delta/wrapdelta'
 ], ( Batch ) => {
 	return Batch;
 } );

+ 82 - 0
packages/ckeditor5-engine/src/treemodel/delta/wrapdelta.js

@@ -0,0 +1,82 @@
+/**
+ * @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/position',
+	'treemodel/element',
+	'treemodel/operation/insertoperation',
+	'treemodel/operation/moveoperation',
+	'ckeditorerror'
+], ( Delta, register, Position, Element, InsertOperation, MoveOperation, CKEditorError ) => {
+	/**
+	 * To provide specific OT behavior and better collisions solving, {@link treeModel.Batch#merge} method
+	 * uses the `WrapDelta` class which inherits from the `Delta` class and may overwrite some methods.
+	 *
+	 * @class treeModel.delta.WrapDelta
+	 */
+	class WrapDelta extends Delta {}
+
+	/**
+	 * Wraps given range with given element or with a new element of specified name if string has been passed.
+	 * **Note:** given range should be a "flat range" (see {@link treeModel.Range#isFlat}). If not, error will be thrown.
+	 *
+	 * @chainable
+	 * @method wrap
+	 * @memberOf treeModel.Batch
+	 * @param {treeModel.Range} range Range to wrap.
+	 * @param {treeModel.Element|String} elementOrString Element or name of element to wrap the range with.
+	 */
+	register( 'wrap', function( range, elementOrString ) {
+		if ( !range.isFlat ) {
+			/**
+			 * Range to wrap is not flat.
+			 *
+			 * @error batch-wrap-range-not-flat
+			 */
+			throw new CKEditorError( 'batch-wrap-range-not-flat: Range to wrap is not flat.' );
+		}
+
+		let element = elementOrString instanceof Element ? elementOrString : new Element( elementOrString );
+
+		if ( element.getChildCount() > 0 ) {
+			/**
+			 * Element to wrap with is not empty.
+			 *
+			 * @error batch-wrap-element-not-empty
+			 */
+			throw new CKEditorError( 'batch-wrap-element-not-empty: Element to wrap with is not empty.' );
+		}
+
+		if ( element.parent !== null ) {
+			/**
+			 * Element to wrap with is already attached to a tree model.
+			 *
+			 * @error batch-wrap-element-attached
+			 */
+			throw new CKEditorError( 'batch-wrap-element-attached: Element to wrap with is already attached to tree model.' );
+		}
+
+		const delta = new WrapDelta();
+
+		let insert = new InsertOperation( range.end, element, this.doc.version );
+		this.doc.applyOperation( insert );
+		delta.addOperation( insert );
+
+		let targetPosition = Position.createFromParentAndOffset( element, 0 );
+		let move = new MoveOperation( range.start, range.end.offset - range.start.offset, targetPosition, this.doc.version );
+		this.doc.applyOperation( move );
+		delta.addOperation( move );
+
+		this.addDelta( delta );
+
+		return this;
+	} );
+
+	return WrapDelta;
+} );

+ 103 - 0
packages/ckeditor5-engine/tests/treemodel/delta/wrapdelta.js

@@ -0,0 +1,103 @@
+/**
+ * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* bender-tags: treemodel, delta */
+
+/* bender-include: ../../_tools/tools.js */
+
+'use strict';
+
+const modules = bender.amd.require(
+	'treemodel/document',
+	'treemodel/position',
+	'treemodel/range',
+	'treemodel/element',
+	'ckeditorerror'
+);
+
+describe( 'Batch', () => {
+	let Document, Position, Range, Element, CKEditorError;
+
+	let doc, root, range;
+
+	before( () => {
+		Document = modules[ 'treemodel/document' ];
+		Position = modules[ 'treemodel/position' ];
+		Range = modules[ 'treemodel/range' ];
+		Element = modules[ 'treemodel/element' ];
+		CKEditorError = modules.ckeditorerror;
+	} );
+
+	beforeEach( () => {
+		doc = new Document();
+		root = doc.createRoot( 'root' );
+
+		root.insertChildren( 0, 'foobar' );
+
+		range = new Range( new Position( root, [ 2 ] ), new Position( root, [ 4 ] ) );
+	} );
+
+	describe( 'wrap', () => {
+		it( 'should wrap flat range with given element', () => {
+			let p = new Element( 'p' );
+			doc.batch().wrap( range, p );
+
+			expect( root.getChildCount() ).to.equal( 5 );
+			expect( root.getChild( 0 ).character ).to.equal( 'f' );
+			expect( root.getChild( 1 ).character ).to.equal( 'o' );
+			expect( root.getChild( 2 ) ).to.equal( p );
+			expect( p.getChild( 0 ).character ).to.equal( 'o' );
+			expect( p.getChild( 1 ).character ).to.equal( 'b' );
+			expect( root.getChild( 3 ).character ).to.equal( 'a' );
+			expect( root.getChild( 4 ).character ).to.equal( 'r' );
+		} );
+
+		it( 'should wrap flat range with an element of given name', () => {
+			doc.batch().wrap( range, 'p' );
+
+			expect( root.getChildCount() ).to.equal( 5 );
+			expect( root.getChild( 0 ).character ).to.equal( 'f' );
+			expect( root.getChild( 1 ).character ).to.equal( 'o' );
+			expect( root.getChild( 2 ).name ).to.equal( 'p' );
+			expect( root.getChild( 2 ).getChild( 0 ).character ).to.equal( 'o' );
+			expect( root.getChild( 2 ).getChild( 1 ).character ).to.equal( 'b' );
+			expect( root.getChild( 3 ).character ).to.equal( 'a' );
+			expect( root.getChild( 4 ).character ).to.equal( 'r' );
+		} );
+
+		it( 'should throw if range to wrap is not flat', () => {
+			root.insertChildren( 6, [ new Element( 'p', [], 'xyz' ) ] );
+			let notFlatRange = new Range( new Position( root, [ 3 ] ), new Position( root, [ 6, 2 ] ) );
+
+			expect( () => {
+				doc.batch().wrap( notFlatRange, 'p' );
+			} ).to.throw( CKEditorError, /^batch-wrap-range-not-flat/ );
+		} );
+
+		it( 'should throw if element to wrap with has children', () => {
+			let p = new Element( 'p', [], 'a' );
+
+			expect( () => {
+				doc.batch().wrap( range, p );
+			} ).to.throw( CKEditorError, /^batch-wrap-element-not-empty/ );
+		} );
+
+		it( 'should throw if element to wrap with has children', () => {
+			let p = new Element( 'p' );
+			root.insertChildren( 0, p );
+
+			expect( () => {
+				doc.batch().wrap( range, p );
+			} ).to.throw( CKEditorError, /^batch-wrap-element-attached/ );
+		} );
+
+		it( 'should be chainable', () => {
+			const batch = doc.batch();
+
+			const chain = batch.wrap( range, 'p' );
+			expect( chain ).to.equal( batch );
+		} );
+	} );
+} );