/* * file: DEdit.js * * @BEGINLICENSE * Copyright 2010 Brook Novak (email : brooknovak@seaweed-editor.com) * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * @ENDLICENSE */ /** * @namespace The debug namespace will be removed in release builds. */ debug = {}; /** * Asserts a condition. If a condition fails a alert box shows and an exception is thrown. * * @param {Boolean} cond A condition * * @param {String} msg An optional message * * @throws {Error} if a condition fails */ debug.assert = function(cond, msg) { try { if (!cond) throw new Error("Assertion failed" + (msg ? ": " + msg : "")); } catch (e) { var fullMsg = e.message + (e.stack ? "\nstack:\n" + e.stack : ""); alert(fullMsg); throw e; } } debug.close = function(){ debug.stop = true; if (typeof _debugConsole != "undefined" && _debugConsole) { _debugConsole.parentNode.removeChild(_debugConsole); _debugConsole = null; } } debug.stop = false; /** * Prints a message to a debug console. * * @param {String} msg A message to print. */ debug.print = function(msg){ if (debug.stop) return; if (window.console && console.log) { console.log(msg); } else if (window.opera && window.opera.postError){ window.opera.postError(msg); } else { // Setup debug console if (typeof _debugConsole == "undefined" || !_debugConsole) { _debugConsole = document.createElement("div"); _debugConsole.style.backgroundColor = "#444444"; _debugConsole.style.position = "fixed"; _debugConsole.style.border = "2px solid #000000"; _debugConsole.style.width = "320px"; _debugConsole.style.height = "174px"; _debugConsole.style.zIndex = "9999"; _debugConsole.style.top = "0"; _debugConsole.style.left = "0"; _debugConsole.innerHTML = '
close
~Seaweed~ Debug Console
'; _debugOutput = document.createElement("div"); _debugOutput.style.backgroundColor = "white"; _debugOutput.style.overflow = "scroll"; _debugOutput.style.width = _debugConsole.style.width; _debugOutput.style.height = "140px"; _debugConsole.appendChild(_debugOutput); } if (document.body && _debugConsole && _debugConsole.parentNode != document.body) document.body.appendChild(_debugConsole); _debugOutput.innerHTML += msg.replace(/\n/g,"
"); var st = _debugOutput.scrollHeight - _debugOutput.clientHeight; if (st < 0) st = 0; _debugOutput.scrollTop = st; } } /** * Prints a message to a debug console with a new line * * @param {String} msg A message to print. */ debug.println = function(msg){ debug.print(msg + "\n"); }; /** * Prints out a TODO message and it's stack location * @param {String} msg An optional todo message */ debug.todo = function(msg) { try { throw new Error(); } catch (e) { var fullMsg = "TODO: " + (msg ? msg : "") + (e.stack ? "\nAt" + e.stack : + ""); debug.println(fullMsg); } }; /* * file: Core.js * * @BEGINLICENSE * Copyright 2010, Brook Jesse Novak, All rights reserved * @ENDLICENSE */ // Short-hands which can be munged var // Notes: // Not in core notation ($...) or internal notation (_...) since not ready for all scripts until library initialized. /** * The document.body reference. Available when library initialized. * @type Node */ docBody, /** * @type undefined */ $undefined; /** * @namespace The main namespace for the whole system * @author Brook Jesse Novak */ de = { version : "0.0.1", /** * @private * Module register */ m : [], /** * @namespace Contains useful collections like listed lists and hashsets. * @author Brook Jesse Novak */ collections: {}, /** * @namespace The DOM events subsystem. * @author Brook Jesse Novak */ events: {}, /** * Adds a callback function to be invoked once the DOM is ready. * Allows multiple registrations of handlers. *
* NOTE: In the debug release, the window "onload" events are used instead of "domready" * events and therefore take longer to be raised than expected. This is to ensure that all * scripts are downloaded by the bootstrapper. * * @type Function * @param {Function} handler A call back function to be invoked when the document is loaded and direct edit can be initialized. */ onready : (function() { var handlers = []; // zeroed when dom ready occurs function onReadyHandler() { if (handlers) { // First onload event? // Fire event to handlers for (var i in handlers) { handlers[i](); } // Mark that onload event has occured handlers = 0; } }; // @DEBUG ON (function() { // @DEBUG OFF // @DEBUG ON // Use window onload in debug release since some broswers can invoke onready // events before the bootstrapper download all scripts in the seaweed api. // Register to onload event if (window.addEventListener) window.addEventListener("load", onReadyHandler, false); else if (window.attachEvent) window.attachEvent("onload", onReadyHandler); else { // Save exisiting handlers if (window.onload) handlers.push(window.onload); window.onload = onloadFunc; } return; // @DEBUG OFF // For the release use DOMReady event - nice and quick. // This code is based from JQuerry 1.3.2 bindReady event (MIT licensed code) if (document.addEventListener) { // W3C Compliant // Use the handy event callback document.addEventListener("DOMContentLoaded", function(){ document.removeEventListener("DOMContentLoaded", arguments.callee, false); onReadyHandler(); }, false); } else if (document.attachEvent) { // IE // Ensure firing before onload. // maybe late but safe also for iframes document.attachEvent("onreadystatechange", function(){ if (document.readyState === "complete") { document.detachEvent("onreadystatechange", arguments.callee); onReadyHandler(); } }); // If IE and not an iframe continually check to see if the document is ready if (document.documentElement.doScroll && window == window.top) (function(){ if (!handlers) return; try { // If IE is used, use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch (error) { setTimeout(arguments.callee, 0); return; } // and execute any waiting functions onReadyHandler(); })(); // Fallback: use window onload. NOTE: In release mode this will be present // in this very script so it is safe to use it. _addHandler(window, "load", function(){ onReadyHandler(); _removeHandler(window, "load", arguments.callee); }); } // @DEBUG ON })(); // @DEBUG OFF // The function return function(handler) { // Pending onload? if (handlers) handlers.push(handler); else handler(); // Immedite exec since onload occured }; })(), /** * Prepares DEdit for usage */ init: function() { // Already initialized? if (!de.m) return; docBody = document.body; var modules = de.m; // @DEBUG ON // Check integrity of init queue for (var i in modules) { // Esnure no module depends on self if (modules[i].depends) { for (var j in modules[i].depends) { debug.assert(modules[i].depends[j] != modules[i].name, "Bad dependancy: Module " + modules[i].name + " depends on self"); var found = false; for (var k in bootstrap.loadedModules) { if (bootstrap.loadedModules[k] == modules[i].depends[j]) { found = true; break; } } debug.assert(found, "Bad dependancy: Module " + modules[i].name + " depends on unknown dependancy \"" + modules[i].depends[j] + "\""); } } } // @DEBUG OFF // Initialize modules var orderChanged; do { // sort dependancies in a bubble sort manner // NOTE: This does not detect cyclic dependancies... thus can inifitely loop // in such cases. //TODO: Faster/elegant way of building dependency tree orderChanged = false; for (var i = 0; i < modules.length; i++) { var currentMod = modules[i]; // If this has no dependancies then leave as is if (!currentMod.depends) continue; // Check that dependancies occur before this for (var j = 0; j < currentMod.depends.length; j++) { for (var k = i + 1; k < modules.length; k++) { if (currentMod.depends[j] == modules[k].name) { // Move this (modules[i]) to after the dependancy (modules[k]) var old = modules; modules = old.slice(0, i).concat(old.slice(i + 1, k + 1).concat([currentMod].concat(old.slice(k + 1)))); orderChanged = true; break; } } if (orderChanged) break; } if (orderChanged) break; } // Next module } while (orderChanged); // Continue bubble sorting the dependancy list // Execute initialization code for (i in modules) { if (modules[i].init) modules[i].init(); } // cleanup initialization breadcrumbs delete de["m"]; } }; /* * Declare core internals inline .. this is a special setup since Core is the first script declared */ /** *

* Enqueue's an intialization function to be invoked during the DEdit API initialization phase (after DOM is ready). *

*

* If a module (script) needs to be initialized before usage and depends on the document DOM state to be ready - * or should only be initialized when the API is explicitely been asked to be initialized, or initialization code * depends on a public API interface then use this function (at most once per module). *

* NOTE: All internals will be loaded upon execution of the initialization code, therefore there is no need to * delcare dependancies (@DEPENDS or via this method) for usage of internals within initialization code. * * @param {String} moduleName The name of the module (script file name without extension). * * @param {Function} init An optional initialization method. If the initialization code depends on * other modules' public interfaces, then specify the module names as additional arguments to this call. */ function $enqueueInit(moduleName, init){ // @DEBUG ON debug.assert(de.m ? true : false, "Attempted to enqueue initializor function after API has initialized"); for (var i in de.m) { // Integrity check debug.assert(de.m[i].name != moduleName, 'An initializor is already registered under the name "' + moduleName + '"'); } // @DEBUG OFF // Discover dependancies (declared as extra arguments) var dependancies = Array.prototype.slice.call(arguments); dependancies.splice(0, 2); // Store the initializor for the module de.m.push({ name: moduleName, init: init, depends: dependancies }); }; /** * Adds/suppliments all members to a target object from a source object. * If the target object has a member that is also contained in source, it will * be overridden with the source member. * * Leaves the source object in tact. * * @param {Object} target The destination object * * @param {Object} source The source object * * @param {Boolean} override (Optional) True to override existing members on conflicts, * false skip conflicts. Defaults to true * * @return {Object} The target object */ function $extend(target, source, override) { if (override !== false) override = true; for (var mem in source) { if (override || typeof target[mem] == "undefined") target[mem] = source[mem]; } return target; } /** * Create a hash map of booleans from a comma separated set of keys. * @param {String} str A comma separated set of keys. White spaces are not truncated * @return {Object} A lookup map */ function $createLookupMap(str){ var arr = str.split(","); var map = {}; for (var i in arr) { map[arr[i]] = true; } return map; } /** * Shorthand for document.createElement * @param {String} tag The element name * @return {Element} A new element */ function $createElement(tag) { return document.createElement(tag); } // @DEBUG ON // MSHTML Does not provide Node type constants. // Therefore declare them explicitely. This is only needed in the debug version // since the release version replaces them with actual values // NOTE: This may cause other scripts confusion!! If they test for the presence of // the node object and assume it is an actual node class! if (typeof Node == "undefined") { Node = {_DE_DEBUG_CREATED : true}; } if (typeof Node.ELEMENT_NODE == "undefined") { $extend(Node, { // @REPLACE Node.ELEMENT_NODE 1 ELEMENT_NODE : 1, // @REPLACE Node.ATTRIBUTE_NODE 2 ATTRIBUTE_NODE : 2, // @REPLACE Node.TEXT_NODE 3 TEXT_NODE : 3, // @REPLACE Node.CDATA_SECTION_NODE 4 CDATA_SECTION_NODE : 4, // @REPLACE Node.ENTITY_REFERENCE_NODE 5 ENTITY_REFERENCE_NODE : 5, // @REPLACE Node.ENTITY_NODE 6 ENTITY_NODE : 6, // @REPLACE Node.PROCESSING_INSTRUCTION_NODE 7 PROCESSING_INSTRUCTION_NODE : 7, // @REPLACE Node.COMMENT_NODE 8 COMMENT_NODE : 8, // @REPLACE Node.DOCUMENT_NODE 9 DOCUMENT_NODE : 9, // @REPLACE Node.DOCUMENT_TYPE_NODE 10 DOCUMENT_TYPE_NODE : 10, // @REPLACE Node.DOCUMENT_FRAGMENT_NODE 11 DOCUMENT_FRAGMENT_NODE : 11, // @REPLACE Node.NOTATION_NODE 12 NOTATION_NODE : 12 }); } // @DEBUG OFF /* * This Bootloader was auto-generated on 27/02/2010 14:18:11 * @author Brook Novak Copyright 2010 Brook Novak (email : brooknovak@seaweed-editor.com) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ (function() { var baseScripts=["collections/DoublyLinkedList.js","MVC.js","Platform.js","UndoMan.js","Util.js"]; var dependantScripts=["Changes.js","Clipboard.js","ContainerNormalization.js","Cursor.js","Doc.js","Error.js","events/Events.js","events/Mouse.js","FormatEnvironment.js","Fragment.js","OperationManager.js","events/Keyboard.js","Selection.js","Spell.js","Typing.js","actions/TextAlignAction.js","actions/SplitContainerAction.js","actions/RemoveTextAction.js","actions/RemoveDOMAction.js","actions/PromoteItemAction.js","actions/ModifyTableAction.js","actions/ItemizeAction.js","actions/InsertTextAction.js","actions/InsertHTMLAction.js","actions/IndentAction.js","actions/FormatAction.js","actions/DemoteItemAction.js","actions/CreateTableAction.js","actions/ChangeContainerAction.js","actions/BlockQuoteAction.js","DTDUtil.js","Viewport.js","WhitespaceUtil.js"]; var thisFilename="DEdit.js"; /* * file: Bootstrapper.js * * @BEGINLICENSE * Copyright 2010, Brook Jesse Novak, All rights reserved * @ENDLICENSE */ /********************************************************************************************************************** * @author Brook Novak * * Assumes: * - Core.js is prepended(or loaded). * - An array named "baseScripts" exists with all relative paths of scripts to load first in the given order. * - An array named "dependantScripts" exists with all relative paths of scripts to load after the base scripts. * - A string named "thisFilename" exists, set to the filename of this file, which is contained in the libraries TDL. * - That this script is encapsulated in an anonymous function (To avoid polluting global scope) **********************************************************************************************************************/ // Mimic the addHandler method in the events module to make available before the events module script is even available var eventHandlers = []; de.events.addHandler = function(eventSource, eventName, handler) { // will be overridden eventHandlers.push(arguments); } // Override the onready function (declared in Core)- so that all callbacks are invoked once // the window.onload occurs AND all scripts are loaded var onReadyCallbacks = [], origReady = de.onready; de.onready = function(handler) { onReadyCallbacks.push(handler); } /** * @namespace * The bootstrap namespace. */ bootstrap = { /** * A list of all laoded module names * @type [String] */ loadedModules : [], /** * For all scripts in the API you must invoke this function to register the script when the script is first executed. * * @param {String} name The name of module that is loaded. * The Module name is the relative path without the file extension (.js), * and directory separators are replaced with period-symbols. */ provides : function(name) { // debug.println("Loaded: " + name); this.loadedModules.push(name); // First Phase: base-script loading if (baseScripts.length > 0) { // A base script is loaded - remove it baseScripts.shift(); if (baseScripts.length > 0) { // More base scripts to load // Load the next base script loadScript(baseURL + baseScripts[0]); } else { // Finished download base scripts // Pull in remaining API source files asynchronously for (var i in dependantScripts) { loadScript(baseURL + dependantScripts[i]); } } } else { // Second phase: dependant-script loading // A dependant script is loaded. // Get relative path for registered module var registeredPath = name.replace(/\./g,"/").toLowerCase() + ".js"; // Remove include path for the registered module for (var i = 0; i < dependantScripts.length; i++) { if (dependantScripts[i].toLowerCase() == registeredPath) { dependantScripts.splice(i, 1); break; } } if (dependantScripts.length == 0) { // All scripts loaded? // Schedule finishing-up code on another event to let the last script load setTimeout(function() { // Add all registered events... for (var i in eventHandlers) { var args = eventHandlers[i]; var eventSource = (args.length > 0) ? args[0] : null, eventName = (args.length > 1) ? args[1] : null, handler = (args.length > 2) ? args[2] : null; _addHandler(eventSource, eventName, handler); } // Add all on-ready funcs for (var i in onReadyCallbacks) { origReady(onReadyCallbacks[i]); // Fires instantly is onload already occured } de.onready = origReady; }, 1); } } } }; // Determine the base URL of the library. var baseURL; var libcoreRegExp = new RegExp("(^|[\\/\\\\])" + thisFilename.replace(/\./g,"\\.") + "(\\?|$)"); var scriptElements = document.getElementsByTagName("script"); for (var i in scriptElements) { var src = scriptElements[i].src; if (src && src.match(libcoreRegExp)) { baseURL = src; i = src.lastIndexOf("/"); if (i == -1) i = src.lastIndexOf("\\"); baseURL = (i == -1) ? "" : src.substring(0, i + 1); break; } } if (!baseURL) throw new Error("Unable to import library: could not discover DEdit libhome"); // Pull in API base source files first, once this is done the rest of the api source will be pulled in loadScript(baseURL + baseScripts[0]); function loadScript(scriptSrc){ // gets document head element var docHead = document.getElementsByTagName('head')[0]; if (docHead) { // creates a new script tag var scriptEl = document.createElement('script'); // adds src and type attribute to script tag scriptEl.setAttribute('src', scriptSrc); scriptEl.setAttribute('type', 'text/javascript'); // append the script tag to document head element docHead.appendChild(scriptEl); } } })();