You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2010/03/29 00:53:15 UTC

svn commit: r928511 [2/7] - /myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/inputHtml/resource/

Modified: myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/inputHtml/resource/kupubasetools.js
URL: http://svn.apache.org/viewvc/myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/inputHtml/resource/kupubasetools.js?rev=928511&r1=928510&r2=928511&view=diff
==============================================================================
--- myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/inputHtml/resource/kupubasetools.js (original)
+++ myfaces/tomahawk/trunk/core/src/main/resources/org/apache/myfaces/custom/inputHtml/resource/kupubasetools.js Sun Mar 28 22:53:14 2010
@@ -7,10 +7,8 @@
  * Contributors see CREDITS.txt.
  *
  *****************************************************************************/
-
 // $Id$
 
-
 //----------------------------------------------------------------------------
 //
 // Toolboxes
@@ -34,96 +32,87 @@ function KupuTool() {
 
     this.toolboxes = {};
 
-    // methods
-    this.initialize = function(editor) {
-        /* Initialize the tool.
+    // private methods
+    addEventHandler = addEventHandler;
+};
 
-            Obviously this can be overriden but it will do
-            for the most simple cases
-        */
-        this.editor = editor;
-    };
+// methods
+KupuTool.prototype.initialize = function(editor) {
+    /* Initialize the tool.
 
-    this.registerToolBox = function(id, toolbox) {
-        /* register a ui box 
-        
-            Note that this needs to be called *after* the tool has been 
-            registered to the KupuEditor
-        */
-        this.toolboxes[id] = toolbox;
-        toolbox.initialize(this, this.editor);
-    };
+        Obviously this can be overriden but it will do
+        for the most simple cases
+    */
+    this.editor = editor;
+};
+
+KupuTool.prototype.registerToolBox = function(id, toolbox) {
+    /* register a ui box 
     
-    this.updateState = function(selNode, event) {
-        /* Is called when user moves cursor to other element 
+        Note that this needs to be called *after* the tool has been 
+        registered to the KupuEditor
+    */
+    this.toolboxes[id] = toolbox;
+    toolbox.initialize(this, this.editor);
+};
 
-            Calls the updateState for all toolboxes and may want perform
-            some actions itself
-        */
-        for (id in this.toolboxes) {
-            this.toolboxes[id].updateState(selNode, event);
-        };
+KupuTool.prototype.updateState = function(selNode, event) {
+    /* Is called when user moves cursor to other element 
+
+        Calls the updateState for all toolboxes and may want perform
+        some actions itself
+    */
+    for (var id in this.toolboxes) {
+        this.toolboxes[id].updateState(selNode, event);
     };
+};
 
-    this.enable = function() {
-        // Called when the tool is enabled after a form is dismissed.
-    }
+KupuTool.prototype.enable = function() {
+    // Called when the tool is enabled after a form is dismissed.
+};
 
-    this.disable = function() {
-        // Called when the tool is disabled (e.g. for a modal form)
-    }
-    // private methods
-    addEventHandler = addEventHandler;
-    
-    this._selectSelectItem = function(select, item) {
-        this.editor.logMessage(_('Deprecation warning: KupuTool._selectSelectItem'));
-    };
-    this._fixTabIndex = function(element) {
-        var tabIndex = this.editor.getDocument().getEditable().tabIndex-1;
-        if (tabIndex && !element.tabIndex) {
-            element.tabIndex = tabIndex;
-        }
-    }
-}
+KupuTool.prototype.disable = function() {
+    // Called when the tool is disabled (e.g. for a modal form)
+};
 
 function KupuToolBox() {
     /* Superclass for a user-interface object that controls a tool */
+};
 
-    this.initialize = function(tool, editor) {
-        /* store a reference to the tool and the editor */
-        this.tool = tool;
-        this.editor = editor;
-    };
+KupuToolBox.prototype.initialize = function(tool, editor) {
+    /* store a reference to the tool and the editor */
+    this.tool = tool;
+    this.editor = editor;
+};
 
-    this.updateState = function(selNode, event) {
-        /* update the toolbox according to the current iframe's situation */
-    };
-    
-    this._selectSelectItem = function(select, item) {
-        this.editor.logMessage(_('Deprecation warning: KupuToolBox._selectSelectItem'));
-    };
+KupuToolBox.prototype.updateState = function(selNode, event) {
+    /* update the toolbox according to the current iframe's situation */
 };
 
-function NoContextMenu(object) {
+function noContextMenu(object) {
     /* Decorator for a tool to suppress the context menu */
     object.createContextMenuElements = function(selNode, event) {
         return [];
-    }
+    };
     return object;
 }
 
 // Helper function for enabling/disabling tools
-function KupuButtonDisable(button) {
+function kupuButtonDisable(button) {
     button = button || this.button;
-    button.disabled = "disabled";
-    button.className += ' disabled';
-}
-function KupuButtonEnable(button) {
-    button = button || this.button;
-    button.disabled = "";
-    button.className = button.className.replace(/ *\bdisabled\b/g, '');
-}
+    if (button) {
+        button.disabled = "disabled";
+        button.className += ' disabled';
+    }
+};
 
+function kupuButtonEnable(button) {
+    button = button || this.button;
+    if (button) {
+        button.disabled = "";
+        button.className = button.className.replace(/ *\bdisabled\b/g, '');
+    }
+};
 
 //----------------------------------------------------------------------------
 // Implementations
