rich.js
Summary
Tools for rich editing operations on mark-up.
Version: 0.8
$Id: overview-summary-rich.js.html,v 1.7 2006/08/23 23:30:17 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 true;
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 newState, IP;
var collapsed = selection.isCollapsed;
if(selection.isCollapsed) {
var node = selection.focusNode;
if(node && node.nodeType == mozile.dom.TEXT_NODE &&
!mozile.edit.isEmptyToken(node) ) {
if( (state.direction == mozile.edit.PREVIOUS &&
selection.focusOffset > 0) ||
(state.direction == mozile.edit.NEXT &&
selection.focusOffset < node.data.length) ) {
mozile.edit.removeText.request(state, fresh, state.direction);
}
else {
IP = selection.getInsertionPoint();
IP.seek(state.direction);
IP.extend();
}
}
else {
IP = selection.getInsertionPoint();
IP.seek(state.direction);
IP.extend();
}
}
if(!selection.isCollapsed) {
if(selection.getRangeAt(0).commonAncestorContainer.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.");
mozile.edit.removeNode.request(state, fresh, target);
mozile.edit.insertNode.request(state, fresh,
state.destination.parentNode, state.destination.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.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(document.documentElement, anchorNode)) {
selection.collapse(anchorNode, anchorOffset);
}
else {
IP = mozile.edit.getInsertionPoint(toNode, mozile.edit.NEXT);
if(IP) IP.select();
}
if(mozile.dom.isAncestorOf(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(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);
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;
}
var oldContainer = target.parentNode;
var 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;
var limitNode = mozile.xpath.getNode(state.limitNode);
var after = false;
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.Split = function(name) {
this.name = name;
this.group = false;
this.remove = true;
this.makesChanges = "node";
this.watchesChanges = "node";
this.target = "block";
this.direction = "ancestor";
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.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) {
this.name = name;
this.group = false;
this.remove = true;
this.makesChanges = "node";
this.watchesChanges = "node";
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.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);
}
else if(this.element && this.element.cloneNode) {
state.element = this.element.cloneNode(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;
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) {
var previousNode;
if(selection.focusNode.nodeType == mozile.dom.TEXT_NODE) {
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) {
var text;
if(mozile.edit.isBlock(state.element))
text = mozile.edit.createEmptyToken();
else text = 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);
var 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);
}
}
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.Wrap = function(name) {
this.name = name;
this.group = false;
this.makesChanges = "node";
this.watchesChanges = "node";
this.nested = false;
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.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.isActive = function(event) {
if(this.prompt) return false;
if(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.styleName) {
mozile.dom.setStyle(state.wrapper, this.styleName, this.styleValue);
}
}
else if(this.element && this.element.cloneNode) {
state.wrapper = this.element.cloneNode(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;
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) {
var IP = mozile.edit.getInsertionPoint(outerWrapper, mozile.edit.PREVIOUS);
IP.seekNode(mozile.edit.NEXT, false);
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);
nextNode = newState.newContainer;
textNode = document.createTextNode("");
mozile.edit.insertNode.request(state, fresh,
nextNode.parentNode, nextNode.previousSibling, textNode);
selection.collapse(textNode, 0);
}
}
else {
if(range.startContainer.nodeType == mozile.dom.TEXT_NODE) {
newState = mozile.edit.splitNodes.request(state, fresh, range.startContainer, range.startOffset, range.startContainer.parentNode);
nextNode = newState.newContainer;
}
else nextNode = range.startContainer.childNodes[range.startOffset];
mozile.edit.insertNode.request(state, fresh,
nextNode.parentNode, nextNode.previousSibling, wrapper);
wrapper.appendChild(document.createTextNode(""));
selection.collapse(wrapper.firstChild, 0);
}
}
else {
var container = range.commonAncestorContainer;
var startWrapper, endWrapper;
if(!this.nested) {
startWrapper = this._getWrapper(range.startContainer, true);
endWrapper = this._getWrapper(range.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;
}
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 {
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) {
var IP = mozile.edit.getInsertionPoint(previousNode, mozile.edit.PREVIOUS);
if(IP) {
IP.seekNode(mozile.edit.NEXT, false);
IP.select();
IP = mozile.edit.getInsertionPoint(nextNode, mozile.edit.NEXT);
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(this._isWrapper(nextNode) &&
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) {
this.name = name;
this.group = false;
this.makesChanges = "node";
this.watchesChanges = "node";
this.target = "element";
this.direction = "ancestor";
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) {
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.Replace = function(name) {
this.name = name;
this.group = false;
this.makesChanges = "node";
this.watchesChanges = "node";
this.target = "element";
this.direction = "ancestor";
this.copyAttributes = true;
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) {
var target = mozile.edit._getTarget(event, this.target, this.direction);
if(target) return true;
return false;
}
mozile.edit.Replace.prototype.isActive = function(event) {
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) 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);
}
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 start.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.Style = function(name) {
this.name = name;
this.group = false;
this.makesChanges = "node";
this.watchesChanges = "node";
this.target = "element";
this.direction = "ancestor";
this.styleName = null;
this.styleValue = null;
mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Style.prototype = new mozile.edit.Command;
mozile.edit.Style.prototype.constructor = mozile.edit.Style;
mozile.edit.Style.prototype.isAvailable = function(event) {
var target = mozile.edit._getTarget(event, this.target, this.direction);
if(target) return true;
return false;
}
mozile.edit.Style.prototype.isActive = function(event) {
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;
}
mozile.edit.Style.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.styleName) return false;
if(!this.styleValue) 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;
var state = {target: node};
state.styleName = this.styleName.replace(/\-(\w)/g, function (strMatch, p1){
return p1.toUpperCase();
});
if(typeof(this.styleValue) == "function") {
var result = this.styleValue(event, state);
if(result === null) return false;
}
return true;
}
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.target = state.storeNode(target);
state.styleName = this.styleName.replace(/\-(\w)/g, function (strMatch, p1){
return p1.toUpperCase();
});
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;
if(this.prompt) {
if(!this.prompt(event, state)) return null;
}
return state;
}
mozile.edit.Style.prototype.execute = function(state, fresh) {
var target = mozile.xpath.getNode(state.target);
state.targetNode = target;
state.oldValue = target.style[state.styleName];
target.style[state.styleName] = state.styleValue;
state.executed = true;
return state;
}
mozile.edit.Style.prototype.unexecute = function(state, fresh) {
state.targetNode.style[state.styleName] = state.oldValue;
state.executed = false;
return state;
}
mozile.edit._mergeNodes = function(state, fresh, firstNode, secondNode) {
var firstBlock = mozile.edit.getParentBlock(firstNode);
var secondBlock = mozile.edit.getParentBlock(secondNode);
if(!firstBlock || !secondBlock) return false;
if(firstBlock == secondBlock) {
return mozile.edit._normalize(state, fresh, firstNode, secondNode);
}
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;
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;
}
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);
}
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);
if(!IP) return null;
var result = IP.seekNode(-1 * state.direction, false);
if(!result) IP.seekNode(state.direction, false);
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) ) {
if(target == limitNode) {
var content = mozile.edit.createEmptyToken();
mozile.edit.insertNode.request(state, fresh, target, null, content);
selection.collapse(content, 0);
return null;
}
mozile.edit.removeNode.request(state, fresh, target);
if(state.direction == mozile.edit.PREVIOUS) {
if(parentBlock == target ||
parentBlock == mozile.edit.getParentBlock(IP.getNode())) {
IP.select();
}
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();
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();
}
var result = mozile.edit._normalize(state, fresh, firstNode, secondNode);
}
else {
return null;
}
if(target == limitNode) return null;
else return mozile.edit._removeEmpty(state, fresh, parent, limitNode);
}
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;
}
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:
var rng = mozile.edit.lookupRNG(target);
if((rng && rng.mayContain("text")) || !rng) {
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]);
}
}
var emptyToken = mozile.edit.createEmptyToken();
mozile.edit.insertNode.request(state, fresh,
target, target.lastChild, emptyToken);
return true;
}
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;
}
}
mozile.edit._removeRange = function(state, fresh, direction) {
var selection = mozile.dom.selection.get();
var range = selection.getRangeAt(0);
var container = range.commonAncestorContainer;
if(!direction) direction = mozile.edit.PREVIOUS;
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];
var treeWalker = document.createTreeWalker(container, mozile.dom.NodeFilter.SHOW_ALL, null, false);
treeWalker.currentNode = startNode;
var current = treeWalker.nextNode();
while(current) {
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();
else {
var target = current;
current = treeWalker.nextSibling();
if(!current) current = treeWalker.nextNode();
mozile.edit.removeNode.request(state, fresh, target);
}
}
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);
}
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.");
}
if(startNode && endNode && startNode.parentNode && endNode.parentNode) {
var result = mozile.edit._mergeNodes(state, fresh, startNode, endNode);
if(!result && direction == mozile.edit.NEXT &&
mozile.edit.getParentBlock(startNode) != mozile.edit.getParentBlock(endNode)) {
selection.collapse(endNode, 0);
}
}
return state;
}
mozile.edit._unwrapNode = function(state, fresh, target) {
var previousNode = target.previousSibling;
var nextNode = target.nextSibling;
var lastChild;
while(target.lastChild) {
lastChild = target.lastChild;
mozile.edit.moveNode.request(state, fresh, null, target, target.lastChild);
}
mozile.edit.removeNode.request(state, fresh, target);
mozile.edit._normalize(state, fresh, nextNode.previousSibling, nextNode);
mozile.edit._normalize(state, fresh, previousNode, previousNode.nextSibling);
return lastChild;
}
Documentation generated by
JSDoc on Wed Aug 23 18:45:51 2006