rich.js

Summary

Tools for rich editing operations on mark-up.

Version: 0.8 $Id: overview-summary-rich.js.html,v 1.10 2008/02/20 18:47:09 jameso Exp $

Author: James A. Overton


Class Summary
mozile.edit.Insert  
mozile.edit.Replace  
mozile.edit.SetAttribute  
mozile.edit.Split  
mozile.edit.Style  
mozile.edit.Unwrap  
mozile.edit.Wrap  

/* ***** BEGIN LICENSE BLOCK *****
 * Licensed under Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * Full Terms at http://mozile.mozdev.org/0.8/LICENSE
 *
 * 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 Tools for rich editing operations on mark-up.
 * @link http://mozile.mozdev.org 
 * @author James A. Overton <james@overton.ca>
 * @version 0.8
 * $Id: overview-summary-rich.js.html,v 1.10 2008/02/20 18:47:09 jameso Exp $
 */

mozile.require("mozile.edit");
mozile.provide("mozile.edit.*");

/**
 * Indicates the rich editing commands have been defined.
 * @type Boolean
 */
mozile.edit.rich = true;


/**
 * Removes a node from the document.
 * @type mozile.edit.Command
 */
mozile.edit.insertNode = new mozile.edit.Command("insertNode");

/**
 * Always false. This command is never triggered, but called directly by other commands.
 * @param {Event} event Optional. The event object to be tested.
 * @param {Node} parentNode Optional. If the node is to be inserted at the beginning of a parent node, provide the node here.
 * @param {Node} previousSibling Optional. If the node is to be inserted after a previous node, provide the node here.
 * @param {Node} content Optional. The node to be inserted.
 * @type Boolean
 */
mozile.edit.insertNode.test = function(event, parentNode, previousSibling, content) {
	if(event) {
		return false;
	}
	
	if(!parentNode && !previousSibling) return false;
	if(!content) return false;
	
	return true;
}

/**
 * Prepares a state object for the insert node command.
 * @param {Event} event Optional. The event object to be converted into a state.
 * @param {Node} parentNode Optional. If the node is to be inserted at the beginning of a parent node, provide the node here.
 * @param {Node} previousSibling Optional. If the node is to be inserted after a previous node, provide the node here.
 * @param {Node} content Optional. The node to be inserted.
 * @type mozile.edit.State
 */
mozile.edit.insertNode.prepare = function(event, parentNode, previousSibling, content) {
	var state = new mozile.edit.State(this, false); // don't need the selection
	state.location = {parentNode: null, previousSibling: null};
	
	state.location.parentNode = state.storeNode(parentNode);
	state.location.previousSibling = state.storeNode(previousSibling);
	
	state.content = null;
	if(content) state.content = content;
	
	return state;
}

/**
 * Inserts a node at the given location.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.insertNode.execute = function(state, fresh) {
	if(!state.content) throw("Error [mozile.edit.insertNode.execute]: No content provided.");
	var location = {previousSibling: null, parentNode: null};

	// Get "before" nodes
	if(state.location.previousSibling) {
		location.previousSibling = mozile.xpath.getNode(state.location.previousSibling);
	}
	else if(state.location.parentNode) {
		location.parentNode = mozile.xpath.getNode(state.location.parentNode);
	}
	else throw("Error [mozile.edit.insertNode.execute]: No previous sibling or parentNode provided.");

	// Insert the node.
	if(location.previousSibling) mozile.dom.insertAfter(state.content, location.previousSibling);
	else if(location.parentNode) mozile.dom.prependChild(state.content, location.parentNode);

	state.executed = true;
	return state;
}

/**
 * Removes an inserted node.
 * @param {mozile.edit.State} state The state information needed to unexecute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.insertNode.unexecute = function(state, fresh) {
	if(state.content.parentNode) {
		state.content.parentNode.removeChild(state.content);
	}
	else mozile.debug.debug("mozile.edit.insertNode.unexecute", "No parent for state.content "+ state.content);

	state.executed = false;
	return state;
}





/**
 * Removes a node from the document.
 * @type mozile.edit.Command
 */
mozile.edit.removeNode = new mozile.edit.Command("removeNode");

/**
 * Always false. This command is never triggered, but called directly by other commands.
 * @param {Event} event Optional. The event object to be tested.
 * @param {String} content Optional. The content removed.
 * @type Boolean
 */
mozile.edit.removeNode.test = function(event, content) {
	if(event) {
		return false;
	}
	
	if(!content) return false;
	if(!content.parentNode) return false;
	
	return true;
}

/**
 * Prepares a state object for the remove node command.
 * @param {Event} event The event object to be converted into a state.
 * @param {String} content Optional. The content removed.
 * @type mozile.edit.State
 */
mozile.edit.removeNode.prepare = function(event, content) {
	var state = new mozile.edit.State(this, false); // don't need the selection
	
	state.content = null;
	if(content) state.content = content;
	else if(event) state.content = mozile.edit._getNode(event);
	
	return state;
}

/**
 * Removes the node at the current selection. Stores the removed node so that the operation can be undone. Does not manipulate the selection.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.removeNode.execute = function(state, fresh) {
	var target = state.content;
	//alert([target, target.previousSibling, target.parentNode, target.data].join("\n"));
	var parentNode = target.parentNode;
	if(!parentNode) throw("Error [mozile.edit.removeNode.execute]: No parent node for node '"+ target +"'.");

	var previousSibling = target.previousSibling;
	if(previousSibling && !state.previousSibling)
		state.previousSibling = mozile.xpath.getXPath(previousSibling);
	else if(!state.parentNode)
		state.parentNode = mozile.xpath.getXPath(parentNode);

	parentNode.removeChild(target);

	state.executed = true;
	return state;
}

/**
 * Restores a removed node.
 * @param {mozile.edit.State} state The state information needed to unexecute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.removeNode.unexecute = function(state, fresh) {
	if(state.previousSibling) {
		var previousSibling = mozile.xpath.getNode(state.previousSibling);
		if(!previousSibling) throw("Error [mozile.edit.removeNode.unexecute]: Could not find previousSibling '"+ state.previousSibling +"'.");
		mozile.dom.insertAfter(state.content, previousSibling);
	}

	else if(state.parentNode) {
		var parentNode = mozile.xpath.getNode(state.parentNode);
		mozile.dom.prependChild(state.content, parentNode);
	}

	else mozile.debug.inform("mozile.edit.removeNode.unexecute", "No parent or previousSibling.");

	state.executed = false;
	return state;
}


/**
 * Removes text and nodes from a range.
 * @type mozile.edit.Command
 */
mozile.edit.remove = new mozile.edit.Command("remove");

/**
 * True if the event was a keypress of the delete key.
 * @param {Event} event The event object to be tested.
 * @param {Integer} direction Optional. The direction for the removal to use. Defaults to previous.
 * @param {String} content Optional. The content removed.
 * @param {Boolean} preserve Optional. When true the container will not be removed, event if it's empty.
 * @type Boolean
 */
mozile.edit.remove.test = function(event, direction, content, preserve) {
	if(event) {
		if(!mozile.edit.checkAccelerators(event, ["Backspace", "Delete"]))
			return false;	
	}
	
	var selection;
	if(event && event.selection) selection = event.selection;
	else selection = mozile.dom.selection.get();
	
	if(selection.isCollapsed) {
		var dir = mozile.edit.PREVIOUS;
		if(direction == mozile.edit.NEXT) dir = direction;
		var IP = selection.getInsertionPoint(true);
		if(!IP) return false;
		var node = IP.getNode();
		if(!IP.seek(dir)) return false;
		// Make sure the new IP is editable.
		if(node != IP.getNode() &&
			!mozile.edit.getContainer(IP.getNode())) return false;
	}
	
	return true;
}

/**
 * Prepares a state object for the remove command.
 * @param {Event} event The event object to be converted into a state.
 * @param {Integer} direction Optional. The direction for the removal to use. Defaults to previous.
 * @param {String} content Optional. The content removed.
 * @param {Boolean} preserve Optional. When true the container will not be removed, event if it's empty.
 * @type mozile.edit.State
 */
mozile.edit.remove.prepare = function(event, direction, content, preserve) {
	var state = new mozile.edit.State(this);

	state.direction = mozile.edit.PREVIOUS;
	if(direction) state.direction = direction;
	else if(event && mozile.edit.convertKeyCode(event.keyCode) == "Delete")
		state.direction = mozile.edit.NEXT;

	state.content = " ";
	if(content) state.content = content;

	state.preserve = false;
	if(preserve === true) state.preserve = true;

	return state;
}

/**
 * Removes both text and ranges.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.remove.execute = function(state, fresh) {
	//alert("Remove");
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	state.actions = new Array();
	var node, newState, IP;

	// Collapsed case.
	if(selection.isCollapsed) {
		node = selection.focusNode;
		IP = selection.getInsertionPoint(true);
		if(IP) IP.seek(state.direction);
		if(node.nodeType == mozile.dom.TEXT_NODE && IP && IP.getNode() == node) 
			mozile.edit.removeText.request(state, fresh, state.direction);
		else if(IP) IP.extend();
	}
	
	// Non-collapsed case.
	if(!selection.isCollapsed) {
		node = selection.getRangeAt(0).commonAncestorContainer;
		if(node.nodeType == mozile.dom.TEXT_NODE)
			mozile.edit.removeText.request(state, fresh, state.direction);
		else mozile.edit._removeRange(state, fresh, state.direction);
	}

	// Remove the empty container, or ensure it's non-empty.
	if(state.preserve) {
		mozile.edit._ensureNonEmpty(state, fresh, selection.focusNode);
	}
	else {
		mozile.edit._removeEmpty(state, fresh, selection.focusNode,
			mozile.edit.getParentBlock(selection.focusNode));
	}

	state.selection.after = selection.store();
	state.executed = true;
	return state;
}





/**
 * Moves a node from one location in the document to another.
 * @type mozile.edit.Command
 */
mozile.edit.moveNode = new mozile.edit.Command("moveNode");

/**
 * Always false. This command is always called instead of triggered.
 * @param {Event} event Optional. The event object to be tested.
 * @param {Node} destinationParentNode Optional. The new parentNode for the target.
 * @param {Node} destinationPreviousSibling Optional. The new previousSibling for the target.
 * @param {Node} target Optional. The node to move.
 * @type Boolean
 */
mozile.edit.moveNode.test = function(event, destinationParentNode, destinationPreviousSibling, target) {
	if(event) {
		return false;
	}
	
	if(!destinationParentNode && !destinationPreviousSibling) return false;
	if(!target) return false;

	return true;
}

/**
 * Prepares a state object for the move node command.
 * @param {Event} event Optional. The event object to be converted into a state.
 * @param {Node} destinationParentNode Optional. The new parentNode for the target.
 * @param {Node} destinationPreviousSibling Optional. The new previousSibling for the target.
 * @param {Node} target Optional. The node to move.
 * @type mozile.edit.State
 */
mozile.edit.moveNode.prepare = function(event, destinationParentNode, destinationPreviousSibling, target) {
	var state = new mozile.edit.State(this);
	state.destination = {parentNode: null, previousSibling: null};
	
	state.destination.parentNode = state.storeNode(destinationParentNode);
	state.destination.previousSibling = state.storeNode(destinationPreviousSibling);
	state.target = state.storeNode(target);
	
	return state;
}

/**
 * Removes a node from one location and inserts it at another. XPath locations are stored so that the node can be moved back. (The node itself is not stored.)
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.moveNode.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	
	// Get some selection data.
	var anchorNode = selection.anchorNode;
	var anchorOffset = selection.anchorOffset;
	var focusNode = selection.focusNode;
	var focusOffset = selection.focusOffset;
	
	var target = mozile.xpath.getNode(state.target);
	if(!target) throw("Error: mozile.edit.moveNode.execute No target node.");
	var parentNode = mozile.xpath.getNode(state.destination.parentNode);
	var previousSibling = mozile.xpath.getNode(state.destination.previousSibling);

	// Move the node
	mozile.edit.removeNode.request(state, fresh, target);
	mozile.edit.insertNode.request(state, fresh, parentNode, previousSibling, target);

	// Restore the selection.
	selection.collapse(anchorNode, anchorOffset);
	if(focusNode != anchorNode || focusOffset != anchorOffset) {
		selection.extend(focusNode, focusOffset);
	}

	state.selection.after = selection.store();
	state.executed = true;
	return state;
}



/**
 * Move the node before its previous sibling.
 * @type mozile.edit.Command
 */
