edit.js
Summary
Tools for basic text editing operations.
Version: 0.8
$Id: overview-summary-edit.js.html,v 1.9 2006/08/23 23:30:17 jameso Exp $
Author: James A. Overton
mozile.require("mozile.dom");
mozile.require("mozile.xml");
mozile.provide("mozile.edit.*");
mozile.edit = new Object();
mozile.edit.prototype = new mozile.Module;
mozile.edit.editable = true;
mozile.edit.status = false;
mozile.edit.NEXT = 1;
mozile.edit.PREVIOUS = -1;
mozile.edit.allCommands = new Object();
mozile.edit.keyCodes = {
8: "Backspace",
9: "Tab",
12: "Clear",
13: "Return",
14: "Enter",
19: "Pause",
27: "Escape",
32: "Space",
33: "Page-Up",
34: "Page-Down",
35: "End",
36: "Home",
37: "Left",
38: "Up",
39: "Right",
40: "Down",
45: "Insert",
46: "Delete",
112: "F1",
113: "F2",
114: "F3",
115: "F4",
116: "F5",
117: "F6",
118: "F7",
119: "F8",
121: "F9",
122: "F10",
123: "F11",
123: "F12"
}
mozile.edit.getContainer = function(element) {
if(!element || !element.nodeType) return null;
if(element.nodeType != mozile.dom.ELEMENT_NODE) element = element.parentNode;
if(!element || !element.nodeType) return null;
var doc = element.ownerDocument;
while(element && element.nodeType &&
element.nodeType == mozile.dom.ELEMENT_NODE) {
if(mozile.edit.isEditableElement(element)) return element;
switch(element.getAttribute("contentEditable")) {
case "true":
mozile.editElement(element);
return element;
case "false":
return null;
}
element = element.parentNode;
}
return null;
}
mozile.edit.isEditable = function(node) {
if(!node) return false;
var container = mozile.edit.getContainer(node)
if(container && container != node) return true;
else return false;
}
mozile.edit.isEditableElement = function(element) {
if(element && mozile.edit.getMark(element, "editable")) return true;
return false;
}
mozile.edit.setStatus = function(status) {
status = Boolean(status);
if(mozile.edit.status != status) {
mozile.edit.status = status;
if(mozile.useDesignMode == true &&
typeof(document.documentElement.contentEditable) == "undefined") {
document.designMode = (status) ? "on" : "off";
}
}
return mozile.edit.status;
}
mozile.edit.enable = function() {
mozile.edit.editable = true;
return mozile.edit.editable;
}
mozile.edit.disable = function() {
mozile.edit.editable = false;
return mozile.edit.editable;
}
mozile.edit.start = function() {
return mozile.edit.setStatus(true);
}
mozile.edit.stop = function() {
return mozile.edit.setStatus(false);
}
mozile.edit.setMark = function(element, key, value) {
if(!element || element.nodeType == undefined) return null;
if(element.nodeType != mozile.dom.ELEMENT_NODE) return null;
if(!key || typeof(key) != "string") return null;
try {
if(element.mozile == undefined || typeof(element.mozile) != "object")
element.mozile = new Object();
element.mozile[key] = value;
return value;
} catch(e) {
return null;
}
}
mozile.edit.getMark = function(element, key) {
if(!element || element.nodeType == undefined) return undefined;
if(element.nodeType != mozile.dom.ELEMENT_NODE) return undefined;
if(!key || typeof(key) != "string") return undefined;
if(element.mozile == undefined || !element.mozile) return undefined;
if(element.mozile[key] == undefined) return undefined;
return element.mozile[key];
}
mozile.edit.lookupRNG = function(node) {
if(!node) return null;
var element = node;
if(node.nodeType != mozile.dom.ELEMENT_NODE) element = node.parentNode;
if(!mozile.schema) return null;
var name = mozile.dom.getLocalName(element);
if(name && mozile.dom.isHTML(node)) name = name.toLowerCase();
var matches = mozile.schema.getNodes("element", name);
if(matches.length > 0) return matches[0];
else return null;
}
mozile.edit.parseMES = function(container, node) {
if(node.nodeType != mozile.dom.ELEMENT_NODE) return;
var command, define;
for(var i=0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
switch(mozile.dom.getNamespaceURI(child)) {
case mozile.xml.ns.mes:
switch(mozile.dom.getLocalName(child)) {
case "ref":
define = mozile.edit.followMESRef(child);
if(define) mozile.edit.parseMES(container, define);
break;
case "command":
command = mozile.edit.generateCommand(child);
if(command) container.addCommand(command);
break;
case "group":
command = mozile.edit.generateCommand(child);
if(command) {
container.addCommand(command);
if(command._commands.length == 0)
mozile.edit.parseMES(command, child);
}
break;
}
break;
case mozile.xml.ns.rng:
if(child.nodeName == "ref") {
var name = child.getAttribute("name");
if(!name) continue;
define = mozile.schema._root.getDefinition(name);
mozile.edit.parseMES(container, define._element);
}
break;
}
}
}
mozile.edit.followMESRef = function(element) {
var define = mozile.edit.getMark(element, "define");
if(define && define.nodeType && define.nodeType == mozile.dom.ELEMENT_NODE)
return define;
var name = element.getAttribute("name");
if(!name) return null;
var doc = element.ownerDocument.documentElement;
define = null;
var child;
for(var i=0; i < doc.childNodes.length; i++) {
child = doc.childNodes[i];
if(mozile.dom.getNamespaceURI(child) == mozile.xml.ns.mes &&
mozile.dom.getLocalName(child) == "define" &&
child.getAttribute("name") == name) {
define = child;
break;
}
}
if(define) {
mozile.edit.setMark(element, "define", define);
return define;
}
else return null;
}
mozile.edit.generateCommands = function(schema) {
var elements = schema.getNodes("element");
for(var i=0; i < elements.length; i++) {
elements[i].addCommand(mozile.edit.navigateLeftRight);
if(elements[i].mayContain("text")) {
elements[i].addCommand(mozile.edit.insertText);
elements[i].addCommand(mozile.edit.removeText);
}
if(mozile.edit.remove) elements[i].addCommand(mozile.edit.remove);
mozile.edit.parseMES(elements[i], elements[i]._element);
}
}
mozile.edit.generateCommand = function(node) {
var name = node.getAttribute("name");
if(!name) return null;
if(mozile.edit.allCommands[name]) return mozile.edit.allCommands[name];
var command;
if(mozile.dom.getLocalName(node) == "command") {
var className = node.getAttribute("class");
if(className && mozile.edit[className]) {
eval("command = new mozile.edit."+ className +"(name)");
}
else command = new mozile.edit.Command(name);
var child = node.firstChild;
while(child) {
if(child.nodeType == mozile.dom.ELEMENT_NODE) {
switch(mozile.dom.getLocalName(child)) {
case "element":
var element = mozile.dom.getFirstChildElement(child);
if(element) command.element = element;
break;
case "script":
command.script = child;
break;
}
}
child = child.nextSibling;
}
}
else if(mozile.dom.getLocalName(node) == "group") {
command = new mozile.edit.CommandGroup(name);
}
else return null;
command.node = node;
var properties = ["priority", "label", "image", "tooltip", "accel", "makesChanges", "watchesChanges", "element", "text", "remove", "nested", "direction", "target", "collapse", "copyAttributes", "styleName", "styleValue"];
for(var i=0; i < properties.length; i++) {
var property = properties[i];
if(node.getAttribute(property)) {
var value = node.getAttribute(property);
if(value.toLowerCase() == "true") value = true;
else if(value.toLowerCase() == "false") value = false;
command[property] = value;
}
}
if(command.accel) {
command.accels = mozile.edit.splitAccelerators(command.accel);
}
if(command.target && !command.direction) {
command.direction = null;
}
if(command.script) {
child = command.script.firstChild;
while(child) {
if(child.nodeType == mozile.dom.TEXT_NODE ||
child.nodeType == mozile.dom.CDATA_SECTION_NODE) {
command.evaluate(child.data);
}
child = child.nextSibling;
}
}
return command;
}
mozile.edit.checkAccelerators = function(event, accelerators) {
if(!event) return false;
if(typeof(accelerators) != "object" || !accelerators.length) return false;
for(var i=0; i < accelerators.length; i++) {
if(mozile.edit.checkAccelerator(event, accelerators[i])) return true;
}
return false;
}
mozile.edit.checkAccelerator = function(event, accelerator) {
if(!event) return false;
if(typeof(accelerator) != "string") return false;
if(mozile.browser.isIE) {
if(event.type != "keydown") {
if(event.type != "keypress") return false;
if(event.keyCode && !mozile.edit.keyCodes[event.keyCode]) return false;
}
}
else if(event.type != "keypress") return false;
if(event.accel == undefined) event.accel = mozile.edit.generateAccelerator(event);
if(event.accel.toLowerCase() == accelerator.toLowerCase()) return true;
else return false;
}
mozile.edit.generateAccelerator = function(event) {
if(!event) return "";
var accel = "";
if(event.metaKey) accel = accel + "Meta-";
if(event.ctrlKey) accel = accel + "Control-";
if(event.altKey) accel = accel + "Alt-";
if(event.shiftKey) accel = accel + "Shift-";
if(event.keyCode && mozile.edit.convertKeyCode(event.keyCode)) {
accel = accel + mozile.edit.convertKeyCode(event.keyCode);
}
else if(event.charCode == 32) accel = accel + "Space";
else accel = accel + String.fromCharCode(event.charCode).toUpperCase();
var command = "Control";
if(mozile.os.isMac) command = "Meta";
accel = accel.replace(command, "Command");
return accel;
}
mozile.edit.splitAccelerators = function(accelerators) {
var accels = new Array();
var split = accelerators.split(/\s/);
var accel;
for(var i=0; i < split.length; i++) {
accel = split[i];
accel = accel.replace(/\s+/g, "");
if(accel) accels.push(accel);
}
return accels;
}
mozile.edit.parseAccelerator = function(accelerator) {
accelerator = accelerator.replace(/\s.*/, "");
var accel = {
command: false,
meta: false,
ctrl: false,
alt: false,
shift: false,
charCode: 0,
character: "",
abbr: ""
}
if(accelerator.indexOf("Command") > -1) accel.command = true;
if(accelerator.indexOf("Meta") > -1) accel.meta = true;
if(accelerator.indexOf("Control") > -1) accel.ctrl = true;
if(accelerator.indexOf("Alt") > -1) accel.alt = true;
if(accelerator.indexOf("Shift") > -1) accel.shift = true;
accel.character = accelerator.substring(accelerator.lastIndexOf("-")+1);
if(mozile.os.isMac) {
if(accel.ctrl) accel.abbr += "\u2303";
if(accel.alt) accel.abbr += "\u2325";
if(accel.shift) accel.abbr += "\u21E7";
if(accel.command) accel.abbr += "\u2318";
accel.abbr += accel.character;
}
else {
if(accel.command) accel.abbr += "Ctrl+";
if(accel.alt) accel.abbr += "Alt+";
if(accel.shift) accel.abbr += "Shift+";
accel.abbr += accel.character;
}
return accel;
}
mozile.edit.convertKeyCode = function(keyCode) {
if(mozile.edit.keyCodes[keyCode]) return mozile.edit.keyCodes[keyCode];
else return null;
}
mozile.edit.addCommand = function(command) {
if(!command) return command;
if(!this._commands) this._commands = new Array();
if(!this._priority) this._priority = new Array();
this._commands.push(command);
this._priority.push(command);
this._priority.sort(mozile.edit.compareCommands);
return command;
}
mozile.edit.compareCommands = function(command1, command2) {
if(command1.priority == undefined || Number(command1.priority) == NaN)
command1.priority = 0;
if(command2.priority == undefined || Number(command2.priority) == NaN)
command2.priority = 0;
return command2.priority - command1.priority;
}
mozile.edit.getCommand = function(name) {
if(mozile.edit.allCommands[name]) return mozile.edit.allCommands[name];
else return null;
}
mozile.edit.handleEvent = function(event) {
if(!this._priority) return null;
var state;
for(var i=0; i < this._priority.length; i++) {
state = this._priority[i].trigger(event);
if(state) return state;
}
return null;
}
mozile.edit.addDefaultCommand = function(command) {
if(!mozile.edit._defaultCommands) mozile.edit._defaultCommands = new Array();
mozile.edit._defaultCommands.push(command)
return command;
}
mozile.edit.handleDefault = function(event) {
if(!mozile.edit._defaultCommands) return null;
var state;
for(var i=0; i < mozile.edit._defaultCommands.length; i++) {
state = mozile.edit._defaultCommands[i].trigger(event);
if(state) return state;
}
return null;
}
mozile.edit.extendRNG = function() {
mozile.rng.Element.prototype.create = function(parent) {
var node = mozile.dom.createElement(this.getName());
if(parent) {
parent.appendChild(node);
return parent;
}
else return node;
}
mozile.rng.Element.prototype.addCommand = mozile.edit.addCommand;
mozile.rng.Element.prototype.handleEvent = mozile.edit.handleEvent;
}
if(mozile.rng) mozile.edit.extendRNG();
mozile.edit.State = function(command, selection) {
this.command = command;
this.selection = null;
if(selection !== false) {
if(!selection) selection = mozile.dom.selection.get();
this.selection = { before: selection.store() };
}
this.reversible = true;
this.cancel = true;
this.changesMade = command.makesChanges;
this.executed = false;
}
mozile.edit.State.prototype.toString = function() {
return "[object mozile.edit.State]";
}
mozile.edit.State.prototype.storeNode = function(input) {
if(!input) return null;
if(typeof(input) == "string") {
if(input.indexOf("/") != 0) return null;
return input;
}
else {
var xpath = mozile.xpath.getXPath(input);
if(xpath) return xpath;
else return null;
}
return null;
}
mozile.edit.Command = function(name) {
this.name = name;
this.group = false;
this.makesChanges = "node";
this.watchesChanges = "node";
mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Command.prototype.toString = function() {
return "[object mozile.edit.Command '"+ this.name +"']";
}
mozile.edit.Command.prototype.evaluate = function(code) {
eval(code);
}
mozile.edit.Command.prototype.respond = function(change) {
if(!change || typeof(change) != "string") return false;
if(change == "none") return false;
switch(this.watchesChanges) {
case "none": return false;
case "state": if(change == "state") return true;
case "text": if(change == "text") return true;
case "node": if(change == "node") return true;
}
return false;
}
mozile.edit.Command.prototype.isAvailable = function(event) {
return true;
}
mozile.edit.Command.prototype.isActive = function(event) {
return false;
}
mozile.edit.Command.prototype.test = function(event) {
if(event) {
if(this.accels) return mozile.edit.checkAccelerators(event, this.accels);
else if(this.accel) {
this.accels = mozile.edit.splitAccelerators(this.accel);
return mozile.edit.checkAccelerator(event, this.accel);
}
else return false;
}
return true;
}
mozile.edit.Command.prototype.prepare = function(event) {
var state = new mozile.edit.State(this);
if(this.prompt) {
if(!this.prompt(event, state)) return null;
}
return state;
}
mozile.edit.Command.prototype.trigger = function(event) {
if(this.test(event)) {
return this.execute(this.prepare(event), true);
}
return null;
}
mozile.edit.Command.prototype.request = function(state, fresh, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) {
var test = this.test(null, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
if(!test) return null;
var newState = this.prepare(null, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
if(!newState) return null;
newState = this.execute(newState, fresh);
if(!newState.executed) return null;
if(!state.actions) state.actions = new Array();
state.actions.push(newState);
return newState;
}
mozile.edit.Command.prototype.execute = function(state, fresh) {
mozile.debug.inform("mozile.edit.Command.execute", "Command '"+ this.name +"' executed with state "+ state);
state.executed = true;
return state;
}
mozile.edit.Command.prototype.unexecute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(!fresh) selection.restore(state.selection.after);
if(state.actions) {
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) mozile.debug.inform(this.name +".unexecute", "Child command "+ i +" failed to unexecute.");
}
}
selection.restore(state.selection.before);
state.executed = false;
return state;
}
mozile.edit.CommandGroup = function(name) {
this.name = name;
this.group = true;
this.makesChanges = "none";
this.watchesChanges = "none";
this._commands = new Array();
mozile.edit.allCommands[this.name] = this;
}
mozile.edit.CommandGroup.prototype = new mozile.edit.Command;
mozile.edit.CommandGroup.prototype.constructor = mozile.edit.CommandGroup;
mozile.edit.CommandGroup.prototype.toString = function() {
return "[object mozile.edit.CommandGroup '"+ this.name +"']";
}
mozile.edit.CommandGroup.prototype.addCommand = mozile.edit.addCommand;
mozile.edit.CommandGroup.prototype.trigger = mozile.edit.handleEvent;
mozile.edit._undoStack = new Array();
mozile.edit._undoIndex = -1;
mozile.edit.currentState = null;
mozile.edit.dumpUndoStack = function() {
var entries = new Array("Undo Stack [ "+ mozile.edit._undoIndex +" / "+ mozile.edit._undoStack.length +" ]");
for(var i=0; i < mozile.edit._undoStack.length; i++) {
var picked = " ";
if(i == mozile.edit._undoIndex) picked = "> "
entries.push(picked + i +". "+ mozile.edit._undoStack[i].command.name);
}
return entries.join("\n");
}
mozile.edit.done = function(state) {
if(!state || !state.reversible) return;
mozile.edit._undoStack = mozile.edit._undoStack.slice(0, mozile.edit._undoIndex + 1);
mozile.edit._undoStack.push(state);
mozile.edit._undoIndex = mozile.edit._undoStack.length - 1;
mozile.edit.setCurrentState();
}
mozile.edit.setCurrentState = function() {
mozile.edit.currentState = mozile.edit._undoStack[mozile.edit._undoIndex];
}
mozile.edit.save = new mozile.edit.Command("Save");
mozile.edit.save.accel = "Command-S";
mozile.edit.save.image = "silk/page_save";
mozile.edit.save.makesChanges = "none";
mozile.edit.save.watchesChanges = "state";
mozile.edit.addCommand(mozile.edit.save);
mozile.edit.save.isAvailable = function(event) {
if(!mozile.save) return false;
if(mozile.save.isSaved()) return false;
else return true;
}
mozile.edit.save.execute = function(state, fresh) {
mozile.save.save();
state.reversible = false;
state.executed = true;
return state;
}
mozile.edit.source = new mozile.edit.Command("Source");
mozile.edit.source.image = "silk/html";
mozile.edit.source.makesChanges = "none";
mozile.edit.source.watchesChanges = "none";
mozile.edit.addCommand(mozile.edit.source);
mozile.edit.source.execute = function(state, fresh) {
if(mozile.save && mozile.gui){
var content = mozile.save.getContent(document);
content = mozile.save.cleanMarkup(content);
mozile.gui.display("<h3>Page Source</h3>\n<pre>"+ content +"</pre>");
}
state.reversible = false;
state.executed = true;
return state;
}
mozile.edit.debug = new mozile.edit.Command("Debug");
mozile.edit.debug.accel = "Command-D";
mozile.edit.debug.image = "silk/bug";
mozile.edit.debug.makesChanges = "none";
mozile.edit.debug.watchesChanges = "none";
mozile.edit.addCommand(mozile.edit.debug);
mozile.edit.debug.execute = function(state, fresh) {
mozile.debug.show();
state.reversible = false;
state.executed = true;
return state;
}
mozile.edit.undo = new mozile.edit.Command("Undo");
mozile.edit.undo.accel = "Command-Z";
mozile.edit.undo.image = "silk/arrow_undo";
mozile.edit.undo.makesChanges = "node";
mozile.edit.undo.watchesChanges = "state";
mozile.edit.addCommand(mozile.edit.undo);
mozile.edit.undo.test = function(event) {
if(mozile.edit._undoIndex < 0) return false;
if(event) {
return mozile.edit.checkAccelerator(event, this.accel);
}
return true;
}
mozile.edit.undo.isAvailable = function(event) {
if(mozile.edit._undoIndex < 0) return false;
else return true;
}
mozile.edit.undo.prepare = function(event, repeated) {
var state = new mozile.edit.State(this, false);
state.repeated = false;
if(repeated) state.repeated = repeated;
if(event) state.repeated = event.repeat;
state.reversible = false;
return state;
}
mozile.edit.undo.execute = function(state, fresh) {
var undoState = mozile.edit._undoStack[mozile.edit._undoIndex];
if(undoState) {
undoState.command.unexecute(undoState, false);
mozile.edit._undoIndex--;
mozile.edit.setCurrentState();
state.changesMade = undoState.changesMade;
}
state.executed = true;
return state;
}
mozile.edit.redo = new mozile.edit.Command("Redo");
mozile.edit.redo.accel = "Command-Shift-Z";
mozile.edit.redo.image = "silk/arrow_redo";
mozile.edit.redo.makesChanges = "node";
mozile.edit.redo.watchesChanges = "state";
mozile.edit.addCommand(mozile.edit.redo);
mozile.edit.redo.test = function(event) {
if(mozile.edit._undoIndex + 1 >= mozile.edit._undoStack.length) return false;
if(event) {
return mozile.edit.checkAccelerator(event, this.accel);
}
return true;
}
mozile.edit.redo.isAvailable = function(event) {
if(mozile.edit._undoIndex + 1 >= mozile.edit._undoStack.length) return false;
else return true;
}
mozile.edit.redo.prepare = function(event, repeated) {
var state = new mozile.edit.State(this, false);
state.repeated = false;
if(repeated) state.repeated = repeated;
if(event) state.repeated = event.repeat;
state.reversible = false;
return state;
}
mozile.edit.redo.execute = function(state, fresh) {
var redoState = mozile.edit._undoStack[mozile.edit._undoIndex + 1];
if(redoState) {
mozile.edit._undoIndex++;
redoState.command.execute(redoState, false);
mozile.edit.setCurrentState();
state.changesMade = redoState.changesMade;
}
state.executed = true;
return state;
}
mozile.edit.clipboard = null;
mozile.edit.updateClipboard = function() {
}
mozile.edit.copy = new mozile.edit.Command("Copy");
mozile.edit.copy.accel = "Command-C";
mozile.edit.copy.image = "silk/page_copy";
mozile.edit.addCommand(mozile.edit.copy);
mozile.edit.copy.prepare = function(event) {
var state = new mozile.edit.State(this, false);
state.reversible = false;
state.cancel = false;
return state;
}
mozile.edit.copy.execute = function(state, fresh) {
var selection = mozile.dom.selection.get();
var range = selection.getRangeAt(0);
if(range.commonAncestorContainer.nodeType == mozile.dom.TEXT_NODE ||
!mozile.edit.rich) {
mozile.edit.clipboard = range.toString();
}
else mozile.edit.clipboard = range.cloneContents();
state.executed = true;
return state;
}
mozile.edit.cut = new mozile.edit.Command("Cut");
mozile.edit.cut.accel = "Command-X";
mozile.edit.cut.image = "silk/cut";
mozile.edit.addCommand(mozile.edit.cut);
mozile.edit.cut.test = function(event) {
if(event) {
if(!event.editable) return false;
if(!mozile.edit.checkAccelerator(event, this.accel)) return false;
if(!mozile.edit.rich) {
if(event.node) return false;
if(event.node.nodeType != mozile.dom.TEXT_NODE) return false;
}
}
return true;
}
mozile.edit.cut.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();
if(range.commonAncestorContainer.nodeType == mozile.dom.TEXT_NODE ||
!mozile.edit.rich) {
mozile.edit.clipboard = range.toString();
mozile.edit.removeText.request(state, fresh);
}
else {
mozile.edit.clipboard = range.cloneContents();
mozile.edit.remove.request(state, fresh);
}
state.selection.after = selection.store();
state.executed = true;
return state;
}
mozile.edit.cut.unexecute = function(state, fresh) {
var selection = mozile.dom.selection.get();
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.paste.unexecute Child command unexecute failed at action "+ i +".");
}
selection.restore(state.selection.before);
selection.collapseToEnd();
state.executed = false;
return state;
}
mozile.edit.paste = new mozile.edit.Command("Paste");
mozile.edit.paste.accel = "Command-V";
mozile.edit.paste.image = "silk/page_paste";
mozile.edit.addCommand(mozile.edit.paste);
mozile.edit.paste.test = function(event) {
if(!mozile.edit.clipboard) return false;
if(event) {
if(!event.editable) return false;
if(!mozile.edit.checkAccelerator(event, this.accel)) return false;
if(!mozile.edit.rich) {
if(typeof(mozile.edit.clipboard) != "string") return false;
if(event.node) return false;
if(event.node.nodeType != mozile.dom.TEXT_NODE) return false;
}
}
return true;
}
mozile.edit.paste.prepare = function(event) {
var state = new mozile.edit.State(this);
if(typeof(mozile.edit.clipboard) == "string") {
state.content = mozile.edit.clipboard;
}
else state.content = mozile.edit.clipboard.cloneNode(true);
state.reversible = true;
return state;
}
mozile.edit.paste.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();
if(!selection.isCollapsed) {
if(mozile.edit.remove) mozile.edit.remove.request(state, fresh);
else mozile.edit.removeText.request(state, fresh);
}
if(typeof(state.content) == "string") {
mozile.edit.insertText.request(state, fresh, mozile.edit.NEXT, state.content);
}
else {
var previousSibling = null;
var nextSibling = null;
if(selection.focusNode.nodeType == mozile.dom.TEXT_NODE) {
mozile.edit.splitNode.request(state, fresh);
previousSibling = selection.focusNode.previousSibling;
nextSibling = selection.focusNode;
}
else {
previousSibling = selection.focusNode.childNodes[selection.focusOffset - 1];
nextSibling = selection.focusNode.childNodes[selection.focusOffset];
}
var parentNode = null;
if(!previousSibling) parentNode = selection.focusNode.parentNode;
var clone;
if(state.content.childNodes.length > 1) {
clone = state.content.firstChild.cloneNode(true);
if(previousSibling && previousSibling.nodeType == mozile.dom.TEXT_NODE &&
clone.nodeType == mozile.dom.TEXT_NODE) {
selection.collapse(previousSibling, previousSibling.data.length);
mozile.edit.insertText.request(state, fresh, null, clone.data);
}
else {
mozile.edit.insertNode.request(state, fresh, parentNode, previousSibling, clone);
previousSibling = clone;
parentNode = null;
}
}
for(var i=1; i < state.content.childNodes.length - 1; i++) {
clone = state.content.childNodes[i].cloneNode(true);
mozile.edit.insertNode.request(state, fresh, parentNode, previousSibling, clone);
previousSibling = clone;
parentNode = null;
}
clone = state.content.lastChild.cloneNode(true);
if(nextSibling && nextSibling.nodeType == mozile.dom.TEXT_NODE &&
clone.nodeType == mozile.dom.TEXT_NODE) {
selection.collapse(nextSibling, 0);
mozile.edit.insertText.request(state, fresh, null, clone.data);
}
else {
mozile.edit.insertNode.request(state, fresh, parentNode, previousSibling, clone);
var IP = mozile.edit.getInsertionPoint(clone, mozile.edit.PREVIOUS);
if(IP) IP.select();
else {
IP = mozile.edit.getInsertionPoint(nextSibling, mozile.edit.NEXT);
if(IP) IP.select();
else mozile.debug.debug("mozile.edit.paste.execute", "Nowhere to collapse to.");
}
}
}
state.selection.after = selection.store();
state.executed = true;
return state;
}
mozile.edit.paste.unexecute = function(state, fresh) {
var selection = mozile.dom.selection.get();
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.paste.unexecute Child command unexecute failed at action "+ i +".");
}
selection.restore(state.selection.before);
selection.collapseToEnd();
state.executed = false;
return state;
}
mozile.edit.test = new mozile.edit.Command("Test");
mozile.edit.test.accel = "Escape";
mozile.edit.addCommand(mozile.edit.test);
mozile.edit.test.execute = function(state, fresh) {
mozile.require("mozile.util");
var output = new Array();
output.push("Debugging Information:");
output.push("Undo: "+ mozile.edit._undoIndex +" / "+ mozile.edit._undoStack.length);
var selection = mozile.dom.selection.get();
output.push("Selection:\n"+ mozile.util.dumpValues(selection.store()));
var element = selection.focusNode;
if(element.nodeType != mozile.dom.ELEMENT_NODE) element = element.parentNode;
var rng = mozile.edit.lookupRNG(element);
if(rng) {
if(rng.getName()) output.push("RNG: "+ rng +" "+ rng.getName());
else output.push("RNG: "+ rng);
output.push("Text? "+ rng.mayContain("text"));
}
else output.push("No matching RNG object.");
alert(output.join("\n"));
state.reversible = false;
state.executed = true;
return state;
}
mozile.edit.tweak = new mozile.edit.Command("Tweak");
mozile.edit.tweak.accel = "Command-E";
mozile.edit.addCommand(mozile.edit.tweak);
mozile.edit.tweak.execute = function(state, fresh) {
if(mozile.browser.isIE) {
var selection = mozile.dom.selection.get();
var range = selection.getRangeAt(0);
range._range.move("character", 1);
selection.removeAllRanges();
selection.addRange(range);
}
state.reversible = false;
state.executed = true;
return state;
}
mozile.require("mozile.edit.InsertionPoint");
mozile.edit.navigateLeftRight = new mozile.edit.Command("NavigateLeftRight");
mozile.edit.navigateLeftRight.priority = 15;
mozile.edit.navigateLeftRight.accel = "Left Right";
mozile.edit.navigateLeftRight.accels =
mozile.edit.splitAccelerators(mozile.edit.navigateLeftRight.accel);
mozile.edit.navigateLeftRight.makesChanges = "none";
mozile.edit.navigateLeftRight.watchesChanges = "none";
mozile.edit.navigateLeftRight.prepare = function(event, direction, extend) {
var state = new mozile.edit.State(this, false);
state.direction = mozile.edit.NEXT;
if(direction) state.direction = direction;
else if(event && event.keyCode == 37) state.direction = mozile.edit.PREVIOUS;
state.extend = false;
if(extend) state.extend = extend;
else if(event) state.extend = event.shiftKey;
state.reversible = false;
return state;
}
mozile.edit.navigateLeftRight.execute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(selection.isCollapsed || state.extend) {
var IP = selection.getInsertionPoint();
IP.seek(state.direction);
if(state.extend) IP.extend();
else IP.select();
}
else {
if(state.direction == mozile.edit.NEXT) selection.collapseToEnd();
else selection.collapseToStart();
}
state.executed = true;
return state;
}
mozile.edit.Navigate = function(name) {
this.name = name;
this.group = false;
this.remove = true;
this.makesChanges = "none";
this.watchesChanges = "none";
this.direction = "next";
this.collapse = null;
mozile.edit.allCommands[this.name] = this;
}
mozile.edit.Navigate.prototype = new mozile.edit.Command;
mozile.edit.Navigate.prototype.constructor = mozile.edit.Navigate;
mozile.edit.Navigate.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);
if(this.prompt) {
if(!this.prompt(event, state)) return null;
}
state.reversible = false;
return state;
}
mozile.edit.Navigate.prototype.execute = function(state, fresh) {
var selection = mozile.dom.selection.get();
var target = mozile.xpath.getNode(state.target);
var direction = mozile.edit.NEXT;
if(this.direction == "previous") direction = mozile.edit.PREVIOUS;
var IP = mozile.edit.getInsertionPoint(target, direction);
if(IP) {
IP.select();
IP = mozile.edit.getInsertionPoint(target, -1 * direction);
if(IP) IP.extend();
if(this.collapse == "start") selection.collapseToStart();
else if(this.collapse == "end") selection.collapseToEnd();
var x = mozile.dom.getX(target);
var y = mozile.dom.getY(target);
var pX = window.pageXOffset;
var pY = window.pageYOffset;
if(x < pX || x > (pX + window.innerWidth) ||
y < pY || y > (pY + window.innerHeight) ) {
window.scroll(x-50, y-100);
}
}
state.executed = true;
return state;
}
mozile.edit.insertText = new mozile.edit.Command("InsertText");
mozile.edit.insertText.priority = 10;
mozile.edit.insertText.makesChanges = "text";
mozile.edit.insertText.watchesChanges = "none";
mozile.edit.insertText.test = function(event, direction, content, node) {
if(event) {
if(event.type != "keypress") return false;
if(event.ctrlKey || event.metaKey) return false;
if(!mozile.os.isMac && event.altKey) return false;
if(!node && event.charCode == 32 && !mozile.alternateSpace) {
var range = event.range;
if(!range) range = mozile.dom.selection.get().getRangeAt(0);
if(range.startContainer.nodeType == mozile.dom.TEXT_NODE) {
if(range.startContainer.data.charAt(range.startOffset-1) == " ") {
return false;
}
}
}
if(event.charCode && event.charCode >= 32) {
if(mozile.browser.isSafari &&
event.charCode >= 63232 &&
event.charCode <= 63235) return false;
return true;
}
return false;
}
else {
if(typeof(content) != "string") return false;
}
return true;
}
mozile.edit.insertText.prepare = function(event, direction, content, node) {
var state = new mozile.edit.State(this);
state.direction = mozile.edit.NEXT;
if(direction) state.direction = direction;
state.content = " ";
if(content) state.content = content;
else if(event) state.content = String.fromCharCode(event.charCode);
state.node = state.storeNode(node);
state.remove = false;
var selection = null;
if(event && event.selection) selection = event.selection;
else selection = mozile.dom.selection.get();
if(mozile.alternateSpace && !state.node && state.content == " ") {
var range = selection.getRangeAt(0);
var alt = mozile.alternateSpace;
var text = range.startContainer;
var offset = range.startOffset;
var nextChar = null;
if(range.endContainer.nodeType == mozile.dom.TEXT_NODE)
nextChar = range.endContainer.data.charAt(range.endOffset);
var previousChar = null;
var previousAlt = false;
if(text.nodeType == mozile.dom.TEXT_NODE) {
previousChar = text.data.charAt(offset-1);
var data = text.data.substring(0, offset);
if(offset && data.lastIndexOf(alt) + alt.length == offset) {
previousAlt = true;
previousChar = text.data.charAt(offset - alt.length - 1);
}
}
if(previousAlt) {
if(previousChar && previousChar != " ") {
state.remove = true;
state.content = " " + alt;
}
else if(!nextChar || nextChar == " ") state.content = alt;
}
else if(!nextChar || nextChar == " ") state.content = alt;
else if(!previousChar || previousChar == " ") state.content = alt;
}
return state;
}
mozile.edit.insertText.execute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(!fresh) selection.restore(state.selection.before);
state.emptyToken = false;
state.actions = new Array();
if(!state.node && !selection.isCollapsed) {
if(mozile.edit.remove) mozile.edit.remove.request(state, fresh, state.direction, null, true);
else mozile.edit.removeText.request(state, fresh, state.direction);
}
if(state.node) {
var node = mozile.xpath.getNode(state.node);
state.changedNode = node;
state.oldData = node.data;
node.data = state.content;
if(state.direction == mozile.edit.NEXT) {
selection.collapse(node, 0);
selection.extend(node, node.data.length);
}
else {
selection.collapse(node, node.data.length);
selection.extend(node, 0);
}
state.selection.after = selection.store();
}
else if(mozile.edit.isEmptyToken(selection.focusNode)) {
state.emptyToken = true;
selection.focusNode.data = state.content;
selection.collapse(selection.focusNode, selection.focusNode.data.length);
state.selection.after = selection.store();
}
else if(selection.focusNode.nodeType != mozile.dom.TEXT_NODE) {
state.newNode = document.createTextNode(state.content);
if(selection.focusOffset == 0) mozile.dom.prependChild(state.newNode, selection.focusNode);
else selection.focusNode.insertBefore(state.newNode, selection.focusNode.childNodes[selection.focusOffset]);
selection.collapse(state.newNode, state.newNode.data.length);
state.selection.after = selection.store();
}
else {
var text = selection.focusNode;
var offset = selection.focusOffset;
if(state.remove) {
mozile.edit.removeText.request(state, fresh, -1 * state.direction, mozile.alternateSpace);
offset -= mozile.alternateSpace.length;
}
text.insertData(offset, state.content);
var newOffset = offset + (state.direction * state.content.length);
selection.collapse(text, newOffset);
if(state.actions.length == 0)
state.selection.after = selection.store(state.selection.before, newOffset);
else state.selection.after = selection.store();
}
state.executed = true;
return state;
}
mozile.edit.insertText.unexecute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(!fresh) selection.restore(state.selection.after);
if(state.changedNode) state.changedNode.data = state.oldData;
else if(state.emptyToken) selection.focusNode.data = mozile.emptyToken;
else if(state.newNode) state.newNode.parentNode.removeChild(state.newNode);
else selection.focusNode.deleteData(selection.focusOffset - state.content.length, state.content.length);
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.inertText.unexecute Child command unexecute failed at action "+ i +".");
}
selection.restore(state.selection.before);
state.executed = false;
return state;
}
mozile.edit.removeText = new mozile.edit.Command("RemoveText");
mozile.edit.removeText.priority = 10;
mozile.edit.removeText.makesChanges = "text";
mozile.edit.removeText.watchesChanges = "none";
mozile.edit.removeText.test = function(event, direction, content) {
var dir;
if(event) {
if(mozile.edit.checkAccelerator(event, "Backspace")) {
dir = mozile.edit.PREVIOUS;
}
if(mozile.edit.checkAccelerator(event, "Delete")) {
dir = mozile.edit.NEXT;
}
if(!dir) return false;
}
if(!dir) dir = mozile.edit.PREVIOUS;
if(direction) dir = direction;
var selection;
if(event && event.selection) selection = event.selection;
if(!selection) selection = mozile.dom.selection.get();
if(!selection) return false;
var node;
if(event && event.node) node = event.node;
if(!node) {
var range;
if(event && event.range) range = event.range;
if(!range) range = selection.getRangeAt(0);
if(!range) return false;
node = range.commonAncestorContainer;
if(event) {
event.selection = selection;
event.range = range;
event.node = node;
}
}
if(!node) return false;
if(node.nodeType != mozile.dom.TEXT_NODE) return false;
if(event) {
if(mozile.edit.remove) {
if(node.data.length <= 2) return false;
if(!selection.isCollapsed) return false;
}
else if(!selection.isCollapsed) return true;
if(dir == mozile.edit.PREVIOUS &&
selection.focusOffset > 0) return true;
if(dir == mozile.edit.NEXT &&
selection.focusOffset < node.data.length) return true;
return false;
}
else return true;
}
mozile.edit.removeText.prepare = function(event, direction, content) {
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 = null;
if(content) state.content = content;
return state;
}
mozile.edit.removeText.execute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(!fresh) selection.restore(state.selection.before);
if(!state.direction) state.direction = mozile.edit.PREVIOUS;
if(selection.isCollapsed) {
var firstOffset = selection.focusOffset;
var secondOffset = selection.focusOffset;
if(!state.content) {
var IP = selection.getInsertionPoint();
IP.seek(state.direction);
if(state.direction == mozile.edit.PREVIOUS) firstOffset = IP.getOffset();
else secondOffset = IP.getOffset();
state.content = selection.focusNode.data.substring(firstOffset, secondOffset);
}
else {
if(state.direction == mozile.edit.PREVIOUS)
firstOffset -= state.content.length;
else secondOffset += state.content.length;
}
selection.focusNode.deleteData(firstOffset, state.content.length);
selection.collapse(selection.focusNode, firstOffset);
state.selection.after = selection.store(state.selection.before, firstOffset);
}
else {
var range = selection.getRangeAt(0);
if(mozile.browser.isIE && range.startContainer != range.endContainer) {
if(range.endOffset == 0) range.setEnd(range.startContainer, range.startContainer.data.length);
else range.setStart(range.endContainer, 0);
}
state.content = range.startContainer.data.substring(range.startOffset, range.endOffset);
range.startContainer.deleteData(range.startOffset, range.endOffset - range.startOffset);
selection.collapse(range.startContainer, range.startOffset);
state.selection.after = selection.store(state.selection.before, range.startOffset);
}
state.executed = true;
return state;
}
mozile.edit.removeText.unexecute = function(state, fresh) {
var selection = mozile.dom.selection.get();
if(!fresh) selection.restore(state.selection.after);
if(!selection || !selection.focusNode) throw("Error: mozile.edit.removeText.unexecute no selection.focusNode");
selection.focusNode.insertData(selection.focusOffset, state.content);
selection.restore(state.selection.before);
state.executed = false;
return state;
}
mozile.edit.isBlock = function(node) {
if(!node) return false;
if(node.nodeType != mozile.dom.ELEMENT_NODE) return false;
var display = mozile.dom.getStyle(node, "display");
switch(display) {
case "block":
case "list-item":
return true;
}
return false;
}
mozile.edit.getParentBlock = function(node) {
while(node) {
if(mozile.edit.isBlock(node)) return node;
else node = node.parentNode;
}
return null;
}
mozile.edit.isNodeEditable = function(node) {
if(!node) return false;
if(node.nodeType == mozile.dom.TEXT_NODE) {
var rng = mozile.edit.lookupRNG(node);
if(rng) return rng.mayContain("text");
else return true;
}
else if(node.nodeType == mozile.dom.ELEMENT_NODE) {
var rng = mozile.edit.lookupRNG(node);
if(rng) return rng.mayContain("text");
else {
mozile.debug.debug("mozile.edit.isNodeEditable", "No RNG Element for element named '"+ node.nodeName +"'.");
return true;
}
}
return false;
}
mozile.edit.isChildless = function(node) {
if(node.nodeType == mozile.dom.COMMENT_NODE) return true;
if(node.nodeType != mozile.dom.ELEMENT_NODE) return false;
var rng = mozile.edit.lookupRNG(node);
if(rng) {
if(rng.mayContain("element")) return false;
else return true;
}
else return false;
}
mozile.edit.createEmptyToken = function() {
return document.createTextNode(mozile.emptyToken);
}
mozile.edit.isEmptyToken = function(node) {
if(node && node.nodeType == mozile.dom.TEXT_NODE &&
node.data == mozile.emptyToken) return true;
else return false;
}
mozile.edit.containsEmptyToken = function(node, offset) {
if(!node || node.nodeType != mozile.dom.TEXT_NODE) return false;
if(offset == undefined || Number(offset)) {
if(node.data.indexOf(mozile.emptyToken) > -1) return true;
else return false;
}
else {
var data = node.data.substring(offset);
if(data.indexOf(mozile.emptyToken) == 0) return true;
else return false;
}
}
mozile.edit.isEmpty = function(node) {
switch(node.nodeType) {
case mozile.dom.TEXT_NODE:
if(node.data.match(/\S/)) return false;
if(mozile.edit.isEmptyToken(node)) return false;
return true;
case mozile.dom.ELEMENT_NODE:
var children = node.childNodes;
var i=0;
for(i=0; i < children.length; i++) {
if(children[i].nodeType == mozile.dom.TEXT_NODE &&
!mozile.edit.isEmpty(children[i]) )
return false;
}
for(i=0; i < children.length; i++) {
if(children[i].nodeType == mozile.dom.ELEMENT_NODE &&
!mozile.edit.isEmpty(children[i]) )
return false;
}
return true;
default:
return true;
}
}
mozile.edit._getElementName = function(command) {
var elementName;
if(typeof(command.element) == "string") elementName = command.element;
else if(command.element && command.element.cloneNode)
elementName = mozile.dom.getLocalName(command.element);
elementName = elementName.toLowerCase();
return elementName;
}
mozile.edit._getNode = function(event) {
var node;
if(event && event.node) node = event.node;
if(!node) {
var selection;
if(event && event.selection) selection = event.selection;
if(!selection) selection = mozile.dom.selection.get();
if(!selection) return false;
var range;
if(event && event.range) range = event.range;
if(!range) range = selection.getRangeAt(0);
if(!range) return false;
node = range.commonAncestorContainer;
if(event) {
event.selection = selection;
event.range = range;
event.node = node;
}
}
if(!node) return null;
else return node;
}
mozile.edit._getTarget = function(event, target, direction) {
var node = mozile.edit._getNode(event);
if(!node) return null;
var test, result;
if(!direction) direction = "ancestor";
if(direction != "ancestor" && direction != "descendant" &&
direction != "next" && direction != "previous") {
mozile.debug.debug("mozile.edit._getTarget", "Invalid direction '"+ direction +"'.");
return null;
}
if(typeof(target) == "function") {
result = target(event, null);
}
else if(typeof(target) == "string") {
if(target.toLowerCase() == "any") {
test = function(node) {
if(node) return true;
else return false;
}
}
else if(target.toLowerCase() == "text") {
test = function(node) {
if(node.nodeType == mozile.dom.TEXT_NODE) return true;
else return false;
}
}
else if(target.toLowerCase() == "element") {
test = function(node) {
if(node.nodeType == mozile.dom.ELEMENT_NODE) return true;
else return false;
}
}
else if(target.toLowerCase() == "block") {
test = function(node) {
if(mozile.edit.isBlock(node)) return true;
else return false;
}
}
else if(target.toLowerCase().indexOf("localname") == 0) {
var name = target.substring(9);
var match = name.match(/\W*(\w+)\W*/);
if(match && match[1]) {
name = match[1].toLowerCase();
test = function(node) {
var localName = mozile.dom.getLocalName(node);
if(localName && localName.toLowerCase() == name) return true;
else return false;
}
}
else return null;
}
else return null;
}
else return null;
var treeWalker;
if(test && !result) {
if(direction != "ancestor" && !treeWalker) {
var root = document.documentElement;
if(direction == "descendant") {
root = node;
if(root.nodeType != mozile.dom.ELEMENT_NODE) root = root.parentNode;
direction = "next";
}
treeWalker = document.createTreeWalker(root, mozile.dom.NodeFilter.SHOW_ALL, null, false);
treeWalker.currentNode = node;
}
var startNode = node;
while(node) {
if(direction == "next") node = treeWalker.nextNode();
else if(direction == "previous") {
node = treeWalker.previousNode();
if(mozile.dom.isAncestorOf(node, startNode)) continue;
}
if(node && test(node) && mozile.edit.isEditable(node) &&
mozile.edit.getInsertionPoint(node, mozile.edit.NEXT)) {
result = node;
break;
}
if(direction == "ancestor") node = node.parentNode;
}
}
if(result) return result;
else return null;
}
Documentation generated by
JSDoc on Wed Aug 23 18:45:51 2006