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