Sfoglia il codice sorgente

WIP - DocumentSelection.

Maciej Bukowski 8 anni fa
parent
commit
d56630f47a

+ 67 - 68
packages/ckeditor5-engine/src/model/documentselection.js

@@ -11,6 +11,8 @@ import Position from './position';
 import Text from './text';
 import TextProxy from './textproxy';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
+import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 
 import Selection from './selection';
 
@@ -168,12 +170,16 @@ export default class DocumentSelection {
 		return this._selection.rangeCount > 0;
 	}
 
+	get isBackward() {
+		return this._selection.isBackward;
+	}
+
 	/**
 	 * Unbinds all events previously bound by document selection.
 	 */
 	destroy() {
-		for ( let i = 0; i < this._ranges.length; i++ ) {
-			this._ranges[ i ].detach();
+		for ( const range of this._selection._ranges ) {
+			range.detach();
 		}
 
 		this.stopListening();
@@ -222,34 +228,32 @@ export default class DocumentSelection {
 		this._refreshAttributes();
 	}
 
+	/**
+	 * @protected
+	 * @param {*} key
+	 * @param {*} value
+	 */
 	_setAttribute( key, value ) {
-		this._selection.setAttribute( key, value );
+		// Store attribute in parent element if the selection is collapsed in an empty node.
+		if ( this._selection.isCollapsed && this._selection.anchor.parent.isEmpty ) {
+			this._storeAttribute( key, value );
+		}
+
+		if ( this._setAttribute2( key, value ) ) {
+			// Fire event with exact data.
+			const attributeKeys = [ key ];
+			this.fire( 'change:attribute', { attributeKeys, directChange: true } );
+		}
 	}
 
 	/**
 	 * @private
 	 */
 	_removeAllRanges() {
-		super.removeAllRanges();
+		this._selection.removeAllRanges();
 		this._refreshAttributes();
 	}
 
-	// /**
-	//  * @inheritDoc
-	//  */
-	// setAttribute( key, value ) {
-	// 	// Store attribute in parent element if the selection is collapsed in an empty node.
-	// 	if ( this.isCollapsed && this.anchor.parent.isEmpty ) {
-	// 		this._storeAttribute( key, value );
-	// 	}
-
-	// 	if ( this._setAttribute( key, value ) ) {
-	// 		// Fire event with exact data.
-	// 		const attributeKeys = [ key ];
-	// 		this.fire( 'change:attribute', { attributeKeys, directChange: true } );
-	// 	}
-	// }
-
 	/**
 	 * Should be used only by the {@link module:engine/model/writer~Writer} class.
 	 *
@@ -268,6 +272,14 @@ export default class DocumentSelection {
 		}
 	}
 
+	getAttributes() {
+		return this._selection.getAttributes();
+	}
+
+	getAttribute() {
+		return this._selection.getAttribute();
+	}
+
 	// /**
 	//  * @inheritDoc
 	//  */
@@ -321,25 +333,6 @@ export default class DocumentSelection {
 		throw new CKEditorError( 'documentselection-cannot-create: Cannot create a new DocumentSelection instance.' );
 	}
 
-	// /**
-	//  * @inheritDoc
-	//  */
-	// _popRange() {
-	// 	this._ranges.pop().detach();
-	// }
-
-	// /**
-	//  * @inheritDoc
-	//  */
-	// _pushRange( range ) {
-	// 	const liveRange = this._prepareRange( range );
-
-	// 	// `undefined` is returned when given `range` is in graveyard root.
-	// 	if ( liveRange ) {
-	// 		this._ranges.push( liveRange );
-	// 	}
-	// }
-
 	// /**
 	//  * Prepares given range to be added to selection. Checks if it is correct,
 	//  * converts it to {@link module:engine/model/liverange~LiveRange LiveRange}
@@ -459,39 +452,39 @@ export default class DocumentSelection {
 		return key.startsWith( storePrefix );
 	}
 
-	// /**
-	//  * Internal method for setting `DocumentSelection` attribute. Supports attribute priorities (through `directChange`
-	//  * parameter).
-	//  *
-	//  * @private
-	//  * @param {String} key Attribute key.
-	//  * @param {*} value Attribute value.
-	//  * @param {Boolean} [directChange=true] `true` if the change is caused by `Selection` API, `false` if change
-	//  * is caused by `Batch` API.
-	//  * @returns {Boolean} Whether value has changed.
-	//  */
-	// _setAttribute( key, value, directChange = true ) {
-	// 	const priority = directChange ? 'normal' : 'low';
+	/**
+	 * Internal method for setting `DocumentSelection` attribute. Supports attribute priorities (through `directChange`
+	 * parameter).
+	 *
+	 * @private
+	 * @param {String} key Attribute key.
+	 * @param {*} value Attribute value.
+	 * @param {Boolean} [directChange=true] `true` if the change is caused by `Selection` API, `false` if change
+	 * is caused by `Batch` API.
+	 * @returns {Boolean} Whether value has changed.
+	 */
+	_setAttribute2( key, value, directChange = true ) {
+		const priority = directChange ? 'normal' : 'low';
 
-	// 	if ( priority == 'low' && this._attributePriority.get( key ) == 'normal' ) {
-	// 		// Priority too low.
-	// 		return false;
-	// 	}
+		if ( priority == 'low' && this._attributePriority.get( key ) == 'normal' ) {
+			// Priority too low.
+			return false;
+		}
 
-	// 	const oldValue = super.getAttribute( key );
+		const oldValue = this.selection.getAttribute( key );
 
-	// 	// Don't do anything if value has not changed.
-	// 	if ( oldValue === value ) {
-	// 		return false;
-	// 	}
+		// Don't do anything if value has not changed.
+		if ( oldValue === value ) {
+			return false;
+		}
 
-	// 	this._attrs.set( key, value );
+		this._attrs.set( key, value );
 
-	// 	// Update priorities map.
-	// 	this._attributePriority.set( key, priority );
+		// Update priorities map.
+		this._attributePriority.set( key, priority );
 
-	// 	return true;
-	// }
+		return true;
+	}
 
 	/**
 	 * Internal method for removing `DocumentSelection` attribute. Supports attribute priorities (through `directChange`
@@ -513,7 +506,7 @@ export default class DocumentSelection {
 		}
 
 		// Don't do anything if value has not changed.
-		if ( !super.hasAttribute( key ) ) {
+		if ( !this._selection.hasAttribute( key ) ) {
 			return false;
 		}
 
@@ -552,7 +545,7 @@ export default class DocumentSelection {
 
 		for ( const [ key, value ] of attrs ) {
 			// Attribute may not be set because of attributes or because same key/value is already added.
-			const gotAdded = this._setAttribute( key, value, directChange );
+			const gotAdded = this._setAttribute2( key, value, directChange );
 
 			if ( gotAdded ) {
 				changed.add( key );
@@ -745,6 +738,12 @@ export default class DocumentSelection {
 	}
 }
 
+mix( DocumentSelection, EmitterMixin );
+
+/**
+ * @event change:attribute
+ */
+
 // Helper function for {@link module:engine/model/documentselection~DocumentSelection#_updateAttributes}.
 //
 // It takes model item, checks whether it is a text node (or text proxy) and, if so, returns it's attributes. If not, returns `null`.

+ 21 - 22
packages/ckeditor5-engine/tests/model/documentselection.js

@@ -121,7 +121,7 @@ describe( 'DocumentSelection', () => {
 		} );
 
 		it( 'should back off to the default algorithm if selection has ranges', () => {
-			selection.addRange( range );
+			selection._setTo( range );
 
 			expect( selection.isCollapsed ).to.be.false;
 		} );
@@ -143,7 +143,7 @@ describe( 'DocumentSelection', () => {
 		} );
 
 		it( 'should back off to the default algorithm if selection has ranges', () => {
-			selection.addRange( range );
+			selection._setTo( range );
 
 			expect( selection.anchor.isEqual( range.start ) ).to.be.true;
 		} );
@@ -166,7 +166,7 @@ describe( 'DocumentSelection', () => {
 		} );
 
 		it( 'should back off to the default algorithm if selection has ranges', () => {
-			selection.addRange( range );
+			selection._setTo( range );
 
 			expect( selection.focus.isEqual( range.end ) ).to.be.true;
 		} );
@@ -176,11 +176,14 @@ describe( 'DocumentSelection', () => {
 		it( 'should return proper range count', () => {
 			expect( selection.rangeCount ).to.equal( 1 );
 
-			selection.addRange( new Range( new Position( root, [ 0 ] ), new Position( root, [ 0 ] ) ) );
+			selection._setTo( new Range( new Position( root, [ 0 ] ), new Position( root, [ 0 ] ) ) );
 
 			expect( selection.rangeCount ).to.equal( 1 );
 
-			selection.addRange( new Range( new Position( root, [ 2 ] ), new Position( root, [ 2 ] ) ) );
+			selection._setTo( [
+				new Range( new Position( root, [ 2 ] ), new Position( root, [ 2 ] ) ),
+				new Range( new Position( root, [ 0 ] ), new Position( root, [ 0 ] ) )
+			] );
 
 			expect( selection.rangeCount ).to.equal( 2 );
 		} );
@@ -188,7 +191,7 @@ describe( 'DocumentSelection', () => {
 
 	describe( 'hasOwnRange', () => {
 		it( 'should return true if selection has any ranges set', () => {
-			selection.addRange( new Range( new Position( root, [ 0 ] ), new Position( root, [ 0 ] ) ) );
+			selection._setTo( new Range( new Position( root, [ 0 ] ), new Position( root, [ 0 ] ) ) );
 
 			expect( selection.hasOwnRange ).to.be.true;
 		} );
@@ -207,14 +210,14 @@ describe( 'DocumentSelection', () => {
 
 		it( 'should throw an error when range is invalid', () => {
 			expect( () => {
-				selection.addRange( { invalid: 'range' } );
+				selection._setTo( { invalid: 'range' } );
 			} ).to.throw( CKEditorError, /model-selection-added-not-range/ );
 		} );
 
 		it( 'should not add a range that is in graveyard', () => {
 			const spy = testUtils.sinon.stub( log, 'warn' );
 
-			selection.addRange( Range.createIn( doc.graveyard ) );
+			selection._setTo( Range.createIn( doc.graveyard ) );
 
 			expect( selection._ranges.length ).to.equal( 0 );
 			expect( spy.calledOnce ).to.be.true;
@@ -223,7 +226,7 @@ describe( 'DocumentSelection', () => {
 		it( 'should refresh attributes', () => {
 			const spy = testUtils.sinon.spy( selection, '_updateAttributes' );
 
-			selection.addRange( range );
+			selection._setTo( range );
 
 			expect( spy.called ).to.be.true;
 		} );
@@ -231,8 +234,7 @@ describe( 'DocumentSelection', () => {
 
 	describe( 'setCollapsedAt()', () => {
 		it( 'detaches all existing ranges', () => {
-			selection.addRange( range );
-			selection.addRange( liveRange );
+			selection._setTo( [ range, liveRange ] );
 
 			const spy = testUtils.sinon.spy( LiveRange.prototype, 'detach' );
 			selection.setCollapsedAt( root );
@@ -243,8 +245,7 @@ describe( 'DocumentSelection', () => {
 
 	describe( 'destroy()', () => {
 		it( 'should unbind all events', () => {
-			selection.addRange( liveRange );
-			selection.addRange( range );
+			selection._setTo( [ range, liveRange ] );
 
 			const ranges = selection._ranges;
 
@@ -278,7 +279,7 @@ describe( 'DocumentSelection', () => {
 			const newEndPos = Position.createAt( root, 4 );
 			const spy = testUtils.sinon.spy( LiveRange.prototype, 'detach' );
 
-			selection.addRange( new Range( startPos, endPos ) );
+			selection._setTo( new Range( startPos, endPos ) );
 
 			selection.moveFocusTo( newEndPos );
 
@@ -290,8 +291,7 @@ describe( 'DocumentSelection', () => {
 		let spy, ranges;
 
 		beforeEach( () => {
-			selection.addRange( liveRange );
-			selection.addRange( range );
+			selection._setTo( [ liveRange, range ] );
 
 			spy = sinon.spy();
 			selection.on( 'change:range', spy );
@@ -332,20 +332,19 @@ describe( 'DocumentSelection', () => {
 	describe( 'setRanges()', () => {
 		it( 'should throw an error when range is invalid', () => {
 			expect( () => {
-				selection.setRanges( [ { invalid: 'range' } ] );
+				selection._setTo( [ { invalid: 'range' } ] );
 			} ).to.throw( CKEditorError, /model-selection-added-not-range/ );
 		} );
 
 		it( 'should detach removed ranges', () => {
-			selection.addRange( liveRange );
-			selection.addRange( range );
+			selection._setTo( [ liveRange, range ] );
 
 			const oldRanges = Array.from( selection._ranges );
 
 			sinon.spy( oldRanges[ 0 ], 'detach' );
 			sinon.spy( oldRanges[ 1 ], 'detach' );
 
-			selection.setRanges( [] );
+			selection.setTo( [] );
 
 			expect( oldRanges[ 0 ].detach.called ).to.be.true;
 			expect( oldRanges[ 1 ].detach.called ).to.be.true;
@@ -354,7 +353,7 @@ describe( 'DocumentSelection', () => {
 		it( 'should refresh attributes', () => {
 			const spy = sinon.spy( selection, '_updateAttributes' );
 
-			selection.setRanges( [ range ] );
+			selection._setTo( [ range ] );
 
 			expect( spy.called ).to.be.true;
 		} );
@@ -365,7 +364,7 @@ describe( 'DocumentSelection', () => {
 
 			setData( model, 'f<$text italic="true">[o</$text><$text bold="true">ob]a</$text>r' );
 
-			selection.setRanges( [ Range.createFromPositionAndShift( selection.getLastRange().end, 0 ) ] );
+			selection._setTo( [ Range.createFromPositionAndShift( selection.getLastRange().end, 0 ) ] );
 
 			expect( selection.getAttribute( 'bold' ) ).to.equal( true );
 			expect( selection.hasAttribute( 'italic' ) ).to.equal( false );