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
mozile.require("mozile.edit");
mozile.provide("mozile.edit.*");
mozile.edit.rich = true;
mozile.edit.insertNode = new mozile.edit.Command("insertNode");
mozile.edit.insertNode.test = function(event, parentNode, previousSibling, content) {
if(event) {
return false;
}
if(!parentNode && !previousSibling) return false;
if(!content) return false;
return true;
}
mozile.edit.insertNode.prepare = function(event, parentNode, previousSibling, content) {
var state = new mozile.edit.State(this, false);
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;
}
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};
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.");
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;
}
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;
}
mozile.edit.removeNode = new mozile.edit.Command("removeNode");
mozile.edit.removeNode.test = function(event, content) {
if(event) {
return false;
}
if(!content) return false;
if(!content.parentNode) return false;
return true;
}
mozile.edit.removeNode.prepare = function(event, content) {
var state = new mozile.edit.State(this, false);
state.content = null;
if(content) state.content = content;
else if(event) state.content = mozile.edit._getNode(event);
return state;
}
mozile.edit.removeNode.execute = function(state, fresh) {
var target = state.content;
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;
}
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;
}
mozile.edit.remove = new mozile.edit.Command("remove");
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;
if(node != IP.getNode() &&
!mozile.edit.getContainer(IP.getNode())) return false;
}
return true;
}
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;
}
mozile.edit.remove.execute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(!fresh) selection.restore(state.selection.before);
state.actions = new Array();
var node, newState, IP;
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();
}
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);
}
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;
}
mozile.edit.moveNode = new mozile.edit.Command("moveNode");
mozile.edit.moveNode.test = function(event, destinationParentNode, destinationPreviousSibling, target) {
if(event) {
return false;
}
if(!destinationParentNode && !destinationPreviousSibling) return false;
if(!target) return false;
return true;
}
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;
}
mozile.edit.moveNode.execute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(!fresh) selection.restore(state.selection.before);
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);
mozile.edit.removeNode.request(state, fresh, target);
mozile.edit.insertNode.request(state, fresh, parentNode, previousSibling, target);
selection.collapse(anchorNode, anchorOffset);
if(focusNode != anchorNode || focusOffset != anchorOffset) {
selection.extend(focusNode, focusOffset);
}
state.selection.after = selection.store();
state.executed = true;
return state;
}
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);
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;
}
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;
}
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;
}
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;
}
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);
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;
}
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;
}
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;
}
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;
}
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.target = "localname li";
mozile.edit.indentList.direction = "ancestor";
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;
}
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;
}
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;
}
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;
}
mozile.edit.mergeNodes = new mozile.edit.Command("mergeNodes");
mozile.edit.mergeNodes.test = function(event, from, to) {
if(event) {
return false;
}
if(!from) return false;
if(!to) return false;
return true;
}
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;
}
mozile.edit.mergeNodes.execute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(!fresh) selection.restore(state.selection.before);
state.actions = new Array();
var fromNode = mozile.xpath.getNode(state.from);
var toNode = mozile.xpath.getNode(state.to);
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;
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) {
var offset = firstNode.data.length;
if(anchorNode == secondNode) {
anchorNode = firstNode;
anchorOffset += offset;
}
if(focusNode == secondNode) {
focusNode = firstNode;
focusOffset += offset;
}
mozile.edit.insertText.request(state, fresh,
null, firstNode.data + secondNode.data, firstNode);
mozile.edit.removeNode.request(state, fresh, secondNode);
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);
}
firstNode = toNode.lastChild;
secondNode = fromNode.firstChild
while(fromNode.firstChild) {
mozile.edit.moveNode.request(state, fresh, toNode, toNode.lastChild, fromNode.firstChild);
}
mozile.edit.removeNode.request(state, fresh, fromNode);
var IP;
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();
}
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();
}
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;
}
mozile.edit.splitNode = new mozile.edit.Command("splitNode");
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;
}
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;
}
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;
var anchorNode = selection.anchorNode;
var anchorOffset = selection.anchorOffset;
var focusNode = selection.focusNode;
var focusOffset = selection.focusOffset;
if(target.nodeType == mozile.dom.TEXT_NODE && state.offset != undefined) {
state.splitNode = target;
oldContainer = target;
newContainer = target.splitText(state.offset);
if(anchorNode == target && anchorOffset >= state.offset) {
anchorNode = newContainer;
anchorOffset -= state.offset;
}
if(focusNode == target && focusOffset >= state.offset) {
focusNode = newContainer;
focusOffset -= state.offset;
}
}
else if(target.nodeType == mozile.dom.TEXT_NODE ||
target.nodeType == mozile.dom.ELEMENT_NODE) {
var i = 0;
if(state.after) i++;
var node = target;
while(node) {
i++;
node = node.previousSibling;
}
oldContainer = target.parentNode;
newContainer = oldContainer.cloneNode(false);
mozile.edit.insertNode.request(state, fresh,
null, oldContainer, newContainer);
var newContainerPath = mozile.xpath.getXPath(newContainer);
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);
}
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;
}
mozile.edit.splitNode.unexecute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(!fresh) selection.restore(state.selection.after);
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 +".");
}
if(state.splitNode) {
state.splitNode.appendData(state.newContainer.data);
if(state.newContainer.parentNode) {
state.newContainer.parentNode.removeChild(state.newContainer);
}
}
selection.restore(state.selection.before);
state.executed = false;
return state;
}
mozile.edit.splitNodes = new mozile.edit.Command("splitNodes");
mozile.edit.splitNodes.test = function(event, target, offset, limitNode, shallow) {
if(event) {
return false;
}
return true;
}
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;
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;
}
mozile.edit.splitNodes.execute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(!fresh) selection.restore(state.selection.before);
state.actions = new Array();
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;
while(node) {
if(node.nodeType == mozile.dom.ELEMENT_NODE &&
mozile.edit.isChildless(node) && node.nextSibling) {
node = node.nextSibling;
}
else break;
}
if(offset == null || offset == 0) {
offset = null;
while(node) {
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;
}
}
else if(node.data && offset == node.data.length) {
offset = null;
if(node.nextSibling) node = node.nextSibling;
while(node) {
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;
}
if(node == node.parentNode.lastChild) after = true;
}
while(node) {
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";
mozile.edit.removeFormatting.isAvailable = function(event) {
if(!event || !event.editable) return false;
return true;
}
mozile.edit.removeFormatting.test = function(event) {
if(event) {
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);
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;
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 {
var container = range.commonAncestorContainer;
var node, offset, startNode, endNode, previousNode, nextNode;
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;
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;
var treeWalker = document.createTreeWalker(container, mozile.dom.NodeFilter.SHOW_ALL, null, false);
treeWalker.currentNode = startNode;
var current = treeWalker.currentNode;
var last = previousNode;
while(current) {
if(current == nextNode) break;
if(current.nodeType == mozile.dom.ELEMENT_NODE &&
!mozile.edit.isBlock(current)) {
treeWalker.currentNode = last;
mozile.edit._unwrapNode(state, fresh, current, false);
current = treeWalker.nextNode();
}
else {
last = current;
current = treeWalker.nextNode();
}
}
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();
}
}
}
var treeWalker = document.createTreeWalker(container, mozile.dom.NodeFilter.SHOW_ALL, null, false);
treeWalker.currentNode = previousNode;
var current = treeWalker.currentNode;
var result;
while(current) {
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;
}
mozile.edit.Split = function(name, localization) {
this.name = name;
this.group = false;
this.remove = true;
this.makesChanges = "node";
this.watchesChanges = "node";
this.target = "block";
this.direction = "ancestor";
this.localize(localization);
mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Split.prototype = new mozile.edit.Command;
mozile.edit.Split.prototype.constructor = mozile.edit.Split;
mozile.edit.Split.prototype.isAvailable = function(event) {
if(!event || !event.editable) return false;
return true;
}
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;
}
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);
if(this.prompt) {
if(!this.prompt(event, state)) return null;
}
return state;
}
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();
state.newContainer = newState.newContainer;
state.oldContainer = newState.oldContainer;
state.selection.after = selection.store();
state.executed = true;
return state;
}
mozile.edit.Insert = function(name, localization) {
this.name = name;
this.group = false;
this.remove = true;
this.makesChanges = "node";
this.watchesChanges = "node";
this.localize(localization);
mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Insert.prototype = new mozile.edit.Command;
mozile.edit.Insert.prototype.constructor = mozile.edit.Insert;
mozile.edit.Insert.prototype.isAvailable = function(event) {
if(!event || !event.editable) return false;
return true;
}
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;
}
if(this.prompt) {
if(!this.prompt(event, state)) return null;
}
return state;
}
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;
if(this.remove && !selection.isCollapsed) {
mozile.edit.remove.request(state, fresh, mozile.edit.NEXT);
selection = mozile.dom.selection.get();
}
if(state.text) {
mozile.edit.insertText.request(state, fresh, null, state.text);
state.selection.after = selection.store();
state.executed = true;
return state;
}
if(selection.isCollapsed) {
if(selection.focusNode.nodeType == mozile.dom.TEXT_NODE) {
if(selection.focusOffset == 0) {
previousNode = selection.focusNode.previousSibling;
}
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);
}
}
else {
var range = selection.getRangeAt(0);
var container = range.commonAncestorContainer;
if(container.nodeType == mozile.dom.TEXT_NODE) container = container.parentNode;
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;
mozile.edit.insertNode.request(state, fresh,
null, previousNode, state.element);
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);
}
}
if(text) selection.collapse(text, 0);
else {
var IP = mozile.edit.getInsertionPoint(state.element, mozile.edit.NEXT);
if(IP) {
if(this.remove) {
IP.seekNode(mozile.edit.NEXT, false);
if(IP) IP.select();
}
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;
}
mozile.edit.insert = new mozile.edit.Insert("insert");
mozile.edit.Wrap = function(name, localization) {
this.name = name;
this.group = false;
this.makesChanges = "node";
this.watchesChanges = "node";
this.nested = false;
this.localize(localization);
mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Wrap.prototype = new mozile.edit.Command;
mozile.edit.Wrap.prototype.constructor = mozile.edit.Wrap;
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;
}
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;
}
mozile.edit.Wrap.prototype.isAvailable = function(event) {
if(!event || !event.editable) return false;
return true;
}
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;
}
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);
}
if(this.prompt) {
if(!this.prompt(event, state)) return null;
}
return state;
}
mozile.edit.Wrap.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 wrapper = state.wrapper;
state.wrappers = new Array();
var nextNode, previousNode, textNode, newState, IP;
if(range.collapsed) {
var outerWrapper = this._getWrapper(range.commonAncestorContainer, true);
if(outerWrapper && !this.nested) {
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();
mozile.edit._removeEmpty(state, fresh,
outerWrapper.lastChild, outerWrapper.parentNode);
}
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);
}
}
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);
}
}
else {
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];
}
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;
var node, offset, startNode, endNode;
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;
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;
var treeWalker = document.createTreeWalker(container, mozile.dom.NodeFilter.SHOW_ALL, null, false);
var allNodesWrapped = false;
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) {
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();
}
}
}
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) {
if(current == nextNode) break;
if(mozile.dom.isAncestorOf(wrapper, current, container)) break;
if(mozile.dom.isAncestorOf(current, wrapper, container))
current = treeWalker.nextNode();
else if(mozile.dom.isAncestorOf(current, nextNode, container))
current = treeWalker.nextNode();
else if(mozile.edit.isBlock(current))
current = treeWalker.nextNode();
else if(current.nodeType == mozile.dom.TEXT_NODE &&
!mozile.edit.mayContainText(current))
current = treeWalker.nextNode();
else {
target = current;
current = treeWalker.nextSibling();
if(!current) current = treeWalker.nextNode();
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;
}
mozile.edit.moveNode.request(state, fresh, wrapper, wrapper.lastChild, target);
}
}
}
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();
}
}
}
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;
}
mozile.edit.Unwrap = function(name, localization) {
this.name = name;
this.group = false;
this.makesChanges = "node";
this.watchesChanges = "node";
this.target = "element";
this.direction = "ancestor";
this.localize(localization);
mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Unwrap.prototype = new mozile.edit.Command;
mozile.edit.Unwrap.prototype.constructor = mozile.edit.Unwrap;
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;
}
mozile.edit.Unwrap.prototype.test = function(event, targetNode) {
if(event) {
if(this.accel) {
if(mozile.edit.checkAccelerators(event, this.accels)) { }
if(mozile.edit.checkAccelerator(event, this.accel)) { }
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;
}
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);
if(this.prompt) {
if(!this.prompt(event, state)) return null;
}
return state;
}
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.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;
};
mozile.edit.Replace = function(name, localization) {
this.name = name;
this.group = false;
this.makesChanges = "node";
this.watchesChanges = "node";
this.target = "element";
this.direction = "ancestor";
this.copyAttributes = true;
this.localize(localization);
mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Replace.prototype = new mozile.edit.Command;
mozile.edit.Replace.prototype.constructor = mozile.edit.Replace;
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;
}
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;
}
mozile.edit.Replace.prototype.test = function(event) {
if(event) {
if(this.accel) {
if(mozile.edit.checkAccelerators(event, this.accels)) { }
if(mozile.edit.checkAccelerator(event, this.accel)) { }
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;
}
mozile.edit.Replace.prototype.prepare = function(event) {
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.cloneNode) {
state.element = this.element.cloneNode(true);
}
var target = mozile.edit._getTarget(event, this.target, this.direction);
state.target = state.storeNode(target);
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];
}
}
}
if(this.prompt) {
if(!this.prompt(event, state)) return null;
}
return state;
}
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;
}
mozile.edit.SetAttribute = function(name, localization) {
this.name = name;
this.group = false;
this.makesChanges = "node";
this.watchesChanges = "node";
this.target = "element";
this.direction = "ancestor";
this.attributeName = null;
this.attributeValue = null;
this.localize(localization);
mozile.edit.allCommands[this.name] = this;
}
mozile.edit.SetAttribute.