/**
* Originally based of this code... - refactored for Roo...
* https://github.com/aaalsaleh/undo-manager
* undo-manager.js
* @author Abdulrahman Alsaleh
* @copyright 2015 Abdulrahman Alsaleh
* @license MIT License (c)
*
* Hackily modifyed by alan@roojs.com
*
*
*
*
* TOTALLY UNTESTED...
*
* Documentation to be done....
*/
/**
* @class Roo.lib.UndoManager
* An undo manager implementation in JavaScript. It follows the W3C UndoManager and DOM Transaction
* Draft and the undocumented and disabled Mozilla Firefox's UndoManager implementation.
* Usage:
* <pre><code>
editor.undoManager = new Roo.lib.UndoManager(1000, editor);
</code></pre>
* For more information see this blog post with examples:
* <a href="http://www.cnitblog.com/seeyeah/archive/2011/12/30/38728.html/">DomHelper
- Create Elements using DOM, HTML fragments and Templates</a>.
* @constructor
* @param {Number} limit how far back to go ... use 1000?
* @param {Object} scope usually use document..
*/
Roo.lib.UndoManager = function (limit, undoScopeHost)
{
this.stack = [];
this.limit = limit;
this.scope = undoScopeHost;
this.fireEvent = typeof CustomEvent != 'undefined' && undoScopeHost && undoScopeHost.dispatchEvent;
if (this.fireEvent) {
this.bindEvents();
}
this.reset();
};
Roo.lib.UndoManager.prototype = {
limit : false,
stack : false,
scope : false,
fireEvent : false,
position : 0,
length : 0,
/**
* To push and execute a transaction, the method undoManager.transact
* must be called by passing a transaction object as the first argument, and a merge
* flag as the second argument. A transaction object has the following properties:
*
* Usage:
<pre><code>
undoManager.transact({
label: 'Typing',
execute: function() { ... },
undo: function() { ... },
// redo same as execute
redo: function() { this.execute(); }
}, false);
// merge transaction
undoManager.transact({
label: 'Typing',
execute: function() { ... }, // this will be run...
undo: function() { ... }, // what to do when undo is run.
// redo same as execute
redo: function() { this.execute(); }
}, true);
</code></pre>
*
*
* @param {Object} transaction The transaction to add to the stack.
* @return {String} The HTML fragment
*/
transact : function (transaction, merge)
{
if (arguments.length < 2) {
throw new TypeError('Not enough arguments to UndoManager.transact.');
}
transaction.execute();
this.stack.splice(0, this.position);
if (merge && this.length) {
this.stack[0].push(transaction);
} else {
this.stack.unshift([transaction]);
}
this.position = 0;
if (this.limit && this.stack.length > this.limit) {
this.length = this.stack.length = this.limit;
} else {
this.length = this.stack.length;
}
if (this.fireEvent) {
this.scope.dispatchEvent(
new CustomEvent('DOMTransaction', {
detail: {
transactions: this.stack[0].slice()
},
bubbles: true,
cancelable: false
})
);
}
//Roo.log("transaction: pos:" + this.position + " len: " + this.length + " slen:" + this.stack.length);
},
undo : function ()
{
//Roo.log("undo: pos:" + this.position + " len: " + this.length + " slen:" + this.stack.length);
if (this.position < this.length) {
for (var i = this.stack[this.position].length - 1; i >= 0; i--) {
this.stack[this.position][i].undo();
}
this.position++;
if (this.fireEvent) {
this.scope.dispatchEvent(
new CustomEvent('undo', {
detail: {
transactions: this.stack[this.position - 1].slice()
},
bubbles: true,
cancelable: false
})
);
}
}
},
redo : function ()
{
if (this.position > 0) {
for (var i = 0, n = this.stack[this.position - 1].length; i < n; i++) {
this.stack[this.position - 1][i].redo();
}
this.position--;
if (this.fireEvent) {
this.scope.dispatchEvent(
new CustomEvent('redo', {
detail: {
transactions: this.stack[this.position].slice()
},
bubbles: true,
cancelable: false
})
);
}
}
},
item : function (index)
{
if (index >= 0 && index < this.length) {
return this.stack[index].slice();
}
return null;
},
clearUndo : function () {
this.stack.length = this.length = this.position;
},
clearRedo : function () {
this.stack.splice(0, this.position);
this.position = 0;
this.length = this.stack.length;
},
/**
* Reset the undo - probaly done on load to clear all history.
*/
reset : function()
{
this.stack = [];
this.position = 0;
this.length = 0;
this.current_html = this.scope.innerHTML;
if (this.timer !== false) {
clearTimeout(this.timer);
}
this.timer = false;
this.merge = false;
this.addEvent();
},
current_html : '',
timer : false,
merge : false,
// this will handle the undo/redo on the element.?
bindEvents : function()
{
var el = this.scope;
el.undoManager = this;
this.scope.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.keyCode === 90) {
if (e.shiftKey) {
el.undoManager.redo(); // Ctrl/Command + Shift + Z
} else {
el.undoManager.undo(); // Ctrl/Command + Z
}
e.preventDefault();
}
});
/// ignore keyup..
this.scope.addEventListener('keyup', function(e) {
if ((e.ctrlKey || e.metaKey) && e.keyCode === 90) {
e.preventDefault();
}
});
var t = this;
el.addEventListener('input', function(e) {
if(el.innerHTML == t.current_html) {
return;
}
// only record events every second.
if (t.timer !== false) {
clearTimeout(t.timer);
t.timer = false;
}
t.timer = setTimeout(function() { t.merge = false; }, 1000);
t.addEvent(t.merge);
t.merge = true; // ignore changes happening every second..
});
},
/**
* Manually add an event.
* Normall called without arguements - and it will just get added to the stack.
*
*/
addEvent : function(merge)
{
//Roo.log("undomanager +" + (merge ? 'Y':'n'));
// not sure if this should clear the timer
merge = typeof(merge) == 'undefined' ? false : merge;
this.scope.undoManager.transact({
scope : this.scope,
oldHTML: this.current_html,
newHTML: this.scope.innerHTML,
// nothing to execute (content already changed when input is fired)
execute: function() { },
undo: function() {
this.scope.innerHTML = this.current_html = this.oldHTML;
},
redo: function() {
this.scope.innerHTML = this.current_html = this.newHTML;
}
}, false); //merge);
this.merge = merge;
this.current_html = this.scope.innerHTML;
}
};