mozile.edit.moveNodePrevious = new mozile.edit.Command("moveNodePrevious");
mozile.edit.moveNodePrevious.accel = "Command-Up";
mozile.edit.moveNodePrevious.tooltip = "Move the element up";
mozile.edit.moveNodePrevious.image = "silk/arrow_up";
mozile.edit.moveNodePrevious.target = "block";
mozile.edit.moveNodePrevious.direction = "ancestor";
mozile.edit.commands.addCommand(mozile.edit.moveNodePrevious);


/**
 * Indicates that the command is currently available.
 * In the case of an "unlink" command it would me that the cursor is inside a "link" element, and the command's button should indicate that fact. If not, the command's button should be "disabled" (whatever that might mean for a particular GUI).
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.moveNodePrevious.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(target && mozile.dom.getPreviousSiblingElement(target)) return true;
	return false;
}

/**
 * True if there is a target and a previous sibling.
 * @param {Event} event The event object to be tested.
 * @type Boolean
 */
mozile.edit.moveNodePrevious.test = function(event, target) {
	if(event) {
		if(!mozile.edit.checkAccelerator(event, this.accel)) return false;
	}
	
	if(!target) target = mozile.edit._getTarget(event, this.target, this.direction);
	if(!target) return false;
	if(!mozile.dom.getPreviousSiblingElement(target)) return false;

	return true;
}

/**
 * True if there is a target and a previous sibling.
 * @param {Event} event The event object to be tested.
 * @type Boolean
 */
mozile.edit.moveNodePrevious.prepare = function(event, target) {
	var state = new mozile.edit.State(this);
	
	if(!target) target = mozile.edit._getTarget(event, this.target, this.direction);
	state.targetNode = target;
	state.target = state.storeNode(target);

	return state;
}

/**
 * Moves the element so it is before its previous sibling.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.moveNodePrevious.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	state.actions = new Array();
	
	var target = mozile.xpath.getNode(state.target);
	if(!target) throw("Error: mozile.edit.moveNodePrevious.execute No target node.");
	var parent = null;
	var previous = mozile.dom.getPreviousSiblingElement(target);
	if(!previous) throw("Error: mozile.edit.moveNodePrevious.execute No previous node.");
	previous = mozile.dom.getPreviousSiblingElement(previous);
	if(!previous) parent = target.parentNode;

	mozile.edit.moveNode.request(state, fresh, parent, previous, target);

	state.selection.after = selection.store();
	state.executed = true;
	return state;
}


/**
 * Move the node before its next sibling.
 * @type mozile.edit.Command
 */
mozile.edit.moveNodeNext = new mozile.edit.Command("moveNodeNext");
mozile.edit.moveNodeNext.accel = "Command-Down";
mozile.edit.moveNodeNext.tooltip = "Move the element down";
mozile.edit.moveNodeNext.image = "silk/arrow_down";
mozile.edit.moveNodeNext.target = "block";
mozile.edit.moveNodeNext.direction = "ancestor";
mozile.edit.commands.addCommand(mozile.edit.moveNodeNext);


/**
 * Indicates that the command is currently available.
 * In the case of an "unlink" command it would me that the cursor is inside a "link" element, and the command's button should indicate that fact. If not, the command's button should be "disabled" (whatever that might mean for a particular GUI).
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.moveNodeNext.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(target && mozile.dom.getNextSiblingElement(target)) return true;
	return false;
}

/**
 * True if there is a target and a next sibling.
 * @param {Event} event The event object to be tested.
 * @type Boolean
 */
mozile.edit.moveNodeNext.test = function(event, target) {
	if(event) {
		if(!mozile.edit.checkAccelerator(event, this.accel)) return false;
	}
	
	if(!target) target = mozile.edit._getTarget(event, this.target, this.direction);
	if(!target) return false;
	if(!mozile.dom.getNextSiblingElement(target)) return false;

	return true;
}

/**
 * Prepares to move the node past its next sibling.
 * @param {Event} event The event object to be tested.
 * @type Boolean
 */
mozile.edit.moveNodeNext.prepare = function(event, target) {
	var state = new mozile.edit.State(this);
	
	if(!target) target = mozile.edit._getTarget(event, this.target, this.direction);
	state.targetNode = target;
	state.target = state.storeNode(target);

	return state;
}

/**
 * Moves the element so it is after its next sibling.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.moveNodeNext.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	state.actions = new Array();
	
	var target = mozile.xpath.getNode(state.target);
	if(!target) throw("Error: mozile.edit.moveNodeNext.execute No target node.");
	var next = mozile.dom.getNextSiblingElement(target);
	if(!next) throw("Error: mozile.edit.moveNodeNext.execute No next node.");
	
	var result = mozile.edit.moveNode.request(state, fresh, null, next, target);

	state.selection.after = selection.store();
	state.executed = true;
	return state;
}




/**
 * Move the node before its next sibling.
 * @type mozile.edit.Command
 */
mozile.edit.indentList = new mozile.edit.Command("indentList");
mozile.edit.indentList.accel = "Command-Right";
mozile.edit.indentList.tooltip = "Create a sub-list";
//mozile.edit.indentList.image = "silk/arrow_right";
mozile.edit.indentList.target = "localname li";
mozile.edit.indentList.direction = "ancestor";


/**
 * Indicates that the command is currently available.
 * In the case of an "unlink" command it would me that the cursor is inside a "link" element, and the command's button should indicate that fact. If not, the command's button should be "disabled" (whatever that might mean for a particular GUI).
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.indentList.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(target) return true;
	return false;
}

/**
 * True if there is a target and a next sibling.
 * @param {Event} event The event object to be tested.
 * @type Boolean
 */
mozile.edit.indentList.test = function(event, target) {
	if(event) {
		if(!mozile.edit.checkAccelerator(event, this.accel)) return false;
	}
	
	if(!target) target = mozile.edit._getTarget(event, this.target, this.direction);
	if(!target) return false;

	return true;
}

/**
 * Prepares to move the node past its next sibling.
 * @param {Event} event The event object to be tested.
 * @type Boolean
 */
mozile.edit.indentList.prepare = function(event, target) {
	var state = new mozile.edit.State(this);
	
	if(!target) target = mozile.edit._getTarget(event, this.target, this.direction);
	state.targetNode = target;
	state.target = state.storeNode(target);

	return state;
}

/**
 * Moves the element so it is after its next sibling.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.indentList.execute = function(state, fresh) {
	state.actions = new Array();
	
	var target = mozile.xpath.getNode(state.target);
	if(!target) throw("Error: mozile.edit.moveNodeNext.execute No target node.");
	var list = mozile.dom.createElement("ul");
	
	mozile.edit.insertNode.request(state, fresh, null, target, list);
	mozile.edit.moveNode.request(state, fresh, list, null, target);

	state.executed = true;
	return state;
}




/**
 * Merges two nodes, appending the children of the second node to the first node.
 * @type mozile.edit.Command
 */
mozile.edit.mergeNodes = new mozile.edit.Command("mergeNodes");

/**
 * Always false. This command is always called instead of triggered.
 * @param {Event} event Optional. The event object to be tested.
 * @param from Optional. A Node or an XPath for the node to be merged into the "to" node.
 * @param to Optional. A Node or an XPath for the node which will gain the content of the "from" node.
 * @type Boolean
 */
mozile.edit.mergeNodes.test = function(event, from, to) {
	if(event) {
		return false;
	}
	
	if(!from) return false;
	if(!to) return false;
	
	return true;
}

/**
 * Prepares a state object for the merge nodes command.
 * @param {Event} event Optional. The event object to be converted into a state.
 * @param from Optional. A Node or an XPath for the node to be merged into the "to" node.
 * @param to Optional. A Node or an XPath for the node which will gain the content of the "from" node.
 * @type mozile.edit.State
 */
mozile.edit.mergeNodes.prepare = function(event, from, to) {
	var state = new mozile.edit.State(this);

	state.from = state.storeNode(from);
	state.to = state.storeNode(to);
	
	return state;
}

/**
 * Merges two nodes.
 * <p>In the case of text, nodes are merged using the DOM Text.appendData() method.
 * In the case of elements, child nodes are moved to the "to" node using moveNode, and the "from" element is removed using removeNode.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.mergeNodes.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	state.actions = new Array();

	// Get target nodes
	var fromNode = mozile.xpath.getNode(state.from);
	var toNode = mozile.xpath.getNode(state.to);
	
	// Get some selection data.
	var anchorNode = selection.anchorNode;
	var anchorOffset = selection.anchorOffset;
	var focusNode = selection.focusNode;
	var focusOffset = selection.focusOffset;
	
	mozile.edit._removeEmptyTokens(state, fresh, toNode);
	mozile.edit._removeEmptyTokens(state, fresh, fromNode);
	var firstNode, secondNode;

	// Normalize adjacent text nodes
	if(fromNode.nodeType == mozile.dom.TEXT_NODE &&
		toNode.nodeType == mozile.dom.TEXT_NODE) {
		if(fromNode.nextSibling == toNode) {
			firstNode = fromNode;
			secondNode = toNode;
		}
		else if(toNode.nextSibling == fromNode) {
			firstNode = toNode;
			secondNode = fromNode;
		}
		if(firstNode && secondNode) {
			// Fix selection.
			var offset = firstNode.data.length;
			if(anchorNode == secondNode) {
				anchorNode = firstNode;
				anchorOffset += offset;
				//alert("Anchor "+ mozile.xpath.getXPath(anchorNode) +" "+ anchorOffset);
			}
			if(focusNode == secondNode) {
				focusNode = firstNode;
				focusOffset += offset;
				//alert("Focus "+ mozile.xpath.getXPath(focusNode) +" "+ focusOffset);
			}
			
			// Merge nodes.
			mozile.edit.insertText.request(state, fresh,
				null, firstNode.data + secondNode.data, firstNode);
			mozile.edit.removeNode.request(state, fresh, secondNode);

			// Restore the selection.
			selection.collapse(anchorNode, anchorOffset);
			if(focusNode != anchorNode || focusOffset != anchorOffset) {
			  selection.extend(focusNode, focusOffset);
			}

			state.selection.after = selection.store();
			state.executed = true;
			return state;
		}
		else throw("Error [mozile.edit.mergeNodes.execute]: Cannot merge text non-adjacent nodes: "+ state.from +" "+ state.to);
	}

	// Move all children from fromNode to the end of toNode.
	firstNode = toNode.lastChild;
	secondNode = fromNode.firstChild
	while(fromNode.firstChild) {
		mozile.edit.moveNode.request(state, fresh, toNode, toNode.lastChild, fromNode.firstChild);
	}

	// Remove fromNode
	mozile.edit.removeNode.request(state, fresh, fromNode);
	
	// Restore the selection.
	var IP;
	// Collapse to the anchor.
	if(mozile.dom.isAncestorOf(mozile.document.documentElement, anchorNode)) {
		selection.collapse(anchorNode, anchorOffset);
	}
	else {
		IP = mozile.edit.getInsertionPoint(toNode, mozile.edit.NEXT);
		if(IP) IP.select();
	}
	// Extend to the focus.
	if(mozile.dom.isAncestorOf(mozile.document.documentElement, focusNode)) {
		if(focusNode != selection.anchorNode || 
			focusOffset != selection.anchorOffset) {
			selection.extend(focusNode, focusOffset);
		}
	}
	else {
		IP = mozile.edit.getInsertionPoint(toNode, mozile.edit.PREVIOUS);
		if(IP) IP.extend();
	}
	
	// Normalize last node of toNode with first node of fromNode
	if(firstNode && firstNode.nodeType == mozile.dom.TEXT_NODE &&
	  secondNode && secondNode.nodeType == mozile.dom.TEXT_NODE) {
	  this.request(state, fresh, firstNode, secondNode);
	}

	state.selection.after = selection.store();
	state.executed = true;
	return state;
}




/**
 * Splits a node by cloning it, inserting the clone after the original, and moving all the original's children after the cursor into the clone.
 * @type mozile.edit.Command
 */
