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; }