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

Commit

Permalink
Merge pull request #89 from ckeditor/t/88
Browse files Browse the repository at this point in the history
Other: The command API has been redesigned. The `Command` methods are now public and consistent. Commands can be used in a standalone mode (without the editor). The `CommandCollection` was introduced and replaced the `Map` of commands used in `editor.commands`. Closes #88.

  Besides changes mentioned in this point and in the "Breaking changes" section, other minor changes were done:

  * `Editor#execute()` now accepts multiple command arguments.
  * `Command#value` property was standardized.

BREAKING CHANGES: The `Command`'s protected `_doExecute()` and `_checkEnabled()` methods have been replaced by public `execute()` and `refresh()` methods.

BREAKING CHANGES: The `Command`'s `refreshState` event was removed and one should use `change:isEnabled` in order to control and override its state.

BREAKING CHANGES: The `core/command/command` module has been moved to the root directory (so the `Command` class is `core/command~Command` now).

BREAKING CHANGES: The `Command#refresh()` method is now automatically called on `editor.document#changesDone`.

BREAKING CHANGES: The `editor.commands` map was replaced by a `CommandCollection` instance so `editor.commands.set()` calls need to be replaced with `editor.commands.add()`.
  • Loading branch information
szymonkups authored Jun 13, 2017
2 parents 9194337 + cbc986c commit b76983b
Show file tree
Hide file tree
Showing 11 changed files with 667 additions and 503 deletions.
122 changes: 122 additions & 0 deletions src/command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
* For licensing, see LICENSE.md.
*/

/**
* @module core/command
*/

import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
import mix from '@ckeditor/ckeditor5-utils/src/mix';

/**
* The base class for CKEditor commands.
*
* Commands are the main way to manipulate editor contents and state. They are mostly used by UI elements (or by other
* commands) to make changes in the model. Commands are available in every part of code that has access to
* the {@link module:core/editor/editor~Editor editor} instance.
*
* Instances of registered commands can be retrieved from {@link module:core/editor/editor~Editor#commands}.
* The easiest way to execute a command is through {@link module:core/editor/editor~Editor#execute}.
*
* @mixes module:utils/observablemixin~ObservableMixin
*/
export default class Command {
/**
* Creates a new `Command` instance.
*
* @param {module:core/editor/editor~Editor} editor Editor on which this command will be used.
*/
constructor( editor ) {
/**
* The editor on which this command will be used.
*
* @readonly
* @member {module:core/editor/editor~Editor}
*/
this.editor = editor;

/**
* The value of a command. Concrete command class should define what it represents.
*
* For example, the `bold` command's value is whether the selection starts in a bolded text.
* And the value of the `link` command may be an object with links details.
*
* It's possible for a command to have no value (e.g. for stateless actions such as `uploadImage`).
*
* @observable
* @readonly
* @member #value
*/
this.set( 'value', undefined );

/**
* Flag indicating whether a command is enabled or disabled.
* A disabled command should do nothing when executed.
*
* @observable
* @readonly
* @member {Boolean} #isEnabled
*/
this.set( 'isEnabled', false );

this.decorate( 'execute' );

// By default every command is refreshed when changes are applied to the model.
this.listenTo( this.editor.document, 'changesDone', () => {
this.refresh();
} );

this.on( 'execute', evt => {
if ( !this.isEnabled ) {
evt.stop();
}
}, { priority: 'high' } );
}

/**
* Refreshes the command. The command should update its {@link #isEnabled} and {@link #value} property
* in this method.
*
* This method is automatically called when
* {@link module:engine/model/document~Document#event:changesDone any changes are applied to the model}.
*/
refresh() {
this.isEnabled = true;
}

/**
* Executes the command.
*
* A command may accept parameters. They will be passed from {@link module:core/editor/editor~Editor#execute}
* to the command.
*
* The `execute()` method will automatically abort when the command is disabled ({@link #isEnabled} is `false`).
* This behavior is implemented by a high priority listener to the {@link #event:execute} event.
*
* @fires execute
*/
execute() {}

/**
* Destroys the command.
*/
destroy() {
this.stopListening();
}

/**
* Event fired by the {@link #execute} method. The command action is a listener to this event so it's
* possible to change/cancel the behavior of the command by listening to this event.
*
* See {@link module:utils/observablemixin~ObservableMixin.decorate} for more information and samples.
*
* **Note:** This event is fired even if command is disabled. However, it is automatically blocked
* by a high priority listener in order to prevent command execution.
*
* @event execute
*/
}