mozile.edit.splitNode = new mozile.edit.Command("splitNode");

/**
 * Always false. This command is never triggered, but called directly by other commands instead.
 * @param {Event} event The event object to be tested.
 * @param {Node} target Optional. The first node in the new split element.
 * @param {Node} offset Optional. An integer offset within a text node where the text node will be split.
 * @param {Boolean} after Optional. When true the node will be split after the target rather than before it.
 * @type Boolean
 */
mozile.edit.splitNode.test = function(event, target, offset, after) {
	if(event) {
		return false;
	}
	if(!target) return false;
	if(!target.nodeType) return false;
	if(target.nodeType != mozile.dom.TEXT_NODE &&
		target.nodeType != mozile.dom.ELEMENT_NODE) 
		return false;
	
	return true;
}

/**
 * Prepares a state object for the split nodes command.
 * @param {Event} event Optional. The event object to be converted into a state.
 * @param {Node} target Optional. The first node in the new split element.
 * @param {Node} offset Optional. An integer offset within a text node where the text node will be split.
 * @param {Boolean} after Optional. When true the node will be split after the target rather than before it.
 * @type mozile.edit.State
 */
mozile.edit.splitNode.prepare = function(event, target, offset, after) {
	var state = new mozile.edit.State(this);

	target = state.storeNode(target);
	state.target = target;
	
	state.offset = null;
	if(!isNaN(parseInt(offset))) state.offset = offset;

	state.after = false;
	if(after === true) state.after = true;

	return state;
}

/**
 * Splits a node in two. 
 * <ul>
 *  <li>If the focusNode is a text node, it is split at the focusOffset,
 *  <li>A clone of the focusNode or focusNode's parent is created,
 *  <li>The clone is inserted after the focusNode
 *  <li>All children after the focusOffset are moved into the clone.
 * </ul>
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.splitNode.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	state.actions = new Array();

	var target = mozile.xpath.getNode(state.target);
	if(target.nodeType == mozile.dom.ELEMENT_NODE && state.offset &&
		target.childNodes[state.offset]) {
		target = target.childNodes[state.offset];
	}
	var oldContainer, newContainer;

	// Get some selection data.
	var anchorNode = selection.anchorNode;
	var anchorOffset = selection.anchorOffset;
	var focusNode = selection.focusNode;
	var focusOffset = selection.focusOffset;
	
	//alert("Anchor "+ mozile.xpath.getXPath(anchorNode) +" "+ anchorOffset +"\n"+
	//	"Focus "+ mozile.xpath.getXPath(focusNode) +" "+ focusOffset);
		
	// Split a text node.
	if(target.nodeType == mozile.dom.TEXT_NODE && state.offset != undefined) {
		state.splitNode = target;
		oldContainer = target;
		newContainer = target.splitText(state.offset);

		// Fix the selection.
		if(anchorNode == target && anchorOffset >= state.offset) {
			anchorNode = newContainer;
			anchorOffset -= state.offset;
		}
		if(focusNode == target && focusOffset >= state.offset) {
			focusNode = newContainer;
			focusOffset -= state.offset;
		}
	}

	// Split an element.
	else if(target.nodeType == mozile.dom.TEXT_NODE ||
		target.nodeType == mozile.dom.ELEMENT_NODE) {
		// Count previous siblings (not including the target node).
		var i = 0;
		if(state.after) i++;
		var node = target;
		while(node) {
			i++;
			node = node.previousSibling;
		}
	
		// Create the new node.	
		oldContainer = target.parentNode;
		newContainer = oldContainer.cloneNode(false);
		//alert(mozile.xpath.getXPath(target) +" "+ state.offset +"\n"+
		//	oldContainer +" "+ newContainer);
		mozile.edit.insertNode.request(state, fresh, 
			null, oldContainer, newContainer);
		var newContainerPath = mozile.xpath.getXPath(newContainer);
		
		// Move nodes
		while(oldContainer.childNodes.length >= i) {
			mozile.edit.moveNode.request(state, fresh, 
				newContainerPath, null, oldContainer.lastChild);
		}
		
		if(mozile.edit.isBlock(oldContainer))
			mozile.edit._ensureNonEmpty(state, fresh, oldContainer);
		if(mozile.edit.isBlock(newContainer))
			mozile.edit._ensureNonEmpty(state, fresh, newContainer);
	}

	// Restore the selection.
	selection.collapse(anchorNode, anchorOffset);
	if(focusNode != selection.anchorNode || 
		focusOffset != selection.anchorOffset) {
		selection.extend(focusNode, focusOffset);
	}

	state.oldContainer = oldContainer;
	state.newContainer = newContainer;
	state.selection.after = selection.store();
	state.executed = true;
	return state;
}

/**
 * Merges split nodes.
 * Unexecutes all move operations, removes the cloned node, and normalizes the focusNode if it was split.
 * @param {mozile.edit.State} state The state information needed to unexecute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.splitNode.unexecute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.after);

	// Unexecute actions in reverse order.
	for(var i = state.actions.length - 1; i >= 0; i--) {
		state.actions[i] = state.actions[i].command.unexecute(state.actions[i], fresh);
		if(state.actions[i].executed) throw("Error: mozile.edit.splitNode.unexecute Child command unexecute failed at action "+ i +".");
	}

	// Normalize text node.
	if(state.splitNode) {
		state.splitNode.appendData(state.newContainer.data);
		if(state.newContainer.parentNode) {
			state.newContainer.parentNode.removeChild(state.newContainer);
		}
		//else mozile.debug.debug("mozile.edit.splitNode.unexecute", "New container has no parent: "+ state.newContainer.nodeName);
	}

	selection.restore(state.selection.before);
	state.executed = false;
	return state;
}





/**
 * Splits mutliple nodes.
 * @type mozile.edit.Command
 */
mozile.edit.splitNodes = new mozile.edit.Command("splitNodes");

/**
 * True when given an "enter" key keypress.
 * @param {Event} event The event object to be tested.
 * @param {Node} target Optional. The first node in the new split element.
 * @param {Node} offset Optional. An integer offset within a text node where the text node will be split.
 * @param {Node} limitNode Optional. The first node which will not be split. Defaults to the parent block's parent.
 * @param {Boolean} shallow Optional. Indicates that the method should not climb all the way to the limitNode.
 * @type Boolean
 */
mozile.edit.splitNodes.test = function(event, target, offset, limitNode, shallow) {
	if(event) {
		return false;
	}
	
	return true;
}

/**
 * Prepares a state object for the split nodes command.
 * @param {Event} event The event object to be converted into a state.
 * @param {Node} target Optional. The first node in the new split element.
 * @param {Node} offset Optional. An integer offset within a text node where the text node will be split.
 * @param {Node} limitNode Optional. The first node which will not be split. Defaults to the parent block's parent.
 * @param {Boolean} shallow Optional. Indicates that the method should not climb all the way to the limitNode.
 * @type mozile.edit.State
 */
mozile.edit.splitNodes.prepare = function(event, target, offset, limitNode, shallow) {
	var state = new mozile.edit.State(this);

	if(!target) {	
		var selection = mozile.dom.selection.get();
		var range = selection.getRangeAt(0);
		if(range.startContainer.nodeType == mozile.dom.TEXT_NODE)
			target = range.startContainer;
		else target = range.startContainer.childNodes[range.startOffset];
	}
	state.target = state.storeNode(target);

	state.offset = null;
	if(offset != undefined && offset != null) state.offset = offset;
	// If the command was triggered by an event, and no offset was given, 
	// indicate that the focusOffset should be used.
	else if(event) state.offset = "focusOffset";

	var limit = null;
	if(limitNode && mozile.dom.isAncestorOf(limitNode, target)) {
		limit = limitNode;
	}
	else limit = mozile.edit.getParentBlock(target).parentNode;
	state.limitNode = state.storeNode(limit);
	
	state.shallow = false;
	if(shallow === true) state.shallow = true;

	return state;
}

/**
 * Splits multiple nodes by calling splitNode repeatedly.
 * If the selection is not collapsed, the range is removed first.
 * Starting at the focusNode, each ancestor is split until a block level element is reached. 
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.splitNodes.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	state.actions = new Array();

	//alert(state.target +" "+ offset +"\n"+ state.limitNode);
	var newState;
	var node = mozile.xpath.getNode(state.target);
	var offset = state.offset;
	if(offset == "focusOffset") offset = selection.focusOffset;
	if(node.nodeType == mozile.dom.ELEMENT_NODE && node.childNodes[offset]){
		node = node.childNodes[offset];
		offset = null;
	}
	var limitNode = mozile.xpath.getNode(state.limitNode);
	var after = false;
	//alert(mozile.xpath.getXPath(node) +" "+ offset +"\n"+ mozile.xpath.getXPath(limitNode));
	
	// Try to avoid childless elements.
	while(node) {
		if(node.nodeType == mozile.dom.ELEMENT_NODE && 
			mozile.edit.isChildless(node) && node.nextSibling) {
			node = node.nextSibling;
		}
		else break;
	}
	
	// Try to avoid splitting by climbing toward the root.
	// The insertion point changes, but the visible cursor position does not.
	// Don't climb any higher than the limitNode's grandchildren.
	// Climb to the left.
	if(offset == null || offset == 0) {
		offset = null;
		while(node) {
			//alert("Climbing left "+ mozile.xpath.getXPath(node) +" "+ offset);
			if(node == limitNode) break;
			if(!node.parentNode) break;
			if(node.parentNode == limitNode) break;
			if(node != node.parentNode.firstChild) break;
			if(state.shallow) {
				if(!node.parentNode.parentNode) break;
				if(node.parentNode.parentNode == limitNode) break;
			}
			node = node.parentNode;
		}
	}
	// Climb to the right.
	else if(node.data && offset == node.data.length) {
		offset = null;
		if(node.nextSibling) node = node.nextSibling;
		while(node) {
			//alert("Climbing right "+ mozile.xpath.getXPath(node) +" "+ offset);
			if(node == limitNode) break;
			if(!node.parentNode) break;
			if(node.parentNode == limitNode) break;
			if(node != node.parentNode.lastChild) break;
			if(!node.parentNode.nextSibling) break;
			if(state.shallow) {
				if(!node.parentNode.parentNode) break;
				if(node.parentNode.parentNode == limitNode) break;
			}
			node = node.parentNode.nextSibling;
		}
		// Special case: this is the last child of its parent.
		if(node == node.parentNode.lastChild) after = true;
	}
	
	// Split until the limit node is reached.
	while(node) {
		//alert("Splitting "+ mozile.xpath.getXPath(node) +" "+ offset);
		if(node == limitNode) break;
		if(offset == null && node.parentNode == limitNode) break;
		if(!node.parentNode) break;

		newState = mozile.edit.splitNode.request(state, fresh, node, offset, after);

		if(newState && newState.newContainer) {
			node = newState.newContainer;
			offset = null;
		}
	}
	
	if(newState) {
		if(newState.oldContainer) state.oldContainer = newState.oldContainer;
		if(newState.newContainer) state.newContainer = newState.newContainer;
	}
	else {
		state.oldContainer = node.previousSibling;
		state.newContainer = node;
	}

	state.selection.after = selection.store();
	state.executed = true;
	return state;
}





mozile.edit.removeFormatting = new mozile.edit.Command("removeFormatting");
mozile.edit.removeFormatting.image = "silk/pencil_delete";
mozile.edit.removeFormatting.label = "Remove Formatting";
mozile.edit.removeFormatting.tooltip = "Remove formatting";
mozile.edit.removeFormatting.target = "block";
mozile.edit.removeFormatting.direction = "ancestor";

/**
 * Indicates that the command is available to be used.
 * @param {Event} event Optional. The current event object.
 * @type Boolean
 */
mozile.edit.removeFormatting.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	return true;
}

