Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 691e53e

Browse files
authored
Merge pull request #1044 from ckeditor/t/1042
Feature: Introduced `Schema#getLimitElement()`. Closes #1042.
2 parents 9f7e0a2 + c1479a4 commit 691e53e

File tree

3 files changed

+127
-17
lines changed

3 files changed

+127
-17
lines changed

src/controller/deletecontent.js

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -168,21 +168,6 @@ function checkCanBeMerged( leftPos, rightPos ) {
168168
return true;
169169
}
170170

171-
// Returns the lowest limit element defined in `Schema.limits` for passed selection.
172-
function getLimitElement( schema, selection ) {
173-
let element = selection.getFirstRange().getCommonAncestor();
174-
175-
while ( !schema.limits.has( element.name ) ) {
176-
if ( element.parent ) {
177-
element = element.parent;
178-
} else {
179-
break;
180-
}
181-
}
182-
183-
return element;
184-
}
185-
186171
function insertParagraph( batch, position, selection ) {
187172
const paragraph = new Element( 'paragraph' );
188173
batch.insert( position, paragraph );
@@ -191,7 +176,7 @@ function insertParagraph( batch, position, selection ) {
191176
}
192177

193178
function replaceEntireContentWithParagraph( batch, selection ) {
194-
const limitElement = getLimitElement( batch.document.schema, selection );
179+
const limitElement = batch.document.schema.getLimitElement( selection );
195180

196181
batch.remove( Range.createIn( limitElement ) );
197182
insertParagraph( batch, Position.createAt( limitElement ), selection );
@@ -202,7 +187,7 @@ function replaceEntireContentWithParagraph( batch, selection ) {
202187
// * selection contains at least two elements,
203188
// * whether the paragraph is allowed in schema in the common ancestor.
204189
function shouldEntireContentBeReplacedWithParagraph( schema, selection ) {
205-
const limitElement = getLimitElement( schema, selection );
190+
const limitElement = schema.getLimitElement( selection );
206191
const limitStartPosition = Position.createAt( limitElement );
207192
const limitEndPosition = Position.createAt( limitElement, 'end' );
208193

src/model/schema.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,35 @@ export default class Schema {
382382
return validRanges;
383383
}
384384

385+
/**
386+
* Returns the lowest {@link module:engine/model/schema~Schema#limits limit element} containing the entire
387+
* selection or the root otherwise.
388+
*
389+
* @param {module:engine/model/selection~Selection} selection Selection which returns the common ancestor.
390+
* @returns {module:engine/model/element~Element}
391+
*/
392+
getLimitElement( selection ) {
393+
// Find the common ancestor for all selection's ranges.
394+
let element = Array.from( selection.getRanges() )
395+
.reduce( ( node, range ) => {
396+
if ( !node ) {
397+
return range.getCommonAncestor();
398+
}
399+
400+
return node.getCommonAncestor( range.getCommonAncestor() );
401+
}, null );
402+
403+
while ( !this.limits.has( element.name ) ) {
404+
if ( element.parent ) {
405+
element = element.parent;
406+
} else {
407+
break;
408+
}
409+
}
410+
411+
return element;
412+
}
413+
385414
/**
386415
* Returns {@link module:engine/model/schema~SchemaItem schema item} that was registered in the schema under given name.
387416
* If item has not been found, throws error.

tests/model/schema/schema.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,4 +641,100 @@ describe( 'Schema', () => {
641641
expect( result[ 1 ].end.path ).to.members( [ 1 ] );
642642
} );
643643
} );
644+
645+
describe( 'getLimitElement()', () => {
646+
let doc, root;
647+
648+
beforeEach( () => {
649+
doc = new Document();
650+
schema = doc.schema;
651+
root = doc.createRoot();
652+
653+
schema.registerItem( 'div', '$block' );
654+
schema.registerItem( 'article', '$block' );
655+
schema.registerItem( 'section', '$block' );
656+
schema.registerItem( 'paragraph', '$block' );
657+
schema.registerItem( 'widget', '$block' );
658+
schema.registerItem( 'image', '$block' );
659+
schema.registerItem( 'caption', '$block' );
660+
schema.allow( { name: 'image', inside: 'widget' } );
661+
schema.allow( { name: 'caption', inside: 'image' } );
662+
schema.allow( { name: 'paragraph', inside: 'article' } );
663+
schema.allow( { name: 'article', inside: 'section' } );
664+
schema.allow( { name: 'section', inside: 'div' } );
665+
schema.allow( { name: 'widget', inside: 'div' } );
666+
} );
667+
668+
it( 'always returns $root element if any other limit was not defined', () => {
669+
schema.limits.clear();
670+
671+
setData( doc, '<div><section><article><paragraph>foo[]bar</paragraph></article></section></div>' );
672+
expect( schema.getLimitElement( doc.selection ) ).to.equal( root );
673+
} );
674+
675+
it( 'returns the limit element which is the closest element to common ancestor for collapsed selection', () => {
676+
schema.limits.add( 'article' );
677+
schema.limits.add( 'section' );
678+
679+
setData( doc, '<div><section><article><paragraph>foo[]bar</paragraph></article></section></div>' );
680+
681+
const article = root.getNodeByPath( [ 0, 0, 0 ] );
682+
683+
expect( schema.getLimitElement( doc.selection ) ).to.equal( article );
684+
} );
685+
686+
it( 'returns the limit element which is the closest element to common ancestor for non-collapsed selection', () => {
687+
schema.limits.add( 'article' );
688+
schema.limits.add( 'section' );
689+
690+
setData( doc, '<div><section><article>[foo</article><article>bar]</article></section></div>' );
691+
692+
const section = root.getNodeByPath( [ 0, 0 ] );
693+
694+
expect( schema.getLimitElement( doc.selection ) ).to.equal( section );
695+
} );
696+
697+
it( 'works fine with multi-range selections', () => {
698+
schema.limits.add( 'article' );
699+
schema.limits.add( 'widget' );
700+
schema.limits.add( 'div' );
701+
702+
setData(
703+
doc,
704+
'<div>' +
705+
'<section>' +
706+
'<article>' +
707+
'<paragraph>[foo]</paragraph>' +
708+
'</article>' +
709+
'</section>' +
710+
'<widget>' +
711+
'<image>' +
712+
'<caption>b[a]r</caption>' +
713+
'</image>' +
714+
'</widget>' +
715+
'</div>'
716+
);
717+
718+
const div = root.getNodeByPath( [ 0 ] );
719+
expect( schema.getLimitElement( doc.selection ) ).to.equal( div );
720+
} );
721+
722+
it( 'works fine with multi-range selections even if limit elements are not defined', () => {
723+
schema.limits.clear();
724+
725+
setData(
726+
doc,
727+
'<div>' +
728+
'<section>' +
729+
'<article>' +
730+
'<paragraph>[foo]</paragraph>' +
731+
'</article>' +
732+
'</section>' +
733+
'</div>' +
734+
'<section>b[]ar</section>'
735+
);
736+
737+
expect( schema.getLimitElement( doc.selection ) ).to.equal( root );
738+
} );
739+
} );
644740
} );

0 commit comments

Comments
 (0)