112 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			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;
 | |
| }
 |