mozile.edit.removeFormatting.test = function(event) {
	if(event) {
		//if(this.accels) return mozile.edit.checkAccelerators(event, this.accels);
		return false;
	}
	
	var selection = mozile.dom.selection.get();	
	var range = selection.getRangeAt(0);
	var node = range.commonAncestorContainer;
	var target = mozile.edit._getTarget(event, this.target, this.direction);

	// Ignore text nodes at the top of a block.
	if(node.nodeType == mozile.dom.TEXT_NODE && node.parentNode == target)
		return false;
	
	return true;
}

mozile.edit.removeFormatting.prepare = function(event) {
	var state = new mozile.edit.State(this);
	var target = mozile.edit._getTarget(event, this.target, this.direction);
	state.target = state.storeNode(target);
	state.limit = state.storeNode(target);
	return state;
}

mozile.edit.removeFormatting.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	var range = selection.getRangeAt(0);
	state.actions = new Array();
	var target, limit, newState, textNode;

	// Insert an empty element at the selection.
	if(range.collapsed) {
		target = mozile.xpath.getNode(state.target);
		limit = mozile.xpath.getNode(state.limit);
		newState = mozile.edit.splitNodes(state, fresh, target, selection.anchorOffset, limit);
		textNode = mozile.document.createTextNode("");
		mozile.edit.insertNode(state, fresh, null, state.oldContainer, textNode);
		selection.collapse(textNode, 0);
	}

	else {
		// Get the container.
		var container = range.commonAncestorContainer;

		// Split nodes.
		var node, offset, startNode, endNode, previousNode, nextNode;
		// Split the end node.
		node = range.endContainer;
		offset = range.endOffset;
		var endContainer = mozile.edit.getParentBlock(node);
		newState = mozile.edit.splitNodes.request(state, fresh, 
			node, offset, endContainer);
		endNode = newState.oldContainer;
		nextNode = newState.newContainer;
		
		// Split the start node.
		node = range.startContainer;
		offset = range.startOffset;
		var startContainer = mozile.edit.getParentBlock(node);
		newState = mozile.edit.splitNodes.request(state, fresh, 
			node, offset, startContainer);
		previousNode = newState.oldContainer;
		startNode = newState.newContainer;
		if(endNode == node) endNode = startNode;

		//alert("Start "+ mozile.xpath.getXPath(startNode) +"\n"+
		//  "End "+ mozile.xpath.getXPath(endNode) +"\n"+
		//  "Previous "+ mozile.xpath.getXPath(previousNode) +"\n"+
		//  "Next "+ mozile.xpath.getXPath(nextNode) +"\n"+
		//  "Container "+ mozile.xpath.getXPath(container) );

		// Set up the treeWalker.
		var treeWalker = document.createTreeWalker(container, mozile.dom.NodeFilter.SHOW_ALL, null, false);
		
		// Remove all wrappers.
		treeWalker.currentNode = startNode;
		var current = treeWalker.currentNode;
		var last = previousNode;
		while(current) {
			//alert(mozile.xpath.getXPath(current) +"\n"+ 
			//	mozile.xpath.getXPath(nextNode) );
			if(current == nextNode) break;
			// TODO: Ignore childless elements?
			if(current.nodeType == mozile.dom.ELEMENT_NODE && 
				!mozile.edit.isBlock(current)) {
				treeWalker.currentNode = last;
				mozile.edit._unwrapNode(state, fresh, current, false);
				current = treeWalker.nextNode();
				//alert(mozile.xpath.getXPath(current));
			}
			else {
				last = current;
				current = treeWalker.nextNode();
			}
		}

		// Restore the selection.
		selection = mozile.dom.selection.get();
		range = selection.getRangeAt(0);
		container = range.commonAncestorContainer;
		if(container.nodeType != mozile.dom.TEXT_NODE) {
			IP = mozile.edit.getInsertionPoint(previousNode, mozile.edit.PREVIOUS, true);
			if(IP) {
				IP.seekNode(mozile.edit.NEXT, false);
				IP.select();
				IP = mozile.edit.getInsertionPoint(nextNode, mozile.edit.NEXT, true);
				if(IP) {
					IP.seekNode(mozile.edit.PREVIOUS, false);
					IP.extend();
				}
			}
		}

		// Set up the treeWalker.
		var treeWalker = document.createTreeWalker(container, mozile.dom.NodeFilter.SHOW_ALL, null, false);
		
		// Normalize text nodes.
		treeWalker.currentNode = previousNode;
		var current = treeWalker.currentNode;
		var result;
		while(current) {
			//alert(mozile.xpath.getXPath(current) +"\n"+ 
			//	current.data +"\n"+ 
			//	mozile.xpath.getXPath(nextNode) +"\n"+ 
			//	nextNode.data);
			if(current == nextNode || !nextNode.parentNode) break;
			if(current.nextSibling) {
				result = mozile.edit._normalize(state, fresh, 
					current, current.nextSibling);
				if(result) continue;
			}
			current = treeWalker.nextNode();
		}

	}

	state.selection.after = selection.store();
	state.executed = true;
	return state;	
}



/**
 * Splits a target element using splitNodes.
 * @param {String} name The command's name.
 * @param {Object} localization Optional. The localization object to use.
 * @constructor
 */
mozile.edit.Split = function(name, localization) { 
	/**
	 * The name for this command group.
	 * @type String
	 */
	 this.name = name;
	 
	 /**
	  * Indicates that this command does not contain more commands.
	  * @type Boolean
	  */
	 this.group = false;
	 
	 /**
	  * Indicates that any contents of the selection should be removed before inserting.
	  * @type Boolean
	  */
	 this.remove = true;
	 
	 /**
	  * Specifies what kind of change this command makes to the document.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.makesChanges = "node";
	 
	 /**
	  * Specifies what kind of change will cause this command to change its isActive or isAvailable states.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.watchesChanges = "node";
	 
	 /**
	  * Determines what node is the target of the split. Can be a string: "element", "block", or "localName [name]"; or a function.
	  * @see #mozile.edit._getTarget
	  * @type String
	  */
	 this.target = "block";
	 
	 /**
	  * Determines what direction to search for the target in. 
	  * @see #mozile.edit._getTarget
	  * @type String
	  */
	 this.direction = "ancestor";
	 
	 // Perform localization.
	 this.localize(localization);

	 // Register this command group on the list of all commands.
	 mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Split.prototype = new mozile.edit.Command;
mozile.edit.Split.prototype.constructor = mozile.edit.Split;

/**
 * Indicates that the command is available to be used.
 * @param {Event} event Optional. The current event object.
 * @type Boolean
 */
mozile.edit.Split.prototype.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	return true;
}

/**
 * Prepares a state object for the Split command.
 * @param {Event} event The event object to be converted into a state.
 * @type mozile.edit.State
 */
mozile.edit.Split.prototype.test = function(event) {
	if(event) {
		if(this.accel && !this.accels) {
			this.accels = mozile.edit.splitAccelerators(this.accel);
		}
		if(this.accels) {
			if(!mozile.edit.checkAccelerators(event, this.accels)) return false;
		}
		else return false;
	}
	
	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(!target) return false;
	
	return true;
}

/**
 * Prepares a state object for the Split command.
 * @param {Event} event The event object to be converted into a state.
 * @type mozile.edit.State
 */
mozile.edit.Split.prototype.prepare = function(event) {
	var state = new mozile.edit.State(this);

	var target = mozile.edit._getTarget(event, this.target, this.direction);
	state.limit = state.storeNode(target.parentNode);

	// Some commands may need additional information from the user.
	// If so, define a method named "prompt", which takes the event and the state and returns a Boolean true when it succeeds.
	if(this.prompt) {
		if(!this.prompt(event, state)) return null;
	}

	return state;
}

/**
 * Inserts a new element at the selection.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.Split.prototype.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	state.actions = new Array();
	
	if(!selection.isCollapsed) mozile.edit.remove.request(state, fresh);
	var limit = mozile.xpath.getNode(state.limit);
	var newState = mozile.edit.splitNodes.request(state, fresh, selection.focusNode, selection.focusOffset, limit, true);
	var IP = mozile.edit.getInsertionPoint(newState.newContainer, mozile.edit.NEXT);
	if(IP) IP.select();
	//alert(newState);
	
	state.newContainer = newState.newContainer;
	state.oldContainer = newState.oldContainer;

	state.selection.after = selection.store();
	state.executed = true;
	return state;
}



/**
 * A command used to insert a new element.
 * @param {String} name The command's name.
 * @param {Object} localization Optional. The localization object to use.
 * @constructor
 */
mozile.edit.Insert = function(name, localization) { 
	/**
	 * The name for this command group.
	 * @type String
	 */
	 this.name = name;
	 
	 /**
	  * Indicates that this command does not contain more commands.
	  * @type Boolean
	  */
	 this.group = false;
	 
	 /**
	  * Indicates that any contents of the selection should be removed before inserting.
	  * @type Boolean
	  */
	 this.remove = true;
	 
	 /**
	  * Specifies what kind of change this command makes to the document.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.makesChanges = "node";
	 
	 /**
	  * Specifies what kind of change will cause this command to change its isActive or isAvailable states.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.watchesChanges = "node";
	 
	 // Perform localization.
	 this.localize(localization);

	 // Register this command group on the list of all commands.
	 mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Insert.prototype = new mozile.edit.Command;
mozile.edit.Insert.prototype.constructor = mozile.edit.Insert;

/**
 * Indicates that the command is available to be used.
 * @param {Event} event Optional. The current event object.
 * @type Boolean
 */
mozile.edit.Insert.prototype.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	return true;
}


/**
 * Prepares a state object for the wrap command.
 * @param {Event} event The event object to be converted into a state.
 * @type mozile.edit.State
 */
mozile.edit.Insert.prototype.prepare = function(event) {
	var selection = mozile.dom.selection.get();
	selection.restore();

	var state = new mozile.edit.State(this);
	
	state.element = null;
	if(typeof(this.element) == "string") {
		state.element = mozile.dom.createElement(this.element);
		if(this.className) {
			mozile.dom.setClass(state.element, this.className);
		}
		if(this.styleName) {
			mozile.dom.setStyle(state.element, this.styleName, this.styleValue);
		}
	}
	else if(this.element && this.element.nodeType) {
		state.element = mozile.dom.importNode(this.element, true);
	}

	state.text = null;
	if(state.element == null && this.text) {
		state.text = this.text;
	}
	

	// Some commands may need additional information from the user.
	// If so, define a method named "prompt", which takes the event and the state and returns a Boolean true when it succeeds.
	if(this.prompt) {
		if(!this.prompt(event, state)) return null;
	}

	return state;
}

