mozdev.org

Mozile (xhtml editing in your browser)    

resources:
Overview

XHTMLBasic.js

Summary

The XHTMLBasic modules includes commands derived from the MozileCommand object which allow for editing of HTML and XHTML documents. ALso included is a revision of the Mozile.initializeToolbar() function, which creates a number of commands commonly used in XHTML editing. This module does not include support for validation of XHTML documents. Instead it makes some assumptions about XHTML, which lead to a more light-weight implementation. Elements are distinguished into block and non-block (inline) elements, based on their CSS "display" property. Most commands function differently on blocks than they do on inline elements. While the distinction could allow the creation of invalid XHTML documents, in most cases the commands will preserve the validity of the XHTML. Module options: - semantic=[true|false] Default is "true". When this option is set to "true", "strong" and "em" will be used for the bold and italic commands. When it is false, "b" and "i" will be used instead.

Version: 0.7

Author: James A. Overton


Class Summary
MozileBlockSetCommand  
MozileInsertCommand  
MozileStyleCommand  
MozileUnformatCommand  
MozileWrapCommand  

/* ***** BEGIN LICENSE BLOCK *****
 * Licensed under Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * Full Terms at http://mozile.mozdev.org/license2.html
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is James A. Overton's code (james@overton.ca).
 *
 * The Initial Developer of the Original Code is James A. Overton.
 * Portions created by the Initial Developer are Copyright (C) 2005-2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *	James A. Overton <james@overton.ca>
 *
 * ***** END LICENSE BLOCK ***** */

/** 
 * @fileoverview The XHTMLBasic modules includes commands derived from the MozileCommand object which allow for editing of HTML and XHTML documents. ALso included is a revision of the Mozile.initializeToolbar() function, which creates a number of commands commonly used in XHTML editing. 
 * This module does not include support for validation of XHTML documents. Instead it makes some assumptions about XHTML, which lead to a more light-weight implementation. Elements are distinguished into block and non-block (inline) elements, based on their CSS "display" property. Most commands function differently on blocks than they do on inline elements. While the distinction could allow the creation of invalid XHTML documents, in most cases the commands will preserve the validity of the XHTML.
 * Module options:
 * - semantic=[true|false] Default is "true". When this option is set to "true", "strong" and "em" will be used for the bold and italic commands. When it is false, "b" and "i" will be used instead.
 * @link http://mozile.mozdev.org 
 * @author James A. Overton <james@overton.ca>
 * @version 0.7
 */




/** Mozile Block Set Command - Object -
 * Sets the properties of the block element which contains the selection. If the selection is collapsed, then the first block-level container for the selection is changed. If the selection is not collapsed (i.e. it contains a range), then that selection is wrapped in a new container.
 * <p>Configuration String Requirements: "tag=[tagName]", for example "tag=h1".
 * @constructor
 * @param {String} configString A properly formatted configuration string.
 */
function MozileBlockSetCommand(configString) {

	/**
	 * @private
	 * @type String
	 */
	this._configString = String(configString);

}

MozileBlockSetCommand.prototype = new MozileCommand();
MozileBlockSetCommand.prototype.constructor = MozileBlockSetCommand;


/**
 * Gets the tag for the module. Throws an error if none exists.
 * @type String
 */
MozileBlockSetCommand.prototype.getTag = function() {
	if(!this.getOption("tag")) throw Error("MozileBlockSetCommand requires a 'tag' in the configuration string.");
	return this.getOption("tag");
}

/**
 * @type XULElement
 */
MozileBlockSetCommand.prototype.getMenuitem = function() {
	if(!this._menuitem) {
		this._createMenuitem();
		this._menuitem.setAttribute("type", "checkbox");
	}
	return this._menuitem;
}

/**
 * True only if the getParentBlock().nodeName matches this.getTag()
 * @type Boolean
 */
MozileBlockSetCommand.prototype.isActive = function() {	
	var selection = window.getSelection();
	var range = selection.getRangeAt(0);
	var node = range.commonAncestorContainer;
	var parentBlock = node.getParentBlock();
	if(parentBlock.nodeName.toLowerCase()==this.getTag()) {
		return true;
	}
	else {
		return false;
	}
}

/**
 * Executes the command.
 * @param {Event} event The event object which triggered the command.
 * @type Void
 */
MozileBlockSetCommand.prototype.execute = function(event) {		
	var selection = window.getSelection();
	var range = selection.getRangeAt(0).cloneRange();
	
	// If the selection is not collapsed, wrap the selection in the tag and return.
	if(!selection.isCollapsed) {
		var element = document.createElement(this.getTag());
		element.appendChild(range.extractContents());
		range.insertNode(element);
		return;
	}
	
	// If the selection is collapsed, then we replace the current block container with a the new tag.
	var node = range.commonAncestorContainer;
	
	// Get the first ancestor element which is a block.
	node = node.getParentBlock();
	
	// Select that node, extract the contents, create a new element, append the contents, insert it before the old node, and remove the old node.
	if(node.parentNode) {
		var focusOffset = selection.focusOffset;

		range.selectNodeContents(node);
		var contents = range.extractContents();
		var newNode = document.createElement(this.getTag());
		newNode.appendChild(contents);
		node.parentNode.replaceChild(newNode, node);

		// Try to restore the selection.
		range = document.createRange();
		try {
			range.setStart(newNode.firstChild, focusOffset);
		} catch(e) {
			range.setStart(newNode.firstChild, 0);
		}
		selection.removeAllRanges();
		selection.addRange(range);
	}
}







