@ckeditor/ckeditor5-select-all
Version:
Select all feature for CKEditor 5.
191 lines (185 loc) • 6.76 kB
JavaScript
/**
* @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
import { Command, Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
import { getCode, parseKeystroke } from '@ckeditor/ckeditor5-utils/dist/index.js';
import { IconSelectAll } from '@ckeditor/ckeditor5-icons/dist/index.js';
import { ButtonView, MenuBarMenuListItemButtonView } from '@ckeditor/ckeditor5-ui/dist/index.js';
/**
* The select all command.
*
* It is used by the {@link module:select-all/selectallediting~SelectAllEditing select all editing feature} to handle
* the <kbd>Ctrl/⌘</kbd>+<kbd>A</kbd> keystroke.
*
* Executing this command changes the {@glink framework/architecture/editing-engine#model model}
* selection so it contains the entire content of the editable root of the editor the selection is
* {@link module:engine/model/selection~ModelSelection#anchor anchored} in.
*
* If the selection was anchored in a {@glink framework/tutorials/widgets/implementing-a-block-widget nested editable}
* (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.
*/ class SelectAllCommand extends Command {
/**
* @inheritDoc
*/ constructor(editor){
super(editor);
// It does not affect data so should be enabled in read-only mode.
this.affectsData = false;
}
/**
* @inheritDoc
*/ execute() {
const model = this.editor.model;
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(scopeElement, 'in');
});
}
}
/**
* Checks whether the element is a valid select-all scope. Returns true, if the element is a
* {@link module:engine/model/schema~ModelSchema#isLimit limit}, and can contain any text or paragraph.
*
* @param schema Schema to check against.
* @param element Model element.
*/ function isSelectAllScope(schema, element) {
return schema.isLimit(element) && (schema.checkChild(element, '$text') || schema.checkChild(element, 'paragraph'));
}
const SELECT_ALL_KEYSTROKE = /* #__PURE__ */ parseKeystroke('Ctrl+A');
/**
* The select all editing feature.
*
* It registers the `'selectAll'` {@link module:select-all/selectallcommand~SelectAllCommand command}
* and the <kbd>Ctrl/⌘</kbd>+<kbd>A</kbd> keystroke listener which executes it.
*/ class SelectAllEditing extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'SelectAllEditing';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ init() {
const editor = this.editor;
const t = editor.t;
const view = editor.editing.view;
const viewDocument = view.document;
editor.commands.add('selectAll', new SelectAllCommand(editor));
this.listenTo(viewDocument, 'keydown', (eventInfo, domEventData)=>{
if (getCode(domEventData) === SELECT_ALL_KEYSTROKE) {
editor.execute('selectAll');
domEventData.preventDefault();
}
});
// Add the information about the keystroke to the accessibility database.
editor.accessibility.addKeystrokeInfos({
keystrokes: [
{
label: t('Select all'),
keystroke: 'CTRL+A'
}
]
});
}
}
/**
* The select all UI feature.
*
* It registers the `'selectAll'` UI button in the editor's
* {@link module:ui/componentfactory~ComponentFactory component factory}. When clicked, the button
* executes the {@link module:select-all/selectallcommand~SelectAllCommand select all command}.
*/ class SelectAllUI extends Plugin {
/**
* @inheritDoc
*/ static get pluginName() {
return 'SelectAllUI';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
/**
* @inheritDoc
*/ init() {
const editor = this.editor;
editor.ui.componentFactory.add('selectAll', ()=>{
const buttonView = this._createButton(ButtonView);
buttonView.set({
tooltip: true
});
return buttonView;
});
editor.ui.componentFactory.add('menuBar:selectAll', ()=>{
return this._createButton(MenuBarMenuListItemButtonView);
});
}
/**
* Creates a button for select all command to use either in toolbar or in menu bar.
*/ _createButton(ButtonClass) {
const editor = this.editor;
const locale = editor.locale;
const command = editor.commands.get('selectAll');
const view = new ButtonClass(editor.locale);
const t = locale.t;
view.set({
label: t('Select all'),
icon: IconSelectAll,
keystroke: 'Ctrl+A'
});
view.bind('isEnabled').to(command, 'isEnabled');
// Execute the command.
this.listenTo(view, 'execute', ()=>{
editor.execute('selectAll');
editor.editing.view.focus();
});
return view;
}
}
/**
* The select all feature.
*
* This is a "glue" plugin which loads the {@link module:select-all/selectallediting~SelectAllEditing select all editing feature}
* and the {@link module:select-all/selectallui~SelectAllUI select all UI feature}.
*
* Please refer to the documentation of individual features to learn more.
*/ class SelectAll extends Plugin {
/**
* @inheritDoc
*/ static get requires() {
return [
SelectAllEditing,
SelectAllUI
];
}
/**
* @inheritDoc
*/ static get pluginName() {
return 'SelectAll';
}
/**
* @inheritDoc
*/ static get isOfficialPlugin() {
return true;
}
}
export { SelectAll, SelectAllCommand, SelectAllEditing, SelectAllUI };
//# sourceMappingURL=index.js.map