111 lines
3.5 KiB
JavaScript
111 lines
3.5 KiB
JavaScript
if (typeof ot === 'undefined') {
|
|
// Export for browsers
|
|
var ot = {};
|
|
}
|
|
|
|
ot.UndoManager = (function () {
|
|
'use strict';
|
|
|
|
var NORMAL_STATE = 'normal';
|
|
var UNDOING_STATE = 'undoing';
|
|
var REDOING_STATE = 'redoing';
|
|
|
|
// Create a new UndoManager with an optional maximum history size.
|
|
function UndoManager (maxItems) {
|
|
this.maxItems = maxItems || 50;
|
|
this.state = NORMAL_STATE;
|
|
this.dontCompose = false;
|
|
this.undoStack = [];
|
|
this.redoStack = [];
|
|
}
|
|
|
|
// Add an operation to the undo or redo stack, depending on the current state
|
|
// of the UndoManager. The operation added must be the inverse of the last
|
|
// edit. When `compose` is true, compose the operation with the last operation
|
|
// unless the last operation was alread pushed on the redo stack or was hidden
|
|
// by a newer operation on the undo stack.
|
|
UndoManager.prototype.add = function (operation, compose) {
|
|
if (this.state === UNDOING_STATE) {
|
|
this.redoStack.push(operation);
|
|
this.dontCompose = true;
|
|
} else if (this.state === REDOING_STATE) {
|
|
this.undoStack.push(operation);
|
|
this.dontCompose = true;
|
|
} else {
|
|
var undoStack = this.undoStack;
|
|
if (!this.dontCompose && compose && undoStack.length > 0) {
|
|
undoStack.push(operation.compose(undoStack.pop()));
|
|
} else {
|
|
undoStack.push(operation);
|
|
if (undoStack.length > this.maxItems) { undoStack.shift(); }
|
|
}
|
|
this.dontCompose = false;
|
|
this.redoStack = [];
|
|
}
|
|
};
|
|
|
|
function transformStack (stack, operation) {
|
|
var newStack = [];
|
|
var Operation = operation.constructor;
|
|
for (var i = stack.length - 1; i >= 0; i--) {
|
|
var pair = Operation.transform(stack[i], operation);
|
|
if (typeof pair[0].isNoop !== 'function' || !pair[0].isNoop()) {
|
|
newStack.push(pair[0]);
|
|
}
|
|
operation = pair[1];
|
|
}
|
|
return newStack.reverse();
|
|
}
|
|
|
|
// Transform the undo and redo stacks against a operation by another client.
|
|
UndoManager.prototype.transform = function (operation) {
|
|
this.undoStack = transformStack(this.undoStack, operation);
|
|
this.redoStack = transformStack(this.redoStack, operation);
|
|
};
|
|
|
|
// Perform an undo by calling a function with the latest operation on the undo
|
|
// stack. The function is expected to call the `add` method with the inverse
|
|
// of the operation, which pushes the inverse on the redo stack.
|
|
UndoManager.prototype.performUndo = function (fn) {
|
|
this.state = UNDOING_STATE;
|
|
if (this.undoStack.length === 0) { throw new Error("undo not possible"); }
|
|
fn(this.undoStack.pop());
|
|
this.state = NORMAL_STATE;
|
|
};
|
|
|
|
// The inverse of `performUndo`.
|
|
UndoManager.prototype.performRedo = function (fn) {
|
|
this.state = REDOING_STATE;
|
|
if (this.redoStack.length === 0) { throw new Error("redo not possible"); }
|
|
fn(this.redoStack.pop());
|
|
this.state = NORMAL_STATE;
|
|
};
|
|
|
|
// Is the undo stack not empty?
|
|
UndoManager.prototype.canUndo = function () {
|
|
return this.undoStack.length !== 0;
|
|
};
|
|
|
|
// Is the redo stack not empty?
|
|
UndoManager.prototype.canRedo = function () {
|
|
return this.redoStack.length !== 0;
|
|
};
|
|
|
|
// Whether the UndoManager is currently performing an undo.
|
|
UndoManager.prototype.isUndoing = function () {
|
|
return this.state === UNDOING_STATE;
|
|
};
|
|
|
|
// Whether the UndoManager is currently performing a redo.
|
|
UndoManager.prototype.isRedoing = function () {
|
|
return this.state === REDOING_STATE;
|
|
};
|
|
|
|
return UndoManager;
|
|
|
|
}());
|
|
|
|
// Export for CommonJS
|
|
if (typeof module === 'object') {
|
|
module.exports = ot.UndoManager;
|
|
}
|