diff --git a/packages/ckeditor5-select-all/docs/features/select-all.md b/packages/ckeditor5-select-all/docs/features/select-all.md
index de101c47cfe..f4026d5a65c 100644
--- a/packages/ckeditor5-select-all/docs/features/select-all.md
+++ b/packages/ckeditor5-select-all/docs/features/select-all.md
@@ -9,10 +9,14 @@ The {@link module:select-all/selectall~SelectAll} feature allows selecting the e
## Demo
-Press Ctrl/⌘+A 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 Ctrl/⌘+A or use the toolbar button to select the entire content of the editor.
{@snippet features/select-all}
+
+ 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.
+
+
## Installation
diff --git a/packages/ckeditor5-select-all/src/selectallcommand.js b/packages/ckeditor5-select-all/src/selectallcommand.js
index 392b0a3e902..eac8f25783e 100644
--- a/packages/ckeditor5-select-all/src/selectallcommand.js
+++ b/packages/ckeditor5-select-all/src/selectallcommand.js
@@ -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
*/
@@ -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' ) );
+}
diff --git a/packages/ckeditor5-select-all/tests/selectallcommand.js b/packages/ckeditor5-select-all/tests/selectallcommand.js
index 4dbc09f48f2..7c037cb1a2c 100644
--- a/packages/ckeditor5-select-all/tests/selectallcommand.js
+++ b/packages/ckeditor5-select-all/tests/selectallcommand.js
@@ -70,13 +70,51 @@ describe( 'SelectAllCommand', () => {
} );
it( 'should select all (selection in a nested editable)', () => {
- setData( model, 'foo[bar]' );
+ setData( model, 'foob[ar]' );
editor.execute( 'selectAll' );
expect( getData( model ) ).to.equal( 'foo[bar]' );
} );
+ it( 'should select all (selection within limit element)', () => {
+ setData( model,
+ 'foo' +
+ '' +
+ '' +
+ '' +
+ 'foo' +
+ '' +
+ '[' +
+ 'bar' +
+ ']' +
+ '[' +
+ 'baz' +
+ ']' +
+ '' +
+ '
'
+ );
+
+ editor.execute( 'selectAll' );
+
+ expect( getData( model ) ).to.equal(
+ '[foo' +
+ '' +
+ '' +
+ '' +
+ 'foo' +
+ '' +
+ '' +
+ 'bar' +
+ '' +
+ '' +
+ 'baz' +
+ '' +
+ '' +
+ '
]'
+ );
+ } );
+
it( 'should select all in the closest nested editable (nested editable inside another nested editable)', () => {
setData( model,
'foo' +
@@ -103,5 +141,81 @@ describe( 'SelectAllCommand', () => {
''
);
} );
+
+ it( 'should select all in the parent select-all-limit element (the entire editable is selected)', () => {
+ setData( model, 'foo[bar]' );
+
+ editor.execute( 'selectAll' );
+
+ expect( getData( model ) ).to.equal( '[foobar]' );
+ } );
+
+ it( 'should select all in the parent sellect-all-limit element (consecutive execute() on a nested editable)', () => {
+ setData( model,
+ 'foo' +
+ '' +
+ '' +
+ '' +
+ 'foo' +
+ 'b[]ar' +
+ '' +
+ '' +
+ '
'
+ );
+
+ editor.execute( 'selectAll' );
+ editor.execute( 'selectAll' );
+
+ expect( getData( model ) ).to.equal( 'foo' +
+ '' +
+ '' +
+ '' +
+ '[foo' +
+ 'bar]' +
+ '' +
+ '' +
+ '
'
+ );
+
+ editor.execute( 'selectAll' );
+
+ expect( getData( model ) ).to.equal( '[foo' +
+ '' +
+ '' +
+ '' +
+ 'foo' +
+ 'bar' +
+ '' +
+ '' +
+ '
]'
+ );
+ } );
+
+ it( 'should not change the selection (the entire editor is selected)', () => {
+ setData( model,
+ '[foo' +
+ '' +
+ '' +
+ '' +
+ 'foo' +
+ 'bar' +
+ '' +
+ '' +
+ '
]'
+ );
+
+ editor.execute( 'selectAll' );
+
+ expect( getData( model ) ).to.equal( '[foo' +
+ '' +
+ '' +
+ '' +
+ 'foo' +
+ 'bar' +
+ '' +
+ '' +
+ '
]'
+ );
+ } );
} );
} );