/** Mozile Unformat Command Object -
 * This command will remove all formatting from an non-collapsed selection, or remove the wrapper element from around the current node if the selection is collapsed.
 * A "tag" option can be set, in which case the command will remove only that tag. For example, "tag=a" will remove link tags. This is optional.
 * @constructor
 * @param {String} configString A properly formatted configuration string.
 */
function MozileUnformatCommand(configString) {

	/**
	 * @private
	 * @type String
	 */
	this._configString = String(configString);
}

MozileUnformatCommand.prototype = new MozileCommand();
MozileUnformatCommand.prototype.constructor = MozileUnformatCommand;


/**
 * Gets the tag to be removed, if that option has been set.
 * If the Mozile option "replaceAnchors" is set, then the method replaces "a" with "mozileReplaceAnchors".
 * @type String
 */
MozileUnformatCommand.prototype.getTag = function() {
	var tag = this.getOption("tag");
	if(tag && tag == "a" && mozile.getOption("replaceAnchors")) return "mozileAnchorReplacement".toLowerCase();
	else return tag;
}


/**
 * If this command has its "tag" option set, this method checks to see if an ancestor has the matching tag name.
 * @type Boolean
 */
MozileUnformatCommand.prototype.isAvailable = function() {
	if(this.getTag() == undefined) return true;
	else {
		try {
			var node = window.getSelection().getRangeAt(0).commonAncestorContainer;
			while(node) {
				if(node.nodeName.toLowerCase() == this.getTag()) return true;
				else node = node.parentNode;
			}
		} catch(e) { }
	}
	return false;
}


/** 
 * Executes the command.
 * @param {Event} event The event object which triggered the command.
 * @type Void
 */
MozileUnformatCommand.prototype.execute = function(event) {
	
	var selection = window.getSelection();
	var range = selection.getRangeAt(0).cloneRange();
	var contents,text;
	
	// if a tag is specified, remove only that tag.
	if(this.getTag() != undefined) {
		var focusOffset = selection.focusOffset;
		var node = range.commonAncestorContainer;
		var target = null;
		while(node) {		
			if(node.nodeName.toLowerCase() == this.getTag()) {
				target = node;
				break;
			}
			else node = node.parentNode;
		}
		if(target) {
			if(target.nodeType != Node.ELEMENT_NODE) target = target.parentNode;
			if(!target) return false;
			range.selectNodeContents(target);
			contents = range.extractContents();
			var firstChild = contents.firstChild;
			range.selectNode(node);
			range.insertNode(contents);
			// Try to restore the selection.
			try {
				range.setStart(firstChild, focusOffset);
			}
			catch(e) {
				range.setStart(firstChild,0);
			}
			range.collapse(true);
			selection.removeAllRanges()
			selection.addRange(range);
		}
		return true;
	}
	
	// if the selection is not collapsed, extract the contents as a string and replace the current selection with the string.
	else if(!selection.isCollapsed) {
		contents = range.toString();
		text = document.createTextNode(contents);
		range.deleteContents();
		range.insertNode(text);
		
		// Restore the selection.
		range.selectNode(text);
		selection.removeAllRanges();
		selection.addRange(range);
	}
	
	// if the selection is collapsed, remove the element containing the selection, but reinsert all the contents where the element used to be.
	else {	
		var focusOffset = selection.focusOffset;
		var node = range.commonAncestorContainer;
		if(node.nodeType != Node.ELEMENT_NODE) node = node.parentNode;
		
		range.selectNodeContents(node);
		contents = range.extractContents();
		var firstChild = contents.firstChild;
		range.selectNode(node);
		range.insertNode(contents);

		// Try to restore the selection.
		try {
			range.setStart(firstChild, focusOffset);
		}
		catch(e) {
			range.setStart(firstChild,0);
		}
		range.collapse(true);
		selection.removeAllRanges()
		selection.addRange(range);
	
	}
	return true;

}





/** Mozile Wrap Command Object -
 * Wraps the current selection with an element. This command has three modes: inline, toggle, and block. Inline means that the current selection is wraped in a non-block element. Toggle acts like inline, except that if the current selection is already inside the given element type then the selection "unwrapped" so that it is no longer within that element. Block means that the wrapper element is a block.
 * <p>Configuration String Requirements: "mode=[inline|toggle|block], tag=[tagName]", for example "mode=toggle, tag=b".
 * @constructor
 * @param {String} configString A properly formatted configuration string.
 */
function MozileWrapCommand(configString) {

	/**
	 * @private
	 * @type String
	 */
	this._configString = String(configString);

}

MozileWrapCommand.prototype = new MozileCommand();
MozileWrapCommand.prototype.constructor = MozileWrapCommand;


/**
 * Gets the mode for the module. Throws an error if none exists.
 * @type String
 */
MozileWrapCommand.prototype.getMode = function() {
	if(!this.getOption("mode")) throw Error("Invalid configuration string.");
	return this.getOption("mode");
}

/**
 * Gets the tag for the module. Throws an error if none exists.
 * @type String
 */
MozileWrapCommand.prototype.getTag = function() {
	if(!this.getOption("tag")) throw Error("Invalid configuration string.");
	return this.getOption("tag");
}

/**
 * @type XULElement
 */
MozileWrapCommand.prototype.getMenuitem = function() {
	if(!this._menuitem) {
		this._createMenuitem();
		this._menuitem.setAttribute("type", "checkbox");
	}
	return this._menuitem;
}



/** Mozile Wrap Command - Is Active -
 * Checks to see if the current node has a parent which matches this.getTag().
 * @type Boolean
 */
MozileWrapCommand.prototype.isActive = function() {

	var selection = window.getSelection();
	var range = selection.getRangeAt(0);
	var node = range.commonAncestorContainer;
	var flag = false;
	while(node) {
		if(node.nodeName.toLowerCase()==this.getTag()) {
			flag = true;
			break;
		} 
		node = node.parentNode;
	}

	return flag;
}