/**
 * Inserts a new element at the selection.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.Insert.prototype.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	state.actions = new Array();
	var newState;
	var previousNode;
	var text;

	// Remove the current range, if it is not collapsed.
	if(this.remove && !selection.isCollapsed) {
		mozile.edit.remove.request(state, fresh, mozile.edit.NEXT);
		selection = mozile.dom.selection.get();
	}
	
	// Insert some text.
	if(state.text) {
		mozile.edit.insertText.request(state, fresh, null, state.text);
		state.selection.after = selection.store();
		state.executed = true;
		return state;
	}

	// Insert an empty element, and add an empty token if needed.
	if(selection.isCollapsed) {
		if(selection.focusNode.nodeType == mozile.dom.TEXT_NODE) {
			// If we're at the beginning of a text node, there's no need to split it.
			if(selection.focusOffset == 0) {
				previousNode = selection.focusNode.previousSibling;
			}
			// Split the text node.
			else {
				newState = mozile.edit.splitNode.request(state, fresh,
					selection.focusNode, selection.focusOffset);
				previousNode = newState.oldContainer;
			}
		}
		else previousNode = selection.focusNode[selection.focusOffset];

		mozile.edit.insertNode.request(state, fresh, 
			null, previousNode, state.element);

		if(!this.remove) {
			if(mozile.edit.isBlock(state.element))
				text = mozile.edit.createEmptyToken();
			else text = mozile.document.createTextNode("");
			state.element.appendChild(text);
		}
	}

	// Insert an element and add content to it.
	else {	
		var range = selection.getRangeAt(0);
		var container = range.commonAncestorContainer;
		if(container.nodeType == mozile.dom.TEXT_NODE) container = container.parentNode;

		// Split start and end nodes until reaching the commonAncestorContainer.
		// Store these two values for IE.
		var startContainer = range.startContainer;
		var startOffset = range.startOffset;
		newState = mozile.edit.splitNodes.request(state, fresh, 
			range.endContainer, range.endOffset, container);
		var nextNode = newState.newContainer;
		newState = mozile.edit.splitNodes.request(state, fresh, 
			startContainer, startOffset, container);
		previousNode = newState.oldContainer;

		// Insert the new element.
		mozile.edit.insertNode.request(state, fresh, 
			null, previousNode, state.element);

		// Move the nodes in between the start and end containers.
		// Loop until the nextNode is found or we run out of nodes.
		var current = state.element.nextSibling;
		while(current) {
			if(current == nextNode) break;
			var target = current;
			current = current.nextSibling;
			mozile.edit.moveNode.request(state, fresh, state.element, state.element.lastChild, target);
		}
	}
	
	// Restore the selection.
	if(text) selection.collapse(text, 0);
	else {
		var IP = mozile.edit.getInsertionPoint(state.element, mozile.edit.NEXT);
		if(IP) {
			// Move to the next node.
			if(this.remove) {
				IP.seekNode(mozile.edit.NEXT, false);
				if(IP) IP.select();
			}
			// Select this new node.
			else {
				IP.select();
				IP = mozile.edit.getInsertionPoint(state.element, mozile.edit.PREVIOUS);
				if(IP) IP.extend();
			}
		}
	}

	state.selection.after = selection.store();
	state.executed = true;
	return state;
}


// Create a general use insert command.
mozile.edit.insert = new mozile.edit.Insert("insert");





/**
 * A command used to wrap a selection inside a new element.
 * @param {String} name The command's name.
 * @param {Object} localization Optional. The localization object to use.
 * @constructor
 */
mozile.edit.Wrap = function(name, localization) { 
	/**
	 * The name for this command group.
	 * @type String
	 */
	 this.name = name;
	 
	 /**
	  * Indicates that this command does not contain more commands.
	  * @type Boolean
	  */
	 this.group = false;
	 
	 /**
	  * Specifies what kind of change this command makes to the document.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.makesChanges = "node";
	 
	 /**
	  * Specifies what kind of change will cause this command to change its isActive or isAvailable states.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.watchesChanges = "node";
	 
	 /**
	  * Determines whether wrappers may be nested.
	  * @type Boolean
	  */
	 this.nested = false;
	 
	 // Perform localization.
	 this.localize(localization);

	 // Register this command group on the list of all commands.
	 mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Wrap.prototype = new mozile.edit.Command;
mozile.edit.Wrap.prototype.constructor = mozile.edit.Wrap;


/**
 * Checks to see if the given node is a wrapper.
 * @param {Node} node The node to check.
 * @type Boolean
 */
mozile.edit.Wrap.prototype._isWrapper = function(node) {
	if(!node) return false;
	var targetName = mozile.dom.getLocalName(node);
	if(!targetName) return false;
	targetName = targetName.toLowerCase();

	var wrapperName = mozile.edit._getElementName(this);
	if(!wrapperName) return false;
	wrapperName = wrapperName.toLowerCase();

	if(targetName == wrapperName) {
		if(this.className) {
			if(mozile.dom.hasClass(node, this.className)) return true;
		}
		if(this.styleName) {
			var styleName = mozile.dom.convertStyleName(this.styleName);
			if(node.style && node.style[styleName] &&
				node.style[styleName] == this.styleValue) return true;
		}
		else return true;
	}
	
	return false;
}

/**
 * Finds the wrapper for the given node if it has one.
 * @param {Node} node The node to start searching from.
 * @param {Boolean} outerWrapper Optional. When true the method will continue to search for wrappers instead of stopping when one is found.
 * @type Element
 */
mozile.edit.Wrap.prototype._getWrapper = function(node, outerWrapper) {
	if(!node) return false;
	var wrapper = null;

	while(node) {
		if(this._isWrapper(node)) wrapper = node;
		if(wrapper && !outerWrapper) break;
		node = node.parentNode;
	}
	
	return wrapper;
}

/**
 * Indicates that the command is available to be used.
 * @param {Event} event Optional. The current event object.
 * @type Boolean
 */
mozile.edit.Wrap.prototype.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	return true;
}

/**
 * Indicates that the command is currently active.
 * In the case of a "strong" command it would me that the cursor is inside a "strong" element, and the command's button should indicate that fact.
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.Wrap.prototype.isActive = function(event) {
	if(!event || !event.editable) return false;
	if(this.prompt) return false;
	if(event && event.node && this._getWrapper(event.node)) return true;
	else return false;
}

/**
 * Prepares a state object for the wrap command.
 * @param {Event} event The event object to be converted into a state.
 * @type mozile.edit.State
 */
mozile.edit.Wrap.prototype.prepare = function(event) {
	var state = new mozile.edit.State(this);
	
	state.wrapper = null;
	if(typeof(this.element) == "string") {
		state.wrapper = mozile.dom.createElement(this.element);
		if(this.className) {
			mozile.dom.setClass(state.wrapper, this.className);
		}
		if(this.styleName) {
			mozile.dom.setStyle(state.wrapper, this.styleName, this.styleValue);
		}
	}
	else if(this.element && this.element.nodeType) {
		state.wrapper = mozile.dom.importNode(this.element, true);
	}

	// Some commands may need additional information from the user.
	// If so, define a method named "prompt", which takes the event and the state and returns a Boolean true when it succeeds.
	if(this.prompt) {
		if(!this.prompt(event, state)) return null;
	}
	
	return state;
}

/**
 * Wraps the current selection in a new element.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.Wrap.prototype.execute = function(state, fresh) {
	//alert("Wrapping");
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	var range = selection.getRangeAt(0);
	state.actions = new Array();
	var wrapper = state.wrapper;
	state.wrappers = new Array();
	var nextNode, previousNode, textNode, newState, IP;

	// Insert an empty element at the selection.
	if(range.collapsed) {
		var outerWrapper = this._getWrapper(range.commonAncestorContainer, true);
		
		// If the node is already wrapper, and wrappers shouldn't be nested, unwrap it.
		if(outerWrapper && !this.nested) {
			// If we're at the end of a wrapper, jump out of it.
			if(selection.focusNode == outerWrapper.lastChild &&
				selection.focusOffset == selection.focusNode.data.length) {
				IP = mozile.edit.getInsertionPoint(outerWrapper, mozile.edit.PREVIOUS, true);
				if(IP) IP.seekNode(mozile.edit.NEXT, false);
				if(IP) IP.select();
				// If the wrapper is empty, remove it.
				mozile.edit._removeEmpty(state, fresh, 
					outerWrapper.lastChild, outerWrapper.parentNode);
			}
			// Split the wrapper and insert a new text node.
			else {
				newState = mozile.edit.splitNodes.request(state, fresh, range.startContainer, range.startOffset, outerWrapper.parentNode);
				previousNode = newState.oldContainer;
				textNode = mozile.document.createTextNode("");
				mozile.edit.insertNode.request(state, fresh, 
					previousNode.parentNode, previousNode, textNode);
				selection.collapse(textNode, 0);
			}
		}
		
		// If the node has no wrapper, insert a new wrapper and text node.
		else {
			if(range.startContainer.nodeType == mozile.dom.TEXT_NODE) {
				if(range.startOffset < range.startContainer.data.length) {
					newState = mozile.edit.splitNodes.request(state, fresh, 
						range.startContainer, range.startOffset, 
						range.startContainer.parentNode);
					previousNode = newState.oldContainer;
				}
				else previousNode = range.startContainer;
			}
			else if(range.startOffset > 0) {
				previousNode = range.startContainer.childNodes[range.startOffset - 1];
			}
			mozile.edit.insertNode.request(state, fresh, 
				previousNode.parentNode, previousNode, wrapper);
			wrapper.appendChild(mozile.document.createTextNode(""));
			selection.collapse(wrapper.firstChild, 0);
		}
	}

	// Wrap the selection with one or more new elements.
	// Create as many wrappers as needed.
	else {
		// Get the containers.
		var container = range.commonAncestorContainer;
		var startContainer = range.startContainer;
		if(startContainer.nodeType == mozile.dom.ELEMENT_NODE &&
			startContainer.childNodes[range.startOffset]) {
			startContainer = startContainer.childNodes[range.startOffset];
		}
		var endContainer = range.endContainer;
		if(endContainer.nodeType == mozile.dom.ELEMENT_NODE &&
			endContainer.childNodes[range.endOffset]) {
			endContainer = endContainer.childNodes[range.endOffset];
		}
		
		// Get the start and end wrappers, if they exist.
		var startWrapper, endWrapper;
		if(!this.nested) {
			startWrapper = this._getWrapper(startContainer, true);
			endWrapper = this._getWrapper(endContainer, true);
		}
		if(startWrapper && endWrapper)
			container = mozile.dom.getCommonAncestor(startWrapper, endWrapper);
		else if(startWrapper)
			container = mozile.dom.getCommonAncestor(startWrapper, container);
		else if(endWrapper)
			container = mozile.dom.getCommonAncestor(endWrapper, container);
		container = container.parentNode;
		// Split nodes.
		var node, offset, startNode, endNode;
		// Split the end node.
		node = range.endContainer;
		offset = range.endOffset;
		var endContainer = node.parentNode;
		if(endWrapper) endContainer = endWrapper.parentNode;
		newState = mozile.edit.splitNodes.request(state, fresh, 
			node, offset, endContainer);
		endNode = newState.oldContainer;
		nextNode = newState.newContainer;
		
		// Split the start node.
		node = range.startContainer;
		offset = range.startOffset;
		var startContainer = node.parentNode;
		if(startWrapper) startContainer = startWrapper.parentNode;
		newState = mozile.edit.splitNodes.request(state, fresh, 
			node, offset, startContainer);
		previousNode = newState.oldContainer;
		startNode = newState.newContainer;
		if(endNode == node) endNode = startNode;

		//console.info("Start "+ mozile.xpath.getXPath(startNode) +"\n"+
		//  "End "+ mozile.xpath.getXPath(endNode) +"\n"+
		//  "Previous "+ mozile.xpath.getXPath(previousNode) +"\n"+
		//  "Next "+ mozile.xpath.getXPath(nextNode) +"\n"+
		//  "Container "+ mozile.xpath.getXPath(container) );

		// Set up the treeWalker.
		var treeWalker = document.createTreeWalker(container, mozile.dom.NodeFilter.SHOW_ALL, null, false);
		var allNodesWrapped = false;
		
		// Remove all wrappers.
		if(!this.nested) {
			allNodesWrapped = true;
			treeWalker.currentNode = startNode;
			var current = treeWalker.currentNode;
			var oldWrapper;
			while(current) {
				if(current == nextNode) break;
				oldWrapper = this._getWrapper(current);
				if(oldWrapper) {
					// Get the next "current" node.
					if(oldWrapper.nextSibling) {
						current = oldWrapper.nextSibling;
						if(current == nextNode) current = null;
						else if(current.nodeType == mozile.dom.TEXT_NODE) {
							allNodesWrapped = false;
							current = current.nextSibling;
						}
					}
					mozile.edit._unwrapNode(state, fresh, oldWrapper);
				}
				else {
					allNodesWrapped = false;
					current = treeWalker.nextNode();
				}
			}
		}

		//alert("Start "+ mozile.xpath.getXPath(startNode) +"\n"+
		//  "End "+ mozile.xpath.getXPath(endNode) +"\n"+
		//  "Previous "+ mozile.xpath.getXPath(previousNode) +"\n"+
		//  "Next "+ mozile.xpath.getXPath(nextNode) +"\n"+
		//  "Container "+ mozile.xpath.getXPath(container) +"\n"+
		//  "All Nodes Wrapped "+ allNodesWrapped);

		// If any nodes were not previously wrapped, wrap everything again.
		// startNode == nextNode is a special case; it usually occurs when several different wrappers are added in sequence.
		if(!allNodesWrapped || startNode == nextNode) {
			if(previousNode) {
				treeWalker.currentNode = previousNode;
				treeWalker.nextSibling();
			}
			else if(startNode == nextNode) {
				treeWalker.currentNode = startNode;
				nextNode = container;
			}
			else treeWalker.currentNode = startNode;
			current = treeWalker.currentNode;
			var target, lastParent;
			while(current) {
				//alert(mozile.xpath.getXPath(current));
				// Check to see if the current node should NOT be moved.
				if(current == nextNode) break;

				// This avoids a loop in IE. Maybe a flaw in Mozile's IE TreeWalker.
				if(mozile.dom.isAncestorOf(wrapper, current, container)) break;
				// If the current node is inside the current wrapper, skip it.
				if(mozile.dom.isAncestorOf(current, wrapper, container))
					current = treeWalker.nextNode();
				// If the current node is an ancestor of the nextNode, start climbing toward the nextNode.
				else if(mozile.dom.isAncestorOf(current, nextNode, container))
					current = treeWalker.nextNode();
				// If the current node is a block element, move into it.
				else if(mozile.edit.isBlock(current))
					current = treeWalker.nextNode();
				// Ignore text nodes that can't contain text.
				else if(current.nodeType == mozile.dom.TEXT_NODE &&
					!mozile.edit.mayContainText(current))
					current = treeWalker.nextNode();
		
				// Move the node into the current wrapper.
				else {
					target = current;
					current = treeWalker.nextSibling();
					if(!current) current = treeWalker.nextNode();
					// If we've changed parents, create a new wrapper.
					if(target.parentNode && target.parentNode != lastParent) {
						wrapper = state.wrapper.cloneNode(true);
						state.wrappers.push(wrapper);
						mozile.edit.insertNode.request(state, fresh, null, target, wrapper);
						lastParent = target.parentNode;
						//alert(mozile.xpath.getXPath(target) +"\n"+ mozile.xpath.getXPath(wrapper));
					}
					mozile.edit.moveNode.request(state, fresh, wrapper, wrapper.lastChild, target);
				}
			}
		}

		// Restore the selection.
		selection = mozile.dom.selection.get();
		range = selection.getRangeAt(0);
		container = range.commonAncestorContainer;
		if(container.nodeType != mozile.dom.TEXT_NODE) {
			IP = mozile.edit.getInsertionPoint(previousNode, mozile.edit.PREVIOUS, true);
			if(IP) {
				IP.seekNode(mozile.edit.NEXT, false);
				IP.select();
				IP = mozile.edit.getInsertionPoint(nextNode, mozile.edit.NEXT, true);
				if(IP) {
					IP.seekNode(mozile.edit.PREVIOUS, false);
					IP.extend();
				}
			}
		}

		//alert("Next Prev "+ mozile.xpath.getXPath(nextNode.previousSibling) +"\n"+
		//	"Next "+ mozile.xpath.getXPath(nextNode) +"\n"+
		//	"Prev "+ mozile.xpath.getXPath(previousNode) +"\n"+
		//	"Prev Next "+ mozile.xpath.getXPath(previousNode.nextSibling)
		//	);
			
		// Merge adjacent wrappers.
		if(this._isWrapper(previousNode) && 
			this._isWrapper(previousNode.nextSibling)) {
		  mozile.edit.mergeNodes.request(state, fresh, previousNode.nextSibling, previousNode);
		}
		if(nextNode && this._isWrapper(nextNode) && nextNode.previousSibling &&
			this._isWrapper(nextNode.previousSibling)) {
		  mozile.edit.mergeNodes.request(state, fresh, nextNode, nextNode.previousSibling);
		}

	}

	state.selection.after = selection.store();
	state.executed = true;
	return state;
}



/**
 * A command used to remove a container element, but keep the child nodes.
 * @param {String} name The command's name.
 * @param {Object} localization Optional. The localization object to use.
 * @constructor
 */
