X-Git-Url: http://git.roojs.org/?p=roojs1;a=blobdiff_plain;f=roojs-core-debug.js;h=73e26b3337abac782af0466068522afde7506ce8;hp=fd3afc41ac1729907af6c83f0b549938ee3d57a4;hb=fc32d3336ffe6a4ac93549dab21fd04b7d7056d3;hpb=d6d1e1fe78ba08b1a873583bb12bf05588ff45ae diff --git a/roojs-core-debug.js b/roojs-core-debug.js index fd3afc41ac..73e26b3337 100644 --- a/roojs-core-debug.js +++ b/roojs-core-debug.js @@ -1205,12 +1205,35 @@ document.write(dt.format(Date.patterns.ShortDate)); /** Returns the number of milliseconds between this date and date @param {Date} date (optional) Defaults to now - @return {Number} The diff in milliseconds + @param {String} interval (optional) Default Date.MILLI, A valid date interval enum value (eg. Date.DAY) + @return {Number} The diff in milliseconds or units of interval @member Date getElapsed */ -Date.prototype.getElapsed = function(date) { - return Math.abs((date || new Date()).getTime()-this.getTime()); +Date.prototype.getElapsed = function(date, interval) +{ + date = date || new Date(); + var ret = Math.abs(date.getTime()-this.getTime()); + switch (interval) { + + case Date.SECOND: + return Math.floor(ret / (1000)); + case Date.MINUTE: + return Math.floor(ret / (1000*60)); + case Date.HOUR: + return Math.floor(ret / (1000*60*60)); + case Date.DAY: + return Math.floor(ret / (1000*60*60*24)); + case Date.MONTH: // this does not give exact number...?? + return ((date.format("Y") - this.format("Y")) * 12) + (date.format("m") - this.format("m")); + case Date.YEAR: // this does not give exact number...?? + return (date.format("Y") - this.format("Y")); + + case Date.MILLI: + default: + return ret; + } }; + // was in date file.. @@ -4907,7 +4930,468 @@ Roo.lib.Easing = { } }; })(); -/* +/** + * 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: + *

+
+
+editor.undoManager = new Roo.lib.UndoManager(1000, editor);
+ 
+
+ +* For more information see this blog post with examples: +* DomHelper + - Create Elements using DOM, HTML fragments and Templates. +* @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: +

+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); 
+
+ * + * + * @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; + } + + + + + + +}; +/** + * @class Roo.lib.Range + * @constructor + * This is a toolkit, normally used to copy features into a Dom Range element + * Roo.lib.Range.wrap(x); + * + * + * + */ +Roo.lib.Range = function() { }; + +/** + * Wrap a Dom Range object, to give it new features... + * @static + * @param {Range} the range to wrap + */ +Roo.lib.Range.wrap = function(r) { + return Roo.apply(r, Roo.lib.Range.prototype); +}; +/** + * find a parent node eg. LI / OL + * @param {string|Array} node name or array of nodenames + * @return {DomElement|false} + */ +Roo.apply(Roo.lib.Range.prototype, +{ + + closest : function(str) + { + if (typeof(str) != 'string') { + // assume it's a array. + for(var i = 0;i < str.length;i++) { + var r = this.closest(str[i]); + if (r !== false) { + return r; + } + + } + return false; + } + str = str.toLowerCase(); + var n = this.commonAncestorContainer; // might not be a node + while (n.nodeType != 1) { + n = n.parentNode; + } + + if (n.nodeName.toLowerCase() == str ) { + return n; + } + if (n.nodeName.toLowerCase() == 'body') { + return false; + } + + return n.closest(str) || false; + + }, + cloneRange : function() + { + return Roo.lib.Range.wrap(Range.prototype.cloneRange.call(this)); + } +});/** + * @class Roo.lib.Selection + * @constructor + * This is a toolkit, normally used to copy features into a Dom Selection element + * Roo.lib.Selection.wrap(x); + * + * + * + */ +Roo.lib.Selection = function() { }; + +/** + * Wrap a Dom Range object, to give it new features... + * @static + * @param {Range} the range to wrap + */ +Roo.lib.Selection.wrap = function(r, doc) { + Roo.apply(r, Roo.lib.Selection.prototype); + r.ownerDocument = doc; // usefull so we dont have to keep referening to it. + return r; +}; +/** + * find a parent node eg. LI / OL + * @param {string|Array} node name or array of nodenames + * @return {DomElement|false} + */ +Roo.apply(Roo.lib.Selection.prototype, +{ + /** + * the owner document + */ + ownerDocument : false, + + getRangeAt : function(n) + { + return Roo.lib.Range.wrap(Selection.prototype.getRangeAt.call(this,n)); + }, + + /** + * insert node at selection + * @param {DomElement|string} node + * @param {string} cursor (after|in|none) where to place the cursor after inserting. + */ + insertNode: function(node, cursor) + { + if (typeof(node) == 'string') { + node = this.ownerDocument.createElement(node); + if (cursor == 'in') { + node.innerHTML = ' '; + } + } + + var range = this.getRangeAt(0); + + if (this.type != 'Caret') { + range.deleteContents(); + } + var sn = node.childNodes[0]; // select the contents. + + + + range.insertNode(node); + if (cursor == 'after') { + node.insertAdjacentHTML('afterend', ' '); + sn = node.nextSibling; + } + + if (cursor == 'none') { + return; + } + + this.cursorText(sn); + }, + + cursorText : function(n) + { + + //var range = this.getRangeAt(0); + range = Roo.lib.Range.wrap(new Range()); + //range.selectNode(n); + + var ix = Array.from(n.parentNode.childNodes).indexOf(n); + range.setStart(n.parentNode,ix); + range.setEnd(n.parentNode,ix+1); + //range.collapse(false); + + this.removeAllRanges(); + this.addRange(range); + + Roo.log([n, range, this,this.baseOffset,this.extentOffset, this.type]); + }, + cursorAfter : function(n) + { + if (!n.nextSibling || n.nextSibling.nodeValue != ' ') { + n.insertAdjacentHTML('afterend', ' '); + } + this.cursorText (n.nextSibling); + } + + +});/* * Based on: * Ext JS Library 1.1.1 * Copyright(c) 2006-2007, Ext JS, LLC.