/**
 * Executes the command.
 * @param {Event} event The event object which triggered the command.
 * @type Void
 */
MozileWrapCommand.prototype.execute = function(event) {
	
	var selection = window.getSelection();
	var range = selection.getRangeAt(0).cloneRange();
	
	var container, element, text, oldRange, blocks, firstElement, lastElement, i, flag;
	

	// Handle the wrap based on the mode
	switch (this.getMode()) {		// Inline Case
		case "inline":
			// If the selection is collapsed, just add a new wrapper element with and empty text node.
			if(selection.isCollapsed) {
				element = document.createElement(this.getTag());
				text = document.createTextNode("");
				element.appendChild(text);
				range.insertNode(element);
				range.selectNode(text);
				range.collapse(true);
				selection.removeAllRanges();
				selection.addRange(range);
			}
			
			// If the selection is not collapsed, wrap the whole range.
			else {
				element = document.createElement(this.getTag());
				range = this._wrapRange(range, element)
				selection.removeAllRanges();
				selection.addRange(range);
			}
				
			return true;
			break;


		// Inline Toggle Case
		case "toggle":
			// If the selection is within a case of this.getTag(), then unwrap it.
			if(this.isActive()) {
				
				// Get the element which makes this selection active.
				container = range.commonAncestorContainer;
				flag = false;
				while(container) {
					if( container.nodeName.toLowerCase() == this.getTag() ) {
						flag = true;
						break;
					} 
					container = container.parentNode;
				}
				
				element = document.createElement(this.getTag());
				range = this._unwrapRange(container, range, element);
				selection.removeAllRanges();
				selection.addRange(range);
			
			}
			
			// Otherwise, behave like "inline"
			else {
		
				// If the selection is collapsed, just add a new wrapper element with and empty text node.
				if(selection.isCollapsed) {
					element = document.createElement(this.getTag());
					text = document.createTextNode("");
					element.appendChild(text);
					range.insertNode(element);
					range.selectNode(text);
					range.collapse(true);
					selection.removeAllRanges();
					selection.addRange(range);
				}
				
				// If the selection is not collapsed, wrap the whole range.
				else {
					element = document.createElement(this.getTag());
					range = this._wrapRange(range, element);
					selection.removeAllRanges();
					selection.addRange(range);	
				}
			}
				
			return true;
			break;

		// All other cases (particularly "block").
		default:
			element = document.createElement(this.getTag());
			element.appendChild(range.extractContents());
			range.insertNode(element);
			if(selection.isCollapsed) {
				text = document.createTextNode("");
				element.appendChild(text);
				range.selectNodeContents(text);
				selection.removeAllRanges();
				selection.addRange(range);
				selection.collapseToEnd();
			}
			else {
				range.selectNodeContents(element);
				selection.removeAllRanges();
				selection.addRange(range);
			}
			return true;	
			break;
	}
	
	return true;
	
}




/** Mozile Wrap Command - Wrap Block -
 * Extract the contents of the given block, and inserts them within the given element which is made to be a child of the block.
 * @private
 * @param block The block to be wrapped.
 * @param element The element with which to wrap the block.
 * @return The new element.
 */
MozileWrapCommand.prototype._wrapBlock = function(block, element) {
	var range = document.createRange();
	if(block.nodeType==1) {
		range.selectNodeContents(block);
	}
	else {
		//alert(block.textContent);
		range.selectNode(block);
	}
	element.appendChild(range.extractContents());
	range.insertNode(element);
	return element;
}



/** Mozile Wrap Command - Wrap Inline -
 * Wraps a range (which does not span blocks) inside the given element.
 * @private
 * @param range The range to be wrapped.
 * @param element The element with which to wrap the block.
 * @return The new element.
 */
MozileWrapCommand.prototype._wrapInline = function(range, element) {
	element.appendChild(range.extractContents());
	range.insertNode(element);
	return element;
}



/** Mozile Wrap Command - Wrap Range -
 * Wraps an entire range (which might span multiple blocks) in copies of the given elementTemplate. This involves calls to wrapBlock and wrapInline.
 * @private
 * @param range The range to be wrapped.
 * @param elementTemplae The element which will be cloned and used to wrap the block.
 * @return The new range.
 */
MozileWrapCommand.prototype._wrapRange = function(range, elementTemplate) {
	// Get the block in the range.
	var blocks = range.getBlocks();

	var element, firstElement, lastElement;
	
	// If there are no blocks just wrap the selection.
	if(blocks.length<2) {
		element = elementTemplate.cloneNode(true);
		element = this._wrapInline(range, element);
		range.selectNodeContents(element);
	}
	
	// If there are several blocks, then use wrapInline on the first and last, and wrapBlock on all the ones in between
	else {
		// Store the range
		var oldRange = range.cloneRange();
		
		// Wrap to the end of the startContainer.
		range = oldRange.cloneRange();
		if(blocks[0].nodeType==1) {
			range.setEnd(blocks[0].lastChild, blocks[0].lastChild.textContent.length);
		}
		else {
			range.setEnd(blocks[0], blocks[0].textContent.length);
		}
		element = elementTemplate.cloneNode(true);
		firstElement = this._wrapInline(range, element);
		
		// Wrap the contents of all inner blocks.
		for(var i=1; i < blocks.length-1; i++) {
			element = elementTemplate.cloneNode(true);
			this._wrapBlock(blocks[i], element);				
		}
		
		// Wrap from the start of the endContainer.
		range = oldRange.cloneRange();
		range.setStart(blocks[blocks.length-1], 0);
		element = elementTemplate.cloneNode(true);
		lastElement = this._wrapInline(range, element);
	
		// Reset range and restore the selection
		range = document.createRange();
		//range.selectNodeContents(lastElement);
		range.setStart(firstElement.firstChild, 0);
		range.setEnd(lastElement.lastChild, lastElement.lastChild.textContent.length);
	}
	
	return range;
}

