浏览代码

Moved `Document#getNearesetSelectionRange` to `Schema#getNearesetSelectionRange`.

Maciej Bukowski 7 年之前
父节点
当前提交
b5d95a34f3

+ 2 - 94
packages/ckeditor5-engine/src/model/document.js

@@ -13,7 +13,6 @@ import Position from './position';
 import RootElement from './rootelement';
 import History from './history';
 import DocumentSelection from './documentselection';
-import TreeWalker from './treewalker';
 import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 import clone from '@ckeditor/ckeditor5-utils/src/lib/lodash/clone';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
@@ -249,59 +248,6 @@ export default class Document {
 	}
 
 	/**
-	 * Basing on given `position`, finds and returns a {@link module:engine/model/range~Range Range} instance that is
-	 * nearest to that `position` and is a correct range for selection.
-	 *
-	 * Correct selection range might be collapsed - when it's located in position where text node can be placed.
-	 * Non-collapsed range is returned when selection can be placed around element marked as "object" in
-	 * {@link module:engine/model/schema~Schema schema}.
-	 *
-	 * Direction of searching for nearest correct selection range can be specified as:
-	 * * `both` - searching will be performed in both ways,
-	 * * `forward` - searching will be performed only forward,
-	 * * `backward` - searching will be performed only backward.
-	 *
-	 * When valid selection range cannot be found, `null` is returned.
-	 *
-	 * @param {module:engine/model/position~Position} position Reference position where new selection range should be looked for.
-	 * @param {'both'|'forward'|'backward'} [direction='both'] Search direction.
-	 * @returns {module:engine/model/range~Range|null} Nearest selection range or `null` if one cannot be found.
-	 */
-	getNearestSelectionRange( position, direction = 'both' ) {
-		const schema = this.model.schema;
-
-		// Return collapsed range if provided position is valid.
-		if ( schema.checkChild( position, '$text' ) ) {
-			return new Range( position );
-		}
-
-		let backwardWalker, forwardWalker;
-
-		if ( direction == 'both' || direction == 'backward' ) {
-			backwardWalker = new TreeWalker( { startPosition: position, direction: 'backward' } );
-		}
-
-		if ( direction == 'both' || direction == 'forward' ) {
-			forwardWalker = new TreeWalker( { startPosition: position } );
-		}
-
-		for ( const data of combineWalkers( backwardWalker, forwardWalker ) ) {
-			const type = ( data.walker == backwardWalker ? 'elementEnd' : 'elementStart' );
-			const value = data.value;
-
-			if ( value.type == type && schema.isObject( value.item ) ) {
-				return Range.createOn( value.item );
-			}
-
-			if ( schema.checkChild( value.nextPosition, '$text' ) ) {
-				return new Range( value.nextPosition );
-			}
-		}
-
-		return null;
-	}
-
-	/**
 	 * Used to register a post-fixer callback. Post-fixers mechanism guarantees that the features that listen to
 	 * {@link module:engine/model/model~Model#event:_change model's change event} will operate on a correct model state.
 	 *
@@ -380,10 +326,11 @@ export default class Document {
 	 */
 	_getDefaultRange() {
 		const defaultRoot = this._getDefaultRoot();
+		const schema = this.model.schema;
 
 		// Find the first position where the selection can be put.
 		const position = new Position( defaultRoot, [ 0 ] );
-		const nearestRange = this.getNearestSelectionRange( position );
+		const nearestRange = schema.getNearestSelectionRange( position );
 
 		// If valid selection range is not found - return range collapsed at the beginning of the root.
 		return nearestRange || new Range( position );
@@ -446,42 +393,3 @@ function validateTextNodePosition( rangeBoundary ) {
 
 	return true;
 }
-
-// Generator function returning values from provided walkers, switching between them at each iteration. If only one walker
-// is provided it will return data only from that walker.
-//
-// @param {module:engine/module/treewalker~TreeWalker} [backward] Walker iterating in backward direction.
-// @param {module:engine/module/treewalker~TreeWalker} [forward] Walker iterating in forward direction.
-// @returns {Iterable.<Object>} Object returned at each iteration contains `value` and `walker` (informing which walker returned
-// given value) fields.
-function* combineWalkers( backward, forward ) {
-	let done = false;
-
-	while ( !done ) {
-		done = true;
-
-		if ( backward ) {
-			const step = backward.next();
-
-			if ( !step.done ) {
-				done = false;
-				yield {
-					walker: backward,
-					value: step.value
-				};
-			}
-		}
-
-		if ( forward ) {
-			const step = forward.next();
-
-			if ( !step.done ) {
-				done = false;
-				yield {
-					walker: forward,
-					value: step.value
-				};
-			}
-		}
-	}
-}

+ 1 - 1
packages/ckeditor5-engine/src/model/documentselection.js

@@ -873,7 +873,7 @@ class LiveSelection extends Selection {
 		const positionCandidate = Position.createFromPosition( removedRangeStart );
 
 		// Find a range that is a correct selection range and is closest to the start of removed range.
-		const selectionRange = this._document.getNearestSelectionRange( positionCandidate );
+		const selectionRange = this._model.schema.getNearestSelectionRange( positionCandidate );
 
 		// Remove the old selection range before preparing and adding new selection range. This order is important,
 		// because new range, in some cases, may intersect with old range (it depends on `getNearestSelectionRange()` result).

+ 92 - 0
packages/ckeditor5-engine/src/model/schema.js

@@ -14,6 +14,7 @@ import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import Range from './range';
 import Position from './position';
 import Element from './element';
+import TreeWalker from './treewalker';
 
 /**
  * The model's schema. It defines allowed and disallowed structures of nodes as well as nodes' attributes.
@@ -668,6 +669,58 @@ export default class Schema {
 	}
 
 	/**
+	 * Basing on given `position`, finds and returns a {@link module:engine/model/range~Range Range} instance that is
+	 * nearest to that `position` and is a correct range for selection.
+	 *
+	 * Correct selection range might be collapsed - when it's located in position where text node can be placed.
+	 * Non-collapsed range is returned when selection can be placed around element marked as "object" in
+	 * {@link module:engine/model/schema~Schema schema}.
+	 *
+	 * Direction of searching for nearest correct selection range can be specified as:
+	 * * `both` - searching will be performed in both ways,
+	 * * `forward` - searching will be performed only forward,
+	 * * `backward` - searching will be performed only backward.
+	 *
+	 * When valid selection range cannot be found, `null` is returned.
+	 *
+	 * @param {module:engine/model/position~Position} position Reference position where new selection range should be looked for.
+	 * @param {'both'|'forward'|'backward'} [direction='both'] Search direction.
+	 * @returns {module:engine/model/range~Range|null} Nearest selection range or `null` if one cannot be found.
+	 */
+
+	getNearestSelectionRange( position, direction = 'both' ) {
+		// Return collapsed range if provided position is valid.
+		if ( this.checkChild( position, '$text' ) ) {
+			return new Range( position );
+		}
+
+		let backwardWalker, forwardWalker;
+
+		if ( direction == 'both' || direction == 'backward' ) {
+			backwardWalker = new TreeWalker( { startPosition: position, direction: 'backward' } );
+		}
+
+		if ( direction == 'both' || direction == 'forward' ) {
+			forwardWalker = new TreeWalker( { startPosition: position } );
+		}
+
+		for ( const data of combineWalkers( backwardWalker, forwardWalker ) ) {
+			const type = ( data.walker == backwardWalker ? 'elementEnd' : 'elementStart' );
+			const value = data.value;
+
+			if ( value.type == type && this.isObject( value.item ) ) {
+				return Range.createOn( value.item );
+			}
+
+			if ( this.checkChild( value.nextPosition, '$text' ) ) {
+				return new Range( value.nextPosition );
+			}
+		}
+
+		return null;
+	}
+
+	/**
 	 * Removes attributes disallowed by the schema.
 	 *
 	 * @param {Iterable.<module:engine/model/node~Node>} nodes Nodes that will be filtered.
@@ -1384,3 +1437,42 @@ function mapContextItem( ctxItem ) {
 		};
 	}
 }
+
+// Generator function returning values from provided walkers, switching between them at each iteration. If only one walker
+// is provided it will return data only from that walker.
+//
+// @param {module:engine/module/treewalker~TreeWalker} [backward] Walker iterating in backward direction.
+// @param {module:engine/module/treewalker~TreeWalker} [forward] Walker iterating in forward direction.
+// @returns {Iterable.<Object>} Object returned at each iteration contains `value` and `walker` (informing which walker returned
+// given value) fields.
+function* combineWalkers( backward, forward ) {
+	let done = false;
+
+	while ( !done ) {
+		done = true;
+
+		if ( backward ) {
+			const step = backward.next();
+
+			if ( !step.done ) {
+				done = false;
+				yield {
+					walker: backward,
+					value: step.value
+				};
+			}
+		}
+
+		if ( forward ) {
+			const step = forward.next();
+
+			if ( !step.done ) {
+				done = false;
+				yield {
+					walker: forward,
+					value: step.value
+				};
+			}
+		}
+	}
+}

+ 1 - 1
packages/ckeditor5-engine/src/model/utils/insertcontent.js

@@ -161,7 +161,7 @@ class Insertion {
 			return Range.createOn( this.nodeToSelect );
 		}
 
-		return this.model.document.getNearestSelectionRange( this.position );
+		return this.model.schema.getNearestSelectionRange( this.position );
 	}
 
 	/**

+ 0 - 233
packages/ckeditor5-engine/tests/model/document.js

@@ -14,7 +14,6 @@ import Collection from '@ckeditor/ckeditor5-utils/src/collection';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import count from '@ckeditor/ckeditor5-utils/src/count';
 import { jsonParseStringify } from './_utils/utils';
-import { setData, getData } from '../../src/dev-utils/model';
 
 describe( 'Document', () => {
 	let model, doc;
@@ -168,238 +167,6 @@ describe( 'Document', () => {
 		} );
 	} );
 
-	describe( 'getNearestSelectionRange()', () => {
-		let selection;
-
-		beforeEach( () => {
-			model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
-
-			model.schema.register( 'emptyBlock', { allowIn: '$root' } );
-
-			model.schema.register( 'widget', {
-				allowIn: '$root',
-				isObject: true
-			} );
-
-			model.schema.register( 'blockWidget', {
-				allowIn: '$root',
-				allowContentOf: '$block',
-				isObject: true
-			} );
-
-			doc.createRoot();
-			selection = doc.selection;
-		} );
-
-		test(
-			'should return collapsed range if text node can be placed at that position - both',
-			'<paragraph>[]</paragraph>',
-			'both',
-			'<paragraph>[]</paragraph>'
-		);
-
-		test(
-			'should return collapsed range if text node can be placed at that position - forward',
-			'<paragraph>[]</paragraph>',
-			'forward',
-			'<paragraph>[]</paragraph>'
-		);
-
-		test(
-			'should return collapsed range if text node can be placed at that position - backward',
-			'<paragraph>[]</paragraph>',
-			'backward',
-			'<paragraph>[]</paragraph>'
-		);
-
-		test( 'should return null in empty document - both', '', 'both', null );
-
-		test( 'should return null in empty document - backward', '', 'backward', null );
-
-		test( 'should return null in empty document - forward', '', 'forward', null );
-
-		test(
-			'should find range before when searching both ways',
-			'<paragraph></paragraph>[]<paragraph></paragraph>',
-			'both',
-			'<paragraph>[]</paragraph><paragraph></paragraph>'
-		);
-
-		test(
-			'should find range before when searching backward',
-			'<paragraph></paragraph>[]<paragraph></paragraph>',
-			'backward',
-			'<paragraph>[]</paragraph><paragraph></paragraph>'
-		);
-
-		test(
-			'should find range after when searching forward',
-			'<paragraph></paragraph>[]<paragraph></paragraph>',
-			'forward',
-			'<paragraph></paragraph><paragraph>[]</paragraph>'
-		);
-
-		test(
-			'should find range after when searching both ways when it is closer',
-			'<paragraph></paragraph><emptyBlock></emptyBlock>[]<paragraph></paragraph>',
-			'both',
-			'<paragraph></paragraph><emptyBlock></emptyBlock><paragraph>[]</paragraph>'
-		);
-
-		test(
-			'should find range before when searching both ways when it is closer',
-			'<paragraph></paragraph><emptyBlock></emptyBlock>[]<emptyBlock></emptyBlock><emptyBlock></emptyBlock><paragraph></paragraph>',
-			'both',
-			'<paragraph>[]</paragraph><emptyBlock></emptyBlock><emptyBlock></emptyBlock><emptyBlock></emptyBlock><paragraph></paragraph>'
-		);
-
-		test(
-			'should return null if there is no valid range',
-			'[]<emptyBlock></emptyBlock>',
-			'both',
-			null
-		);
-
-		test(
-			'should return null if there is no valid range in given direction - backward',
-			'[]<paragraph></paragraph>',
-			'backward',
-			null
-		);
-
-		test(
-			'should return null if there is no valid range in given direction - forward',
-			'<paragraph></paragraph>[]',
-			'forward',
-			null
-		);
-
-		test(
-			'should move forward when placed at root start',
-			'[]<paragraph></paragraph><paragraph></paragraph>',
-			'both',
-			'<paragraph>[]</paragraph><paragraph></paragraph>'
-		);
-
-		test(
-			'should move backward when placed at root end',
-			'<paragraph></paragraph><paragraph></paragraph>[]',
-			'both',
-			'<paragraph></paragraph><paragraph>[]</paragraph>'
-		);
-
-		describe( 'in case of objects which do not allow text inside', () => {
-			test(
-				'should select nearest object (o[]o) - both',
-				'<widget></widget>[]<widget></widget>',
-				'both',
-				'[<widget></widget>]<widget></widget>'
-			);
-
-			test(
-				'should select nearest object (o[]o) - forward',
-				'<widget></widget>[]<widget></widget>',
-				'forward',
-				'<widget></widget>[<widget></widget>]'
-			);
-
-			test(
-				'should select nearest object (o[]o) - backward',
-				'<widget></widget>[]<widget></widget>',
-				'both',
-				'[<widget></widget>]<widget></widget>'
-			);
-
-			test(
-				'should select nearest object (p[]o) - forward',
-				'<paragraph></paragraph>[]<widget></widget>',
-				'forward',
-				'<paragraph></paragraph>[<widget></widget>]'
-			);
-
-			test(
-				'should select nearest object (o[]p) - both',
-				'<widget></widget>[]<paragraph></paragraph>',
-				'both',
-				'[<widget></widget>]<paragraph></paragraph>'
-			);
-
-			test(
-				'should select nearest object (o[]p) - backward',
-				'<widget></widget>[]<paragraph></paragraph>',
-				'backward',
-				'[<widget></widget>]<paragraph></paragraph>'
-			);
-
-			test(
-				'should select nearest object ([]o) - both',
-				'[]<widget></widget><paragraph></paragraph>',
-				'both',
-				'[<widget></widget>]<paragraph></paragraph>'
-			);
-
-			test(
-				'should select nearest object ([]o) - forward',
-				'[]<widget></widget><paragraph></paragraph>',
-				'forward',
-				'[<widget></widget>]<paragraph></paragraph>'
-			);
-
-			test(
-				'should select nearest object (o[]) - both',
-				'<paragraph></paragraph><widget></widget>[]',
-				'both',
-				'<paragraph></paragraph>[<widget></widget>]'
-			);
-
-			test(
-				'should select nearest object (o[]) - backward',
-				'<paragraph></paragraph><widget></widget>[]',
-				'both',
-				'<paragraph></paragraph>[<widget></widget>]'
-			);
-		} );
-
-		describe( 'in case of objects which allow text inside', () => {
-			test(
-				'should select nearest object which allows text (o[]o) - both',
-				'<blockWidget></blockWidget>[]<blockWidget></blockWidget>',
-				'both',
-				'[<blockWidget></blockWidget>]<blockWidget></blockWidget>'
-			);
-
-			test(
-				'should select nearest object (o[]p) - both',
-				'<blockWidget></blockWidget>[]<paragraph></paragraph>',
-				'both',
-				'[<blockWidget></blockWidget>]<paragraph></paragraph>'
-			);
-
-			test(
-				'should select nearest object which allows text ([]o) - both',
-				'[]<blockWidget></blockWidget><paragraph></paragraph>',
-				'both',
-				'[<blockWidget></blockWidget>]<paragraph></paragraph>'
-			);
-		} );
-
-		function test( testName, data, direction, expected ) {
-			it( testName, () => {
-				setData( model, data );
-				const range = doc.getNearestSelectionRange( selection.anchor, direction );
-
-				if ( expected === null ) {
-					expect( range ).to.be.null;
-				} else {
-					model.change( writer => {
-						writer.setSelection( range );
-					} );
-					expect( getData( model ) ).to.equal( expected );
-				}
-			} );
-		}
-	} );
-
 	describe( '_getDefaultRoot()', () => {
 		it( 'should return graveyard root if there are no other roots in the document', () => {
 			expect( doc._getDefaultRoot() ).to.equal( doc.graveyard );

+ 235 - 0
packages/ckeditor5-engine/tests/model/schema.js

@@ -1118,6 +1118,241 @@ describe( 'Schema', () => {
 		} );
 	} );
 
+	describe( 'getNearestSelectionRange()', () => {
+		let selection, model, doc;
+
+		beforeEach( () => {
+			model = new Model();
+			doc = model.document;
+			schema = model.schema;
+			model.schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+
+			model.schema.register( 'emptyBlock', { allowIn: '$root' } );
+
+			model.schema.register( 'widget', {
+				allowIn: '$root',
+				isObject: true
+			} );
+
+			model.schema.register( 'blockWidget', {
+				allowIn: '$root',
+				allowContentOf: '$block',
+				isObject: true
+			} );
+
+			doc.createRoot();
+			selection = doc.selection;
+		} );
+
+		test(
+			'should return collapsed range if text node can be placed at that position - both',
+			'<paragraph>[]</paragraph>',
+			'both',
+			'<paragraph>[]</paragraph>'
+		);
+
+		test(
+			'should return collapsed range if text node can be placed at that position - forward',
+			'<paragraph>[]</paragraph>',
+			'forward',
+			'<paragraph>[]</paragraph>'
+		);
+
+		test(
+			'should return collapsed range if text node can be placed at that position - backward',
+			'<paragraph>[]</paragraph>',
+			'backward',
+			'<paragraph>[]</paragraph>'
+		);
+
+		test( 'should return null in empty document - both', '', 'both', null );
+
+		test( 'should return null in empty document - backward', '', 'backward', null );
+
+		test( 'should return null in empty document - forward', '', 'forward', null );
+
+		test(
+			'should find range before when searching both ways',
+			'<paragraph></paragraph>[]<paragraph></paragraph>',
+			'both',
+			'<paragraph>[]</paragraph><paragraph></paragraph>'
+		);
+
+		test(
+			'should find range before when searching backward',
+			'<paragraph></paragraph>[]<paragraph></paragraph>',
+			'backward',
+			'<paragraph>[]</paragraph><paragraph></paragraph>'
+		);
+
+		test(
+			'should find range after when searching forward',
+			'<paragraph></paragraph>[]<paragraph></paragraph>',
+			'forward',
+			'<paragraph></paragraph><paragraph>[]</paragraph>'
+		);
+
+		test(
+			'should find range after when searching both ways when it is closer',
+			'<paragraph></paragraph><emptyBlock></emptyBlock>[]<paragraph></paragraph>',
+			'both',
+			'<paragraph></paragraph><emptyBlock></emptyBlock><paragraph>[]</paragraph>'
+		);
+
+		test(
+			'should find range before when searching both ways when it is closer',
+			'<paragraph></paragraph><emptyBlock></emptyBlock>[]<emptyBlock></emptyBlock><emptyBlock></emptyBlock><paragraph></paragraph>',
+			'both',
+			'<paragraph>[]</paragraph><emptyBlock></emptyBlock><emptyBlock></emptyBlock><emptyBlock></emptyBlock><paragraph></paragraph>'
+		);
+
+		test(
+			'should return null if there is no valid range',
+			'[]<emptyBlock></emptyBlock>',
+			'both',
+			null
+		);
+
+		test(
+			'should return null if there is no valid range in given direction - backward',
+			'[]<paragraph></paragraph>',
+			'backward',
+			null
+		);
+
+		test(
+			'should return null if there is no valid range in given direction - forward',
+			'<paragraph></paragraph>[]',
+			'forward',
+			null
+		);
+
+		test(
+			'should move forward when placed at root start',
+			'[]<paragraph></paragraph><paragraph></paragraph>',
+			'both',
+			'<paragraph>[]</paragraph><paragraph></paragraph>'
+		);
+
+		test(
+			'should move backward when placed at root end',
+			'<paragraph></paragraph><paragraph></paragraph>[]',
+			'both',
+			'<paragraph></paragraph><paragraph>[]</paragraph>'
+		);
+
+		describe( 'in case of objects which do not allow text inside', () => {
+			test(
+				'should select nearest object (o[]o) - both',
+				'<widget></widget>[]<widget></widget>',
+				'both',
+				'[<widget></widget>]<widget></widget>'
+			);
+
+			test(
+				'should select nearest object (o[]o) - forward',
+				'<widget></widget>[]<widget></widget>',
+				'forward',
+				'<widget></widget>[<widget></widget>]'
+			);
+
+			test(
+				'should select nearest object (o[]o) - backward',
+				'<widget></widget>[]<widget></widget>',
+				'both',
+				'[<widget></widget>]<widget></widget>'
+			);
+
+			test(
+				'should select nearest object (p[]o) - forward',
+				'<paragraph></paragraph>[]<widget></widget>',
+				'forward',
+				'<paragraph></paragraph>[<widget></widget>]'
+			);
+
+			test(
+				'should select nearest object (o[]p) - both',
+				'<widget></widget>[]<paragraph></paragraph>',
+				'both',
+				'[<widget></widget>]<paragraph></paragraph>'
+			);
+
+			test(
+				'should select nearest object (o[]p) - backward',
+				'<widget></widget>[]<paragraph></paragraph>',
+				'backward',
+				'[<widget></widget>]<paragraph></paragraph>'
+			);
+
+			test(
+				'should select nearest object ([]o) - both',
+				'[]<widget></widget><paragraph></paragraph>',
+				'both',
+				'[<widget></widget>]<paragraph></paragraph>'
+			);
+
+			test(
+				'should select nearest object ([]o) - forward',
+				'[]<widget></widget><paragraph></paragraph>',
+				'forward',
+				'[<widget></widget>]<paragraph></paragraph>'
+			);
+
+			test(
+				'should select nearest object (o[]) - both',
+				'<paragraph></paragraph><widget></widget>[]',
+				'both',
+				'<paragraph></paragraph>[<widget></widget>]'
+			);
+
+			test(
+				'should select nearest object (o[]) - backward',
+				'<paragraph></paragraph><widget></widget>[]',
+				'both',
+				'<paragraph></paragraph>[<widget></widget>]'
+			);
+		} );
+
+		describe( 'in case of objects which allow text inside', () => {
+			test(
+				'should select nearest object which allows text (o[]o) - both',
+				'<blockWidget></blockWidget>[]<blockWidget></blockWidget>',
+				'both',
+				'[<blockWidget></blockWidget>]<blockWidget></blockWidget>'
+			);
+
+			test(
+				'should select nearest object (o[]p) - both',
+				'<blockWidget></blockWidget>[]<paragraph></paragraph>',
+				'both',
+				'[<blockWidget></blockWidget>]<paragraph></paragraph>'
+			);
+
+			test(
+				'should select nearest object which allows text ([]o) - both',
+				'[]<blockWidget></blockWidget><paragraph></paragraph>',
+				'both',
+				'[<blockWidget></blockWidget>]<paragraph></paragraph>'
+			);
+		} );
+
+		function test( testName, data, direction, expected ) {
+			it( testName, () => {
+				setData( model, data );
+				const range = schema.getNearestSelectionRange( selection.anchor, direction );
+
+				if ( expected === null ) {
+					expect( range ).to.be.null;
+				} else {
+					model.change( writer => {
+						writer.setSelection( range );
+					} );
+					expect( getData( model ) ).to.equal( expected );
+				}
+			} );
+		}
+	} );
+
 	describe( 'removeDisallowedAttributes()', () => {
 		let model, doc, root;