@ckeditor/ckeditor5-undo
Advanced tools
Comparing version 35.2.1 to 35.3.0
{ | ||
"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; | ||
}); | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
97115
12
567