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

Commit

Permalink
Merge 9832e55 into 04858be
Browse files Browse the repository at this point in the history
  • Loading branch information
jodator authored Aug 2, 2019
2 parents 04858be + 9832e55 commit a1c4745
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 174 deletions.
43 changes: 20 additions & 23 deletions src/model/documentselection.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,37 +237,22 @@ export default class DocumentSelection {
*
* This method's result can be used for example to apply block styling to all blocks covered by this selection.
*
* **Note:** `getSelectedBlocks()` always returns the deepest block.
* **Note:** `getSelectedBlocks()` returns blocks that are nested in other non-block elements
* but will not return blocks nested in other blocks.
*
* In this case the function will return exactly all 3 paragraphs:
*
* <paragraph>[a</paragraph>
* <quote>
* <blockQuote>
* <paragraph>b</paragraph>
* </quote>
* </blockQuote>
* <paragraph>c]d</paragraph>
*
* In this case the paragraph will also be returned, despite the collapsed selection:
*
* <paragraph>[]a</paragraph>
*
* **Special case**: If a selection ends at the beginning of a block, that block is not returned as from user perspective
* this block wasn't selected. See [#984](https://github.com/ckeditor/ckeditor5-engine/issues/984) for more details.
*
* <paragraph>[a</paragraph>
* <paragraph>b</paragraph>
* <paragraph>]c</paragraph> // this block will not be returned
*
* @returns {Iterable.<module:engine/model/element~Element>}
*/
getSelectedBlocks() {
return this._selection.getSelectedBlocks();
}

/**
* Returns blocks that aren't nested in other selected blocks.
*
* In this case the method will return blocks A, B and E because C & D are children of block B:
* In such scenario however, only blocks A, B & E will be returned as blocks C & D are nested in block B:
*
* [<blockA></blockA>
* <blockB>
Expand All @@ -276,12 +261,24 @@ export default class DocumentSelection {
* </blockB>
* <blockE></blockE>]
*
* **Note:** To get all selected blocks use {@link #getSelectedBlocks `getSelectedBlocks()`}.
* If the selection is inside a block all the inner blocks (A & B) are returned:
*
* <block>
* <blockA>[a</blockA>
* <blockB>b]</blockB>
* </block>
*
* **Special case**: If a selection ends at the beginning of a block, that block is not returned as from user perspective
* this block wasn't selected. See [#984](https://github.com/ckeditor/ckeditor5-engine/issues/984) for more details.
*
* <paragraph>[a</paragraph>
* <paragraph>b</paragraph>
* <paragraph>]c</paragraph> // this block will not be returned
*
* @returns {Iterable.<module:engine/model/element~Element>}
*/
getTopMostBlocks() {
return this._selection.getTopMostBlocks();
getSelectedBlocks() {
return this._selection.getSelectedBlocks();
}

/**
Expand Down
89 changes: 51 additions & 38 deletions src/model/selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -644,20 +644,37 @@ export default class Selection {
*
* This method's result can be used for example to apply block styling to all blocks covered by this selection.
*
* **Note:** `getSelectedBlocks()` always returns the deepest block.
* **Note:** `getSelectedBlocks()` returns blocks that are nested in other non-block elements
* but will not return blocks nested in other blocks.
*
* In this case the function will return exactly all 3 paragraphs:
*
* <paragraph>[a</paragraph>
* <quote>
* <blockQuote>
* <paragraph>b</paragraph>
* </quote>
* </blockQuote>
* <paragraph>c]d</paragraph>
*
* In this case the paragraph will also be returned, despite the collapsed selection:
*
* <paragraph>[]a</paragraph>
*
* In such scenario however, only blocks A, B & E will be returned as blocks C & D are nested in block B:
*
* [<blockA></blockA>
* <blockB>
* <blockC></blockC>
* <blockD></blockD>
* </blockB>
* <blockE></blockE>]
*
* If the selection is inside a block all the inner blocks (A & B) are returned:
*
* <block>
* <blockA>[a</blockA>
* <blockB>b]</blockB>
* </block>
*
* **Special case**: If a selection ends at the beginning of a block, that block is not returned as from user perspective
* this block wasn't selected. See [#984](https://github.com/ckeditor/ckeditor5-engine/issues/984) for more details.
*
Expand All @@ -671,56 +688,30 @@ export default class Selection {
const visited = new WeakSet();

for ( const range of this.getRanges() ) {
// Get start block of range in case of a collapsed range.
const startBlock = getParentBlock( range.start, visited );

if ( startBlock ) {
if ( startBlock && isTopBlockInRange( startBlock, range ) ) {
yield startBlock;
}

for ( const value of range.getWalker() ) {
if ( value.type == 'elementEnd' && isUnvisitedBlockContainer( value.item, visited ) ) {
yield value.item;
const block = value.item;

if ( value.type == 'elementEnd' && isUnvisitedTopBlock( block, visited, range ) ) {
yield block;
}
}

const endBlock = getParentBlock( range.end, visited );

// #984. Don't return the end block if the range ends right at its beginning.
if ( endBlock && !range.end.isTouching( Position._createAt( endBlock, 0 ) ) ) {
if ( endBlock && !range.end.isTouching( Position._createAt( endBlock, 0 ) ) && isTopBlockInRange( endBlock, range ) ) {
yield endBlock;
}
}
}

/**
* Returns blocks that aren't nested in other selected blocks.
*
* In this case the method will return blocks A, B and E because C & D are children of block B:
*
* [<blockA></blockA>
* <blockB>
* <blockC></blockC>
* <blockD></blockD>
* </blockB>
* <blockE></blockE>]
*
* **Note:** To get all selected blocks use {@link #getSelectedBlocks `getSelectedBlocks()`}.
*
* @returns {Iterable.<module:engine/model/element~Element>}
*/
* getTopMostBlocks() {
const selected = Array.from( this.getSelectedBlocks() );

for ( const block of selected ) {
const parentBlock = findAncestorBlock( block );

// Filter out blocks that are nested in other selected blocks (like paragraphs in tables).
if ( !parentBlock || !selected.includes( parentBlock ) ) {
yield block;
}
}
}

/**
* Checks whether the selection contains the entire content of the given element. This means that selection must start
* at a position {@link module:engine/model/position~Position#isTouching touching} the element's start and ends at position
Expand Down Expand Up @@ -830,7 +821,7 @@ mix( Selection, EmitterMixin );

// Checks whether the given element extends $block in the schema and has a parent (is not a root).
// Marks it as already visited.
function isUnvisitedBlockContainer( element, visited ) {
function isUnvisitedBlock( element, visited ) {
if ( visited.has( element ) ) {
return false;
}
Expand All @@ -840,6 +831,11 @@ function isUnvisitedBlockContainer( element, visited ) {
return element.document.model.schema.isBlock( element ) && element.parent;
}

// Checks if the given element is a $block was not previously visited and is a top block in a range.
function isUnvisitedTopBlock( element, visited, range ) {
return isUnvisitedBlock( element, visited ) && isTopBlockInRange( element, range );
}

// Finds the lowest element in position's ancestors which is a block.
// It will search until first ancestor that is a limit element.
// Marks all ancestors as already visited to not include any of them later on.
Expand All @@ -858,7 +854,7 @@ function getParentBlock( position, visited ) {

hasParentLimit = schema.isLimit( element );

return !hasParentLimit && isUnvisitedBlockContainer( element, visited );
return !hasParentLimit && isUnvisitedBlock( element, visited );
} );

// Mark all ancestors of this position's parent, because find() might've stopped early and
Expand All @@ -868,6 +864,23 @@ function getParentBlock( position, visited ) {
return block;
}

// Checks if the blocks is not nested in other block inside a range.
//
// @param {module:engine/model/elmenent~Element} block Block to check.
// @param {module:engine/model/range~Range} range Range to check.
function isTopBlockInRange( block, range ) {
const parentBlock = findAncestorBlock( block );

if ( !parentBlock ) {
return true;
}

// Add loose flag to check as parentRange can be equal to range.
const isParentInRange = range.containsRange( Range._createOn( parentBlock ), true );

return !isParentInRange;
}

// Returns first ancestor block of a node.
//
// @param {module:engine/model/node~Node} node
Expand Down
Loading

0 comments on commit a1c4745

Please sign in to comment.