浏览代码

Moved writer-related LiveSelection parts to the Writer.

Maciej Bukowski 8 年之前
父节点
当前提交
65ebfee514

+ 2 - 48
packages/ckeditor5-engine/src/model/liveselection.js

@@ -207,11 +207,6 @@ export default class LiveSelection extends Selection {
 	 * @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 ];
@@ -223,11 +218,6 @@ export default class LiveSelection extends Selection {
 	 * @inheritDoc
 	 */
 	removeAttribute( key ) {
-		// Remove stored attribute from parent element if the selection is collapsed in an empty node.
-		if ( this.isCollapsed && this.anchor.parent.isEmpty ) {
-			this._removeStoredAttribute( key );
-		}
-
 		if ( this._removeAttribute( key ) ) {
 			// Fire event with exact data.
 			const attributeKeys = [ key ];
@@ -240,7 +230,7 @@ export default class LiveSelection extends Selection {
 	 */
 	clearAttributes() {
 		if ( this.isCollapsed && this.anchor.parent.isEmpty ) {
-			this._setStoredAttributesTo( [] );
+			this.removeStoredAttributes();
 		}
 
 		const changed = this._setAttributesTo( new Map() );
@@ -532,41 +522,11 @@ export default class LiveSelection extends Selection {
 	}
 
 	/**
-	 * Removes attribute with given key from attributes stored in current selection's parent node.
-	 *
-	 * @private
-	 * @param {String} key Key of attribute to remove.
-	 */
-	_removeStoredAttribute( key ) {
-		const storeKey = LiveSelection._getStoreAttributeKey( key );
-
-		this._model.change( writer => {
-			writer.removeAttribute( storeKey, this.anchor.parent );
-		} );
-	}
-
-	/**
-	 * Stores given attribute key and value in current selection's parent node.
-	 *
-	 * @private
-	 * @param {String} key Key of attribute to set.
-	 * @param {*} value Attribute value.
-	 */
-	_storeAttribute( key, value ) {
-		const storeKey = LiveSelection._getStoreAttributeKey( key );
-
-		this._model.change( writer => {
-			writer.setAttribute( storeKey, value, this.anchor.parent );
-		} );
-	}
-
-	/**
 	 * Sets selection attributes stored in current selection's parent node to given set of attributes.
 	 *
 	 * @private
-	 * @param {Iterable|Object} attrs Iterable object containing attributes to be set.
 	 */
-	_setStoredAttributesTo( attrs ) {
+	removeStoredAttributes() {
 		const selectionParent = this.anchor.parent;
 
 		this._model.change( writer => {
@@ -575,12 +535,6 @@ export default class LiveSelection extends Selection {
 
 				writer.removeAttribute( storeKey, selectionParent );
 			}
-
-			for ( const [ key, value ] of attrs ) {
-				const storeKey = LiveSelection._getStoreAttributeKey( key );
-
-				writer.setAttribute( storeKey, value, selectionParent );
-			}
 		} );
 	}
 

+ 21 - 2
packages/ckeditor5-engine/src/model/writer.js

@@ -35,6 +35,7 @@ import Element from './element';
 import RootElement from './rootelement';
 import Position from './position';
 import Range from './range.js';
+import DocumentSelection from './documentselection';
 
 import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
 
@@ -859,7 +860,16 @@ export default class Writer {
 	setSelectionAttribute( key, value ) {
 		this._assertWriterUsedCorrectly();
 
-		this.model.document.selection._setAttribute( key, value );
+		const selection = this.model.document.selection;
+
+		// Store attribute in parent element if the selection is collapsed in an empty node.
+		if ( selection.isCollapsed && selection.anchor.parent.isEmpty ) {
+			const storeKey = DocumentSelection._getStoreAttributeKey( key );
+
+			this.setAttribute( storeKey, value, selection.anchor.parent );
+		}
+
+		selection._setAttribute( key, value );
 	}
 
 	/**
@@ -870,7 +880,16 @@ export default class Writer {
 	removeSelectionAttribute( key ) {
 		this._assertWriterUsedCorrectly();
 
-		this.model.document.selection._removeAttribute( key );
+		const selection = this.model.document.selection;
+
+		// Remove stored attribute from parent element if the selection is collapsed in an empty node.
+		if ( selection.isCollapsed && selection.anchor.parent.isEmpty ) {
+			const storeKey = DocumentSelection._getStoreAttributeKey( key );
+
+			this.removeAttribute( storeKey, selection.anchor.parent );
+		}
+
+		selection._removeAttribute( key );
 	}
 
 	/**

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

@@ -296,8 +296,8 @@ describe( 'DocumentSelection', () => {
 		} );
 	} );
 
-	// TODO - merge with setTo
-	describe( 'setRanges() - TODO', () => {
+	// TODO - merge with other setTo's
+	describe( 'setTo()', () => {
 		it( 'should throw an error when range is invalid', () => {
 			expect( () => {
 				selection._setTo( [ { invalid: 'range' } ] );
@@ -713,13 +713,11 @@ describe( 'DocumentSelection', () => {
 		} );
 
 		describe( '_setAttribute()', () => {
-			it( 'should store attribute if the selection is in empty node', () => {
+			it( 'should set attribute', () => {
 				selection._setTo( [ rangeInEmptyP ] );
 				selection._setAttribute( 'foo', 'bar' );
 
 				expect( selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
-
-				expect( emptyP.getAttribute( fooStoreAttrKey ) ).to.equal( 'bar' );
 			} );
 		} );
 
@@ -730,18 +728,6 @@ describe( 'DocumentSelection', () => {
 				selection._removeAttribute( 'foo' );
 
 				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
-
-				expect( fullP.hasAttribute( fooStoreAttrKey ) ).to.be.false;
-			} );
-
-			it( 'should remove stored attribute if the selection is in empty node', () => {
-				selection._setTo( [ rangeInEmptyP ] );
-				selection._setAttribute( 'foo', 'bar' );
-				selection._removeAttribute( 'foo' );
-
-				expect( selection.getAttribute( 'foo' ) ).to.be.undefined;
-
-				expect( emptyP.hasAttribute( fooStoreAttrKey ) ).to.be.false;
 			} );
 		} );
 
@@ -924,7 +910,10 @@ describe( 'DocumentSelection', () => {
 				} );
 
 				selection._setTo( [ rangeInEmptyP ] );
-				selection._setAttribute( 'foo', 'bar' );
+
+				model.change( writer => {
+					writer.setSelectionAttribute( 'foo', 'bar' );
+				} );
 
 				expect( batchTypes ).to.deep.equal( [ 'default' ] );
 				expect( emptyP.getAttribute( fooStoreAttrKey ) ).to.equal( 'bar' );
@@ -1015,9 +1004,9 @@ describe( 'DocumentSelection', () => {
 
 			it( 'uses model change to clear attributes', () => {
 				selection._setTo( [ rangeInEmptyP ] );
-				selection._setAttribute( 'foo', 'bar' );
 
 				model.change( writer => {
+					writer.setSelectionAttribute( 'foo', 'bar' );
 					writer.insertText( 'x', rangeInEmptyP.start );
 
 					// `emptyP` still has the attribute, because attribute clearing is in enqueued block.
@@ -1050,8 +1039,10 @@ describe( 'DocumentSelection', () => {
 			// Rename and some other deltas don't specify range in doc#change event.
 			// So let's see if there's no crash or something.
 			it( 'are not removed on rename', () => {
-				selection._setTo( [ rangeInEmptyP ] );
-				selection._setAttribute( 'foo', 'bar' );
+				model.change( writer => {
+					writer.setSelection( rangeInEmptyP );
+					writer.setSelectionAttribute( 'foo', 'bar' );
+				} );
 
 				sinon.spy( model, 'enqueueChange' );
 

+ 156 - 1
packages/ckeditor5-engine/tests/model/writer.js

@@ -17,8 +17,8 @@ import Range from '../../src/model/range';
 
 import count from '@ckeditor/ckeditor5-utils/src/count';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
-
 import { getNodesAndText } from '../../tests/model/_utils/utils';
+import DocumentSelection from '../../src/model/documentselection';
 
 describe( 'Writer', () => {
 	let model, doc, batch;
@@ -2026,6 +2026,137 @@ describe( 'Writer', () => {
 		} );
 	} );
 
+	describe( 'setSelection()', () => {
+		let root;
+
+		beforeEach( () => {
+			model.schema.register( 'p', { inheritAllFrom: '$block' } );
+			model.schema.extend( 'p', { allowIn: '$root' } );
+
+			root = doc.createRoot();
+			root.appendChildren( [
+				new Element( 'p' ),
+				new Element( 'p' ),
+				new Element( 'p', [], new Text( 'foo' ) )
+			] );
+		} );
+
+		it( 'should use DocumentSelection#_setTo method', () => {
+			const firstParagraph = root.getNodeByPath( [ 1 ] );
+
+			const setToSpy = sinon.spy( DocumentSelection.prototype, '_setTo' );
+			setSelection( firstParagraph );
+
+			expect( setToSpy.calledOnce ).to.be.true;
+			setToSpy.restore();
+		} );
+
+		it( 'should change document selection ranges', () => {
+			const range = new Range( new Position( root, [ 1 ] ), new Position( root, [ 2, 2 ] ) );
+
+			setSelection( range, true );
+
+			expect( model.document.selection._ranges.length ).to.equal( 1 );
+			expect( model.document.selection._ranges[ 0 ].start.path ).to.deep.equal( [ 1 ] );
+			expect( model.document.selection._ranges[ 0 ].end.path ).to.deep.equal( [ 2, 2 ] );
+			expect( model.document.selection.isBackward ).to.be.true;
+		} );
+	} );
+
+	describe( 'setSelectionFocus()', () => {
+		let root;
+
+		beforeEach( () => {
+			model.schema.register( 'p', { inheritAllFrom: '$block' } );
+			model.schema.extend( 'p', { allowIn: '$root' } );
+
+			root = doc.createRoot();
+			root.appendChildren( [
+				new Element( 'p' ),
+				new Element( 'p' ),
+				new Element( 'p', [], new Text( 'foo' ) )
+			] );
+		} );
+
+		it( 'should use DocumentSelection#_setFocus method', () => {
+			const firstParagraph = root.getNodeByPath( [ 1 ] );
+
+			const setFocusSpy = sinon.spy( DocumentSelection.prototype, '_setFocus' );
+			setSelectionFocus( firstParagraph );
+
+			expect( setFocusSpy.calledOnce ).to.be.true;
+			setFocusSpy.restore();
+		} );
+
+		it( 'should change document selection ranges', () => {
+			setSelection( new Position( root, [ 1 ] ) );
+			setSelectionFocus( new Position( root, [ 2, 2 ] ) );
+
+			expect( model.document.selection._ranges.length ).to.equal( 1 );
+			expect( model.document.selection._ranges[ 0 ].start.path ).to.deep.equal( [ 1 ] );
+			expect( model.document.selection._ranges[ 0 ].end.path ).to.deep.equal( [ 2, 2 ] );
+		} );
+	} );
+
+	describe( 'setSelectionAttribute()', () => {
+		const fooStoreAttrKey = DocumentSelection._getStoreAttributeKey( 'foo' );
+		let root, rangeInEmptyP, emptyP;
+
+		beforeEach( () => {
+			model.schema.register( 'p', { inheritAllFrom: '$block' } );
+			model.schema.extend( 'p', { allowIn: '$root' } );
+
+			root = doc.createRoot();
+			root.appendChildren( [
+				new Element( 'p', [], [] ),
+				new Element( 'p' ),
+				new Element( 'p', [], new Text( 'foo' ) )
+			] );
+
+			rangeInEmptyP = new Range( new Position( root, [ 0, 0 ] ), new Position( root, [ 0, 0 ] ) );
+			emptyP = root.getChild( 0 );
+		} );
+
+		it( 'should store attribute if the selection is in empty node', () => {
+			setSelection( rangeInEmptyP );
+			setSelectionAttribute( 'foo', 'bar' );
+
+			expect( model.document.selection.getAttribute( 'foo' ) ).to.equal( 'bar' );
+
+			expect( emptyP.getAttribute( fooStoreAttrKey ) ).to.equal( 'bar' );
+		} );
+	} );
+
+	describe( 'removeSelectionAttribute()', () => {
+		const fooStoreAttrKey = DocumentSelection._getStoreAttributeKey( 'foo' );
+		let root, rangeInEmptyP, emptyP;
+
+		beforeEach( () => {
+			model.schema.register( 'p', { inheritAllFrom: '$block' } );
+			model.schema.extend( 'p', { allowIn: '$root' } );
+
+			root = doc.createRoot();
+			root.appendChildren( [
+				new Element( 'p', [], [] ),
+				new Element( 'p' ),
+				new Element( 'p', [], new Text( 'foo' ) )
+			] );
+
+			rangeInEmptyP = new Range( new Position( root, [ 0, 0 ] ), new Position( root, [ 0, 0 ] ) );
+			emptyP = root.getChild( 0 );
+		} );
+
+		it( 'should remove stored attribute if the selection is in empty node', () => {
+			setSelection( rangeInEmptyP );
+			setSelectionAttribute( 'foo', 'bar' );
+			removeSelectionAttribute( 'foo' );
+
+			expect( model.document.selection.getAttribute( 'foo' ) ).to.be.undefined;
+
+			expect( emptyP.hasAttribute( fooStoreAttrKey ) ).to.be.false;
+		} );
+	} );
+
 	function createText( data, attributes ) {
 		return model.change( writer => {
 			return writer.createText( data, attributes );
@@ -2157,4 +2288,28 @@ describe( 'Writer', () => {
 			writer.removeMarker( markerOrName );
 		} );
 	}
+
+	function setSelection( selectable, backwardSelectionOrOffset ) {
+		model.enqueueChange( batch, writer => {
+			writer.setSelection( selectable, backwardSelectionOrOffset );
+		} );
+	}
+
+	function setSelectionFocus( itemOrPosition, offset ) {
+		model.enqueueChange( batch, writer => {
+			writer.setSelectionFocus( itemOrPosition, offset );
+		} );
+	}
+
+	function setSelectionAttribute( key, value ) {
+		model.enqueueChange( batch, writer => {
+			writer.setSelectionAttribute( key, value );
+		} );
+	}
+
+	function removeSelectionAttribute( key ) {
+		model.enqueueChange( batch, writer => {
+			writer.removeSelectionAttribute( key );
+		} );
+	}
 } );