/** Mozile Wrap Command - Unwrap Range -
 * Given a range which does not span blocks, remove the range from the container which used to hold it, this removing it from the scope of the this.getTag() element.
 * @private
 * @param container The container element with name this.getTag() from which the range wil be removed.
 * @param range The range to be unwrapped.
 * @param elementTemplate The element which will be cloned and used to wrap the block.
 * @return The new range.
 */
MozileWrapCommand.prototype._unwrapRange = function(container, range, elementTemplate) {
	
	// Remember whether the range was collapsed, and store a copy of it.
	var collapsed = range.collapsed;		
	var oldRange = range.cloneRange();
	var element, firstElement, lastElement;
	
	// Wrap part of the container after the range.
	range.selectNodeContents(container);
	range.setStart(oldRange.endContainer, oldRange.endOffset);
	element = elementTemplate.cloneNode(true);
	this._wrapInline(range, element);
	
	// Wrap the part of the container before the range.
	range.selectNodeContents(container);
	range.setEnd(oldRange.startContainer, oldRange.startOffset);
	element = elementTemplate.cloneNode(true);
	this._wrapInline(range, element);
	
	// Remove the container and replace it with its contents.
	range.selectNodeContents(container);
	var contents = range.extractContents();
	firstElement = contents.firstChild;
	lastElement = contents.lastChild;
	// this method avoids leaving empty elements, as opposed to: range.selectNode(container); range.insertNode(contents);
	container.parentNode.replaceChild(contents, container);
	
	// Restore the selection. If the selection was collapsed, insert a new text node between the firstElement and lastElement and select that.
	if(collapsed) {
		var text = document.createTextNode("");
		text = lastElement.parentNode.insertBefore(text, lastElement);
		range.selectNodeContents(text);
		range.collapse(true);
	}
	else {
		range = document.createRange();
		range.setStartAfter(firstElement);
		range.setEndBefore(lastElement);				
	}

	return range;

}




/** Mozile Style Command Object -
 * Wraps the selection in span tags, or uses the style attribute to add CSS to a given selection. Behaves in most ways like the MozileWrapCommand it inherits from. 
 * <p>Configuration String Requirements: "mode=[inline|toggle|toggleBlock|block], style=[CSS style rules]", for example "mode=toggle, style='color: red; font-weight: bold". A tag is optional, and the default is "span". An attribute is also optional, and the default is "style".
 * @constructor
 * @param {String} configString A properly formatted configuration string.
 */
function MozileStyleCommand(configString) {

	/**
	 * @private
	 * @type String
	 */
	this._configString = String(configString);

}

MozileStyleCommand.prototype = new MozileWrapCommand();
MozileStyleCommand.prototype.constructor = MozileStyleCommand;


/**
 * Gets the style for the module. Throws an error if none exists.
 * @type String
 */
MozileStyleCommand.prototype.getStyle = function() {
	if(!this.getOption("style")) throw Error("Invalid configuration string.");
	return this.getOption("style");
}

/**
 * Gets the tag for the module. Default is "span".
 * @type String
 */
MozileStyleCommand.prototype.getTag = function() {
	if(!this.getOption("tag")) this.setOption("tag", "span");
	return this.getOption("tag");
}

/**
 * Gets the attribute for the tage. Default is "style".
 * @type String
 */
MozileStyleCommand.prototype.getAttribute = function() {
	if(!this.getOption("attribute")) this.setOption("attribute", "style");
	return this.getOption("attribute");
}



/** Mozile Style Command - Is Active -
 * Checks to see if the current node has a parent which has this.getAttribute() matching this.getStyle().
 * @type Boolean
 */
MozileStyleCommand.prototype.isActive = function() {

	var selection = window.getSelection();
	var range = selection.getRangeAt(0);
	var node = range.commonAncestorContainer;
	var flag = false;
	while(node) {
		// If the node is an element with this.getAttribute(), and the value matches [beginning or non-word character]this.getStyle(), then the command is active.
		if(node.nodeType==1 && node.getAttribute(this.getAttribute()) && node.getAttribute(this.getAttribute()).match("(^|\W)"+this.getStyle()) ) {
			flag = true;
			break;
		} 
		node = node.parentNode;
	}

	return flag;
}



/**
 * Executes the command.
 * @param {Event} event The event object which triggered the command.
 * @type Void
 */