@@ -131,32 +120,35 @@ function KupuButtonEnable(button) {
 
 function KupuButton(buttonid, commandfunc, tool) {
     /* Base prototype for kupu button tools */
-    this.buttonid = buttonid;
-    this.button = getFromSelector(buttonid);
-    this.commandfunc = commandfunc;
-    this.tool = tool;
-
-    this.initialize = function(editor) {
-        this.editor = editor;
-        this._fixTabIndex(this.button);
-        addEventHandler(this.button, 'click', this.execCommand, this);
+    if (arguments.length) {
+        this.buttonid = buttonid;
+        this.button = getFromSelector(buttonid);
+        this.commandfunc = commandfunc;
+        this.tool = tool;
+        this.disable = kupuButtonDisable;
+        this.enable = kupuButtonEnable;
     };
+};
 
-    this.execCommand = function() {
-        /* exec this button's command */
-        this.commandfunc(this, this.editor, this.tool);
-    };
+KupuButton.prototype = new KupuTool;
 
-    this.updateState = function(selNode, event) {
-        /* override this in subclasses to determine whether a button should
-            look 'pressed in' or not
-        */
-    };
-    this.disable = KupuButtonDisable;
-    this.enable = KupuButtonEnable;
+KupuButton.prototype.initialize = function(editor) {
+    this.editor = editor;
+    if (!this.button) return;
+    addEventHandler(this.button, 'click', this.execCommand, this);
+};
+
+KupuButton.prototype.execCommand = function() {
+    /* exec this button's command */
+    this.commandfunc(this, this.editor, this.tool);
+};
+
+KupuButton.prototype.updateState = function(selNode, event) {
+    /* override this in subclasses to determine whether a button should
+        look 'pressed in' or not
+    */
 };
 
-KupuButton.prototype = new KupuTool;
 function KupuStateButton(buttonid, commandfunc, checkfunc, offclass, onclass) {
     /* A button that can have two states (e.g. pressed and
        not-pressed) based on CSS classes */
@@ -181,6 +173,7 @@ function KupuStateButton(buttonid, comma
         
             if the state of the button should be changed, we set the class
         */
+        if (!this.button) return;
         var currclass = this.button.className;
         var newclass = null;
         if (this.checkfunc(selNode, this, this.editor, event)) {
@@ -202,37 +195,45 @@ KupuStateButton.prototype = new KupuButt
  * Mozilla&Firefox have a bug on windows which can cause a crash if you
  * change CSS positioning styles on an element which has focus.
  */
-function KupuLateFocusStateButton(buttonid, commandfunc, checkfunc, offclass, onclass) {
-    KupuStateButton.apply(this, [buttonid, commandfunc, checkfunc, offclass, onclass]);
-    this.execCommand = function() {
-        /* exec this button's command */
-        this.button.className = (this.pressed ? this.offclass : this.onclass);
-        this.pressed = !this.pressed;
-        this.commandfunc(this, this.editor);
-        this.editor.focusDocument();
-    };
+function KupuLateFocusStateButton(buttonid, commandfunc, checkfunc,
+        offclass, onclass) {
+    KupuStateButton.apply(this, [buttonid, commandfunc, checkfunc,
+                                 offclass, onclass]);
 }
+
 KupuLateFocusStateButton.prototype = new KupuStateButton;
 
+KupuLateFocusStateButton.prototype.execCommand = function() {
+    /* exec this button's command */
+    this.button.className = (this.pressed ? this.offclass : this.onclass);
+    this.pressed = !this.pressed;
+    this.commandfunc(this, this.editor);
+    this.editor.focusDocument();
+};
+
 function KupuRemoveElementButton(buttonid, element_name, cssclass) {
     /* A button specialized in removing elements in the current node
        context. Typical usages include removing links, images, etc. */
     this.button = getFromSelector(buttonid);
+    this.element_name = element_name;
     this.onclass = 'invisible';
     this.offclass = cssclass;
     this.pressed = false;
+};
 
-    this.commandfunc = function(button, editor) {
-        editor.removeNearestParentOfType(editor.getSelectedNode(), element_name);
-    };
+KupuRemoveElementButton.prototype = new KupuStateButton;
 
-    this.checkfunc = function(currnode, button, editor, event) {
-        var element = editor.getNearestParentOfType(currnode, element_name);
-        return (element ? false : true);
-    };
+KupuRemoveElementButton.prototype.commandfunc = function(button, editor) {
+    editor.focusDocument();
+    editor.removeNearestParentOfType(editor.getSelectedNode(), this.element_name);
+    editor.updateState();
 };
 
-KupuRemoveElementButton.prototype = new KupuStateButton;
+KupuRemoveElementButton.prototype.checkfunc = function(currnode, button,
+        editor, event) {
+    var element = editor.getNearestParentOfType(currnode, this.element_name);
+    return (element ? false : true);
+};
 
 function KupuUI(textstyleselectid) {
     /* View 
@@ -244,300 +245,361 @@ function KupuUI(textstyleselectid) {
     
     // attributes
     this.tsselect = getFromSelector(textstyleselectid);
-    var paraoptions = [];
-    var tableoptions = [];
+    this.paraoptions = [];
+    this.tableoptions = [];
+    this.styleoptions = [];
+    this.tableoffset = 0;
+    this.styleoffset = 0;
+    this.tablegrp = null;
     this.optionstate = -1;
     this.otherstyle = null;
     this.tablestyles = {};
+    this.charstyles = {};
     this.styles = {}; // use an object here so we can use the 'in' operator later on
+    this.blocktagre = /^(p|div|h.|ul|ol|dl|menu|dir|pre|blockquote|address|center)$/i;
+    this.spanre = /^span\b/i;
+    this.tblre = /^thead|tbody|table|t[rdh]\b/i;
+};
 
-    this.initialize = function(editor) {
-        /* initialize the ui like tools */
-        this.editor = editor;
-        this.cleanStyles();
-        this.enableOptions(false);
-        this._fixTabIndex(this.tsselect);
+
+KupuUI.prototype = new KupuTool;
+
+KupuUI.prototype.initialize = function(editor) {
+    /* initialize the ui like tools */
+    this.editor = editor;
+    this.cleanStyles();
+    this.enableOptions(false);
+    if (this.tsselect) {
         this._selectevent = addEventHandler(this.tsselect, 'change', this.setTextStyleHandler, this);
-    };
+    }
+};
 
-    this.getStyles = function() {
-        if (!paraoptions) {
-            this.cleanStyles();
-        }
-        return [ paraoptions, tableoptions ];
+KupuUI.prototype.getStyles = function() {
+    if (!this.paraoptions) {
+        this.cleanStyles();
     }
+    return [ this.paraoptions, this.tableoptions ];
+};
 
-    this.setTextStyleHandler = function(event) {
-        this.setTextStyle(this.tsselect.options[this.tsselect.selectedIndex].value);
-    };
-    
-    // event handlers
-    this.basicButtonHandler = function(action) {
-        /* event handler for basic actions (toolbar buttons) */
-        this.editor.execCommand(action);
-        this.editor.updateState();
-    };
+KupuUI.prototype.setTextStyleHandler = function(event) {
+    this.setTextStyle(this.tsselect.options[this.tsselect.selectedIndex].value);
+};
 
-    this.saveButtonHandler = function() {
-        /* handler for the save button */
-        this.editor.saveDocument();
-    };
-
-    this.saveAndExitButtonHandler = function(redirect_url) {
-        /* save the document and, if successful, redirect */
-        this.editor.saveDocument(redirect_url);
-    };
-
-    this.cutButtonHandler = function() {
-        try {
-            this.editor.execCommand('Cut');
-        } catch (e) {
-            if (this.editor.getBrowserName() == 'Mozilla') {
-                alert(_('Cutting from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
-            } else {
-                throw e;
-            };
+// event handlers
+KupuUI.prototype.basicButtonHandler = function(action) {
+    /* event handler for basic actions (toolbar buttons) */
+    this.editor.execCommand(action);
+    this.editor.updateState();
+};
+
+KupuUI.prototype.saveButtonHandler = function() {
+    /* handler for the save button */
+    this.editor.saveDocument();
+};
+
+KupuUI.prototype.saveAndExitButtonHandler = function(redirect_url) {
+    /* save the document and, if successful, redirect */
+    this.editor.saveDocument(redirect_url);
+};
+
+KupuUI.prototype.cutButtonHandler = function() {
+    try {
+        this.editor.execCommand('Cut');
+    } catch (e) {
+        if (this.editor.getBrowserName() == 'Mozilla') {
+            alert(_('Cutting from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
+        } else {
+            throw e;
         };
-        this.editor.updateState();
     };
+    this.editor.updateState();
+};
 
-    this.copyButtonHandler = function() {
-        try {
-            this.editor.execCommand('Copy');
-        } catch (e) {
-            if (this.editor.getBrowserName() == 'Mozilla') {
-                alert(_('Copying from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
-            } else {
-                throw e;
-            };
+KupuUI.prototype.copyButtonHandler = function() {
+    try {
+        this.editor.execCommand('Copy');
+    } catch (e) {
+        if (this.editor.getBrowserName() == 'Mozilla') {
+            alert(_('Copying from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
+        } else {
+            throw e;
         };
-        this.editor.updateState();
     };
+    this.editor.updateState();
+};
 
-    this.pasteButtonHandler = function() {
-        try {
-            this.editor.execCommand('Paste');
-        } catch (e) {
-            if (this.editor.getBrowserName() == 'Mozilla') {
-                alert(_('Pasting from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
-            } else {
-                throw e;
-            };
+KupuUI.prototype.pasteButtonHandler = function() {
+    try {
+        this.editor.execCommand('Paste');
+    } catch (e) {
+        if (this.editor.getBrowserName() == 'Mozilla') {
+            alert(_('Pasting from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
+        } else {
+            throw e;
         };
-        this.editor.updateState();
     };
+    this.editor.updateState();
+};
 
-    this.cleanStyles = function() {
-        var options = this.tsselect.options;
-        var parastyles = this.styles;
-        var tablestyles = this.tablestyles;
-
-        tableoptions.push([options[0].text, 'td|']);
-        tablestyles['td'] = 0;
-        paraoptions.push([options[0].text, 'p|']);
-        parastyles['p'] = 0;
-        while (options.length > 1) {
-            opt = options[1];
-            var v = opt.value;
-            if (/^thead|tbody|table|t[rdh]\b/i.test(v)) {
-                var otable = tableoptions;
-                var styles = tablestyles;
-            } else {
-                var otable = paraoptions;
-                var styles = parastyles;
-            }
-            if (v.indexOf('|') > -1) {
-                var split = v.split('|');
-                v = split[0].toLowerCase() + "|" + split[1];
-            } else {
-                v = v.toLowerCase()+"|";
-            };
-            otable.push([opt.text, v]);
-            styles[v] = otable.length - 1;
-            options[1] = null;
-        }
+KupuUI.prototype.cleanStyles = function() {
+    if (!this.tsselect) return;
+    var options = this.tsselect.options;
+    var parastyles = this.styles;
+    var tablestyles = this.tablestyles;
+    var charstyles = this.charstyles;
+    
+    var normal = ['Normal', 'p|'];
+    var td = ['Plain Cell', 'td|'];
+    var nostyle = ['(remove style)', ''];
+
+    var opts = [];
+    while (options.length) {
+        var opt = options[0];
         options[0] = null;
-    }
-
-    // Remove otherstyle and switch to appropriate style set.
-    this.enableOptions = function(inTable) {
-        var select = this.tsselect;
-        var options = select.options;
-        if (this.otherstyle) {
-            options[options.length-1] = null;
-            this.otherstyle = null;
+        var v = opt.value;
+        if (v.indexOf('|') > -1) {
+            var split = v.split('|');
+            v = split[0].toLowerCase() + "|" + split[1];
+        } else {
+            v = v.toLowerCase()+"|";
+        };
+        var optarray = [opt.text, v];
+        if (v=='td|') {
+            td = optarray;
+        } else if (v=='p|') {
+            normal = optarray;
+        } else if (v=='') {
+            nostyle = optarray;
+        } else {
+            opts.push([opt.text,v]);
         }
-        if (this.optionstate == inTable) return; /* No change */
-
-        var valid = inTable ? tableoptions : paraoptions;
+    }
+    this.tableoptions.push(td);
+    tablestyles[td[1]] = 0;
+    this.paraoptions.push(normal);
+    parastyles[normal[1]] = 0;
+
+    for (var i = 0; i < opts.length; i++) {
+        optarray = opts[i];
+        v = optarray[1];
+
+        if (this.spanre.test(v)) {
+            charstyles[v] = this.styleoptions.length;
+            this.styleoptions.push(optarray);
+        } else if (this.tblre.test(v)) {
+            tablestyles[v] = this.tableoptions.length;
+            this.tableoptions.push(optarray);
+        } else {
+            parastyles[v] = this.paraoptions.length;
+            this.paraoptions.push(optarray);
+        };
+    };
+    this.paraoptions.push(nostyle);
+    this.styleoffset = this.paraoptions.length;
+    this.tableoffset = this.styleoffset + this.styleoptions.length;
+};
 
-        while (options.length) options[0] = null;
+// Remove otherstyle and switch to appropriate style set.
+KupuUI.prototype.enableOptions = function(inTable) {
+    if (!this.tsselect) return;
+    var select = this.tsselect;
+    var options = select.options;
+    if (this.otherstyle) {
+        options[0] = null;
         this.otherstyle = null;
+    }
+    if (this.optionstate == inTable) return; /* No change */
 
-        for (var i = 0; i < valid.length; i++) {
-            var opt = document.createElement('option');
-            opt.text = valid[i][0];
-            opt.value = valid[i][1];
-            options.add(opt);
-        }
-        select.selectedIndex = 0;
-        this.optionstate = inTable;
-    }
-    
-    this.setIndex = function(currnode, tag, index, styles) {
-        var className = currnode.className;
-        this.styletag = tag;
-        this.classname = className;
-        var style = tag+'|'+className;
-
-        if (style in styles) {
-            return styles[style];
-        } else if (!className && tag in styles) {
-            return styles[tag];
-        }
-        return index;
-    }
-
-    this.nodeStyle = function(node) {
-        var currnode = node;
-        var index = -1;
-        var options = this.tsselect.options;
-        this.styletag = undefined;
-        this.classname = '';
-        this.intable = false;
-
-        while (currnode) {
-            var tag = currnode.nodeName.toLowerCase();
-
-            if (/^body$/.test(tag)) {
-                if (!this.styletag) {
-                    // Force style setting
-                    //this.setTextStyle(options[0].value, true);
-                    // Forced style messes up in Firefox: return -1 to
-                    // indicate no style 
-                    return -1;
-                }
-                break;
-            }
-            if (/^(p|div|h.|ul|ol|dl|menu|dir|pre|blockquote|address|center)$/.test(tag)) {
-                index = this.setIndex(currnode, tag, index, this.styles);
-            }
-            if (/^thead|tbody|table|t[rdh]$/.test(tag)) {
-                this.intable = true;
-                index = this.setIndex(currnode, tag, index, this.tablestyles);
+    // while (select.firstChild) select.removeChild(select.firstChild);
 
-                if (index > 0 || tag=='table') {
-                    return index; // Stop processing if in a table
-                }
+    function option(info) {
+        return newElement('option', {'value': info[1]}, [info[0]]);
+    }
+    if (this.optionstate==-1) {
+        for (var i = 0; i < this.paraoptions.length; i++) {
+            select.appendChild(option(this.paraoptions[i]));
+        }
+        if (this.styleoptions.length) {
+            var grp = document.createElement('optgroup');
+            grp.label = 'Character styles';
+            for (var i = 0; i < this.styleoptions.length; i++) {
+                grp.appendChild(option(this.styleoptions[i]));
             }
-            currnode = currnode.parentNode;
+            select.appendChild(grp);
         }
-        return index;
     }
+    if (inTable) {
+        var grp = (this.tablegrp = document.createElement('optgroup'));
+        grp.label = 'Table elements';
+        for (var i = 0; i < this.tableoptions.length; i++) {
+            grp.appendChild(option(this.tableoptions[i]));
+        }
+        select.appendChild(grp);
+    } else {
+        while (select.options[this.tableoffset]) {
+            select.options[this.tableoffset] = null;
+        };
+        if (this.tablegrp) {
+            select.removeChild(this.tablegrp);
+            this.tablegrp = null;
+        };
+    };
+    this.optionstate = inTable;
+};
 
-    this.updateState = function(selNode) {
-        /* set the text-style pulldown */
-
-        // first get the nearest style
-        // search the list of nodes like in the original one, break if we encounter a match,
-        // this method does some more than the original one since it can handle commands in
-        // the form of '<style>|<classname>' next to the plain
-        // '<style>' commands
-        var index = undefined;
-        var mixed = false;
-        var styletag, classname;
-
-        var selection = this.editor.getSelection();
+KupuUI.prototype.setIndex = function(currnode, tag, index, styles) {
+    var className = currnode.className;
+    this.styletag = tag;
+    this.classname = className;
+    var style = tag+'|'+className;
+
+    if (style in styles) {
+        return styles[style];
+    } else if (!className && tag in styles) {
+        return styles[tag];
+    }
+    return index;
+};
 
-        for (var el=selNode.firstChild; el; el=el.nextSibling) {
-            if (el.nodeType==1 && selection.containsNode(el)) {
-                var i = this.nodeStyle(el);
-                if (index===undefined) {
-                    index = i;
-                    styletag = this.styletag;
-                    classname = this.classname;
-                }
-                if (index != i || styletag!=this.styletag || classname != this.classname) {
-                    mixed = true;
-                    break;
-                }
+KupuUI.prototype.nodeStyle = function(node) {
+    var currnode = node;
+    var index = -1;
+    this.styletag = undefined;
+    this.classname = '';
+
+    // Set the table state correctly
+    this.intable = false;
+
+    while(currnode) {
+        var tag = currnode.nodeName;
+        if (/^body$/i.test(tag)) break;
+        if (this.tblre.test(tag)) {
+            this.intable = true;
+            break;
+        };
+        currnode = currnode.parentNode;
+    };
+    currnode = node;
+    while (currnode) {
+        var tag = currnode.nodeName.toLowerCase();
+
+        if (/^body$/.test(tag)) {
+            if (!this.styletag) {
+                // Forced style messes up in Firefox: return -1 to
+                // indicate no style 
+                return -1;
+            }
+            break;
+        }
+        if (this.spanre.test(tag)) {
+            index = this.setIndex(currnode, tag, index, this.charstyles);
+            if (index >= 0) return index+this.styleoffset; // span takes priority
+        } else if (this.blocktagre.test(tag)) {
+            index = this.setIndex(currnode, tag, index, this.styles);
+        } else if (this.tblre.test(tag)) {
+            if (index > 0) return index; // block or span takes priority.
+            index = this.setIndex(currnode, tag, index, this.tablestyles);
+            if (index >= 0 || tag=='table') {
+                return index+this.tableoffset; // Stop processing if in a table
             }
-        };
-
-        if (index===undefined) {
-            index = this.nodeStyle(selNode);
         }
+        currnode = currnode.parentNode;
+    }
+    return index;
+};
 
-        this.enableOptions(this.intable);
+KupuUI.prototype.updateState = function(selNode) {
+    /* set the text-style pulldown */
 
-        if (index < 0 || mixed) {
-            if (mixed) {
-                var caption = 'Mixed styles';
-            } else if (this.styletag) {
-                var caption = 'Other: ' + this.styletag + ' '+ this.classname;
-            } else {
-                var caption = '<no style>';
+    // first get the nearest style
+    // search the list of nodes like in the original one, break if we encounter a match,
+    // this method does some more than the original one since it can handle commands in
+    // the form of '<style>|<classname>' next to the plain
+    // '<style>' commands
+    if (!this.tsselect) return;
+    var index = undefined;
+    var mixed = false;
+    var styletag, classname;
+
+    var selection = this.editor.getSelection();
+
+    for (var el=selNode.firstChild; el; el=el.nextSibling) {
+        if (el.nodeType==1 && selection.containsNode(el)) {
+            var i = this.nodeStyle(el);
+            if (index===undefined) {
+                index = i;
+                styletag = this.styletag;
+                classname = this.classname;
+            }
+            if (index != i || styletag!=this.styletag || classname != this.classname) {
+                mixed = true;
+                break;
             }
-
-            var opt = document.createElement('option');
-            opt.text = caption;
-            this.otherstyle = opt;
-            this.tsselect.options.add(opt);
-
-            index = this.tsselect.length-1;
         }
-        this.tsselect.selectedIndex = Math.max(index,0);
     };
 
-    this._cleanNode = function(node) {
-                /* Clean up a block style node (e.g. P, DIV, Hn)
-                 * Remove trailing whitespace, then also remove up to one
-                 * trailing <br>
-                 * If the node is now empty, remove the node itself.
-                 */
-        var len = node.childNodes.length;
-        function stripspace() {
-            var c;
-            while ((c = node.lastChild) && c.nodeType==3 && /^\s*$/.test(c.data)) {
-                node.removeChild(c);
-            }
+    if (index===undefined) {
+        index = this.nodeStyle(selNode);
+    }
+    this.enableOptions(this.intable);
+
+    if (index < 0 || mixed) {
+        if (mixed) {
+            var caption = 'Mixed styles';
+        } else if (this.styletag) {
+            var caption = 'Other: ' + this.styletag + ' '+ this.classname;
+        } else {
+            var caption = '<no style>';
         }
-        stripspace();
-        var c = node.lastChild;
-        if (c && c.nodeType==1 && c.tagName=='BR') {
+
+        var opt = newElement('option');
+        opt.text = caption;
+        this.otherstyle = opt;
+        this.tsselect.options.add(opt,0);
+        index = 0;
+    }
+    this.tsselect.selectedIndex = Math.max(index,0);
+};
+
+KupuUI.prototype._cleanNode = function(node, preserveEmpty) {
+            /* Clean up a block style node (e.g. P, DIV, Hn)
+             * Remove trailing whitespace, then also remove up to one
+             * trailing <br>
+             * If the node is now empty and no preserveEmpty, remove the node itself.
+             */
+    function stripspace() {
+        var c;
+        while ((c = node.lastChild) && c.nodeType==3 && (/^\s*$/.test(c.data))) {
             node.removeChild(c);
         }
-        stripspace();
-        if (node.childNodes.length==0) {
-            node.parentNode.removeChild(node);
-        };
     }
+    stripspace();
+    var c = node.lastChild;
+    if (c && c.nodeType==1 && c.tagName=='BR') {
+        node.removeChild(c);
+    }
+    stripspace();
+    if (node.childNodes.length==0 && !preserveEmpty) {
+        node.parentNode.removeChild(node);
+    };
+};
 
-    this._cleanCell = function(eltype, classname) {
-        var selNode = this.editor.getSelectedNode();
-        var el = this.editor.getNearestParentOfType(selNode, eltype);
-        if (!el) {
-                // Maybe changing type
-            el = this.editor.getNearestParentOfType(selNode, eltype=='TD'?'TH':'TD');
-        }
-        if (!el) return;
-
-            // Remove formatted div or p from a cell
-        var node, nxt, n;
-        for (node = el.firstChild; node;) {
-            if (/DIV|P/.test(node.nodeName)) {
-                for (var n = node.firstChild; n;) {
-                    var nxt = n.nextSibling;
-                    el.insertBefore(n, node); // Move nodes out of div
-                    n = nxt;
-                }
-                nxt = node.nextSibling;
-                el.removeChild(node);
-                node = nxt;
-            } else {
-                node = node.nextSibling;
-            }
-        }
+KupuUI.prototype._cleanCell = function(eltype, classname, strip) {
+    var alttype=eltype=='TD'?'TH':eltype=='TH'?'TD':null;
+    
+    var selNode = this.editor.getSelectedNode(true);
+    var el = this.editor.getNearestParentOfType(selNode, eltype);
+    if (!el && alttype) {
+        // Maybe changing type
+        el = this.editor.getNearestParentOfType(selNode, alttype);
+    }
+
+    //either the selection is inside a cell, spans cells, or includes
+    //a collection of cells
+
+    //first, if contained in a cell
+    
+    if (el) {
         if (eltype != el.tagName) {
                 // Change node type.
             var node = el.ownerDocument.createElement(eltype);
@@ -550,108 +612,218 @@ function KupuUI(textstyleselectid) {
             el = node;
         }
             // now set the classname
-        if (classname) {
-            el.className = classname;
-        } else {
-            el.removeAttribute("class");
-            el.removeAttribute("className");
-        }
-
-    }
+        this._setClass(el, classname);
+        if (strip && el.childNodes.length==1) {
+            var node = el.firstChild;
+            if (this.blocktagre.test(node.nodeName)) {
+                for (var n = node.firstChild; n;) {
+                    var nxt = n.nextSibling;
+                    el.insertBefore(n, node); // Move nodes out of block
+                    n = nxt;
+                };
+                nxt = node.nextSibling;
+                el.removeChild(node);
+                node = nxt;
+            };
+        };
+    } else {
+        //otherwise, find all cells that intersect the selection
+        var selection = this.editor.getSelection();
+        var nodes = selNode.getElementsByTagName(eltype);
 
-    this._setClass = function(el, classname) {
-        var parent = el.parentNode;
-        if (parent.tagName=='DIV') {
-            // fixup buggy formatting
-            var gp = parent.parentNode;
-            if (el != parent.firstChild) {
-                var previous = parent.cloneNode(false);
-                while (el != parent.firstChild) {
-                    previous.appendChild(parent.firstChild);
-                }
-                gp.insertBefore(previous, parent);
-                this._cleanNode(previous);
-            }
-            gp.insertBefore(el, parent);
-            this._cleanNode(el);
-            this._cleanNode(parent);
-        } else {
-            this._cleanNode(el);
-        }
-        // now set the classname
-        if (classname) {
-            el.className = classname;
-        } else {
-            el.removeAttribute("class");
-            el.removeAttribute("className");
-        }
-    }
-    this.setTextStyle = function(style, noupdate) {
-            /* parse the argument into a type and classname part
-               generate a block element accordingly 
-            */
-        var classname = '';
-        var eltype = style.toUpperCase();
-        if (style.indexOf('|') > -1) {
-            style = style.split('|');
-            eltype = style[0].toUpperCase();
-            classname = style[1];
-        };
-
-        var command = eltype;
-            // first create the element, then find it and set the classname
-        if (this.editor.getBrowserName() == 'IE') {
-            command = '<' + eltype + '>';
+        var cellNodes = [];
+        for (var i = 0; i < nodes.length; i++) {
+            cellNodes.push(nodes.item(i));
+        };
+        if (alttype) {
+            nodes = selNode.getElementsByTagName(alttype);
+            for (var i = 0; i < nodes.length; i++) {
+                cellNodes.push(nodes.item(i));
+            };
         };
-        if (/T[RDH]/.test(eltype)) {
-            this._cleanCell(eltype, classname);
-        }
-        else {
-            this.editor.getDocument().execCommand('formatblock', command);
+        
+        for (var i = 0; i < cellNodes.length; i++) {
+            el = cellNodes[i];
 
-                // now get a reference to the element just added
-            var selNode = this.editor.getSelectedNode();
-            var el = this.editor.getNearestParentOfType(selNode, eltype);
-            if (el) {
+            if(selection.intersectsNode(el)) {
+                if (eltype != el.tagName) {
+                    // Change node type.
+                    var node = el.ownerDocument.createElement(eltype);
+                    var parent = el.parentNode;
+                    parent.insertBefore(node, el);
+                    while (el.firstChild) {
+                        node.appendChild(el.firstChild);
+                    };
+                    parent.removeChild(el);
+                    el = node;
+                };
                 this._setClass(el, classname);
-            } else {
-                var selection = this.editor.getSelection();
-                var elements = selNode.getElementsByTagName(eltype);
-                for (var i = 0; i < elements.length; i++) {
-                    el = elements[i];
-                    if (selection.containsNode(el)) {
-                        this._setClass(el, classname);
-                    }
-                }
             }
         }
-        if (el) {
-            this.editor.getSelection().selectNodeContents(el);
+    }
+};
+
+KupuUI.prototype._setClass = function(el, classname) {
+    var parent = el.parentNode;
+    if (parent.tagName=='DIV') {
+        // fixup buggy formatting
+        var gp = parent.parentNode;
+        if (el != parent.firstChild) {
+            var previous = parent.cloneNode(false);
+            while (el != parent.firstChild) {
+                previous.appendChild(parent.firstChild);
+            }
+            gp.insertBefore(previous, parent);
+            this._cleanNode(previous);
+        }
+        gp.insertBefore(el, parent);
+        this._cleanNode(parent);
+    };
+    // now set the classname
+    if (classname) {
+        el.className = classname;
+    } else {
+        el.removeAttribute("class");
+        el.removeAttribute("className");
+    }
+};
+
+KupuUI.prototype._removeStyle = function() {
+    var self = this;
+    function needbreak(e) {
+        if (isblock && e) {
+            if (self.blocktagre.test(e.nodeName) || (/^br$/i.test(e.nodeName))) return;
+            parent.insertBefore(ed.newElement('br'), n);
+        }
+    }
+    var n = this.editor.getSelectedNode(true);
+    var ed = this.editor;
+    while(n) {
+        var tag = n.nodeName.toLowerCase();
+        var isblock = this.blocktagre.test(tag);
+        if (this.tblre.test(tag) && n.className) {
+            n.removeAttribute("class");
+            n.removeAttribute("className");
+            return;
         }
-        if (!noupdate) {
-            this.editor.updateState();
+        if (isblock || tag == 'span') {
+            var parent = n.parentNode;
+            var el;
+            needbreak(n.previousSibling);
+            while ((el = n.firstChild)) {
+                parent.insertBefore(el, n);
+            }
+            needbreak(n.nextSibling);
+            parent.removeChild(n);
+            return;
         }
+        n = n.parentNode;
     };
-  
-    this.createContextMenuElements = function(selNode, event) {
-        var ret = new Array();
-        ret.push(new ContextMenuElement(_('Cut'), 
-                    this.cutButtonHandler, this));
-        ret.push(new ContextMenuElement(_('Copy'), 
-                    this.copyButtonHandler, this));
-        ret.push(new ContextMenuElement(_('Paste'), 
-                    this.pasteButtonHandler, this));
-        return ret;
-    };
-    this.disable = function() {
-        this.tsselect.disabled = "disabled";
-    }
-    this.enable = function() {
-        this.tsselect.disabled = "";
-    }
-}
+};
 
-KupuUI.prototype = new KupuTool;
+KupuUI.prototype.setTextStyle = function(style, noupdate) {
+    /* parse the argument into a type and classname part
+       generate a block element accordingly 
+    */
+    var classname = '';
+    var eltype = style.toUpperCase();
+    if (style.indexOf('|') > -1) {
+        style = style.split('|');
+        eltype = style[0].toUpperCase();
+        classname = style[1];
+    };
+
+    var doc = this.editor.getDocument();
+    var command = eltype;
+        // first create the element, then find it and set the classname
+    if (this.editor.getBrowserName() == 'IE') {
+        command = '<' + eltype + '>';
+    };
+    if (!style) {
+        this._removeStyle();
+    } else if (this.tblre.test(eltype)) {
+        this._cleanCell(eltype, classname);
+    } else if (eltype=='SPAN') {
+        doc.execCommand('removeformat', null);
+        if (this.editor.getBrowserName()=='IE') {
+            // removeformat is broken in IE: it doesn't remove span
+            // tags
+            var selNode = this.editor.getSelectedNode();
+            var selection = this.editor.getSelection();
+            var elements = selNode.getElementsByTagName('span');
+            for (var i = 0; i < elements.length; i++) {
+                var span = elements[i];
+                if (selection.containsNode(span)) {
+                    var parent = span.parentNode;
+                    while (span.firstChild) {
+                        parent.insertBefore(span.firstChild, span);
+                    };
+                    parent.removeChild(span);
+                };
+            };
+        }
+        if (classname) {
+            doc.execCommand('fontsize', '2');
+            // Now convert font tags to spans
+            var inner = doc.getDocument();
+            var elements = inner.getElementsByTagName('FONT');
+            while (elements.length > 0) {
+                var font = elements[0];
+                var span = inner.createElement('SPAN');
+                span.className = classname;
+                var parent = font.parentNode;
+                parent.replaceChild(span, font);
+                while (font.firstChild) {
+                    span.appendChild(font.firstChild);
+                };
+            };
+        };
+    }
+    else {
+        doc.execCommand('formatblock', command);
+
+            // now get a reference to the element just added
+        var selNode = this.editor.getSelectedNode(true);
+        var el = this.editor.getNearestParentOfType(selNode, eltype);
+        if (el) {
+            this._setClass(el, classname);
+        } else {
+            var selection = this.editor.getSelection();
+            var elements = selNode.getElementsByTagName(eltype);
+            for (var i = 0; i < elements.length; i++) {
+                el = elements[i];
+                if (selection.containsNode(el)) {
+                    this._setClass(el, classname);
+                }
+            }
+        }
+    }
+    if (el) {
+        this.editor.getSelection().selectNodeContents(el);
+    }
+    if (!noupdate) {
+        this.editor.updateState();
+    }
+};
+
+KupuUI.prototype.createContextMenuElements = function(selNode, event) {
+    var ret = [];
+    ret.push(new ContextMenuElement(_('Cut'), 
+                this.cutButtonHandler, this));
+    ret.push(new ContextMenuElement(_('Copy'), 
+                this.copyButtonHandler, this));
+    ret.push(new ContextMenuElement(_('Paste'), 
+                this.pasteButtonHandler, this));
+    return ret;
+};
+
+KupuUI.prototype.disable = function() {
+    if (this.tsselect) this.tsselect.disabled = "disabled";
+};
+
+KupuUI.prototype.enable = function() {
+    if (this.tsselect) this.tsselect.disabled = "";
+};
 
 function ColorchooserTool(fgcolorbuttonid, hlcolorbuttonid, colorchooserid) {
     /* the colorchooser */
@@ -660,342 +832,353 @@ function ColorchooserTool(fgcolorbuttoni
     this.hlcolorbutton = getFromSelector(hlcolorbuttonid);
     this.ccwindow = getFromSelector(colorchooserid);
     this.command = null;
+}
 
-    this.initialize = function(editor) {
-        /* attach the event handlers */
-        this.editor = editor;
-        
-        this.createColorchooser(this.ccwindow);
-
-        addEventHandler(this.fgcolorbutton, "click", this.openFgColorChooser, this);
-        addEventHandler(this.hlcolorbutton, "click", this.openHlColorChooser, this);
-        addEventHandler(this.ccwindow, "click", this.chooseColor, this);
+ColorchooserTool.prototype = new KupuTool;
 
-        this.hide();
+ColorchooserTool.prototype.initialize = function(editor) {
+    /* attach the event handlers */
+    this.editor = editor;
+    if (!(this.fgcolorbutton && this.hlcolorbutton && this.ccwindow)) return;
+    this.createColorchooser(this.ccwindow);
+
+    addEventHandler(this.fgcolorbutton, "click", this.openFgColorChooser, this);
+    addEventHandler(this.hlcolorbutton, "click", this.openHlColorChooser, this);
+    addEventHandler(this.ccwindow, "click", this.chooseColor, this);
+    this.hide();
+};
 
-        this.editor.logMessage(_('Colorchooser tool initialized'));
-    };
+ColorchooserTool.prototype.updateState = function(selNode) {
+    /* update state of the colorchooser */
+    this.hide();
+};
 
-    this.updateState = function(selNode) {
-        /* update state of the colorchooser */
-        this.hide();
-    };
+ColorchooserTool.prototype.openFgColorChooser = function() {
+    /* event handler for opening the colorchooser */
+    this.command = "forecolor";
+    this.show();
+};
 
-    this.openFgColorChooser = function() {
-        /* event handler for opening the colorchooser */
-        this.command = "forecolor";
-        this.show();
-    };
+ColorchooserTool.prototype.openHlColorChooser = function() {
+    /* event handler for closing the colorchooser */
+    if (this.editor.getBrowserName() == "IE") {
+        this.command = "backcolor";
+    } else {
+        this.command = "hilitecolor";
+    }
+    this.show();
+};
 
-    this.openHlColorChooser = function() {
-        /* event handler for closing the colorchooser */
-        if (this.editor.getBrowserName() == "IE") {
-            this.command = "backcolor";
-        } else {
-            this.command = "hilitecolor";
-        }
-        this.show();
-    };
+ColorchooserTool.prototype.chooseColor = function(event) {
+    /* event handler for choosing the color */
+    var target = _SARISSA_IS_MOZ ? event.target : event.srcElement;
+    var cell = this.editor.getNearestParentOfType(target, 'td');
+    var ed = this.editor;
+    var doc = ed.getDocument();
+    ed.execCommand('styleWithCSS', true);
+    doc.execCommand(this.command, cell.bgColor);
+    ed.execCommand('styleWithCSS', false);
+    // this.editor.execCommand(this.command, cell.bgColor);
+    this.hide();
 
-    this.chooseColor = function(event) {
-        /* event handler for choosing the color */
-        var target = _SARISSA_IS_MOZ ? event.target : event.srcElement;
-        var cell = this.editor.getNearestParentOfType(target, 'td');
-        this.editor.execCommand(this.command, cell.getAttribute('bgColor'));
-        this.hide();
-    
-        this.editor.logMessage(_('Color chosen'));
-    };
+    this.editor.logMessage(_('Color chosen'));
+};
 
-    this.show = function(command) {
-        /* show the colorchooser */
-        this.ccwindow.style.display = "block";
-    };
+ColorchooserTool.prototype.show = function(command) {
+    /* show the colorchooser */
+    this.ccwindow.style.display = "block";
+};
 
-    this.hide = function() {
-        /* hide the colorchooser */
-        this.command = null;
-        this.ccwindow.style.display = "none";
-    };
+ColorchooserTool.prototype.hide = function() {
+    /* hide the colorchooser */
+    this.ccwindow.style.display = "none";
+};
 
-    this.createColorchooser = function(table) {
-        /* create the colorchooser table */
-        
-        var chunks = new Array('00', '33', '66', '99', 'CC', 'FF');
-        table.setAttribute('id', 'kupu-colorchooser-table');
-        table.style.borderWidth = '2px';
-        table.style.borderStyle = 'solid';
-        table.style.position = 'absolute';
-        table.style.cursor = 'default';
-        table.style.display = 'none';
-
-        var tbody = document.createElement('tbody');
-
-        for (var i=0; i < 6; i++) {
-            var tr = document.createElement('tr');
-            var r = chunks[i];
-            for (var j=0; j < 6; j++) {
-                var g = chunks[j];
-                for (var k=0; k < 6; k++) {
-                    var b = chunks[k];
-                    var color = '#' + r + g + b;
-                    var td = document.createElement('td');
-                    td.setAttribute('bgColor', color);
-                    td.style.backgroundColor = color;
-                    td.style.borderWidth = '1px';
-                    td.style.borderStyle = 'solid';
-                    td.style.fontSize = '1px';
-                    td.style.width = '10px';
-                    td.style.height = '10px';
-                    var text = document.createTextNode('\u00a0');
-                    td.appendChild(text);
-                    tr.appendChild(td);
-                }
+ColorchooserTool.prototype.createColorchooser = function(table) {
+    /* create the colorchooser table */
+    
+    var chunks = ['00', '33', '66', '99', 'CC', 'FF'];
+    table.setAttribute('id', 'kupu-colorchooser-table');
+    table.style.borderWidth = '2px';
+    table.style.borderStyle = 'solid';
+    table.style.position = 'absolute';
+    table.style.cursor = 'default';
+    table.style.display = 'none';
+
+    var tbody = document.createElement('tbody');
+
+    for (var i=0; i < 6; i++) {
+        var tr = document.createElement('tr');
+        var r = chunks[i];
+        for (var j=0; j < 6; j++) {
+            var g = chunks[j];
+            for (var k=0; k < 6; k++) {
+                var b = chunks[k];
+                var color = '#' + r + g + b;
+                var td = document.createElement('td');
+                td.setAttribute('bgColor', color);
+                td.style.backgroundColor = color;
+                td.style.borderWidth = '1px';
+                td.style.borderStyle = 'solid';
+                td.style.fontSize = '1px';
+                td.style.width = '10px';
+                td.style.height = '10px';
+                var text = document.createTextNode('\u00a0');
+                td.appendChild(text);
+                tr.appendChild(td);
             }
-            tbody.appendChild(tr);
         }
-        table.appendChild(tbody);
-
-        return table;
-    };
-    this.enable = function() {
-        KupuButtonEnable(this.fgcolorbutton);
-        KupuButtonEnable(this.hlcolorbutton);
-    }
-    this.disable = function() {
-        KupuButtonDisable(this.fgcolorbutton);
-        KupuButtonDisable(this.hlcolorbutton);
+        tbody.appendChild(tr);
     }
-}
+    table.appendChild(tbody);
 
-ColorchooserTool.prototype = new KupuTool;
+    return table;
+};
+
+ColorchooserTool.prototype.enable = function() {
+    kupuButtonEnable(this.fgcolorbutton);
+    kupuButtonEnable(this.hlcolorbutton);
+};
+
+ColorchooserTool.prototype.disable = function() {
+    kupuButtonDisable(this.fgcolorbutton);
+    kupuButtonDisable(this.hlcolorbutton);
+};
 
 function PropertyTool(titlefieldid, descfieldid) {
     /* The property tool */
 
     this.titlefield = getFromSelector(titlefieldid);
     this.descfield = getFromSelector(descfieldid);
+};
 
-    this.initialize = function(editor) {
-        /* attach the event handlers and set the initial values */
-        this.editor = editor;
-        addEventHandler(this.titlefield, "change", this.updateProperties, this);
-        addEventHandler(this.descfield, "change", this.updateProperties, this);
-        
-        // set the fields
-        var heads = this.editor.getInnerDocument().getElementsByTagName('head');
-        if (!heads[0]) {
-            this.editor.logMessage(_('No head in document!'), 1);
-        } else {
-            var head = heads[0];
-            var titles = head.getElementsByTagName('title');
-            if (titles.length) {
-                this.titlefield.value = titles[0].text;
-            }
-            var metas = head.getElementsByTagName('meta');
-            if (metas.length) {
-                for (var i=0; i < metas.length; i++) {
-                    var meta = metas[i];
-                    if (meta.getAttribute('name') && 
-                            meta.getAttribute('name').toLowerCase() == 
-                            'description') {
-                        this.descfield.value = meta.getAttribute('content');
-                        break;
-                    }
-                }
-            }
-        }
-
-        this.editor.logMessage(_('Property tool initialized'));
-    };
-
-    this.updateProperties = function() {
-        /* event handler for updating the properties form */
-        var doc = this.editor.getInnerDocument();
-        var heads = doc.getElementsByTagName('HEAD');
-        if (!heads) {
-            this.editor.logMessage(_('No head in document!'), 1);
-            return;
-        }
+PropertyTool.prototype = new KupuTool;
 
+PropertyTool.prototype.initialize = function(editor) {
+    /* attach the event handlers and set the initial values */
+    this.editor = editor;
+    addEventHandler(this.titlefield, "change", this.updateProperties, this);
+    addEventHandler(this.descfield, "change", this.updateProperties, this);
+    
+    // set the fields
+    var heads = this.editor.getInnerDocument().getElementsByTagName('head');
+    if (!heads[0]) {
+        this.editor.logMessage(_('No head in document!'), 1);
+    } else {
         var head = heads[0];
-
-        // set the title
         var titles = head.getElementsByTagName('title');
-        if (!titles) {
-            var title = doc.createElement('title');
-            var text = doc.createTextNode(this.titlefield.value);
-            title.appendChild(text);
-            head.appendChild(title);
-        } else {
-            var title = titles[0];
-            // IE6 title has no children, and refuses appendChild.
-            // Delete and recreate the title.
-            if (title.childNodes.length == 0) {
-                title.removeNode(true);
-                title = doc.createElement('title');
-                title.innerText = this.titlefield.value;
-                head.appendChild(title);
-            } else {
-                title.childNodes[0].nodeValue = this.titlefield.value;
+        if (titles.length) {
+            this.titlefield.value = titles[0].text;
+        }
+        var metas = head.getElementsByTagName('meta');
+        if (metas.length) {
+            for (var i=0; i < metas.length; i++) {
+                var meta = metas[i];
+                if (meta.getAttribute('name') && 
+                        meta.getAttribute('name').toLowerCase() == 
+                        'description') {
+                    this.descfield.value = meta.getAttribute('content');
+                    break;
+                }
             }
         }
-        document.title = this.titlefield.value;
+    }
+};
 
-        // let's just fulfill the usecase, not think about more properties
-        // set the description
-        var metas = doc.getElementsByTagName('meta');
-        var descset = 0;
-        for (var i=0; i < metas.length; i++) {
-            var meta = metas[i];
-            if (meta.getAttribute('name') && 
-                    meta.getAttribute('name').toLowerCase() == 'description') {
-                meta.setAttribute('content', this.descfield.value);
-                descset = 1;
-            }
+PropertyTool.prototype.updateProperties = function() {
+    /* event handler for updating the properties form */
+    var doc = this.editor.getInnerDocument();
+    var heads = doc.getElementsByTagName('HEAD');
+    if (!heads) {
+        this.editor.logMessage(_('No head in document!'), 1);
+        return;
+    }
+
+    var head = heads[0];
+
+    // set the title
+    var titles = head.getElementsByTagName('title');
+    if (!titles) {
+        var title = doc.createElement('title');
+        var text = doc.createTextNode(this.titlefield.value);
+        title.appendChild(text);
+        head.appendChild(title);
+    } else {
+        var title = titles[0];
+        // IE6 title has no children, and refuses appendChild.
+        // Delete and recreate the title.
+        if (title.childNodes.length == 0) {
+            title.removeNode(true);
+            title = doc.createElement('title');
+            title.innerText = this.titlefield.value;
+            head.appendChild(title);
+        } else {
+            title.childNodes[0].nodeValue = this.titlefield.value;
         }
+    }
+    document.title = this.titlefield.value;
 
-        if (!descset) {
-            var meta = doc.createElement('meta');
-            meta.setAttribute('name', 'description');
+    // let's just fulfill the usecase, not think about more properties
+    // set the description
+    var metas = doc.getElementsByTagName('meta');
+    var descset = 0;
+    for (var i=0; i < metas.length; i++) {
+        var meta = metas[i];
+        if (meta.getAttribute('name') && 
+                meta.getAttribute('name').toLowerCase() == 'description') {
             meta.setAttribute('content', this.descfield.value);
-            head.appendChild(meta);
+            descset = 1;
         }
+    }
 
-        this.editor.logMessage(_('Properties modified'));
-    };
-}
+    if (!descset) {
+        var meta = doc.createElement('meta');
+        meta.setAttribute('name', 'description');
+        meta.setAttribute('content', this.descfield.value);
+        head.appendChild(meta);
+    }
 
-PropertyTool.prototype = new KupuTool;
+    this.editor.logMessage(_('Properties modified'));
+};
 
-function LinkTool() {
+function LinkTool(popupurl, popupwidth, popupheight, popupprops) {
     /* Add and update hyperlinks */
+    this.popupurl = popupurl || 'kupupopups/link.html';
+    this.popupwidth = popupwidth || 300;
+    this.popupheight = popupheight || 200;
+    this.popupprops = popupprops || '';
+}
+
+LinkTool.prototype = new KupuTool;
     
-    this.initialize = function(editor) {
-        this.editor = editor;
-        this.editor.logMessage(_('Link tool initialized'));
-    };
-    
-    this.createLinkHandler = function(event) {
-        /* create a link according to a url entered in a popup */
-        var linkWindow = openPopup('kupupopups/link.html', 300, 200);
-        linkWindow.linktool = this;
-        linkWindow.focus();
-    };
+LinkTool.prototype.initialize = function(editor) {
+    this.editor = editor;
+};
 
-    this.updateLink = function (linkel, url, type, name, target, title) {
-        if (type && type == 'anchor') {
-            linkel.removeAttribute('href');
-            linkel.setAttribute('name', name);
+LinkTool.prototype.createLinkHandler = function(event) {
+    /* create a link according to a url entered in a popup */
+    var linkWindow = openPopup(this.popupurl, this.popupwidth,
+                               this.popupheight, this.popupprops);
+    linkWindow.linktool = this;
+    linkWindow.focus();
+};
+
+LinkTool.prototype.updateLink = function (linkel, url, type, name, target, title, className, bForce) {
+    if (type && type == 'anchor') {
+        linkel.removeAttribute('href');
+        linkel.setAttribute('name', name);
+    } else {
+        linkel.href = url;
+        if (linkel.innerHTML == "" || (bForce && linkel.innerHTML==url)) {
+            var doc = this.editor.getInnerDocument();
+            while (linkel.firstChild) { linkel.removeChild(linkel.firstChild); };
+            linkel.appendChild(doc.createTextNode(title || url));
+        }
+        if (title) {
+            linkel.title = title;
         } else {
-            linkel.href = url;
-            if (linkel.innerHTML == "") {
-                var doc = this.editor.getInnerDocument();
-                linkel.appendChild(doc.createTextNode(title || url));
-            }
-            if (title) {
-                linkel.title = title;
-            } else {
-                linkel.removeAttribute('title');
-            }
-            if (target && target != '') {
-                linkel.setAttribute('target', target);
-            }
-            else {
-                linkel.removeAttribute('target');
-            };
-            linkel.style.color = this.linkcolor;
+            linkel.removeAttribute('title');
+        }
+        if (target) {
+            linkel.setAttribute('target', target);
+        }
+        else {
+            linkel.removeAttribute('target');
         };
-    };
-
-    this.formatSelectedLink = function(url, type, name, target, title) {
-        var currnode = this.editor.getSelectedNode();
-
-        // selection inside link
-        var linkel = this.editor.getNearestParentOfType(currnode, 'A');
-        if (linkel) {
-            this.updateLink(linkel, url, type, name, target, title);
-            return true;
+        if (className===undefined) {
+            linkel.removeAttribute('className');
+        } else {
+            linkel.className = className;
         }
+        linkel.style.color = this.linkcolor;
+    };
+};
 
-        if (currnode.nodeType!=1) return false;
+LinkTool.prototype.formatSelectedLink = function(url, type, name, target, title, className, bForce) {
+    var currnode = this.editor.getSelectedNode();
 
-        // selection contains links
-        var linkelements = currnode.getElementsByTagName('A');
-        var selection = this.editor.getSelection();
-        var containsLink = false;
-        for (var i = 0; i < linkelements.length; i++) {
-            linkel = linkelements[i];
-            if (selection.containsNode(linkel)) {
-                this.updateLink(linkel, url, type, name, target, title);
-                containsLink = true;
-            }
-        };
-        return containsLink;
-    }
-
-    // Can create a link in the following circumstances:
-    //   The selection is inside a link:
-    //      just update the link attributes.
-    //   The selection contains links:
-    //      update the attributes of the contained links
-    //   No links inside or outside the selection:
-    //      create a link around the selection
-    //   No selection:
-    //      insert a link containing the title
-    //
-    // the order of the arguments is a bit odd here because of backward
-    // compatibility
-    this.createLink = function(url, type, name, target, title) {
-        if (!this.formatSelectedLink(url, type, name, target, title)) {
-            // No links inside or outside.
-            this.editor.execCommand("CreateLink", url);
-            if (!this.formatSelectedLink(url, type, name, target, title)) {
-                // Insert link with no text selected, insert the title
-                // or URI instead.
-                var doc = this.editor.getInnerDocument();
-                linkel = doc.createElement("a");
-                linkel.setAttribute('href', url);
-                linkel.setAttribute('class', 'generated');
-                this.editor.getSelection().replaceWithNode(linkel, true);
-                this.updateLink(linkel, url, type, name, target, title);
-            };
+    // selection inside link
+    var linkel = this.editor.getNearestParentOfType(currnode, 'A');
+    if (linkel) {
+        this.updateLink(linkel, url, type, name, target, title, className, bForce);
+        return true;
+    }
+
+    if (currnode.nodeType!=1) return false;
+
+    // selection contains links
+    var linkelements = currnode.getElementsByTagName('A');
+    var selection = this.editor.getSelection();
+    var containsLink = false;
+    for (var i = 0; i < linkelements.length; i++) {
+        linkel = linkelements[i];
+        if (selection.containsNode(linkel)) {
+            this.updateLink(linkel, url, type, name, target, title, className, bForce);
+            containsLink = true;
         }
-        this.editor.logMessage(_('Link added'));
-        this.editor.updateState();
     };
-    
-    this.deleteLink = function() {
-        /* delete the current link */
-        var currnode = this.editor.getSelectedNode();
-        var linkel = this.editor.getNearestParentOfType(currnode, 'a');
-        if (!linkel) {
-            this.editor.logMessage(_('Not inside link'));
-            return;
-        };
-        while (linkel.childNodes.length) {
-            linkel.parentNode.insertBefore(linkel.childNodes[0], linkel);
+    return containsLink;
+};
+
+// Can create a link in the following circumstances:
+//   The selection is inside a link:
+//      just update the link attributes.
+//   The selection contains links:
+//      update the attributes of the contained links
+//   No links inside or outside the selection:
+//      create a link around the selection
+//   No selection:
+//      insert a link containing the title
+//
+// the order of the arguments is a bit odd here because of backward
+// compatibility
+LinkTool.prototype.createLink = function(url, type, name, target, title, className) {
+    url = url.strip();
+    if (!url) {
+        this.deleteLink();
+        return;
+    };
+    if (!this.formatSelectedLink(url, type, name, target, title, className)) {
+        // No links inside or outside.
+        this.editor.execCommand("CreateLink", url);
+        if (!this.formatSelectedLink(url, type, name, target, title, className, true)) {
+            // Insert link with no text selected, insert the title
+            // or URI instead.
+            var doc = this.editor.getInnerDocument();
+            var linkel = doc.createElement("a");
+            linkel.setAttribute('href', url);
+            linkel.setAttribute('class', className || 'generated');
+            this.editor.getSelection().replaceWithNode(linkel, true);
+            this.updateLink(linkel, url, type, name, target, title, className);
         };
-        linkel.parentNode.removeChild(linkel);
-        
-        this.editor.logMessage(_('Link removed'));
-        this.editor.updateState();
+    }
+};
+
+LinkTool.prototype.deleteLink = function() {
+    /* delete the current link */
+    var currnode = this.editor.getSelectedNode();
+    var linkel = this.editor.getNearestParentOfType(currnode, 'a');
+    if (!linkel) {
+        this.editor.logMessage(_('Not inside link'));
+        return;
     };
-    
-    this.createContextMenuElements = function(selNode, event) {
-        /* create the 'Create link' or 'Remove link' menu elements */
-        var ret = new Array();
-        var link = this.editor.getNearestParentOfType(selNode, 'a');
-        if (link) {
-            ret.push(new ContextMenuElement(_('Delete link'), this.deleteLink, this));
-        } else {
-            ret.push(new ContextMenuElement(_('Create link'), this.createLinkHandler, this));
-        };
-        return ret;
+    while (linkel.childNodes.length) {
+        linkel.parentNode.insertBefore(linkel.childNodes[0], linkel);
     };
-}
+    linkel.parentNode.removeChild(linkel);
+};
 
-LinkTool.prototype = new KupuTool;
+LinkTool.prototype.createContextMenuElements = function(selNode, event) {
+    /* create the 'Create link' or 'Remove link' menu elements */
+    var ret = [];
+    var link = this.editor.getNearestParentOfType(selNode, 'a');
+    if (link) {
+        ret.push(new ContextMenuElement(_('Delete link'), this.deleteLink, this));
+    } else {
+        ret.push(new ContextMenuElement(_('Create link'), this.createLinkHandler, this));
+    };
+    return ret;
+};
 
 function LinkToolBox(inputid, buttonid, toolboxid, plainclass, activeclass) {
     /* create and edit links */
@@ -1005,107 +1188,138 @@ function LinkToolBox(inputid, buttonid, 
     this.toolboxel = getFromSelector(toolboxid);
     this.plainclass = plainclass;
     this.activeclass = activeclass;
-    
-    this.initialize = function(tool, editor) {
-        /* attach the event handlers */
-        this.tool = tool;
-        this.editor = editor;
-        addEventHandler(this.input, "blur", this.updateLink, this);
-        addEventHandler(this.button, "click", this.addLink, this);
-    };
+};
 
-    this.updateState = function(selNode) {
-        /* if we're inside a link, update the input, else empty it */
-        var linkel = this.editor.getNearestParentOfType(selNode, 'a');
-        if (linkel) {
-            // check first before setting a class for backward compatibility
-            if (this.toolboxel) {
-                this.toolboxel.className = this.activeclass;
-            };
-            this.input.value = linkel.getAttribute('href');
-        } else {
-            // check first before setting a class for backward compatibility
-            if (this.toolboxel) {
-                this.toolboxel.className = this.plainclass;
-            };
-            this.input.value = '';
-        }
-    };
-    
-    this.addLink = function(event) {
-        /* add a link */
-        var url = this.input.value;
-        this.tool.createLink(url);
-    };
+LinkToolBox.prototype = new LinkToolBox;
     
-    this.updateLink = function() {
-        /* update the current link */
-        var currnode = this.editor.getSelectedNode();
-        var linkel = this.editor.getNearestParentOfType(currnode, 'A');
-        if (!linkel) {
-            return;
-        }
+LinkToolBox.prototype.initialize = function(tool, editor) {
+    /* attach the event handlers */
+    this.tool = tool;
+    this.editor = editor;
+    if (!this.button) return;
+    addEventHandler(this.input, "blur", this.updateLink, this);
+    addEventHandler(this.button, "click", this.addLink, this);
+};
 
-        var url = this.input.value;
-        linkel.setAttribute('href', url);
+LinkToolBox.prototype.updateState = function(selNode) {
+    /* if we're inside a link, update the input, else empty it */
+    var linkel = this.editor.getNearestParentOfType(selNode, 'a');
+    if (linkel) {
+        // check first before setting a class for backward compatibility
+        if (this.toolboxel) {
+            this.toolboxel.className = this.activeclass;
+        };
+        this.input.value = linkel.getAttribute('href');
+    } else {
+        // check first before setting a class for backward compatibility
+        if (this.toolboxel) {
+            this.toolboxel.className = this.plainclass;
+        };
+        this.input.value = '';
+    }
+};
 
-        this.editor.logMessage(_('Link modified'));
-    };
+LinkToolBox.prototype.addLink = function(event) {
+    /* add a link */
+    var url = this.input.value;
+    this.editor.focusDocument();
+    this.tool.createLink(url);
+    this.editor.updateState();
 };
 
-LinkToolBox.prototype = new LinkToolBox;
+LinkToolBox.prototype.updateLink = function() {
+    /* update the current link */
+    var currnode = this.editor.getSelectedNode();
+    var linkel = this.editor.getNearestParentOfType(currnode, 'A');
+    if (!linkel) {
+        return;
+    }
+
+    var url = this.input.value;
+    linkel.setAttribute('href', url);
 
-function ImageTool() {
+    this.editor.updateState();
+};
+
+function ImageTool(popupurl, popupwidth, popupheight, popupprops) {
     /* Image tool to add images */
-    
-    this.initialize = function(editor) {
-        /* attach the event handlers */
-        this.editor = editor;
-        this.editor.logMessage(_('Image tool initialized'));
-    };
-
-    this.createImageHandler = function(event) {
-        /* create an image according to a url entered in a popup */
-        var imageWindow = openPopup('kupupopups/image.html', 300, 200);
-        imageWindow.imagetool = this;
-        imageWindow.focus();
-    };
-
-    this.createImage = function(url, alttext, imgclass) {
-        /* create an image */
-        var img = this.editor.getInnerDocument().createElement('img');
-        img.src = url;
-        img.removeAttribute('height');
-        img.removeAttribute('width');
-        if (alttext) {
-            img.alt = alttext;
-        };
-        if (imgclass) {
-            img.className = imgclass;
-        };
-        img = this.editor.insertNodeAtSelection(img, 1);
-        this.editor.logMessage(_('Image inserted'));
-        this.editor.updateState();
-        return img;
-    };
+    this.popupurl = popupurl || 'kupupopups/image.html';
+    this.popupwidth = popupwidth || 300;
+    this.popupheight = popupheight || 200;
+    this.popupprops = popupprops || '';
+};
 
-    this.setImageClass = function(imgclass) {
-        /* set the class of the selected image */
-        var currnode = this.editor.getSelectedNode();
-        var currimg = this.editor.getNearestParentOfType(currnode, 'IMG');
-        if (currimg) {
-            currimg.className = imgclass;
-        };
+ImageTool.prototype = new KupuTool;
+
+ImageTool.prototype.initialize = function(editor) {
+    /* attach the event handlers */
+    this.editor = editor;
+};
+
+ImageTool.prototype.createImageHandler = function(event) {
+    /* create an image according to a url entered in a popup */
+    var imageWindow = openPopup(this.popupurl, this.popupwidth,
+                                this.popupheight, this.popupprops);
+    imageWindow.imagetool = this;
+    imageWindow.focus();
+};
+
+ImageTool.prototype.newNode = function(name, obj) {
+    var ed = this.editor;
+    var currobj = ed.getNearestParentOfType(ed.getSelectedNode(), name);
+    if (currobj) {
+        var p = currobj.parentNode;
+        p.insertBefore(obj, currobj);
+        p.removeChild(currobj);
+        return obj;
+    } else {
+        return ed.insertNodeAtSelection(obj, 1);
+    }
+};
+
+ImageTool.prototype.createImage = function(url, alttext, imgclass) {
+    /* create an image */
+    var img = this.editor.getInnerDocument().createElement('img');
+    img.src = url;
+    img.setAttribute('kupu-src', url);
+    img.removeAttribute('height');
+    img.removeAttribute('width');
+    if (alttext) {
+        img.alt = alttext;
+    };
+    if (imgclass) {
+        img.className = imgclass;
     };
+    this.newNode('IMG', img);
+    return img;
+};
 
-    this.createContextMenuElements = function(selNode, event) {
-        return new Array(new ContextMenuElement(_('Create image'), this.createImageHandler, this));
+ImageTool.prototype.create_flash = function(url, alttext, className, width, height) {
+    var ed = this.editor;
+    var obj = ed.newElement('object',
+        {src:url, alt:alttext, className:className, width:width,
+         height:height, type:'application/x-shockwave-flash',
+         'data':url},
+        [ed.newElement('param', {name:'movie', value:url})]);
+    this.newNode('OBJECT', obj);
+};
+
+ImageTool.prototype.setImageClass = function(imgclass) {
+    /* set the class of the selected image */
+    var currnode = this.editor.getSelectedNode();
+    var currimg = this.editor.getNearestParentOfType(currnode, 'IMG');
+    if (currimg) {
+        currimg.className = imgclass;
     };
-}
+};
 
-ImageTool.prototype = new KupuTool;
+ImageTool.prototype.createContextMenuElements = function(selNode, event) {
+    return [new ContextMenuElement(_('Create image'), this.createImageHandler,
+                                   this)];
+};
 
-function ImageToolBox(inputfieldid, insertbuttonid, classselectid, toolboxid, plainclass, activeclass) {
+function ImageToolBox(inputfieldid, insertbuttonid, classselectid, toolboxid,
+                      plainclass, activeclass) {
     /* toolbox for adding images */
 
     this.inputfield = getFromSelector(inputfieldid);
@@ -1114,740 +1328,711 @@ function ImageToolBox(inputfieldid, inse
     this.toolboxel = getFromSelector(toolboxid);
     this.plainclass = plainclass;
     this.activeclass = activeclass;
+};
 
-    this.initialize = function(tool, editor) {
-        this.tool = tool;
-        this.editor = editor;
-        addEventHandler(this.classselect, "change", this.setImageClass, this);
-        addEventHandler(this.insertbutton, "click", this.addImage, this);
-    };
+ImageToolBox.prototype = new KupuToolBox;
 
-    this.updateState = function(selNode, event) {
-        /* update the state of the toolbox element */
-        var imageel = this.editor.getNearestParentOfType(selNode, 'img');
-        if (imageel) {
-            // check first before setting a class for backward compatibility
-            if (this.toolboxel) {
-                this.toolboxel.className = this.activeclass;
-                this.inputfield.value = imageel.getAttribute('src');
-                var imgclass = imageel.className ? imageel.className : 'image-inline';
-                selectSelectItem(this.classselect, imgclass);
-            };
-        } else {
-            if (this.toolboxel) {
-                this.toolboxel.className = this.plainclass;
-            };
-        };
-    };
+ImageToolBox.prototype.initialize = function(tool, editor) {
+    this.tool = tool;
+    this.editor = editor;
+    addEventHandler(this.classselect, "change", this.setImageClass, this);
+    addEventHandler(this.insertbutton, "click", this.addImage, this);
+};
 
-    this.addImage = function() {
-        /* add an image */
-        var url = this.inputfield.value;
-        var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
-        this.tool.createImage(url, null, sel_class);
-        this.editor.focusDocument();
+ImageToolBox.prototype.updateState = function(selNode, event) {
+    /* update the state of the toolbox element */
+    var imageel = this.editor.getNearestParentOfType(selNode, 'img');
+    if (imageel) {
+        // check first before setting a class for backward compatibility
+        if (this.toolboxel) {
+            this.toolboxel.className = this.activeclass;
+            this.inputfield.value = imageel.getAttribute('src');
+            var imgclass = imageel.className ? imageel.className : 'image-inline';
+            selectSelectItem(this.classselect, imgclass);
+        };
+    } else {
+        if (this.toolboxel) {
+            this.toolboxel.className = this.plainclass;
+        };
     };
+};
 
-    this.setImageClass = function() {
-        /* set the class for the current image */
-        var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
-        this.tool.setImageClass(sel_class);
-        this.editor.focusDocument();
-    };
+ImageToolBox.prototype.addImage = function() {
+    /* add an image */
+    var url = this.inputfield.value;
+    var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
+    this.editor.focusDocument();
+    this.tool.createImage(url, null, sel_class);
+    this.editor.updateState();
 };
 
-ImageToolBox.prototype = new KupuToolBox;
+ImageToolBox.prototype.setImageClass = function() {
+    /* set the class for the current image */
+    var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
+    this.editor.focusDocument();
+    this.tool.setImageClass(sel_class);
+    this.editor.updateState();
+};
 
 function TableTool() {
     /* The table tool */
+};
 
-    // XXX There are some awfully long methods in here!!
-    this.createContextMenuElements = function(selNode, event) {
-        var table =  this.editor.getNearestParentOfType(selNode, 'table');
-        if (!table) {
-            ret = new Array();
-            var el = new ContextMenuElement(_('Add table'), this.addPlainTable, this);
-            ret.push(el);
-            return ret;
-        } else {
-            var ret = new Array();
-            ret.push(new ContextMenuElement(_('Add row'), this.addTableRow, this));
-            ret.push(new ContextMenuElement(_('Delete row'), this.delTableRow, this));
-            ret.push(new ContextMenuElement(_('Add column'), this.addTableColumn, this));
-            ret.push(new ContextMenuElement(_('Delete column'), this.delTableColumn, this));
-            ret.push(new ContextMenuElement(_('Delete Table'), this.delTable, this));
-            return ret;
-        };
-    };
+TableTool.prototype = new KupuTool;
 
-    this.addPlainTable = function() {
-        /* event handler for the context menu */
-        this.createTable(2, 3, 1, 'plain');
+// XXX There are some awfully long methods in here!!
+TableTool.prototype.createContextMenuElements = function(selNode, event) {
+    var table =  this.editor.getNearestParentOfType(selNode, 'table');
+    if (!table) {
+        var ret = [];
+        var el = new ContextMenuElement(_('Add table'), this.addPlainTable, this);
+        ret.push(el);
+        return ret;
+    } else {
+        var ret = [];
+        ret.push(new ContextMenuElement(_('Add row'), this.addTableRow, this));
+        ret.push(new ContextMenuElement(_('Delete row'), this.delTableRow, this));
+        ret.push(new ContextMenuElement(_('Add column'), this.addTableColumn, this));
+        ret.push(new ContextMenuElement(_('Delete column'), this.delTableColumn, this));
+        ret.push(new ContextMenuElement(_('Delete Table'), this.delTable, this));
+        return ret;
     };
+};
 
-    this.createTable = function(rows, cols, makeHeader, tableclass) {
-        /* add a table */
-        if (rows < 1 || rows > 99 || cols < 1 || cols > 99) {
-            this.editor.logMessage(_('Invalid table size'), 1);
-            return;
-        };
-
-        var doc = this.editor.getInnerDocument();
-
-        table = doc.createElement("table");
-        table.className = tableclass;
+TableTool.prototype.addPlainTable = function() {
+    /* event handler for the context menu */
+    this.createTable(2, 3, 1, 'plain');
+};
 
-        // If the user wants a row of headings, make them
-        if (makeHeader) {
-            var tr = doc.createElement("tr");
-            var thead = doc.createElement("thead");
-            for (i=0; i < cols; i++) {
-                var th = doc.createElement("th");
-                th.appendChild(doc.createTextNode("Col " + i+1));
-                tr.appendChild(th);
-            }
-            thead.appendChild(tr);
-            table.appendChild(thead);
-        }
-
-        tbody = doc.createElement("tbody");
-        for (var i=0; i < rows; i++) {
-            var tr = doc.createElement("tr");
-            for (var j=0; j < cols; j++) {
-                var td = doc.createElement("td");
-                var content = doc.createTextNode('\u00a0');
-                td.appendChild(content);
-                tr.appendChild(td);
-            }
-            tbody.appendChild(tr);
+TableTool.prototype.createTable = function(rows, cols, makeHeader, tableclass) {
+    /* add a table */
+    if (rows < 1 || rows > 99 || cols < 1 || cols > 99) {
+        this.editor.logMessage(_('Invalid table size'), 1);
+        return;
+    };
+
+    var doc = this.editor.getInnerDocument();
+    var table = doc.createElement("table");
+    table.className = tableclass;
+
+    // If the user wants a row of headings, make them
+    if (makeHeader) {
+        var tr = doc.createElement("tr");
+        var thead = doc.createElement("thead");
+        for (var i=1; i <= cols; i++) {
+            var th = doc.createElement("th");
+            th.appendChild(doc.createTextNode("Col " + i));
+            tr.appendChild(th);
+        }
+        thead.appendChild(tr);
+        table.appendChild(thead);
+    }
+
+    var tbody = doc.createElement("tbody");
+    for (var i=0; i < rows; i++) {
+        var tr = doc.createElement("tr");
+        for (var j=0; j < cols; j++) {
+            var td = doc.createElement("td");
+            var content = doc.createTextNode('\u00a0');
+            td.appendChild(content);
+            tr.appendChild(td);
         }
-        table.appendChild(tbody);
-        this.editor.insertNodeAtSelection(table);
-
-        this._setTableCellHandlers(table);
-
-        this.editor.logMessage(_('Table added'));
-        this.editor.updateState();
-        return table;
-    };
+        tbody.appendChild(tr);
+    }
+    table.appendChild(tbody);
+    this.editor.insertNodeAtSelection(table);
 
-    this._setTableCellHandlers = function(table) {
-        // make each cell select its full contents if it's clicked
-        addEventHandler(table, 'click', this._selectContentIfEmpty, this);
+    this._setTableCellHandlers(table);
+    return table;
+};
 
-        var cells = table.getElementsByTagName('td');
-        for (var i=0; i < cells.length; i++) {
-            addEventHandler(cells[i], 'click', this._selectContentIfEmpty, this);
-        };
-        
-        // select the nbsp in the first cell
-        var firstcell = cells[0];
-        if (firstcell) {
-            var children = firstcell.childNodes;
-            if (children.length == 1 && children[0].nodeType == 3 && 
-                    children[0].nodeValue == '\xa0') {
-                var selection = this.editor.getSelection();
-                selection.selectNodeContents(firstcell);
-            };
-        };
+TableTool.prototype._setTableCellHandlers = function(table) {
+    // make each cell select its full contents if it's clicked
+    addEventHandler(table, 'click', this._selectContentIfEmpty, this);
+
+    var cells = table.getElementsByTagName('td');
+    for (var i=0; i < cells.length; i++) {
+        addEventHandler(cells[i], 'click', this._selectContentIfEmpty, this);
     };
     
-    this._selectContentIfEmpty = function() {
-        var selNode = this.editor.getSelectedNode();
-        var cell = this.editor.getNearestParentOfType(selNode, 'td');
-        if (!cell) {
-            return;
-        };
-        var children = cell.childNodes;
+    // select the nbsp in the first cell
+    var firstcell = cells[0];
+    if (firstcell) {
+        var children = firstcell.childNodes;
         if (children.length == 1 && children[0].nodeType == 3 && 
                 children[0].nodeValue == '\xa0') {
             var selection = this.editor.getSelection();
-            selection.selectNodeContents(cell);
+            selection.selectNodeContents(firstcell);
         };
     };
+};
 
-    this.addTableRow = function() {
-        /* Find the current row and add a row after it */
-        var currnode = this.editor.getSelectedNode();
-        var currtbody = this.editor.getNearestParentOfType(currnode, "TBODY");
-        var bodytype = "tbody";
-        if (!currtbody) {
-            currtbody = this.editor.getNearestParentOfType(currnode, "THEAD");
-            bodytype = "thead";
-        }
-        var parentrow = this.editor.getNearestParentOfType(currnode, "TR");
-        var nextrow = parentrow.nextSibling;
-
-        // get the number of cells we should place
-        var colcount = 0;
-        for (var i=0; i < currtbody.childNodes.length; i++) {
-            var el = currtbody.childNodes[i];
-            if (el.nodeType != 1) {
-                continue;
-            }
-            if (el.nodeName.toLowerCase() == 'tr') {
-                var cols = 0;
-                for (var j=0; j < el.childNodes.length; j++) {
-                    if (el.childNodes[j].nodeType == 1) {
-                        cols++;
-                    }
-                }
-                if (cols > colcount) {
-                    colcount = cols;
+TableTool.prototype._selectContentIfEmpty = function() {
+    var selNode = this.editor.getSelectedNode();
+    var cell = this.editor.getNearestParentOfType(selNode, 'td');
+    if (!cell) {
+        return;
+    };
+    var children = cell.childNodes;
+    if (children.length == 1 && children[0].nodeType == 3 && 
+            children[0].nodeValue == '\xa0') {
+        var selection = this.editor.getSelection();
+        selection.selectNodeContents(cell);
+    };
+};
+
+TableTool.prototype.addTableRow = function() {
+    /* Find the current row and add a row after it */
+    var currnode = this.editor.getSelectedNode();
+    var currtbody = this.editor.getNearestParentOfType(currnode, "TBODY");
+    var bodytype = "tbody";
+    if (!currtbody) {
+        currtbody = this.editor.getNearestParentOfType(currnode, "THEAD");
+        bodytype = "thead";
+    }
+    var parentrow = this.editor.getNearestParentOfType(currnode, "TR");
+    var nextrow = parentrow.nextSibling;
+
+    // get the number of cells we should place
+    var colcount = 0;
+    for (var i=0; i < currtbody.childNodes.length; i++) {
+        var el = currtbody.childNodes[i];
+        if (el.nodeType != 1) {
+            continue;
+        }
+        if (el.nodeName.toLowerCase() == 'tr') {
+            var cols = 0;
+            for (var j=0; j < el.childNodes.length; j++) {
+                if (el.childNodes[j].nodeType == 1) {
+                    cols++;
                 }
             }
-        }
-
-        var newrow = this.editor.getInnerDocument().createElement("TR");
-
-        for (var i = 0; i < colcount; i++) {
-            var newcell;
-            if (bodytype == 'tbody') {
-                newcell = this.editor.getInnerDocument().createElement("TD");
-            } else {
-                newcell = this.editor.getInnerDocument().createElement("TH");
+            if (cols > colcount) {
+                colcount = cols;
             }
-            var newcellvalue = this.editor.getInnerDocument().createTextNode("\u00a0");
-            newcell.appendChild(newcellvalue);
-            newrow.appendChild(newcell);
         }
+    }
 
-        if (!nextrow) {
-            currtbody.appendChild(newrow);
-        } else {
-            currtbody.insertBefore(newrow, nextrow);
-        }
-        
-        this.editor.focusDocument();
-        this.editor.logMessage(_('Table row added'));
-    };
+    var newrow = this.editor.getInnerDocument().createElement("TR");
 
-    this.delTableRow = function() {
-        /* Find the current row and delete it */
-        var currnode = this.editor.getSelectedNode();
-        var parentrow = this.editor.getNearestParentOfType(currnode, "TR");
-        if (!parentrow) {
-            this.editor.logMessage(_('No row to delete'), 1);
-            return;
+    for (var i = 0; i < colcount; i++) {
+        var newcell;
+        if (bodytype == 'tbody') {
+            newcell = this.editor.getInnerDocument().createElement("TD");
+        } else {
+            newcell = this.editor.getInnerDocument().createElement("TH");
         }
+        var newcellvalue = this.editor.getInnerDocument().createTextNode("\u00a0");
+        newcell.appendChild(newcellvalue);

[... 2308 lines stripped ...]