      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