|
||||||||
PREV NEXT | FRAMES NO FRAMES |
This file defines the core Mozile objects, which are required to allow basic editing and to support the rest of the Mozile system. Additional tools and interface elements are defined in "core/interface.js".
Version: 0.7
Author: James A. Overton
Class Summary | |
InsertionPoint | |
MozileComponent | |
MozileEditor | |
MozileLinkResource | |
MozileMediator | |
MozileModule | |
MozileResource | |
MozileScriptResource | |
MozileStyleResource |
Method Summary | |
static String
|
checkArgument(<String> defaultValue, value)
Checks |
static Object
|
dumpArray(arr)
Dump Array - Displays the contents of the array in key=>value pairs, using an alert. |
static Array
|
executeXPath(<String> expression, <Node> node)
Executes an XPath expression in the context of the document or a node. |
static Object
|
mozileHandleBlur(event)
Mozile Handle Blur - Takes appropriate action when a Mozile editor loses focus: hide the caret and toobar. |
static Void
|
mozileHandleEvent(event)
A centralized event handler, which calls other handlers as needed. |
static Object
|
mozileHandleFocus(event)
Mozile Handle Focus - Takes appropriate action when a Mozile editor gains focus: shows the caret and toolbar, then sets the currentEditor. |
static Object
|
mozileHandleKeypress(event)
Mozile Handle Keypress - Decides what action to take for the given "keypress" event, and calls the appropriate function. |
static Object
|
mozileHandleKeyup(event)
Mozile Handle Keyup - Decides what action to take for the given "keyup" event, and calls the appropriate functions: update the toolbar and store the current state for undo/redo. |
static Object
|
mozileHandleMouseup(event)
Mozile Handle Mouseup - Takes appropriate action when Mozile detects a mouseup event: show the caret and the toolbar. |
static Object
|
printXML(XML)
Print XML - Serializes the given XML and returns it as a string |
/* ***** BEGIN LICENSE BLOCK ***** * Licensed under Version: MPL 1.1/GPL 2.0/LGPL 2.1 * Full Terms at http://mozile.mozdev.org/license2.html * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is James A. Overton's code (james@overton.ca). * * The Initial Developer of the Original Code is James A. Overton. * Portions created by the Initial Developer are Copyright (C) 2005-2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * James A. Overton <james@overton.ca> * * ***** END LICENSE BLOCK ***** */ /** * @fileoverview This file defines the core Mozile objects, which are required to allow basic editing and to support the rest of the Mozile system. Additional tools and interface elements are defined in "core/interface.js". * @link http://mozile.mozdev.org * @author James A. Overton <james@overton.ca> * @version 0.7 */ /**** GLOBALS ****/ var mozileVersion = "0.7.4"; // Declare the XUL namespace, which is used for the creation of elements in the Mozile toolbar. var XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; /**** FUNCTIONS ****/ /** Mozile Debug - * A basic debugging tool. It logs messages to the mozileDebugArray, but only if their level exceeds the Mozile.getDebugLevel() setting, or they are marked "Status Message". * * @param details An array of information. The first two fields are "File", and "Function" (usually a function name). Fancier debugging functions might use more fields. * @param level An integer describing the importance of the debugging message. 4="critical", 3="very important", 2="important", 1="normal", 0="not important". * @param message A string containing the debugging message. * @return Always true. */ function mozileDebug(details, level, message) { // If the level is higher than the debug level or the "Status Message" value is true, then save the message. if(level >= mozile.getDebugLevel() || details["Status Message"]) { var date = new Date(); // add it to the debugging array mozileDebugList.push([date.toLocaleString(), details, level, message]); } return true; } // Create the global debug list for this document. var mozileDebugList = new Array(); /** * A centralized event handler, which calls other handlers as needed. * @param event The focus event to be handled. * @type Void */ function mozileHandleEvent(event) { switch(event.type) { case "focus": mozileHandleFocus(event); break; case "blur": mozileHandleBlur(event); break; case "keypress": mozileHandleKeypress(event); break; case "keyup": mozileHandleKeyup(event); break; case "mouseup": mozileHandleMouseup(event); break; } } /** Mozile Handle Focus - * Takes appropriate action when a Mozile editor gains focus: shows the caret and toolbar, then sets the currentEditor. * * @param event The focus event to be handled. * @return True if successful. */ function mozileHandleFocus(event) { try{ mozile } catch(e) { return; } if(!mozile || !mozile.isEditable()) { if(mozile.getOption("activateOnFocus") == true) mozile.startEditing(); else return; } mozile.showCaret(); if(mozileInterface) mozile.showToolbars(); // TOOD: This might not work right with Mozile.makeDocumentEditable() if(event.target && event.target.nodeType==1 && document.defaultView.getComputedStyle(event.target, '').getPropertyValue("-moz-user-modify").toLowerCase() == "read-write") { mozile.setCurrentEditor(event.target); mozileEditor.replaceAnchors(); } return; } /** Mozile Handle Blur - * Takes appropriate action when a Mozile editor loses focus: hide the caret and toobar. * * @param event The blur event to be handled. * @return True if successful. */ function mozileHandleBlur(event) { mozile.hideCaret(); if(mozileInterface) mozile.hideToolbars(); mozileEditor.restoreAnchors(); return true; } /** Mozile Handle Keypress - * Decides what action to take for the given "keypress" event, and calls the appropriate function. Somewhat complicated. I've tried to optimize it for speed, because it gets called frequently, so it only asks for information as needed. * * @param event The keypress event to be handled. * @return True when successful, false otherwise. */ function mozileHandleKeypress(event) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "mozileHandleKeypress()"; //mozile.debug(f,1,"Handling keypress event "+ event); // Ignore the function keys (i.e. F7). if(event.keyCode >= 112 && event.keyCode <= 135) { // mozile.debug(f,0,"Function key keypress"); return true; } var selection = window.getSelection(); // Make sure we have a focusNode if(!selection.focusNode) { // mozile.debug(f,0,"No selection"); return true; } else { // mozile.debug(f,0,"Selection "+ selection); } // Handle arrow keys. When the cursor has moved, update the toolbar. Also, don't let the cursor get stuck against an empty element. if(event.keyCode >=33 && event.keyCode <= 40) { // Handle left and right arrow keys. The cursor can get stuck against an empty node when moving left or right. When this happens, we can help it get unstuck. if(event.keyCode == 37 || event.keyCode == 39) { // Check to see if the cursor hasn't moved since the last keypress if(selection.focusNode == mozileEditor.getLastNode() && selection.focusOffset == mozileEditor.getLastOffset() && event.keyCode == mozileEditor.getLastKeyCode() ) { // Check to see if we are at the beginning of end of the node, and set the proper direction var direction = null; if(event.keyCode==37 && selection.focusOffset == 0) { direction="previous"; } if(event.keyCode==39 && selection.focusOffset == selection.focusNode.textContent.length) { direction="next"; } // If the direction was set, then find a new text node in that direction. if(direction) { // mozile.debug(f,1,"Cursor seems stuck. Jumping to "+direction+" IP."); // Seek a new IP var node = mozileEditor.seekTextNode(direction, selection.focusNode); // Collapse the selection into the new IP. if(direction=="next") selection.collapse(node, 0); else selection.collapse(node, node.textContent.length); // mozile.debug(f,0,"Arrow keypress, direction "+ direction); event.stopPropagation(); event.preventDefault(); } } // If the direction wasn't set, we just store the IP and don't worry about unsticking the cursor. mozileEditor.setLastNode(selection.focusNode); mozileEditor.setLastOffset(selection.focusOffset); mozileEditor.setLastKeyCode(event.keyCode); return true; } // If the toolbar is supposed to update often, update it now. if(mozileInterface && mozile.getOption("toolbarUpdateFrequency")==2) { mozile.updateToolbars(); } return true; } // Check to see if the document is editable. if(mozile.isEditable() == false) return false; if(!event.target || event.target.nodeType!=1 || document.defaultView.getComputedStyle(event.target, '').getPropertyValue("-moz-user-modify").toLowerCase() != "read-write") return false; // Handle Modifiers. Check for keyboard shortcuts triggering commands. if((event.ctrlKey || event.metaKey) && mozile.getOption("keyboardShortcuts") && mozile.getAccelerators() != null) { 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-"; accel = accel + String.fromCharCode(event.charCode).toUpperCase(); // mozile.debug(f,0,"Accelerator keypress: "+ accel); //alert(accel); //dumpArray(mozile.getAccelerators()); if(mozile.getAccelerator(accel)) { mozile.executeCommand(mozile.getAccelerator(accel).getId(), event); if(mozileInterface) mozile.updateToolbars(); // mozile.debug(f,0,"Accelerator command found: "+ mozile.getAccelerators()[accel].id); // In the case of copy, don't prevent propagation if(mozile.getAccelerator(accel).getId()=="Mozile-Copy") return true; event.stopPropagation(); event.preventDefault(); return true; } else { // mozile.debug(f,0,"Accelerator command not found"); return true; } } // Check editable. If this isn't the same node as the last time we checked, check that the parent node is modifiable and accepts input. if(selection.focusNode != mozileEditor.getLastNode()) { try { var userModify = document.defaultView.getComputedStyle(selection.focusNode.parentNode, '').getPropertyValue("-moz-user-modify").toLowerCase(); var userInput = document.defaultView.getComputedStyle(selection.focusNode.parentNode, '').getPropertyValue("-moz-user-input").toLowerCase(); if(userModify=="read-only" || userInput=="disabled") { mozile.debug(f,1,"Not user modifiable!"); return true; } else { // mozile.debug(f,0,"New modifiable node."); } } catch(e) { alert("Bad selection? "+e+"\n"+selection.focusNode); } } // Store this IP mozileEditor.setLastNode(selection.focusNode); mozileEditor.setLastOffset(selection.focusOffset); mozileEditor.setLastKeyCode(event.keyCode); // Handle delete and backspace keys. if(event.keyCode == event.DOM_VK_BACK_SPACE) { mozileEditor.deletion("previous"); // mozile.debug(f,0,"Back space keypress"); event.stopPropagation(); event.preventDefault(); return true; } if(event.keyCode == event.DOM_VK_DELETE){ mozileEditor.deletion("next"); // mozile.debug(f,0,"Delete keypress"); event.stopPropagation(); event.preventDefault(); return true; } // Handle all other non-command keystrokes by inserting a string with the value of the character code. if(!event.ctrlKey && !event.metaKey && event.keyCode != event.DOM_VK_ENTER && event.keyCode != event.DOM_VK_RETURN && event.keyCode != event.DOM_VK_TAB ) { mozileEditor.insertString(String.fromCharCode(event.charCode)); mozile.keyCounter++; if(mozile.keyCounter > mozile.getOption("maxKeyCount")) mozile.storeState("Typing"); // mozile.debug(f,0,"Non-command keypress "+event.charCode); event.stopPropagation(); event.preventDefault(); return true; } // Check to see if the node uses CSS white-space="pre". var whiteSpace = document.defaultView.getComputedStyle(selection.focusNode.parentNode, '').getPropertyValue("white-space").toLowerCase(); // Handle the enter key. If the white-space is "pre", insert a newline, and otherwise split the current block element. if(event.keyCode == event.DOM_VK_ENTER || event.keyCode == event.DOM_VK_RETURN){ if(whiteSpace=="pre") { mozileEditor.insertString("\n"); } else { mozileEditor.splitBlock(); } mozile.storeState("Enter Key"); // mozile.debug(f,0,"Enter keypress"); event.stopPropagation(); return true; } // Handle the tab key by inserting a tab. We might add other behaviours later. if(event.keyCode == event.DOM_VK_TAB) { mozileEditor.insertString("\t"); mozile.keyCounter++; if(mozile.keyCounter > mozile.getOption("maxKeyCount")) mozile.storeState("Typing"); // mozile.debug(f,0,"Tab keypress"); event.stopPropagation(); event.preventDefault(); return true; } mozile.debug(f,1,"Keypress not handled"); return true; } /** Mozile Handle Keyup - * Decides what action to take for the given "keyup" event, and calls the appropriate functions: update the toolbar and store the current state for undo/redo. * * @param event The keyup event to be handled. * @return True when successful, false otherwise. */ function mozileHandleKeyup(event) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "mozileHandleKeyup()"; //mozile.debug(f,1,"Handling keyup event "+ event); if(!mozile.isEditable()) return false; if(mozileInterface) mozile.showToolbars(); // if an arrow key has been release, update the toolbar. if(event.keyCode >=33 && event.keyCode <= 40) { if(mozileInterface) mozile.updateToolbars(); return true; } // Handle delete and backspace keys. if(event.keyCode == event.DOM_VK_BACK_SPACE) { mozile.storeState("Backspace Key"); return true; } if(event.keyCode == event.DOM_VK_DELETE){ mozile.storeState("Delete Key"); return true; } return true; } /** Mozile Handle Mouseup - * Takes appropriate action when Mozile detects a mouseup event: show the caret and the toolbar. * * @param event The mouseup event to be handled. * @return True if successful. */ function mozileHandleMouseup(event) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "mozileHandleMouseup()"; mozile.debug(f,1,"Mouseup "+ event); if(!mozile.isEditable()) return false; mozile.showCaret(); if(mozileInterface) { mozile.showToolbars(); if(mozile.getOption("toolbarUpdateFrequency")==2) mozile.updateToolbars(); } return true; } /** * Checks * @param {String} defaultValue The default value. * @param value The value to check. * @type String */ function checkArgument(defaultValue, value) { if(value) { return String(value); } else { return defaultValue; } } /** * Executes an XPath expression in the context of the document or a node. * @param {String} expression The XPath expression. * @param {Node} node Optional. The context within which the expression should be executed. The default is the documentElement. * @type Array * @return An array of results. */ function executeXPath(expression, node) { if(!node) node = document.documentElement; var XP = new XPathEvaluator; var results = XP.evaluate(expression, node, XP.createNSResolver(node.ownerDocument.documentElement), 0, null); var nodes = new Array(); var result = results.iterateNext(); while(result) { nodes.push(result); result = results.iterateNext(); } return nodes; } /**** OBJECTS ****/ /** * A test to see if tis is an X/HTML document. Fairly crude test at the moment. * @type Boolean */ Document.prototype.isHTML = function() { if(document.documentElement.localName.toLowerCase() == "html") return true; else return false; } /** * Returns the "head" element of an HTML document, or the document elment of a general XML document. * @type Element */ Document.prototype.getHead = function() { var head = document.documentElement; if(this.isHTML()) head = document.getElementsByTagName("head")[0]; return head; } /** * Returns the "body" element of an HTML document, or the document elment of a general XML document. * @type Element */ Document.prototype.getBody = function() { var body = document.documentElement; if(this.isHTML()) body = document.getElementsByTagName("body")[0]; return body; } /** * A simple convenience function which inserted the newNode after the referenceNode. * @type Node * @return The inserted node. */ Node.prototype.insertAfter = function(newNode, refNode) { if(refNode.nextSibling) { return this.insertBefore(newNode, refNode.nextSibling); } else { return this.appendChild(newNode); } } /** * Checks to see if this node has a CSS display property which matches the matchDisplayBlock regular expression (defined globally at the top of this file). Currently, "block", "list-item", "table-cell", and "moz-box" qualify as blocks, but this could change. * @type Boolean * @return True if the node counts as a block, false otherwise. */ Node.prototype.isBlock = function() { if(!this._matchDisplayBlock) this._matchDisplayBlock = /(block|list\-item|table\-cell|\-moz\-box)/; if(this.nodeType == 1) { var display = document.defaultView.getComputedStyle(this, '').getPropertyValue("display").toLowerCase(); if(this._matchDisplayBlock.test(display)) { return true; } } return false; } /** * Climbs toward the root of the DOM tree until it finds an ancestor node for which isBlock is true, or it reaches the documentElement. * @type Node * @return First node which is an ancestor and which isBlock=true, or the documentElement. In practise this will always be an element. */ Node.prototype.getParentBlock = function() { var thisNode = this; while(thisNode) { if(thisNode.isBlock()) { return thisNode; } thisNode = thisNode.parentNode; } return document.documentElement; } /** * Climbs the DOM tree until it finds the given node, or the documentElement. * @param node The node which might be an ancestor of this node. * @type Boolean * @return True if the given node is an ancestor of this node, false otherwise. */ Node.prototype.isAncestorOf = function(node) { var thisNode = node; while(thisNode) { if(thisNode == this) { return true; } thisNode = thisNode.parentNode; } return false; } /** * Returns a simple unique XPath location for this node. * @type String */ Node.prototype.getXPath = function() { var nodeName; if(document.isHTML()) nodeName = this.nodeName.toLowerCase(); else if(this.nodeType == this.ATTRIBUTE_NODE) nodeName = this.nodeName; else if(this.prefix) nodeName = this.nodeName; else nodeName = "xmlns:"+this.nodeName; if(this == document.documentElement) return "/"+nodeName; // ELEMENT_NODE else if(this.nodeType == this.ELEMENT_NODE) { var s=1; for(var c=0; c < this.parentNode.childNodes.length; c++) { if(this.parentNode.childNodes[c] == this) break; else if(this.parentNode.childNodes[c].nodeName == this.nodeName) s++; } return this.parentNode.getXPath() +"/"+ nodeName +"["+ s +"]"; } // ATTRIBUTE_NODE else if(this.nodeType == this.ATTRIBUTE_NODE) { return this.ownerElement.getXPath() +"/@"+ nodeName; } // TEXT_NODE else if(this.nodeType == this.TEXT_NODE) { var s=1; for(var c=0; c < this.parentNode.childNodes.length; c++) { if(this.parentNode.childNodes[c] == this) break; else if(this.parentNode.childNodes[c].nodeType == 3) s++; } return this.parentNode.getXPath() +"/text()["+ s +"]"; } // CDATA_SECTION_NODE // ENTITY_REFERENCE_NODE // ENTITY_NODE // PROCESSING_INSTRUCTION_NODE // COMMENT_NODE // DOCUMENT_NODE // DOCUMENT_TYPE_NODE // DOCUMENT_FRAGMENT_NODE // NOTATION_NODE else return this.parentNode.getXPath(); } /** * Deletes the contents of the first included range. * @type Void */ Selection.prototype.deleteContents = function() { var range = document.createRange(); range.setStart(this.anchorNode, this.anchorOffset); // Check to see if the focusNode is before the anchorNode if(range.comparePoint(this.focusNode, this.focusOffset)==-1) { range.setStart(this.focusNode, this.focusOffset); range.setEnd(this.anchorNode, this.anchorOffset); this.collapseToStart(); } else { range.setEnd(this.focusNode, this.focusOffset); this.collapseToEnd(); } range.deleteContents(); } /** * Stores the selection's key properties in a memento object for later restoration. * @type Object */ Selection.prototype.store = function() { if(!this.anchorNode) return null; var memento = new Object(); memento.anchorPath = this.anchorNode.getXPath(); memento.anchorOffset = this.anchorOffset; memento.focusPath = this.focusNode.getXPath(); memento.focusOffset = this.focusOffset; return memento; } /** * Restores the selection state from a memento object. * @param {Object} memento The output of Selection.store(). * @type Void */ Selection.prototype.restore = function(memento) { try { var anchorNode = executeXPath(memento.anchorPath)[0]; var focusNode = executeXPath(memento.focusPath)[0]; if(!anchorNode.nodeType || !focusNode.nodeType) return; } catch(e) { return; } var range = document.createRange(); range.setStart(anchorNode, memento.anchorOffset); // Check to see if the focusNode is before the anchorNode if(range.comparePoint(focusNode, memento.focusOffset)==-1) { range.setStart(focusNode, memento.focusOffset); range.setEnd(anchorNode, memento.anchorOffset); } else range.setEnd(focusNode, memento.focusOffset); this.removeAllRanges(); this.addRange(range); } /** * Creates a new insertion point from the focusNode and focusOffset. * @type InsertionPoint */ Selection.prototype.createInsertionPoint = function() { if(!this.focusNode || this.focusOffset==undefined) return null; else return new InsertionPoint(this.focusNode, this.focusOffset); } /** * An insertion point is the pair of a text node and an offset within that node which is an approprite location for text to be inserted in the document. A text node with no contents is not a valid insertion point. Unless CSS white-space="pre", a text node which contains nothing but white-space characters is not a valid insertion point either. * @constructor * @param {Text} node A text node. * @param {Integer} offset The offset within that node. */ function InsertionPoint(node, offset) { /** * @private */ this._node = node; /** * @private */ this._offset = offset; } // Define some regular expressions; InsertionPoint.prototype._matchLeadingWS = /^(\s*)/; InsertionPoint.prototype._matchTrailingWS = /(\s*)$/; InsertionPoint.prototype._matchNonWS = /\S/; /** * Gets the current node. * @type Node */ InsertionPoint.prototype.getNode = function() { return this._node; } /** * Sets the current node. * @param {Node} node A text node. * @type Node */ InsertionPoint.prototype.setNode = function(node) { this._node = node; return node; } /** * Gets the offset in the current node. * @type Integer */ InsertionPoint.prototype.getOffset = function() { return this._offset; } /** * Sets the offset in the current node. * @param {Integer} offset * @type Integer */ InsertionPoint.prototype.setOffset = function(offset) { this._offset = offset; return offset; } /** * Changes the selection to match the IP. * @type Void */ InsertionPoint.prototype.select = function() { var selection = window.getSelection(); var range = document.createRange(); range.setStart(this.getNode(), this.getOffset()); selection.removeAllRanges(); selection.addRange(range); } /** * Extends the selection to the IP. * @type Void */ InsertionPoint.prototype.extend = function() { window.getSelection().extend(this.getNode(), this.getOffset()); } /** * Sets the node and offset to the next insertion point. * @type Void */ InsertionPoint.prototype.next = function() { this._seek("next"); } /** * Sets the node and offset to the previous insertion point. * @type Void */ InsertionPoint.prototype.previous = function() { this._seek("previous"); } /** * Sets the node and offset to the next insertion point. * <p>If the offset is at the end of the node, the method seeks the next text node. It then measures the length of the whitespace after the offset (if any). Then "moveBy" is set based on the lenght of the result and the CSS white-space mode. If the length takes the offset to the end of the node, the method is called again on the next text node. * @param {String} direction Can be "next" or "previous". * @type Void */ InsertionPoint.prototype._seek = function(direction) { var content = this.getNode().textContent; if(direction=="next" && this.getOffset() == content.length) this._seekTextNode("next"); else if(direction=="previous" && this.getOffset() == 0) this._seekTextNode("previous"); var substring, result; if(direction=="next") { substring = content.substring(this.getOffset(), content.length); result = this._matchLeadingWS(substring); } else { substring = content.substring(0, this.getOffset()); result = this._matchTrailingWS(substring); } var moveBy = 0; if(result[0].length < 2) moveBy = 1; else if(document.defaultView.getComputedStyle(startNode.parentNode, '').getPropertyValue("white-space").toLowerCase() == "pre") moveBy = 1; else if(result[0].length < substring.length) moveBy = result[0].length; else if(result[0].length == substring.length) { this._seekTextNode(direction); this._seek(direction); return; } else throw Error("Unhandled case in InsertionPoint.seek()"); if(direction=="next") this.setOffset(this.getOffset() + moveBy); else this.setOffset(this.getOffset() - moveBy); } /** * Sets the node to the next text node. * @param {String} direction Can be "next" or "previous". * @type Void */ InsertionPoint.prototype._seekTextNode = function(direction) { if(!mozile || !mozile.getCurrentEditor() || !mozile.getCurrentEditor().isAncestorOf(this.getNode())) return; var treeWalker = document.createTreeWalker(mozile.getCurrentEditor(), NodeFilter.SHOW_TEXT, null, false); // Find this.getNode while(treeWalker.currentNode != this.getNode()) treeWalker.nextNode(); var checkNode; // Loop until a non-white-space or "pre" text node is reached do { if(direction=="next") checkNode = treeWalker.nextNode(); else checkNode = treeWalker.previousNode(); // If the node contains non-white-space characters then use this node. if(this._matchNonWS.test(checkNode.textContent)) break; // If the parent's CSS "white-space=pre" then use this node. if(document.defaultView.getComputedStyle(checkNode.parentNode, '').getPropertyValue("white-space").toLowerCase()=="pre") break; } while(checkNode); if(checkNode) this.setNode(checkNode); if(direction=="next") this.setOffset(0); else this.setOffset(checkNode.textContent.length); } /** Mozile Component - * Class defining Mozile components with subclasses for modules, commands, and the mediator. * <p>Configuration String format (some options may conflict): "root='path/to/mozile', mode=XHTML, namespace='http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd', keyboardShortcuts=true, toolbarPosition=fixed, toolbarUpdateFrequency=2, warnBeforeUnload=true, debugLevel=0". See the notes for "Mozile.parseConfig" for proper configuration string format. * @constructor * @param {String} configString A properly formatted configuration string. */ function MozileComponent(configString) { /** * @private * @type String */ this._configString = String(configString); } /** * Returns "[object MozileComponent]". * @type String */ MozileComponent.prototype.toString = function() { return "[object "+ this.constructor.toString().match(/^function\s+(\w+)/)[1] +"]"; } /** * Get the name from the config string. It will be the first word. * @type String * @return The resource name. */ MozileComponent.prototype.getName = function() { if(!this._name) { // Determine Name var firstWord = /\s*(\w*)/; var name = firstWord.exec(this.getConfigString())[1]; if(name) { /** * @private * @type String */ this._name = name; } else { throw Error("Invalid configuration string."); } } return this._name; } /** * @type String * @return The configuration string. */ MozileComponent.prototype.getConfigString = function() { return this._configString; } /** * @type Object * @return The options object -- treat as a named key-value array or a hash table. */ MozileComponent.prototype.getOptions = function() { if(this._options) return this._options; else return this._parseOptions(); } /** * Gets a value from the options for a given key * @param {String} key * @param defaultValue Optional. If this is given and the key does not exist then defaultValue is assigned and returned. * @type String * @return The value if found or the defaultValue if given. Otherwise undefined. */ MozileComponent.prototype.getOption = function(key, defaultValue) { if(this.getOptions()[key] != undefined) return this.getOptions()[key]; else if(defaultValue != undefined) return this.setOption(key, defaultValue); else return undefined; } /** * Gets a value from the options for a given key * @param {String} key * @param value * @return The value given. */ MozileComponent.prototype.setOption = function(key, value) { this.getOptions()[key] = this._cleanOption(value); return value; } /** * Tries to convert a string into a number or boolean. * @param {String} key * @return The converted value. */ MozileComponent.prototype._cleanOption = function(val) { if(String(val).toLowerCase() == "false") val = false; else if(String(val).toLowerCase() == "true") val = true; else if(!isNaN(Number(val))) val = Number(val); return val; } /** * Parses the configuration string into an option array. * @private * @param {String} optionString The option string, following the standard format for module or command options (see "mozile.js" under "Declare Modules", or the Mozile.createCommand() method in this file). * @return A module options array. * @type Array */ MozileComponent.prototype._parseOptions = function() { var options = new Object(); this._options = options; // If there is no colon there are no options, so return. if(this.getConfigString().indexOf(":")==-1) { return this._options; } var optionString = this.getConfigString().substring(this.getConfigString().indexOf(":")+1, this.getConfigString().length); // get arrays, which have the format "name=[val1, 'val2', val3]" var arrayPattern = /(\S+)=\[(.*?)\]/; var leading = /^\s*'?/; var trailing = /'?\s*$/; while(optionString.match(arrayPattern)) { optionString = optionString.replace(arrayPattern, function(word) { var whole = word.match(arrayPattern); if(whole.length == 3) { var results = new Array(); var parts = whole[2].split(","); var part; for(var p=0; p<parts.length; p++) { part = parts[p].replace(leading, '').replace(trailing, ''); if(part != "") results.push( MozileComponent.prototype._cleanOption(part)); } options[whole[1]] = results; } return ""; } ); } var optionArray = optionString.split(","); // Now parse each option into key-value pairs, and add them to the options as options[key]=value. // This regular expression matches two formats: with='spa ces' and without=spaces. var parseOption = /(\S+)='(.+)'|(\S+)=(\S+)/; var option, arr; for(o in optionArray) { option = optionArray[o]; arr = parseOption.exec(option); if(arr) { var key,val; if(!arr[1] && !arr[2]) key=arr[3],val=arr[4]; else key=arr[1],val=arr[2]; options[key] = this._cleanOption(val); } } return this._options; } /** Mozile Module - * Subclass of MozileComponent designed for manipulating modules. * @constructor * @param {String} configString A properly formatted configuration string. */ function MozileModule(configString) { /** * @private * @type String */ this._configString = String(configString); } MozileModule.prototype = new MozileComponent; MozileModule.prototype.constructor = MozileModule; /** * Gets the id for the module, generating it if necessary. * @type String */ MozileModule.prototype.getId = function() { if(!this._id) this._id = "Mozile-"+ this.getName() +"-"+ this.getName() +".js"; return this._id; } /** * Gets the path to the module, generating it if necessary. * @type String */ MozileModule.prototype.getPath = function() { if(!this._path) { var path=""; if(this.getOption("remotePath")) path = this.getOption("remotePath"); else path = mozile.getRoot() +"modules/"; path = path + this.getName(); if(this.getOption("remoteVersion")) path = path +"-"+ this.getOption("remoteVersion"); this._path = path +"/"; } return this._path; } /** * Gets the source to the module, generating it if necessary. * @type String */ MozileModule.prototype.getSource = function() { if(!this._source) this._source = this.getPath() + this.getName() +".js"; return this._source; } /** * Loads the module's main script, and registers the module with the Mozile object. * @type Void */ MozileModule.prototype.load = function() { var script = new MozileScriptResource(this.getId(), this.getSource()); script.load(); // Register the module with the global mediator mozile.addModule(this); this.setOption("version", "Unknown Version"); } /** * Initializes the module. * @type Void */ MozileModule.prototype.init = function() { } /** Mozile Mediator - * Coordinates the behaviour of the other objects. Follows the Mediator design pattern. The Mozile code base relies on there being a single global instance of this object called "mozile". * <p>Configuration String format (some options may conflict): "root='path/to/mozile', mode=XHTML, namespace='http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd', keyboardShortcuts=true, toolbarPosition=fixed, toolbarUpdateFrequency=2, warnBeforeUnload=true, debugLevel=0". See the notes for "Mozile.parseConfig" for proper configuration string format. * @constructor * @param {String} configString A properly formatted configuration string. */ function MozileMediator(configString) { /** * @private * @type String */ this._configString = String("MozileMediator: "+configString); /** * True if editing is ON, false if editing is OFF. * @private * @type Boolean */ //this._editable = false; /** Mozile - Key Counter - * Counts the number of keypresses which lead to character insertion. Used to trigger storeState undo/redo steps. * @type Integer */ this.keyCounter = 0; /** Mozile - Changes Saved - * This is true after any of the output methods (documentToXML, etc.) have been called, and false after any other command. */ this.changesSaved = true; // Define some default options this.getOption("mode", "XHTML"); this.getOption("toolbarPosition", "absolute"); // can be "absolute" or "fixed" this.getOption("maxKeyCount", 20); this.getOption("toolbarUpdateFrequency", 2); this.getOption("defaultInterval", 100); this.getOption("keyboardShortcuts", true); this.getOption("preloaded", false); this.getOption("loadJIT", false); this.getOption("activateOnFocus", true); this.getOption("replaceAnchors", true); // Save option defaults this.getOption("content", "document"); // can be "document" or "editor" // Set up the onbeforeunload event handler. If warnBeforUnload has been set to "false", then the function returns nothing. Otherwise (the default case) a message is returned. if(this.getOption("warnBeforeUnload", true)) { window.onbeforeunload = function() { if(!mozile.changesSaved) return "There are unsaved changes in this document. Changes will be lost if you navigate away from this page."; else return null; } } else { window.onbeforeunload = function() { return; } } if(!this.isExtension()) { this.setSharedData("enhancement", "false"); this.setSharedData("editable", "false"); this.setSharedData("activateOnFocus", this.getOption("activateOnFocus")); } /** * @private */ this._interfaces = new Array( {name: "MozileAboutInterface", access: "mozile.getAboutInterface()", source: "core/about.xml"}, {name: "MozileMessageInterface", access: "mozile.getMessageInterface()", source: "core/message.xml"}, {name: "MozileSaveInterface", access: "mozile.getSaveInterface()", source: "core/save.xml"}, {name: "MozileSourceInterface", access: "mozile.getSourceInterface()", source: "core/source.xml"}, {name: "HTTPPostInterface", access: "mozile.getModule('HTTPPost').getInterface()", source: "modules/HTTPPost/savemsg.xml"} ); } MozileMediator.prototype = new MozileComponent; MozileMediator.prototype.constructor = MozileMediator; MozileMediator.prototype.debug = mozileDebug; /** * @type String */ MozileMediator.prototype.getVersion = function() { return mozileVersion; } /** * The root directory for this Mozile installation. This file "core.js" should be found at "[root]core/core.js". * @type String */ MozileMediator.prototype.getRoot = function() { if(!this.getOption("root")) throw Error("Invalid configuration string."); return this.getOption("root"); } /** * Returns true if this object is running in the Mozile Extension, and false otherwise. * @type Boolean */ MozileMediator.prototype.isExtension = function() { if(this._extension == undefined) { try { if(mozileExtension) this._extension = true; } catch(e) { this._extension = false; } } return this._extension; } /** * Returns true if the Mozile Extension is enhancing this object, and false otherwise. Can also check for a particular enhancement. * @param {String} name Optional. The name of the enhancement to be checked. * @type Boolean */ MozileMediator.prototype.isEnhanced = function(name) { if(this.getSharedData("enhancement")==undefined) return false; else if(name && typeof(this.getSharedData("enhancement"))=="string") { var enhancements = this.getSharedData("enhancement").split(";"); for(var i=0; i < enhancements.length; i++) { if(enhancements[i] == name) return true; } return false; } else return this.getSharedData("enhancement"); } /** * The mode controls which tools are used to manipulate the document. The options are "HTML", "XHTML", and "XML". Each of the three modes has its quirks. * TODO: Currently, Mozile makes no use of the mode. * @type String */ MozileMediator.prototype.getMode = function() { return this.getOption("mode"); } /** * An integer indicating how verbose debugging should be. 0 means only critical errors are shown. Higher values mean less verbose debugging: 4="critical", 3="very important", 2="important", 1="normal", 0="not important". Only messages with the specified level or higher will be logged. * @type Integer */ MozileMediator.prototype.getDebugLevel = function() { return this.getOption("debugLevel", 4); } /** * Get an attribute from the core.js script tag. * @param {String} attribute The attribute name. * @type String */ MozileMediator.prototype.getSharedData = function(attribute) { if(document.getElementById("Mozile-Core-core.js") && document.getElementById("Mozile-Core-core.js").hasAttribute(attribute)) { var value = document.getElementById("Mozile-Core-core.js").getAttribute(attribute); return this._cleanOption(unescape(value)); } else return undefined; } /** * Set an attribute from the core.js script tag. * @param {String} attribute * @param {String} value * @type String */ MozileMediator.prototype.setSharedData = function(attribute, value) { if(!document.getElementById("Mozile-Core-core.js")) return value; if(value=="") document.getElementById("Mozile-Core-core.js").removeAttribute(attribute); else document.getElementById("Mozile-Core-core.js").setAttribute(attribute, escape(value)); return value; } /** * Checks for changes to the attributes. * @type Void */ MozileMediator.prototype.watchSharedData = function() { if(!this._watches) { this._watches = new Object(); this._watches["editable"] = {value: undefined, ontrue: "mozile.startEditing()", onfalse: "mozile.stopEditing()"}; this._watches["activateOnFocus"] = {value: undefined, onchange: "mozile.setOption('activateOnFocus', value)"}; this._watches["serverRequest"] = {value: undefined, onchange: "eval(value); this.setSharedData('serverRequest', '')"}; } var value; var watches = this._watches; for(key in watches) { if(!watches[key]) continue; value = this.getSharedData(key); if(value != watches[key]["value"]) { try { if(value==null) { /* do nothing */ } else if(value == "true" && watches[key]["ontrue"]) eval(watches[key]["ontrue"]); else if(value == "false" && watches[key]["onfalse"]) eval(watches[key]["onfalse"]); else if(watches[key]["onchange"]) eval(watches[key]["onchange"]); } catch(e) { } // Key might have been erased if(watches[key]) watches[key]["value"] = value; } } } /** * If Mozile is enhanced, sends a request to the MozileExtension. * @param {String} requestCode Only a few request types are handled: "getClipboard", "setClipboard". * @type Void */ MozileMediator.prototype.clientRequest = function(requestCode, evalOnChange) { if(!mozile.isEnhanced()) return; this.clearRequest(); this._watches["serverReply"] = {value: "", onchange: "try{"+evalOnChange+"}catch(e){};mozile.clearRequest()" } this.setSharedData("clientRequest", requestCode); } /** * Clears the shared data attributes relevat to client requests. * @type Void */ MozileMediator.prototype.clearRequest = function() { mozile._watches["serverReply"] = undefined; mozile.setSharedData("serverReply", ""); mozile.setSharedData("clientRequest", ""); } /** * An associative array containing all the MozileResource objects for scripts, links, and styles. * <p>TODO: What should be done about core.js and interface.js? * @type Object */ MozileMediator.prototype.getResources = function() { if(!this._resources) this._resources = new Object(); return this._resources; } /** * Gets a resource from the list by its id. * @param {String} id * @type MozileResource */ MozileMediator.prototype.getResource = function(id) { if(this.getResources()[id]) return this.getResources()[id]; else return undefined; } /** * Adds a resource to the resources list. * @param {MozileResource} resource * @type MozileResource * @return The resource object given. */ MozileMediator.prototype.addResource = function(resource) { if(resource.getId()) this.getResources()[resource.getId()] = resource; else throw Error("Invalid resource."); return resource; } /** * An associative array containing all the loaded module objects, indexed by their names. * @type Object */ MozileMediator.prototype.getModules = function() { if(!this._modules) this._modules = new Object(); return this._modules; } /** * Gets a module from the list by its name. * @param {String} name * @type MozileModule */ MozileMediator.prototype.getModule = function(name) { if(this.getModules()[name]) return this.getModules()[name]; else return undefined; } /** * Adds a module to the modules list. * @param {MozileModule} module * @type MozileModule * @return The module object given. */ MozileMediator.prototype.addModule = function(module) { if(module.getName()) this.getModules()[module.getName()] = module; else throw Error("Invalid module."); return module; } /** * An array containing entries for every editor in the document. * @type Array */ MozileMediator.prototype.getEditors = function() { if(!this._editors) this._editors = new Array(); return this._editors; } /** * Returns true if the given element is in the editors array. * @type Boolean */ MozileMediator.prototype.isEditor = function(element) { for(var i=0; i < this.getEditors().length; i++) { if(this.getEditors()[i] == element) return true; } return false; } /** * Adds an element to the editors list. * @param {Element} editor * @type Element * @return The element given. */ MozileMediator.prototype.addEditor = function(editor) { this.getEditors().push(editor); return editor; } /** * Get the element of the editor which last had the focus. * @type Element */ MozileMediator.prototype.getCurrentEditor = function() { if(this._currentEditor) return this._currentEditor; else return undefined; } /** * Sets the current editor. * @param {Element} element The root element of the current editor. * @type Element */ MozileMediator.prototype.setCurrentEditor = function(element) { this._currentEditor = element; return element; } /** * Gets the CSSStyleSheet object which is manipulated to add the XBL bindings which control the Mozile toolbar and the Mozile editors. Adds the stylesheet if needed. * @type CSSStyleSheet */ MozileMediator.prototype.getStyleSheet = function() { if(!this._styleSheet) { var style = new MozileStyleResource("Mozile-Core-StyleSheet"); style.load(); this._styleSheet = style.getStylesheet(); // Add the rules to the sheet. this._styleSheet.insertRule("mozileAnchorReplacement { color: blue; text-decoration: underline; }", this._styleSheet.cssRules.length); } return this._styleSheet; } /** * Mozile checks the UserAgent string for the browser, and tries to determine what operating system the browser is running under. Can be "Linux", "Windows", or "Mac". Note that the UserAgent can be spoofed, so this is not entirely reliable. * @type String */ MozileMediator.prototype.getOperatingSystem = function() { if(!this._operatingSystem) { var userAgent = navigator.userAgent.toLowerCase(); if(userAgent.indexOf("windows") >= 0) this._operatingSystem = "Windows"; if(userAgent.indexOf("linux") >= 0) this._operatingSystem = "Linux"; if(userAgent.indexOf("macintosh") >= 0) this._operatingSystem = "Mac"; } return this._operatingSystem; } /** * Gets the name of the browser. Can be "Firefox", "SeaMonkey", or "Mozilla" (which is the default). * @type String */ MozileMediator.prototype.getBrowserName = function() { if(!this._browserName) { var userAgent = navigator.userAgent.toLowerCase(); if(userAgent.indexOf("firefox") >= 0) this._browserName = "Firefox"; else if(userAgent.indexOf("seamonkey") >= 0) this._browserName = "SeaMonkey"; else this._browserName = "Mozilla"; } return this._browserName; } /** * Gets the version number for the browser. * @type String */ MozileMediator.prototype.getBrowserVersion = function() { if(!this._browserVersion) { var userAgent = navigator.userAgent; if(this.getBrowserName()=="Firefox") this._browserVersion = userAgent.match(/Firefox\/(\S+)/)[1]; else if(this.getBrowserName()=="SeaMonkey") this._browserVersion = userAgent.match(/SeaMonkey\/(\S+)/)[1]; else this._browserVersion = this.getMozillaVersion(); } return this._browserVersion; } /** * Gets the Gecko version for the browser. * @type String */ MozileMediator.prototype.getGeckoVersion = function() { if(!this._geckoVersion) { this._geckoVersion = navigator.userAgent.match(/Gecko\/(\S+)/)[1]; } return this._geckoVersion; } /** * Gets the version of Mozilla on which the browser is based. * @type String */ MozileMediator.prototype.getMozillaVersion = function() { if(!this._mozillaVersion) { this._mozillaVersion = navigator.userAgent.match(/rv\:(\S+)\)/)[1]; } return this._mozillaVersion; } /** * "true" if editing has been started, "false" otherwise. * @type Boolean */ MozileMediator.prototype.isEditable = function() { return this.getSharedData("editable"); // return this._editable; } /** * Enables editing in the document. * @type Void */ MozileMediator.prototype.startEditing = function() { if(!this.isEditable()) { //this._editable = true; this.setSharedData("editable", "true"); this.load(); this.showCaret(); this.showToolbars(); this.storeState("Editing started"); } } /** * Disables editing in the document. * @type Void */ MozileMediator.prototype.stopEditing = function() { if(this.isEditable() != false) { mozileEditor.restoreAnchors(); this.hideCaret(); this.hideToolbars(); //this._editable = false; this.setSharedData("editable", "false"); } } /** Mozile - Status - * Sets the content of the Mozile statusbar and enters a special kind of debugging message. In addition to the normal debug arguments, status messages can include a "value" argument which indicates the percentage displayed on the progress bar (if none is given or the value is "false" then the progress bar is hidden). They can also include a "more" string, which will be entered into the "oncommand" attribute of the mozileMoreButton; this is usually used to popup a window or dialog with additional information. * * @param details An array of information. The first two fields are "File", and "Function" (the a function name). Fancier debugging functions might use more fields. * @param level An integer describing the importance of the debugging message. 4="critical", 3="very important", 2="important", 1="normal", 0="not important". * @param message A string containing the debugging message. * @param value Optional An integer between 0 and 100 indicating the value of the progressmeter. If no value is provided or the value is "false", the progressmeter is not shown. * @param more Optional A string which is placed in the "oncommand" attribute of the "mozileMoreButton". If no string is provided, the button is not shown. * @return Always true. */ MozileMediator.prototype.status = function(details, level, message) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile.status()"; //this.debug(f,1,"Setting status"); try { var value = false; if(arguments.length > 3) { value = arguments[3]; } var more = false; if(arguments.length > 4) { more = arguments[4]; } //this.debug(f,1,"Setting status to: "+ message +" -> "+ value +"% -> "+ more); // Send this message to the debug function. var g = new Array(); for(key in details) { g[key] = details[key]; } g["Status Message"]=true; var msg = message; if(value) msg = msg +" "+ value +"%"; this.debug(g, level, msg); // If the statusbar doesn't exist, just return now. //if(!this._statusbar) return true; var messageField = document.getElementById("mozileStatusMessage"); var progressmeter = document.getElementById("mozileProgressmeter"); var moreButton = document.getElementById("mozileMoreButton"); if(!messageField || !progressmeter || !moreButton) return true; messageField.value = message; // If "value" is set, show the progressmeter and set its value. if(value != false) { progressmeter.value = value; progressmeter.collapsed=false; } // Otherwise hide the progresmeter and zero it. else { progressmeter.value = 0; progressmeter.collapsed=true; } // If "more" is set, show the moreButton and set the oncommand attribute. // should be "oncommand" but thestatusbar tends to disappear too fast... if(more != false) { moreButton.setAttribute("onmousedown", more); moreButton.collapsed = false; } // Otherwise hide it and clear the oncommand attribute. else { moreButton.setAttribute("onmousedown", ""); moreButton.collapsed = true; } } catch(e) { alert(e); //this.debug(f,2,"Failed to set status message: "+ details["File"] +", "+ details["function"] +", "+ message); } return true; } /** * Activates the editing caret. It requires privileges to set the "browsewithcaret" preference. When there are no privileges the "editing" shared data attribute is set instead, and the Mozile Extension will activate the caret if it is present. * @type Boolean */ MozileMediator.prototype.showCaret = function() { if(this.isExtension()) { if(!this._mozilePrefs) { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); this._mozilePrefs = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getBranch(null); } this._mozilePrefs.setBoolPref('accessibility.browsewithcaret', true); return true; } else { this.setSharedData("editing", "true"); return false; } } /** * Disables the editing caret unless the "caretAlwaysOn" preference is set. * @type Boolean */ MozileMediator.prototype.hideCaret = function() { if(this.isExtension()) { if(!this._mozilePrefs) { netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); this._mozilePrefs = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getBranch(null); } if(!this._mozilePrefs.getBoolPref("mozile.caretAlwaysOn")) { this._mozilePrefs.setBoolPref('accessibility.browsewithcaret', false); } return true; } else { this.setSharedData("editing", "false"); return false; } } /** Mozile - Create Editor - * Creates a Mozile editor in the document using its id. Two CSS rules are added to this.styleSheet, the first of which binds an XBL widget to the element and captures the focus, while the second tells all children to ignore the focus. * * @param id String. The id of the element to be made an editor. * @param options String. A list of options which will be used in this editor. TODO: No options are currently implemented. * @return True if successful. */ MozileMediator.prototype.createEditor = function(id, options) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile.createEditor()"; this.debug(f,1,"Creating editor "+ id +" "+ options); // Define the new rules. var rule1 = "#"+ id +" { -moz-binding: url(" + this.getRoot() +"core/core.xml#editor); -moz-user-focus: normal; -moz-user-modify: read-write; -moz-user-input: auto; -moz-user-select: text; }"; var rule2 = "#"+ id +" * { -moz-user-focus: ignore; }"; var rule3 = "#"+ id +" input { -moz-user-focus: none; -moz-user-modify: read-only; -moz-user-input: auto; -moz-user-select: text; }"; // Add the rules. this.getStyleSheet().insertRule(rule1, this.getStyleSheet().cssRules.length); this.getStyleSheet().insertRule(rule2, this.getStyleSheet().cssRules.length); this.getStyleSheet().insertRule(rule3, this.getStyleSheet().cssRules.length); return true; } /** Mozile - Create Editors - * Creates multiple Mozile editors in the document, using a CSS selector. * * @param selector String. The id of the element to be made an editor. * @param options String. A list of options which will be used in this editor. TODO: No options are currently implemented. * @return True if successful. */ MozileMediator.prototype.createEditors = function(selector, options) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile.createEditors()"; this.debug(f,1,"Creating editor "+ selector +" "+ options); // Define the new rules var rule1 = selector +" { -moz-binding: url(" + this.getRoot() +"core/core.xml#editor); -moz-user-focus: normal; -moz-user-modify: read-write; -moz-user-input: auto; -moz-user-select: text; }"; var rule2 = selector +" * { -moz-user-focus: ignore; }"; var rule3 = selector +" input { -moz-user-focus: none; -moz-user-modify: read-only; -moz-user-input: auto; -moz-user-select: text; }"; // Add the rules to the sheet. this.getStyleSheet().insertRule(rule1, this.getStyleSheet().cssRules.length); this.getStyleSheet().insertRule(rule2, this.getStyleSheet().cssRules.length); this.getStyleSheet().insertRule(rule3, this.getStyleSheet().cssRules.length); return true; } /** Mozile - Make Document Editable - * Makes the entire document editable. Instead of using XBL bindings on elements selected by CSS, this function creates a number of document-wide eventListeners. * * @param options String. A list of options which will be used in this editor. TODO: No options are currently implemented. * @return True if successful. */ MozileMediator.prototype.makeDocumentEditable = function(options) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile.makeDocumentEditable()"; this.debug(f,1,"Making document editable "+ options); //this.createEditors("body > center",""); //return true; var events = ["focus", "blur", "keydown", "keypress", "keyup", "mousedown", "click", "mouseup", "dblclick", "select"]; for(var e=0; e< events.length; e++) { document.addEventListener(events[e], mozileHandleEvent, false); } // Get the root element name var rootNode = document.getBody(); var rootName = rootNode.nodeName; // Create the CSS rules var rule1 = rootName +" { -moz-user-focus: normal; -moz-user-modify: read-write; -moz-user-input: auto; -moz-user-select: text; }"; var rule2 = rootName +" * { -moz-user-focus: ignore; }"; var rule3 = rootName +" input { -moz-user-focus: none; -moz-user-modify: read-only; -moz-user-input: auto; -moz-user-select: text; }"; // Apply the CSS rules to the sheet. this.getStyleSheet().insertRule(rule1, this.getStyleSheet().cssRules.length); this.getStyleSheet().insertRule(rule2, this.getStyleSheet().cssRules.length); this.getStyleSheet().insertRule(rule3, this.getStyleSheet().cssRules.length); this.addEditor(rootNode); rootNode.setAttribute('tabindex',12345); return true; } /** * Removes any interfaces which may have been injected into the document by the Mozile Extension. * @type Void */ MozileMediator.prototype._cleanInterfaces = function() { var element; for (var i=0; i < this._interfaces.length; i++) { try { eval(this._interfaces[i]["access"] +".hide()"); } catch(e) { element = document.getElementById(this._interfaces[i]["name"]); if(element) element.parentNode.removeChild(element); } } } /** * The XML declaration for the document, if there is one. * @private * @type String */ MozileMediator.prototype._getXMLDeclaration = function() { var xmlDeclaration = ""; if(document.xmlVersion) { xmlDeclaration = '<?xml version="'+ document.xmlVersion +'" encoding="'+ document.xmlEncoding +'"?>\n' } return xmlDeclaration; } /** * The DOCTYPE declaration for the document, if there is one. * @private * @type String */ MozileMediator.prototype._getDoctypeDeclaration = function() { // Get the DOCTYPE if there is one, and serialize it. var doctypeDeclaration = "" if(document.doctype) { var serializer = new XMLSerializer; doctypeDeclaration = serializer.serializeToString(document.doctype) +"\n"; } return doctypeDeclaration; } /** * The Processing Instructions for the document, if there are any. * @private * @type String */ MozileMediator.prototype._getProcessingInstructions = function() { var evaluator = new XPathEvaluator(); var PIString = ""; var PIList = evaluator.evaluate("/processing-instruction()", document, null, XPathResult.ANY_TYPE, null); var PI = PIList.iterateNext(); while (PI) { PIString += "<?"+ PI.target +" "+ PI.data + "?>\n"; PI = PIList.iterateNext(); } return PIString; } /** * Removes all traces of Mozile in the document. It does this by removing all of the resources on the resourceList, the toolbar element, and the "tabindex" attributes of all elements with tabindex=12345. It's meant to act on a clone of the document element, and not the document itself (which would disable Mozile in the process). * * @param {Element} element The root element from which all other elements will be removed. * @type Element * @return The cleaned up element. */ MozileMediator.prototype._cleanDOM = function(element) { // Unload all resources. for(var id in this.getResources()) { this.getResource(id).unload(element); } // Fix any altered anchor tags. mozileEditor.restoreAnchors(element); // Get rid of any MozileToolbar elements. var mozileToolbars = element.getElementsByTagName("mozileToolbar"); while(mozileToolbars.length){ mozileToolbars[0].parentNode.removeChild(mozileToolbars[0]); } /** Walk the tree... * - Remove any "mozileInterface" elements (which might have been injected by the Extension but never used). * - Remove "MozileToolbar" or "MozileStatusbar" if found. * - Clean up the "tabindex" attributes that we've added. This is an effective but inefficient method: use a treewalker over elements, and check for a "tabindex" matching the (hopefully unique) code 12345. */ var treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT, null, false); var current = treeWalker.firstChild(); var removals = new Array(); while(current) { if(current.hasAttribute("class") && current.getAttribute("class") == "mozileInterface") removals.push(current); // mark for removal else if(current.hasAttribute("id") && current.getAttribute("id") == "MozileToolbar") removals.push(current); else if(current.hasAttribute("id") && current.getAttribute("id") == "MozileStatusbar") removals.push(current); else if(current.getAttribute("tabindex") && current.getAttribute("tabindex") == "12345") current.removeAttribute("tabindex"); current = treeWalker.nextNode(); } while(removals.length) { removals[0].parentNode.removeChild(removals[0]); removals.shift(); } // Check the element itself for the tabindex attribute. if(element.getAttribute("tabindex") && element.getAttribute("tabindex") == "12345") { element.removeAttribute("tabindex"); } return element; } /** * Convert HTML to xhtml compatible. All tag names are converted to lower case. Converts a whole html document or just a fragment. The string to be converted must have been generated by the XML Serializer. * @private * @param {String} contents The string to be converted. * @type String * @return The converted string. */ MozileMediator.prototype._htmlToXHTML = function(contents) { if(!this._tagPattern) this._tagPattern = /<(\/*)(\w*)/g; /* match tags */ // use an anonymous replace function to convert to lower case. return contents.replace(this._tagPattern, function(word) { return word.toLowerCase(); }); } /** * Convert HTML to xhtml compatible. All tag names are converted to lower case. The string to be converted must have been generated by the XML Serializer. * @private * @param {String} contents The string to be converted. * @param {String} charset Optional. Specifies a character set, such as "UTF-8". * @param {Array} attrList Optional. See @see{_getConversionAttributes}. * @param {Array} entityList Optional. See @see{#_getEntityVersion}. * @type String * @return The converted string. If the conversion fails, the original string is returned. */ MozileMediator.prototype._convertCharacterSet = function(contents, charset, attrList, entityList) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile._convertCharacterSet()"; if(!charset) charset = this.getSaveOption("current", "saveCharset", document.characterSet); var result = "Charset: "+ charset +"\n"; result += "Conversion Attributes: "+ this._getConversionAttributes() +"\n"; result += "Entity Version: "+ this._getEntityVersion() +"\n"; this.status(f,1,result); try { var SAC = Components.classes["@mozilla.org/intl/saveascharset;1"].createInstance(); SAC.QueryInterface(Components.interfaces.nsISaveAsCharset); SAC.Init(charset, this._getConversionAttributes(attrList), this._getEntityVersion(entityList)); return SAC.Convert(contents); }catch (e) { //throw Error("nsISaveAsCharset exception: "+e); return contents; } } /** * Given the "saveConversion" option array, this function computes an integer specifying what attributes should be converted by the nsISaveAsCharset.init method. Options are in groups: "none" or "entitybeforecharset" or "entitiyaftercharset"; "nofallback" or "questionmark" or "escapedunicode" or "decimalncr" or "hexncr"; "charsetfallback"; "ignoreignorables". For X/HTML the defaults are "entitybeforecharset", "ignoreignorables", "decimalncr". * @private * @param {Array} attrList Optional. * @type Integer * @return A PRUint32 signifying the attributes to convert. */ MozileMediator.prototype._getConversionAttributes = function(attrList) { if(!attrList) attrList = this.getSaveOption("current", "saveConversion"); if(!attrList && this.getMode() != "XML") attrList = ["entitybeforecharset", "ignoreignorables", "decimalncr" ]; var attr = 0; if ( (attrList instanceof Object) && (attrList instanceof Array) ) { var attributes = new Array(); attributes = attributes.concat(attrList); while ( attributes.length > 0 ) { switch ( attributes.shift() ) { case "nofallback" : attr &= ~0xFF; break; case "questionmark" : attr &= ~0xFF; attr |= 1; break; case "escapedunicode" : attr &= ~0xFF; attr |= 2; break; case "decimalncr" : attr &= ~0xFF; attr |= 3; break; case "hexncr" : attr &= ~0xFF; attr |= 4; break; case "none" : attr &= ~0x300; break; case "entitybeforecharset" : attr &= ~0x300; attr |= 0x100; break; case "entityaftercharset" : attr &= ~0x300; attr |= 0x200; break; case "charsetfallback" : attr |= 0x400; break; case "ignoreignorables" : attr |= 0x800; break; } } } return attr; } /** * Given the "saveEntities" option array, this function computes an integer specifying what entities which should be converted by the nsISaveAsCharset.init method. Options are: "none", "html40latin1", "html40symbols", "html40special", "mathml20", "transliterate". * @private * @param {Array} entityList Optional. * @type Integer * @return A PRUint32 signifying the attributes to convert. */ MozileMediator.prototype._getEntityVersion = function(entityList) { if(!entityList) entityList = this.getSaveOption("current", "saveEntities"); var version = 0; if ( (entityList instanceof Object) && (entityList instanceof Array) ) { var entities = new Array(); entities = entities.concat(entityList); while ( entities.length > 0 ) { switch ( entities.shift() ) { case "none" : entities = new Array(); version = 0; break; case "html40latin1" : version |= 1; break; case "html40symbols" : version |= 2; break; case "html40special" : version |= 4; break; case "transliterate" : version |= 8; break; case "mathml20" : version |= 16; break; } } } return version; } /** * Extract the contents of the document as HTML, first cleaning up any mess that Mozile has made. It maintains any XML declaration, XML processing instructions, and Doctype declarations it finds. * <p>Since editing modes are not yet supported, this method behaves very much the documentToXML method. * @type String * @return String of the serialized HTML contents. */ MozileMediator.prototype.documentToHTML = function() { var serializer = new XMLSerializer; var newDoc = document.documentElement.cloneNode(true); newDoc = this._cleanDOM(newDoc); var contents = this._getXMLDeclaration(); contents += this._getDoctypeDeclaration(); contents += this._getProcessingInstructions(); contents += serializer.serializeToString(newDoc); return contents; } /** Mozile - Document To XML - * Extract the contents of the document as XML, first cleaning up any mess that Mozile has made. It maintains any XML declaration, XML processing instructions, and Doctype declarations it finds. * <p>Because Mozile's mode support is not finished, this method isn't exactly as it should be. It lowercases all element names to make them look more like XML. * @type String * @return String of the serialized XML contents. */ MozileMediator.prototype.documentToXML = function() { // Lowercase the element names, and return the string. return this._htmlToXHTML(this.documentToHTML()); } /** * Extract the contents of the current editor as HTML, first cleaning up any mess that Mozile has made. A simpler version of the documentToHTML method. * @type String * @return String of the serialized XML contents. */ MozileMediator.prototype.editorToHTML = function() { if(!this.getCurrentEditor() || this.getCurrentEditor() == null) throw Error("Error: No editor selected!"); var serializer = new XMLSerializer; var editor = this.getCurrentEditor().cloneNode(true); editor = this._cleanDOM(editor); var contents = serializer.serializeToString(editor); return contents; } /** * Extract the contents of the current editor as XML, first cleaning up any mess that Mozile has made. A simpler version of documentToXML. * @type String * @return String of the serialized XML contents. */ MozileMediator.prototype.editorToXML = function() { return this._htmlToXHTML(this.editorToHTML()); } /** Mozile - Content - * Returns the string of Mozile's content, depending on the current save options. * * @return Always true. */ MozileMediator.prototype.content = function() { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile.content()"; this.debug(f,1,"Getting Mozile content."); var content = this.getSaveOption("current", "content"); var format = this.getSaveOption("current", "format"); if(format == "XHTML") format = "XML"; this.debug(f,1,"Saving "+ content +"To"+ format); var result; try { result = eval("this."+ content +"To"+ format +"()"); } catch(e) { this.debug(f,1,"Bad result: "+e); } return result; } /** Mozile - Store State - * This is the stub of a command used in the UndoRedo module. It does nothing except rest the keyCounter and send a status message. * * @return Always true. */ MozileMediator.prototype.storeState = function(command) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile.storeState()"; this.debug(f,1,"Storing current state"); // If this is the first change after a save, then change the status message and set the changesSaved to false. if(this.changesSaved == true) { this.status(f,1,"Editing"); this.changesSaved = false; } // Reset the key counter. this.keyCounter = 0; return true; } /** * Loads "interface.js" which is the second stage of Mozile loading. * @type Void */ MozileMediator.prototype.load = function() { if(this._loadComplete) return; if(mozileInterface) { this.addResource(new MozileScriptResource("Mozile-Core-core.js", this.getRoot() + "core/core.js")); if(this.getOption("preloaded")) this.addResource(new MozileScriptResource("Mozile-Core-mozile.js", this.getRoot() + "core/mozile.js")); var link = new MozileLinkResource("Mozile-Core-interface.css", this.getRoot() + "core/interface.css") link.load(); var script = new MozileScriptResource("Mozile-Core-interface.js", this.getRoot() +"core/interface.js"); if(this.getOption("preloaded")) this.addResource(script); else script.load(); var mozileModule; for(var m=0; m < mozileModules.length; m++){ mozileModule = new MozileModule(mozileModules[m]); if(this.getOption("preloaded")) { this.addResource(new MozileScriptResource(mozileModule.getId(), mozileModule.getSource())); this.addModule(mozileModule); } else mozileModule.load(); } var complete = new MozileScriptResource("Mozile-Core-loadComplete", ""); complete.setContent("mozile.loaded()"); if(this.getOption("preloaded")) this.addResource(complete); else complete.load(); } this._loadComplete = true; // load is complete, whether or not the interface was loaded. } /** * A "post-load" function which calls methods after load is complete. * @type Void */ MozileMediator.prototype.loaded = function() { if(this._loadedComplete) return; this._loadedComplete = true; if(this.isExtension()) return; for(var name in this.getModules()) { this.getModule(name).init(); } this.getToolbar(); this._cleanInterfaces(); var rootNode = document.getBody(); if(this.isEditor(rootNode)) this.startEditing(); } /** Mozile Editor - * Controls the manipulation of the document through the Selection object. * @constructor * @param {String} configString A properly formatted configuration string. */ function MozileEditor(configString) { /** * @private * @type String */ this._configString = String(configString); } MozileEditor.prototype = new MozileComponent; MozileEditor.prototype.constructor = MozileEditor; var mozileEditor = new MozileEditor("DefaultEditor"); /** * Get the last node edited. * @type Node */ MozileEditor.prototype.getLastNode = function() { if(this._lastNode) return this._lastNode; else return undefined; } /** * Set the last node edited. * @param {Node} node * @type Node */ MozileEditor.prototype.setLastNode = function(node) { this._lastNode = node; return node; } /** * Get the offset from the last node edited. * @type Integer */ MozileEditor.prototype.getLastOffset = function() { if(this._lastOffset) return this._lastOffset; else return undefined; } /** * Set the last node edited. * @param {Integer} offset * @type Integer */ MozileEditor.prototype.setLastOffset = function(offset) { this._lastOffset = offset; return offset; } /** * Get the keyCode of the last keypress. * @type Integer */ MozileEditor.prototype.getLastKeyCode = function() { if(this._lastKeyCode) return this._lastKeyCode; else return undefined; } /** * Set the last node edited. * @param {Integer} keyCode * @type Integer */ MozileEditor.prototype.setLastKeyCode = function(keyCode) { this._lastKeyCode = keyCode; return keyCode; } /** * Replace all "a" nodes with copies named "mozileLinkReplacement". * @param {Element} element Optional. The root element for the replacement. If none is given the mozile.currentEditor is used. * @type Integer * @return Number of elements replaced. */ MozileEditor.prototype.replaceAnchors = function(element) { if(!mozile.getOption("replaceAnchors")) return 0; var elements; if(arguments.length > 0) elements = element.getElementsByTagName("a"); else elements = mozile.getCurrentEditor().getElementsByTagName("a"); var newElement; var range = document.createRange(); var i=0, j=0; while(elements.length) { newElement = document.createElement("mozileAnchorReplacement"); // copy all attributes for(i=0; i < elements[0].attributes.length; i++) { newElement.setAttribute(elements[0].attributes[i].name, elements[0].attributes[i].value); } // Replace range.selectNodeContents(elements[0]); newElement.appendChild(range.extractContents()); elements[0].parentNode.replaceChild(newElement, elements[0]); j++ } return j; } /** * Restore all "a" nodes that were replaced. * @param {Element} element Optional. The root element for the replacement. If none is given the mozile.currentEditor is used. * @type Integer * @return Number of elements replaced. */ MozileEditor.prototype.restoreAnchors = function(element) { if(!mozile.getOption("replaceAnchors")) return 0; var elements; if(arguments.length > 0) elements = element.getElementsByTagName("mozileAnchorReplacement"); else if(!mozile.getCurrentEditor()) return 0; else elements = mozile.getCurrentEditor().getElementsByTagName("mozileAnchorReplacement"); var newElement; var range = document.createRange(); var i=0, j=0; while(elements.length) { newElement = document.createElement("a"); // copy all attributes for(i=0; i < elements[0].attributes.length; i++) { newElement.setAttribute(elements[0].attributes[i].name, elements[0].attributes[i].value); } // Replace range.selectNodeContents(elements[0]); newElement.appendChild(range.extractContents()); elements[0].parentNode.replaceChild(newElement, elements[0]); j++ } return j; } /** Mozile Editor - Insert String - * Inserts a string at the current selection index. If the selection is not collapsed, then the selection is deleted before the new string is inserted. The selection is then collapsed and set to the end of the inserted string. * * @param string The string to be inserted. * @return True when string has been inserted, false otherwise. */ MozileEditor.prototype.insertString = function(string) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile.insertString()"; mozile.debug(f,1,"Inserting string "+ string); // Get the current selection var selection = window.getSelection(); // If it is not collapsed, delete the contents of the selection if(!selection.isCollapsed) { this.deletion("next"); } // Get the focusNode, and make sure it's a text node var focusNode = selection.focusNode; if(focusNode.nodeType != 3) { // if it's not a text node, try its first child... selection.extend(focusNode.firstChild,0); focusNode = selection.focusNode; // if we still can't find it, fail if(focusNode.nodeType != 3) { mozile.debug(f,4,"This node is not a text node! " + focusNode); return false; } } // Insert the string at the focusOffset focusNode.insertData(selection.focusOffset, string); // Try to move the selection forward by the length of the string. try { selection.extend(selection.focusNode,selection.focusOffset+string.length); } catch(e) { alert("Error in insertString when trying to extend selection: "+e); } // Collapse the selection again. selection.collapseToEnd(); return true; } /** Mozile Editor - Insert Fragment - * Inserts all the children of a document fragment or an element at the current selection index. If the selection is not collapsed, then the selection is deleted before the fragment is inserted. * * @param fragment Either a document fragment, or an element with child nodes to be cloned and inserted. * @return True when the fragment has been inserted, false otherwise. */ MozileEditor.prototype.insertFragment = function(fragment) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile.insertFragment()"; mozile.debug(f,1,"Inserting fragment "+ fragment); // Get the current selection var selection = window.getSelection(); // If it is not collapsed, delete the contents of the selection if(!selection.isCollapsed) { this.deletion("next"); } // Get the focusNode, and make sure it's a text node var anchorNode = selection.anchorNode; if(anchorNode.nodeType != 3) { mozile.debug(f,4,"This node is not a text node! "+anchorNode); return false; } // Split the current text node to make room for the new nodes. selection.anchorNode.splitText(selection.anchorOffset); var parent = selection.anchorNode.parentNode; var next = selection.anchorNode.nextSibling; var children; // In both cases (document fragment or an element) get the children. if(fragment.documentElement) children = fragment.documentElement.childNodes; else children = fragment.cloneNode(true).childNodes; // Clone each child node "deep" and insert it before the "next" node. var newNode; for(var i=0;i<children.length;i++) { newNode = children[i].cloneNode(true); parent.insertBefore(newNode, next); } // Move the selection selection.collapseToEnd(); return true; } /** Mozile Editor - Delete - * Deletes the current selection, or everything between the current and the previous insertion points. This will usually mean a single character, but *ML white-space rules can make it more complicated. * * @param direction A string indicating where to delete. Can be "next" or "previous". * @return True if successful, false otherwise. */ MozileEditor.prototype.deletion = function(direction) { // var f = new Array(); // f["File"] = "core/core.js"; // f["Function"] = "Mozile.deletion()"; // mozile.debug(f,1,"Deleting in direction "+ direction); // Get the current selection var selection = window.getSelection(); var collapsed = selection.isCollapsed; // Handle the case of a collapsed selection (which is more complicated than a non-collapsed selection). if(collapsed) { // Get the next (or previous) insertion point, starting at the current selection. var arr = this.seekIP(direction, selection.focusNode, selection.focusOffset, true); // Check to make sure that the new insertion point node exists. if(!arr || !arr[0]) return false; // If this is not the same node as the one we started with, check to make sure that the new insertion point node is marked modifiable. if(!arr[2]) { var userModify = document.defaultView.getComputedStyle(arr[0].parentNode, '').getPropertyValue("-moz-user-modify").toLowerCase(); var userInput = document.defaultView.getComputedStyle(arr[0].parentNode, '').getPropertyValue("-moz-user-input").toLowerCase(); if(userModify=="read-only" || userInput=="disabled") return false; } // Try to extend the selection to the new insertion point. try { selection.extend(arr[0],arr[1]); } catch(e) { return false; } } // Get some information about the range. var range = selection.getRangeAt(0).cloneRange(); var startBlock = range.startContainer.getParentBlock(); var endBlock = range.endContainer.getParentBlock(); // Now delete the contents of the selection. // If the selection is collapsed, or starts and ends in the same block, then delete its contents. if(startBlock==endBlock || !collapsed) { selection.deleteContents(); } else { if(direction=="previous") { range.setStart(range.startContainer, range.startOffset+1); } //alert("Case "+ collapsed); } // If the selection is no longer in a text node, create a new one and select it. if(collapsed && selection.anchorNode.nodeType==1) { var text = document.createTextNode(""); selection.anchorNode.appendChild(text); range.selectNode(text); range.collapse(true); selection.removeAllRanges(); selection.addRange(range); } // If the start and end blocks are different, then we want to merge the endBlock with the startBlock. if(startBlock != endBlock ) { range.collapse(true); selection.removeAllRanges(); selection.addRange(range); this.insertFragment(endBlock); endBlock.parentNode.removeChild(endBlock); } return true; } /** Mozile Editor - Split Block - * Splits the current block in two, creating a new block after the old one in the process. This is the usual behaviour of the enter key. * * @return Always true. */ MozileEditor.prototype.splitBlock = function() { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile.splitBlock()"; mozile.debug(f,1,"Splitting block"); // Get the selection, and delete any contents it might have. var selection = window.getSelection(); if(!selection.isCollapsed) { selection.deleteContents(); } if(mozile.isEditor(selection.focusNode) || (selection.focusNode.nodeType == 3 && mozile.isEditor(selection.focusNode.parentNode) ) ) return false; // Store some information for later. var range = selection.getRangeAt(0).cloneRange(); var focusNode = selection.focusNode; var focusOffset = selection.focusOffset; var node = focusNode; var flag = false; var display; // Get the first ancestor element which is has CSS display=block. node = node.getParentBlock(); // If the node has a parent, then split it. Otherwise do nothing. if(node.parentNode) { // Select the whole node, then set the range's start to the selection's focus. range.selectNodeContents(node); var nodeString = range.toString(); range.setStart(focusNode, focusOffset); // Clone the target node "shallow" (not deep) var newNode = node.cloneNode(false); // Put the contents of the range into the newNode (this removes them from the old node). var rangeString = range.toString(); if(rangeString!="") { newNode.appendChild(range.extractContents()); } // Handle the different kinds of rangeStrings we can have. var textNode = null; switch(rangeString) { // If the range includes the whole node, then case nodeString: newNode.appendChild(document.createTextNode("")); node.parentNode.insertAfter(newNode, node); textNode = focusNode; break; // If the range is empty, give the new node an empty text node case "": textNode = document.createTextNode(""); newNode.appendChild(textNode); node.parentNode.insertAfter(newNode, node); break; default: newNode.appendChild(range.extractContents()); node.parentNode.insertAfter(newNode, node); textNode = this.seekTextNode("next", focusNode); break; } // Move the selection into the first insetion point in the new node try { selection.extend(textNode, 0); selection.collapseToEnd(); } catch(e) { mozile.debug(f,1,"Major error splitting block: "+e); // do nothing } } mozile.debug(f,1,"Done splitting block"); return true; } /** Mozile Editor - Seek Insertion Point - * Finds the next (or previous) insertion point, starting at a given text node and offset. The concept of an insertion point is critical to word-processing, but does not map nicely onto any of the DOM concepts. An insertion point is the pair of a text node and offset within that node, where characters can be inserted. A text node with no contents is not a valid insertion point. Unless CSS white-space="pre", a text node which contains nothing but white-space characters is not a valid insertion point either. * * @param direction A string indicating the direction of the search. Can be "next" or "previous". * @param startNode The text node to begin the search in. * @param startOffset Integer. The offset within the startNode to begin the search at. * @param firstNode Boolean. True if this is the first node being tested, and false otherwise. * @return An array, with the first entry being a text node, the second being the offset in that node, and the third a boolean value which is true if we are still in the first node tested. */ MozileEditor.prototype.seekIP = function(direction, startNode, startOffset, firstNode) { // var f = new Array(); // f["File"] = "core/core.js"; // f["Function"] = "Mozile.seekIP()"; // mozile.debug(f,1,"Seeking IP in direction "+ direction +" starting at "+startNode+" "+startOffset); var newNode; // If the startOffset is 0 and we are asked for the previous IP, call seekIP on the previous text node. If there is no such node inside the editor, return false. if(direction=="previous" && startOffset==0) { newNode = this.seekTextNode(direction, startNode); if(newNode) return this.seekIP(direction, newNode, newNode.textContent.length, false); else return false; } var content = startNode.textContent; // If the startOffset is equal to the length of the content string, and we are asked for the next IP, then call seekIP on the next text node. If there is no such node within the editor, return false if(direction=="next" && startOffset==content.length) { newNode = this.seekTextNode(direction, startNode); if(newNode) return this.seekIP(direction, newNode, 0, false); else return false; } var arr = new Array(); // Get a substring from the contents, from the startOffset to the beginning or end. Then use a regular expression to get the leading or trailing white-space. var substring, result; if(direction=="next") { substring = content.substring(startOffset, content.length); if(!this._matchLeadingWS) this._matchLeadingWS = /^(\s*)/; result = this._matchLeadingWS(substring); } else { substring = content.substring(0, startOffset); if(!this._matchTrailingWS) this._matchTrailingWS = /(\s*)$/; result = this._matchTrailingWS(substring); } // Hopefully there was some result... if(result) { // If the result has length is 0 or 1, then we ignore the special white-space rules for HTML and XML, and just send the subsequent offset. if(result[0].length < 2) { arr[0] = startNode; if(direction=="next") arr[1] = startOffset + 1; else arr[1] = startOffset - 1; arr[2] = firstNode; return arr; } // Handle longer results. else { // We need to know the white-space mode. var wsMode = document.defaultView.getComputedStyle(startNode.parentNode, '').getPropertyValue("white-space").toLowerCase(); // If the white-space mode is "pre", then again we ignore the special whie-space rules, and send the subsequent offset. if(wsMode == "pre") { arr[0] = startNode; if(direction=="next") arr[1] = startOffset + 1; else arr[1] = startOffset - 1; arr[2] = firstNode; return arr; } // If the result is long, but not as long as the substring, then move by the length of the result. if(result[0].length < substring.length) { arr[0] = startNode; if(direction=="next") arr[1] = startOffset + result[0].length; else arr[1] = startOffset - result[0].length; arr[2] = firstNode; return arr; } // If the result has length equal to the substring, we'll need to get move to another text node and find the IP there. (The length should never be greater, but we will handle that the same way.) else { newNode = this.seekTextNode(direction, startNode); if(newNode) { if(direction=="next") return this.seekIP(direction, newNode, 1, false); else return this.seekIP(direction, newNode, newNode.textContent.length, false); } else { // No new node! return false; } } } } else { // There was some problem with the regular expression. return false; } } /** Mozile Editor - Seek Text Node - * Finds the next (or previous) text node which either has CSS white-space="pre" or contains some non-white-space characters. If there is not such node, it returns false. * * @param direction String. Can be "next" or "previous". * @param startNode The text node to begin the search in. * @return The requested text node. */ MozileEditor.prototype.seekTextNode = function(direction, startNode) { var f = new Array(); f["File"] = "core/core.js"; f["Function"] = "Mozile.seekTextNode()"; mozile.debug(f,1,"Seeking Text Node in direction "+ direction +" starting at "+startNode); // Create a tree walker for the current editor. var treeWalker = document.createTreeWalker(mozile.getCurrentEditor(), NodeFilter.SHOW_TEXT, null, false); // Loop until we find the startNode. while(treeWalker.currentNode != startNode) { treeWalker.nextNode(); } // Get the next or previous node, as appropriate. var checkNode; if(direction=="next") checkNode = treeWalker.nextNode(); else checkNode = treeWalker.previousNode(); // Loop until a non-white-space or "pre" text node is reached var wsMode; while(checkNode) { if(!this._matchNonWS) this._matchNonWS = /\S/; // If the node contains non-white-space characters, then return it. if(this._matchNonWS.test(checkNode.textContent)) { mozile.debug(f,1,"Returning text node: "+checkNode.textContent); return checkNode; } // Now check to see that the text node has CSS white-space="pre" or contains something other than white-space characters. If it does not, check the next node until you find some non-white-space characters. Then return. // wsMode will be "normal", "no-wrap", or "pre". wsMode = document.defaultView.getComputedStyle(checkNode.parentNode, '').getPropertyValue("white-space").toLowerCase(); if(wsMode=="pre") return checkNode; // If neither of this tests succeeds, then grab the next (or previous) node. if(direction=="next") checkNode = treeWalker.nextNode(); else checkNode = treeWalker.previousNode(); } // If we made it this far, then something is broken. mozile.debug(f,3,"No editable text node in direction "+direction); return false; } /** Mozile Resource - * An abstract class for external resources, such as JavaScripts and CSS files. * You can give the resource's element text content with the setContent(content) method. For mocre complex content, first createElement() then manipulate the returned element. * @constructor * * @param {String} category The category of the resource, used as the localName of the resource's element. * @param {String} id The id for the resource, used as the id of the resources' element. * @param {String} type Optional. The type for the resource, used for the type attribute. E.g. "text/css". * @param {String} namespace Optional. The namespace for the resource's element. Defaults to the global XHTMLNS. */ function MozileResource(category, id, type, namespace) { // Declare private variables. /** * @private * @type String */ this._category = String(category); /** * @private * @type String */ this._id = String(id); /** * @private * @type String */ this._type = checkArgument("", arguments[2]); /** * @private * @type String */ this._namespace = checkArgument(XHTMLNS, arguments[3]); } /** * Returns "[object MozileResource]". * @type String */ MozileResource.prototype.toString = function() { return "[object "+ this.constructor.toString().match(/^function\s+(\w+)/)[1] +"]"; } /** * @type String * @return The category of resource. */ MozileResource.prototype.getCategory = function() { return this._category; } /** * @type String * @return The resource id. */ MozileResource.prototype.getId = function() { return this._id; } /** * @type String */ MozileResource.prototype.getType = function() { return this._type; } /** * @type String */ MozileResource.prototype.getNamespace = function() {return this._namespace; } /** * Gets element associated with the resource. Creates the element if necessary. * @type String */ MozileResource.prototype.getElement = function() { if(!this._element) { if(document.getElementById(this.getId())) this._element = document.getElementById(this.getId()); else this._element = this.createElement(); } return this._element; } /** * @type TextNode * @return The content of the resource (a node). */ MozileResource.prototype.getContent = function() { if(this._contentNode) return this._contentNode; else return undefined; } /** * @param {String} content Creates a text node which will be appended to the element. * @type TextNode * @return The content of the resource. */ MozileResource.prototype.setContent = function(content) { this._contentNode = document.createTextNode(content); return this._contentNode; } /** * Creates a new element for the resource and returns it. * @type Element */ MozileResource.prototype.createElement = function() { this._element = document.createElementNS(this._namespace, this._category); this._element.setAttribute("id", this.getId()); this._element.setAttribute("type", this.getType()); if(this.getContent() && this.getContent().nodeType == 3) { this._element.appendChild(this.getContent()); } return this._element; } /** * Inserts the resource's element into the document * @type Void */ MozileResource.prototype.load = function() { // Don't load if already loaded. if(this.getElement().parentNode) return; if(document.documentElement.tagName.toLowerCase() == "html") { document.getElementsByTagName("head")[0].appendChild(this.getElement()); } else { document.documentElement.insertBefore(this.getElement(), document.documentElement.firstChild); } // Register this resource with the global mediator mozile.addResource(this); } /** * Removes the resource's element from the document. * * @param {Element} element Optional. If an element is provided the resource's element will be removed from the given element. * @type Boolean * @return True if any elements have been removed. False otherwise. */ MozileResource.prototype.unload = function(element) { if(arguments.length > 0 && element) { element = arguments[0]; } else { element = document; } var elements = element.getElementsByTagName(this._category); var elementsRemoved = false; for(var i=0;i<elements.length;i++) { if(elements[i].getAttribute("id") == this.getId()) { elements[i].parentNode.removeChild(elements[i]); elementsRemoved = true; } } return elementsRemoved; } /** Mozile Script Resource - * Subclass of MozileResource designed for script tags. * @constructor * @param {String} id The id for the resource, used as the id of the resources' element. * @param {String} source The URL for src attribute. * @param {String} type Optional. The type for the resource, used for the type attribute. Default is "application/x-javascript". * @param {String} namespace Optional. The namespace for the resource's element. Defaults to the global XHTMLNS. */ function MozileScriptResource(id, source, type, namespace) { // Declare private variables. /** * @private * @type String */ this._category = "script"; /** * @private * @type String */ this._id = String(id); /** * @private * @type String */ this._source = String(source); /** * @private * @type String */ this._type = checkArgument("application/x-javascript", arguments[2]); /** * @private * @type String */ this._namespace = checkArgument(XHTMLNS, arguments[3]); } MozileScriptResource.prototype = new MozileResource; MozileScriptResource.prototype.constructor = MozileScriptResource; /** * @type String */ MozileScriptResource.prototype.getSource = function() { return this._source; } /** * Creates a new element for the resource and returns it. * @type Element */ MozileScriptResource.prototype.createElement = function() { this._element = document.createElementNS(this._namespace, this._category); this._element.setAttribute("id", this.getId()); this._element.setAttribute("type", this.getType()); if(this.getSource() != "") this._element.setAttribute("src", this.getSource()); if(this.getContent() && this.getContent().nodeType == 3) { this._element.appendChild(this.getContent()); } return this._element; } /** Mozile Link Resource - * Subclass of MozileResource designed for link tags. * @constructor * @param {String} id The id for the resource, used as the id of the resources' element. * @param {String} source The URL for href attribute. * @param {String} relation Optional. The relation for the rel attribute. Default is "stylesheet". * @param {String} type Optional. The type for the resource, used for the type attribute. Default is "text/css". * @param {String} namespace Optional. The namespace for the resource's element. Defaults to the global XHTMLNS. */ function MozileLinkResource(id, source, relation, type, namespace) { // Declare private variables. /** * @private * @type String */ this._category = "link"; /** * @private * @type String */ this._id = String(id); /** * @private * @type String */ this._source = String(source); /** * @private * @type String */ this._relation = checkArgument("stylesheet", arguments[2]); /** * @private * @type String */ this._type = checkArgument("text/css", arguments[3]); /** * @private * @type String */ this._namespace = checkArgument(XHTMLNS, arguments[4]); } MozileLinkResource.prototype = new MozileResource; MozileLinkResource.prototype.constructor = MozileLinkResource; /** * @type String */ MozileLinkResource.prototype.getSource = function() { return this._source; } /** * @type String */ MozileLinkResource.prototype.getRelation = function() { return this._relation; } /** * Link cannot have children. Overrides parent method with an empty function. * @type Undefined */ MozileLinkResource.prototype.getContent = function() { return undefined; } /** * Link cannot have children. Overrides parent method with an empty function. * @type Undefined */ MozileLinkResource.prototype.setContent = function(content) { return undefined; } /** * Creates a new element for the resource and returns it. * @type Element */ MozileLinkResource.prototype.createElement = function() { this._element = document.createElementNS(this._namespace, this._category); this._element.setAttribute("id", this.getId()); this._element.setAttribute("rel", this.getRelation()); this._element.setAttribute("type", this.getType()); this._element.setAttribute("href", this.getSource()); return this._element; } /** Mozile Style Resource - * Subclass of MozileResource designed for style tags. * @constructor * @param {String} id The id for the resource, used as the id of the resources' element. * @param {String} type Optional. The type for the resource, used for the type attribute. Default is "text/css". * @param {String} media Optional. The value for the media attribute. Default is none. * @param {String} namespace Optional. The namespace for the resource's element. Defaults to the global XHTMLNS. */ function MozileStyleResource(id, type, media, namespace) { // Declare private variables. /** * @private * @type String */ this._category = "style"; /** * @private * @type String */ this._id = String(id); /** * @private * @type String */ this._type = checkArgument("text/css", arguments[1]); /** * @private * @type String */ this._media = checkArgument("", arguments[2]); /** * @private * @type String */ this._namespace = checkArgument(XHTMLNS, arguments[3]); } MozileStyleResource.prototype = new MozileResource; MozileStyleResource.prototype.constructor = MozileStyleResource; /** * @type String */ MozileStyleResource.prototype.getMedia = function() { return this._media; } /** * Finds the {@link http://developer.mozilla.org/en/docs/DOM:stylesheet CSSStyleSheet} associated with the element. * @type CSSStyleSheet */ MozileStyleResource.prototype.getStylesheet = function() { // If there is no stylesheet, try to find the it if(!this._stylesheet) { for(var i=0; i < document.styleSheets.length; i++) { if(document.styleSheets.item(i).ownerNode == this.getElement()) { /** @private */ this._stylesheet = document.styleSheets.item(i); return this._stylesheet; } } } // Check again if(!this._stylesheet) return undefined; else return this._stylesheet; } /** * Creates a new element for the resource and returns it. * @type Element */ MozileStyleResource.prototype.createElement = function() { /** @private */ this._element = document.createElementNS(this._namespace, this._category); this._element.setAttribute("id", this.getId()); this._element.setAttribute("type", this.getType()); if(this._media != "") { this._element.setAttribute("media", this.getMedia()); } if(this.getContent() && this.getContent().nodeType == 3) { this._element.appendChild(this.getContent()); } return this._element; } /** Mozile - Test Function - * This function is only used for testing. It gets overridden in the TestModules. * * @return Always 0. */ // MozileMediator.prototype.testFunction = function() { return 0; } MozileMediator.prototype.testAlert = function() { try{ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect UniversalBrowserWrite "); alert("Mozile Server-Side Privileged! Extension: "+ mozile.isExtension()); } catch(e){ alert("Mozile Server-Side Unprivileged! \n"+e); } } /**** Activate Configuration ****/ // If the "mozileInterface" variable is set to "false", then we are ready to call the mozileConfiguration() function, which will create a Mozile instance and load modules. // If the "mozileInterface" variable is not "false", then more code will be loaded before the configuration is called, so we do nothing here. //try { // Global MozileMediator instance var mozile = new MozileMediator("root='"+mozileScriptSource+"'," + mozileOptions); this._watchInterval = window.setInterval("mozile.watchSharedData()", mozile.getOption("defaultInterval")); mozileConfiguration(); // Load Just-In-Time means wait before loading the interface if(!mozile.getOption("loadJIT")) { mozile.load(); } //} //catch(e) { // alert("Error in core.js when calling mozileConfiguration: "+e); //} /**** UTILITY FUNCTIONS ****/ // These functions are used for development, and should not be relied upon in production code. /** Dump Array - * Displays the contents of the array in key=>value pairs, using an alert. * * @param arr The array to be dumped * @return Nothing. */ function dumpArray(arr) { var s = "Array Dump: "; for(key in arr) { s = s + key +"=>"+ arr[key] +"\n"; } alert(s); } /** Print XML - * Serializes the given XML and returns it as a string * * @param XML The XML to be serialized. * @return String version of the XML. */ function printXML(XML) { if(!XML || XML=="") return "Nothing to print!!"; var objXMLSerializer = new XMLSerializer; var output = objXMLSerializer.serializeToString(XML); // fix stupid uppercase tags! output = output.replace( /<(\/?)([A-Z]+)/g, function (output,p1,p2,offset,s) { return '<'+p1+p2.toLowerCase() ; } ) ; return output; }
|
||||||||
PREV NEXT | FRAMES NO FRAMES |