You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by sn...@apache.org on 2006/10/08 21:54:16 UTC

svn commit: r454197 [3/29] - in /incubator/roller/trunk/web: WEB-INF/classes/ roller-ui/authoring/editors/ roller-ui/authoring/editors/xinha/ roller-ui/authoring/editors/xinha/conf/ roller-ui/authoring/editors/xinha/contrib/ roller-ui/authoring/editors...

Added: incubator/roller/trunk/web/roller-ui/authoring/editors/xinha/htmlarea.js
URL: http://svn.apache.org/viewvc/incubator/roller/trunk/web/roller-ui/authoring/editors/xinha/htmlarea.js?view=auto&rev=454197
==============================================================================
--- incubator/roller/trunk/web/roller-ui/authoring/editors/xinha/htmlarea.js (added)
+++ incubator/roller/trunk/web/roller-ui/authoring/editors/xinha/htmlarea.js Sun Oct  8 12:53:13 2006
@@ -0,0 +1,6792 @@
+
+  /*--------------------------------------:noTabs=true:tabSize=2:indentSize=2:--
+    --  Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
+    --
+    --  Use of Xinha is granted by the terms of the htmlArea License (based on
+    --  BSD license)  please read license.txt in this package for details.
+    --
+    --  Xinha was originally based on work by Mihai Bazon which is:
+    --      Copyright (c) 2003-2004 dynarch.com.
+    --      Copyright (c) 2002-2003 interactivetools.com, inc.
+    --      This copyright notice MUST stay intact for use.
+    --
+    --  Developers - Coding Style:
+    --   For the sake of not committing needlessly conflicting changes,
+    --
+    --   * New code to be indented with 2 spaces ("soft tab").
+    --   * New code preferably uses BSD-Style Bracing
+    --      if ( foo )
+    --      {
+    --        bar();
+    --      }
+    --   * Don't change brace styles unless you're working on the non BSD-Style
+    --     area (so we don't get spurious changes in line numbering).
+    --   * Don't change indentation unless you're working on the badly indented
+    --     area (so we don't get spurious changes of large blocks of code).
+    --   * Jedit is the recommended editor, a comment of this format should be
+    --     included in the top 10 lines of the file (see the embedded edit mode)
+    --
+    --  $HeadURL: http://svn.xinha.python-hosting.com/trunk/htmlarea.js $
+    --  $LastChangedDate: 2006-10-05 02:14:50 +1300 (Thu, 05 Oct 2006) $
+    --  $LastChangedRevision: 595 $
+    --  $LastChangedBy: ray $
+    --------------------------------------------------------------------------*/
+
+HTMLArea.version =
+{
+  'Release'   : 'Trunk',
+  'Head'      : '$HeadURL: http://svn.xinha.python-hosting.com/trunk/htmlarea.js $'.replace(/^[^:]*: (.*) \$$/, '$1'),
+  'Date'      : '$LastChangedDate: 2006-10-05 02:14:50 +1300 (Thu, 05 Oct 2006) $'.replace(/^[^:]*: ([0-9-]*) ([0-9:]*) ([+0-9]*) \((.*)\) \$/, '$4 $2 $3'),
+  'Revision'  : '$LastChangedRevision: 595 $'.replace(/^[^:]*: (.*) \$$/, '$1'),
+  'RevisionBy': '$LastChangedBy: ray $'.replace(/^[^:]*: (.*) \$$/, '$1')
+};
+
+if ( typeof _editor_url == "string" )
+{
+  // Leave exactly one backslash at the end of _editor_url
+  _editor_url = _editor_url.replace(/\x2f*$/, '/');
+}
+else
+{
+  alert("WARNING: _editor_url is not set!  You should set this variable to the editor files path; it should preferably be an absolute path, like in '/htmlarea/', but it can be relative if you prefer.  Further we will try to load the editor files correctly but we'll probably fail.");
+  _editor_url = '';
+}
+
+// make sure we have a language
+if ( typeof _editor_lang == "string" )
+{
+  _editor_lang = _editor_lang.toLowerCase();
+}
+else
+{
+  _editor_lang = "en";
+}
+
+// skin stylesheet to load
+if ( typeof _editor_skin !== "string" )
+{
+  _editor_skin = "";
+}
+
+var __htmlareas = [];
+
+// browser identification
+HTMLArea.agt       = navigator.userAgent.toLowerCase();
+HTMLArea.is_ie	   = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
+HTMLArea.is_opera  = (HTMLArea.agt.indexOf("opera") != -1);
+HTMLArea.is_mac	   = (HTMLArea.agt.indexOf("mac") != -1);
+HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
+HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
+HTMLArea.is_gecko  = (navigator.product == "Gecko");
+HTMLArea.isRunLocally = document.URL.toLowerCase().search(/^file:/) != -1;
+if ( HTMLArea.isRunLocally )
+{
+  alert('Xinha *must* be installed on a web server. Locally opened files (those that use the "file://" protocol) cannot properly function. Xinha will try to initialize but may not be correctly loaded.');
+}
+
+// Creates a new HTMLArea object.  Tries to replace the textarea with the given
+// ID with it.
+function HTMLArea(textarea, config)
+{
+  if ( !textarea )
+  {
+    throw("Tried to create HTMLArea without textarea specified.");
+  }
+
+  if ( HTMLArea.checkSupportedBrowser() )
+  {
+    if ( typeof config == "undefined" )
+    {
+      this.config = new HTMLArea.Config();
+    }
+    else
+    {
+      this.config = config;
+    }
+    this._htmlArea = null;
+
+    if ( typeof textarea != 'object' )
+    {
+      textarea = HTMLArea.getElementById('textarea', textarea);
+    }
+    this._textArea = textarea;
+       
+    // Before we modify anything, get the initial textarea size
+    this._initial_ta_size =
+    {
+      w: textarea.style.width  ? textarea.style.width  : ( textarea.offsetWidth  ? ( textarea.offsetWidth  + 'px' ) : ( textarea.cols + 'em') ),
+      h: textarea.style.height ? textarea.style.height : ( textarea.offsetHeight ? ( textarea.offsetHeight + 'px' ) : ( textarea.rows + 'em') )
+    };
+    // Create the loading message elements
+    if ( this.config.showLoading )
+    {
+      // Create and show the main loading message and the sub loading message for details of loading actions
+      // global element
+      var loading_message = document.createElement("div");
+      loading_message.id = "loading_" + textarea.name;
+      loading_message.className = "loading";
+      try
+      {
+        // how can i find the real width in pixels without % or em *and* with no visual errors ?
+        // for instance, a textarea with a style="width:100%" and the body padding > 0 result in a horizontal scrollingbar while loading
+        // A few lines above seems to indicate offsetWidth is not always set
+        loading_message.style.width = textarea.offsetWidth + 'px';
+      }
+      catch (ex)
+      {
+        // offsetWidth seems not set, so let's use this._initial_ta_size.w, but sometimes it may be too huge width
+        loading_message.style.width = this._initial_ta_size.w;
+      }
+      loading_message.style.left = HTMLArea.findPosX(textarea) +  'px';
+      loading_message.style.top = (HTMLArea.findPosY(textarea) + parseInt(this._initial_ta_size.h, 10) / 2) +  'px';
+      // main static message
+      var loading_main = document.createElement("div");
+      loading_main.className = "loading_main";
+      loading_main.id = "loading_main_" + textarea.name;
+      loading_main.appendChild(document.createTextNode(HTMLArea._lc("Loading in progress. Please wait !")));
+      // sub dynamic message
+      var loading_sub = document.createElement("div");
+      loading_sub.className = "loading_sub";
+      loading_sub.id = "loading_sub_" + textarea.name;
+      loading_sub.appendChild(document.createTextNode(HTMLArea._lc("Constructing main object")));
+      loading_message.appendChild(loading_main);
+      loading_message.appendChild(loading_sub);
+      document.body.appendChild(loading_message);
+      this.setLoadingMessage("Constructing object");
+    }
+
+    this._editMode = "wysiwyg";
+    this.plugins = {};
+    this._timerToolbar = null;
+    this._timerUndo = null;
+    this._undoQueue = [this.config.undoSteps];
+    this._undoPos = -1;
+    this._customUndo = true;
+    this._mdoc = document; // cache the document, we need it in plugins
+    this.doctype = '';
+    this.__htmlarea_id_num = __htmlareas.length;
+    __htmlareas[this.__htmlarea_id_num] = this;
+
+    this._notifyListeners = {};
+
+    // Panels
+    var panels = 
+    {
+      right:
+      {
+        on: true,
+        container: document.createElement('td'),
+        panels: []
+      },
+      left:
+      {
+        on: true,
+        container: document.createElement('td'),
+        panels: []
+      },
+      top:
+      {
+        on: true,
+        container: document.createElement('td'),
+        panels: []
+      },
+      bottom:
+      {
+        on: true,
+        container: document.createElement('td'),
+        panels: []
+      }
+    };
+
+    for ( var i in panels )
+    {
+      if(!panels[i].container) { continue; } // prevent iterating over wrong type
+      panels[i].div = panels[i].container; // legacy
+      panels[i].container.className = 'panels ' + i;
+      HTMLArea.freeLater(panels[i], 'container');
+      HTMLArea.freeLater(panels[i], 'div');
+    }
+    // finally store the variable
+    this._panels = panels;
+
+    HTMLArea.freeLater(this, '_textArea');
+  }
+}
+
+HTMLArea.onload = function() { };
+HTMLArea.init = function() { HTMLArea.onload(); };
+
+// cache some regexps
+HTMLArea.RE_tagName  = /(<\/|<)\s*([^ \t\n>]+)/ig;
+HTMLArea.RE_doctype  = /(<!doctype((.|\n)*?)>)\n?/i;
+HTMLArea.RE_head     = /<head>((.|\n)*?)<\/head>/i;
+HTMLArea.RE_body     = /<body[^>]*>((.|\n|\r|\t)*?)<\/body>/i;
+HTMLArea.RE_Specials = /([\/\^$*+?.()|{}[\]])/g;
+HTMLArea.RE_email    = /[_a-zA-Z\d\-\.]{3,}@[_a-zA-Z\d\-]{2,}(\.[_a-zA-Z\d\-]{2,})+/i;
+HTMLArea.RE_url      = /(https?:\/\/)?(([a-z0-9_]+:[a-z0-9_]+@)?[a-z0-9_-]{2,}(\.[a-z0-9_-]{2,}){2,}(:[0-9]+)?(\/\S+)*)/i;
+
+HTMLArea.Config = function()
+{
+  var cfg = this;
+  this.version = HTMLArea.version.Revision;
+
+  // Width and Height
+  //  you may set these as follows
+  //  width = 'auto'      -- the width of the original textarea will be used
+  //  width = 'toolbar'   -- the width of the toolbar will be used
+  //  width = '<css measure>' -- use any css measurement, eg width = '75%'
+  //
+  //  height = 'auto'     -- the height of the original textarea
+  //  height = '<css measure>' -- any css measurement, eg height = '480px'
+  this.width  = "auto";
+  this.height = "auto";
+
+  // the next parameter specifies whether the toolbar should be included
+  // in the size above, or are extra to it.  If false then it's recommended
+  // to have explicit pixel sizes above (or on your textarea and have auto above)
+  this.sizeIncludesBars = true;
+
+  // the next parameter specifies whether the panels should be included
+  // in the size above, or are extra to it.  If false then it's recommended
+  // to have explicit pixel sizes above (or on your textarea and have auto above)
+  this.sizeIncludesPanels = true;
+
+  // each of the panels has a dimension, for the left/right it's the width
+  // for the top/bottom it's the height.
+  //
+  // WARNING: PANEL DIMENSIONS MUST BE SPECIFIED AS PIXEL WIDTHS
+  this.panel_dimensions =
+  {
+    left:   '200px', // Width
+    right:  '200px',
+    top:    '100px', // Height
+    bottom: '100px'
+  };
+
+  // enable creation of a status bar?
+  this.statusBar = true;
+
+  // intercept ^V and use the HTMLArea paste command
+  // If false, then passes ^V through to browser editor widget
+  this.htmlareaPaste = false;
+
+  this.mozParaHandler = 'best'; // set to 'built-in', 'dirty' or 'best'
+                                // built-in: will (may) use 'br' instead of 'p' tags
+                                // dirty   : will use p and work good enough for the majority of cases,
+                                // best    : works the best, but it's about 12kb worth of javascript
+                                //   and will probably be slower than 'dirty'.  This is the "EnterParagraphs"
+                                //   plugin from "hipikat", rolled in to be part of the core code
+
+  // maximum size of the undo queue
+  this.undoSteps = 20;
+
+  // the time interval at which undo samples are taken
+  this.undoTimeout = 500;	// 1/2 sec.
+
+  // set this to true if you want to explicitly right-justify when 
+  // setting the text direction to right-to-left
+  this.changeJustifyWithDirection = false;
+
+  // if true then HTMLArea will retrieve the full HTML, starting with the
+  // <HTML> tag.
+  this.fullPage = false;
+
+  // style included in the iframe document
+  this.pageStyle = "";
+
+  // external stylesheets to load (REFERENCE THESE ABSOLUTELY)
+  this.pageStyleSheets = [];
+
+  // specify a base href for relative links
+  this.baseHref = null;
+
+  // we can strip the base href out of relative links to leave them relative, reason for this
+  //   especially if you don't specify a baseHref is that mozilla at least (& IE ?) will prefix
+  //   the baseHref to any relative links to make them absolute, which isn't what you want most the time.
+  this.stripBaseHref = true;
+
+  // and we can strip the url of the editor page from named links (eg <a href="#top">...</a>)
+  //  reason for this is that mozilla at least (and IE ?) prefixes location.href to any
+  //  that don't have a url prefixing them
+  this.stripSelfNamedAnchors = true;
+
+  // sometimes high-ascii in links can cause problems for servers (basically they don't recognise them)
+  //  so you can use this flag to ensure that all characters other than the normal ascii set (actually
+  //  only ! through ~) are escaped in URLs to % codes
+  this.only7BitPrintablesInURLs = true;
+
+  // if you are putting the HTML written in Xinha into an email you might want it to be 7-bit
+  //  characters only.  This config option (off by default) will convert all characters consuming
+  //  more than 7bits into UNICODE decimal entity references (actually it will convert anything
+  //  below <space> (chr 20) except cr, lf and tab and above <tilde> (~, chr 7E))
+  this.sevenBitClean = false;
+
+  // sometimes we want to be able to replace some string in the html comng in and going out
+  //  so that in the editor we use the "internal" string, and outside and in the source view
+  //  we use the "external" string  this is useful for say making special codes for
+  //  your absolute links, your external string might be some special code, say "{server_url}"
+  //  an you say that the internal represenattion of that should be http://your.server/
+  this.specialReplacements = {}; // { 'external_string' : 'internal_string' }
+
+  // set to true if you want Word code to be cleaned upon Paste
+  this.killWordOnPaste = true;
+
+  // enable the 'Target' field in the Make Link dialog
+  this.makeLinkShowsTarget = true;
+
+  // CharSet of the iframe, default is the charset of the document
+  this.charSet = HTMLArea.is_gecko ? document.characterSet : document.charset;
+
+  // URL-s
+  this.imgURL = "images/";
+  this.popupURL = "popups/";
+
+  // remove tags (these have to be a regexp, or null if this functionality is not desired)
+  this.htmlRemoveTags = null;
+
+  // Turning this on will turn all "linebreak" and "separator" items in your toolbar into soft-breaks,
+  // this means that if the items between that item and the next linebreak/separator can
+  // fit on the same line as that which came before then they will, otherwise they will
+  // float down to the next line.
+
+  // If you put a linebreak and separator next to each other, only the separator will
+  // take effect, this allows you to have one toolbar that works for both flowToolbars = true and false
+  // infact the toolbar below has been designed in this way, if flowToolbars is false then it will
+  // create explictly two lines (plus any others made by plugins) breaking at justifyleft, however if
+  // flowToolbars is false and your window is narrow enough then it will create more than one line
+  // even neater, if you resize the window the toolbars will reflow.  Niiiice.
+
+  this.flowToolbars = true;
+  
+  // set to true if you want the loading panel to show at startup
+  this.showLoading = false;
+
+  // size of color picker cells
+  this.colorPickerCellSize = '6px';
+  // granularity of color picker cells (number per column/row)
+  this.colorPickerGranularity = 18;
+  // position of color picker from toolbar button
+  this.colorPickerPosition = 'bottom,right';
+
+  /** CUSTOMIZING THE TOOLBAR
+   * -------------------------
+   *
+   * It is recommended that you customize the toolbar contents in an
+   * external file (i.e. the one calling HTMLArea) and leave this one
+   * unchanged.  That's because when we (InteractiveTools.com) release a
+   * new official version, it's less likely that you will have problems
+   * upgrading HTMLArea.
+   */
+  this.toolbar =
+  [
+    ["popupeditor"],
+    ["separator","formatblock","fontname","fontsize","bold","italic","underline","strikethrough"],
+    ["separator","forecolor","hilitecolor","textindicator"],
+    ["separator","subscript","superscript"],
+    ["linebreak","separator","justifyleft","justifycenter","justifyright","justifyfull"],
+    ["separator","insertorderedlist","insertunorderedlist","outdent","indent"],
+    ["separator","inserthorizontalrule","createlink","insertimage","inserttable"],
+    ["linebreak","separator","undo","redo","selectall","print"], (HTMLArea.is_gecko ? [] : ["cut","copy","paste","overwrite","saveas"]),
+    ["separator","killword","clearfonts","removeformat","toggleborders","splitblock","lefttoright", "righttoleft"],
+    ["separator","htmlmode","showhelp","about"]
+  ];
+
+
+  this.fontname =
+  {
+    "&mdash; font &mdash;": '',
+    "Arial":	         'arial,helvetica,sans-serif',
+    "Courier New":	   'courier new,courier,monospace',
+    "Georgia":	       'georgia,times new roman,times,serif',
+    "Tahoma":	         'tahoma,arial,helvetica,sans-serif',
+    "Times New Roman": 'times new roman,times,serif',
+    "Verdana":	       'verdana,arial,helvetica,sans-serif',
+    "impact":	         'impact',
+    "WingDings":	     'wingdings'
+  };
+
+  this.fontsize =
+  {
+    "&mdash; size &mdash;": "",
+    "1 (8 pt)" : "1",
+    "2 (10 pt)": "2",
+    "3 (12 pt)": "3",
+    "4 (14 pt)": "4",
+    "5 (18 pt)": "5",
+    "6 (24 pt)": "6",
+    "7 (36 pt)": "7"
+  };
+
+  this.formatblock =
+  {
+    "&mdash; format &mdash;": "",
+    "Heading 1": "h1",
+    "Heading 2": "h2",
+    "Heading 3": "h3",
+    "Heading 4": "h4",
+    "Heading 5": "h5",
+    "Heading 6": "h6",
+    "Normal"   : "p",
+    "Address"  : "address",
+    "Formatted": "pre"
+  };
+
+  this.customSelects = {};
+
+  function cut_copy_paste(e, cmd, obj) { e.execCommand(cmd); }
+
+  this.debug = true;
+
+  this.URIs =
+  {
+   "blank": "popups/blank.html",
+   "link": "link.html",
+   "insert_image": "insert_image.html",
+   "insert_table": "insert_table.html",
+   "select_color": "select_color.html",
+   "about": "about.html",
+   "help": "editor_help.html"
+  };
+
+
+  // ADDING CUSTOM BUTTONS: please read below!
+  // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]"
+  //    - ID: unique ID for the button.  If the button calls document.execCommand
+  //	    it's wise to give it the same name as the called command.
+  //    - ACTION: function that gets called when the button is clicked.
+  //              it has the following prototype:
+  //                 function(editor, buttonName)
+  //              - editor is the HTMLArea object that triggered the call
+  //              - buttonName is the ID of the clicked button
+  //              These 2 parameters makes it possible for you to use the same
+  //              handler for more HTMLArea objects or for more different buttons.
+  //    - ToolTip: tooltip, will be translated below
+  //    - Icon: path to an icon image file for the button
+  //            OR; you can use an 18x18 block of a larger image by supllying an array
+  //            that has three elemtents, the first is the larger image, the second is the column
+  //            the third is the row.  The ros and columns numbering starts at 0 but there is
+  //            a header row and header column which have numbering to make life easier.
+  //            See images/buttons_main.gif to see how it's done.
+  //    - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time.
+  this.btnList =
+  {
+    bold: [ "Bold", HTMLArea._lc({key: 'button_bold', string: ["ed_buttons_main.gif",3,2]}, 'HTMLArea'), false, function(e) { e.execCommand("bold"); } ],
+    italic: [ "Italic", HTMLArea._lc({key: 'button_italic', string: ["ed_buttons_main.gif",2,2]}, 'HTMLArea'), false, function(e) { e.execCommand("italic"); } ],
+    underline: [ "Underline", HTMLArea._lc({key: 'button_underline', string: ["ed_buttons_main.gif",2,0]}, 'HTMLArea'), false, function(e) { e.execCommand("underline"); } ],
+    strikethrough: [ "Strikethrough", HTMLArea._lc({key: 'button_strikethrough', string: ["ed_buttons_main.gif",3,0]}, 'HTMLArea'), false, function(e) { e.execCommand("strikethrough"); } ],
+    subscript: [ "Subscript", HTMLArea._lc({key: 'button_subscript', string: ["ed_buttons_main.gif",3,1]}, 'HTMLArea'), false, function(e) { e.execCommand("subscript"); } ],
+    superscript: [ "Superscript", HTMLArea._lc({key: 'button_superscript', string: ["ed_buttons_main.gif",2,1]}, 'HTMLArea'), false, function(e) { e.execCommand("superscript"); } ],
+
+    justifyleft: [ "Justify Left", ["ed_buttons_main.gif",0,0], false, function(e) { e.execCommand("justifyleft"); } ],
+    justifycenter: [ "Justify Center", ["ed_buttons_main.gif",1,1], false, function(e){ e.execCommand("justifycenter"); } ],
+    justifyright: [ "Justify Right", ["ed_buttons_main.gif",1,0], false, function(e) { e.execCommand("justifyright"); } ],
+    justifyfull: [ "Justify Full", ["ed_buttons_main.gif",0,1], false, function(e) { e.execCommand("justifyfull"); } ],
+
+    orderedlist: [ "Ordered List", ["ed_buttons_main.gif",0,3], false, function(e) { e.execCommand("insertorderedlist"); } ],
+    unorderedlist: [ "Bulleted List", ["ed_buttons_main.gif",1,3], false, function(e) { e.execCommand("insertunorderedlist"); } ],
+    insertorderedlist: [ "Ordered List", ["ed_buttons_main.gif",0,3], false, function(e) { e.execCommand("insertorderedlist"); } ],
+    insertunorderedlist: [ "Bulleted List", ["ed_buttons_main.gif",1,3], false, function(e) { e.execCommand("insertunorderedlist"); } ],
+
+    outdent: [ "Decrease Indent", ["ed_buttons_main.gif",1,2], false, function(e) { e.execCommand("outdent"); } ],
+    indent: [ "Increase Indent",["ed_buttons_main.gif",0,2], false, function(e) { e.execCommand("indent"); } ],
+    forecolor: [ "Font Color", ["ed_buttons_main.gif",3,3], false, function(e) { e.execCommand("forecolor"); } ],
+    hilitecolor: [ "Background Color", ["ed_buttons_main.gif",2,3], false, function(e) { e.execCommand("hilitecolor"); } ],
+
+    undo: [ "Undoes your last action", ["ed_buttons_main.gif",4,2], false, function(e) { e.execCommand("undo"); } ],
+    redo: [ "Redoes your last action", ["ed_buttons_main.gif",5,2], false, function(e) { e.execCommand("redo"); } ],
+    cut: [ "Cut selection", ["ed_buttons_main.gif",5,0], false, cut_copy_paste ],
+    copy: [ "Copy selection", ["ed_buttons_main.gif",4,0], false, cut_copy_paste ],
+    paste: [ "Paste from clipboard", ["ed_buttons_main.gif",4,1], false, cut_copy_paste ],
+    selectall: [ "Select all", "ed_selectall.gif", false, function(e) {e.execCommand("selectall");} ],
+
+    inserthorizontalrule: [ "Horizontal Rule", ["ed_buttons_main.gif",6,0], false, function(e) { e.execCommand("inserthorizontalrule"); } ],
+    createlink: [ "Insert Web Link", ["ed_buttons_main.gif",6,1], false, function(e) { e._createLink(); } ],
+    insertimage: [ "Insert/Modify Image", ["ed_buttons_main.gif",6,3], false, function(e) { e.execCommand("insertimage"); } ],
+    inserttable: [ "Insert Table", ["ed_buttons_main.gif",6,2], false, function(e) { e.execCommand("inserttable"); } ],
+
+    htmlmode: [ "Toggle HTML Source", ["ed_buttons_main.gif",7,0], true, function(e) { e.execCommand("htmlmode"); } ],
+    toggleborders: [ "Toggle Borders", ["ed_buttons_main.gif",7,2], false, function(e) { e._toggleBorders(); } ],
+    print: [ "Print document", ["ed_buttons_main.gif",8,1], false, function(e) { if(HTMLArea.is_gecko) {e._iframe.contentWindow.print(); } else { e.focusEditor(); print(); } } ],
+    saveas: [ "Save as", "ed_saveas.gif", false, function(e) { e.execCommand("saveas",false,"noname.htm"); } ],
+    about: [ "About this editor", ["ed_buttons_main.gif",8,2], true, function(e) { e.execCommand("about"); } ],
+    showhelp: [ "Help using editor", ["ed_buttons_main.gif",9,2], true, function(e) { e.execCommand("showhelp"); } ],
+
+    splitblock: [ "Split Block", "ed_splitblock.gif", false, function(e) { e._splitBlock(); } ],
+    lefttoright: [ "Direction left to right", ["ed_buttons_main.gif",0,4], false, function(e) { e.execCommand("lefttoright"); } ],
+    righttoleft: [ "Direction right to left", ["ed_buttons_main.gif",1,4], false, function(e) { e.execCommand("righttoleft"); } ],
+    overwrite: [ "Insert/Overwrite", "ed_overwrite.gif", false, function(e) { e.execCommand("overwrite"); } ],
+
+    wordclean: [ "MS Word Cleaner", ["ed_buttons_main.gif",5,3], false, function(e) { e._wordClean(); } ],
+    clearfonts: [ "Clear Inline Font Specifications", ["ed_buttons_main.gif",5,4], true, function(e) { e._clearFonts(); } ],
+    removeformat: [ "Remove formatting", ["ed_buttons_main.gif",4,4], false, function(e) { e.execCommand("removeformat"); } ],
+    killword: [ "Clear MSOffice tags", ["ed_buttons_main.gif",4,3], false, function(e) { e.execCommand("killword"); } ]
+  };
+
+  /* ADDING CUSTOM BUTTONS
+   * ---------------------
+   *
+   * It is recommended that you add the custom buttons in an external
+   * file and leave this one unchanged.  That's because when we
+   * (InteractiveTools.com) release a new official version, it's less
+   * likely that you will have problems upgrading HTMLArea.
+   *
+   * Example on how to add a custom button when you construct the HTMLArea:
+   *
+   *   var editor = new HTMLArea("your_text_area_id");
+   *   var cfg = editor.config; // this is the default configuration
+   *   cfg.btnList["my-hilite"] =
+   *	[ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action
+   *	  "Highlight selection", // tooltip
+   *	  "my_hilite.gif", // image
+   *	  false // disabled in text mode
+   *	];
+   *   cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar
+   *
+   * An alternate (also more convenient and recommended) way to
+   * accomplish this is to use the registerButton function below.
+   */
+  // initialize tooltips from the I18N module and generate correct image path
+  for ( var i in this.btnList )
+  {
+    var btn = this.btnList[i];
+    // prevent iterating over wrong type
+    if ( typeof btn != 'object' )
+    {
+      continue;
+    } 
+    if ( typeof btn[1] != 'string' )
+    {
+      btn[1][0] = _editor_url + this.imgURL + btn[1][0];
+    }
+    else
+    {
+      btn[1] = _editor_url + this.imgURL + btn[1];
+    }
+    btn[0] = HTMLArea._lc(btn[0]); //initialize tooltip
+  }
+
+};
+
+/** Helper function: register a new button with the configuration.  It can be
+ * called with all 5 arguments, or with only one (first one).  When called with
+ * only one argument it must be an object with the following properties: id,
+ * tooltip, image, textMode, action.  Examples:
+ *
+ * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...});
+ * 2. config.registerButton({
+ *      id       : "my-hilite",      // the ID of your button
+ *      tooltip  : "Hilite text",    // the tooltip
+ *      image    : "my-hilite.gif",  // image to be displayed in the toolbar
+ *      textMode : false,            // disabled in text mode
+ *      action   : function(editor) { // called when the button is clicked
+ *                   editor.surroundHTML('<span class="hilite">', '</span>');
+ *                 },
+ *      context  : "p"               // will be disabled if outside a <p> element
+ *    });
+ */
+HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context)
+{
+  var the_id;
+  if ( typeof id == "string" )
+  {
+    the_id = id;
+  }
+  else if ( typeof id == "object" )
+  {
+    the_id = id.id;
+  }
+  else
+  {
+    alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
+    return false;
+  }
+  // check for existing id
+//  if(typeof this.customSelects[the_id] != "undefined")
+//  {
+    // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
+//  }
+//  if(typeof this.btnList[the_id] != "undefined") {
+    // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
+//  }
+  switch ( typeof id )
+  {
+    case "string":
+      this.btnList[id] = [ tooltip, image, textMode, action, context ];
+    break;
+    case "object":
+      this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ];
+    break;
+  }
+};
+
+HTMLArea.prototype.registerPanel = function(side, object)
+{
+  if ( !side )
+  {
+    side = 'right';
+  }
+  this.setLoadingMessage('Register panel ' + side);
+  var panel = this.addPanel(side);
+  if ( object )
+  {
+    object.drawPanelIn(panel);
+  }
+};
+
+/** The following helper function registers a dropdown box with the editor
+ * configuration.  You still have to add it to the toolbar, same as with the
+ * buttons.  Call it like this:
+ *
+ * FIXME: add example
+ */
+HTMLArea.Config.prototype.registerDropdown = function(object)
+{
+  // check for existing id
+//  if ( typeof this.customSelects[object.id] != "undefined" )
+//  {
+    // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
+//  }
+//  if ( typeof this.btnList[object.id] != "undefined" )
+//  {
+    // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
+//  }
+  this.customSelects[object.id] = object;
+};
+
+/** Call this function to remove some buttons/drop-down boxes from the toolbar.
+ * Pass as the only parameter a string containing button/drop-down names
+ * delimited by spaces.  Note that the string should also begin with a space
+ * and end with a space.  Example:
+ *
+ *   config.hideSomeButtons(" fontname fontsize textindicator ");
+ *
+ * It's useful because it's easier to remove stuff from the defaul toolbar than
+ * create a brand new toolbar ;-)
+ */
+HTMLArea.Config.prototype.hideSomeButtons = function(remove)
+{
+  var toolbar = this.toolbar;
+  for ( var i = toolbar.length; --i >= 0; )
+  {
+    var line = toolbar[i];
+    for ( var j = line.length; --j >= 0; )
+    {
+      if ( remove.indexOf(" " + line[j] + " ") >= 0 )
+      {
+        var len = 1;
+        if ( /separator|space/.test(line[j + 1]) )
+        {
+          len = 2;
+        }
+        line.splice(j, len);
+      }
+    }
+  }
+};
+
+/** Helper Function: add buttons/drop-downs boxes with title or separator to the toolbar
+ * if the buttons/drop-downs boxes doesn't allready exists.
+ * id: button or selectbox (as array with separator or title)
+ * where: button or selectbox (as array if the first is not found take the second and so on)
+ * position:
+ * -1 = insert button (id) one position before the button (where)
+ * 0 = replace button (where) by button (id)
+ * +1 = insert button (id) one position after button (where)
+ *
+ * cfg.addToolbarElement(["T[title]", "button_id", "separator"] , ["first_id","second_id"], -1);
+*/
+
+HTMLArea.Config.prototype.addToolbarElement = function(id, where, position)
+{
+  var toolbar = this.toolbar;
+  var a, i, j, o, sid;
+  var idIsArray = false;
+  var whereIsArray = false;
+  var whereLength = 0;
+  var whereJ = 0;
+  var whereI = 0;
+  var exists = false;
+  var found = false;
+  // check if id and where are arrys
+  if ( ( id && typeof id == "object" ) && ( id.constructor == Array ) )
+  {
+    idIsArray = true;
+  }
+  if ( ( where && typeof where == "object" ) && ( where.constructor == Array ) )
+  {
+    whereIsArray = true;
+    whereLength = where.length;
+	}
+
+  if ( idIsArray ) //find the button/select box in input array
+  {
+    for ( i = 0; i < id.length; ++i )
+    {
+      if ( ( id[i] != "separator" ) && ( id[i].indexOf("T[") !== 0) )
+      {
+        sid = id[i];
+      }
+    }
+  }
+  else
+  {
+    sid = id;
+  }
+
+  for ( i = 0; !exists && !found && i < toolbar.length; ++i )
+  {
+    a = toolbar[i];
+    for ( j = 0; !found && j < a.length; ++j )
+    {
+      // check if button/select box exists
+      if ( a[i] == sid )
+      { 
+        exists = true;
+        break;
+      }
+      if ( whereIsArray )
+      {
+        for ( o = 0; o < whereLength; ++o )
+        {
+          if ( a[j] == where[o] )
+          {
+            if ( o === 0 )
+            {
+              found = true;
+              j--;
+              break;
+            }
+            else
+            {
+              whereI = i;
+              whereJ = j;
+              whereLength = o;
+            }
+          }
+        }
+      }
+      else
+      {
+        // find the position to insert
+        if ( a[j] == where )
+        { 
+          found = true;
+          break;
+        }
+      }
+    }
+  }
+
+  if ( !exists )
+  {
+    //if check found any other as the first button
+    if ( !found && whereIsArray )
+    { 
+      if ( where.length != whereLength )
+      {
+        j = whereJ;
+        a = toolbar[whereI];
+        found = true;
+      }
+    }
+    if ( found )
+    {
+      // replace the found button
+      if ( position === 0 )
+      {
+        if ( idIsArray)
+        {
+          a[j] = id[id.length-1];
+          for ( i = id.length-1; --i >= 0; )
+          {
+            a.splice(j, 0, id[i]);
+          }
+        }
+        else
+        {
+          a[j] = id;
+        }
+      }
+      else
+      { 
+        // insert before/after the found button
+        if ( position < 0 )
+        {
+          j = j + position + 1; //correct position before
+        }
+        else if ( position > 0 )
+        {
+          j = j + position; //correct posion after
+        }
+        if ( idIsArray )
+        {
+          for ( i = id.length; --i >= 0; )
+          {
+            a.splice(j, 0, id[i]);
+          }
+        }
+        else
+        {
+          a.splice(j, 0, id);
+        }
+      }
+    }
+    else
+    {
+      // no button found
+      toolbar[0].splice(0, 0, "separator");
+      if ( idIsArray)
+      {
+        for ( i = id.length; --i >= 0; )
+        {
+          toolbar[0].splice(0, 0, id[i]);
+        }
+      }
+      else
+      {
+        toolbar[0].splice(0, 0, id);
+      }
+    }
+  }
+};
+
+HTMLArea.Config.prototype.removeToolbarElement = HTMLArea.Config.prototype.hideSomeButtons;
+
+/** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
+HTMLArea.replaceAll = function(config)
+{
+  var tas = document.getElementsByTagName("textarea");
+  // @todo: weird syntax, doesnt help to read the code, doesnt obfuscate it and doesnt make it quicker, better rewrite this part
+  for ( var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate() )
+  {
+    // NOP
+  }
+};
+
+/** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
+HTMLArea.replace = function(id, config)
+{
+  var ta = HTMLArea.getElementById("textarea", id);
+  return ta ? (new HTMLArea(ta, config)).generate() : null;
+};
+
+// Creates the toolbar and appends it to the _htmlarea
+HTMLArea.prototype._createToolbar = function ()
+{
+  this.setLoadingMessage('Create Toolbar');
+  var editor = this;	// to access this in nested functions
+
+  var toolbar = document.createElement("div");
+  // ._toolbar is for legacy, ._toolBar is better thanks.
+  this._toolBar = this._toolbar = toolbar;
+  toolbar.className = "toolbar";
+  toolbar.unselectable = "1";
+
+  HTMLArea.freeLater(this, '_toolBar');
+  HTMLArea.freeLater(this, '_toolbar');
+  
+  var tb_row = null;
+  var tb_objects = {};
+  this._toolbarObjects = tb_objects;
+
+	this._createToolbar1(editor, toolbar, tb_objects);
+	this._htmlArea.appendChild(toolbar);      
+  
+  return toolbar;
+};
+
+// FIXME : function never used, can probably be removed from source
+HTMLArea.prototype._setConfig = function(config)
+{
+	this.config = config;
+};
+
+HTMLArea.prototype._addToolbar = function()
+{
+	this._createToolbar1(this, this._toolbar, this._toolbarObjects);
+};
+
+/**
+ * Create a break element to add in the toolbar
+ *
+ * @return {Object} HTML element to add
+ * @private
+ */
+HTMLArea._createToolbarBreakingElement = function()
+{
+  var brk = document.createElement('div');
+  brk.style.height = '1px';
+  brk.style.width = '1px';
+  brk.style.lineHeight = '1px';
+  brk.style.fontSize = '1px';
+  brk.style.clear = 'both';
+  return brk;
+};
+
+// separate from previous createToolBar to allow dynamic change of toolbar
+HTMLArea.prototype._createToolbar1 = function (editor, toolbar, tb_objects)
+{
+  var tb_row;
+  // This shouldn't be necessary, but IE seems to float outside of the container
+  // when we float toolbar sections, so we have to clear:both here as well
+  // as at the end (which we do have to do).
+  if ( editor.config.flowToolbars )
+  {
+    toolbar.appendChild(HTMLArea._createToolbarBreakingElement());
+  }
+
+  // creates a new line in the toolbar
+  function newLine()
+  {
+    if ( typeof tb_row != 'undefined' && tb_row.childNodes.length === 0)
+    {
+      return;
+    }
+
+    var table = document.createElement("table");
+    table.border = "0px";
+    table.cellSpacing = "0px";
+    table.cellPadding = "0px";
+    if ( editor.config.flowToolbars )
+    {
+      if ( HTMLArea.is_ie )
+      {
+        table.style.styleFloat = "left";
+      }
+      else
+      {
+        table.style.cssFloat = "left";
+      }
+    }
+
+    toolbar.appendChild(table);
+    // TBODY is required for IE, otherwise you don't see anything
+    // in the TABLE.
+    var tb_body = document.createElement("tbody");
+    table.appendChild(tb_body);
+    tb_row = document.createElement("tr");
+    tb_body.appendChild(tb_row);
+
+    table.className = 'toolbarRow'; // meh, kinda.
+  } // END of function: newLine
+
+  // init first line
+  newLine();
+
+  // updates the state of a toolbar element.  This function is member of
+  // a toolbar element object (unnamed objects created by createButton or
+  // createSelect functions below).
+  function setButtonStatus(id, newval)
+  {
+    var oldval = this[id];
+    var el = this.element;
+    if ( oldval != newval )
+    {
+      switch (id)
+      {
+        case "enabled":
+          if ( newval )
+          {
+            HTMLArea._removeClass(el, "buttonDisabled");
+            el.disabled = false;
+          }
+          else
+          {
+            HTMLArea._addClass(el, "buttonDisabled");
+            el.disabled = true;
+          }
+        break;
+        case "active":
+          if ( newval )
+          {
+            HTMLArea._addClass(el, "buttonPressed");
+          }
+          else
+          {
+            HTMLArea._removeClass(el, "buttonPressed");
+          }
+        break;
+      }
+      this[id] = newval;
+    }
+  } // END of function: setButtonStatus
+
+  // this function will handle creation of combo boxes.  Receives as
+  // parameter the name of a button as defined in the toolBar config.
+  // This function is called from createButton, above, if the given "txt"
+  // doesn't match a button.
+  function createSelect(txt)
+  {
+    var options = null;
+    var el = null;
+    var cmd = null;
+    var customSelects = editor.config.customSelects;
+    var context = null;
+    var tooltip = "";
+    switch (txt)
+    {
+      case "fontsize":
+      case "fontname":
+      case "formatblock":
+        // the following line retrieves the correct
+        // configuration option because the variable name
+        // inside the Config object is named the same as the
+        // button/select in the toolbar.  For instance, if txt
+        // == "formatblock" we retrieve config.formatblock (or
+        // a different way to write it in JS is
+        // config["formatblock"].
+        options = editor.config[txt];
+        cmd = txt;
+      break;
+      default:
+        // try to fetch it from the list of registered selects
+        cmd = txt;
+        var dropdown = customSelects[cmd];
+        if ( typeof dropdown != "undefined" )
+        {
+          options = dropdown.options;
+          context = dropdown.context;
+          if ( typeof dropdown.tooltip != "undefined" )
+          {
+            tooltip = dropdown.tooltip;
+          }
+        }
+        else
+        {
+          alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
+        }
+      break;
+    }
+    if ( options )
+    {
+      el = document.createElement("select");
+      el.title = tooltip;
+      var obj =
+      {
+        name	: txt, // field name
+        element : el,	// the UI element (SELECT)
+        enabled : true, // is it enabled?
+        text	: false, // enabled in text mode?
+        cmd	: cmd, // command ID
+        state	: setButtonStatus, // for changing state
+        context : context
+      };
+      
+      HTMLArea.freeLater(obj);
+      
+      tb_objects[txt] = obj;
+      
+      for ( var i in options )
+      {
+        // prevent iterating over wrong type
+        if ( typeof(options[i]) != 'string' )
+        {
+          continue;
+        }
+        var op = document.createElement("option");
+        op.innerHTML = HTMLArea._lc(i);
+        op.value = options[i];
+        el.appendChild(op);
+      }
+      HTMLArea._addEvent(el, "change", function () { editor._comboSelected(el, txt); } );
+    }
+    return el;
+  } // END of function: createSelect
+
+  // appends a new button to toolbar
+  function createButton(txt)
+  {
+    // the element that will be created
+    var el, btn, obj = null;
+    switch (txt)
+    {
+      case "separator":
+        if ( editor.config.flowToolbars )
+        {
+          newLine();
+        }
+        el = document.createElement("div");
+        el.className = "separator";
+      break;
+      case "space":
+        el = document.createElement("div");
+        el.className = "space";
+      break;
+      case "linebreak":
+        newLine();
+        return false;
+      case "textindicator":
+        el = document.createElement("div");
+        el.appendChild(document.createTextNode("A"));
+        el.className = "indicator";
+        el.title = HTMLArea._lc("Current style");
+        obj =
+        {
+          name	: txt, // the button name (i.e. 'bold')
+          element : el, // the UI element (DIV)
+          enabled : true, // is it enabled?
+          active	: false, // is it pressed?
+          text	: false, // enabled in text mode?
+          cmd	: "textindicator", // the command ID
+          state	: setButtonStatus // for changing state
+        };
+      
+        HTMLArea.freeLater(obj);
+      
+        tb_objects[txt] = obj;
+      break;
+      default:
+        btn = editor.config.btnList[txt];
+    }
+    if ( !el && btn )
+    {
+      el = document.createElement("a");
+      el.style.display = 'block';
+      el.href = 'javascript:void(0)';
+      el.style.textDecoration = 'none';
+      el.title = btn[0];
+      el.className = "button";
+      el.style.direction = "ltr";
+      // let's just pretend we have a button object, and
+      // assign all the needed information to it.
+      obj =
+      {
+        name : txt, // the button name (i.e. 'bold')
+        element : el, // the UI element (DIV)
+        enabled : true, // is it enabled?
+        active : false, // is it pressed?
+        text : btn[2], // enabled in text mode?
+        cmd	: btn[3], // the command ID
+        state	: setButtonStatus, // for changing state
+        context : btn[4] || null // enabled in a certain context?
+      };
+      HTMLArea.freeLater(el);
+      HTMLArea.freeLater(obj);
+
+      tb_objects[txt] = obj;
+
+      // prevent drag&drop of the icon to content area
+      el.ondrag = function() { return false; };
+
+      // handlers to emulate nice flat toolbar buttons
+      HTMLArea._addEvent(
+        el,
+        "mouseout",
+        function(ev)
+        {
+          if ( obj.enabled )
+          {
+            //HTMLArea._removeClass(el, "buttonHover");
+            HTMLArea._removeClass(el, "buttonActive");
+            if ( obj.active )
+            {
+              HTMLArea._addClass(el, "buttonPressed");
+            }
+          }
+        }
+      );
+
+      HTMLArea._addEvent(
+        el,
+        "mousedown",
+        function(ev)
+        {
+          if ( obj.enabled )
+          {
+            HTMLArea._addClass(el, "buttonActive");
+            HTMLArea._removeClass(el, "buttonPressed");
+            HTMLArea._stopEvent(HTMLArea.is_ie ? window.event : ev);
+          }
+        }
+      );
+
+      // when clicked, do the following:
+      HTMLArea._addEvent(
+        el,
+        "click",
+        function(ev)
+        {
+          if ( obj.enabled )
+          {
+            HTMLArea._removeClass(el, "buttonActive");
+            //HTMLArea._removeClass(el, "buttonHover");
+            if ( HTMLArea.is_gecko )
+            {
+              editor.activateEditor();
+            }
+            obj.cmd(editor, obj.name, obj);
+            HTMLArea._stopEvent(HTMLArea.is_ie ? window.event : ev);
+          }
+        }
+      );
+
+      var i_contain = HTMLArea.makeBtnImg(btn[1]);
+      var img = i_contain.firstChild;
+      el.appendChild(i_contain);
+
+      obj.imgel = img;      
+      obj.swapImage = function(newimg)
+      {
+        if ( typeof newimg != 'string' )
+        {
+          img.src = newimg[0];
+          img.style.position = 'relative';
+          img.style.top  = newimg[2] ? ('-' + (18 * (newimg[2] + 1)) + 'px') : '-18px';
+          img.style.left = newimg[1] ? ('-' + (18 * (newimg[1] + 1)) + 'px') : '-18px';
+        }
+        else
+        {
+          obj.imgel.src = newimg;
+          img.style.top = '0px';
+          img.style.left = '0px';
+        }
+      };
+      
+    }
+    else if( !el )
+    {
+      el = createSelect(txt);
+    }
+
+    return el;
+  }
+
+  var first = true;
+  for ( var i = 0; i < this.config.toolbar.length; ++i )
+  {
+    if ( !first )
+    {
+      // createButton("linebreak");
+    }
+    else
+    {
+      first = false;
+    }
+    if ( this.config.toolbar[i] === null )
+    {
+      this.config.toolbar[i] = ['separator'];
+    }
+    var group = this.config.toolbar[i];
+
+    for ( var j = 0; j < group.length; ++j )
+    {
+      var code = group[j];
+      var tb_cell;
+      if ( /^([IT])\[(.*?)\]/.test(code) )
+      {
+        // special case, create text label
+        var l7ed = RegExp.$1 == "I"; // localized?
+        var label = RegExp.$2;
+        if ( l7ed )
+        {
+          label = HTMLArea._lc(label);
+        }
+        tb_cell = document.createElement("td");
+        tb_row.appendChild(tb_cell);
+        tb_cell.className = "label";
+        tb_cell.innerHTML = label;
+      }
+      else if ( typeof code != 'function' )
+      {
+        var tb_element = createButton(code);
+        if ( tb_element )
+        {
+          tb_cell = document.createElement("td");
+          tb_cell.className = 'toolbarElement';
+          tb_row.appendChild(tb_cell);
+          tb_cell.appendChild(tb_element);
+        }
+        else if ( tb_element === null )
+        {
+          alert("FIXME: Unknown toolbar item: " + code);
+        }
+      }
+    }
+  }
+
+  if ( editor.config.flowToolbars )
+  {
+    toolbar.appendChild(HTMLArea._createToolbarBreakingElement());
+  }
+
+  return toolbar;
+};
+
+// @todo : is this some kind of test not finished ?
+//         Why the hell this is not in the config object ?
+var use_clone_img = false;
+HTMLArea.makeBtnImg = function(imgDef, doc)
+{
+  if ( !doc )
+  {
+    doc = document;
+  }
+
+  if ( !doc._htmlareaImgCache )
+  {
+    doc._htmlareaImgCache = {};
+    HTMLArea.freeLater(doc._htmlareaImgCache);
+  }
+
+  var i_contain = null;
+  if ( HTMLArea.is_ie && ( ( !doc.compatMode ) || ( doc.compatMode && doc.compatMode == "BackCompat" ) ) )
+  {
+    i_contain = doc.createElement('span');
+  }
+  else
+  {
+    i_contain = doc.createElement('div');
+    i_contain.style.position = 'relative';
+  }
+
+  i_contain.style.overflow = 'hidden';
+  i_contain.style.width = "18px";
+  i_contain.style.height = "18px";
+  i_contain.className = 'buttonImageContainer';
+
+  var img = null;
+  if ( typeof imgDef == 'string' )
+  {
+    if ( doc._htmlareaImgCache[imgDef] )
+    {
+      img = doc._htmlareaImgCache[imgDef].cloneNode();
+    }
+    else
+    {
+      img = doc.createElement("img");
+      img.src = imgDef;
+      img.style.width = "18px";
+      img.style.height = "18px";
+      if ( use_clone_img )
+      {
+        doc._htmlareaImgCache[imgDef] = img.cloneNode();
+      }
+    }
+  }
+  else
+  {
+    if ( doc._htmlareaImgCache[imgDef[0]] )
+    {
+      img = doc._htmlareaImgCache[imgDef[0]].cloneNode();
+    }
+    else
+    {
+      img = doc.createElement("img");
+      img.src = imgDef[0];
+      img.style.position = 'relative';
+      if ( use_clone_img )
+      {
+        doc._htmlareaImgCache[imgDef[0]] = img.cloneNode();
+      }
+    }
+    // @todo: Using 18 dont let us use a theme with its own icon toolbar height
+    //        and width. Probably better to calculate this value 18
+    //        var sizeIcon = img.width / nb_elements_per_image;
+    img.style.top  = imgDef[2] ? ('-' + (18 * (imgDef[2] + 1)) + 'px') : '-18px';
+    img.style.left = imgDef[1] ? ('-' + (18 * (imgDef[1] + 1)) + 'px') : '-18px';
+  }
+  i_contain.appendChild(img);
+  return i_contain;
+};
+
+HTMLArea.prototype._createStatusBar = function()
+{
+  this.setLoadingMessage('Create StatusBar');
+  var statusbar = document.createElement("div");
+  statusbar.className = "statusBar";
+  this._statusBar = statusbar;
+  HTMLArea.freeLater(this, '_statusBar');
+  
+  // statusbar.appendChild(document.createTextNode(HTMLArea._lc("Path") + ": "));
+  // creates a holder for the path view
+  var div = document.createElement("span");
+  div.className = "statusBarTree";
+  div.innerHTML = HTMLArea._lc("Path") + ": ";
+  this._statusBarTree = div;
+  HTMLArea.freeLater(this, '_statusBarTree');
+  this._statusBar.appendChild(div);
+
+  div = document.createElement("span");
+  div.innerHTML = HTMLArea._lc("You are in TEXT MODE.  Use the [<>] button to switch back to WYSIWYG.");
+  div.style.display = "none";
+  this._statusBarTextMode = div;
+  HTMLArea.freeLater(this, '_statusBarTextMode');
+  this._statusBar.appendChild(div);
+
+  if ( !this.config.statusBar )
+  {
+    // disable it...
+    statusbar.style.display = "none";
+  }
+
+  return statusbar;
+};
+
+// Creates the HTMLArea object and replaces the textarea with it.
+HTMLArea.prototype.generate = function ()
+{
+  var i;
+  var editor = this;	// we'll need "this" in some nested functions
+  this.setLoadingMessage('Generate Xinha object');
+
+  if ( typeof Dialog == 'undefined' )
+  {
+    HTMLArea._loadback(_editor_url + 'dialog.js', this.generate, this );
+    return false;
+  }
+
+  if ( typeof HTMLArea.Dialog == 'undefined' )
+  {
+    HTMLArea._loadback(_editor_url + 'inline-dialog.js', this.generate, this );
+    return false;
+  }
+
+  if ( typeof PopupWin == 'undefined' )
+  {
+    HTMLArea._loadback(_editor_url + 'popupwin.js', this.generate, this );
+    return false;
+  }
+
+  if ( typeof colorPicker == 'undefined' )
+  {
+    HTMLArea._loadback(_editor_url + 'popups/color_picker.js', this.generate, this );
+    return false;
+  }
+
+  if ( _editor_skin !== "" )
+  {
+    var found = false;
+    var head = document.getElementsByTagName("head")[0];
+    var links = document.getElementsByTagName("link");
+    for(i = 0; i<links.length; i++)
+    {
+      if ( ( links[i].rel == "stylesheet" ) && ( links[i].href == _editor_url + 'skins/' + _editor_skin + '/skin.css' ) )
+      {
+        found = true;
+      }
+    }
+    if ( !found )
+    {
+      var link = document.createElement("link");
+      link.type = "text/css";
+      link.href = _editor_url + 'skins/' + _editor_skin + '/skin.css';
+      link.rel = "stylesheet";
+      head.appendChild(link);
+    }
+  }
+
+  //backwards-compatibility: load FullScreen-Plugin if we find a "popupeditor"-button in the toolbar
+  // @todo: remove the backward compatibility in release 2.0
+  var toolbar = editor.config.toolbar;
+  for ( i = toolbar.length; --i >= 0; )
+  {
+    for ( var j = toolbar[i].length; --j >= 0; )
+    {
+      if ( toolbar[i][j]=="popupeditor" )
+      {
+        if ( typeof FullScreen == "undefined" )
+        {
+          // why can't we use the following line instead ?
+//          HTMLArea.loadPlugin("FullScreen", this.generate );
+          HTMLArea.loadPlugin("FullScreen", function() { editor.generate(); } );
+          return false;
+        }
+        editor.registerPlugin('FullScreen');
+      }
+    }
+  }
+
+  // If this is gecko, set up the paragraph handling now
+  if ( HTMLArea.is_gecko && editor.config.mozParaHandler == 'best' )
+  {
+    if ( typeof EnterParagraphs == 'undefined' )
+    {
+      // why can't we use the following line instead ?
+//      HTMLArea.loadPlugin("EnterParagraphs", this.generate );
+      HTMLArea.loadPlugin("EnterParagraphs", function() { editor.generate(); } );
+      return false;
+    }
+    editor.registerPlugin('EnterParagraphs');
+  }
+
+  // create the editor framework, yah, table layout I know, but much easier
+  // to get it working correctly this way, sorry about that, patches welcome.
+
+  this._framework =
+  {
+    'table':   document.createElement('table'),
+    'tbody':   document.createElement('tbody'), // IE will not show the table if it doesn't have a tbody!
+    'tb_row':  document.createElement('tr'),
+    'tb_cell': document.createElement('td'), // Toolbar
+
+    'tp_row':  document.createElement('tr'),
+    'tp_cell': this._panels.top.container,   // top panel
+
+    'ler_row': document.createElement('tr'),
+    'lp_cell': this._panels.left.container,  // left panel
+    'ed_cell': document.createElement('td'), // editor
+    'rp_cell': this._panels.right.container, // right panel
+
+    'bp_row':  document.createElement('tr'),
+    'bp_cell': this._panels.bottom.container,// bottom panel
+
+    'sb_row':  document.createElement('tr'),
+    'sb_cell': document.createElement('td')  // status bar
+
+  };
+  HTMLArea.freeLater(this._framework);
+  
+  var fw = this._framework;
+  fw.table.border = "0";
+  fw.table.cellPadding = "0";
+  fw.table.cellSpacing = "0";
+
+  fw.tb_row.style.verticalAlign = 'top';
+  fw.tp_row.style.verticalAlign = 'top';
+  fw.ler_row.style.verticalAlign= 'top';
+  fw.bp_row.style.verticalAlign = 'top';
+  fw.sb_row.style.verticalAlign = 'top';
+  fw.ed_cell.style.position     = 'relative';
+
+  // Put the cells in the rows        set col & rowspans
+  // note that I've set all these so that all panels are showing
+  // but they will be redone in sizeEditor() depending on which
+  // panels are shown.  It's just here to clarify how the thing
+  // is put togethor.
+  fw.tb_row.appendChild(fw.tb_cell);
+  fw.tb_cell.colSpan = 3;
+
+  fw.tp_row.appendChild(fw.tp_cell);
+  fw.tp_cell.colSpan = 3;
+
+  fw.ler_row.appendChild(fw.lp_cell);
+  fw.ler_row.appendChild(fw.ed_cell);
+  fw.ler_row.appendChild(fw.rp_cell);
+
+  fw.bp_row.appendChild(fw.bp_cell);
+  fw.bp_cell.colSpan = 3;
+
+  fw.sb_row.appendChild(fw.sb_cell);
+  fw.sb_cell.colSpan = 3;
+
+  // Put the rows in the table body
+  fw.tbody.appendChild(fw.tb_row);  // Toolbar
+  fw.tbody.appendChild(fw.tp_row); // Left, Top, Right panels
+  fw.tbody.appendChild(fw.ler_row);  // Editor/Textarea
+  fw.tbody.appendChild(fw.bp_row);  // Bottom panel
+  fw.tbody.appendChild(fw.sb_row);  // Statusbar
+
+  // and body in the table
+  fw.table.appendChild(fw.tbody);
+
+  var htmlarea = this._framework.table;
+  this._htmlArea = htmlarea;
+  HTMLArea.freeLater(this, '_htmlArea');
+  htmlarea.className = "htmlarea";
+
+    // create the toolbar and put in the area
+  this._framework.tb_cell.appendChild( this._createToolbar() );
+
+    // create the IFRAME & add to container
+  var iframe = document.createElement("iframe");
+  iframe.src = _editor_url + editor.config.URIs.blank;
+  this._framework.ed_cell.appendChild(iframe);
+  this._iframe = iframe;
+  this._iframe.className = 'xinha_iframe';
+  HTMLArea.freeLater(this, '_iframe');
+  
+    // creates & appends the status bar
+  var statusbar = this._createStatusBar();
+  this._framework.sb_cell.appendChild(statusbar);
+
+  // insert Xinha before the textarea.
+  var textarea = this._textArea;
+  textarea.parentNode.insertBefore(htmlarea, textarea);
+  textarea.className = 'xinha_textarea';
+
+  // extract the textarea and insert it into the htmlarea
+  HTMLArea.removeFromParent(textarea);
+  this._framework.ed_cell.appendChild(textarea);
+
+
+  // Set up event listeners for saving the iframe content to the textarea
+  if ( textarea.form )
+  {
+    // onsubmit get the HTMLArea content and update original textarea.
+    HTMLArea.prependDom0Event(
+      this._textArea.form,
+      'submit',
+      function()
+      {
+        editor._textArea.value = editor.outwardHtml(editor.getHTML());
+        return true;
+      }
+    );
+
+    var initialTAContent = textarea.value;
+
+    // onreset revert the HTMLArea content to the textarea content
+    HTMLArea.prependDom0Event(
+      this._textArea.form,
+      'reset',
+      function()
+      {
+        editor.setHTML(editor.inwardHtml(initialTAContent));
+        editor.updateToolbar();
+        return true;
+      }
+    );
+  }
+
+  // add a handler for the "back/forward" case -- on body.unload we save
+  // the HTML content into the original textarea.
+  HTMLArea.prependDom0Event(
+    window,
+    'unload',
+    function()
+    {
+      textarea.value = editor.outwardHtml(editor.getHTML());
+      return true;
+    }
+  );
+
+  // Hide textarea
+  textarea.style.display = "none";
+
+  // Initalize size
+  editor.initSize();
+
+  // Add an event to initialize the iframe once loaded.
+  editor._iframeLoadDone = false;
+  HTMLArea._addEvent(
+    this._iframe,
+    'load',
+    function(e)
+    {
+      if ( !editor._iframeLoadDone )
+      {
+        editor._iframeLoadDone = true;
+        editor.initIframe();
+      }
+      return true;
+    }
+  );
+
+};
+
+/**
+ * Size the editor according to the INITIAL sizing information.
+ * config.width
+ *    The width may be set via three ways
+ *    auto    = the width is inherited from the original textarea
+ *    toolbar = the width is set to be the same size as the toolbar
+ *    <set size> = the width is an explicit size (any CSS measurement, eg 100em should be fine)
+ *
+ * config.height
+ *    auto    = the height is inherited from the original textarea
+ *    <set size> = an explicit size measurement (again, CSS measurements)
+ *
+ * config.sizeIncludesBars
+ *    true    = the tool & status bars will appear inside the width & height confines
+ *    false   = the tool & status bars will appear outside the width & height confines
+ *
+ */
+
+HTMLArea.prototype.initSize = function()
+{
+  this.setLoadingMessage('Init editor size');
+  var editor = this;
+  var width = null;
+  var height = null;
+
+  switch ( this.config.width )
+  {
+    case 'auto':
+      width = this._initial_ta_size.w;
+    break;
+
+    case 'toolbar':
+      width = this._toolBar.offsetWidth + 'px';
+    break;
+
+    default :
+      // @todo: check if this is better :
+      // width = (parseInt(this.config.width, 10) == this.config.width)? this.config.width + 'px' : this.config.width;
+      width = /[^0-9]/.test(this.config.width) ? this.config.width : this.config.width + 'px';
+    break;
+  }
+
+  switch ( this.config.height )
+  {
+    case 'auto':
+      height = this._initial_ta_size.h;
+    break;
+
+    default :
+      // @todo: check if this is better :
+      // height = (parseInt(this.config.height, 10) == this.config.height)? this.config.height + 'px' : this.config.height;
+      height = /[^0-9]/.test(this.config.height) ? this.config.height : this.config.height + 'px';
+    break;
+  }
+
+  this.sizeEditor(width, height, this.config.sizeIncludesBars, this.config.sizeIncludesPanels);
+
+  // why can't we use the following line instead ?
+//  this.notifyOn('panel_change',this.sizeEditor);
+  this.notifyOn('panel_change',function() { editor.sizeEditor(); });
+};
+
+/**
+ *  Size the editor to a specific size, or just refresh the size (when window resizes for example)
+ *  @param width optional width (CSS specification)
+ *  @param height optional height (CSS specification)
+ *  @param includingBars optional boolean to indicate if the size should include or exclude tool & status bars
+ */
+HTMLArea.prototype.sizeEditor = function(width, height, includingBars, includingPanels)
+{
+
+  // We need to set the iframe & textarea to 100% height so that the htmlarea
+  // isn't "pushed out" when we get it's height, so we can change them later.
+  this._iframe.style.height   = '100%';
+  this._textArea.style.height = '100%';
+  this._iframe.style.width    = '';
+  this._textArea.style.width  = '';
+
+  if ( includingBars !== null )
+  {
+    this._htmlArea.sizeIncludesToolbars = includingBars;
+  }
+  if ( includingPanels !== null )
+  {
+    this._htmlArea.sizeIncludesPanels = includingPanels;
+  }
+
+  if ( width )
+  {
+    this._htmlArea.style.width = width;
+    if ( !this._htmlArea.sizeIncludesPanels )
+    {
+      // Need to add some for l & r panels
+      var rPanel = this._panels.right;
+      if ( rPanel.on && rPanel.panels.length && HTMLArea.hasDisplayedChildren(rPanel.div) )
+      {
+        this._htmlArea.style.width = (this._htmlArea.offsetWidth + parseInt(this.config.panel_dimensions.right, 10)) + 'px';
+      }
+
+      var lPanel = this._panels.left;
+      if ( lPanel.on && lPanel.panels.length && HTMLArea.hasDisplayedChildren(lPanel.div) )
+      {
+        this._htmlArea.style.width = (this._htmlArea.offsetWidth + parseInt(this.config.panel_dimensions.left, 10)) + 'px';
+      }
+    }
+  }
+
+  if ( height )
+  {
+    this._htmlArea.style.height = height;
+    if ( !this._htmlArea.sizeIncludesToolbars )
+    {
+      // Need to add some for toolbars
+      this._htmlArea.style.height = (this._htmlArea.offsetHeight + this._toolbar.offsetHeight + this._statusBar.offsetHeight) + 'px';
+    }
+
+    if ( !this._htmlArea.sizeIncludesPanels )
+    {
+      // Need to add some for t & b panels
+      var tPanel = this._panels.top;
+      if ( tPanel.on && tPanel.panels.length && HTMLArea.hasDisplayedChildren(tPanel.div) )
+      {
+        this._htmlArea.style.height = (this._htmlArea.offsetHeight + parseInt(this.config.panel_dimensions.top, 10)) + 'px';
+      }
+
+      var bPanel = this._panels.bottom;
+      if ( bPanel.on && bPanel.panels.length && HTMLArea.hasDisplayedChildren(bPanel.div) )
+      {
+        this._htmlArea.style.height = (this._htmlArea.offsetHeight + parseInt(this.config.panel_dimensions.bottom, 10)) + 'px';
+      }
+    }
+  }
+
+  // At this point we have this._htmlArea.style.width & this._htmlArea.style.height
+  // which are the size for the OUTER editor area, including toolbars and panels
+  // now we size the INNER area and position stuff in the right places.
+  width  = this._htmlArea.offsetWidth;
+  height = this._htmlArea.offsetHeight;
+
+  // Set colspan for toolbar, and statusbar, rowspan for left & right panels, and insert panels to be displayed
+  // into thier rows
+  var panels = this._panels;
+  var editor = this;
+  var col_span = 1;
+
+  function panel_is_alive(pan)
+  {
+    if ( panels[pan].on && panels[pan].panels.length && HTMLArea.hasDisplayedChildren(panels[pan].container) )
+    {
+      panels[pan].container.style.display = '';
+      return true;
+    }
+    // Otherwise make sure it's been removed from the framework
+    else
+    {
+      panels[pan].container.style.display='none';
+      return false;
+    }
+  }
+
+  if ( panel_is_alive('left') )
+  {
+    col_span += 1;      
+  }
+
+//  if ( panel_is_alive('top') )
+//  {
+    // NOP
+//  }
+
+  if ( panel_is_alive('right') )
+  {
+    col_span += 1;
+  }
+
+//  if ( panel_is_alive('bottom') )
+//  {
+    // NOP
+//  }
+
+  this._framework.tb_cell.colSpan = col_span;
+  this._framework.tp_cell.colSpan = col_span;
+  this._framework.bp_cell.colSpan = col_span;
+  this._framework.sb_cell.colSpan = col_span;
+
+  // Put in the panel rows, top panel goes above editor row
+  if ( !this._framework.tp_row.childNodes.length )
+  {
+    HTMLArea.removeFromParent(this._framework.tp_row);
+  }
+  else
+  {
+    if ( !HTMLArea.hasParentNode(this._framework.tp_row) )
+    {
+      this._framework.tbody.insertBefore(this._framework.tp_row, this._framework.ler_row);
+    }
+  }
+
+  // bp goes after the editor
+  if ( !this._framework.bp_row.childNodes.length )
+  {
+    HTMLArea.removeFromParent(this._framework.bp_row);
+  }
+  else
+  {
+    if ( !HTMLArea.hasParentNode(this._framework.bp_row) )
+    {
+      this._framework.tbody.insertBefore(this._framework.bp_row, this._framework.ler_row.nextSibling);
+    }
+  }
+
+  // finally if the statusbar is on, insert it
+  if ( !this.config.statusBar )
+  {
+    HTMLArea.removeFromParent(this._framework.sb_row);
+  }
+  else
+  {
+    if ( !HTMLArea.hasParentNode(this._framework.sb_row) )
+    {
+      this._framework.table.appendChild(this._framework.sb_row);
+    }
+  }
+
+  // Size and set colspans, link up the framework
+  this._framework.lp_cell.style.width  = this.config.panel_dimensions.left;
+  this._framework.rp_cell.style.width  = this.config.panel_dimensions.right;
+  this._framework.tp_cell.style.height = this.config.panel_dimensions.top;
+  this._framework.bp_cell.style.height = this.config.panel_dimensions.bottom;
+  this._framework.tb_cell.style.height = this._toolBar.offsetHeight + 'px';
+  this._framework.sb_cell.style.height = this._statusBar.offsetHeight + 'px';
+
+  var edcellheight = height - this._toolBar.offsetHeight - this._statusBar.offsetHeight;
+  if ( panel_is_alive('top') )
+  {
+    edcellheight -= parseInt(this.config.panel_dimensions.top, 10);
+  }
+  if ( panel_is_alive('bottom') )
+  {
+    edcellheight -= parseInt(this.config.panel_dimensions.bottom, 10);
+  }
+  this._iframe.style.height = edcellheight + 'px';  
+//  this._framework.rp_cell.style.height = edcellheight + 'px';
+//  this._framework.lp_cell.style.height = edcellheight + 'px';
+  
+  // (re)size the left and right panels so they are equal the editor height
+//  for(var i = 0; i < this._panels.left.panels.length; i++)
+//  {
+//    this._panels.left.panels[i].style.height = this._iframe.style.height;
+//  }
+  
+//  for(var i = 0; i < this._panels.right.panels.length; i++)
+//  {
+//    this._panels.right.panels[i].style.height = this._iframe.style.height;
+//  }  
+  
+  var edcellwidth = width;
+  if ( panel_is_alive('left') )
+  {
+    edcellwidth -= parseInt(this.config.panel_dimensions.left, 10);
+  }
+  if ( panel_is_alive('right') )
+  {
+    edcellwidth -= parseInt(this.config.panel_dimensions.right, 10);    
+  }
+  this._iframe.style.width = edcellwidth + 'px';
+
+  this._textArea.style.height = this._iframe.style.height;
+  this._textArea.style.width  = this._iframe.style.width;
+     
+  this.notifyOf('resize', {width:this._htmlArea.offsetWidth, height:this._htmlArea.offsetHeight});
+};
+
+HTMLArea.prototype.addPanel = function(side)
+{
+  var div = document.createElement('div');
+  div.side = side;
+  if ( side == 'left' || side == 'right' )
+  {
+    div.style.width  = this.config.panel_dimensions[side];
+    if(this._iframe) div.style.height = this._iframe.style.height;     
+  }
+  HTMLArea.addClasses(div, 'panel');
+  this._panels[side].panels.push(div);
+  this._panels[side].div.appendChild(div);
+
+  this.notifyOf('panel_change', {'action':'add','panel':div});
+
+  return div;
+};
+
+
+HTMLArea.prototype.removePanel = function(panel)
+{
+  this._panels[panel.side].div.removeChild(panel);
+  var clean = [];
+  for ( var i = 0; i < this._panels[panel.side].panels.length; i++ )
+  {
+    if ( this._panels[panel.side].panels[i] != panel )
+    {
+      clean.push(this._panels[panel.side].panels[i]);
+    }
+  }
+  this._panels[panel.side].panels = clean;
+  this.notifyOf('panel_change', {'action':'remove','panel':panel});
+};
+
+HTMLArea.prototype.hidePanel = function(panel)
+{
+  if ( panel && panel.style.display != 'none' )
+  {
+    panel.style.display = 'none';
+    this.notifyOf('panel_change', {'action':'hide','panel':panel});
+  }
+};
+
+HTMLArea.prototype.showPanel = function(panel)
+{
+  if ( panel && panel.style.display == 'none' )
+  {
+    panel.style.display = '';
+    this.notifyOf('panel_change', {'action':'show','panel':panel});
+  }
+};
+
+HTMLArea.prototype.hidePanels = function(sides)
+{
+  if ( typeof sides == 'undefined' )
+  {
+    sides = ['left','right','top','bottom'];
+  }
+
+  var reShow = [];
+  for ( var i = 0; i < sides.length;i++ )
+  {
+    if ( this._panels[sides[i]].on )
+    {
+      reShow.push(sides[i]);
+      this._panels[sides[i]].on = false;
+    }
+  }
+  this.notifyOf('panel_change', {'action':'multi_hide','sides':sides});
+};
+
+HTMLArea.prototype.showPanels = function(sides)
+{
+  if ( typeof sides == 'undefined' )
+  {
+    sides = ['left','right','top','bottom'];
+  }
+
+  var reHide = [];
+  for ( var i = 0; i < sides.length; i++ )
+  {
+    if ( !this._panels[sides[i]].on )
+    {
+      reHide.push(sides[i]);
+      this._panels[sides[i]].on = true;
+    }
+  }
+  this.notifyOf('panel_change', {'action':'multi_show','sides':sides});
+};
+
+HTMLArea.objectProperties = function(obj)
+{
+  var props = [];
+  for ( var x in obj )
+  {
+    props[props.length] = x;
+  }
+  return props;
+};
+
+/*
+ * EDITOR ACTIVATION NOTES:
+ *  when a page has multiple Xinha editors, ONLY ONE should be activated at any time (this is mostly to
+ *  work around a bug in Mozilla, but also makes some sense).  No editor should be activated or focused
+ *  automatically until at least one editor has been activated through user action (by mouse-clicking in
+ *  the editor).
+ */
+HTMLArea.prototype.editorIsActivated = function()
+{
+  try
+  {
+    return HTMLArea.is_gecko? this._doc.designMode == 'on' : this._doc.body.contentEditable;
+  }
+  catch (ex)
+  {
+    return false;
+  }
+};
+
+HTMLArea._someEditorHasBeenActivated = false;
+HTMLArea._currentlyActiveEditor      = false;
+HTMLArea.prototype.activateEditor = function()
+{
+  // We only want ONE editor at a time to be active
+  if ( HTMLArea._currentlyActiveEditor )
+  {
+    if ( HTMLArea._currentlyActiveEditor == this )
+    {
+      return true;
+    }
+    HTMLArea._currentlyActiveEditor.deactivateEditor();
+  }
+
+  if ( HTMLArea.is_gecko && this._doc.designMode != 'on' )
+  {
+    try
+    {
+      // cannot set design mode if no display
+      if ( this._iframe.style.display == 'none' )
+      {
+        this._iframe.style.display = '';
+        this._doc.designMode = 'on';
+        this._iframe.style.display = 'none';
+      }
+      else
+      {
+        this._doc.designMode = 'on';
+      }
+    } catch (ex) {}
+  }
+  else if ( !HTMLArea.is_gecko && this._doc.body.contentEditable !== true )
+  {
+    this._doc.body.contentEditable = true;
+  }
+
+  // We need to know that at least one editor on the page has been activated
+  // this is because we will not focus any editor until an editor has been activated
+  HTMLArea._someEditorHasBeenActivated = true;
+  HTMLArea._currentlyActiveEditor      = this;
+
+  var editor = this;
+  this.enableToolbar();
+};
+
+HTMLArea.prototype.deactivateEditor = function()
+{
+  // If the editor isn't active then the user shouldn't use the toolbar
+  this.disableToolbar();
+
+  if ( HTMLArea.is_gecko && this._doc.designMode != 'off' )
+  {
+    try
+    {
+      this._doc.designMode = 'off';
+    } catch (ex) {}
+  }
+  else if ( !HTMLArea.is_gecko && this._doc.body.contentEditable !== false )
+  {
+    this._doc.body.contentEditable = false;
+  }
+
+  if ( HTMLArea._currentlyActiveEditor != this )
+  {
+    // We just deactivated an editor that wasn't marked as the currentlyActiveEditor
+
+    return; // I think this should really be an error, there shouldn't be a situation where
+            // an editor is deactivated without first being activated.  but it probably won't
+            // hurt anything.
+  }
+
+  HTMLArea._currentlyActiveEditor = false;
+};
+
+HTMLArea.prototype.initIframe = function()
+{
+  this.setLoadingMessage('Init IFrame');
+  this.disableToolbar();
+  var doc = null;
+  var editor = this;
+  try
+  {
+    if ( editor._iframe.contentDocument )
+    {
+      this._doc = editor._iframe.contentDocument;        
+    }
+    else
+    {
+      this._doc = editor._iframe.contentWindow.document;
+    }
+    doc = this._doc;
+    // try later
+    if ( !doc )
+    {
+      if ( HTMLArea.is_gecko )
+      {
+        setTimeout(function() { editor.initIframe(); }, 50);
+        return false;
+      }
+      else
+      {
+        alert("ERROR: IFRAME can't be initialized.");
+      }
+    }
+  }
+  catch(ex)
+  { // try later
+    setTimeout(function() { editor.initIframe(); }, 50);
+  }
+  
+  HTMLArea.freeLater(this, '_doc');
+  
+  doc.open("text/html","replace");
+  var html = '';
+  if ( !editor.config.fullPage )
+  {
+    html = "<html>\n";
+    html += "<head>\n";
+    html += "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=" + editor.config.charSet + "\">\n";
+    if ( typeof editor.config.baseHref != 'undefined' && editor.config.baseHref !== null )
+    {
+      html += "<base href=\"" + editor.config.baseHref + "\"/>\n";
+    }
+    html += "<style title=\"table borders\">";
+    html += ".htmtableborders, .htmtableborders td, .htmtableborders th {border : 1px dashed lightgrey ! important;} \n";
+    html += "</style>\n";
+    html += "<style type=\"text/css\">";
+    html += "html, body { border: 0px;  background-color: #ffffff; } \n";
+    html += "span.macro, span.macro ul, span.macro div, span.macro p {background : #CCCCCC;}\n";
+    html += "</style>\n";
+
+    if ( editor.config.pageStyle )
+    {
+      html += "<style type=\"text/css\">\n" + editor.config.pageStyle + "\n</style>";
+    }
+
+    if ( typeof editor.config.pageStyleSheets !== 'undefined' )
+    {
+      for ( var i = 0; i < editor.config.pageStyleSheets.length; i++ )
+      {
+        if ( editor.config.pageStyleSheets[i].length > 0 )
+        {
+          html += "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + editor.config.pageStyleSheets[i] + "\">";
+          //html += "<style> @import url('" + editor.config.pageStyleSheets[i] + "'); </style>\n";
+        }
+      }
+    }
+    html += "</head>\n";
+    html += "<body>\n";
+    html +=   editor.inwardHtml(editor._textArea.value);
+    html += "</body>\n";
+    html += "</html>";
+  }
+  else
+  {
+    html = editor.inwardHtml(editor._textArea.value);
+    if ( html.match(HTMLArea.RE_doctype) )
+    {
+      editor.setDoctype(RegExp.$1);
+      html = html.replace(HTMLArea.RE_doctype, "");
+    }
+  }
+  doc.write(html);
+  doc.close();
+
+  this.setEditorEvents();
+};
+  
+/**
+ * Delay a function until the document is ready for operations.
+ * See ticket:547
+ * @param {object} F (Function) The function to call once the document is ready
+ * @public
+ */
+HTMLArea.prototype.whenDocReady = function(F)
+{
+  var E = this;
+  if ( this._doc && this._doc.body )
+  {
+    F();
+  }
+  else
+  {
+    setTimeout(function() { E.whenDocReady(F); }, 50);
+  }
+};
+
+// Switches editor mode; parameter can be "textmode" or "wysiwyg".  If no
+// parameter was passed this function toggles between modes.
+HTMLArea.prototype.setMode = function(mode)
+{
+  var html;
+  if ( typeof mode == "undefined" )
+  {
+    mode = this._editMode == "textmode" ? "wysiwyg" : "textmode";
+  }
+  switch ( mode )
+  {
+    case "textmode":
+      html = this.outwardHtml(this.getHTML());
+      this.setHTML(html);
+
+      // Hide the iframe
+      this.deactivateEditor();
+      this._iframe.style.display   = 'none';
+      this._textArea.style.display = '';
+
+      if ( this.config.statusBar )
+      {
+        this._statusBarTree.style.display = "none";
+        this._statusBarTextMode.style.display = "";
+      }
+
+      this.notifyOf('modechange', {'mode':'text'});
+    break;
+
+    case "wysiwyg":
+      html = this.inwardHtml(this.getHTML());
+      this.deactivateEditor();
+      this.setHTML(html);
+      this._iframe.style.display   = '';
+      this._textArea.style.display = "none";
+      this.activateEditor();
+      if ( this.config.statusBar )
+      {
+        this._statusBarTree.style.display = "";
+        this._statusBarTextMode.style.display = "none";
+      }
+
+      this.notifyOf('modechange', {'mode':'wysiwyg'});
+    break;
+
+    default:
+      alert("Mode <" + mode + "> not defined!");
+      return false;
+  }
+  this._editMode = mode;
+
+  for ( var i in this.plugins )
+  {
+    var plugin = this.plugins[i].instance;
+    if ( plugin && typeof plugin.onMode == "function" )
+    {
+      plugin.onMode(mode);
+    }
+  }
+};
+
+HTMLArea.prototype.setFullHTML = function(html)
+{
+  var save_multiline = RegExp.multiline;
+  RegExp.multiline = true;
+  if ( html.match(HTMLArea.RE_doctype) )
+  {
+    this.setDoctype(RegExp.$1);
+    html = html.replace(HTMLArea.RE_doctype, "");
+  }
+  RegExp.multiline = save_multiline;
+  if ( !HTMLArea.is_ie )
+  {
+    if ( html.match(HTMLArea.RE_head) )
+    {
+      this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
+    }
+    if ( html.match(HTMLArea.RE_body) )
+    {
+      this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
+    }
+  }
+  else
+  {
+    var reac = this.editorIsActivated();
+    if ( reac )
+    {
+      this.deactivateEditor();
+    }
+    var html_re = /<html>((.|\n)*?)<\/html>/i;
+    html = html.replace(html_re, "$1");
+    this._doc.open("text/html","replace");
+    this._doc.write(html);
+    this._doc.close();
+    if ( reac )
+    {
+      this.activateEditor();
+    }
+    this.setEditorEvents();
+    return true;
+  }
+};
+
+HTMLArea.prototype.setEditorEvents = function()
+{
+  var editor=this;
+  var doc=this._doc;
+  editor.whenDocReady(
+    function()
+    {
+      // if we have multiple editors some bug in Mozilla makes some lose editing ability
+      HTMLArea._addEvents(
+        doc,
+        ["mousedown"],
+        function()
+        {
+          editor.activateEditor();
+          return true;
+        }
+      );
+
+      // intercept some events; for updating the toolbar & keyboard handlers
+      HTMLArea._addEvents(
+        doc,
+        ["keydown", "keypress", "mousedown", "mouseup", "drag"],
+        function (event)
+        {
+          return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
+        }
+      );
+
+      // check if any plugins have registered refresh handlers
+      for ( var i in editor.plugins )
+      {
+        var plugin = editor.plugins[i].instance;
+        HTMLArea.refreshPlugin(plugin);
+      }
+
+      // specific editor initialization
+      if ( typeof editor._onGenerate == "function" )
+      {
+        editor._onGenerate();
+      }
+
+      HTMLArea.addDom0Event(window, 'resize', function(e) { editor.sizeEditor(); });
+      editor.removeLoadingMessage();
+    }
+  );
+};
+  
+/***************************************************
+ *  Category: PLUGINS
+ ***************************************************/
+
+// Create the specified plugin and register it with this HTMLArea
+// return the plugin created to allow refresh when necessary
+HTMLArea.prototype.registerPlugin = function()
+{
+  var plugin = arguments[0];
+
+  // @todo : try to avoid the use of eval()
+  // We can only register plugins that have been succesfully loaded
+  if ( plugin === null || typeof plugin == 'undefined' || (typeof plugin == 'string' && eval('typeof ' + plugin) == 'undefined') )
+  {
+    return false;
+  }
+
+  var args = [];
+  for ( var i = 1; i < arguments.length; ++i )
+  {
+    args.push(arguments[i]);
+  }
+  return this.registerPlugin2(plugin, args);
+};
+
+// this is the variant of the function above where the plugin arguments are
+// already packed in an array.  Externally, it should be only used in the
+// full-screen editor code, in order to initialize plugins with the same
+// parameters as in the opener window.
+HTMLArea.prototype.registerPlugin2 = function(plugin, args)
+{
+  // @todo : try to avoid the use of eval()
+  if ( typeof plugin == "string" )
+  {
+    plugin = eval(plugin);
+  }
+  if ( typeof plugin == "undefined" )
+  {
+    /* FIXME: This should never happen. But why does it do? */
+    return false;
+  }
+  var obj = new plugin(this, args);
+  if ( obj )
+  {
+    var clone = {};
+    var info = plugin._pluginInfo;
+    for ( var i in info )
+    {
+      clone[i] = info[i];
+    }
+    clone.instance = obj;
+    clone.args = args;
+    this.plugins[plugin._pluginInfo.name] = clone;
+    return obj;
+  }
+  else
+  {
+    alert("Can't register plugin " + plugin.toString() + ".");
+  }
+};
+
+// static function that loads the required plugin and lang file, based on the
+// language loaded already for HTMLArea.  You better make sure that the plugin
+// _has_ that language, otherwise shit might happen ;-)
+HTMLArea.getPluginDir = function(pluginName)
+{
+  return _editor_url + "plugins/" + pluginName;
+};
+
+HTMLArea.loadPlugin = function(pluginName, callback)
+{
+  // @todo : try to avoid the use of eval()
+  // Might already be loaded
+  if ( eval('typeof ' + pluginName) != 'undefined' )
+  {
+    if ( callback )
+    {
+      callback(pluginName);
+    }
+    return true;
+  }
+
+  var dir = this.getPluginDir(pluginName);
+  var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, function (str, l1, l2, l3) { return l1 + "-" + l2.toLowerCase() + l3; }).toLowerCase() + ".js";
+  var plugin_file = dir + "/" + plugin;
+
+  HTMLArea._loadback(plugin_file, callback ? function() { callback(pluginName); } : null);
+  return false;
+};
+
+HTMLArea._pluginLoadStatus = {};
+HTMLArea.loadPlugins = function(plugins, callbackIfNotReady)
+{
+  // Rip the ones that are loaded and look for ones that have failed
+  var retVal = true;
+  var nuPlugins = HTMLArea.cloneObject(plugins);
+  while ( nuPlugins.length )
+  {
+    var p = nuPlugins.pop();
+    if ( typeof HTMLArea._pluginLoadStatus[p] == 'undefined' )
+    {
+      // Load it
+      HTMLArea._pluginLoadStatus[p] = 'loading';
+      HTMLArea.loadPlugin(p,
+        function(plugin)
+        {
+          // @todo : try to avoid the use of eval()
+          if ( eval('typeof ' + plugin) != 'undefined' )
+          {
+            HTMLArea._pluginLoadStatus[plugin] = 'ready';
+          }
+          else
+          {
+            // Actually, this won't happen, because if the script fails
+            // it will throw an exception preventing the callback from
+            // running.  This will leave it always in the "loading" state
+            // unfortunatly that means we can't fail plugins gracefully
+            // by just skipping them.
+            HTMLArea._pluginLoadStatus[plugin] = 'failed';
+          }
+        }
+      );
+      retVal = false;
+    }
+    else
+    {
+      // @todo: a simple (if) would not be better than this tortuous (switch) structure ?
+      // if ( HTMLArea._pluginLoadStatus[p] !== 'failed' && HTMLArea._pluginLoadStatus[p] !== 'ready' )
+      // {
+      //   retVal = false;
+      // }
+      switch ( HTMLArea._pluginLoadStatus[p] )
+      {
+        case 'failed':
+        case 'ready' :
+        break;
+
+        //case 'loading':
+        default       :
+         retVal = false;
+       break;
+      }
+    }
+  }
+
+  // All done, just return
+  if ( retVal )
+  {
+    return true;
+  } 
+
+  // Waiting on plugins to load, return false now and come back a bit later
+  // if we have to callback
+  if ( callbackIfNotReady )
+  {
+    setTimeout(function() { if ( HTMLArea.loadPlugins(plugins, callbackIfNotReady) ) { callbackIfNotReady(); } }, 150);
+  }
+  return retVal;
+};
+
+// refresh plugin by calling onGenerate or onGenerateOnce method.
+HTMLArea.refreshPlugin = function(plugin)
+{
+  if ( plugin && typeof plugin.onGenerate == "function" )
+  {
+    plugin.onGenerate();
+  }
+  if ( plugin && typeof plugin.onGenerateOnce == "function" )
+  {
+    plugin.onGenerateOnce();
+    plugin.onGenerateOnce = null;
+  }
+};
+
+HTMLArea.loadStyle = function(style, plugin)
+{
+  var url = _editor_url || '';
+  if ( typeof plugin != "undefined" )
+  {
+    url += "plugins/" + plugin + "/";
+  }
+  url += style;
+  // @todo: would not it be better to check the first character instead of a regex ?
+  // if ( typeof style == 'string' && style.charAt(0) == '/' )
+  // {
+  //   url = style;
+  // }
+  if ( /^\//.test(style) )
+  {
+    url = style;
+  }
+  var head = document.getElementsByTagName("head")[0];
+  var link = document.createElement("link");
+  link.rel = "stylesheet";
+  link.href = url;
+  head.appendChild(link);
+  //document.write("<style type='text/css'>@import url(" + url + ");</style>");
+};
+HTMLArea.loadStyle(typeof _editor_css == "string" ? _editor_css : "htmlarea.css");
+
+/***************************************************
+ *  Category: EDITOR UTILITIES
+ ***************************************************/
+
+HTMLArea.prototype.debugTree = function()
+{
+  var ta = document.createElement("textarea");
+  ta.style.width = "100%";
+  ta.style.height = "20em";
+  ta.value = "";
+  function debug(indent, str)
+  {
+    for ( ; --indent >= 0; )
+    {
+      ta.value += " ";
+    }
+    ta.value += str + "\n";
+  }
+  function _dt(root, level)
+  {
+    var tag = root.tagName.toLowerCase(), i;
+    var ns = HTMLArea.is_ie ? root.scopeName : root.prefix;
+    debug(level, "- " + tag + " [" + ns + "]");
+    for ( i = root.firstChild; i; i = i.nextSibling )
+    {
+      if ( i.nodeType == 1 )
+      {
+        _dt(i, level + 2);
+      }
+    }
+  }
+  _dt(this._doc.body, 0);
+  document.body.appendChild(ta);
+};
+
+HTMLArea.getInnerText = function(el)
+{
+  var txt = '', i;
+  for ( i = el.firstChild; i; i = i.nextSibling )
+  {
+    if ( i.nodeType == 3 )
+    {
+      txt += i.data;
+    }
+    else if ( i.nodeType == 1 )
+    {
+      txt += HTMLArea.getInnerText(i);
+    }
+  }
+  return txt;
+};
+
+HTMLArea.prototype._wordClean = function()
+{
+  var editor = this;
+  var stats =
+  {
+    empty_tags : 0,
+    mso_class  : 0,
+    mso_style  : 0,
+    mso_xmlel  : 0,
+    orig_len   : this._doc.body.innerHTML.length,
+    T          : (new Date()).getTime()
+  };
+  var stats_txt =
+  {
+    empty_tags : "Empty tags removed: ",
+    mso_class  : "MSO class names removed: ",
+    mso_style  : "MSO inline style removed: ",
+    mso_xmlel  : "MSO XML elements stripped: "
+  };
+
+  function showStats()
+  {
+    var txt = "HTMLArea word cleaner stats: \n\n";
+    for ( var i in stats )
+    {
+      if ( stats_txt[i] )
+      {
+        txt += stats_txt[i] + stats[i] + "\n";
+      }
+    }
+    txt += "\nInitial document length: " + stats.orig_len + "\n";
+    txt += "Final document length: " + editor._doc.body.innerHTML.length + "\n";
+    txt += "Clean-up took " + (((new Date()).getTime() - stats.T) / 1000) + " seconds";
+    alert(txt);
+  }
+
+  function clearClass(node)
+  {
+    var newc = node.className.replace(/(^|\s)mso.*?(\s|$)/ig, ' ');
+    if ( newc != node.className )
+    {
+      node.className = newc;
+      if ( ! ( /\S/.test(node.className) ) )
+      {
+        node.removeAttribute("className");
+        ++stats.mso_class;
+      }
+    }
+  }
+
+  function clearStyle(node)
+  {
+    var declarations = node.style.cssText.split(/\s*;\s*/);
+    for ( var i = declarations.length; --i >= 0; )
+    {
+      if ( ( /^mso|^tab-stops/i.test(declarations[i]) ) || ( /^margin\s*:\s*0..\s+0..\s+0../i.test(declarations[i]) ) )
+      {
+        ++stats.mso_style;
+        declarations.splice(i, 1);
+      }
+    }
+    node.style.cssText = declarations.join("; ");
+  }
+
+  var stripTag = null;
+  if ( HTMLArea.is_ie )
+  {
+    stripTag = function(el)
+    {
+      el.outerHTML = HTMLArea.htmlEncode(el.innerText);
+      ++stats.mso_xmlel;
+    };
+  }
+  else
+  {
+    stripTag = function(el)
+    {
+      var txt = document.createTextNode(HTMLArea.getInnerText(el));
+      el.parentNode.insertBefore(txt, el);
+      HTMLArea.removeFromParent(el);
+      ++stats.mso_xmlel;
+    };
+  }
+
+  function checkEmpty(el)
+  {
+    // @todo : check if this is quicker
+    //  if (!['A','SPAN','B','STRONG','I','EM','FONT'].contains(el.tagName) && !el.firstChild)
+    if ( /^(a|span|b|strong|i|em|font)$/i.test(el.tagName) && !el.firstChild)
+    {
+      HTMLArea.removeFromParent(el);
+      ++stats.empty_tags;
+    }
+  }
+
+  function parseTree(root)
+  {
+    var tag = root.tagName.toLowerCase(), i, next;
+    // @todo : probably better to use String.indexOf() instead of this ugly regex
+    // if ((HTMLArea.is_ie && root.scopeName != 'HTML') || (!HTMLArea.is_ie && tag.indexOf(':') !== -1)) {
+    if ( ( HTMLArea.is_ie && root.scopeName != 'HTML' ) || ( !HTMLArea.is_ie && ( /:/.test(tag) ) ) )
+    {
+      stripTag(root);
+      return false;
+    }
+    else
+    {
+      clearClass(root);
+      clearStyle(root);
+      for ( i = root.firstChild; i; i = next )
+      {
+        next = i.nextSibling;
+        if ( i.nodeType == 1 && parseTree(i) )
+        {

[... 4023 lines stripped ...]