1 /** 2 * @fileOverview xq.EditHistory manages editing history and performs UNDO/REDO. 3 */ 4 xq.EditHistory = xq.Class({ 5 /** 6 * Initializer 7 * 8 * @constructor 9 * @param {xq.RichDom} rdom RichDom instance 10 * @param {Number} [max] maximum UNDO buffer size(default value is 100). 11 */ 12 initialize: function(rdom, max) { 13 xq.addToFinalizeQueue(this); 14 if (!rdom) throw "IllegalArgumentException"; 15 16 this.disabled = false; 17 this.max = max || 100; 18 this.rdom = rdom; 19 this.root = rdom.getRoot(); 20 this.clear(); 21 22 this.lastModified = Date.get(); 23 }, 24 getLastModifiedDate: function() { 25 return this.lastModified; 26 }, 27 isUndoable: function() { 28 return this.queue.length > 0 && this.index > 0; 29 }, 30 isRedoable: function() { 31 return this.queue.length > 0 && this.index < this.queue.length - 1; 32 }, 33 disable: function() { 34 this.disabled = true; 35 }, 36 enable: function() { 37 this.disabled = false; 38 }, 39 undo: function() { 40 this.pushContent(); 41 42 if (this.isUndoable()) { 43 this.index--; 44 this.popContent(); 45 return true; 46 } else { 47 return false; 48 } 49 }, 50 redo: function() { 51 if (this.isRedoable()) { 52 this.index++; 53 this.popContent(); 54 return true; 55 } else { 56 return false; 57 } 58 }, 59 onCommand: function() { 60 this.lastModified = Date.get(); 61 if(this.disabled) return false; 62 63 return this.pushContent(); 64 }, 65 onEvent: function(event) { 66 this.lastModified = Date.get(); 67 if(this.disabled) return false; 68 69 // ignore normal keys 70 if('keydown' == event.type && !(event.ctrlKey || event.metaKey)) return false; 71 if(['keydown', 'keyup', 'keypress'].indexOf(event.type) != -1 && !event.ctrlKey && !event.altKey && !event.metaKey && [33,34,35,36,37,38,39,40].indexOf(event.keyCode) == -1) return false; 72 if(['keydown', 'keyup', 'keypress'].indexOf(event.type) != -1 && (event.ctrlKey || event.metaKey) && [89,90].indexOf(event.keyCode) != -1) return false; 73 74 // ignore ctrl/shift/alt/meta keys 75 if([16,17,18,224].indexOf(event.keyCode) != -1) return false; 76 77 return this.pushContent(); 78 }, 79 popContent: function() { 80 this.lastModified = Date.get(); 81 var entry = this.queue[this.index]; 82 if (entry.caret > 0) { 83 var html=entry.html.substring(0, entry.caret) + '<span id="caret_marker_00700"></span>' + entry.html.substring(entry.caret); 84 this.root.innerHTML = html; 85 } else { 86 this.root.innerHTML = entry.html; 87 } 88 this.restoreCaret(); 89 }, 90 pushContent: function(ignoreCaret) { 91 if(xq.Browser.isTrident && !ignoreCaret && !this.rdom.hasFocus()) return false; 92 if(!this.rdom.getCurrentElement()) return false; 93 94 var html = this.root.innerHTML; 95 if(html == (this.queue[this.index] ? this.queue[this.index].html : null)) return false; 96 var caret = ignoreCaret ? -1 : this.saveCaret(); 97 98 if(this.queue.length >= this.max) { 99 this.queue.shift(); 100 } else { 101 this.index++; 102 } 103 104 this.queue.splice(this.index, this.queue.length - this.index, {html:html, caret:caret}); 105 return true; 106 }, 107 clear: function() { 108 this.index = -1; 109 this.queue = []; 110 this.pushContent(true); 111 }, 112 saveCaret: function() { 113 if(this.rdom.hasSelection()) return null; 114 115 // FF on Mac has a caret problem with these lines. --2007/11/19 116 var marker = this.rdom.pushMarker(); 117 var str = xq.Browser.isTrident ? '<SPAN class='+marker.className : '<span class="'+marker.className+'"'; 118 var caret = this.rdom.getRoot().innerHTML.indexOf(str); 119 this.rdom.popMarker(true); 120 121 return caret; 122 123 /* 124 // This is old code. It also has same problem. 125 126 if(this.rdom.hasSelection()) return null; 127 128 var bookmark = this.rdom.saveSelection(); 129 var marker = this.rdom.pushMarker(); 130 131 var str = xq.Browser.isTrident ? '<SPAN class='+marker.className : '<span class="'+marker.className+'"'; 132 var caret = this.rdom.getRoot().innerHTML.indexOf(str); 133 134 this.rdom.popMarker(); 135 this.rdom.restoreSelection(bookmark); 136 137 return caret; 138 */ 139 }, 140 restoreCaret: function() { 141 var marker = this.rdom.$('caret_marker_00700'); 142 143 if(marker) { 144 this.rdom.selectElement(marker, true); 145 this.rdom.collapseSelection(false); 146 this.rdom.deleteNode(marker); 147 } else { 148 var node = this.rdom.tree.findForward(this.rdom.getRoot(), function(node) { 149 return this.isBlock(node) && !this.hasBlocks(node); 150 }.bind(this.rdom.tree)); 151 this.rdom.selectElement(node, false); 152 this.rdom.collapseSelection(false); 153 154 } 155 } 156 }); 157