HackMD/public/vendor/ot/undo-manager.js

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