ソースを参照

Merge pull request #1044 from ckeditor/t/1042

Feature: Introduced `Schema#getLimitElement()`. Closes #1042.
Piotrek Koszuliński 8 年 前
コミット
e64ed50db1

+ 2 - 17
packages/ckeditor5-engine/src/controller/deletecontent.js

@@ -168,21 +168,6 @@ function checkCanBeMerged( leftPos, rightPos ) {
 	return true;
 }
 
-// Returns the lowest limit element defined in `Schema.limits` for passed selection.
-function getLimitElement( schema, selection ) {
-	let element = selection.getFirstRange().getCommonAncestor();
-
-	while ( !schema.limits.has( element.name ) ) {
-		if ( element.parent ) {
-			element = element.parent;
-		} else {
-			break;
-		}
-	}
-
-	return element;
-}
-
 function insertParagraph( batch, position, selection ) {
 	const paragraph = new Element( 'paragraph' );
 	batch.insert( position, paragraph );
@@ -191,7 +176,7 @@ function insertParagraph( batch, position, selection ) {
 }
 
 function replaceEntireContentWithParagraph( batch, selection ) {
-	const limitElement = getLimitElement( batch.document.schema, selection );
+	const limitElement = batch.document.schema.getLimitElement( selection );
 
 	batch.remove( Range.createIn( limitElement ) );
 	insertParagraph( batch, Position.createAt( limitElement ), selection );
@@ -202,7 +187,7 @@ function replaceEntireContentWithParagraph( batch, selection ) {
 // * selection contains at least two elements,
 // * whether the paragraph is allowed in schema in the common ancestor.
 function shouldEntireContentBeReplacedWithParagraph( schema, selection ) {
-	const limitElement = getLimitElement( schema, selection );
+	const limitElement = schema.getLimitElement( selection );
 	const limitStartPosition = Position.createAt( limitElement );
 	const limitEndPosition = Position.createAt( limitElement, 'end' );
 

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

@@ -383,6 +383,35 @@ export default class Schema {
 	}
 
 	/**
+	 * Returns the lowest {@link module:engine/model/schema~Schema#limits limit element} containing the entire
+	 * selection or the root otherwise.
+	 *
+	 * @param {module:engine/model/selection~Selection} selection Selection which returns the common ancestor.
+	 * @returns {module:engine/model/element~Element}
+	 */
+	getLimitElement( selection ) {
+		// Find the common ancestor for all selection's ranges.
+		let element = Array.from( selection.getRanges() )
+			.reduce( ( node, range ) => {
+				if ( !node ) {
+					return range.getCommonAncestor();
+				}
+
+				return node.getCommonAncestor( range.getCommonAncestor() );
+			}, null );
+
+		while ( !this.limits.has( element.name ) ) {
+			if ( element.parent ) {
+				element = element.parent;
+			} else {
+				break;
+			}
+		}
+
+		return element;
+	}
+
+	/**
 	 * Returns {@link module:engine/model/schema~SchemaItem schema item} that was registered in the schema under given name.
 	 * If item has not been found, throws error.
 	 *

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

@@ -641,4 +641,100 @@ describe( 'Schema', () => {
 			expect( result[ 1 ].end.path ).to.members( [ 1 ] );
 		} );
 	} );
+
+	describe( 'getLimitElement()', () => {
+		let doc, root;
+
+		beforeEach( () => {
+			doc = new Document();
+			schema = doc.schema;
+			root = doc.createRoot();
+
+			schema.registerItem( 'div', '$block' );
+			schema.registerItem( 'article', '$block' );
+			schema.registerItem( 'section', '$block' );
+			schema.registerItem( 'paragraph', '$block' );
+			schema.registerItem( 'widget', '$block' );
+			schema.registerItem( 'image', '$block' );
+			schema.registerItem( 'caption', '$block' );
+			schema.allow( { name: 'image', inside: 'widget' } );
+			schema.allow( { name: 'caption', inside: 'image' } );
+			schema.allow( { name: 'paragraph', inside: 'article' } );
+			schema.allow( { name: 'article', inside: 'section' } );
+			schema.allow( { name: 'section', inside: 'div' } );
+			schema.allow( { name: 'widget', inside: 'div' } );
+		} );
+
+		it( 'always returns $root element if any other limit was not defined', () => {
+			schema.limits.clear();
+
+			setData( doc, '<div><section><article><paragraph>foo[]bar</paragraph></article></section></div>' );
+			expect( schema.getLimitElement( doc.selection ) ).to.equal( root );
+		} );
+
+		it( 'returns the limit element which is the closest element to common ancestor for collapsed selection', () => {
+			schema.limits.add( 'article' );
+			schema.limits.add( 'section' );
+
+			setData( doc, '<div><section><article><paragraph>foo[]bar</paragraph></article></section></div>' );
+
+			const article = root.getNodeByPath( [ 0, 0, 0 ] );
+
+			expect( schema.getLimitElement( doc.selection ) ).to.equal( article );
+		} );
+
+		it( 'returns the limit element which is the closest element to common ancestor for non-collapsed selection', () => {
+			schema.limits.add( 'article' );
+			schema.limits.add( 'section' );
+
+			setData( doc, '<div><section><article>[foo</article><article>bar]</article></section></div>' );
+
+			const section = root.getNodeByPath( [ 0, 0 ] );
+
+			expect( schema.getLimitElement( doc.selection ) ).to.equal( section );
+		} );
+
+		it( 'works fine with multi-range selections', () => {
+			schema.limits.add( 'article' );
+			schema.limits.add( 'widget' );
+			schema.limits.add( 'div' );
+
+			setData(
+				doc,
+				'<div>' +
+					'<section>' +
+						'<article>' +
+							'<paragraph>[foo]</paragraph>' +
+						'</article>' +
+					'</section>' +
+					'<widget>' +
+						'<image>' +
+							'<caption>b[a]r</caption>' +
+						'</image>' +
+					'</widget>' +
+				'</div>'
+			);
+
+			const div = root.getNodeByPath( [ 0 ] );
+			expect( schema.getLimitElement( doc.selection ) ).to.equal( div );
+		} );
+
+		it( 'works fine with multi-range selections even if limit elements are not defined', () => {
+			schema.limits.clear();
+
+			setData(
+				doc,
+				'<div>' +
+					'<section>' +
+						'<article>' +
+							'<paragraph>[foo]</paragraph>' +
+						'</article>' +
+					'</section>' +
+				'</div>' +
+				'<section>b[]ar</section>'
+			);
+
+			expect( schema.getLimitElement( doc.selection ) ).to.equal( root );
+		} );
+	} );
 } );