|
|
@@ -4,7 +4,7 @@
|
|
|
*/
|
|
|
|
|
|
import BaseCommand from './basecommand.js';
|
|
|
-import { transformDelta as transformDelta } from './basecommand.js';
|
|
|
+import { transformDelta, transformRangesByDeltas } from './basecommand.js';
|
|
|
|
|
|
/**
|
|
|
* Undo command stores {@link engine.model.Batch batches} applied to the {@link engine.model.Document document}
|
|
|
@@ -36,11 +36,14 @@ export default class UndoCommand extends BaseCommand {
|
|
|
// All changes has to be done in one `enqueueChanges` callback so other listeners will not
|
|
|
// step between consecutive deltas, or won't do changes to the document before selection is properly restored.
|
|
|
this.editor.document.enqueueChanges( () => {
|
|
|
- this._undo( item.batch );
|
|
|
- this._restoreSelection( item.selection.ranges, item.selection.isBackward, item.batch.baseVersion );
|
|
|
+ const undoingBatch = this._undo( item.batch );
|
|
|
+
|
|
|
+ const deltas = this.editor.document.history.getDeltas( item.batch.baseVersion );
|
|
|
+ this._restoreSelection( item.selection.ranges, item.selection.isBackward, deltas );
|
|
|
+
|
|
|
+ this.fire( 'revert', item.batch, undoingBatch );
|
|
|
} );
|
|
|
|
|
|
- this.fire( 'revert', item.batch );
|
|
|
this.refreshState();
|
|
|
}
|
|
|
|
|
|
@@ -68,8 +71,6 @@ export default class UndoCommand extends BaseCommand {
|
|
|
*
|
|
|
* @private
|
|
|
* @param {engine.model.Batch} batchToUndo Batch, which deltas will be reversed, transformed and applied.
|
|
|
- * @param {engine.model.Batch} undoingBatch Batch that will contain transformed and applied deltas from `batchToUndo`.
|
|
|
- * @param {engine.model.Document} document Document that is operated on by the command.
|
|
|
*/
|
|
|
_undo( batchToUndo ) {
|
|
|
const document = this.editor.document;
|
|
|
@@ -172,127 +173,7 @@ export default class UndoCommand extends BaseCommand {
|
|
|
history.updateDelta( Number( historyBaseVersion ), updatedHistoryDeltas[ historyBaseVersion ] );
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Restores {@link engine.model.Document#selection document selection} state after a batch has been undone. This
|
|
|
- * is a helper method for {@link undo.UndoCommand#_doExecute}.
|
|
|
- *
|
|
|
- * @private
|
|
|
- * @param {Array.<engine.model.Range>} ranges Ranges to be restored.
|
|
|
- * @param {Boolean} isBackward Flag describing if restored range was selected forward or backward.
|
|
|
- * @param {Number} baseVersion
|
|
|
- * @param {engine.model.Document} document Document that is operated on by the command.
|
|
|
- */
|
|
|
- _restoreSelection( ranges, isBackward, baseVersion ) {
|
|
|
- const document = this.editor.document;
|
|
|
-
|
|
|
- // This will keep the transformed selection ranges.
|
|
|
- const selectionRanges = [];
|
|
|
-
|
|
|
- // Transform all ranges from the restored selection.
|
|
|
- for ( let range of ranges ) {
|
|
|
- const transformedRanges = transformSelectionRange( range, baseVersion, document );
|
|
|
-
|
|
|
- // 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. We will take the first range that
|
|
|
- // is not in the graveyard.
|
|
|
- const transformedRange = transformedRanges.find(
|
|
|
- ( range ) => range.start.root != document.graveyard
|
|
|
- );
|
|
|
-
|
|
|
- // `transformedRange` might be `undefined` if transformed range ended up in graveyard.
|
|
|
- if ( transformedRange ) {
|
|
|
- selectionRanges.push( transformedRange );
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- // `selectionRanges` may be empty if all ranges ended up in graveyard. If that is the case, do not restore selection.
|
|
|
- if ( selectionRanges.length ) {
|
|
|
- document.selection.setRanges( selectionRanges, isBackward );
|
|
|
- }
|
|
|
+ return undoingBatch;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-// Transforms given range `range` by deltas from `document` history, starting from a delta with given `baseVersion`.
|
|
|
-// Returns an array containing one or more ranges, which are result of the transformation.
|
|
|
-function transformSelectionRange( range, baseVersion, document ) {
|
|
|
- const history = document.history;
|
|
|
-
|
|
|
- // We create `transformed` array. At the beginning it will have only the original range.
|
|
|
- // During transformation the original range will change or even break into smaller ranges.
|
|
|
- // After the range is broken into two ranges, we have to transform both of those ranges separately.
|
|
|
- // For that reason, we keep all transformed ranges in one array and operate on it.
|
|
|
- let transformed = [ range ];
|
|
|
-
|
|
|
- // The ranges will be transformed by history deltas that happened after the selection got stored.
|
|
|
- // Note, that at this point, the document history is already updated by undo command execution. We will
|
|
|
- // not transform the range by deltas that got undone or their reversing counterparts.
|
|
|
- transformed = transformRangesByDeltas( transformed, history.getDeltas( baseVersion ) );
|
|
|
-
|
|
|
- // After `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.
|
|
|
- // First, we have to sort those ranges because they don't have to be in an order.
|
|
|
- transformed.sort( ( a, b ) => a.start.isBefore( b.start ) ? -1 : 1 );
|
|
|
-
|
|
|
- // Then, we check if two consecutive ranges are touching.
|
|
|
- for ( let i = 1 ; i < transformed.length; i++ ) {
|
|
|
- let a = transformed[ i - 1 ];
|
|
|
- let b = transformed[ i ];
|
|
|
-
|
|
|
- if ( a.end.isTouching( b.start ) ) {
|
|
|
- a.end = b.end;
|
|
|
- transformed.splice( i, 1 );
|
|
|
- i--;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return transformed;
|
|
|
-}
|
|
|
-
|
|
|
-// Transforms given set of `ranges` by given set of `deltas`. Returns transformed `ranges`.
|
|
|
-function transformRangesByDeltas( ranges, deltas ) {
|
|
|
- for ( let delta of deltas ) {
|
|
|
- for ( let operation of delta.operations ) {
|
|
|
- // We look through all operations from all deltas.
|
|
|
-
|
|
|
- for ( let i = 0; i < ranges.length; i++ ) {
|
|
|
- // We transform every range by every operation.
|
|
|
- let result;
|
|
|
-
|
|
|
- switch ( operation.type ) {
|
|
|
- case 'insert':
|
|
|
- result = ranges[ i ]._getTransformedByInsertion(
|
|
|
- operation.position,
|
|
|
- operation.nodes.maxOffset,
|
|
|
- true
|
|
|
- );
|
|
|
- break;
|
|
|
-
|
|
|
- case 'move':
|
|
|
- case 'remove':
|
|
|
- case 'reinsert':
|
|
|
- result = ranges[ i ]._getTransformedByMove(
|
|
|
- operation.sourcePosition,
|
|
|
- operation.targetPosition,
|
|
|
- operation.howMany,
|
|
|
- true
|
|
|
- );
|
|
|
- break;
|
|
|
- }
|
|
|
-
|
|
|
- // If we have a transformation result, we substitute transformed range with it in `transformed` array.
|
|
|
- // Keep in mind that the result is an array and may contain multiple ranges.
|
|
|
- if ( result ) {
|
|
|
- ranges.splice( i, 1, ...result );
|
|
|
-
|
|
|
- // Fix iterator.
|
|
|
- i = i + result.length - 1;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return ranges;
|
|
|
-}
|