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