mozile.edit.Unwrap = function(name, localization) { 
	/**
	 * The name for this command group.
	 * @type String
	 */
	 this.name = name;
	 
	 /**
	  * Indicates that this command does not contain more commands.
	  * @type Boolean
	  */
	 this.group = false;
	 
	 /**
	  * Specifies what kind of change this command makes to the document.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.makesChanges = "node";
	 
	 /**
	  * Specifies what kind of change will cause this command to change its isActive or isAvailable states.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.watchesChanges = "node";
	 
	 /**
	  * Determines what node is the target of the replacement. Can be a string: "element", "block", or "localName [name]"; or a function.
	  * @see #mozile.edit._getTarget
	  * @type String
	  */
	 this.target = "element";
	 
	 /**
	  * Determines what direction to search for the target in. 
	  * @see #mozile.edit._getTarget
	  * @type String
	  */
	 this.direction = "ancestor";
	 
	 // Perform localization.
	 this.localize(localization);

	 // Register this command group on the list of all commands.
	 mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Unwrap.prototype = new mozile.edit.Command;
mozile.edit.Unwrap.prototype.constructor = mozile.edit.Unwrap;


/**
 * Indicates that the command is currently available.
 * In the case of an "unlink" command it would me that the cursor is inside a "link" element, and the command's button should indicate that fact. If not, the command's button should be "disabled" (whatever that might mean for a particular GUI).
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.Unwrap.prototype.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(target) return true;
	return false;
}

/**
 * Tests an event to see if the command should be executed.
 * @param {Event} event Optional. The event object to test.
 * @param {Node} targetNode Optional. The node to be removed.
 * @type mozile.edit.State
 */
mozile.edit.Unwrap.prototype.test = function(event, targetNode) {
	if(event) {
		if(this.accel) {
			if(mozile.edit.checkAccelerators(event, this.accels)) { /* do nothing */ }
			if(mozile.edit.checkAccelerator(event, this.accel)) { /* do nothing */ }
			else return false;
		}
		else return false;
	}

	if(!this.target) return false;
	var node = targetNode;
	if(!node) node = mozile.edit._getTarget(event, this.target, this.direction);
	if(!node) return false;

	return true;
}

/**
 * Prepares a state object for the wrap command.
 * @param {Event} event The event object to be converted into a state.
 * @param {Node} targetNode Optional. The node to be removed.
 * @type mozile.edit.State
 */
mozile.edit.Unwrap.prototype.prepare = function(event, target) {
	var state = new mozile.edit.State(this);

	if(!target) target = mozile.edit._getTarget(event, this.target, this.direction);
	state.target = state.storeNode(target);

	// Some commands may need additional information from the user.
	// If so, define a method named "prompt", which takes the event and the state and returns a Boolean true when it succeeds.
	if(this.prompt) {
		if(!this.prompt(event, state)) return null;
	}

	return state;
}

/**
 * Inserts a new element at the selection.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.Unwrap.prototype.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	state.actions = new Array();

	var target = mozile.xpath.getNode(state.target);
	mozile.edit._unwrapNode(state, fresh, target);

	state.selection.after = selection.store();
	state.executed = true;
	return state;
}


mozile.edit.unindentList = new mozile.edit.Unwrap("unindentList");
mozile.edit.unindentList.accel = "Command-Left";
mozile.edit.unindentList.tooltip = "Move the list item up one level";
//mozile.edit.unindentList.image = "silk/arrow_left";
mozile.edit.unindentList.target = function(event) {
	var node = mozile.edit._getNode(event);
	while(node) {
		if(node.nodeName && node.nodeName.toLowerCase() == "ul") return node;
		node = node.parentNode;
	}
	return null;
};



/**
 * A command used to replace one element with another.
 * @param {String} name The command's name.
 * @param {Object} localization Optional. The localization object to use.
 * @constructor
 */
mozile.edit.Replace = function(name, localization) { 
	/**
	 * The name for this command group.
	 * @type String
	 */
	 this.name = name;
	 
	 /**
	  * Indicates that this command does not contain more commands.
	  * @type Boolean
	  */
	 this.group = false;
	 
	 /**
	  * Specifies what kind of change this command makes to the document.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.makesChanges = "node";
	 
	 /**
	  * Specifies what kind of change will cause this command to change its isActive or isAvailable states.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.watchesChanges = "node";
	 
	 /**
	  * Determines what node is the target of the replacement. Can be a string: "element", "block", or "localName [name]"; or a function.
	  * @see #mozile.edit._getTarget
	  * @type String
	  */
	 this.target = "element";
	 
	 /**
	  * Determines what direction to search for the target in. 
	  * @see #mozile.edit._getTarget
	  * @type String
	  */
	 this.direction = "ancestor";
	 
	 /**
	  * Determines whether attributes should be copied into the replacement.
	  * @type Boolean
	  */
	 this.copyAttributes = true;
	 
	 // Perform localization.
	 this.localize(localization);

	 // Register this command group on the list of all commands.
	 mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Replace.prototype = new mozile.edit.Command;
mozile.edit.Replace.prototype.constructor = mozile.edit.Replace;


/**
 * Indicates that the command is currently available.
 * In the case of an "unlink" command it would me that the cursor is inside a "link" element, and the command's button should indicate that fact. If not, the command's button should be "disabled" (whatever that might mean for a particular GUI).
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.Replace.prototype.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(target) return true;
	return false;
}

/**
 * Indicates that the command is currently active.
 * In the case of a "strong" command it would me that the cursor is inside a "strong" element, and the command's button should indicate that fact.
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.Replace.prototype.isActive = function(event) {
	if(!event || !event.editable) return false;
	if(this.prompt) return false;
	if(!this.elementName) this.elementName = mozile.edit._getElementName(this);
	if(!this.elementName) return false;

	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(target) {
		var targetName = mozile.dom.getLocalName(target).toLowerCase();
		if(targetName && targetName == this.elementName) {
			if(this.className) {
				if(mozile.dom.hasClass(target, this.className)) return true;
				else return false;
			}
			return true;
		}
	}

	return false;
}

/**
 * Tests an event to see if the command should be executed.
 * @param {Event} event Optional. The event object to test.
 * @type mozile.edit.State
 */
mozile.edit.Replace.prototype.test = function(event) {
	if(event) {
		if(this.accel) {
			if(mozile.edit.checkAccelerators(event, this.accels)) { /* do nothing */ }
			if(mozile.edit.checkAccelerator(event, this.accel)) { /* do nothing */ }
			else return false;
		}
		else return false;
	}

	if(!this.element) return false;
	if(!this.target) return false;
	
	var node = mozile.edit._getTarget(event, this.target, this.direction);
	if(!node) return false;

	return true;
}

/**
 * Prepares a state object for the wrap command.
 * @param {Event} event The event object to be converted into a state.
 * @type mozile.edit.State
 */
mozile.edit.Replace.prototype.prepare = function(event) {
	var state = new mozile.edit.State(this);
	
	// Get the element to replace with.
	state.element = null;
	if(typeof(this.element) == "string") {
		state.element = mozile.dom.createElement(this.element);
		if(this.className) {
			mozile.dom.setClass(state.element, this.className);
		}
		if(this.styleName) {
			mozile.dom.setStyle(state.element, this.styleName, this.styleValue);
		}
	}
	else if(this.element && this.element.cloneNode) {
		state.element = this.element.cloneNode(true);
	}

	var target = mozile.edit._getTarget(event, this.target, this.direction);
	state.target = state.storeNode(target);

	// Copy the target's attributes into the element.
	if(this.copyAttributes) {
		for(var i=0; i < target.attributes.length; i++) {
			var attr = target.attributes[i];
			state.element.setAttribute(attr.nodeName, attr.nodeValue);
		}
		if(target.className) state.element.className = target.className;
		if(target.mozile) {
			state.element.mozile = {};
			for(var key in target.mozile) {
				state.element.mozile[key] = target.mozile[key];
			}
		}
	}

	// Some commands may need additional information from the user.
	// If so, define a method named "prompt", which takes the event and the state and returns a Boolean true when it succeeds.
	if(this.prompt) {
		if(!this.prompt(event, state)) return null;
	}

	return state;
}

/**
 * Inserts a new element at the selection.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.Replace.prototype.execute = function(state, fresh) {
	var selection = mozile.dom.selection.get();
	if(!fresh) selection.restore(state.selection.before);
	var range = selection.getRangeAt(0);
	state.actions = new Array();
	
	var target = mozile.xpath.getNode(state.target);
	var focusNode = mozile.xpath.getXPath(selection.focusNode, state.target);
	var focusOffset = selection.focusOffset;

	mozile.edit.insertNode.request(state, fresh, null, target, state.element);

	while(target.firstChild) {
		mozile.edit.moveNode.request(state, fresh, 
			state.element, state.element.lastChild, target.firstChild);		
	}

	mozile.edit.removeNode.request(state, fresh, target);

	var newFocus = mozile.xpath.getNode(focusNode, state.element);
	if(newFocus) selection.collapse(newFocus, focusOffset);
	state.selection.after = selection.store();
	state.executed = true;
	return state;
}




/**
 * A command used to change and attribute of an element.
 * @param {String} name The command's name.
 * @param {Object} localization Optional. The localization object to use.
 * @constructor
 */
mozile.edit.SetAttribute = function(name, localization) { 
	/**
	 * The name for this command group.
	 * @type String
	 */
	 this.name = name;
	 
	 /**
	  * Indicates that this command does not contain more commands.
	  * @type Boolean
	  */
	 this.group = false;
	 
	 /**
	  * Specifies what kind of change this command makes to the document.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.makesChanges = "node";
	 
	 /**
	  * Specifies what kind of change will cause this command to change its isActive or isAvailable states.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.watchesChanges = "node";
	 
	 /**
	  * Determines what node is the target of the replacement. Can be a string: "element", "block", or "localName [name]"; or a function.
	  * @see #mozile.edit._getTarget
	  * @type String
	  */
	 this.target = "element";
	 
	 /**
	  * Determines what direction to search for the target in. 
	  * @see #mozile.edit._getTarget
	  * @type String
	  */
	 this.direction = "ancestor";
	 
	 /**
	  * Determines the style to be changed.
	  * @type String
	  */
	 this.attributeName = null;
	 
	 /**
	  * Determines the new value for the style. Can be a string or a function.
	  * @type String
	  */
	 this.attributeValue = null;
	 
	 // Perform localization.
	 this.localize(localization);

	 // Register this command group on the list of all commands.
	 mozile.edit.allCommands[this.name] = this;
}
mozile.edit.SetAttribute.prototype = new mozile.edit.Command;
mozile.edit.SetAttribute.prototype.constructor = mozile.edit.SetAttribute;


/**
 * Indicates that the command is currently available.
 * In the case of an "unlink" command it would me that the cursor is inside a "link" element, and the command's button should indicate that fact. If not, the command's button should be "disabled" (whatever that might mean for a particular GUI).
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.SetAttribute.prototype.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(target) return true;
	return false;
}

/**
 * Indicates that the command is currently active.
 * In the case of a "strong" command it would me that the cursor is inside a "strong" element, and the command's button should indicate that fact.
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.SetAttribute.prototype.isActive = function(event) {
	if(!event || !event.editable) return false;
	if(this.prompt) return false;
	if(!this.attributeName) return false;

	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(target && target.getAttribute(this.attributeName) == this.attributeName) 
		return true;

	return false;
}

/**
 * Tests an event to see if the command should be executed.
 * @param {Event} event Optional. The event object to test.
 * @type mozile.edit.State
 */
mozile.edit.SetAttribute.prototype.test = function(event, target, attributeName, attributeValue) {
	if(event) {
		if(this.accel) {
			if(mozile.edit.checkAccelerators(event, this.accels)) { /* do nothing */ }
			if(mozile.edit.checkAccelerator(event, this.accel)) { /* do nothing */ }
			else return false;
		}
		else return false;
	}

	if(!this.target) return false;
	var node = mozile.edit._getTarget(event, this.target, this.direction);
	if(!node) return false;

	if(this.prompt) return true;

	if(arguments.legnth < 3) attributeName = this.attributeName;
	if(!attributeName) return false;
	if(arguments.legnth < 4) attributeValue = this.attributeValue;

	var state = {targetNode: node};
	state.attributeName = attributeName;
	if(typeof(attributeValue) == "function") {
		var result = attributeValue(event, state);
		if(result === null) return false;
	}

	return true;
}

/**
 * Prepares a state object for the wrap command.
 * @param {Event} event The event object to be converted into a state.
 * @type mozile.edit.State
 */
mozile.edit.SetAttribute.prototype.prepare = function(event, target, attributeName, attributeValue) {
	var state = new mozile.edit.State(this);

	if(!target || !target.nodeType || 
		target.nodeType != mozile.dom.ELEMENT_NODE) 
		target = mozile.edit._getTarget(event, this.target, this.direction);
	state.targetNode = target;
	state.target = state.storeNode(target);

	if(arguments.length < 3) attributeName = this.attributeName;
	if(arguments.length < 4) attributeValue = this.attributeValue;
	
	state.attributeName = attributeName;
	state.attributeValue = null;
	if(typeof(attributeValue) == "function")
		state.attributeValue = attributeValue(event, state);
	else state.attributeValue = attributeValue;
	state.oldValue = mozile.dom.getAttribute(target, attributeName);

	// Some commands may need additional information from the user.
	// If so, define a method named "prompt", which takes the event and the state and returns a Boolean true when it succeeds.
	if(this.prompt) {
		if(!this.prompt(event, state)) return null;
	}

	return state;
}

/**
 * Inserts a new element at the selection.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.SetAttribute.prototype.execute = function(state, fresh) {
	var target = mozile.xpath.getNode(state.target);

	mozile.dom.setAttribute(target, state.attributeName, state.attributeValue);

	state.executed = true;
	return state;
}

/**
 * Removes all wrapper elements.
 * @param {mozile.edit.State} state The state information needed to unexecute this command.
 * @type Object
 */
mozile.edit.SetAttribute.prototype.unexecute = function(state, fresh) {
	var target = mozile.xpath.getNode(state.target);
	
	mozile.dom.setAttribute(target, state.attributeName, state.oldValue);

	state.executed = false;
	return state;
}

// Create an instance for general usage.
mozile.edit.setAttribute = new mozile.edit.SetAttribute("setAttribute");



/**
 * A command used to style an element.
 * @param {String} name The command's name.
 * @param {Object} localization Optional. The localization object to use.
 * @constructor
 */
mozile.edit.Style = function(name, localization) { 
	/**
	 * The name for this command group.
	 * @type String
	 */
	 this.name = name;
	 
	 /**
	  * Indicates that this command does not contain more commands.
	  * @type Boolean
	  */
	 this.group = false;
	 
	 /**
	  * Specifies what kind of change this command makes to the document.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.makesChanges = "node";
	 
	 /**
	  * Specifies what kind of change will cause this command to change its isActive or isAvailable states.
	  * See mozile.edit.Command.respond for possible values.
	  * @type String
	  */
	 this.watchesChanges = "node";
	 
	 /**
	  * Determines what node is the target of the replacement. Can be a string: "element", "block", or "localName [name]"; or a function.
	  * @see #mozile.edit._getTarget
	  * @type String
	  */
	 this.target = "element";
	 
	 /**
	  * Determines what direction to search for the target in. 
	  * @see #mozile.edit._getTarget
	  * @type String
	  */
	 this.direction = "ancestor";
	 
	 /**
	  * Determines the style to be changed.
	  * @type String
	  */
	 this.styleName = null;
	 
	 /**
	  * Determines the new value for the style. Can be a string or a function.
	  * @type String
	  */
	 this.styleValue = null;
	 
	 // Perform localization.
	 this.localize(localization);

	 // Register this command group on the list of all commands.
	 mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Style.prototype = new mozile.edit.Command;
mozile.edit.Style.prototype.constructor = mozile.edit.Style;


/**
 * Indicates that the command is currently available.
 * In the case of an "unlink" command it would me that the cursor is inside a "link" element, and the command's button should indicate that fact. If not, the command's button should be "disabled" (whatever that might mean for a particular GUI).
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.Style.prototype.isAvailable = function(event) {
	if(!event || !event.editable) return false;
	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(target) return true;
	return false;
}

/**
 * Indicates that the command is currently active.
 * In the case of a "strong" command it would me that the cursor is inside a "strong" element, and the command's button should indicate that fact.
 * @param {Event} event Optional. The current event.
 * @type Boolean
 */
mozile.edit.Style.prototype.isActive = function(event) {
	if(!event || !event.editable) return false;
	if(this.prompt) return false;
	var styleName = mozile.dom.convertStyleName(this.styleName);
	if(!styleName) return false;

	var target = mozile.edit._getTarget(event, this.target, this.direction);
	if(target && target.style && target.style[styleName] &&
		target.style[styleName] == this.styleValue) return true;

	return false;
}

/**
 * Tests an event to see if the command should be executed.
 * @param {Event} event Optional. The event object to test.
 * @type mozile.edit.State
 */
mozile.edit.Style.prototype.test = function(event) {
	if(event) {
		if(this.accel) {
			if(mozile.edit.checkAccelerators(event, this.accels)) { /* do nothing */ }
			if(mozile.edit.checkAccelerator(event, this.accel)) { /* do nothing */ }
			else return false;
		}
		else return false;
	}

	if(!this.target) return false;
	var node = mozile.edit._getTarget(event, this.target, this.direction);
	if(!node) return false;
	if(!node.style) return false;

	if(this.propmt) return true;

	if(!this.styleName) return false;
	if(!this.styleValue) return false;

	var state = {targetNode: node};
	state.styleName = mozile.dom.convertStyleName(this.styleName);
	if(typeof(this.styleValue) == "function") {
		var result = this.styleValue(event, state);
		if(result === null) return false;
	}

	return true;
}

/**
 * Prepares a state object for the wrap command.
 * @param {Event} event The event object to be converted into a state.
 * @type mozile.edit.State
 */
mozile.edit.Style.prototype.prepare = function(event) {
	var state = new mozile.edit.State(this);

	var target = mozile.edit._getTarget(event, this.target, this.direction);
	state.targetNode = target;
	state.target = state.storeNode(target);
	
	state.styleName = mozile.dom.convertStyleName(this.styleName);
	state.styleValue = null;
	if(typeof(this.styleValue) == "function")
		state.styleValue = this.styleValue(event, state);
	else if(typeof(this.styleValue) == "string")
		state.styleValue = this.styleValue;
	state.oldValue = null;

	// Some commands may need additional information from the user.
	// If so, define a method named "prompt", which takes the event and the state and returns a Boolean true when it succeeds.
	if(this.prompt) {
		if(!this.prompt(event, state)) return null;
	}

	return state;
}

/**
 * Inserts a new element at the selection.
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @type Object
 */
mozile.edit.Style.prototype.execute = function(state, fresh) {
	var target = mozile.xpath.getNode(state.target);

	state.oldValue = target.style[state.styleName];
	target.style[state.styleName] = state.styleValue;

	state.executed = true;
	return state;
}

/**
 * Removes all wrapper elements.
 * @param {mozile.edit.State} state The state information needed to unexecute this command.
 * @type Object
 */
mozile.edit.Style.prototype.unexecute = function(state, fresh) {
	var target = mozile.xpath.getNode(state.target);
	target.style[state.styleName] = state.oldValue;

	state.executed = false;
	return state;
}







/**** Support Methods ****/
/* 
 * NOTE: These methods are meant to be called by commands, but aren't themselves commands.
 */


/**
 * Merge two nodes.
 * This command operates by calling the mergeNodes command.
 * @private
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @param {Text} firstNode The first text node.
 * @param {Text} secondNode The second text node.
 * @type Boolean
 */
mozile.edit._mergeNodes = function(state, fresh, firstNode, secondNode) {
	var firstBlock = mozile.edit.getParentBlock(firstNode);
	var secondBlock = mozile.edit.getParentBlock(secondNode);
	
	//alert("Merge\n"+ mozile.xpath.getXPath(firstNode) +" "+ mozile.xpath.getXPath(secondNode) +"\n"+ mozile.xpath.getXPath(firstBlock) +"\n"+ mozile.xpath.getXPath(secondBlock));
	
	// TODO: Decide when to merge properly.
	// Currently only blocks with matching names are merged.
	if(!firstBlock || !secondBlock) return false;
	if(firstBlock == secondBlock) {
		return mozile.edit._normalize(state, fresh, firstNode, secondNode);
	}

	// Continue to merge if the elements continue to share the same name.
	var lastChild = firstBlock;
	var firstChild = secondBlock;
	var newState;
	while(lastChild.nodeType == mozile.dom.ELEMENT_NODE &&
		firstChild.nodeType == mozile.dom.ELEMENT_NODE &&
		lastChild.nodeName == firstChild.nodeName) {
		var from = firstChild;
		var to = lastChild;
		// Prepare for next iteration.
		lastChild = lastChild.lastChild;
		firstChild = firstChild.firstChild;
		mozile.edit.mergeNodes.request(state, fresh, from, to);
	}
	mozile.edit._normalize(state, fresh, lastChild, firstChild);

	if(lastChild == firstBlock) return false;
	else return true;
}

/**
 * Merge two text nodes.
 * This command operates by calling the mergeNodes command.
 * @private
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @param {Text} firstNode The first text node.
 * @param {Text} secondNode The second text node.
 * @type mozile.edit.State
 */
mozile.edit._normalize = function(state, fresh, firstNode, secondNode) {
	if(!firstNode || !firstNode.parentNode ||
		firstNode.nodeType != mozile.dom.TEXT_NODE) 
		return false;
	if(!secondNode || !secondNode.parentNode ||
		secondNode.nodeType != mozile.dom.TEXT_NODE) 
		return false;
	if(firstNode.nextSibling != secondNode) return false;

	return mozile.edit.mergeNodes.request(state, fresh, firstNode, secondNode);
}

/**
 * Remove a node if it is empty.
 * This command operates by calling the removeNode command.
 * @private
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @param {Node} target The node to check for removal.
 * @type Void
 */
mozile.edit._removeEmpty = function(state, fresh, target, limitNode) {
	var selection = mozile.dom.selection.get();
	if(!target || !target.parentNode) return null;
	var parent = target.parentNode;
	var parentBlock = mozile.edit.getParentBlock(target);

	if(typeof(state.direction) == "undefined")
		state.direction = mozile.edit.PREVIOUS;
	var IP = mozile.edit.getInsertionPoint(target, -1 * state.direction, true);
	if(!IP) return null;
	var result = IP.seekNode(-1 * state.direction, false);
	if(!result) IP.seekNode(state.direction, false); 
	if(!IP) return null;
	if( (target.nodeType == mozile.dom.TEXT_NODE &&
		target.data.length == 0) ||
		(target.nodeType == mozile.dom.ELEMENT_NODE &&
		target != mozile.edit.getContainer(target) &&
		target.childNodes.length == 0) ) {
		
		// Create an empty token.
		if(target == limitNode) {
			var content = mozile.edit.createEmptyToken();
			mozile.edit.insertNode.request(state, fresh, target, null, content);
			selection.collapse(content, 0);
			return null;
		}

		// Remove the node
		//alert("Removng node "+ mozile.xpath.getXPath(target));
		mozile.edit.removeNode.request(state, fresh, target);
		
		// Make sure the selection is good.
		if(state.direction == mozile.edit.PREVIOUS) {
			if(parentBlock == target ||
				parentBlock == mozile.edit.getParentBlock(IP.getNode())) {
				//alert(mozile.xpath.getXPath(parentBlock) +"\n"+ IP);
				//alert(mozile.xml.serialize(parentBlock));
				IP.select();
			}
			// If the IP is in a new block, get the last IP from this block instead.
			else {
				IP = mozile.edit.getInsertionPoint(parentBlock, state.direction);
				if(IP) IP.select();
				else mozile.debug.debug("mozile.edit._removeEmpty", "Nowhere to move the insertion point.");
			}
		}
		else IP.select();
		
		// Normalize remaining nodes.
		if(IP) {
			var firstNode, secondNode;
			if(state.direction == mozile.edit.PREVIOUS) {
				secondNode = IP.getNode();
				IP.seekNode(state.direction, false);
				firstNode = IP.getNode();
			}
			else {
				firstNode = IP.getNode();
				IP.seekNode(state.direction, false);
				secondNode = IP.getNode();
			}
			result = mozile.edit._normalize(state, fresh, firstNode, secondNode);
			//alert("Normalized "+ result);
		}
	}
	else {
		// Do nothing.
		return null;
	}

	if(target == limitNode) return null;
	else return mozile.edit._removeEmpty(state, fresh, parent, limitNode);
}

/**
 * Remove any empty token nodes from the target.
 * @private
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @param {Node} target The node to remove.
 * @type Void
 */
mozile.edit._removeEmptyTokens = function(state, fresh, target) {
	var node = target.firstChild;
	while(node) {	
		if(mozile.edit.isEmptyToken(node)) {
			var content = node;
			node = node.nextSibling;
			mozile.edit.removeNode.request(state, fresh, content);
		}
		else node = node.nextSibling;
	}
	return state;
}

/**
 * Checks to see if a node has no non-whitespace text, and if it has none an empty token is added.
 * @private
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @param {Node} target The element to check for emptiness.
 * @type Boolean
 */
mozile.edit._ensureNonEmpty = function(state, fresh, target) {
	if(!state || !target) return false;
	if(!target.nodeType) return false;

	if(!mozile.edit.isEmpty(target)) return true;

	switch(target.nodeType) {
		case mozile.dom.TEXT_NODE:
			mozile.edit.insertText.request(state, fresh, null, mozile.emptyToken, target);
			return true;

		case mozile.dom.ELEMENT_NODE:
			// If there's no RNG, or the RNG allows it, insert an emptyToken.
			var rng = mozile.edit.lookupRNG(target);
			if((rng && rng.mayContain("text")) || !rng) {
				// Try to use an existing text node.
				for(var i=0; i < target.childNodes.length; i++) {
					if(target.childNodes[i].nodeType == mozile.dom.TEXT_NODE) {
						return mozile.edit._ensureNonEmpty(state, fresh, target.childNodes[i]);
					}
				}
				// If that fails, insert a text node.
				var emptyToken = mozile.edit.createEmptyToken();
				mozile.edit.insertNode.request(state, fresh, 
					target, target.lastChild, emptyToken);
				return true;
			}
			// Otherwise try the child nodes.
			else if(target.firstChild) {
				var child = target.firstChild;
				var result;
				while(child) {
					result = mozile.edit._ensureNonEmpty(state, fresh, child);
					if(result) return true;
					child = child.nextSibling;
				}
			}
			return false;

		default: 
			return false;
	}
}

/**
 * Remove content from a range.
 * The method works as follows:
 * <ul>
 *   <li>Using a treeWalker, each node between the startContainer and endContianer is removed (unless it is an ancestor of one of the containers),
 *   <li>Selected text is removed from the startContainer,
 *   <li>Selected text is removed from the endContainer,
 *   <li>Remaining text nodes are normalized.
 * </ul>
 * This command operates by calling the removeText and removeNode commands.
 * @private
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @param {Integer} direction Optional. A code for the direction to delete in.
 * @type Object
 */
mozile.edit._removeRange = function(state, fresh, direction) {
	var selection = mozile.dom.selection.get();
	//if(!fresh) selection.restore(state.selection.before);
	var range = selection.getRangeAt(0);
	var container = range.commonAncestorContainer;
	if(!direction) direction = mozile.edit.PREVIOUS;
	
	// Get start and end nodes.
	var startNode = range.startContainer;
	if(startNode.nodeType == mozile.dom.ELEMENT_NODE)
		startNode = startNode.childNodes[range.startOffset];
	var endNode = range.endContainer;
	if(endNode.nodeType == mozile.dom.ELEMENT_NODE)
		endNode = endNode.childNodes[range.endOffset];

	// Remove all the nodes between the start and end node, but not the ancestors of the start and end nodes.
	var treeWalker = document.createTreeWalker(container, mozile.dom.NodeFilter.SHOW_ALL, null, false);
	treeWalker.currentNode = startNode;
	var current = treeWalker.nextNode();

	// Loop until the endNode is found (or we run out of nodes).
	while(current) {
		// Check to see if the node should NOT be removed.
		if(!current.parentNode) break;
		if(current == endNode) break;
		if(mozile.dom.isAncestorOf(current, startNode, container))
			current = treeWalker.nextNode();
		else if(mozile.dom.isAncestorOf(current, endNode, container))
			current = treeWalker.nextNode();

		// Remove the node.
		else {
			var target = current;
			current = treeWalker.nextSibling();
			if(!current) current = treeWalker.nextNode();
			mozile.edit.removeNode.request(state, fresh, target);
		}
	}

	// Take care of the startNode and endNode.
	var data;
	if(startNode.nodeType == mozile.dom.TEXT_NODE) {
		data = startNode.data.substring(0, range.startOffset);
		mozile.edit.insertText.request(state, fresh, null, data, startNode);
	}
	else mozile.edit.removeNode.request(state, fresh, startNode);

	if(endNode.nodeType == mozile.dom.TEXT_NODE) {
		data = endNode.data.substring(range.endOffset);
		mozile.edit.insertText.request(state, fresh, null, data, endNode);
	}

	// Set the cursor position.
	if(direction == mozile.edit.NEXT) {
		if(endNode && endNode.nodeType == mozile.dom.TEXT_NODE)
			selection.collapse(endNode, 0);
		else if(startNode && startNode.nodeType == mozile.dom.TEXT_NODE)
			selection.collapse(startNode, startNode.data.length);
		else mozile.debug.debug("mozile.edit._removeRange", "Nowhere to collapse.");
	}
	else {
		if(startNode && startNode.nodeType == mozile.dom.TEXT_NODE)
			selection.collapse(startNode, startNode.data.length);
		else if(endNode && endNode.nodeType == mozile.dom.TEXT_NODE)
			selection.collapse(endNode, 0);
		else mozile.debug.debug("mozile.edit._removeRange", "Nowhere to collapse.");
	}

	// Merge and normalize the start and end containers
	if(startNode && endNode && startNode.parentNode && endNode.parentNode) {
		var result = mozile.edit._mergeNodes(state, fresh, startNode, endNode);
		// Move to next block.
		if(!result && direction == mozile.edit.NEXT &&
			mozile.edit.getParentBlock(startNode) != mozile.edit.getParentBlock(endNode)) {
			selection.collapse(endNode, 0);
		}
	}

	return state;
}


/**
 * This method moves all of a target node's children out of that node, and then removes the node. Returns the last node that was in the wrapper.
 * @private
 * @param {mozile.edit.State} state The state information needed to execute this command.
 * @param {Boolean} fresh Optional. A value of "true" indicates that the window's selection is already in the correct place and does not need to be moved.
 * @param {Node} target The node to remove.
 * @param {Node} normalize Optional. When true text nodes are normalised. Defaults to true.
 * @type Node
 */
mozile.edit._unwrapNode = function(state, fresh, target, normalize) {
	var previousNode = target.previousSibling;
	var nextNode = target.nextSibling;
	var lastChild;

	// Move all children out of the target node.
	while(target.lastChild) {
		lastChild = target.lastChild;
		mozile.edit.moveNode.request(state, fresh, null, target, target.lastChild);
	}

	// Remove the target.
	mozile.edit.removeNode.request(state, fresh, target);

	// Normalize text nodes.
	if(normalize === undefined) normalize = true;
	if(normalize) {
		if(nextNode && nextNode.previousSibling) {
			mozile.edit._normalize(state, fresh, nextNode.previousSibling, nextNode);
		}
		if(previousNode && previousNode.nextSibling) {
			mozile.edit._normalize(state, fresh, previousNode, previousNode.nextSibling);
		}
	}

	return lastChild;
}


/**** Final Configuration ****/
// Enable rich text editing.
mozile.enableEditing(true);





Documentation generated by JSDoc on Wed Feb 20 13:25:28 2008