[ Index ]
 

Code source de Serendipity 1.2

Accédez au Source d'autres logiciels libres

title

Body

[fermer]

/htmlarea/ -> htmlarea.js (source)

   1  // htmlArea v3.0 - Copyright (c) 2003-2004 dynarch.com
   2  //                               2002-2003 interactivetools.com, inc.
   3  // This copyright notice MUST stay intact for use (see license.txt).
   4  //
   5  // A free WYSIWYG editor replacement for <textarea> fields.
   6  // For full source code and docs, visit http://www.interactivetools.com/
   7  //
   8  // Version 3.0 developed by Mihai Bazon.
   9  //   http://dynarch.com/mishoo/
  10  //
  11  // $Id: htmlarea.js,v 1.7 2005/01/11 15:00:34 garvinhicking Exp $
  12  
  13  if (typeof _editor_url == "string") {
  14      // Leave exactly one backslash at the end of _editor_url
  15      _editor_url = _editor_url.replace(/\x2f*$/, '/');
  16  } else {
  17      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.");
  18      _editor_url = '';
  19  }
  20  
  21  // make sure we have a language
  22  if (typeof _editor_lang == "string") {
  23      _editor_lang = _editor_lang.toLowerCase();
  24  } else {
  25      _editor_lang = "en";
  26  }
  27  
  28  // browser identification
  29  HTMLArea.agt = navigator.userAgent.toLowerCase();
  30  HTMLArea.is_ie       = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
  31  HTMLArea.is_opera  = (HTMLArea.agt.indexOf("opera") != -1);
  32  HTMLArea.is_mac       = (HTMLArea.agt.indexOf("mac") != -1);
  33  HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
  34  HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
  35  HTMLArea.is_gecko  = (navigator.product == "Gecko");
  36  
  37  // Creates a new HTMLArea object.  Tries to replace the textarea with the given
  38  // ID with it.
  39  function HTMLArea(textarea, config) {
  40      if (HTMLArea.checkSupportedBrowser()) {
  41          if (typeof config == "undefined") {
  42              this.config = new HTMLArea.Config();
  43          } else {
  44              this.config = config;
  45          }
  46          this._htmlArea = null;
  47          this._textArea = textarea;
  48          this._editMode = "wysiwyg";
  49          this.plugins = {};
  50          this._timerToolbar = null;
  51          this._timerUndo = null;
  52          this._undoQueue = new Array(this.config.undoSteps);
  53          this._undoPos = -1;
  54          this._customUndo = false;
  55          this._mdoc = document; // cache the document, we need it in plugins
  56          this.doctype = '';
  57      }
  58  };
  59  
  60  HTMLArea.onload = function(){};
  61  HTMLArea._scripts = [];
  62  HTMLArea.loadScript = function(url, plugin) {
  63      if (plugin)
  64          url = HTMLArea.getPluginDir(plugin) + '/' + url;
  65      this._scripts.push(url);
  66  };
  67  
  68  HTMLArea.init = function() {
  69      var head = document.getElementsByTagName("head")[0];
  70      var current = 0;
  71      var savetitle = document.title;
  72      var evt = HTMLArea.is_ie ? "onreadystatechange" : "onload";
  73  	function loadNextScript() {
  74          if (current > 0 && HTMLArea.is_ie &&
  75              !/loaded|complete/.test(window.event.srcElement.readyState))
  76              return;
  77          if (current < HTMLArea._scripts.length) {
  78              var url = HTMLArea._scripts[current++];
  79              document.title = "[HTMLArea: loading script " + current + "/" + HTMLArea._scripts.length + "]";
  80              var script = document.createElement("script");
  81              script.type = "text/javascript";
  82              script.src = url;
  83              script[evt] = loadNextScript;
  84              head.appendChild(script);
  85          } else {
  86              document.title = savetitle;
  87              HTMLArea.onload();
  88          }
  89      };
  90      loadNextScript();
  91  };
  92  
  93  HTMLArea.loadScript(_editor_url + "dialog.js");
  94  HTMLArea.loadScript(_editor_url + "popupwin.js");
  95  HTMLArea.loadScript(_editor_url + "lang/" + _editor_lang + ".js");
  96  
  97  // cache some regexps
  98  HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig;
  99  HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i;
 100  HTMLArea.RE_head    = /<head>((.|\n)*?)<\/head>/i;
 101  HTMLArea.RE_body    = /<body>((.|\n)*?)<\/body>/i;
 102  
 103  HTMLArea.Config = function () {
 104      this.version = "3.0";
 105  
 106      this.width = "auto";
 107      this.height = "auto";
 108  
 109      // enable creation of a status bar?
 110      this.statusBar = true;
 111  
 112      // intercept ^V and use the HTMLArea paste command
 113      // If false, then passes ^V through to browser editor widget
 114      this.htmlareaPaste = false;
 115  
 116      // maximum size of the undo queue
 117      this.undoSteps = 20;
 118  
 119      // the time interval at which undo samples are taken
 120      this.undoTimeout = 500;    // 1/2 sec.
 121  
 122      // the next parameter specifies whether the toolbar should be included
 123      // in the size or not.
 124      this.sizeIncludesToolbar = true;
 125  
 126      // if true then HTMLArea will retrieve the full HTML, starting with the
 127      // <HTML> tag.
 128      this.fullPage = false;
 129  
 130      // style included in the iframe document
 131      this.pageStyle = "";
 132  
 133      // set to true if you want Word code to be cleaned upon Paste
 134      this.killWordOnPaste = true;
 135  
 136      // enable the 'Target' field in the Make Link dialog
 137      this.makeLinkShowsTarget = true;
 138  
 139      // BaseURL included in the iframe document
 140      this.baseURL = document.baseURI || document.URL;
 141      if (this.baseURL && this.baseURL.match(/(.*)\/([^\/]+)/))
 142          this.baseURL = RegExp.$1 + "/";
 143  
 144      // URL-s
 145      this.imgURL = "images/";
 146      this.popupURL = "popups/";
 147  
 148      // remove tags (these have to be a regexp, or null if this functionality is not desired)
 149      this.htmlRemoveTags = null;
 150  
 151      /** CUSTOMIZING THE TOOLBAR
 152       * -------------------------
 153       *
 154       * It is recommended that you customize the toolbar contents in an
 155       * external file (i.e. the one calling HTMLArea) and leave this one
 156       * unchanged.  That's because when we (InteractiveTools.com) release a
 157       * new official version, it's less likely that you will have problems
 158       * upgrading HTMLArea.
 159       */
 160      this.toolbar = [
 161          [ "fontname", "space",
 162            "fontsize", "space",
 163            "formatblock", "space",
 164            "bold", "italic", "underline", "strikethrough", "separator",
 165            "subscript", "superscript", "separator",
 166            "copy", "cut", "paste", "space", "undo", "redo", "space", "removeformat", "killword" ],
 167  
 168          [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator",
 169            "lefttoright", "righttoleft", "separator",
 170            "orderedlist", "unorderedlist", "outdent", "indent", "separator",
 171            "forecolor", "hilitecolor", "separator",
 172            "inserthorizontalrule", "createlink", "insertimage", "inserttable", "htmlmode", "separator",
 173            "popupeditor", "separator", "showhelp", "about" ]
 174      ];
 175  
 176      this.fontname = {
 177          "&mdash; font &mdash;":         '',
 178          "Arial":       'arial,helvetica,sans-serif',
 179          "Courier New":       'courier new,courier,monospace',
 180          "Georgia":       'georgia,times new roman,times,serif',
 181          "Tahoma":       'tahoma,arial,helvetica,sans-serif',
 182          "Times New Roman": 'times new roman,times,serif',
 183          "Verdana":       'verdana,arial,helvetica,sans-serif',
 184          "impact":       'impact',
 185          "WingDings":       'wingdings'
 186      };
 187  
 188      this.fontsize = {
 189          "&mdash; size &mdash;"  : "",
 190          "1 (8 pt)" : "1",
 191          "2 (10 pt)": "2",
 192          "3 (12 pt)": "3",
 193          "4 (14 pt)": "4",
 194          "5 (18 pt)": "5",
 195          "6 (24 pt)": "6",
 196          "7 (36 pt)": "7"
 197      };
 198  
 199      this.formatblock = {
 200          "&mdash; format &mdash;"  : "",
 201          "Heading 1": "h1",
 202          "Heading 2": "h2",
 203          "Heading 3": "h3",
 204          "Heading 4": "h4",
 205          "Heading 5": "h5",
 206          "Heading 6": "h6",
 207          "Normal"   : "p",
 208          "Address"  : "address",
 209          "Formatted": "pre"
 210      };
 211  
 212      this.customSelects = {};
 213  
 214  	function cut_copy_paste(e, cmd, obj) {
 215          e.execCommand(cmd);
 216      };
 217  
 218      this.debug = true;
 219  
 220      // ADDING CUSTOM BUTTONS: please read below!
 221      // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]"
 222      //    - ID: unique ID for the button.  If the button calls document.execCommand
 223      //        it's wise to give it the same name as the called command.
 224      //    - ACTION: function that gets called when the button is clicked.
 225      //              it has the following prototype:
 226      //                 function(editor, buttonName)
 227      //              - editor is the HTMLArea object that triggered the call
 228      //              - buttonName is the ID of the clicked button
 229      //              These 2 parameters makes it possible for you to use the same
 230      //              handler for more HTMLArea objects or for more different buttons.
 231      //    - ToolTip: default tooltip, for cases when it is not defined in the -lang- file (HTMLArea.I18N)
 232      //    - Icon: path to an icon image file for the button (TODO: use one image for all buttons!)
 233      //    - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time.
 234      this.btnList = {
 235          bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ],
 236          italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ],
 237          underline: [ "Underline", "ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ],
 238          strikethrough: [ "Strikethrough", "ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ],
 239          subscript: [ "Subscript", "ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ],
 240          superscript: [ "Superscript", "ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ],
 241          justifyleft: [ "Justify Left", "ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ],
 242          justifycenter: [ "Justify Center", "ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ],
 243          justifyright: [ "Justify Right", "ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ],
 244          justifyfull: [ "Justify Full", "ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ],
 245          orderedlist: [ "Ordered List", "ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ],
 246          unorderedlist: [ "Bulleted List", "ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ],
 247          outdent: [ "Decrease Indent", "ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ],
 248          indent: [ "Increase Indent", "ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ],
 249          forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ],
 250          hilitecolor: [ "Background Color", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ],
 251          inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ],
 252          createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ],
 253          insertimage: [ "Insert/Modify Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ],
 254          inserttable: [ "Insert Table", "insert_table.gif", false, function(e) {e.execCommand("inserttable");} ],
 255          htmlmode: [ "Toggle HTML Source", "ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ],
 256          popupeditor: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ],
 257          about: [ "About this editor", "ed_about.gif", true, function(e) {e.execCommand("about");} ],
 258          showhelp: [ "Help using editor", "ed_help.gif", true, function(e) {e.execCommand("showhelp");} ],
 259          undo: [ "Undoes your last action", "ed_undo.gif", false, function(e) {e.execCommand("undo");} ],
 260          redo: [ "Redoes your last action", "ed_redo.gif", false, function(e) {e.execCommand("redo");} ],
 261          cut: [ "Cut selection", "ed_cut.gif", false, cut_copy_paste ],
 262          copy: [ "Copy selection", "ed_copy.gif", false, cut_copy_paste ],
 263          paste: [ "Paste from clipboard", "ed_paste.gif", false, cut_copy_paste ],
 264          lefttoright: [ "Direction left to right", "ed_left_to_right.gif", false, function(e) {e.execCommand("lefttoright");} ],
 265          righttoleft: [ "Direction right to left", "ed_right_to_left.gif", false, function(e) {e.execCommand("righttoleft");} ],
 266          removeformat: [ "Remove formatting", "ed_rmformat.gif", false, function(e) {e.execCommand("removeformat");} ],
 267          print: [ "Print document", "ed_print.gif", false, function(e) {e._iframe.contentWindow.print();} ],
 268          killword: [ "Clear MSOffice tags", "ed_killword.gif", false, function(e) {e.execCommand("killword");} ]
 269      };
 270      /* ADDING CUSTOM BUTTONS
 271       * ---------------------
 272       *
 273       * It is recommended that you add the custom buttons in an external
 274       * file and leave this one unchanged.  That's because when we
 275       * (InteractiveTools.com) release a new official version, it's less
 276       * likely that you will have problems upgrading HTMLArea.
 277       *
 278       * Example on how to add a custom button when you construct the HTMLArea:
 279       *
 280       *   var editor = new HTMLArea("your_text_area_id");
 281       *   var cfg = editor.config; // this is the default configuration
 282       *   cfg.btnList["my-hilite"] =
 283       *    [ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action
 284       *      "Highlight selection", // tooltip
 285       *      "my_hilite.gif", // image
 286       *      false // disabled in text mode
 287       *    ];
 288       *   cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar
 289       *
 290       * An alternate (also more convenient and recommended) way to
 291       * accomplish this is to use the registerButton function below.
 292       */
 293      // initialize tooltips from the I18N module and generate correct image path
 294      for (var i in this.btnList) {
 295          var btn = this.btnList[i];
 296          btn[1] = _editor_url + this.imgURL + btn[1];
 297          if (typeof HTMLArea.I18N.tooltips[i] != "undefined") {
 298              btn[0] = HTMLArea.I18N.tooltips[i];
 299          }
 300      }
 301  };
 302  
 303  /** Helper function: register a new button with the configuration.  It can be
 304   * called with all 5 arguments, or with only one (first one).  When called with
 305   * only one argument it must be an object with the following properties: id,
 306   * tooltip, image, textMode, action.  Examples:
 307   *
 308   * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...});
 309   * 2. config.registerButton({
 310   *      id       : "my-hilite",      // the ID of your button
 311   *      tooltip  : "Hilite text",    // the tooltip
 312   *      image    : "my-hilite.gif",  // image to be displayed in the toolbar
 313   *      textMode : false,            // disabled in text mode
 314   *      action   : function(editor) { // called when the button is clicked
 315   *                   editor.surroundHTML('<span class="hilite">', '</span>');
 316   *                 },
 317   *      context  : "p"               // will be disabled if outside a <p> element
 318   *    });
 319   */
 320  HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) {
 321      var the_id;
 322      if (typeof id == "string") {
 323          the_id = id;
 324      } else if (typeof id == "object") {
 325          the_id = id.id;
 326      } else {
 327          alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
 328          return false;
 329      }
 330      // check for existing id
 331      if (typeof this.customSelects[the_id] != "undefined") {
 332          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
 333      }
 334      if (typeof this.btnList[the_id] != "undefined") {
 335          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
 336      }
 337      switch (typeof id) {
 338          case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break;
 339          case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break;
 340      }
 341  };
 342  
 343  /** The following helper function registers a dropdown box with the editor
 344   * configuration.  You still have to add it to the toolbar, same as with the
 345   * buttons.  Call it like this:
 346   *
 347   * FIXME: add example
 348   */
 349  HTMLArea.Config.prototype.registerDropdown = function(object) {
 350      // check for existing id
 351      if (typeof this.customSelects[object.id] != "undefined") {
 352          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
 353      }
 354      if (typeof this.btnList[object.id] != "undefined") {
 355          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
 356      }
 357      this.customSelects[object.id] = object;
 358  };
 359  
 360  /** Call this function to remove some buttons/drop-down boxes from the toolbar.
 361   * Pass as the only parameter a string containing button/drop-down names
 362   * delimited by spaces.  Note that the string should also begin with a space
 363   * and end with a space.  Example:
 364   *
 365   *   config.hideSomeButtons(" fontname fontsize textindicator ");
 366   *
 367   * It's useful because it's easier to remove stuff from the defaul toolbar than
 368   * create a brand new toolbar ;-)
 369   */
 370  HTMLArea.Config.prototype.hideSomeButtons = function(remove) {
 371      var toolbar = this.toolbar;
 372      for (var i = toolbar.length; --i >= 0;) {
 373          var line = toolbar[i];
 374          for (var j = line.length; --j >= 0; ) {
 375              if (remove.indexOf(" " + line[j] + " ") >= 0) {
 376                  var len = 1;
 377                  if (/separator|space/.test(line[j + 1])) {
 378                      len = 2;
 379                  }
 380                  line.splice(j, len);
 381              }
 382          }
 383      }
 384  };
 385  
 386  /** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
 387  HTMLArea.replaceAll = function(config) {
 388      var tas = document.getElementsByTagName("textarea");
 389      for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate());
 390  };
 391  
 392  /** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
 393  HTMLArea.replace = function(id, config) {
 394      var ta = HTMLArea.getElementById("textarea", id);
 395      return ta ? (new HTMLArea(ta, config)).generate() : null;
 396  };
 397  
 398  // Creates the toolbar and appends it to the _htmlarea
 399  HTMLArea.prototype._createToolbar = function () {
 400      var editor = this;    // to access this in nested functions
 401  
 402      var toolbar = document.createElement("div");
 403      this._toolbar = toolbar;
 404      toolbar.className = "toolbar";
 405      toolbar.unselectable = "1";
 406      var tb_row = null;
 407      var tb_objects = new Object();
 408      this._toolbarObjects = tb_objects;
 409  
 410      // creates a new line in the toolbar
 411  	function newLine() {
 412          var table = document.createElement("table");
 413          table.border = "0px";
 414          table.cellSpacing = "0px";
 415          table.cellPadding = "0px";
 416          toolbar.appendChild(table);
 417          // TBODY is required for IE, otherwise you don't see anything
 418          // in the TABLE.
 419          var tb_body = document.createElement("tbody");
 420          table.appendChild(tb_body);
 421          tb_row = document.createElement("tr");
 422          tb_body.appendChild(tb_row);
 423      }; // END of function: newLine
 424      // init first line
 425      newLine();
 426  
 427      // updates the state of a toolbar element.  This function is member of
 428      // a toolbar element object (unnamed objects created by createButton or
 429      // createSelect functions below).
 430  	function setButtonStatus(id, newval) {
 431          var oldval = this[id];
 432          var el = this.element;
 433          if (oldval != newval) {
 434              switch (id) {
 435                  case "enabled":
 436                  if (newval) {
 437                      HTMLArea._removeClass(el, "buttonDisabled");
 438                      el.disabled = false;
 439                  } else {
 440                      HTMLArea._addClass(el, "buttonDisabled");
 441                      el.disabled = true;
 442                  }
 443                  break;
 444                  case "active":
 445                  if (newval) {
 446                      HTMLArea._addClass(el, "buttonPressed");
 447                  } else {
 448                      HTMLArea._removeClass(el, "buttonPressed");
 449                  }
 450                  break;
 451              }
 452              this[id] = newval;
 453          }
 454      }; // END of function: setButtonStatus
 455  
 456      // this function will handle creation of combo boxes.  Receives as
 457      // parameter the name of a button as defined in the toolBar config.
 458      // This function is called from createButton, above, if the given "txt"
 459      // doesn't match a button.
 460  	function createSelect(txt) {
 461          var options = null;
 462          var el = null;
 463          var cmd = null;
 464          var customSelects = editor.config.customSelects;
 465          var context = null;
 466          var tooltip = "";
 467          switch (txt) {
 468              case "fontsize":
 469              case "fontname":
 470              case "formatblock":
 471              // the following line retrieves the correct
 472              // configuration option because the variable name
 473              // inside the Config object is named the same as the
 474              // button/select in the toolbar.  For instance, if txt
 475              // == "formatblock" we retrieve config.formatblock (or
 476              // a different way to write it in JS is
 477              // config["formatblock"].
 478              options = editor.config[txt];
 479              cmd = txt;
 480              break;
 481              default:
 482              // try to fetch it from the list of registered selects
 483              cmd = txt;
 484              var dropdown = customSelects[cmd];
 485              if (typeof dropdown != "undefined") {
 486                  options = dropdown.options;
 487                  context = dropdown.context;
 488                  if (typeof dropdown.tooltip != "undefined") {
 489                      tooltip = dropdown.tooltip;
 490                  }
 491              } else {
 492                  alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
 493              }
 494              break;
 495          }
 496          if (options) {
 497              el = document.createElement("select");
 498              el.title = tooltip;
 499              var obj = {
 500                  name    : txt, // field name
 501                  element : el,    // the UI element (SELECT)
 502                  enabled : true, // is it enabled?
 503                  text    : false, // enabled in text mode?
 504                  cmd    : cmd, // command ID
 505                  state    : setButtonStatus, // for changing state
 506                  context : context
 507              };
 508              tb_objects[txt] = obj;
 509              for (var i in options) {
 510                  var op = document.createElement("option");
 511                  op.innerHTML = i;
 512                  op.value = options[i];
 513                  el.appendChild(op);
 514              }
 515              HTMLArea._addEvent(el, "change", function () {
 516                  editor._comboSelected(el, txt);
 517              });
 518          }
 519          return el;
 520      }; // END of function: createSelect
 521  
 522      // appends a new button to toolbar
 523  	function createButton(txt) {
 524          // the element that will be created
 525          var el = null;
 526          var btn = null;
 527          switch (txt) {
 528              case "separator":
 529              el = document.createElement("div");
 530              el.className = "separator";
 531              break;
 532              case "space":
 533              el = document.createElement("div");
 534              el.className = "space";
 535              break;
 536              case "linebreak":
 537              newLine();
 538              return false;
 539              case "textindicator":
 540              el = document.createElement("div");
 541              el.appendChild(document.createTextNode("A"));
 542              el.className = "indicator";
 543              el.title = HTMLArea.I18N.tooltips.textindicator;
 544              var obj = {
 545                  name    : txt, // the button name (i.e. 'bold')
 546                  element : el, // the UI element (DIV)
 547                  enabled : true, // is it enabled?
 548                  active    : false, // is it pressed?
 549                  text    : false, // enabled in text mode?
 550                  cmd    : "textindicator", // the command ID
 551                  state    : setButtonStatus // for changing state
 552              };
 553              tb_objects[txt] = obj;
 554              break;
 555              default:
 556              btn = editor.config.btnList[txt];
 557          }
 558          if (!el && btn) {
 559              el = document.createElement("div");
 560              el.title = btn[0];
 561              el.className = "button";
 562              // let's just pretend we have a button object, and
 563              // assign all the needed information to it.
 564              var obj = {
 565                  name    : txt, // the button name (i.e. 'bold')
 566                  element : el, // the UI element (DIV)
 567                  enabled : true, // is it enabled?
 568                  active    : false, // is it pressed?
 569                  text    : btn[2], // enabled in text mode?
 570                  cmd    : btn[3], // the command ID
 571                  state    : setButtonStatus, // for changing state
 572                  context : btn[4] || null // enabled in a certain context?
 573              };
 574              tb_objects[txt] = obj;
 575              // handlers to emulate nice flat toolbar buttons
 576              HTMLArea._addEvent(el, "mouseover", function () {
 577                  if (obj.enabled) {
 578                      HTMLArea._addClass(el, "buttonHover");
 579                  }
 580              });
 581              HTMLArea._addEvent(el, "mouseout", function () {
 582                  if (obj.enabled) with (HTMLArea) {
 583                      _removeClass(el, "buttonHover");
 584                      _removeClass(el, "buttonActive");
 585                      (obj.active) && _addClass(el, "buttonPressed");
 586                  }
 587              });
 588              HTMLArea._addEvent(el, "mousedown", function (ev) {
 589                  if (obj.enabled) with (HTMLArea) {
 590                      _addClass(el, "buttonActive");
 591                      _removeClass(el, "buttonPressed");
 592                      _stopEvent(is_ie ? window.event : ev);
 593                  }
 594              });
 595              // when clicked, do the following:
 596              HTMLArea._addEvent(el, "click", function (ev) {
 597                  if (obj.enabled) with (HTMLArea) {
 598                      _removeClass(el, "buttonActive");
 599                      _removeClass(el, "buttonHover");
 600                      obj.cmd(editor, obj.name, obj);
 601                      _stopEvent(is_ie ? window.event : ev);
 602                  }
 603              });
 604              var img = document.createElement("img");
 605              img.src = btn[1];
 606              img.style.width = "18px";
 607              img.style.height = "18px";
 608              el.appendChild(img);
 609          } else if (!el) {
 610              el = createSelect(txt);
 611          }
 612          if (el) {
 613              var tb_cell = document.createElement("td");
 614              tb_row.appendChild(tb_cell);
 615              tb_cell.appendChild(el);
 616          } else {
 617              alert("FIXME: Unknown toolbar item: " + txt);
 618          }
 619          return el;
 620      };
 621  
 622      var first = true;
 623      for (var i = 0; i < this.config.toolbar.length; ++i) {
 624          if (!first) {
 625              createButton("linebreak");
 626          } else {
 627              first = false;
 628          }
 629          var group = this.config.toolbar[i];
 630          for (var j = 0; j < group.length; ++j) {
 631              var code = group[j];
 632              if (/^([IT])\[(.*?)\]/.test(code)) {
 633                  // special case, create text label
 634                  var l7ed = RegExp.$1 == "I"; // localized?
 635                  var label = RegExp.$2;
 636                  if (l7ed) {
 637                      label = HTMLArea.I18N.custom[label];
 638                  }
 639                  var tb_cell = document.createElement("td");
 640                  tb_row.appendChild(tb_cell);
 641                  tb_cell.className = "label";
 642                  tb_cell.innerHTML = label;
 643              } else {
 644                  createButton(code);
 645              }
 646          }
 647      }
 648  
 649      this._htmlArea.appendChild(toolbar);
 650  };
 651  
 652  HTMLArea.prototype._createStatusBar = function() {
 653      var statusbar = document.createElement("div");
 654      statusbar.className = "statusBar";
 655      this._htmlArea.appendChild(statusbar);
 656      this._statusBar = statusbar;
 657      // statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
 658      // creates a holder for the path view
 659      div = document.createElement("span");
 660      div.className = "statusBarTree";
 661      div.innerHTML = HTMLArea.I18N.msg["Path"] + ": ";
 662      this._statusBarTree = div;
 663      this._statusBar.appendChild(div);
 664      if (!this.config.statusBar) {
 665          // disable it...
 666          statusbar.style.display = "none";
 667      }
 668  };
 669  
 670  // Creates the HTMLArea object and replaces the textarea with it.
 671  HTMLArea.prototype.generate = function () {
 672      var editor = this;    // we'll need "this" in some nested functions
 673      // get the textarea
 674      var textarea = this._textArea;
 675      if (typeof textarea == "string") {
 676          // it's not element but ID
 677          this._textArea = textarea = HTMLArea.getElementById("textarea", textarea);
 678      }
 679      this._ta_size = {
 680          w: textarea.offsetWidth,
 681          h: textarea.offsetHeight
 682      };
 683      textarea.style.display = "none";
 684  
 685      // create the editor framework
 686      var htmlarea = document.createElement("div");
 687      htmlarea.className = "htmlarea";
 688      this._htmlArea = htmlarea;
 689  
 690      // insert the editor before the textarea.
 691      textarea.parentNode.insertBefore(htmlarea, textarea);
 692  
 693      if (textarea.form) {
 694          // we have a form, on submit get the HTMLArea content and
 695          // update original textarea.
 696          var f = textarea.form;
 697          if (typeof f.onsubmit == "function") {
 698              var funcref = f.onsubmit;
 699              if (typeof f.__msh_prevOnSubmit == "undefined") {
 700                  f.__msh_prevOnSubmit = [];
 701              }
 702              f.__msh_prevOnSubmit.push(funcref);
 703          }
 704          f.onsubmit = function() {
 705              editor._textArea.value = editor.getHTML();
 706              var a = this.__msh_prevOnSubmit;
 707              // call previous submit methods if they were there.
 708              if (typeof a != "undefined") {
 709                  for (var i = a.length; --i >= 0;) {
 710                      a[i]();
 711                  }
 712              }
 713          };
 714          if (typeof f.onreset == "function") {
 715              var funcref = f.onreset;
 716              if (typeof f.__msh_prevOnReset == "undefined") {
 717                  f.__msh_prevOnReset = [];
 718              }
 719              f.__msh_prevOnReset.push(funcref);
 720          }
 721          f.onreset = function() {
 722              editor.setHTML(editor._textArea.value);
 723              editor.updateToolbar();
 724              var a = this.__msh_prevOnReset;
 725              // call previous reset methods if they were there.
 726              if (typeof a != "undefined") {
 727                  for (var i = a.length; --i >= 0;) {
 728                      a[i]();
 729                  }
 730              }
 731          };
 732      }
 733  
 734      // add a handler for the "back/forward" case -- on body.unload we save
 735      // the HTML content into the original textarea.
 736      try {
 737  
 738          function addWYSIWYGUnloadEvent(func) {
 739            var oldonunload = window.onunload;
 740            if (typeof window.onunload != 'function') {
 741              window.onunload = func;
 742            } else {
 743              window.onunload = function() {
 744                oldonunload();
 745                func();
 746              }
 747            }
 748          }
 749  
 750          addWYSIWYGUnloadEvent(function() {
 751              editor._textArea.value = editor.getHTML();
 752          });
 753  
 754      } catch(e) {};
 755  
 756      // creates & appends the toolbar
 757      this._createToolbar();
 758  
 759      // create the IFRAME
 760      var iframe = document.createElement("iframe");
 761  
 762      // workaround for the HTTPS problem
 763      // iframe.setAttribute("src", "javascript:void(0);");
 764      iframe.src = _editor_url + "popups/blank.html";
 765  
 766      htmlarea.appendChild(iframe);
 767  
 768      this._iframe = iframe;
 769  
 770      // creates & appends the status bar, if the case
 771      this._createStatusBar();
 772  
 773      // remove the default border as it keeps us from computing correctly
 774      // the sizes.  (somebody tell me why doesn't this work in IE)
 775  
 776      if (!HTMLArea.is_ie) {
 777          iframe.style.borderWidth = "1px";
 778      // iframe.frameBorder = "1";
 779      // iframe.marginHeight = "0";
 780      // iframe.marginWidth = "0";
 781      }
 782  
 783      // size the IFRAME according to user's prefs or initial textarea
 784      var height = (this.config.height == "auto" ? (this._ta_size.h + "px") : this.config.height);
 785      height = parseInt(height);
 786      var width = (this.config.width == "auto" ? (this._ta_size.w + "px") : this.config.width);
 787      width = parseInt(width);
 788  
 789      if (!HTMLArea.is_ie) {
 790          height -= 2;
 791          width -= 2;
 792      }
 793  
 794      iframe.style.width = width + "px";
 795      if (this.config.sizeIncludesToolbar) {
 796          // substract toolbar height
 797          height -= this._toolbar.offsetHeight;
 798          height -= this._statusBar.offsetHeight;
 799      }
 800      if (height < 0) {
 801          height = 0;
 802      }
 803      iframe.style.height = height + "px";
 804  
 805      // the editor including the toolbar now have the same size as the
 806      // original textarea.. which means that we need to reduce that a bit.
 807      textarea.style.width = iframe.style.width;
 808       textarea.style.height = iframe.style.height;
 809  
 810      // IMPORTANT: we have to allow Mozilla a short time to recognize the
 811      // new frame.  Otherwise we get a stupid exception.
 812  	function initIframe() {
 813          var doc = editor._iframe.contentWindow.document;
 814          if (!doc) {
 815              // Try again..
 816              // FIXME: don't know what else to do here.  Normally
 817              // we'll never reach this point.
 818              if (HTMLArea.is_gecko) {
 819                  setTimeout(initIframe, 100);
 820                  return false;
 821              } else {
 822                  alert("ERROR: IFRAME can't be initialized.");
 823              }
 824          }
 825          if (HTMLArea.is_gecko) {
 826              // enable editable mode for Mozilla
 827              doc.designMode = "on";
 828          }
 829          editor._doc = doc;
 830          if (!editor.config.fullPage) {
 831              doc.open();
 832              var html = "<html>\n";
 833              html += "<head>\n";
 834              if (editor.config.baseURL)
 835                  html += '<base href="' + editor.config.baseURL + '" />';
 836                  html += "<style type=\"text/css\">html,body { border: 0px; } " +
 837                      editor.config.pageStyle + "</style>\n";
 838                  if (editor.config.cssFile)
 839                      html += "<style type=\"text/css\">" + editor.config.cssFile + "</style>\n";
 840              html += "</head>\n";
 841              html += "<body>\n";
 842              html += editor._textArea.value;
 843              html += "</body>\n";
 844              html += "</html>";
 845              doc.write(html);
 846              doc.close();
 847          } else {
 848              var html = editor._textArea.value;
 849              if (html.match(HTMLArea.RE_doctype)) {
 850                  editor.setDoctype(RegExp.$1);
 851                  html = html.replace(HTMLArea.RE_doctype, "");
 852              }
 853              doc.open();
 854              doc.write(html);
 855              doc.close();
 856          }
 857  
 858          if (HTMLArea.is_ie) {
 859              // enable editable mode for IE.     For some reason this
 860              // doesn't work if done in the same place as for Gecko
 861              // (above).
 862              doc.body.contentEditable = true;
 863          }
 864  
 865          editor.focusEditor();
 866          // intercept some events; for updating the toolbar & keyboard handlers
 867          HTMLArea._addEvents
 868              (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
 869               function (event) {
 870                   return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
 871               });
 872  
 873          // check if any plugins have registered refresh handlers
 874          for (var i in editor.plugins) {
 875              try {
 876                  var plugin = editor.plugins[i].instance;
 877                  if (typeof plugin.onGenerate == "function")
 878                      plugin.onGenerate();
 879                  if (typeof plugin.onGenerateOnce == "function") {
 880                      plugin.onGenerateOnce();
 881                      plugin.onGenerateOnce = null;
 882                  }
 883              }catch(e) { /*alert(e);*/ }
 884          }
 885  
 886          setTimeout(function() {
 887              editor.updateToolbar();
 888          }, 250);
 889  
 890          if (typeof editor.onGenerate == "function")
 891              editor.onGenerate();
 892      };
 893      setTimeout(initIframe, 100);
 894  };
 895  
 896  // Switches editor mode; parameter can be "textmode" or "wysiwyg".  If no
 897  // parameter was passed this function toggles between modes.
 898  HTMLArea.prototype.setMode = function(mode) {
 899      if (typeof mode == "undefined") {
 900          mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode");
 901      }
 902      switch (mode) {
 903          case "textmode":
 904          this._textArea.value = this.getHTML();
 905          this._iframe.style.display = "none";
 906          this._textArea.style.display = "block";
 907          if (this.config.statusBar) {
 908              this._statusBar.innerHTML = HTMLArea.I18N.msg["TEXT_MODE"];
 909          }
 910          break;
 911          case "wysiwyg":
 912          if (HTMLArea.is_gecko) {
 913              // disable design mode before changing innerHTML
 914              try {
 915                  this._doc.designMode = "off";
 916              } catch(e) {};
 917          }
 918          if (!this.config.fullPage)
 919              this._doc.body.innerHTML = this.getHTML();
 920          else
 921              this.setFullHTML(this.getHTML());
 922          this._iframe.style.display = "block";
 923          this._textArea.style.display = "none";
 924          if (HTMLArea.is_gecko) {
 925              // we need to refresh that info for Moz-1.3a
 926              try {
 927                  this._doc.designMode = "on";
 928              } catch(e) {};
 929          }
 930          if (this.config.statusBar) {
 931              this._statusBar.innerHTML = '';
 932              // this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
 933              this._statusBar.appendChild(this._statusBarTree);
 934          }
 935          break;
 936          default:
 937          alert("Mode <" + mode + "> not defined!");
 938          return false;
 939      }
 940      this._editMode = mode;
 941      this.focusEditor();
 942  
 943      for (var i in this.plugins) {
 944          try {
 945              var plugin = this.plugins[i].instance;
 946              if (typeof plugin.onMode == "function") plugin.onMode(mode);
 947          } catch(e) { /*alert(e);*/ };
 948      }
 949  };
 950  
 951  HTMLArea.prototype.setFullHTML = function(html) {
 952      var save_multiline = RegExp.multiline;
 953      RegExp.multiline = true;
 954      if (html.match(HTMLArea.RE_doctype)) {
 955          this.setDoctype(RegExp.$1);
 956          html = html.replace(HTMLArea.RE_doctype, "");
 957      }
 958      RegExp.multiline = save_multiline;
 959      if (!HTMLArea.is_ie) {
 960          if (html.match(HTMLArea.RE_head))
 961              this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
 962          if (html.match(HTMLArea.RE_body))
 963              this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
 964      } else {
 965          var html_re = /<html>((.|\n)*?)<\/html>/i;
 966          html = html.replace(html_re, "$1");
 967          this._doc.open();
 968          this._doc.write(html);
 969          this._doc.close();
 970          this._doc.body.contentEditable = true;
 971          return true;
 972      }
 973  };
 974  
 975  /***************************************************
 976   *  Category: PLUGINS
 977   ***************************************************/
 978  
 979  // Create the specified plugin and register it with this HTMLArea
 980  HTMLArea.prototype.registerPlugin = function() {
 981      var plugin = arguments[0];
 982      var args = [];
 983      for (var i = 1; i < arguments.length; ++i)
 984          args.push(arguments[i]);
 985      this.registerPlugin2(plugin, args);
 986  };
 987  
 988  // this is the variant of the function above where the plugin arguments are
 989  // already packed in an array.  Externally, it should be only used in the
 990  // full-screen editor code, in order to initialize plugins with the same
 991  // parameters as in the opener window.
 992  HTMLArea.prototype.registerPlugin2 = function(plugin, args) {
 993      if (typeof plugin == "string")
 994          plugin = eval(plugin);
 995      if (typeof plugin == "undefined") {
 996          /* FIXME: This should never happen. But why does it do? */
 997          return false;
 998      }
 999      var obj = new plugin(this, args);
1000      if (obj) {
1001          var clone = {};
1002          var info = plugin._pluginInfo;
1003          for (var i in info)
1004              clone[i] = info[i];
1005          clone.instance = obj;
1006          clone.args = args;
1007          this.plugins[plugin._pluginInfo.name] = clone;
1008      } else
1009          alert("Can't register plugin " + plugin.toString() + ".");
1010  };
1011  
1012  // static function that loads the required plugin and lang file, based on the
1013  // language loaded already for HTMLArea.  You better make sure that the plugin
1014  // _has_ that language, otherwise shit might happen ;-)
1015  HTMLArea.getPluginDir = function(pluginName) {
1016      return _editor_url + "plugins/" + pluginName;
1017  };
1018  
1019  HTMLArea.loadPlugin = function(pluginName) {
1020      var dir = this.getPluginDir(pluginName);
1021      var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g,
1022                      function (str, l1, l2, l3) {
1023                          return l1 + "-" + l2.toLowerCase() + l3;
1024                      }).toLowerCase() + ".js";
1025      var plugin_file = dir + "/" + plugin;
1026      var plugin_lang = dir + "/lang/" + _editor_lang + ".js";
1027      //document.write("<script type='text/javascript' src='" + plugin_file + "'></script>");
1028      //document.write("<script type='text/javascript' src='" + plugin_lang + "'></script>");
1029      this.loadScript(plugin_file);
1030      this.loadScript(plugin_lang);
1031  };
1032  
1033  HTMLArea.loadStyle = function(style, plugin) {
1034      var url = _editor_url || '';
1035      if (typeof plugin != "undefined") {
1036          url += "plugins/" + plugin + "/";
1037      }
1038      url += style;
1039      if (/^\//.test(style))
1040          url = style;
1041      var head = document.getElementsByTagName("head")[0];
1042      var link = document.createElement("link");
1043      link.rel = "stylesheet";
1044      link.href = url;
1045      head.appendChild(link);
1046      //document.write("<style type='text/css'>@import url(" + url + ");</style>");
1047  };
1048  HTMLArea.loadStyle(typeof _editor_css == "string" ? _editor_css : "htmlarea.css");
1049  
1050  /***************************************************
1051   *  Category: EDITOR UTILITIES
1052   ***************************************************/
1053  
1054  HTMLArea.prototype.debugTree = function() {
1055      var ta = document.createElement("textarea");
1056      ta.style.width = "100%";
1057      ta.style.height = "20em";
1058      ta.value = "";
1059  	function debug(indent, str) {
1060          for (; --indent >= 0;)
1061              ta.value += " ";
1062          ta.value += str + "\n";
1063      };
1064  	function _dt(root, level) {
1065          var tag = root.tagName.toLowerCase(), i;
1066          var ns = HTMLArea.is_ie ? root.scopeName : root.prefix;
1067          debug(level, "- " + tag + " [" + ns + "]");
1068          for (i = root.firstChild; i; i = i.nextSibling)
1069              if (i.nodeType == 1)
1070                  _dt(i, level + 2);
1071      };
1072      _dt(this._doc.body, 0);
1073      document.body.appendChild(ta);
1074  };
1075  
1076  HTMLArea.getInnerText = function(el) {
1077      var txt = '', i;
1078      for (i = el.firstChild; i; i = i.nextSibling) {
1079          if (i.nodeType == 3)
1080              txt += i.data;
1081          else if (i.nodeType == 1)
1082              txt += HTMLArea.getInnerText(i);
1083      }
1084      return txt;
1085  };
1086  
1087  HTMLArea.prototype._wordClean = function() {
1088      var
1089          editor = this,
1090          stats = {
1091              empty_tags : 0,
1092              mso_class  : 0,
1093              mso_style  : 0,
1094              mso_xmlel  : 0,
1095              orig_len   : this._doc.body.innerHTML.length,
1096              T          : (new Date()).getTime()
1097          },
1098          stats_txt = {
1099              empty_tags : "Empty tags removed: ",
1100              mso_class  : "MSO class names removed: ",
1101              mso_style  : "MSO inline style removed: ",
1102              mso_xmlel  : "MSO XML elements stripped: "
1103          };
1104  	function showStats() {
1105          var txt = "HTMLArea word cleaner stats: \n\n";
1106          for (var i in stats)
1107              if (stats_txt[i])
1108                  txt += stats_txt[i] + stats[i] + "\n";
1109          txt += "\nInitial document length: " + stats.orig_len + "\n";
1110          txt += "Final document length: " + editor._doc.body.innerHTML.length + "\n";
1111          txt += "Clean-up took " + (((new Date()).getTime() - stats.T) / 1000) + " seconds";
1112          alert(txt);
1113      };
1114  	function clearClass(node) {
1115          var newc = node.className.replace(/(^|\s)mso.*?(\s|$)/ig, ' ');
1116          if (newc != node.className) {
1117              node.className = newc;
1118              if (!/\S/.test(node.className)) {
1119                  node.removeAttribute("className");
1120                  ++stats.mso_class;
1121              }
1122          }
1123      };
1124  	function clearStyle(node) {
1125           var declarations = node.style.cssText.split(/\s*;\s*/);
1126          for (var i = declarations.length; --i >= 0;)
1127              if (/^mso|^tab-stops/i.test(declarations[i]) ||
1128                  /^margin\s*:\s*0..\s+0..\s+0../i.test(declarations[i])) {
1129                  ++stats.mso_style;
1130                  declarations.splice(i, 1);
1131              }
1132          node.style.cssText = declarations.join("; ");
1133      };
1134  	function stripTag(el) {
1135          if (HTMLArea.is_ie)
1136              el.outerHTML = HTMLArea.htmlEncode(el.innerText);
1137          else {
1138              var txt = document.createTextNode(HTMLArea.getInnerText(el));
1139              el.parentNode.insertBefore(txt, el);
1140              el.parentNode.removeChild(el);
1141          }
1142          ++stats.mso_xmlel;
1143      };
1144  	function checkEmpty(el) {
1145          if (/^(a|span|b|strong|i|em|font)$/i.test(el.tagName) &&
1146              !el.firstChild) {
1147              el.parentNode.removeChild(el);
1148              ++stats.empty_tags;
1149          }
1150      };
1151  	function parseTree(root) {
1152          var tag = root.tagName.toLowerCase(), i, next;
1153          if ((HTMLArea.is_ie && root.scopeName != 'HTML') || (!HTMLArea.is_ie && /:/.test(tag))) {
1154              stripTag(root);
1155              return false;
1156          } else {
1157              clearClass(root);
1158              clearStyle(root);
1159              for (i = root.firstChild; i; i = next) {
1160                  next = i.nextSibling;
1161                  if (i.nodeType == 1 && parseTree(i))
1162                      checkEmpty(i);
1163              }
1164          }
1165          return true;
1166      };
1167      parseTree(this._doc.body);
1168      // showStats();
1169      // this.debugTree();
1170      // this.setHTML(this.getHTML());
1171      // this.setHTML(this.getInnerHTML());
1172      // this.forceRedraw();
1173      this.updateToolbar();
1174  };
1175  
1176  HTMLArea.prototype.forceRedraw = function() {
1177      this._doc.body.style.visibility = "hidden";
1178      this._doc.body.style.visibility = "visible";
1179      // this._doc.body.innerHTML = this.getInnerHTML();
1180  };
1181  
1182  // focuses the iframe window.  returns a reference to the editor document.
1183  HTMLArea.prototype.focusEditor = function() {
1184      switch (this._editMode) {
1185          // notice the try { ... } catch block to avoid some rare exceptions in FireFox
1186          // (perhaps also in other Gecko browsers). Manual focus by user is required in
1187          // case of an error. Somebody has an idea?
1188          case "wysiwyg" : try { this._iframe.contentWindow.focus() } catch (e) {} break;
1189          case "textmode": try { this._textArea.focus() } catch (e) {} break;
1190          default       : alert("ERROR: mode " + this._editMode + " is not defined");
1191      }
1192      return this._doc;
1193  };
1194  
1195  // takes a snapshot of the current text (for undo)
1196  HTMLArea.prototype._undoTakeSnapshot = function() {
1197      ++this._undoPos;
1198      if (this._undoPos >= this.config.undoSteps) {
1199          // remove the first element
1200          this._undoQueue.shift();
1201          --this._undoPos;
1202      }
1203      // use the fasted method (getInnerHTML);
1204      var take = true;
1205      var txt = this.getInnerHTML();
1206      if (this._undoPos > 0)
1207          take = (this._undoQueue[this._undoPos - 1] != txt);
1208      if (take) {
1209          this._undoQueue[this._undoPos] = txt;
1210      } else {
1211          this._undoPos--;
1212      }
1213  };
1214  
1215  HTMLArea.prototype.undo = function() {
1216      if (this._undoPos > 0) {
1217          var txt = this._undoQueue[--this._undoPos];
1218          if (txt) this.setHTML(txt);
1219          else ++this._undoPos;
1220      }
1221  };
1222  
1223  HTMLArea.prototype.redo = function() {
1224      if (this._undoPos < this._undoQueue.length - 1) {
1225          var txt = this._undoQueue[++this._undoPos];
1226          if (txt) this.setHTML(txt);
1227          else --this._undoPos;
1228      }
1229  };
1230  
1231  // updates enabled/disable/active state of the toolbar elements
1232  HTMLArea.prototype.updateToolbar = function(noStatus) {
1233      var doc = this._doc;
1234      var text = (this._editMode == "textmode");
1235      var ancestors = null;
1236      if (!text) {
1237          ancestors = this.getAllAncestors();
1238          if (this.config.statusBar && !noStatus) {
1239              this._statusBarTree.innerHTML = HTMLArea.I18N.msg["Path"] + ": "; // clear
1240              for (var i = ancestors.length; --i >= 0;) {
1241                  var el = ancestors[i];
1242                  if (!el) {
1243                      // hell knows why we get here; this
1244                      // could be a classic example of why
1245                      // it's good to check for conditions
1246                      // that are impossible to happen ;-)
1247                      continue;
1248                  }
1249                  var a = document.createElement("a");
1250                  a.href = "#";
1251                  a.el = el;
1252                  a.editor = this;
1253                  a.onclick = function() {
1254                      this.blur();
1255                      this.editor.selectNodeContents(this.el);
1256                      this.editor.updateToolbar(true);
1257                      return false;
1258                  };
1259                  a.oncontextmenu = function() {
1260                      // TODO: add context menu here
1261                      this.blur();
1262                      var info = "Inline style:\n\n";
1263                      info += this.el.style.cssText.split(/;\s*/).join(";\n");
1264                      alert(info);
1265                      return false;
1266                  };
1267                  var txt = el.tagName.toLowerCase();
1268                  a.title = el.style.cssText;
1269                  if (el.id) {
1270                      txt += "#" + el.id;
1271                  }
1272                  if (el.className) {
1273                      txt += "." + el.className;
1274                  }
1275                  a.appendChild(document.createTextNode(txt));
1276                  this._statusBarTree.appendChild(a);
1277                  if (i != 0) {
1278                      this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
1279                  }
1280              }
1281          }
1282      }
1283  
1284      for (var i in this._toolbarObjects) {
1285          var btn = this._toolbarObjects[i];
1286          var cmd = i;
1287          var inContext = true;
1288          if (btn.context && !text) {
1289              inContext = false;
1290              var context = btn.context;
1291              var attrs = [];
1292              if (/(.*)\[(.*?)\]/.test(context)) {
1293                  context = RegExp.$1;
1294                  attrs = RegExp.$2.split(",");
1295              }
1296              context = context.toLowerCase();
1297              var match = (context == "*");
1298              for (var k = 0; k < ancestors.length; ++k) {
1299                  if (!ancestors[k]) {
1300                      // the impossible really happens.
1301                      continue;
1302                  }
1303                  if (match || (ancestors[k].tagName.toLowerCase() == context)) {
1304                      inContext = true;
1305                      for (var ka = 0; ka < attrs.length; ++ka) {
1306                          if (!eval("ancestors[k]." + attrs[ka])) {
1307                              inContext = false;
1308                              break;
1309                          }
1310                      }
1311                      if (inContext) {
1312                          break;
1313                      }
1314                  }
1315              }
1316          }
1317          try {
1318          btn.state("enabled", (!text || btn.text) && inContext);
1319          } catch(e) { /*alert(e);*/ }
1320          if (typeof cmd == "function") {
1321              continue;
1322          }
1323          // look-it-up in the custom dropdown boxes
1324          var dropdown = this.config.customSelects[cmd];
1325          if ((!text || btn.text) && (typeof dropdown != "undefined")) {
1326              try { dropdown.refresh(this); } catch(e) { /*alert(e);*/ }
1327              continue;
1328          }
1329          switch (cmd) {
1330              case "fontname":
1331              case "fontsize":
1332              case "formatblock":
1333              if (!text) try {
1334                  var value = ("" + doc.queryCommandValue(cmd)).toLowerCase();
1335                  if (!value) {
1336                      btn.element.selectedIndex = 0;
1337                      break;
1338                  }
1339                  // HACK -- retrieve the config option for this
1340                  // combo box.  We rely on the fact that the
1341                  // variable in config has the same name as
1342                  // button name in the toolbar.
1343                  var options = this.config[cmd];
1344                  var k = 0;
1345                  for (var j in options) {
1346                      // FIXME: the following line is scary.
1347                      if ((j.toLowerCase() == value) ||
1348                          (options[j].substr(0, value.length).toLowerCase() == value)) {
1349                          btn.element.selectedIndex = k;
1350                          throw "ok";
1351                      }
1352                      ++k;
1353                  }
1354                  btn.element.selectedIndex = 0;
1355              } catch(e) {};
1356              break;
1357              case "textindicator":
1358              if (!text) {
1359                  try {with (btn.element.style) {
1360                      backgroundColor = HTMLArea._makeColor(
1361                          doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor"));
1362                      if (/transparent/i.test(backgroundColor)) {
1363                          // Mozilla
1364                          backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor"));
1365                      }
1366                      color = HTMLArea._makeColor(doc.queryCommandValue("forecolor"));
1367                      fontFamily = doc.queryCommandValue("fontname");
1368                      fontWeight = doc.queryCommandState("bold") ? "bold" : "normal";
1369                      fontStyle = doc.queryCommandState("italic") ? "italic" : "normal";
1370                  }} catch (e) {
1371                      // alert(e + "\n\n" + cmd);
1372                  }
1373              }
1374              break;
1375              case "htmlmode": btn.state("active", text); break;
1376              case "lefttoright":
1377              case "righttoleft":
1378              var el = this.getParentElement();
1379              while (el && !HTMLArea.isBlockElement(el))
1380                  el = el.parentNode;
1381              if (el)
1382                  btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr")));
1383              break;
1384              default:
1385              cmd = cmd.replace(/(un)?orderedlist/i, "insert$1orderedlist");
1386              try {
1387                  btn.state("active", (!text && doc.queryCommandState(cmd)));
1388              } catch (e) {}
1389          }
1390      }
1391      // take undo snapshots
1392      if (this._customUndo && !this._timerUndo) {
1393          this._undoTakeSnapshot();
1394          var editor = this;
1395          this._timerUndo = setTimeout(function() {
1396              editor._timerUndo = null;
1397          }, this.config.undoTimeout);
1398      }
1399  
1400      // check if any plugins have registered refresh handlers
1401      for (var i in this.plugins) {
1402          try {
1403              var plugin = this.plugins[i].instance;
1404              if (typeof plugin.onUpdateToolbar == "function")
1405                  plugin.onUpdateToolbar();
1406          } catch(e) { /*alert(e);*/ }
1407      }
1408  };
1409  
1410  /** Returns a node after which we can insert other nodes, in the current
1411   * selection.  The selection is removed.  It splits a text node, if needed.
1412   */
1413  HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
1414      if (!HTMLArea.is_ie) {
1415          var sel = this._getSelection();
1416          var range = this._createRange(sel);
1417          // remove the current selection
1418          sel.removeAllRanges();
1419          range.deleteContents();
1420          var node = range.startContainer;
1421          var pos = range.startOffset;
1422          switch (node.nodeType) {
1423              case 3: // Node.TEXT_NODE
1424              // we have to split it at the caret position.
1425              if (toBeInserted.nodeType == 3) {
1426                  // do optimized insertion
1427                  node.insertData(pos, toBeInserted.data);
1428                  range = this._createRange();
1429                  range.setEnd(node, pos + toBeInserted.length);
1430                  range.setStart(node, pos + toBeInserted.length);
1431                  sel.addRange(range);
1432              } else {
1433                  node = node.splitText(pos);
1434                  var selnode = toBeInserted;
1435                  if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1436                      selnode = selnode.firstChild;
1437                  }
1438                  node.parentNode.insertBefore(toBeInserted, node);
1439                  this.selectNodeContents(selnode);
1440                  this.updateToolbar();
1441              }
1442              break;
1443              case 1: // Node.ELEMENT_NODE
1444              var selnode = toBeInserted;
1445              if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1446                  selnode = selnode.firstChild;
1447              }
1448              node.insertBefore(toBeInserted, node.childNodes[pos]);
1449              this.selectNodeContents(selnode);
1450              this.updateToolbar();
1451              break;
1452          }
1453      } else {
1454          return null;    // this function not yet used for IE <FIXME>
1455      }
1456  };
1457  
1458  // Returns the deepest node that contains both endpoints of the selection.
1459  HTMLArea.prototype.getParentElement = function() {
1460      var sel = this._getSelection();
1461      var range = this._createRange(sel);
1462      if (HTMLArea.is_ie) {
1463          switch (sel.type) {
1464              case "Text":
1465              case "None":
1466              // It seems that even for selection of type "None",
1467              // there _is_ a parent element and it's value is not
1468              // only correct, but very important to us.  MSIE is
1469              // certainly the buggiest browser in the world and I
1470              // wonder, God, how can Earth stand it?
1471              return range.parentElement();
1472              case "Control":
1473              return range.item(0);
1474              default:
1475              return this._doc.body;
1476          }
1477      } else try {
1478          var p = range.commonAncestorContainer;
1479          if (!range.collapsed && range.startContainer == range.endContainer &&
1480              range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes())
1481              p = range.startContainer.childNodes[range.startOffset];
1482          /*
1483          alert(range.startContainer + ":" + range.startOffset + "\n" +
1484                range.endContainer + ":" + range.endOffset);
1485          */
1486          while (p.nodeType == 3) {
1487              p = p.parentNode;
1488          }
1489          return p;
1490      } catch (e) {
1491          return null;
1492      }
1493  };
1494  
1495  // Returns an array with all the ancestor nodes of the selection.
1496  HTMLArea.prototype.getAllAncestors = function() {
1497      var p = this.getParentElement();
1498      var a = [];
1499      while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1500          a.push(p);
1501          p = p.parentNode;
1502      }
1503      a.push(this._doc.body);
1504      return a;
1505  };
1506  
1507  // Selects the contents inside the given node
1508  HTMLArea.prototype.selectNodeContents = function(node, pos) {
1509      this.focusEditor();
1510      this.forceRedraw();
1511      var range;
1512      var collapsed = (typeof pos != "undefined");
1513      if (HTMLArea.is_ie) {
1514          range = this._doc.body.createTextRange();
1515          range.moveToElementText(node);
1516          (collapsed) && range.collapse(pos);
1517          range.select();
1518      } else {
1519          var sel = this._getSelection();
1520          range = this._doc.createRange();
1521          range.selectNodeContents(node);
1522          (collapsed) && range.collapse(pos);
1523          sel.removeAllRanges();
1524          sel.addRange(range);
1525      }
1526  };
1527  
1528  /** Call this function to insert HTML code at the current position.  It deletes
1529   * the selection, if any.
1530   */
1531  HTMLArea.prototype.insertHTML = function(html) {
1532      var sel = this._getSelection();
1533      var range = this._createRange(sel);
1534      if (HTMLArea.is_ie) {
1535          range.pasteHTML(html);
1536      } else {
1537          // construct a new document fragment with the given HTML
1538          var fragment = this._doc.createDocumentFragment();
1539          var div = this._doc.createElement("div");
1540          div.innerHTML = html;
1541          while (div.firstChild) {
1542              // the following call also removes the node from div
1543              fragment.appendChild(div.firstChild);
1544          }
1545          // this also removes the selection
1546          var node = this.insertNodeAtSelection(fragment);
1547      }
1548  };
1549  
1550  /**
1551   *  Call this function to surround the existing HTML code in the selection with
1552   *  your tags.  FIXME: buggy!  This function will be deprecated "soon".
1553   */
1554  HTMLArea.prototype.surroundHTML = function(startTag, endTag) {
1555      var html = this.getSelectedHTML();
1556      // the following also deletes the selection
1557      this.insertHTML(startTag + html + endTag);
1558  };
1559  
1560  /// Retrieve the selected block
1561  HTMLArea.prototype.getSelectedHTML = function() {
1562      var sel = this._getSelection();
1563      var range = this._createRange(sel);
1564      var existing = null;
1565      if (HTMLArea.is_ie) {
1566          existing = range.htmlText;
1567      } else {
1568          existing = HTMLArea.getHTML(range.cloneContents(), false, this);
1569      }
1570      return existing;
1571  };
1572  
1573  /// Return true if we have some selection
1574  HTMLArea.prototype.hasSelectedText = function() {
1575      // FIXME: come _on_ mishoo, you can do better than this ;-)
1576      return this.getSelectedHTML() != '';
1577  };
1578  
1579  HTMLArea.prototype._createLink = function(link) {
1580      var editor = this;
1581      var outparam = null;
1582      if (typeof link == "undefined") {
1583          link = this.getParentElement();
1584          if (link) {
1585              if (/^img$/i.test(link.tagName))
1586                  link = link.parentNode;
1587              if (!/^a$/i.test(link.tagName))
1588                  link = null;
1589          }
1590      }
1591      if (!link) {
1592          var sel = editor._getSelection();
1593          var range = editor._createRange(sel);
1594          var compare = 0;
1595          if (HTMLArea.is_ie) {
1596              compare = range.compareEndPoints("StartToEnd", range);
1597          } else {
1598              compare = range.compareBoundaryPoints(range.START_TO_END, range);
1599          }
1600          if (compare == 0) {
1601              alert("You need to select some text before creating a link");
1602              return;
1603          }
1604          outparam = {
1605              f_href : '',
1606              f_title : '',
1607              f_target : '',
1608              f_usetarget : editor.config.makeLinkShowsTarget
1609          };
1610      } else
1611          outparam = {
1612              f_href   : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"),
1613              f_title  : link.title,
1614              f_target : link.target,
1615              f_usetarget : editor.config.makeLinkShowsTarget
1616          };
1617      this._popupDialog("link.html", function(param) {
1618          if (!param)
1619              return false;
1620          var a = link;
1621          if (!a) try {
1622              editor._doc.execCommand("createlink", false, param.f_href);
1623              a = editor.getParentElement();
1624              var sel = editor._getSelection();
1625              var range = editor._createRange(sel);
1626              if (!HTMLArea.is_ie) {
1627                  a = range.startContainer;
1628                  if (!/^a$/i.test(a.tagName)) {
1629                      a = a.nextSibling;
1630                      if (a == null)
1631                          a = range.startContainer.parentNode;
1632                  }
1633              }
1634          } catch(e) {}
1635          else {
1636              var href = param.f_href.trim();
1637              editor.selectNodeContents(a);
1638              if (href == "") {
1639                  editor._doc.execCommand("unlink", false, null);
1640                  editor.updateToolbar();
1641                  return false;
1642              }
1643              else {
1644                  a.href = href;
1645              }
1646          }
1647          if (!(a && /^a$/i.test(a.tagName)))
1648              return false;
1649          a.target = param.f_target.trim();
1650          a.title = param.f_title.trim();
1651          editor.selectNodeContents(a);
1652          editor.updateToolbar();
1653      }, outparam);
1654  };
1655  
1656  // Called when the user clicks on "InsertImage" button.  If an image is already
1657  // there, it will just modify it's properties.
1658  HTMLArea.prototype._insertImage = function(image) {
1659      var editor = this;    // for nested functions
1660      var outparam = null;
1661      if (typeof image == "undefined") {
1662          image = this.getParentElement();
1663          if (image && !/^img$/i.test(image.tagName))
1664              image = null;
1665      }
1666      if (image) outparam = {
1667          f_base   : editor.config.baseURL,
1668          f_url    : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"),
1669          f_alt    : image.alt,
1670          f_border : image.border,
1671          f_align  : image.align,
1672          f_vert   : image.vspace,
1673          f_horiz  : image.hspace
1674      };
1675      this._popupDialog("insert_image.html", function(param) {
1676          if (!param) {    // user must have pressed Cancel
1677              return false;
1678          }
1679          var img = image;
1680          if (!img) {
1681              var sel = editor._getSelection();
1682              var range = editor._createRange(sel);
1683              editor._doc.execCommand("insertimage", false, param.f_url);
1684              if (HTMLArea.is_ie) {
1685                  img = range.parentElement();
1686                  // wonder if this works...
1687                  if (img.tagName.toLowerCase() != "img") {
1688                      img = img.previousSibling;
1689                  }
1690              } else {
1691                  img = range.startContainer.previousSibling;
1692              }
1693          } else {
1694              img.src = param.f_url;
1695          }
1696  
1697          for (var field in param) {
1698              var value = param[field];
1699              switch (field) {
1700                  case "f_alt"    : img.alt     = value; break;
1701                  case "f_border" : img.border = parseInt(value || "0"); break;
1702                  case "f_align"  : img.align     = value; break;
1703                  case "f_vert"   : img.vspace = parseInt(value || "0"); break;
1704                  case "f_horiz"  : img.hspace = parseInt(value || "0"); break;
1705              }
1706          }
1707      }, outparam);
1708  };
1709  
1710  // Called when the user clicks the Insert Table button
1711  HTMLArea.prototype._insertTable = function() {
1712      var sel = this._getSelection();
1713      var range = this._createRange(sel);
1714      var editor = this;    // for nested functions
1715      this._popupDialog("insert_table.html", function(param) {
1716          if (!param) {    // user must have pressed Cancel
1717              return false;
1718          }
1719          var doc = editor._doc;
1720          // create the table element
1721          var table = doc.createElement("table");
1722          // assign the given arguments
1723  
1724          for (var field in param) {
1725              var value = param[field];
1726              if (!value) {
1727                  continue;
1728              }
1729              switch (field) {
1730                  case "f_width"   : table.style.width = value + param["f_unit"]; break;
1731                  case "f_align"   : table.align     = value; break;
1732                  case "f_border"  : table.border     = parseInt(value); break;
1733                  case "f_spacing" : table.cellSpacing = parseInt(value); break;
1734                  case "f_padding" : table.cellPadding = parseInt(value); break;
1735              }
1736          }
1737          var cellwidth = 0;
1738          if (param.f_fixed)
1739              cellwidth = Math.floor(100 / parseInt(param.f_cols));
1740          var tbody = doc.createElement("tbody");
1741          table.appendChild(tbody);
1742          for (var i = 0; i < param["f_rows"]; ++i) {
1743              var tr = doc.createElement("tr");
1744              tbody.appendChild(tr);
1745              for (var j = 0; j < param["f_cols"]; ++j) {
1746                  var td = doc.createElement("td");
1747                  if (cellwidth)
1748                      td.style.width = cellwidth + "%";
1749                  tr.appendChild(td);
1750                  // Mozilla likes to see something inside the cell.
1751                  (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br"));
1752              }
1753          }
1754          if (HTMLArea.is_ie) {
1755              range.pasteHTML(table.outerHTML);
1756          } else {
1757              // insert the table
1758              editor.insertNodeAtSelection(table);
1759          }
1760          return true;
1761      }, null);
1762  };
1763  
1764  /***************************************************
1765   *  Category: EVENT HANDLERS
1766   ***************************************************/
1767  
1768  // el is reference to the SELECT object
1769  // txt is the name of the select field, as in config.toolbar
1770  HTMLArea.prototype._comboSelected = function(el, txt) {
1771      this.focusEditor();
1772      var value = el.options[el.selectedIndex].value;
1773      switch (txt) {
1774          case "fontname":
1775          case "fontsize": this.execCommand(txt, false, value); break;
1776          case "formatblock":
1777          (HTMLArea.is_ie) && (value = "<" + value + ">");
1778          this.execCommand(txt, false, value);
1779          break;
1780          default:
1781          // try to look it up in the registered dropdowns
1782          var dropdown = this.config.customSelects[txt];
1783          if (typeof dropdown != "undefined") {
1784              dropdown.action(this);
1785          } else {
1786              alert("FIXME: combo box " + txt + " not implemented");
1787          }
1788      }
1789  };
1790  
1791  // the execCommand function (intercepts some commands and replaces them with
1792  // our own implementation)
1793  HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
1794      var editor = this;    // for nested functions
1795      this.focusEditor();
1796      cmdID = cmdID.toLowerCase();
1797      if (HTMLArea.is_gecko) try { this._doc.execCommand('useCSS', false, true); } catch (e) {};
1798      switch (cmdID) {
1799          case "htmlmode" : this.setMode(); break;
1800          case "hilitecolor":
1801          (HTMLArea.is_ie) && (cmdID = "backcolor");
1802          case "forecolor":
1803          this._popupDialog("select_color.html", function(color) {
1804              if (color) { // selection not canceled
1805                  editor._doc.execCommand(cmdID, false, "#" + color);
1806              }
1807          }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID)));
1808          break;
1809          case "createlink":
1810          this._createLink();
1811          break;
1812          case "popupeditor":
1813          // this object will be passed to the newly opened window
1814          HTMLArea._object = this;
1815          if (HTMLArea.is_ie) {
1816              //if (confirm(HTMLArea.I18N.msg["IE-sucks-full-screen"]))
1817              {
1818                  window.open(this.popupURL("fullscreen.html"), "ha_fullscreen",
1819                          "toolbar=no,location=no,directories=no,status=no,menubar=no," +
1820                          "scrollbars=no,resizable=yes,width=640,height=480");
1821              }
1822          } else {
1823              window.open(this.popupURL("fullscreen.html"), "ha_fullscreen",
1824                      "toolbar=no,menubar=no,personalbar=no,width=640,height=480," +
1825                      "scrollbars=no,resizable=yes");
1826          }
1827          break;
1828          case "undo":
1829          case "redo":
1830          if (this._customUndo)
1831              this[cmdID]();
1832          else
1833              this._doc.execCommand(cmdID, UI, param);
1834          break;
1835          case "inserttable": this._insertTable(); break;
1836          case "insertimage": this._insertImage(); break;
1837          case "about"    : this._popupDialog("about.html", null, this); break;
1838          case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break;
1839  
1840          case "killword": this._wordClean(); break;
1841  
1842          case "cut":
1843          case "copy":
1844          case "paste":
1845          try {
1846              this._doc.execCommand(cmdID, UI, param);
1847              if (this.config.killWordOnPaste)
1848                  this._wordClean();
1849          } catch (e) {
1850              if (HTMLArea.is_gecko) {
1851                  if (typeof HTMLArea.I18N.msg["Moz-Clipboard"] == "undefined") {
1852                      HTMLArea.I18N.msg["Moz-Clipboard"] =
1853                          "Unprivileged scripts cannot access Cut/Copy/Paste programatically " +
1854                          "for security reasons.  Click OK to see a technical note at mozilla.org " +
1855                          "which shows you how to allow a script to access the clipboard.\n\n" +
1856                          "[FIXME: please translate this message in your language definition file.]";
1857                  }
1858                  if (confirm(HTMLArea.I18N.msg["Moz-Clipboard"]))
1859                      window.open("http://mozilla.org/editor/midasdemo/securityprefs.html");
1860              }
1861          }
1862          break;
1863          case "lefttoright":
1864          case "righttoleft":
1865          var dir = (cmdID == "righttoleft") ? "rtl" : "ltr";
1866          var el = this.getParentElement();
1867          while (el && !HTMLArea.isBlockElement(el))
1868              el = el.parentNode;
1869          if (el) {
1870              if (el.style.direction == dir)
1871                  el.style.direction = "";
1872              else
1873                  el.style.direction = dir;
1874          }
1875          break;
1876          default: try { this._doc.execCommand(cmdID, UI, param); }
1877          catch(e) { if (this.config.debug) { alert(e + "\n\nby execCommand(" + cmdID + ");"); } }
1878      }
1879      this.updateToolbar();
1880      return false;
1881  };
1882  
1883  /** A generic event handler for things that happen in the IFRAME's document.
1884   * This function also handles key bindings. */
1885  HTMLArea.prototype._editorEvent = function(ev) {
1886      var editor = this;
1887      var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (!HTMLArea.is_ie && ev.type == "keypress");
1888  
1889      if (keyEvent)
1890          for (var i in editor.plugins) {
1891              try {
1892                  var plugin = editor.plugins[i].instance;
1893                  if (typeof plugin.onKeyPress == "function")
1894                      if (plugin.onKeyPress(ev))
1895                          return false;
1896              } catch(e) { /*alert(e);*/ }
1897          }
1898      if (keyEvent && ev.ctrlKey && !ev.altKey) {
1899          var sel = null;
1900          var range = null;
1901          var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
1902          var cmd = null;
1903          var value = null;
1904          switch (key) {
1905              case 'a':
1906              if (!HTMLArea.is_ie) {
1907                  // KEY select all
1908                  sel = this._getSelection();
1909                  sel.removeAllRanges();
1910                  range = this._createRange();
1911                  range.selectNodeContents(this._doc.body);
1912                  sel.addRange(range);
1913                  HTMLArea._stopEvent(ev);
1914              }
1915              break;
1916  
1917              // simple key commands follow
1918  
1919              case 'b': cmd = "bold"; break;
1920              case 'i': cmd = "italic"; break;
1921              case 'u': cmd = "underline"; break;
1922              case 's': cmd = "strikethrough"; break;
1923              case 'l': cmd = "justifyleft"; break;
1924              case 'e': cmd = "justifycenter"; break;
1925              case 'r': cmd = "justifyright"; break;
1926              case 'j': cmd = "justifyfull"; break;
1927              case 'z': cmd = "undo"; break;
1928              case 'y': cmd = "redo"; break;
1929              case 'v': if (HTMLArea.is_ie || editor.config.htmlareaPaste) { cmd = "paste"; } break;
1930              case 'n': cmd = "formatblock"; value = HTMLArea.is_ie ? "<p>" : "p"; break;
1931  
1932              case '0': cmd = "killword"; break;
1933  
1934              // headings
1935              case '1':
1936              case '2':
1937              case '3':
1938              case '4':
1939              case '5':
1940              case '6':
1941              cmd = "formatblock";
1942              value = "h" + key;
1943              if (HTMLArea.is_ie)
1944                  value = "<" + value + ">";
1945              break;
1946          }
1947          if (cmd) {
1948              // execute simple command
1949              this.execCommand(cmd, false, value);
1950              HTMLArea._stopEvent(ev);
1951          }
1952      }
1953      else if (keyEvent) {
1954          // other keys here
1955          switch (ev.keyCode) {
1956              case 13: // KEY enter
1957              if (HTMLArea.is_gecko && !ev.shiftKey) {
1958                  this.dom_checkInsertP();
1959                  HTMLArea._stopEvent(ev);
1960              }
1961              break;
1962              case 8: // KEY backspace
1963              case 46: // KEY delete
1964              if (HTMLArea.is_gecko && !ev.shiftKey) {
1965                  if (this.dom_checkBackspace())
1966                      HTMLArea._stopEvent(ev);
1967              } else if (HTMLArea.is_ie) {
1968                  if (this.ie_checkBackspace())
1969                      HTMLArea._stopEvent(ev);
1970              }
1971              break;
1972          }
1973      }
1974  
1975      // update the toolbar state after some time
1976      if (editor._timerToolbar) {
1977          clearTimeout(editor._timerToolbar);
1978      }
1979      editor._timerToolbar = setTimeout(function() {
1980          editor.updateToolbar();
1981          editor._timerToolbar = null;
1982      }, 50);
1983  };
1984  
1985  HTMLArea.prototype.convertNode = function(el, newTagName) {
1986      var newel = this._doc.createElement(newTagName);
1987      while (el.firstChild)
1988          newel.appendChild(el.firstChild);
1989      return newel;
1990  };
1991  
1992  HTMLArea.prototype.ie_checkBackspace = function() {
1993      var sel = this._getSelection();
1994      var range = this._createRange(sel);
1995      var r2 = range.duplicate();
1996      r2.moveStart("character", -1);
1997      var a = r2.parentElement();
1998      if (a != range.parentElement() &&
1999          /^a$/i.test(a.tagName)) {
2000          r2.collapse(true);
2001          r2.moveEnd("character", 1);
2002          r2.pasteHTML('');
2003          r2.select();
2004          return true;
2005      }
2006  };
2007  
2008  HTMLArea.prototype.dom_checkBackspace = function() {
2009      var self = this;
2010      setTimeout(function() {
2011          var sel = self._getSelection();
2012          var range = self._createRange(sel);
2013          var SC = range.startContainer;
2014          var SO = range.startOffset;
2015          var EC = range.endContainer;
2016          var EO = range.endOffset;
2017          var newr = SC.nextSibling;
2018          if (SC.nodeType == 3)
2019              SC = SC.parentNode;
2020          if (!/\S/.test(SC.tagName)) {
2021              var p = document.createElement("p");
2022              while (SC.firstChild)
2023                  p.appendChild(SC.firstChild);
2024              SC.parentNode.insertBefore(p, SC);
2025              SC.parentNode.removeChild(SC);
2026              var r = range.cloneRange();
2027              r.setStartBefore(newr);
2028              r.setEndAfter(newr);
2029              r.extractContents();
2030              sel.removeAllRanges();
2031              sel.addRange(r);
2032          }
2033      }, 10);
2034  };
2035  
2036  HTMLArea.prototype.dom_checkInsertP = function() {
2037      var p = this.getAllAncestors();
2038      var block = null;
2039      var body = this._doc.body;
2040      for (var i = 0; i < p.length; ++i)
2041          if (HTMLArea.isBlockElement(p[i]) && !/body|html/i.test(p[i].tagName)) {
2042              block = p[i];
2043              break;
2044          }
2045      var sel = this._getSelection();
2046      var range = this._createRange(sel);
2047      if (!range.collapsed)
2048          range.deleteContents();
2049      sel.removeAllRanges();
2050      var SC = range.startContainer;
2051      var SO = range.startOffset;
2052      var EC = range.endContainer;
2053      var EO = range.endOffset;
2054      //alert(SC.tagName + ":" + SO + " => " + EC.tagName + ":" + EO);
2055      if (SC == EC && SC == body && !SO && !EO) {
2056          p = this._doc.createTextNode(" ");
2057          body.insertBefore(p, body.firstChild);
2058          range.selectNodeContents(p);
2059          SC = range.startContainer;
2060          SO = range.startOffset;
2061          EC = range.endContainer;
2062          EO = range.endOffset;
2063      }
2064      if (!block) {
2065          var r2 = range.cloneRange();
2066          r2.setStartBefore(SC);
2067          r2.setEndAfter(EC);
2068          r2.surroundContents(block = this._doc.createElement("p"));
2069          range.setEndAfter(block);
2070          range.setStart(block.firstChild, SO);
2071      } else range.setEndAfter(block);
2072      var df = range.extractContents();
2073      if (!/\S/.test(block.innerHTML))
2074          block.innerHTML = "<br />";
2075      p = df.firstChild;
2076      if (!/\S/.test(p.innerHTML))
2077          p.innerHTML = "<br />";
2078      if (/^\s*<br\s*\/?>\s*$/.test(p.innerHTML) && /^h[1-6]$/i.test(p.tagName)) {
2079          df.appendChild(this.convertNode(p, "p"));
2080          df.removeChild(p);
2081      }
2082      block.parentNode.insertBefore(df, block.nextSibling);
2083      range.selectNodeContents(block.nextSibling);
2084      range.collapse(true);
2085      sel.addRange(range);
2086      this.forceRedraw();
2087  };
2088  
2089  // retrieve the HTML
2090  HTMLArea.prototype.getHTML = function() {
2091      switch (this._editMode) {
2092          case "wysiwyg"  :
2093          if (!this.config.fullPage) {
2094              return HTMLArea.getHTML(this._doc.body, false, this);
2095          } else
2096              return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this);
2097          case "textmode" : return this._textArea.value;
2098          default        : alert("Mode <" + mode + "> not defined!");
2099      }
2100      return false;
2101  };
2102  
2103  // retrieve the HTML (fastest version, but uses innerHTML)
2104  HTMLArea.prototype.getInnerHTML = function() {
2105      switch (this._editMode) {
2106          case "wysiwyg"  :
2107          if (!this.config.fullPage)
2108              return this._doc.body.innerHTML;
2109          else
2110              return this.doctype + "\n" + this._doc.documentElement.innerHTML;
2111          case "textmode" : return this._textArea.value;
2112          default        : alert("Mode <" + mode + "> not defined!");
2113      }
2114      return false;
2115  };
2116  
2117  // completely change the HTML inside
2118  HTMLArea.prototype.setHTML = function(html) {
2119      switch (this._editMode) {
2120          case "wysiwyg"  :
2121          if (!this.config.fullPage)
2122              this._doc.body.innerHTML = html;
2123          else
2124              // this._doc.documentElement.innerHTML = html;
2125              this._doc.body.innerHTML = html;
2126          break;
2127          case "textmode" : this._textArea.value = html; break;
2128          default        : alert("Mode <" + mode + "> not defined!");
2129      }
2130      return false;
2131  };
2132  
2133  // sets the given doctype (useful when config.fullPage is true)
2134  HTMLArea.prototype.setDoctype = function(doctype) {
2135      this.doctype = doctype;
2136  };
2137  
2138  /***************************************************
2139   *  Category: UTILITY FUNCTIONS
2140   ***************************************************/
2141  
2142  // variable used to pass the object to the popup editor window.
2143  HTMLArea._object = null;
2144  
2145  // function that returns a clone of the given object
2146  HTMLArea.cloneObject = function(obj) {
2147      if (!obj) return null;
2148      var newObj = new Object;
2149  
2150      // check for array objects
2151      if (obj.constructor.toString().indexOf("function Array(") == 1) {
2152          newObj = obj.constructor();
2153      }
2154  
2155      // check for function objects (as usual, IE is fucked up)
2156      if (obj.constructor.toString().indexOf("function Function(") == 1) {
2157          newObj = obj; // just copy reference to it
2158      } else for (var n in obj) {
2159          var node = obj[n];
2160          if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); }
2161          else                         { newObj[n] = node; }
2162      }
2163  
2164      return newObj;
2165  };
2166  
2167  // FIXME!!! this should return false for IE < 5.5
2168  HTMLArea.checkSupportedBrowser = function() {
2169      if (HTMLArea.is_gecko) {
2170          if (navigator.productSub < 20021201) {
2171              alert("You need at least Mozilla-1.3 Alpha.\n" +
2172                    "Sorry, your Gecko is not supported.");
2173              return false;
2174          }
2175          if (navigator.productSub < 20030210) {
2176              alert("Mozilla < 1.3 Beta is not supported!\n" +
2177                    "I'll try, though, but it might not work.");
2178          }
2179      }
2180      return HTMLArea.is_gecko || HTMLArea.is_ie;
2181  };
2182  
2183  // selection & ranges
2184  
2185  // returns the current selection object
2186  HTMLArea.prototype._getSelection = function() {
2187      if (HTMLArea.is_ie) {
2188          return this._doc.selection;
2189      } else {
2190          return this._iframe.contentWindow.getSelection();
2191      }
2192  };
2193  
2194  // returns a range for the current selection
2195  HTMLArea.prototype._createRange = function(sel) {
2196      if (HTMLArea.is_ie) {
2197          return sel.createRange();
2198      } else {
2199          this.focusEditor();
2200          if (typeof sel != "undefined") {
2201              try {
2202                  return sel.getRangeAt(0);
2203              } catch(e) {
2204                  return this._doc.createRange();
2205              }
2206          } else {
2207              return this._doc.createRange();
2208          }
2209      }
2210  };
2211  
2212  // event handling
2213  
2214  HTMLArea._addEvent = function(el, evname, func) {
2215      if (HTMLArea.is_ie) {
2216          el.attachEvent("on" + evname, func);
2217      } else {
2218          el.addEventListener(evname, func, true);
2219      }
2220  };
2221  
2222  HTMLArea._addEvents = function(el, evs, func) {
2223      for (var i = evs.length; --i >= 0;) {
2224          HTMLArea._addEvent(el, evs[i], func);
2225      }
2226  };
2227  
2228  HTMLArea._removeEvent = function(el, evname, func) {
2229      if (HTMLArea.is_ie) {
2230          el.detachEvent("on" + evname, func);
2231      } else {
2232          el.removeEventListener(evname, func, true);
2233      }
2234  };
2235  
2236  HTMLArea._removeEvents = function(el, evs, func) {
2237      for (var i = evs.length; --i >= 0;) {
2238          HTMLArea._removeEvent(el, evs[i], func);
2239      }
2240  };
2241  
2242  HTMLArea._stopEvent = function(ev) {
2243      if (HTMLArea.is_ie) {
2244          ev.cancelBubble = true;
2245          ev.returnValue = false;
2246      } else {
2247          ev.preventDefault();
2248          ev.stopPropagation();
2249      }
2250  };
2251  
2252  HTMLArea._removeClass = function(el, className) {
2253      if (!(el && el.className)) {
2254          return;
2255      }
2256      var cls = el.className.split(" ");
2257      var ar = new Array();
2258      for (var i = cls.length; i > 0;) {
2259          if (cls[--i] != className) {
2260              ar[ar.length] = cls[i];
2261          }
2262      }
2263      el.className = ar.join(" ");
2264  };
2265  
2266  HTMLArea._addClass = function(el, className) {
2267      // remove the class first, if already there
2268      HTMLArea._removeClass(el, className);
2269      el.className += " " + className;
2270  };
2271  
2272  HTMLArea._hasClass = function(el, className) {
2273      if (!(el && el.className)) {
2274          return false;
2275      }
2276      var cls = el.className.split(" ");
2277      for (var i = cls.length; i > 0;) {
2278          if (cls[--i] == className) {
2279              return true;
2280          }
2281      }
2282      return false;
2283  };
2284  
2285  HTMLArea._blockTags = " body form textarea fieldset ul ol dl li div " +
2286  "p h1 h2 h3 h4 h5 h6 quote pre table thead " +
2287  "tbody tfoot tr td iframe address ";
2288  HTMLArea.isBlockElement = function(el) {
2289      return el && el.nodeType == 1 && (HTMLArea._blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
2290  };
2291  
2292  HTMLArea._closingTags = " head script style div span tr td tbody table em strong b i code cite dfn abbr acronym font a title ";
2293  HTMLArea.needsClosingTag = function(el) {
2294      return el && el.nodeType == 1 && (HTMLArea._closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
2295  };
2296  
2297  // performs HTML encoding of some given string
2298  HTMLArea.htmlEncode = function(str) {
2299      // we don't need regexp for that, but.. so be it for now.
2300      str = str.replace(/&/ig, "&amp;");
2301      str = str.replace(/</ig, "&lt;");
2302      str = str.replace(/>/ig, "&gt;");
2303      str = str.replace(/\x22/ig, "&quot;");
2304      // \x22 means '"' -- we use hex reprezentation so that we don't disturb
2305      // JS compressors (well, at least mine fails.. ;)
2306      return str;
2307  };
2308  
2309  // Retrieves the HTML code from the given node.     This is a replacement for
2310  // getting innerHTML, using standard DOM calls.
2311  // Wrapper catch a Mozilla-Exception with non well formed html source code
2312  HTMLArea.getHTML = function(root, outputRoot, editor){
2313      try{
2314          return HTMLArea.getHTMLWrapper(root,outputRoot,editor);
2315      }
2316      catch(e){
2317          alert('Your Document is not well formed. Check JavaScript console for details.');
2318          return editor._iframe.contentWindow.document.body.innerHTML;
2319      }
2320  }
2321  
2322  HTMLArea.getHTMLWrapper = function(root, outputRoot, editor) {
2323      var html = "";
2324      switch (root.nodeType) {
2325          case 1: // Node.ELEMENT_NODE
2326          case 11: // Node.DOCUMENT_FRAGMENT_NODE
2327          var closed;
2328          var i;
2329          var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
2330          if (root_tag == 'br' && !root.nextSibling)
2331              break;
2332          if (outputRoot)
2333              outputRoot = !(editor.config.htmlRemoveTags && editor.config.htmlRemoveTags.test(root_tag));
2334          if (HTMLArea.is_ie && root_tag == "head") {
2335              if (outputRoot)
2336                  html += "<head>";
2337              // lowercasize
2338              var save_multiline = RegExp.multiline;
2339              RegExp.multiline = true;
2340              var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
2341                  return p1 + p2.toLowerCase();
2342              });
2343              RegExp.multiline = save_multiline;
2344              html += txt;
2345              if (outputRoot)
2346                  html += "</head>";
2347              break;
2348          } else if (outputRoot) {
2349              closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root)));
2350              html = "<" + root.tagName.toLowerCase();
2351              var attrs = root.attributes;
2352              for (i = 0; i < attrs.length; ++i) {
2353                  var a = attrs.item(i);
2354                  if (!a.specified) {
2355                      continue;
2356                  }
2357                  var name = a.nodeName.toLowerCase();
2358                  if (/_moz_editor_bogus_node/.test(name)) {
2359                      html = "";
2360                      break;
2361                  }
2362                  if (/_moz|contenteditable|_msh/.test(name)) {
2363                      // avoid certain attributes
2364                      continue;
2365                  }
2366                  var value;
2367                  if (name != "style") {
2368                      // IE5.5 reports 25 when cellSpacing is
2369                      // 1; other values might be doomed too.
2370                      // For this reason we extract the
2371                      // values directly from the root node.
2372                      // I'm starting to HATE JavaScript
2373                      // development.  Browser differences
2374                      // suck.
2375                      //
2376                      // Using Gecko the values of href and src are converted to absolute links
2377                      // unless we get them using nodeValue()
2378                      if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src" && !/^on/.test(name)) {
2379                          value = root[a.nodeName];
2380                      } else {
2381                          value = a.nodeValue;
2382                          // IE seems not willing to return the original values - it converts to absolute
2383                          // links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href")
2384                          // So we have to strip the baseurl manually :-/
2385                          if (HTMLArea.is_ie && (name == "href" || name == "src")) {
2386                              value = editor.stripBaseURL(value);
2387                          }
2388                      }
2389                  } else { // IE fails to put style in attributes list
2390                      // FIXME: cssText reported by IE is UPPERCASE
2391                      value = root.style.cssText;
2392                  }
2393                  if (/(_moz|^$)/.test(value)) {
2394                      // Mozilla reports some special tags
2395                      // here; we don't need them.
2396                      continue;
2397                  }
2398                  html += " " + name + '="' + value + '"';
2399              }
2400              if (html != "") {
2401                  html += closed ? " />" : ">";
2402              }
2403          }
2404          for (i = root.firstChild; i; i = i.nextSibling) {
2405              html += HTMLArea.getHTMLWrapper(i, true, editor);
2406          }
2407          if (outputRoot && !closed) {
2408              html += "</" + root.tagName.toLowerCase() + ">";
2409          }
2410          break;
2411          case 3: // Node.TEXT_NODE
2412          // If a text node is alone in an element and all spaces, replace it with an non breaking one
2413          // This partially undoes the damage done by moz, which translates '&nbsp;'s into spaces in the data element
2414          /* if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) ) html = '&nbsp;';
2415             else */
2416          html = /^script|style$/i.test(root.parentNode.tagName) ? root.data : HTMLArea.htmlEncode(root.data);
2417          break;
2418          case 4: // Node.CDATA_SECTION_NODE
2419          // FIXME: it seems we never get here, but I believe we should..
2420          //        maybe a browser problem?--CDATA sections are converted to plain text nodes and normalized
2421          // CDATA sections should go "as is" without further encoding
2422          html = "<![CDATA[" + root.data + "]]>";
2423          break;
2424          case 8: // Node.COMMENT_NODE
2425          html = "<!--" + root.data + "-->";
2426          break;        // skip comments, for now.
2427      }
2428      return html;
2429  };
2430  
2431  HTMLArea.prototype.stripBaseURL = function(string) {
2432      var baseurl = this.config.baseURL;
2433      // strip to last directory in case baseurl points to a file
2434      baseurl = baseurl.replace(/[^\/]+$/, '');
2435      _baseurl = baseurl;
2436      var basere = new RegExp(baseurl);
2437      string = string.replace(basere, this.config.baseURL);
2438  
2439      // strip host-part of URL which is added by MSIE to links relative to server root
2440      baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
2441      basere = new RegExp(baseurl);
2442      return string.replace(basere, baseurl);
2443  };
2444  
2445  String.prototype.trim = function() {
2446      return this.replace(/^\s+/, '').replace(/\s+$/, '');
2447  };
2448  
2449  // creates a rgb-style color from a number
2450  HTMLArea._makeColor = function(v) {
2451      if (typeof v != "number") {
2452          // already in rgb (hopefully); IE doesn't get here.
2453          return v;
2454      }
2455      // IE sends number; convert to rgb.
2456      var r = v & 0xFF;
2457      var g = (v >> 8) & 0xFF;
2458      var b = (v >> 16) & 0xFF;
2459      return "rgb(" + r + "," + g + "," + b + ")";
2460  };
2461  
2462  // returns hexadecimal color representation from a number or a rgb-style color.
2463  HTMLArea._colorToRgb = function(v) {
2464      if (!v)
2465          return '';
2466  
2467      // returns the hex representation of one byte (2 digits)
2468  	function hex(d) {
2469          return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
2470      };
2471  
2472      if (typeof v == "number") {
2473          // we're talking to IE here
2474          var r = v & 0xFF;
2475          var g = (v >> 8) & 0xFF;
2476          var b = (v >> 16) & 0xFF;
2477          return "#" + hex(r) + hex(g) + hex(b);
2478      }
2479  
2480      if (v.substr(0, 3) == "rgb") {
2481          // in rgb(...) form -- Mozilla
2482          var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2483          if (v.match(re)) {
2484              var r = parseInt(RegExp.$1);
2485              var g = parseInt(RegExp.$2);
2486              var b = parseInt(RegExp.$3);
2487              return "#" + hex(r) + hex(g) + hex(b);
2488          }
2489          // doesn't match RE?!  maybe uses percentages or float numbers
2490          // -- FIXME: not yet implemented.
2491          return null;
2492      }
2493  
2494      if (v.substr(0, 1) == "#") {
2495          // already hex rgb (hopefully :D )
2496          return v;
2497      }
2498  
2499      // if everything else fails ;)
2500      return null;
2501  };
2502  
2503  // modal dialogs for Mozilla (for IE we're using the showModalDialog() call).
2504  
2505  // receives an URL to the popup dialog and a function that receives one value;
2506  // this function will get called after the dialog is closed, with the return
2507  // value of the dialog.
2508  HTMLArea.prototype._popupDialog = function(url, action, init) {
2509      Dialog(this.popupURL(url), action, init);
2510  };
2511  
2512  // paths
2513  
2514  HTMLArea.prototype.imgURL = function(file, plugin) {
2515      if (typeof plugin == "undefined")
2516          return _editor_url + file;
2517      else
2518          return _editor_url + "plugins/" + plugin + "/img/" + file;
2519  };
2520  
2521  HTMLArea.prototype.popupURL = function(file) {
2522      var url = "";
2523      if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
2524          var plugin = RegExp.$1;
2525          var popup = RegExp.$2;
2526          if (!/\.html$/.test(popup))
2527              popup += ".html";
2528          url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
2529      } else
2530          url = _editor_url + this.config.popupURL + file;
2531      return url;
2532  };
2533  
2534  /**
2535   * FIX: Internet Explorer returns an item having the _name_ equal to the given
2536   * id, even if it's not having any id.  This way it can return a different form
2537   * field even if it's not a textarea.  This workarounds the problem by
2538   * specifically looking to search only elements having a certain tag name.
2539   */
2540  HTMLArea.getElementById = function(tag, id) {
2541      var el, i, objs = document.getElementsByTagName(tag);
2542      for (i = objs.length; --i >= 0 && (el = objs[i]);)
2543          if (el.id == id)
2544              return el;
2545      return null;
2546  };
2547  
2548  
2549  
2550  // EOF
2551  // Local variables: //
2552  // c-basic-offset:8 //
2553  // indent-tabs-mode:t //
2554  // End: //


Généré le : Sat Nov 24 09:00:37 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics