Socket
Socket
Sign inDemoInstall

@ckeditor/ckeditor5-undo

Package Overview
Dependencies
Maintainers
1
Versions
619
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ckeditor/ckeditor5-undo - npm Package Compare versions

Comparing version 35.2.1 to 35.3.0

38

package.json
{
"name": "@ckeditor/ckeditor5-undo",
"version": "35.2.1",
"version": "35.3.0",
"description": "Undo feature for CKEditor 5.",

@@ -15,16 +15,19 @@ "keywords": [

"dependencies": {
"@ckeditor/ckeditor5-core": "^35.2.1",
"@ckeditor/ckeditor5-engine": "^35.2.1",
"@ckeditor/ckeditor5-ui": "^35.2.1"
"@ckeditor/ckeditor5-core": "^35.3.0",
"@ckeditor/ckeditor5-engine": "^35.3.0",
"@ckeditor/ckeditor5-ui": "^35.3.0"
},
"devDependencies": {
"@ckeditor/ckeditor5-basic-styles": "^35.2.1",
"@ckeditor/ckeditor5-clipboard": "^35.2.1",
"@ckeditor/ckeditor5-editor-classic": "^35.2.1",
"@ckeditor/ckeditor5-enter": "^35.2.1",
"@ckeditor/ckeditor5-heading": "^35.2.1",
"@ckeditor/ckeditor5-paragraph": "^35.2.1",
"@ckeditor/ckeditor5-typing": "^35.2.1",
"@ckeditor/ckeditor5-table": "^35.2.1",
"@ckeditor/ckeditor5-utils": "^35.2.1"
"@ckeditor/ckeditor5-basic-styles": "^35.3.0",
"@ckeditor/ckeditor5-clipboard": "^35.3.0",
"@ckeditor/ckeditor5-editor-classic": "^35.3.0",
"@ckeditor/ckeditor5-enter": "^35.3.0",
"@ckeditor/ckeditor5-heading": "^35.3.0",
"@ckeditor/ckeditor5-paragraph": "^35.3.0",
"@ckeditor/ckeditor5-typing": "^35.3.0",
"@ckeditor/ckeditor5-table": "^35.3.0",
"@ckeditor/ckeditor5-utils": "^35.3.0",
"typescript": "^4.8.4",
"webpack": "^5.58.1",
"webpack-cli": "^4.9.0"
},

@@ -46,7 +49,12 @@ "engines": {

"lang",
"src",
"src/**/*.js",
"src/**/*.d.ts",
"theme",
"ckeditor5-metadata.json",
"CHANGELOG.md"
]
],
"scripts": {
"build": "tsc -p ./tsconfig.release.json",
"postversion": "npm run build"
}
}

@@ -5,10 +5,7 @@ /**

*/
/**
* @module undo/basecommand
*/
import Command from '@ckeditor/ckeditor5-core/src/command';
import { transformSets } from '@ckeditor/ckeditor5-engine/src/model/operation/transform';
/**

@@ -21,187 +18,152 @@ * Base class for undo feature commands: {@link module:undo/undocommand~UndoCommand} and {@link module:undo/redocommand~RedoCommand}.

export default class BaseCommand extends Command {
constructor( editor ) {
super( editor );
/**
* Stack of items stored by the command. These are pairs of:
*
* * {@link module:engine/model/batch~Batch batch} saved by the command,
* * {@link module:engine/model/selection~Selection selection} state at the moment of saving the batch.
*
* @protected
* @member {Array} #_stack
*/
this._stack = [];
/**
* Stores all batches that were created by this command.
*
* @protected
* @member {WeakSet.<module:engine/model/batch~Batch>} #_createdBatches
*/
this._createdBatches = new WeakSet();
// Refresh state, so the command is inactive right after initialization.
this.refresh();
// Set the transparent batch for the `editor.data.set()` call if the
// batch type is not set already.
this.listenTo( editor.data, 'set', ( evt, data ) => {
// Create a shallow copy of the options to not change the original args.
// And make sure that an object is assigned to data[ 1 ].
data[ 1 ] = { ...data[ 1 ] };
const options = data[ 1 ];
// If batch type is not set, default to non-undoable batch.
if ( !options.batchType ) {
options.batchType = { isUndoable: false };
}
}, { priority: 'high' } );
// Clear the stack for the `transparent` batches.
this.listenTo( editor.data, 'set', ( evt, data ) => {
// We can assume that the object exists and it has a `batchType` property.
// It was ensured with a higher priority listener before.
const options = data[ 1 ];
if ( !options.batchType.isUndoable ) {
this.clearStack();
}
} );
}
/**
* @inheritDoc
*/
refresh() {
this.isEnabled = this._stack.length > 0;
}
/**
* Stores a batch in the command, together with the selection state of the {@link module:engine/model/document~Document document}
* created by the editor which this command is registered to.
*
* @param {module:engine/model/batch~Batch} batch The batch to add.
*/
addBatch( batch ) {
const docSelection = this.editor.model.document.selection;
const selection = {
ranges: docSelection.hasOwnRange ? Array.from( docSelection.getRanges() ) : [],
isBackward: docSelection.isBackward
};
this._stack.push( { batch, selection } );
this.refresh();
}
/**
* Removes all items from the stack.
*/
clearStack() {
this._stack = [];
this.refresh();
}
/**
* Restores the {@link module:engine/model/document~Document#selection document selection} state after a batch was undone.
*
* @protected
* @param {Array.<module:engine/model/range~Range>} ranges Ranges to be restored.
* @param {Boolean} isBackward A flag describing whether the restored range was selected forward or backward.
* @param {Array.<module:engine/model/operation/operation~Operation>} operations Operations which has been applied
* since selection has been stored.
*/
_restoreSelection( ranges, isBackward, operations ) {
const model = this.editor.model;
const document = model.document;
// This will keep the transformed selection ranges.
const selectionRanges = [];
// Transform all ranges from the restored selection.
const transformedRangeGroups = ranges.map( range => range.getTransformedByOperations( operations ) );
const allRanges = transformedRangeGroups.flat();
for ( const rangeGroup of transformedRangeGroups ) {
// While transforming there could appear ranges that are contained by other ranges, we shall ignore them.
const transformed = rangeGroup
.filter( range => range.root != document.graveyard )
.filter( range => !isRangeContainedByAnyOtherRange( range, allRanges ) );
// All the transformed ranges ended up in graveyard.
if ( !transformed.length ) {
continue;
}
// After the range got transformed, we have an array of ranges. Some of those
// ranges may be "touching" -- they can be next to each other and could be merged.
normalizeRanges( transformed );
// For each `range` from `ranges`, we take only one transformed range.
// This is because we want to prevent situation where single-range selection
// got transformed to multi-range selection.
selectionRanges.push( transformed[ 0 ] );
}
// @if CK_DEBUG_ENGINE // console.log( `Restored selection by undo: ${ selectionRanges.join( ', ' ) }` );
// `selectionRanges` may be empty if all ranges ended up in graveyard. If that is the case, do not restore selection.
if ( selectionRanges.length ) {
model.change( writer => {
writer.setSelection( selectionRanges, { backward: isBackward } );
} );
}
}
/**
* Undoes a batch by reversing that batch, transforming reversed batch and finally applying it.
* This is a helper method for {@link #execute}.
*
* @protected
* @param {module:engine/model/batch~Batch} batchToUndo The batch to be undone.
* @param {module:engine/model/batch~Batch} undoingBatch The batch that will contain undoing changes.
*/
_undo( batchToUndo, undoingBatch ) {
const model = this.editor.model;
const document = model.document;
// All changes done by the command execution will be saved as one batch.
this._createdBatches.add( undoingBatch );
const operationsToUndo = batchToUndo.operations.slice().filter( operation => operation.isDocumentOperation );
operationsToUndo.reverse();
// We will process each operation from `batchToUndo`, in reverse order. If there were operations A, B and C in undone batch,
// we need to revert them in reverse order, so first C' (reversed C), then B', then A'.
for ( const operationToUndo of operationsToUndo ) {
const nextBaseVersion = operationToUndo.baseVersion + 1;
const historyOperations = Array.from( document.history.getOperations( nextBaseVersion ) );
const transformedSets = transformSets(
[ operationToUndo.getReversed() ],
historyOperations,
{
useRelations: true,
document: this.editor.model.document,
padWithNoOps: false,
forceWeakRemove: true
}
);
const reversedOperations = transformedSets.operationsA;
// After reversed operation has been transformed by all history operations, apply it.
for ( const operation of reversedOperations ) {
// Before applying, add the operation to the `undoingBatch`.
undoingBatch.addOperation( operation );
model.applyOperation( operation );
document.history.setOperationAsUndone( operationToUndo, operation );
}
}
}
constructor(editor) {
super(editor);
/**
* Stack of items stored by the command. These are pairs of:
*
* * {@link module:engine/model/batch~Batch batch} saved by the command,
* * {@link module:engine/model/selection~Selection selection} state at the moment of saving the batch.
*
* @protected
* @member {Array} #_stack
*/
this._stack = [];
/**
* Stores all batches that were created by this command.
*
* @protected
* @member {WeakSet.<module:engine/model/batch~Batch>} #_createdBatches
*/
this._createdBatches = new WeakSet();
// Refresh state, so the command is inactive right after initialization.
this.refresh();
// Set the transparent batch for the `editor.data.set()` call if the
// batch type is not set already.
this.listenTo(editor.data, 'set', (evt, data) => {
// Create a shallow copy of the options to not change the original args.
// And make sure that an object is assigned to data[ 1 ].
data[1] = { ...data[1] };
const options = data[1];
// If batch type is not set, default to non-undoable batch.
if (!options.batchType) {
options.batchType = { isUndoable: false };
}
}, { priority: 'high' });
// Clear the stack for the `transparent` batches.
this.listenTo(editor.data, 'set', (evt, data) => {
// We can assume that the object exists and it has a `batchType` property.
// It was ensured with a higher priority listener before.
const options = data[1];
if (!options.batchType.isUndoable) {
this.clearStack();
}
});
}
/**
* @inheritDoc
*/
refresh() {
this.isEnabled = this._stack.length > 0;
}
/**
* Stores a batch in the command, together with the selection state of the {@link module:engine/model/document~Document document}
* created by the editor which this command is registered to.
*
* @param {module:engine/model/batch~Batch} batch The batch to add.
*/
addBatch(batch) {
const docSelection = this.editor.model.document.selection;
const selection = {
ranges: docSelection.hasOwnRange ? Array.from(docSelection.getRanges()) : [],
isBackward: docSelection.isBackward
};
this._stack.push({ batch, selection });
this.refresh();
}
/**
* Removes all items from the stack.
*/
clearStack() {
this._stack = [];
this.refresh();
}
/**
* Restores the {@link module:engine/model/document~Document#selection document selection} state after a batch was undone.
*
* @protected
* @param {Array.<module:engine/model/range~Range>} ranges Ranges to be restored.
* @param {Boolean} isBackward A flag describing whether the restored range was selected forward or backward.
* @param {Array.<module:engine/model/operation/operation~Operation>} operations Operations which has been applied
* since selection has been stored.
*/
_restoreSelection(ranges, isBackward, operations) {
const model = this.editor.model;
const document = model.document;
// This will keep the transformed selection ranges.
const selectionRanges = [];
// Transform all ranges from the restored selection.
const transformedRangeGroups = ranges.map(range => range.getTransformedByOperations(operations));
const allRanges = transformedRangeGroups.flat();
for (const rangeGroup of transformedRangeGroups) {
// While transforming there could appear ranges that are contained by other ranges, we shall ignore them.
const transformed = rangeGroup
.filter(range => range.root != document.graveyard)
.filter(range => !isRangeContainedByAnyOtherRange(range, allRanges));
// All the transformed ranges ended up in graveyard.
if (!transformed.length) {
continue;
}
// After the range got transformed, we have an array of ranges. Some of those
// ranges may be "touching" -- they can be next to each other and could be merged.
normalizeRanges(transformed);
// For each `range` from `ranges`, we take only one transformed range.
// This is because we want to prevent situation where single-range selection
// got transformed to multi-range selection.
selectionRanges.push(transformed[0]);
}
// @if CK_DEBUG_ENGINE // console.log( `Restored selection by undo: ${ selectionRanges.join( ', ' ) }` );
// `selectionRanges` may be empty if all ranges ended up in graveyard. If that is the case, do not restore selection.
if (selectionRanges.length) {
model.change(writer => {
writer.setSelection(selectionRanges, { backward: isBackward });
});
}
}
/**
* Undoes a batch by reversing that batch, transforming reversed batch and finally applying it.
* This is a helper method for {@link #execute}.
*
* @protected
* @param {module:engine/model/batch~Batch} batchToUndo The batch to be undone.
* @param {module:engine/model/batch~Batch} undoingBatch The batch that will contain undoing changes.
*/
_undo(batchToUndo, undoingBatch) {
const model = this.editor.model;
const document = model.document;
// All changes done by the command execution will be saved as one batch.
this._createdBatches.add(undoingBatch);
const operationsToUndo = batchToUndo.operations.slice().filter(operation => operation.isDocumentOperation);
operationsToUndo.reverse();
// We will process each operation from `batchToUndo`, in reverse order. If there were operations A, B and C in undone batch,
// we need to revert them in reverse order, so first C' (reversed C), then B', then A'.
for (const operationToUndo of operationsToUndo) {
const nextBaseVersion = operationToUndo.baseVersion + 1;
const historyOperations = Array.from(document.history.getOperations(nextBaseVersion));
const transformedSets = transformSets([operationToUndo.getReversed()], historyOperations, {
useRelations: true,
document: this.editor.model.document,
padWithNoOps: false,
forceWeakRemove: true
});
const reversedOperations = transformedSets.operationsA;
// After reversed operation has been transformed by all history operations, apply it.
for (const operation of reversedOperations) {
// Before applying, add the operation to the `undoingBatch`.
undoingBatch.addOperation(operation);
model.applyOperation(operation);
document.history.setOperationAsUndone(operationToUndo, operation);
}
}
}
}
// Normalizes list of ranges by joining intersecting or "touching" ranges.

@@ -211,19 +173,16 @@ //

//
function normalizeRanges( ranges ) {
ranges.sort( ( a, b ) => a.start.isBefore( b.start ) ? -1 : 1 );
for ( let i = 1; i < ranges.length; i++ ) {
const previousRange = ranges[ i - 1 ];
const joinedRange = previousRange.getJoined( ranges[ i ], true );
if ( joinedRange ) {
// Replace the ranges on the list with the new joined range.
i--;
ranges.splice( i, 2, joinedRange );
}
}
function normalizeRanges(ranges) {
ranges.sort((a, b) => a.start.isBefore(b.start) ? -1 : 1);
for (let i = 1; i < ranges.length; i++) {
const previousRange = ranges[i - 1];
const joinedRange = previousRange.getJoined(ranges[i], true);
if (joinedRange) {
// Replace the ranges on the list with the new joined range.
i--;
ranges.splice(i, 2, joinedRange);
}
}
}
function isRangeContainedByAnyOtherRange( range, ranges ) {
return ranges.some( otherRange => otherRange !== range && otherRange.containsRange( range, true ) );
function isRangeContainedByAnyOtherRange(range, ranges) {
return ranges.some(otherRange => otherRange !== range && otherRange.containsRange(range, true));
}

@@ -5,9 +5,7 @@ /**

*/
/**
* @module undo
*/
export { default as Undo } from './undo';
export { default as UndoEditing } from './undoediting';
export { default as UndoUi } from './undoui';

@@ -5,9 +5,6 @@ /**

*/
/**
* @module undo/redocommand
*/
import BaseCommand from './basecommand';
/**

@@ -24,27 +21,24 @@ * The redo command stores {@link module:engine/model/batch~Batch batches} that were used to undo a batch by

export default class RedoCommand extends BaseCommand {
/**
* Executes the command. This method reverts the last {@link module:engine/model/batch~Batch batch} added to
* the command's stack, applies the reverted and transformed version on the
* {@link module:engine/model/document~Document document} and removes the batch from the stack.
* Then, it restores the {@link module:engine/model/document~Document#selection document selection}.
*
* @fires execute
*/
execute() {
const item = this._stack.pop();
const redoingBatch = this.editor.model.createBatch( { isUndo: true } );
// All changes have to be done in one `enqueueChange` callback so other listeners will not step between consecutive
// operations, or won't do changes to the document before selection is properly restored.
this.editor.model.enqueueChange( redoingBatch, () => {
const lastOperation = item.batch.operations[ item.batch.operations.length - 1 ];
const nextBaseVersion = lastOperation.baseVersion + 1;
const operations = this.editor.model.document.history.getOperations( nextBaseVersion );
this._restoreSelection( item.selection.ranges, item.selection.isBackward, operations );
this._undo( item.batch, redoingBatch );
} );
this.refresh();
}
/**
* Executes the command. This method reverts the last {@link module:engine/model/batch~Batch batch} added to
* the command's stack, applies the reverted and transformed version on the
* {@link module:engine/model/document~Document document} and removes the batch from the stack.
* Then, it restores the {@link module:engine/model/document~Document#selection document selection}.
*
* @fires execute
*/
execute() {
const item = this._stack.pop();
const redoingBatch = this.editor.model.createBatch({ isUndo: true });
// All changes have to be done in one `enqueueChange` callback so other listeners will not step between consecutive
// operations, or won't do changes to the document before selection is properly restored.
this.editor.model.enqueueChange(redoingBatch, () => {
const lastOperation = item.batch.operations[item.batch.operations.length - 1];
const nextBaseVersion = lastOperation.baseVersion + 1;
const operations = this.editor.model.document.history.getOperations(nextBaseVersion);
this._restoreSelection(item.selection.ranges, item.selection.isBackward, operations);
this._undo(item.batch, redoingBatch);
});
this.refresh();
}
}

@@ -5,11 +5,8 @@ /**

*/
/**
* @module undo/undo
*/
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import UndoEditing from './undoediting';
import UndoUI from './undoui';
/**

@@ -107,15 +104,14 @@ * The undo feature.

export default class Undo extends Plugin {
/**
* @inheritDoc
*/
static get requires() {
return [ UndoEditing, UndoUI ];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Undo';
}
/**
* @inheritDoc
*/
static get requires() {
return [UndoEditing, UndoUI];
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'Undo';
}
}

@@ -5,9 +5,6 @@ /**

*/
/**
* @module undo/undocommand
*/
import BaseCommand from './basecommand';
/**

@@ -23,37 +20,26 @@ * The undo command stores {@link module:engine/model/batch~Batch batches} applied to the

export default class UndoCommand extends BaseCommand {
/**
* Executes the command. This method reverts a {@link module:engine/model/batch~Batch batch} added to the command's stack, transforms
* and applies the reverted version on the {@link module:engine/model/document~Document document} and removes the batch from the stack.
* Then, it restores the {@link module:engine/model/document~Document#selection document selection}.
*
* @fires execute
* @fires revert
* @param {module:engine/model/batch~Batch} [batch] A batch that should be undone. If not set, the last added batch will be undone.
*/
execute( batch = null ) {
// If batch is not given, set `batchIndex` to the last index in command stack.
const batchIndex = batch ? this._stack.findIndex( a => a.batch == batch ) : this._stack.length - 1;
const item = this._stack.splice( batchIndex, 1 )[ 0 ];
const undoingBatch = this.editor.model.createBatch( { isUndo: true } );
// All changes has to be done in one `enqueueChange` callback so other listeners will not
// step between consecutive operations, or won't do changes to the document before selection is properly restored.
this.editor.model.enqueueChange( undoingBatch, () => {
this._undo( item.batch, undoingBatch );
const operations = this.editor.model.document.history.getOperations( item.batch.baseVersion );
this._restoreSelection( item.selection.ranges, item.selection.isBackward, operations );
this.fire( 'revert', item.batch, undoingBatch );
} );
this.refresh();
}
/**
* Executes the command. This method reverts a {@link module:engine/model/batch~Batch batch} added to the command's stack, transforms
* and applies the reverted version on the {@link module:engine/model/document~Document document} and removes the batch from the stack.
* Then, it restores the {@link module:engine/model/document~Document#selection document selection}.
*
* @fires execute
* @fires revert
* @param {module:engine/model/batch~Batch} [batch] A batch that should be undone. If not set, the last added batch will be undone.
*/
execute(batch = null) {
// If batch is not given, set `batchIndex` to the last index in command stack.
const batchIndex = batch ? this._stack.findIndex(a => a.batch == batch) : this._stack.length - 1;
const item = this._stack.splice(batchIndex, 1)[0];
const undoingBatch = this.editor.model.createBatch({ isUndo: true });
// All changes has to be done in one `enqueueChange` callback so other listeners will not
// step between consecutive operations, or won't do changes to the document before selection is properly restored.
this.editor.model.enqueueChange(undoingBatch, () => {
this._undo(item.batch, undoingBatch);
const operations = this.editor.model.document.history.getOperations(item.batch.baseVersion);
this._restoreSelection(item.selection.ranges, item.selection.isBackward, operations);
this.fire('revert', item.batch, undoingBatch);
});
this.refresh();
}
}
/**
* Fired when execution of the command reverts some batch.
*
* @event revert
*/

@@ -5,11 +5,8 @@ /**

*/
/**
* @module undo/undoediting
*/
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import UndoCommand from './undocommand';
import RedoCommand from './redocommand';
/**

@@ -23,103 +20,87 @@ * The undo engine feature.

export default class UndoEditing extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'UndoEditing';
}
/**
* @inheritDoc
*/
constructor( editor ) {
super( editor );
/**
* The command that manages undo {@link module:engine/model/batch~Batch batches} stack (history).
* Created and registered during the {@link #init feature initialization}.
*
* @private
* @member {module:undo/undocommand~UndoCommand} #_undoCommand
*/
/**
* The command that manages redo {@link module:engine/model/batch~Batch batches} stack (history).
* Created and registered during the {@link #init feature initialization}.
*
* @private
* @member {module:undo/undocommand~UndoCommand} #_redoCommand
*/
/**
* Keeps track of which batches were registered in undo.
*
* @private
* @member {WeakSet.<module:engine/model/batch~Batch>}
*/
this._batchRegistry = new WeakSet();
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Create commands.
this._undoCommand = new UndoCommand( editor );
this._redoCommand = new RedoCommand( editor );
// Register command to the editor.
editor.commands.add( 'undo', this._undoCommand );
editor.commands.add( 'redo', this._redoCommand );
this.listenTo( editor.model, 'applyOperation', ( evt, args ) => {
const operation = args[ 0 ];
// Do not register batch if the operation is not a document operation.
// This prevents from creating empty undo steps, where all operations where non-document operations.
// Non-document operations creates and alters content in detached tree fragments (for example, document fragments).
// Most of time this is preparing data before it is inserted into actual tree (for example during copy & paste).
// Such operations should not be reversed.
if ( !operation.isDocumentOperation ) {
return;
}
const batch = operation.batch;
const isRedoBatch = this._redoCommand._createdBatches.has( batch );
const isUndoBatch = this._undoCommand._createdBatches.has( batch );
const wasProcessed = this._batchRegistry.has( batch );
// Skip the batch if it was already processed.
if ( wasProcessed ) {
return;
}
// Add the batch to the registry so it will not be processed again.
this._batchRegistry.add( batch );
if ( !batch.isUndoable ) {
return;
}
if ( isRedoBatch ) {
// If this batch comes from `redoCommand`, add it to the `undoCommand` stack.
this._undoCommand.addBatch( batch );
} else if ( !isUndoBatch ) {
// If the batch comes neither from `redoCommand` nor from `undoCommand` then it is a new, regular batch.
// Add the batch to the `undoCommand` stack and clear the `redoCommand` stack.
this._undoCommand.addBatch( batch );
this._redoCommand.clearStack();
}
}, { priority: 'highest' } );
this.listenTo( this._undoCommand, 'revert', ( evt, undoneBatch, undoingBatch ) => {
this._redoCommand.addBatch( undoingBatch );
} );
editor.keystrokes.set( 'CTRL+Z', 'undo' );
editor.keystrokes.set( 'CTRL+Y', 'redo' );
editor.keystrokes.set( 'CTRL+SHIFT+Z', 'redo' );
}
/**
* @inheritDoc
*/
constructor(editor) {
super(editor);
/**
* The command that manages undo {@link module:engine/model/batch~Batch batches} stack (history).
* Created and registered during the {@link #init feature initialization}.
*
* @private
* @member {module:undo/undocommand~UndoCommand} #_undoCommand
*/
/**
* The command that manages redo {@link module:engine/model/batch~Batch batches} stack (history).
* Created and registered during the {@link #init feature initialization}.
*
* @private
* @member {module:undo/undocommand~UndoCommand} #_redoCommand
*/
/**
* Keeps track of which batches were registered in undo.
*
* @private
* @member {WeakSet.<module:engine/model/batch~Batch>}
*/
this._batchRegistry = new WeakSet();
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'UndoEditing';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
// Create commands.
this._undoCommand = new UndoCommand(editor);
this._redoCommand = new RedoCommand(editor);
// Register command to the editor.
editor.commands.add('undo', this._undoCommand);
editor.commands.add('redo', this._redoCommand);
this.listenTo(editor.model, 'applyOperation', (evt, args) => {
const operation = args[0];
// Do not register batch if the operation is not a document operation.
// This prevents from creating empty undo steps, where all operations where non-document operations.
// Non-document operations creates and alters content in detached tree fragments (for example, document fragments).
// Most of time this is preparing data before it is inserted into actual tree (for example during copy & paste).
// Such operations should not be reversed.
if (!operation.isDocumentOperation) {
return;
}
const batch = operation.batch;
const isRedoBatch = this._redoCommand._createdBatches.has(batch);
const isUndoBatch = this._undoCommand._createdBatches.has(batch);
const wasProcessed = this._batchRegistry.has(batch);
// Skip the batch if it was already processed.
if (wasProcessed) {
return;
}
// Add the batch to the registry so it will not be processed again.
this._batchRegistry.add(batch);
if (!batch.isUndoable) {
return;
}
if (isRedoBatch) {
// If this batch comes from `redoCommand`, add it to the `undoCommand` stack.
this._undoCommand.addBatch(batch);
}
else if (!isUndoBatch) {
// If the batch comes neither from `redoCommand` nor from `undoCommand` then it is a new, regular batch.
// Add the batch to the `undoCommand` stack and clear the `redoCommand` stack.
this._undoCommand.addBatch(batch);
this._redoCommand.clearStack();
}
}, { priority: 'highest' });
this.listenTo(this._undoCommand, 'revert', (evt, undoneBatch, undoingBatch) => {
this._redoCommand.addBatch(undoingBatch);
});
editor.keystrokes.set('CTRL+Z', 'undo');
editor.keystrokes.set('CTRL+Y', 'redo');
editor.keystrokes.set('CTRL+SHIFT+Z', 'redo');
}
}