MozileStyleCommand.prototype.execute = function(event) {
	var selection = window.getSelection();
	var range = selection.getRangeAt(0).cloneRange();
	
	var container, element, text, style, oldRange, blocks, firstElement, lastElement, i, flag;
	

	// Handle the wrap based on the mode
	switch (this.getMode()) {
		// Inline Case
		case "inline":
			// if the selection is collapsed, just add a new wrapper element with and empty text node
			if(selection.isCollapsed) {
				element = document.createElement(this.getTag());
				element.setAttribute("style", this.getStyle());
				text = document.createTextNode("");
				element.appendChild(text);
				range.insertNode(element);
				range.selectNode(text);
				range.collapse(true);
				selection.removeAllRanges();
				selection.addRange(range);
			}
			
			// if the selection is not collapsed, wrap the whole range
			else {
				element = document.createElement(this.getTag());
				element.setAttribute("style", this.getStyle());
				range = this._wrapRange(range, element)
				selection.removeAllRanges();
				selection.addRange(range);
			}
				
			return;
			break;


		// Inline Toggle Case
		case "toggle":
			if(this.isActive()) {
				
				// get the element which makes this selection active
				container = range.commonAncestorContainer;
				while(container) {
					if(container.nodeType==1 && container.getAttribute("style") && container.getAttribute("style").match(this.getStyle()) ) {
						break;
					} 
					container = container.parentNode;
				}
				
				element = document.createElement(this.getTag());
				element.setAttribute("style", this.getStyle());
				range = this._unwrapRange(container, range, element);
				selection.removeAllRanges();
				selection.addRange(range);
			
			}
			else {
		
				// if the selection is collapsed, just add a new wrapper element with and empty text node
				if(selection.isCollapsed) {
					element = document.createElement(this.getTag());
					element.setAttribute("style", this.getStyle());
					text = document.createTextNode("");
					element.appendChild(text);
					range.insertNode(element);
					range.selectNode(text);
					range.collapse(true);
					selection.removeAllRanges();
					selection.addRange(range);
				}
				
				// if the selection is not collapsed, then things get trickier
				// The idea is to get all the blocks inside the selection. The first an last are wrapped inline, while the middle blocks are wrapped as whole blocks.
				else {
					element = document.createElement(this.getTag());
					element.setAttribute("style", this.getStyle());
					range = this._wrapRange(range, element);
					selection.removeAllRanges();
					selection.addRange(range);	
				}
			}
				
			return;
			break;

		// Block toggle case
		case "toggleBlock":
			if(this.isActive()) {
				container = range.commonAncestorContainer;
				while(container) {
					if(container.nodeType==1 && container.getAttribute(this.getAttribute()) && container.getAttribute(this.getAttribute()).match(this.getStyle()) ) {
						break;
					} 
					container = container.parentNode;
				}
				element = container;
				style = element.getAttribute(this.getAttribute());
				style = style.replace(this.getStyle(),"");
				element.setAttribute(this.getAttribute(), style);
			}
			else {
				container = range.commonAncestorContainer;
				element = container.getParentBlock();
				style = element.getAttribute(this.getAttribute());
				if(style && style != "") style = style + " "+ this.getStyle();
				else style = this.getStyle();
				element.setAttribute(this.getAttribute(), style);
			}
			
			return;
			break;


		// All other cases (particularly "block")
		default:
			container = range.commonAncestorNode;
			element = container.getParentBlock();
			style = element.getAttribute(this.getAttribute());
			if(style && style != "") style = style + " "+ this.getStyle();
			else style = this.getStyle();
			element.setAttribute(this.getAttribute(), style);

			return;	
			break;
	}
	
	return;
	
}






/** Range Get TextNodes -
 * Returns an array of all the text nodes which are contained within the range.
 *
 * @return An array of text nodes.
 */
Range.prototype.getTextNodes = function() {
			
	// clean up the text nodes in commonAncestorContainer
	var container = this.commonAncestorContainer;
	container.normalize();
	
	var previousNode, startNode, finishNode;
	
	// If the start or end nodes aren't text this won't work
	if(this.startContainer.nodeType!=3) return false;
	if(this.endContainer.nodeType!=3) return false;
	
	// split text nodes at start and end, making sure the startNode is before the finishNode.
	finishNode = this.endContainer.splitText(this.endOffset);
	startNode = this.startContainer.splitText(this.startOffset);
	
	previousNode = startNode.previousSibling;
	
	//alert("Nodes "+startNode.textContent+"\n==\n"+finishNode.textContent);

	// Create the tree walker which will get all text nodes in commonAncestorContainer
	var treeWalker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null, false);
	
	// recurse until we find the startNode
	while(treeWalker.currentNode!=treeWalker.lastNode && treeWalker.currentNode != startNode) {
		treeWalker.nextNode();
	}
	
	// collect the nodes which we should wrap:
	// not white-space only, not already inside this.getTag(), not inside an "a" tag
	var textNodes = new Array();	
	
	if(!this._matchNonWS) this._matchNonWS = /\S/;
	var currentNode = treeWalker.currentNode;
	while(currentNode && currentNode != finishNode) {
		if(currentNode.parentNode &&
			currentNode.parentNode.nodeName.toLowerCase()!="a" &&
			this._matchNonWS.test(currentNode.textContent)  ) {
			textNodes.push(currentNode);
		}
		currentNode = treeWalker.nextNode();
	}

	return textNodes;

}



/** Range Get TextNodes -
 * Returns an array of all the block nodes which are contained within the range. A block can be:
 * - an element with Node.isBlock=true, but not "ul" or "ol"
 * - a text node which is inside the range but not inside any contained block
 *
 * @return An array of block nodes.
 */
