      1 /**
      2  * RichDom for W3C Standard Engine
      3  */
      4 xq.RichDomW3 = Class.create(xq.RichDom, {
      5 	insertNode: function(node) {
      6 		var rng = this.rng();
      7 		rng.insertNode(node);
      8 		rng.selectNode(node);
      9 		rng.collapse(false);
     10 		return node;
     11 	},
     12
     13 	removeTrailingWhitespace: function(block) {
     14 		// TODO: do nothing
     15 	},
     16
     17 	getOuterHTML: function(element) {
     18 		var div = element.ownerDocument.createElement("div");
     19 		div.appendChild(element.cloneNode(true));
     20 		return div.innerHTML;
     21 	},
     22
     23 	correctEmptyElement: function(element) {
     24 		if(!element || element.nodeType != 1 || this.tree.isAtomic(element)) return;
     25
     26 		if(element.firstChild)
     27 			this.correctEmptyElement(element.firstChild);
     28 		else
     29 			element.appendChild(this.makePlaceHolder());
     30 	},
     31
     32 	correctParagraph: function() {
     33 		if(this.hasSelection()) return false;
     34
     35 		var block = this.getCurrentElement();
     36 		var modified = false;
     37
     38 		if(this.tree.isBlockOnlyContainer(block)) {
     39 			this.execCommand("InsertParagraph");
     40
     41 			// check for atomic block element such as HR
     42 			var newBlock = this.getCurrentElement();
     43 			if(this.tree.isAtomic(newBlock.previousSibling)) {
     44 				var nextBlock = this.tree.findForward(
     45 					newBlock,
     46 					function(node) {return this.tree.isBlock(node) && !this.tree.isBlockOnlyContainer(node)}.bind(this)
     47 				);
     48 				if(nextBlock) {
     49 					this.deleteNode(newBlock);
     50 					this.placeCaretAtStartOf(nextBlock);
     51 				}
     52 			}
     53 			modified = true;
     54 		} else if(this.tree.hasMixedContents(block)) {
     55 			this.wrapAllInlineOrTextNodesAs("P", block, true);
     56 			modified = true;
     57 		}
     58
     59 		block = this.getCurrentElement();
     60 		if(this.tree.isBlock(block) && !this._hasPlaceHolderAtEnd(block)) {
     61 			block.appendChild(this.makePlaceHolder());
     62 			modified = true;
     63 		}
     64
     65 		if(this.tree.isBlock(block)) {
     66 			var parentsLastChild = block.parentNode.lastChild;
     67 			if(this.isPlaceHolder(parentsLastChild)) {
     68 				this.deleteNode(parentsLastChild);
     69 				modified = true;
     70 			}
     71 		}
     72
     73 		return modified;
     74 	},
     75
     76 	_hasPlaceHolderAtEnd: function(block) {
     77 		if(!block.hasChildNodes()) return false;
     78 		return this.isPlaceHolder(block.lastChild) || this._hasPlaceHolderAtEnd(block.lastChild);
     79 	},
     80
     81 	applyBackgroundColor: function(color) {
     82 		this.execCommand("styleWithCSS", "true");
     83 		this.execCommand("hilitecolor", color);
     84 		this.execCommand("styleWithCSS", "false");
     85
     86 		// 0. Save current selection
     87 		var bookmark = this.saveSelection();
     88
     89 		// 1. Get selected blocks
     90 		var blocks = this.getSelectedBlockElements();
     91 		if(blocks.length == 0) return;
     92
     93 		// 2. Apply background-color to all adjust inline elements
     94 		// 3. Remove background-color from blocks
     95 		for(var i = 0; i < blocks.length; i++) {
     96 			if((i == 0 || i == blocks.length-1) && !blocks[i].style.backgroundColor) continue;
     97
     98 			var spans = this.wrapAllInlineOrTextNodesAs("SPAN", blocks[i], true);
     99 			for(var j = 0; j < spans.length; j++) {
    100 				spans[j].style.backgroundColor = color;
    101 			}
    102 			blocks[i].style.backgroundColor = "";
    103 		}
    104
    105 		// 4. Restore selection
    106 		this.restoreSelection(bookmark);
    107 	},
    108
    109
    110
    111
    112 	//////
    113 	// Commands
    114 	execCommand: function(commandId, param) {
    115 		return this.doc.execCommand(commandId, false, param || null);
    116 	},
    117
    118 	saveSelection: function() {
    119 		var rng = this.rng();
    120 		return [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset];
    121 	},
    122
    123 	restoreSelection: function(bookmark) {
    124 		var rng = this.rng();
    125 		rng.setStart(bookmark[0], bookmark[1]);
    126 		rng.setEnd(bookmark[2], bookmark[3]);
    127 	},
    128
    129 	applyRemoveFormat: function() {
    130 		this.execCommand("RemoveFormat");
    131 		this.execCommand("Unlink");
    132 	},
    133 	applyEmphasis: function() {
    134 		// Generate <i> tag. It will be replaced with <emphasis> tag during cleanup phase.
    135 		this.execCommand("styleWithCSS", "false");
    136 		this.execCommand("italic");
    137 	},
    138 	applyStrongEmphasis: function() {
    139 		// Generate <b> tag. It will be replaced with <strong> tag during cleanup phase.
    140 		this.execCommand("styleWithCSS", "false");
    141 		this.execCommand("bold");
    142 	},
    143 	applyStrike: function() {
    144 		// Generate <strike> tag. It will be replaced with <style class="strike"> tag during cleanup phase.
    145 		this.execCommand("styleWithCSS", "false");
    146 		this.execCommand("strikethrough");
    147 	},
    148 	applyUnderline: function() {
    149 		// Generate <u> tag. It will be replaced with <em class="underline"> tag during cleanup phase.
    150 		this.execCommand("styleWithCSS", "false");
    151 		this.execCommand("underline");
    152 	},
    153 	execHeading: function(level) {
    154 		this.execCommand("Heading", "H" + level);
    155 	},
    156
    157
    158
    159 	//////
    160 	// Focus/Caret/Selection
    161
    162 	focus: function() {
    163 		setTimeout(this._focus.bind(this), 0);
    164 	},
    165
    166 	/** @private */
    167 	_focus: function() {
    168 		this.win.focus();
    169 		if(!this.hasSelection() && this.getCurrentElement().nodeName == "HTML") {
    170 			this.selectElement(this.doc.body.firstChild);
    171 			this.collapseSelection(true);
    172 		}
    173 	},
    174
    175 	sel: function() {
    176 		return this.win.getSelection();
    177 	},
    178
    179 	rng: function() {
    180 		var sel = this.sel();
    181 		return (sel == null || sel.rangeCount == 0) ? null : sel.getRangeAt(0);
    182 	},
    183
    184 	hasSelection: function() {
    185 		var sel = this.sel();
    186 		return sel && !sel.isCollapsed;
    187 	},
    188
    189 	deleteSelection: function() {
    190 		this.rng().deleteContents();
    191 		this.sel().collapseToStart();
    192 	},
    193
    194 	selectElement: function(element, entireElement) {throw "Not implemented yet"},
    195
    196 	selectBlocksBetween: function(start, end) {
    197 		// required to avoid FF selection bug.
    198 		try {
    199 			if(!xq.Browser.isMac) this.doc.execCommand("SelectAll", false, null);
    200 		} catch(ignored) {}
    201
    202 		var rng = this.rng();
    203 		rng.setStart(start.firstChild, 0);
    204 		rng.setEnd(end, end.childNodes.length);
    205 	},
    206
    207 	collapseSelection: function(toStart) {
    208 		this.rng().collapse(toStart);
    209 	},
    210
    211 	placeCaretAtStartOf: function(element) {
    212 		while(this.tree.isBlock(element.firstChild)) {
    213 			element = element.firstChild;
    214 		}
    215 		this.selectElement(element, false);
    216 		this.collapseSelection(true);
    217 	},
    218
    219 	getSelectionAsHtml: function() {
    220 		var container = document.createElement("div");
    221 		container.appendChild(this.rng().cloneContents());
    222 		return container.innerHTML;
    223 	},
    224
    225 	getSelectionAsText: function() {
    226 		return this.rng().toString()
    227 	},
    228
    229 	hasImportantAttributes: function(element) {
    230 		return !!(element.id || element.className || element.style.cssText);
    231 	},
    232
    233 	isEmptyBlock: function(element) {
    234 		if(!element.hasChildNodes()) return true;
    235 		var children = element.childNodes;
    236 		for(var i = 0; i < children.length; i++) {
    237 			if(!this.isPlaceHolder(children[i]) && !this.isEmptyTextNode(children[i])) return false;
    238 		}
    239 		return true;
    240 	},
    241
    242 	getLastChild: function(element) {
    243 		if(!element || !element.hasChildNodes()) return null;
    244
    245 		var nodes = $A(element.childNodes).reverse();
    246
    247 		for(var i = 0; i < nodes.length; i++) {
    248 			if(!this.isPlaceHolder(nodes[i]) && !this.isEmptyTextNode(nodes[i])) return nodes[i];
    249 		}
    250 		return null;
    251 	},
    252
    253 	getCurrentElement: function() {
    254 		var rng = this.rng();
    255 		if(!rng) return null;
    256
    257 		var container = rng.startContainer;
    258 		return container.nodeType == 3 ? container.parentNode : container;
    259 	},
    260
    261 	getBlockElementsAtSelectionEdge: function(naturalOrder, ignoreEmptyEdges) {
    262 		var start = this.getBlockElementAtSelectionStart();
    263 		var end = this.getBlockElementAtSelectionEnd();
    264
    265 		var reversed = false;
    266
    267 		if(naturalOrder && start != end && this.tree.checkTargetBackward(start, end)) {
    268 			var temp = start;
    269 			start = end;
    270 			end = temp;
    271
    272 			reversed = true;
    273 		}
    274
    275 		if(ignoreEmptyEdges && start != end) {
    276 			// TODO - Firefox sometimes selects one more block.
    277 /*
    278
    279 			var sel = this.sel();
    280 			if(reversed) {
    281 				if(sel.focusNode.nodeType == 1) start = start.nextSibling;
    282 				if(sel.anchorNode.nodeType == 3 && sel.focusOffset == 0) end = end.previousSibling;
    283 			} else {
    284 				if(sel.anchorNode.nodeType == 1) start = start.nextSibling;
    285 				if(sel.focusNode.nodeType == 3 && sel.focusOffset == 0) end = end.previousSibling;
    286 			}
    287 */
    288 		}
    289
    290 		return [start, end];
    291 	},
    292
    293 	getBlockElementAtSelectionStart: function() {
    294 		var block = this.getParentBlockElementOf(this.sel().anchorNode);
    295
    296 		// find bottom-most first block child
    297 		while(this.tree.isBlockContainer(block) && block.firstChild && this.tree.isBlock(block.firstChild)) {
    298 			block = block.firstChild;
    299 		}
    300
    301 		return block;
    302 	},
    303
    304 	getBlockElementAtSelectionEnd: function() {
    305 		var block = this.getParentBlockElementOf(this.sel().focusNode);
    306
    307 		// find bottom-most last block child
    308 		while(this.tree.isBlockContainer(block) && block.lastChild && this.tree.isBlock(block.lastChild)) {
    309 			block = block.lastChild;
    310 		}
    311
    312 		return block;
    313 	},
    314
    315 	isCaretAtBlockStart: function() {
    316 		if(this.isCaretAtEmptyBlock()) return true;
    317 		if(this.hasSelection()) return false;
    318 		var rng = this.rng();
    319 		var node = this.getCurrentBlockElement();
    320 		var isTrue = false;
    321
    322 		if(node == rng.startContainer) {
    323 			var marker = this.pushMarker();
    324 			while (node = this.getFirstChild(node)) {
    325 				if (node == marker) {
    326 					isTrue = true;
    327 					break;
    328 				}
    329 			}
    330 			this.popMarker();
    331 		} else {
    332 			while (node = node.firstChild) {
    333 				if (node == rng.startContainer && rng.startOffset == 0) {
    334 					isTrue = true;
    335 					break;
    336 				}
    337 			}
    338 		}
    339
    340 		return isTrue;
    341 	},
    342
    343 	isCaretAtBlockEnd: function() {
    344 		if(this.isCaretAtEmptyBlock()) return true;
    345 		if(this.hasSelection()) return false;
    346
    347 		var rng = this.rng();
    348 		var node = this.getCurrentBlockElement();
    349 		var isTrue = false;
    350
    351 		if(node == rng.startContainer) {
    352 			var marker = this.pushMarker();
    353 			while (node = this.getLastChild(node)) {
    354 				if ((node == marker) || (this.isPlaceHolder(node) && node.previousSibling == marker)) {
    355 					isTrue = true;
    356 					break;
    357 				}
    358 			}
    359 			this.popMarker();
    360 		} else {
    361 			while (node = this.getLastChild(node)) {
    362 				if (node == rng.endContainer && rng.endContainer.nodeType == 1) {
    363 					isTrue = true;
    364 					break;
    365 				} else if (node == rng.endContainer && rng.endOffset == node.nodeValue.length) {
    366 					isTrue = true;
    367 					break;
    368 				}
    369 			}
    370 		}
    371
    372 		return isTrue;
    373 	}
    374 });
    375