mix( Command, ObservableMixin );
146 changes: 0 additions & 146 deletions src/command/command.js

This file was deleted.

51 changes: 19 additions & 32 deletions src/command/toggleattributecommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,21 @@
* @module core/command/toggleattributecommand
*/

import Command from './command';
import Command from '../command';
import getSchemaValidRanges from './helpers/getschemavalidranges';
import isAttributeAllowedInSelection from './helpers/isattributeallowedinselection';

/**
* An extension of the base {@link module:core/command/command~Command} class, which provides utilities for a command which toggles a single
* An extension of the base {@link module:core/command~Command} class, which provides utilities for a command which toggles a single
* attribute on a text or an element. `ToggleAttributeCommand` uses {@link module:engine/model/document~Document#selection}
* to decide which nodes (if any) should be changed, and applies or removes attributes from them.
*
* The command checks {@link module:engine/model/document~Document#schema} to decide if it should be enabled.
*
* @extends module:core/command~Command
*/
export default class ToggleAttributeCommand extends Command {
/**
* @see module:core/command/command~Command
* @param {module:core/editor/editor~Editor} editor
* @param {String} attributeKey Attribute that will be set by the command.
*/
Expand All @@ -30,43 +31,29 @@ export default class ToggleAttributeCommand extends Command {
/**
* Attribute that will be set by the command.
*
* @readonly
* @member {String}
*/
this.attributeKey = attributeKey;

/**
* Flag indicating whether command is active. For collapsed selection it means that typed characters will have
* Flag indicating whether the command is active. For collapsed selection it means that typed characters will have
* the command's attribute set. For range selection it means that all nodes inside have the attribute applied.
*
* @observable
* @readonly
* @member {Boolean} #value
*/
this.set( 'value', false );

this.listenTo( editor.document, 'changesDone', () => {
this.refreshValue();
this.refreshState();
} );
}

/**
* Updates command's {@link #value value} based on the current selection.
*/
refreshValue() {
this.value = this.editor.document.selection.hasAttribute( this.attributeKey );
}

/**
* Checks if {@link module:engine/model/document~Document#schema} allows to create attribute in
* {@link module:engine/model/document~Document#selection}.
*
* @private
* @returns {Boolean}
* Updates command's {@link #value} based on the current selection.
*/
_checkEnabled() {
const document = this.editor.document;
refresh() {
const doc = this.editor.document;

return isAttributeAllowedInSelection( this.attributeKey, document.selection, document.schema );
this.value = doc.selection.hasAttribute( this.attributeKey );
this.isEnabled = isAttributeAllowedInSelection( this.attributeKey, doc.selection, doc.schema );
}

/**
Expand All @@ -84,30 +71,30 @@ export default class ToggleAttributeCommand extends Command {
*
* If the command is disabled (`isEnabled == false`) when it is executed, nothing will happen.
*
* @private
* @fires execute
* @param {Object} [options] Options of command.
* @param {Boolean} [options.forceValue] If set it will force command behavior. If `true`, command will apply attribute,
* otherwise command will remove attribute. If not set, command will look for it's current value to decide what it should do.
* @param {module:engine/model/batch~Batch} [options.batch] Batch to group undo steps.
*/
_doExecute( options = {} ) {
const document = this.editor.document;
const selection = document.selection;
execute( options = {} ) {
const doc = this.editor.document;
const selection = doc.selection;
const value = ( options.forceValue === undefined ) ? !this.value : options.forceValue;

// If selection has non-collapsed ranges, we change attribute on nodes inside those ranges.
document.enqueueChanges( () => {
doc.enqueueChanges( () => {
if ( selection.isCollapsed ) {
if ( value ) {
selection.setAttribute( this.attributeKey, true );
} else {
selection.removeAttribute( this.attributeKey );
}
} else {
const ranges = getSchemaValidRanges( this.attributeKey, selection.getRanges(), document.schema );
const ranges = getSchemaValidRanges( this.attributeKey, selection.getRanges(), doc.schema );

// Keep it as one undo step.
const batch = options.batch || document.batch();
const batch = options.batch || doc.batch();

for ( const range of ranges ) {
if ( value ) {
Expand Down
Loading

0 comments on commit b76983b

Please sign in to comment.