Range.prototype.getBlocks = function() {

	var blockNodes = new Array();
	
	// If the range is contained in one node, return an empty array
	if(this.startContainer==this.endContainer || this.startContainer.parentNode == this.endContainer.parentNode) return blockNodes;

	var container = this.commonAncestorContainer;
	
	// Create the tree walker which will get all elements and text nodes in the container.
	var treeWalker = document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, null, false);
	
	var currentNode = treeWalker.currentNode;
	var flag = false;
	var textFlag = false;
	var parentBlock;

	// Loop over the nodes.	
	while(currentNode) {
		
		// when we hit the startContainer, set the flag=true and add the parent block to the blockNodes array.
		if(currentNode == this.startContainer) {
			flag=true;
			// If this is a text node at the top level of the container, then it counts as it's own block.
			if(currentNode.nodeType==3 && this.startContainer.parentNode==container) {
				blockNodes.push(currentNode);
			}
			// Otherwise, add its parentBlock.
			else {
				blockNodes.push(currentNode.getParentBlock());
			}
		}
		
		// If the flag is true, try to collect all the blocks.
		if(flag) {
		
			// If the currentNode is an element, not "ul" or "ol", is a block level node, and not an ancestor of the endContainer, then add it to the list. 
			if(currentNode.nodeType==1 && 
				currentNode.nodeName.toLowerCase() != "ul" && 
				currentNode.nodeName.toLowerCase() != "ol" && 
				currentNode.isBlock() && 
				!currentNode.isAncestorOf(this.endContainer) ) {
				
				blockNodes.push(currentNode);
			}


			// If the currentNode is a text node, not the start container, and non-empty, then check to see if its parentBlock is already included. If so, ignore it. If not, add currentNode as a block.
			if(!this._matchNonWS) this._matchNonWS = /\S/;
			if(currentNode.nodeType==3 && 
				currentNode!=this.startContainer &&  
				this._matchNonWS.test(currentNode.textContent) ) {
				
				textFlag = true;
				parentBlock = currentNode.getParentBlock();
				for(var i=0; i<blockNodes.length; i++) {
					if(parentBlock == blockNodes[i])	{
						textFlag = false;
						break;
					}
				}
				// If the node was not caught by that check, add it to the block list.
				if(textFlag) { 
					blockNodes.push(currentNode);
				}
			}

		}
		if(currentNode == this.endContainer) break;
		currentNode = treeWalker.nextNode();
	}

	return blockNodes;

}










/** Mozile Insert Command - Object -
 * Creates a node according to a custom function called "this.createNode()", and inserts it at the current selection. You must define createNode yourself to return whatever object will be inserted (a string, nodes, etc.).
 * <p>Configuration String Requirements: none.  
 * @constructor
 * @param {String} configString A properly formatted configuration string.
 */
function MozileInsertCommand(configString) {

	/**
	 * @private
	 * @type String
	 */
	this._configString = String(configString);

}

MozileInsertCommand.prototype = new MozileCommand();
MozileInsertCommand.prototype.constructor = MozileInsertCommand;


/**
 * Executes the command.
 * @param {Event} event The event object which triggered the command.
 * @type Void
 */
MozileInsertCommand.prototype.execute = function(event) {

	var node = this.createNode();
	if(node) {
		var selection = window.getSelection();
		var range = selection.getRangeAt(0);
		selection.removeAllRanges();
		range.deleteContents();
		range.insertNode(node);

		var IP;
		if(node.hasChildNodes()) {
			IP = node.getLastInsertionPoint();
		}
		else if(node.nextSibling) {
			IP = node.nextSibling.getFirstInsertionPoint();
		}
		if(IP && IP.getNode()) IP.select();

		return true;
	}
	else {
		mozile.debug(f,2,"Nothing to insert!");
		return false;
	}

}



/**
 * Initialize the module.
 * @type Void
 */
