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