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

Commit 7bc0338

Browse files
authoredFeb 12, 2019
Merge pull request #1645 from ckeditor/t/ckeditor5-table/163
Fix: `Selection#getTopMostBlocks()` should not leak from limit elements. Closes ckeditor/ckeditor5-table#163.
2 parents 4f9ac0e + 5c51493 commit 7bc0338

File tree

2 files changed

+54
-17
lines changed

2 files changed

+54
-17
lines changed
 

‎src/model/selection.js

+16-1
Original file line numberDiff line numberDiff line change
@@ -826,10 +826,25 @@ function isUnvisitedBlockContainer( element, visited ) {
826826
}
827827

828828
// Finds the lowest element in position's ancestors which is a block.
829+
// It will search until first ancestor that is a limit element.
829830
// Marks all ancestors as already visited to not include any of them later on.
830831
function getParentBlock( position, visited ) {
832+
const schema = position.parent.document.model.schema;
833+
831834
const ancestors = position.parent.getAncestors( { parentFirst: true, includeSelf: true } );
832-
const block = ancestors.find( element => isUnvisitedBlockContainer( element, visited ) );
835+
836+
let hasParentLimit = false;
837+
838+
const block = ancestors.find( element => {
839+
// Stop searching after first parent node that is limit element.
840+
if ( hasParentLimit ) {
841+
return false;
842+
}
843+
844+
hasParentLimit = schema.isLimit( element );
845+
846+
return !hasParentLimit && isUnvisitedBlockContainer( element, visited );
847+
} );
833848

834849
// Mark all ancestors of this position's parent, because find() might've stopped early and
835850
// the found block may be a child of another block.

‎tests/model/selection.js

+38-16
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,20 @@ describe( 'Selection', () => {
11171117
);
11181118
} );
11191119

1120+
it( 'does not go beyond limit elements', () => {
1121+
model.schema.register( 'table', { isBlock: true, isLimit: true, isObject: true, allowIn: '$root' } );
1122+
model.schema.register( 'tableRow', { allowIn: 'table', isLimit: true } );
1123+
model.schema.register( 'tableCell', { allowIn: 'tableRow', isObject: true } );
1124+
1125+
model.schema.register( 'blk', { allowIn: [ '$root', 'tableCell' ], isObject: true, isBlock: true } );
1126+
1127+
model.schema.extend( 'p', { allowIn: 'tableCell' } );
1128+
1129+
setData( model, '<table><tableRow><tableCell><p>foo</p>[<blk></blk><p>bar]</p></tableCell></tableRow></table>' );
1130+
1131+
expect( stringifyBlocks( doc.selection.getTopMostBlocks() ) ).to.deep.equal( [ 'blk', 'p#bar' ] );
1132+
} );
1133+
11201134
// Map all elements to data of its first child text node.
11211135
function toText( elements ) {
11221136
return Array.from( elements ).map( el => {
@@ -1128,11 +1142,11 @@ describe( 'Selection', () => {
11281142
describe( 'getTopMostBlocks()', () => {
11291143
beforeEach( () => {
11301144
model.schema.register( 'p', { inheritAllFrom: '$block' } );
1131-
model.schema.register( 'lvl0', { isBlock: true, isLimit: true, isObject: true, allowIn: '$root' } );
1132-
model.schema.register( 'lvl1', { allowIn: 'lvl0', isLimit: true } );
1133-
model.schema.register( 'lvl2', { allowIn: 'lvl1', isObject: true } );
1145+
model.schema.register( 'table', { isBlock: true, isLimit: true, isObject: true, allowIn: '$root' } );
1146+
model.schema.register( 'tableRow', { allowIn: 'table', isLimit: true } );
1147+
model.schema.register( 'tableCell', { allowIn: 'tableRow', isObject: true } );
11341148

1135-
model.schema.extend( 'p', { allowIn: 'lvl2' } );
1149+
model.schema.extend( 'p', { allowIn: 'tableCell' } );
11361150
} );
11371151

11381152
it( 'returns an iterator', () => {
@@ -1169,28 +1183,24 @@ describe( 'Selection', () => {
11691183
} );
11701184

11711185
it( 'returns only top most blocks', () => {
1172-
setData( model, '[<p>foo</p><lvl0><lvl1><lvl2><p>bar</p></lvl2></lvl1></lvl0><p>baz</p>]' );
1186+
setData( model, '[<p>foo</p><table><tableRow><tableCell><p>bar</p></tableCell></tableRow></table><p>baz</p>]' );
11731187

1174-
expect( stringifyBlocks( doc.selection.getTopMostBlocks() ) ).to.deep.equal( [ 'p#foo', 'lvl0', 'p#baz' ] );
1188+
expect( stringifyBlocks( doc.selection.getTopMostBlocks() ) ).to.deep.equal( [ 'p#foo', 'table', 'p#baz' ] );
11751189
} );
11761190

11771191
it( 'returns only selected blocks even if nested in other blocks', () => {
1178-
setData( model, '<p>foo</p><lvl0><lvl1><lvl2><p>[b]ar</p></lvl2></lvl1></lvl0><p>baz</p>' );
1192+
setData( model, '<p>foo</p><table><tableRow><tableCell><p>[b]ar</p></tableCell></tableRow></table><p>baz</p>' );
11791193

11801194
expect( stringifyBlocks( doc.selection.getTopMostBlocks() ) ).to.deep.equal( [ 'p#bar' ] );
11811195
} );
11821196

1183-
// Map all elements to names. If element contains child text node it will be appended to name with '#'.
1184-
function stringifyBlocks( elements ) {
1185-
return Array.from( elements ).map( el => {
1186-
const name = el.name;
1197+
it( 'returns only selected blocks even if nested in other blocks (selection on the block)', () => {
1198+
model.schema.register( 'blk', { allowIn: [ '$root', 'tableCell' ], isObject: true, isBlock: true } );
11871199

1188-
const firstChild = el.getChild( 0 );
1189-
const hasText = firstChild && firstChild.data;
1200+
setData( model, '<table><tableRow><tableCell><p>foo</p>[<blk></blk><p>bar]</p></tableCell></tableRow></table>' );
11901201

1191-
return hasText ? `${ name }#${ firstChild.data }` : name;
1192-
} );
1193-
}
1202+
expect( stringifyBlocks( doc.selection.getTopMostBlocks() ) ).to.deep.equal( [ 'blk', 'p#bar' ] );
1203+
} );
11941204
} );
11951205

11961206
describe( 'attributes interface', () => {
@@ -1366,4 +1376,16 @@ describe( 'Selection', () => {
13661376
expect( doc.selection.containsEntireContent() ).to.equal( false );
13671377
} );
13681378
} );
1379+
1380+
// Map all elements to names. If element contains child text node it will be appended to name with '#'.
1381+
function stringifyBlocks( elements ) {
1382+
return Array.from( elements ).map( el => {
1383+
const name = el.name;
1384+
1385+
const firstChild = el.getChild( 0 );
1386+
const hasText = firstChild && firstChild.data;
1387+
1388+
return hasText ? `${ name }#${ firstChild.data }` : name;
1389+
} );
1390+
}
13691391
} );

0 commit comments

Comments
 (0)
This repository has been archived.