mozile.getModule("XHTMLBasic").init = function() {
	// Add XHTML commands.

	// Bold, Italic, Underline, Superscript, Subscript, Unformat

		// Non-semantic mode: use "b" and "i".
	if(mozile.getModule("XHTMLBasic").getOption("semantic")==false) {
		mozile.getCommandList().createCommand("MozileWrapCommand: id=Mozile-XHTMLBasic-Strong, tag=b, mode=toggle, label=Bold, tooltip='Make text bold', accelerator='Command-B', image='"+mozile.getRoot()+"images/bold.png'");
		mozile.getCommandList().createCommand("MozileWrapCommand: id=Mozile-XHTMLBasic-Emphasis, tag=i, mode=toggle, label=Italic, tooltip='Italicize text', accelerator='Command-I', image='"+mozile.getRoot()+"images/italic.png'");
	}
		// Semantic mode: use "strong" and "em" instead of "b" and "i".
	else {
		mozile.getCommandList().createCommand("MozileWrapCommand: id=Mozile-XHTMLBasic-Strong, tag=strong, mode=toggle, label=Strong, tooltip='Make text strong', accelerator='Command-B', image='"+mozile.getRoot()+"images/bold.png'");
		mozile.getCommandList().createCommand("MozileWrapCommand: id=Mozile-XHTMLBasic-Emphasis, tag=em, mode=toggle, label=Emphasis, tooltip='Emphasize text', accelerator='Command-I', image='"+mozile.getRoot()+"images/italic.png'");	
	}

	mozile.getCommandList().createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Underline, style='text-decoration: underline', mode=toggle, label=Underline, tooltip='Underline text', accelerator='Command-U', image='"+mozile.getRoot()+"images/underline.png'");
	mozile.getCommandList().createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Strikethrough, style='text-decoration: line-through', mode=toggle, label=Strikethrough, tooltip='Strikethough text',image='"+mozile.getRoot()+"images/strikethrough.png'");
	mozile.getCommandList().createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Superscript, style='vertical-align: super; font-size: 80%', mode=toggle, label=Superscript, tooltip='Raise text',image='"+mozile.getRoot()+"images/superscript.png'");
	mozile.getCommandList().createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Subscript, style='vertical-align: sub; font-size: 80%', mode=toggle, label=Subscript, tooltip='Lower text',image='"+mozile.getRoot()+"images/subscript.png'");
	// Insert a link
	var link = mozile.getCommandList().createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-Link, label='Link', tooltip='Insert a hyperlink', image='"+mozile.getRoot()+"images/link.png' ");
	link.isAvailable = function() {
		return !unlink.isAvailable();
	}
	link.createNode = function() {
		var node;
		
		if(mozile.getOption("replaceAnchors")) node = document.createElement("mozileAnchorReplacement");
		else node = document.createElement("a");
		
		var href = prompt("What is the URL for the hyperlink?");
		if(href) {
			node.setAttribute("href", href);
			node.appendChild(window.getSelection().getRangeAt(0).extractContents());
			return node;
		} 
		else {
			return false;
		}
	}
	var unlink = mozile.getCommandList().createCommand("MozileUnformatCommand: id=Mozile-XHTMLBasic-Unformat, label=Unformat, tooltip='Remove formatting from selection', tag='a', image='"+mozile.getRoot()+"images/unlink.png'");

	
	// Font Menu
	var fontList = mozile.getCommandList().createCommand("MozileCommandList: id=Mozile-XHTMLBasic-FontList, label=Font, image='"+mozile.getRoot()+"images/fonts.png'");
	fontList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Serif, style='font-family: serif', mode=toggle, label=Serif, tooltip='Use serif font'");
	fontList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Sans-Serif, style='font-family: sans-serif', mode=toggle, label=Sans-Serif, tooltip='Use sans-serif font'");
	fontList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Monospace, style='font-family: monospace', mode=toggle, label=Monospace, tooltip='Use monospace font'");
	
	
	// Size Menu
	var sizeList = mozile.getCommandList().createCommand("MozileCommandList: id=Mozile-XHTMLBasic-SizeList, label=Size, image='"+mozile.getRoot()+"images/size.png'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Smaller, style='font-size: smaller', mode=inline, label=Smaller, tooltip='Make text smaller than surrounding text', accelerator='Command--'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-XX-Small, style='font-size: xx-small', mode=toggle, label=XX-Small, tooltip='Make text extremely small'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-X-Small, style='font-size: x-small', mode=toggle, label=X-Small, tooltip='Make text very small'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Small, style='font-size: small', mode=toggle, label=Small, tooltip='Make text small'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Medium, style='font-size: medium', mode=toggle, label=Medium, tooltip='Make text medium sized'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Large, style='font-size: large', mode=toggle, label=Large, tooltip='Make text large'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-X-Large, style='font-size: x-large', mode=toggle, label=X-Large, tooltip='Make text very large'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-XX-Large, style='font-size: xx-large', mode=toggle, label=XX-Large, tooltip='Make text extremely large'");
	sizeList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Larger, style='font-size: larger', mode=inline, label=Larger, tooltip='Make text larger than surrounding text', accelerator='Command-+'");


	// Format Menu
	var formatList = mozile.getCommandList().createCommand("MozileCommandList: id=Mozile-XHTMLBasic-FormatList, label='Format', image='"+mozile.getRoot()+"images/format.png'");
	formatList.activeElements = new Array("p", "h1", "h2", "h3", "h4", "h5", "h6");
	formatList.isAvailable = function() {
		var focusNode = window.getSelection().focusNode;
		if(!focusNode) return false;
		var block = focusNode.getParentBlock();
		if(!block) return false;
		var blockName = block.localName.toLowerCase();
		for(var i=0; i < this.activeElements.length; i++) {
			if(blockName == this.activeElements[i]) return true;
		}
		return false;
	}
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading1, tag=h1, label='Heading1', tooltip='Level 1 Heading', accesskey=1");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading2, tag=h2, label='Heading2', tooltip='Level 2 Heading', accesskey=2");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading3, tag=h3, label='Heading3', tooltip='Level 3 Heading', accesskey=3");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading4, tag=h4, label='Heading4', tooltip='Level 4 Heading', accesskey=4");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading5, tag=h5, label='Heading5', tooltip='Level 5 Heading', accesskey=5");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Heading6, tag=h6, label='Heading6', tooltip='Level 6 Heading', accesskey=6");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-Paragraph, tag=p, label='Paragraph', tooltip='Paragraph', accesskey=P");
	formatList.createCommand("MozileBlockSetCommand: id=Mozile-XHTMLBasic-ListItem, tag=li, label='List Item', tooltip='List Item', accesskey=L");

	
	// Justify Menu
	var justifyList = mozile.getCommandList().createCommand("MozileCommandList: id=Mozile-XHTMLBasic-JustifyList, label=Justify, image='"+mozile.getRoot()+"images/justify-left.png'");
	justifyList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Justify-Left, style='text-align: left', mode=toggleBlock, label=Left, tooltip='Justify text left'");
	justifyList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Justify-Right, style='text-align: right', mode=toggleBlock, label=Right, tooltip='Justify text right'");
	justifyList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Justify-Conter, style='text-align: center', mode=toggleBlock, label=Center, tooltip='Center text'");
	justifyList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Justify-Full, style='text-align: justify', mode=toggleBlock, label=Full, tooltip='Justify left and right'");


	// Text Colors
	// red, blue, green, yellow, black, white, purple, orange, gray
	var textColorList = mozile.getCommandList().createCommand("MozileCommandList: id=Mozile-XHTMLBasic-TextColorList, label='Text Color', image='"+mozile.getRoot()+"images/text-color.png'");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Red, style='color: red', mode=toggle, label=Red, tooltip='Make text red' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Blue, style='color: blue', mode=toggle, label=Blue, tooltip='Make text blue' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Green, style='color: green', mode=toggle, label=Green, tooltip='Make text green' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Yellow, style='color: yellow', mode=toggle, label=Yellow, tooltip='Make text yellow' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Orange, style='color: orange', mode=toggle, label=Orange, tooltip='Make text orange' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Purple, style='color: purple', mode=toggle, label=Purple, tooltip='Make text purple' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-White, style='color: white', mode=toggle, label=White, tooltip='Make text white' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Gray, style='color: gray', mode=toggle, label=Gray, tooltip='Make text gray' ");
	textColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-Black, style='color: black', mode=toggle, label=Black, tooltip='Make text black' ");

	
	// Background Colors
	// red, blue, green, yellow, black, white, purple, orange, gray
	var backgroundColorList = mozile.getCommandList().createCommand("MozileCommandList: id=Mozile-XHTMLBasic-BackgroundColorList, label='Background Color', image='"+mozile.getRoot()+"images/background-color.png'");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGRed, style='background-color: red', mode=toggle, label=Red, tooltip='Make background red' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGBlue, style='background-color: blue', mode=toggle, label=Blue, tooltip='Make background blue' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGGreen, style='background-color: green', mode=toggle, label=Green, tooltip='Make background green' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGYellow, style='background-color: yellow', mode=toggle, label=Yellow, tooltip='Make background yellow' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGOrange, style='background-color: orange', mode=toggle, label=Orange, tooltip='Make background orange' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGPurple, style='cobackground-colorlor: purple', mode=toggle, label=Purple, tooltip='Make background purple' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGWhite, style='background-color: white', mode=toggle, label=White, tooltip='Make background white' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGGray, style='background-color: gray', mode=toggle, label=Gray, tooltip='Make background gray' ");
	backgroundColorList.createCommand("MozileStyleCommand: id=Mozile-XHTMLBasic-BGBlack, style='background-color: black', mode=toggle, label=Black, tooltip='Make background black' ");
	
	
	// Object Insertion
	var objectList = mozile.getCommandList().createCommand("MozileCommandList: id=Mozile-XHTMLBasic-ObjectList, label='Insert Objects', image='"+mozile.getRoot()+"images/image.png'");
	// Insert a horizontal rule
	var hr = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-HorizontalRule, label='Horizontal Rule', tooltip='Insert a horizontal rule' ");
	hr.createNode = function() {
		return document.createElement("hr");
	}
	// Insert an image
	var image = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-Image, label='Image', tooltip='Insert an image' ");
	image.createNode = function() {
		var node = document.createElement("img");
		var src = prompt("What is the URL for the image?");
		if(src) {
			node.setAttribute("src", src);
			return node;
		} 
		else {
			return false;
		}
	}
	// Insert an unordered list
	var ul = objectList.createCommand("MozileCommand: id=Mozile-XHTMLBasic-UnorderedList, label='Unordered List', tooltip='Insert an unordered list' ");
	ul.execute = function(event) {
		var ul = document.createElement("ul");
		var li = document.createElement("li");
		var textNode = document.createTextNode("");
		li.appendChild(textNode);
		ul.appendChild(li);
		var selection = window.getSelection();
		var range = selection.getRangeAt(0).cloneRange();
		range.insertNode(ul);
		range.selectNode(textNode);
		range.collapse(true);
		selection.removeAllRanges();
		selection.addRange(range);
		return true;
	}
	// Insert an ordered list
	var ol = objectList.createCommand("MozileCommand: id=Mozile-XHTMLBasic-OrderedList, label='Ordered List', tooltip='Insert an ordered list' ");
	ol.execute = function(event) {
		var ol = document.createElement("ol");
		var li = document.createElement("li");
		var textNode = document.createTextNode("");
		li.appendChild(textNode);
		ol.appendChild(li);
		var selection = window.getSelection();
		var range = selection.getRangeAt(0).cloneRange();
		range.insertNode(ol);
		range.selectNode(textNode);
		range.collapse(true);
		selection.removeAllRanges();
		selection.addRange(range);
		return true;
	}
	// Change the document title.
	var title = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-Title, label='Change Title', tooltip='Change the document title', undoable=false ");
	title.execute = function(event) {
		var title = document.getElementsByTagName("title")[0];
		var value = prompt("What should the title be?", document.title);
		if(title && value) {
			while(title.childNodes.length) {
				title.removeChild(title.firstChild);
			}
			title.appendChild(document.createTextNode(value));
			document.title = value;
			return true;
		} 
		else {
			return false;
		}
	}
	// Change the style attribute for this element.
	var style = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-Style, label='Change Style', tooltip='Change the element style', accelerator='Command-Alt-S' ");
	style.execute = function(event) {
		var selection = window.getSelection();
		var range = selection.getRangeAt(0);
		var container = range.commonAncestorContainer;
		if(container.nodeType==3) container = container.parentNode;
		var value = prompt("What should the style for the "+ container.nodeName +" element be?", container.getAttribute("style"));
		if(value==null) {
			return false;
		} 
		else {
			container.setAttribute("style", value);
			return true;
		}
	}
	// Change some attribute for this element.
	var attribute = objectList.createCommand("MozileInsertCommand: id=Mozile-XHTMLBasic-Attribute, label='Change Attribute', tooltip='Change an attribute of the element', accelerator='Command-Shift-A' ");
	attribute.execute = function(event) {
		var selection = window.getSelection();
		var range = selection.getRangeAt(0);
		var container = range.commonAncestorContainer;
		if(container.nodeType==3) container = container.parentNode;
		var attribute = prompt("What attribute of the "+container.nodeName+" element should be changed be?", "");
		if(attribute==null) return false;
		var value = prompt("What should the value of the "+attribute+" attribute be?", container.getAttribute(attribute));
		if(value==null) return false;
		container.setAttribute(attribute, value);
		return true;
	}
	
}



Documentation generated by JSDoc on Wed Nov 1 15:11:15 2006

The mozile project can be contacted through the mailing list or the member list.
Copyright © 2000-2019. All rights reserved. Terms of Use & Privacy Policy.