Skip to content

Commit

Permalink
Merge pull request #6816 from ckeditor/i/6621-select-all
Browse files Browse the repository at this point in the history
Other: Improved the select-all feature so that it includes more and more content if the selection was anchored in a nested editable. Closes #6621.
  • Loading branch information
oleq committed May 28, 2020
2 parents 06a1e81 + a1ee014 commit 6f59c78
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 5 deletions.
6 changes: 5 additions & 1 deletion packages/ckeditor5-select-all/docs/features/select-all.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ The {@link module:select-all/selectall~SelectAll} feature allows selecting the e

## Demo

Press <kbd>Ctrl/⌘</kbd>+<kbd>A</kbd> or use the toolbar button to select the entire content of the editor. Note that when editing an {@link features/image#image-captions image caption}, the selection will only expand to the boundaries of the caption.
Press <kbd>Ctrl/⌘</kbd>+<kbd>A</kbd> or use the toolbar button to select the entire content of the editor.

{@snippet features/select-all}

<info-box>
When the selection is inside the {@link features/image#image-captions image caption}, it will only expand to the boundaries of the caption. Use the keystroke or the toolbar button again to include more content until the entire content of the editor is selected. The same rule applies, for instance, when the selection is inside a table cell or any self–contained (nested) editable region in the content.
</info-box>

## Installation

<info-box info>
Expand Down
32 changes: 29 additions & 3 deletions packages/ckeditor5-select-all/src/selectallcommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import Command from '@ckeditor/ckeditor5-core/src/command';
* {@link module:engine/model/selection~Selection#anchor anchored} in.
*
* If the selection was anchored in a {@glink framework/guides/tutorials/implementing-a-block-widget nested editable}
* (e.g. a caption of an image), the new selection will contain its entire content.
* (e.g. a caption of an image), the new selection will contain its entire content. Successive executions of this command
* will expand the selection to encompass more and more content up to the entire editable root of the editor.
*
* @extends module:core/command~Command
*/
Expand All @@ -30,10 +31,35 @@ export default class SelectAllCommand extends Command {
*/
execute() {
const model = this.editor.model;
const limitElement = model.schema.getLimitElement( model.document.selection );
const selection = model.document.selection;
let scopeElement = model.schema.getLimitElement( selection );

// If an entire scope is selected, or the selection's ancestor is not a scope yet,
// browse through ancestors to find the enclosing parent scope.
if ( selection.containsEntireContent( scopeElement ) || !isSelectAllScope( model.schema, scopeElement ) ) {
do {
scopeElement = scopeElement.parent;

// Do nothing, if the entire `root` is already selected.
if ( !scopeElement ) {
return;
}
} while ( !isSelectAllScope( model.schema, scopeElement ) );
}

model.change( writer => {
writer.setSelection( limitElement, 'in' );
writer.setSelection( scopeElement, 'in' );
} );
}
}

// Checks whether the element is a valid select-all scope.
// Returns true, if the element is a {@link module:engine/model/schema~Schema#isLimit limit},
// and can contain any text or paragraph.
//
// @param {module:engine/model/schema~Schema} schema The schema to check against.
// @param {module:engine/model/element~Element} element
// @return {Boolean}
function isSelectAllScope( schema, element ) {
return schema.isLimit( element ) && ( schema.checkChild( element, '$text' ) || schema.checkChild( element, 'paragraph' ) );
}
116 changes: 115 additions & 1 deletion packages/ckeditor5-select-all/tests/selectallcommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,51 @@ describe( 'SelectAllCommand', () => {
} );

it( 'should select all (selection in a nested editable)', () => {
setData( model, '<paragraph>foo</paragraph><image src="foo.png"><caption>[bar]</caption></image>' );
setData( model, '<paragraph>foo</paragraph><image src="foo.png"><caption>b[ar]</caption></image>' );

editor.execute( 'selectAll' );

expect( getData( model ) ).to.equal( '<paragraph>foo</paragraph><image src="foo.png"><caption>[bar]</caption></image>' );
} );

it( 'should select all (selection within limit element)', () => {
setData( model,
'<paragraph>foo</paragraph>' +
'<table>' +
'<tableRow>' +
'<tableCell>' +
'<paragraph>foo</paragraph>' +
'</tableCell>' +
'[<tableCell>' +
'<paragraph>bar</paragraph>' +
'</tableCell>]' +
'[<tableCell>' +
'<paragraph>baz</paragraph>' +
'</tableCell>]' +
'</tableRow>' +
'</table>'
);

editor.execute( 'selectAll' );

expect( getData( model ) ).to.equal(
'<paragraph>[foo</paragraph>' +
'<table>' +
'<tableRow>' +
'<tableCell>' +
'<paragraph>foo</paragraph>' +
'</tableCell>' +
'<tableCell>' +
'<paragraph>bar</paragraph>' +
'</tableCell>' +
'<tableCell>' +
'<paragraph>baz</paragraph>' +
'</tableCell>' +
'</tableRow>' +
'</table>]'
);
} );

it( 'should select all in the closest nested editable (nested editable inside another nested editable)', () => {
setData( model,
'<paragraph>foo</paragraph>' +
Expand All @@ -103,5 +141,81 @@ describe( 'SelectAllCommand', () => {
'</table>'
);
} );

it( 'should select all in the parent select-all-limit element (the entire editable is selected)', () => {
setData( model, '<paragraph>foo</paragraph><image src="foo.png"><caption>[bar]</caption></image>' );

editor.execute( 'selectAll' );

expect( getData( model ) ).to.equal( '<paragraph>[foo</paragraph><image src="foo.png"><caption>bar</caption></image>]' );
} );

it( 'should select all in the parent sellect-all-limit element (consecutive execute() on a nested editable)', () => {
setData( model,
'<paragraph>foo</paragraph>' +
'<table>' +
'<tableRow>' +
'<tableCell>' +
'<paragraph>foo</paragraph>' +
'<image src="foo.png"><caption>b[]ar</caption></image>' +
'</tableCell>' +
'</tableRow>' +
'</table>'
);

editor.execute( 'selectAll' );
editor.execute( 'selectAll' );

expect( getData( model ) ).to.equal( '<paragraph>foo</paragraph>' +
'<table>' +
'<tableRow>' +
'<tableCell>' +
'<paragraph>[foo</paragraph>' +
'<image src="foo.png"><caption>bar</caption></image>]' +
'</tableCell>' +
'</tableRow>' +
'</table>'
);

editor.execute( 'selectAll' );

expect( getData( model ) ).to.equal( '<paragraph>[foo</paragraph>' +
'<table>' +
'<tableRow>' +
'<tableCell>' +
'<paragraph>foo</paragraph>' +
'<image src="foo.png"><caption>bar</caption></image>' +
'</tableCell>' +
'</tableRow>' +
'</table>]'
);
} );

it( 'should not change the selection (the entire editor is selected)', () => {
setData( model,
'<paragraph>[foo</paragraph>' +
'<table>' +
'<tableRow>' +
'<tableCell>' +
'<paragraph>foo</paragraph>' +
'<image src="foo.png"><caption>bar</caption></image>' +
'</tableCell>' +
'</tableRow>' +
'</table>]'
);

editor.execute( 'selectAll' );

expect( getData( model ) ).to.equal( '<paragraph>[foo</paragraph>' +
'<table>' +
'<tableRow>' +
'<tableCell>' +
'<paragraph>foo</paragraph>' +
'<image src="foo.png"><caption>bar</caption></image>' +
'</tableCell>' +
'</tableRow>' +
'</table>]'
);
} );
} );
} );

0 comments on commit 6f59c78

Please sign in to comment.