@@ -5,13 +5,9 @@ /**

*/
/**
* @module undo/undoui
*/
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
import undoIcon from '../theme/icons/undo.svg';
import redoIcon from '../theme/icons/redo.svg';
/**

@@ -23,57 +19,48 @@ * The undo UI feature. It introduces the `'undo'` and `'redo'` buttons to the editor.

export default class UndoUI extends Plugin {
/**
* @inheritDoc
*/
static get pluginName() {
return 'UndoUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const locale = editor.locale;
const t = editor.t;
const localizedUndoIcon = locale.uiLanguageDirection == 'ltr' ? undoIcon : redoIcon;
const localizedRedoIcon = locale.uiLanguageDirection == 'ltr' ? redoIcon : undoIcon;
this._addButton( 'undo', t( 'Undo' ), 'CTRL+Z', localizedUndoIcon );
this._addButton( 'redo', t( 'Redo' ), 'CTRL+Y', localizedRedoIcon );
}
/**
* Creates a button for the specified command.
*
* @private
* @param {String} name Command name.
* @param {String} label Button label.
* @param {String} keystroke Command keystroke.
* @param {String} Icon Source of the icon.
*/
_addButton( name, label, keystroke, Icon ) {
const editor = this.editor;
editor.ui.componentFactory.add( name, locale => {
const command = editor.commands.get( name );
const view = new ButtonView( locale );
view.set( {
label,
icon: Icon,
keystroke,
tooltip: true
} );
view.bind( 'isEnabled' ).to( command, 'isEnabled' );
this.listenTo( view, 'execute', () => {
editor.execute( name );
editor.editing.view.focus();
} );
return view;
} );
}
/**
* @inheritDoc
*/
static get pluginName() {
return 'UndoUI';
}
/**
* @inheritDoc
*/
init() {
const editor = this.editor;
const locale = editor.locale;
const t = editor.t;
const localizedUndoIcon = locale.uiLanguageDirection == 'ltr' ? undoIcon : redoIcon;
const localizedRedoIcon = locale.uiLanguageDirection == 'ltr' ? redoIcon : undoIcon;
this._addButton('undo', t('Undo'), 'CTRL+Z', localizedUndoIcon);
this._addButton('redo', t('Redo'), 'CTRL+Y', localizedRedoIcon);
}
/**
* Creates a button for the specified command.
*
* @private
* @param {String} name Command name.
* @param {String} label Button label.
* @param {String} keystroke Command keystroke.
* @param {String} Icon Source of the icon.
*/
_addButton(name, label, keystroke, Icon) {
const editor = this.editor;
editor.ui.componentFactory.add(name, locale => {
const command = editor.commands.get(name);
const view = new ButtonView(locale);
view.set({
label,
icon: Icon,
keystroke,
tooltip: true
});
view.bind('isEnabled').to(command, 'isEnabled');
this.listenTo(view, 'execute', () => {
editor.execute(name);
editor.editing.view.focus();
});
return view;
});
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc