[ Index ]
 

Code source de SPIP Agora 1.4

Accédez au Source d'autres logiciels libresSoutenez Angelica Josefina !

title

Body

[fermer]

/Agora1-4/ecrire/include/htmlarea/ -> htmlarea.js (source)

   1  // htmlArea v3.0 - Copyright (c) 2002-2004 interactivetools.com, inc.

   2  // This copyright notice MUST stay intact for use (see license.txt).

   3  //

   4  // Portions (c) dynarch.com, 2003-2004

   5  //

   6  // A free WYSIWYG editor replacement for <textarea> fields.

   7  // For full source code and docs, visit http://www.interactivetools.com/

   8  //

   9  // Version 3.0 developed by Mihai Bazon.

  10  //   http://dynarch.com/mishoo

  11  //

  12  // $Id$

  13  
  14  if (typeof _editor_url == "string") {
  15      // Leave exactly one backslash at the end of _editor_url

  16      _editor_url = _editor_url.replace(/\x2f*$/, '/');
  17  } else {
  18      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.");
  19      _editor_url = '';
  20  }
  21  
  22  // make sure we have a language

  23  if (typeof _editor_lang == "string") {
  24      _editor_lang = _editor_lang.toLowerCase();
  25  } else {
  26      _editor_lang = "en";
  27  }
  28  
  29  // Creates a new HTMLArea object.  Tries to replace the textarea with the given

  30  // ID with it.

  31  function HTMLArea(textarea, config) {
  32      if (HTMLArea.checkSupportedBrowser()) {
  33          if (typeof config == "undefined") {
  34              this.config = new HTMLArea.Config();
  35          } else {
  36              this.config = config;
  37          }
  38          this._htmlArea = null;
  39          this._textArea = textarea;
  40          this._editMode = "wysiwyg";
  41          this.plugins = {};
  42          this._timerToolbar = null;
  43          this._timerUndo = null;
  44          this._undoQueue = new Array(this.config.undoSteps);
  45          this._undoPos = -1;
  46          this._customUndo = false;
  47          this._mdoc = document; // cache the document, we need it in plugins

  48          this.doctype = '';
  49      }
  50  };
  51  
  52  // load some scripts

  53  (function() {
  54      var scripts = HTMLArea._scripts = [ _editor_url + "htmlarea.js",
  55                          _editor_url + "dialog.js",
  56                          _editor_url + "popupwin.js",
  57                          _editor_url + "lang/" + _editor_lang + ".js" ];
  58      var head = document.getElementsByTagName("head")[0];
  59      // start from 1, htmlarea.js is already loaded

  60      for (var i = 1; i < scripts.length; ++i) {
  61          var script = document.createElement("script");
  62          script.src = scripts[i];
  63          head.appendChild(script);
  64      }
  65  })();
  66  
  67  // cache some regexps

  68  HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig;
  69  HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i;
  70  HTMLArea.RE_head    = /<head>((.|\n)*?)<\/head>/i;
  71  HTMLArea.RE_body    = /<body>((.|\n)*?)<\/body>/i;
  72  
  73  HTMLArea.Config = function () {
  74      this.version = "3.0";
  75  
  76      this.width = "auto";
  77      this.height = "auto";
  78  
  79      // enable creation of a status bar?

  80      this.statusBar = true;
  81  
  82      // maximum size of the undo queue

  83      this.undoSteps = 20;
  84  
  85      // the time interval at which undo samples are taken

  86      this.undoTimeout = 500;    // 1/2 sec.

  87  
  88      // the next parameter specifies whether the toolbar should be included

  89      // in the size or not.

  90      this.sizeIncludesToolbar = true;
  91  
  92      // if true then HTMLArea will retrieve the full HTML, starting with the

  93      // <HTML> tag.

  94      this.fullPage = false;
  95  
  96      // style included in the iframe document

  97      this.pageStyle = "";
  98  
  99      // set to true if you want Word code to be cleaned upon Paste

 100      this.killWordOnPaste = false;
 101  
 102      // BaseURL included in the iframe document

 103      this.baseURL = document.baseURI || document.URL;
 104      if (this.baseURL && this.baseURL.match(/(.*)\/([^\/]+)/))
 105          this.baseURL = RegExp.$1 + "/";
 106  
 107      // URL-s

 108      this.imgURL = "images/";
 109      this.popupURL = "popups/";
 110  
 111      /** CUSTOMIZING THE TOOLBAR

 112       * -------------------------

 113       *

 114       * It is recommended that you customize the toolbar contents in an

 115       * external file (i.e. the one calling HTMLArea) and leave this one

 116       * unchanged.  That's because when we (InteractiveTools.com) release a

 117       * new official version, it's less likely that you will have problems

 118       * upgrading HTMLArea.

 119       */
 120      this.toolbar = [
 121          [ "fontname", "space",
 122            "fontsize", "space",
 123            "formatblock", "space",
 124            "bold", "italic", "underline", "strikethrough", "separator",
 125            "subscript", "superscript", "separator",
 126            "copy", "cut", "paste", "space", "undo", "redo" ],
 127  
 128          [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator",
 129            "lefttoright", "righttoleft", "separator",
 130            "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator",
 131            "forecolor", "hilitecolor", "separator",
 132            "inserthorizontalrule", "createlink", "insertimage", "inserttable", "htmlmode", "separator",
 133            "popupeditor", "separator", "showhelp", "about" ]
 134      ];
 135  
 136      this.fontname = {
 137          "Arial":       'arial,helvetica,sans-serif',
 138          "Courier New":       'courier new,courier,monospace',
 139          "Georgia":       'georgia,times new roman,times,serif',
 140          "Tahoma":       'tahoma,arial,helvetica,sans-serif',
 141          "Times New Roman": 'times new roman,times,serif',
 142          "Verdana":       'verdana,arial,helvetica,sans-serif',
 143          "impact":       'impact',
 144          "WingDings":       'wingdings'
 145      };
 146  
 147      this.fontsize = {
 148          "1 (8 pt)":  "1",
 149          "2 (10 pt)": "2",
 150          "3 (12 pt)": "3",
 151          "4 (14 pt)": "4",
 152          "5 (18 pt)": "5",
 153          "6 (24 pt)": "6",
 154          "7 (36 pt)": "7"
 155      };
 156  
 157      this.formatblock = {
 158          "Heading 1": "h1",
 159          "Heading 2": "h2",
 160          "Heading 3": "h3",
 161          "Heading 4": "h4",
 162          "Heading 5": "h5",
 163          "Heading 6": "h6",
 164          "Normal": "p",
 165          "Address": "address",
 166          "Formatted": "pre"
 167      };
 168  
 169      this.customSelects = {};
 170  
 171  	function cut_copy_paste(e, cmd, obj) {
 172          e.execCommand(cmd);
 173      };
 174  
 175      // ADDING CUSTOM BUTTONS: please read below!

 176      // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]"

 177      //    - ID: unique ID for the button.  If the button calls document.execCommand

 178      //        it's wise to give it the same name as the called command.

 179      //    - ACTION: function that gets called when the button is clicked.

 180      //              it has the following prototype:

 181      //                 function(editor, buttonName)

 182      //              - editor is the HTMLArea object that triggered the call

 183      //              - buttonName is the ID of the clicked button

 184      //              These 2 parameters makes it possible for you to use the same

 185      //              handler for more HTMLArea objects or for more different buttons.

 186      //    - ToolTip: default tooltip, for cases when it is not defined in the -lang- file (HTMLArea.I18N)

 187      //    - Icon: path to an icon image file for the button (TODO: use one image for all buttons!)

 188      //    - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time.

 189      this.btnList = {
 190          bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ],
 191          italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ],
 192          underline: [ "Underline", "ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ],
 193          strikethrough: [ "Strikethrough", "ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ],
 194          subscript: [ "Subscript", "ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ],
 195          superscript: [ "Superscript", "ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ],
 196          justifyleft: [ "Justify Left", "ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ],
 197          justifycenter: [ "Justify Center", "ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ],
 198          justifyright: [ "Justify Right", "ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ],
 199          justifyfull: [ "Justify Full", "ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ],
 200          insertorderedlist: [ "Ordered List", "ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ],
 201          insertunorderedlist: [ "Bulleted List", "ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ],
 202          outdent: [ "Decrease Indent", "ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ],
 203          indent: [ "Increase Indent", "ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ],
 204          forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ],
 205          hilitecolor: [ "Background Color", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ],
 206          inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ],
 207          createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ],
 208          insertimage: [ "Insert/Modify Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ],
 209          inserttable: [ "Insert Table", "insert_table.gif", false, function(e) {e.execCommand("inserttable");} ],
 210          htmlmode: [ "Toggle HTML Source", "ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ],
 211          popupeditor: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ],
 212          about: [ "About this editor", "ed_about.gif", true, function(e) {e.execCommand("about");} ],
 213          showhelp: [ "Help using editor", "ed_help.gif", true, function(e) {e.execCommand("showhelp");} ],
 214          undo: [ "Undoes your last action", "ed_undo.gif", false, function(e) {e.execCommand("undo");} ],
 215          redo: [ "Redoes your last action", "ed_redo.gif", false, function(e) {e.execCommand("redo");} ],
 216          cut: [ "Cut selection", "ed_cut.gif", false, cut_copy_paste ],
 217          copy: [ "Copy selection", "ed_copy.gif", false, cut_copy_paste ],
 218          paste: [ "Paste from clipboard", "ed_paste.gif", false, cut_copy_paste ],
 219          lefttoright: [ "Direction left to right", "ed_left_to_right.gif", false, function(e) {e.execCommand("lefttoright");} ],
 220          righttoleft: [ "Direction right to left", "ed_right_to_left.gif", false, function(e) {e.execCommand("righttoleft");} ]
 221      };
 222      /* ADDING CUSTOM BUTTONS

 223       * ---------------------

 224       *

 225       * It is recommended that you add the custom buttons in an external

 226       * file and leave this one unchanged.  That's because when we

 227       * (InteractiveTools.com) release a new official version, it's less

 228       * likely that you will have problems upgrading HTMLArea.

 229       *

 230       * Example on how to add a custom button when you construct the HTMLArea:

 231       *

 232       *   var editor = new HTMLArea("your_text_area_id");

 233       *   var cfg = editor.config; // this is the default configuration

 234       *   cfg.btnList["my-hilite"] =

 235       *    [ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action

 236       *      "Highlight selection", // tooltip

 237       *      "my_hilite.gif", // image

 238       *      false // disabled in text mode

 239       *    ];

 240       *   cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar

 241       *

 242       * An alternate (also more convenient and recommended) way to

 243       * accomplish this is to use the registerButton function below.

 244       */
 245      // initialize tooltips from the I18N module and generate correct image path

 246      for (var i in this.btnList) {
 247          var btn = this.btnList[i];
 248          btn[1] = _editor_url + this.imgURL + btn[1];
 249          if (typeof HTMLArea.I18N.tooltips[i] != "undefined") {
 250              btn[0] = HTMLArea.I18N.tooltips[i];
 251          }
 252      }
 253  };
 254  
 255  /** Helper function: register a new button with the configuration.  It can be

 256   * called with all 5 arguments, or with only one (first one).  When called with

 257   * only one argument it must be an object with the following properties: id,

 258   * tooltip, image, textMode, action.  Examples:

 259   *

 260   * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...});

 261   * 2. config.registerButton({

 262   *      id       : "my-hilite",      // the ID of your button

 263   *      tooltip  : "Hilite text",    // the tooltip

 264   *      image    : "my-hilite.gif",  // image to be displayed in the toolbar

 265   *      textMode : false,            // disabled in text mode

 266   *      action   : function(editor) { // called when the button is clicked

 267   *                   editor.surroundHTML('<span class="hilite">', '</span>');

 268   *                 },

 269   *      context  : "p"               // will be disabled if outside a <p> element

 270   *    });

 271   */
 272  HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) {
 273      var the_id;
 274      if (typeof id == "string") {
 275          the_id = id;
 276      } else if (typeof id == "object") {
 277          the_id = id.id;
 278      } else {
 279          alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
 280          return false;
 281      }
 282      // check for existing id

 283      if (typeof this.customSelects[the_id] != "undefined") {
 284          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");

 285      }
 286      if (typeof this.btnList[the_id] != "undefined") {
 287          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");

 288      }
 289      switch (typeof id) {
 290          case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break;
 291          case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break;
 292      }
 293  };
 294  
 295  /** The following helper function registers a dropdown box with the editor

 296   * configuration.  You still have to add it to the toolbar, same as with the

 297   * buttons.  Call it like this:

 298   *

 299   * FIXME: add example

 300   */
 301  HTMLArea.Config.prototype.registerDropdown = function(object) {
 302      // check for existing id

 303      if (typeof this.customSelects[object.id] != "undefined") {
 304          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");

 305      }
 306      if (typeof this.btnList[object.id] != "undefined") {
 307          // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");

 308      }
 309      this.customSelects[object.id] = object;
 310  };
 311  
 312  /** Call this function to remove some buttons/drop-down boxes from the toolbar.

 313   * Pass as the only parameter a string containing button/drop-down names

 314   * delimited by spaces.  Note that the string should also begin with a space

 315   * and end with a space.  Example:

 316   *

 317   *   config.hideSomeButtons(" fontname fontsize textindicator ");

 318   *

 319   * It's useful because it's easier to remove stuff from the defaul toolbar than

 320   * create a brand new toolbar ;-)

 321   */
 322  HTMLArea.Config.prototype.hideSomeButtons = function(remove) {
 323      var toolbar = this.toolbar;
 324      for (var i in toolbar) {
 325          var line = toolbar[i];
 326          for (var j = line.length; --j >= 0; ) {
 327              if (remove.indexOf(" " + line[j] + " ") >= 0) {
 328                  var len = 1;
 329                  if (/separator|space/.test(line[j + 1])) {
 330                      len = 2;
 331                  }
 332                  line.splice(j, len);
 333              }
 334          }
 335      }
 336  };
 337  
 338  /** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */

 339  HTMLArea.replaceAll = function(config) {
 340      var tas = document.getElementsByTagName("textarea");
 341      for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate());
 342  };
 343  
 344  /** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */

 345  HTMLArea.replace = function(id, config) {
 346      var ta = HTMLArea.getElementById("textarea", id);
 347      return ta ? (new HTMLArea(ta, config)).generate() : null;
 348  };
 349  
 350  // Creates the toolbar and appends it to the _htmlarea

 351  HTMLArea.prototype._createToolbar = function () {
 352      var editor = this;    // to access this in nested functions

 353  
 354      var toolbar = document.createElement("div");
 355      this._toolbar = toolbar;
 356      toolbar.className = "toolbar";
 357      toolbar.unselectable = "1";
 358      var tb_row = null;
 359      var tb_objects = new Object();
 360      this._toolbarObjects = tb_objects;
 361  
 362      // creates a new line in the toolbar

 363  	function newLine() {
 364          var table = document.createElement("table");
 365          table.border = "0px";
 366          table.cellSpacing = "0px";
 367          table.cellPadding = "0px";
 368          toolbar.appendChild(table);
 369          // TBODY is required for IE, otherwise you don't see anything

 370          // in the TABLE.

 371          var tb_body = document.createElement("tbody");
 372          table.appendChild(tb_body);
 373          tb_row = document.createElement("tr");
 374          tb_body.appendChild(tb_row);
 375      }; // END of function: newLine

 376      // init first line

 377      newLine();
 378  
 379      // updates the state of a toolbar element.  This function is member of

 380      // a toolbar element object (unnamed objects created by createButton or

 381      // createSelect functions below).

 382  	function setButtonStatus(id, newval) {
 383          var oldval = this[id];
 384          var el = this.element;
 385          if (oldval != newval) {
 386              switch (id) {
 387                  case "enabled":
 388                  if (newval) {
 389                      HTMLArea._removeClass(el, "buttonDisabled");
 390                      el.disabled = false;
 391                  } else {
 392                      HTMLArea._addClass(el, "buttonDisabled");
 393                      el.disabled = true;
 394                  }
 395                  break;
 396                  case "active":
 397                  if (newval) {
 398                      HTMLArea._addClass(el, "buttonPressed");
 399                  } else {
 400                      HTMLArea._removeClass(el, "buttonPressed");
 401                  }
 402                  break;
 403              }
 404              this[id] = newval;
 405          }
 406      }; // END of function: setButtonStatus

 407  
 408      // this function will handle creation of combo boxes.  Receives as

 409      // parameter the name of a button as defined in the toolBar config.

 410      // This function is called from createButton, above, if the given "txt"

 411      // doesn't match a button.

 412  	function createSelect(txt) {
 413          var options = null;
 414          var el = null;
 415          var cmd = null;
 416          var customSelects = editor.config.customSelects;
 417          var context = null;
 418          switch (txt) {
 419              case "fontsize":
 420              case "fontname":
 421              case "formatblock":
 422              // the following line retrieves the correct

 423              // configuration option because the variable name

 424              // inside the Config object is named the same as the

 425              // button/select in the toolbar.  For instance, if txt

 426              // == "formatblock" we retrieve config.formatblock (or

 427              // a different way to write it in JS is

 428              // config["formatblock"].

 429              options = editor.config[txt];
 430              cmd = txt;
 431              break;
 432              default:
 433              // try to fetch it from the list of registered selects

 434              cmd = txt;
 435              var dropdown = customSelects[cmd];
 436              if (typeof dropdown != "undefined") {
 437                  options = dropdown.options;
 438                  context = dropdown.context;
 439              } else {
 440                  alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
 441              }
 442              break;
 443          }
 444          if (options) {
 445              el = document.createElement("select");
 446              var obj = {
 447                  name    : txt, // field name
 448                  element : el,    // the UI element (SELECT)
 449                  enabled : true, // is it enabled?
 450                  text    : false, // enabled in text mode?
 451                  cmd    : cmd, // command ID
 452                  state    : setButtonStatus, // for changing state
 453                  context : context
 454              };
 455              tb_objects[txt] = obj;
 456              for (var i in options) {
 457                  var op = document.createElement("option");
 458                  op.appendChild(document.createTextNode(i));
 459                  op.value = options[i];
 460                  el.appendChild(op);
 461              }
 462              HTMLArea._addEvent(el, "change", function () {
 463                  editor._comboSelected(el, txt);
 464              });
 465          }
 466          return el;
 467      }; // END of function: createSelect

 468  
 469      // appends a new button to toolbar

 470  	function createButton(txt) {
 471          // the element that will be created

 472          var el = null;
 473          var btn = null;
 474          switch (txt) {
 475              case "separator":
 476              el = document.createElement("div");
 477              el.className = "separator";
 478              break;
 479              case "space":
 480              el = document.createElement("div");
 481              el.className = "space";
 482              break;
 483              case "linebreak":
 484              newLine();
 485              return false;
 486              case "textindicator":
 487              el = document.createElement("div");
 488              el.appendChild(document.createTextNode("A"));
 489              el.className = "indicator";
 490              el.title = HTMLArea.I18N.tooltips.textindicator;
 491              var obj = {
 492                  name    : txt, // the button name (i.e. 'bold')
 493                  element : el, // the UI element (DIV)
 494                  enabled : true, // is it enabled?
 495                  active    : false, // is it pressed?
 496                  text    : false, // enabled in text mode?
 497                  cmd    : "textindicator", // the command ID
 498                  state    : setButtonStatus // for changing state
 499              };
 500              tb_objects[txt] = obj;
 501              break;
 502              default:
 503              btn = editor.config.btnList[txt];
 504          }
 505          if (!el && btn) {
 506              el = document.createElement("div");
 507              el.title = btn[0];
 508              el.className = "button";
 509              // let's just pretend we have a button object, and

 510              // assign all the needed information to it.

 511              var obj = {
 512                  name    : txt, // the button name (i.e. 'bold')
 513                  element : el, // the UI element (DIV)
 514                  enabled : true, // is it enabled?
 515                  active    : false, // is it pressed?
 516                  text    : btn[2], // enabled in text mode?
 517                  cmd    : btn[3], // the command ID
 518                  state    : setButtonStatus, // for changing state
 519                  context : btn[4] || null // enabled in a certain context?
 520              };
 521              tb_objects[txt] = obj;
 522              // handlers to emulate nice flat toolbar buttons

 523              HTMLArea._addEvent(el, "mouseover", function () {
 524                  if (obj.enabled) {
 525                      HTMLArea._addClass(el, "buttonHover");
 526                  }
 527              });
 528              HTMLArea._addEvent(el, "mouseout", function () {
 529                  if (obj.enabled) with (HTMLArea) {
 530                      _removeClass(el, "buttonHover");
 531                      _removeClass(el, "buttonActive");
 532                      (obj.active) && _addClass(el, "buttonPressed");
 533                  }
 534              });
 535              HTMLArea._addEvent(el, "mousedown", function (ev) {
 536                  if (obj.enabled) with (HTMLArea) {
 537                      _addClass(el, "buttonActive");
 538                      _removeClass(el, "buttonPressed");
 539                      _stopEvent(is_ie ? window.event : ev);
 540                  }
 541              });
 542              // when clicked, do the following:

 543              HTMLArea._addEvent(el, "click", function (ev) {
 544                  if (obj.enabled) with (HTMLArea) {
 545                      _removeClass(el, "buttonActive");
 546                      _removeClass(el, "buttonHover");
 547                      obj.cmd(editor, obj.name, obj);
 548                      _stopEvent(is_ie ? window.event : ev);
 549                  }
 550              });
 551              var img = document.createElement("img");
 552              img.src = btn[1];
 553              img.style.width = "18px";
 554              img.style.height = "18px";
 555              el.appendChild(img);
 556          } else if (!el) {
 557              el = createSelect(txt);
 558          }
 559          if (el) {
 560              var tb_cell = document.createElement("td");
 561              tb_row.appendChild(tb_cell);
 562              tb_cell.appendChild(el);
 563          } else {
 564              alert("FIXME: Unknown toolbar item: " + txt);
 565          }
 566          return el;
 567      };
 568  
 569      var first = true;
 570      for (var i in this.config.toolbar) {
 571          if (!first) {
 572              createButton("linebreak");
 573          } else {
 574              first = false;
 575          }
 576          var group = this.config.toolbar[i];
 577          for (var j in group) {
 578              var code = group[j];
 579              if (/^([IT])\[(.*?)\]/.test(code)) {
 580                  // special case, create text label

 581                  var l7ed = RegExp.$1 == "I"; // localized?

 582                  var label = RegExp.$2;
 583                  if (l7ed) {
 584                      label = HTMLArea.I18N.custom[label];
 585                  }
 586                  var tb_cell = document.createElement("td");
 587                  tb_row.appendChild(tb_cell);
 588                  tb_cell.className = "label";
 589                  tb_cell.innerHTML = label;
 590              } else {
 591                  createButton(code);
 592              }
 593          }
 594      }
 595  
 596      this._htmlArea.appendChild(toolbar);
 597  };
 598  
 599  HTMLArea.prototype._createStatusBar = function() {
 600      var statusbar = document.createElement("div");
 601      statusbar.className = "statusBar";
 602      this._htmlArea.appendChild(statusbar);
 603      this._statusBar = statusbar;
 604      // statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));

 605      // creates a holder for the path view

 606      div = document.createElement("span");
 607      div.className = "statusBarTree";
 608      div.innerHTML = HTMLArea.I18N.msg["Path"] + ": ";
 609      this._statusBarTree = div;
 610      this._statusBar.appendChild(div);
 611      if (!this.config.statusBar) {
 612          // disable it...

 613          statusbar.style.display = "none";
 614      }
 615  };
 616  
 617  // Creates the HTMLArea object and replaces the textarea with it.

 618  HTMLArea.prototype.generate = function () {
 619      var editor = this;    // we'll need "this" in some nested functions

 620      // get the textarea

 621      var textarea = this._textArea;
 622      if (typeof textarea == "string") {
 623          // it's not element but ID

 624          this._textArea = textarea = HTMLArea.getElementById("textarea", textarea);
 625      }
 626      this._ta_size = {
 627          w: textarea.offsetWidth,
 628          h: textarea.offsetHeight
 629      };
 630      textarea.style.display = "none";
 631  
 632      // create the editor framework

 633      var htmlarea = document.createElement("div");
 634      htmlarea.className = "htmlarea";
 635      this._htmlArea = htmlarea;
 636  
 637      // insert the editor before the textarea.

 638      textarea.parentNode.insertBefore(htmlarea, textarea);
 639  
 640      if (textarea.form) {
 641          // we have a form, on submit get the HTMLArea content and

 642          // update original textarea.

 643          var f = textarea.form;
 644          if (typeof f.onsubmit == "function") {
 645              var funcref = f.onsubmit;
 646              if (typeof f.__msh_prevOnSubmit == "undefined") {
 647                  f.__msh_prevOnSubmit = [];
 648              }
 649              f.__msh_prevOnSubmit.push(funcref);
 650          }
 651          f.onsubmit = function() {
 652              editor._textArea.value = editor.getHTML();
 653              var a = this.__msh_prevOnSubmit;
 654              // call previous submit methods if they were there.

 655              if (typeof a != "undefined") {
 656                  for (var i in a) {
 657                      a[i]();
 658                  }
 659              }
 660          };
 661      }
 662  
 663      // add a handler for the "back/forward" case -- on body.unload we save

 664      // the HTML content into the original textarea.

 665      window.onunload = function() {
 666          editor._textArea.value = editor.getHTML();
 667      };
 668  
 669      // creates & appends the toolbar

 670      this._createToolbar();
 671  
 672      // create the IFRAME

 673      var iframe = document.createElement("iframe");
 674      htmlarea.appendChild(iframe);
 675  
 676      this._iframe = iframe;
 677  
 678      // creates & appends the status bar, if the case

 679      this._createStatusBar();
 680  
 681      // remove the default border as it keeps us from computing correctly

 682      // the sizes.  (somebody tell me why doesn't this work in IE)

 683  
 684      if (!HTMLArea.is_ie) {
 685          iframe.style.borderWidth = "1px";
 686      // iframe.frameBorder = "1";

 687      // iframe.marginHeight = "0";

 688      // iframe.marginWidth = "0";

 689      }
 690  
 691      // size the IFRAME according to user's prefs or initial textarea

 692      var height = (this.config.height == "auto" ? (this._ta_size.h + "px") : this.config.height);
 693      height = parseInt(height);
 694      var width = (this.config.width == "auto" ? (this._ta_size.w + "px") : this.config.width);
 695      width = parseInt(width);
 696  
 697      if (!HTMLArea.is_ie) {
 698          height -= 2;
 699          width -= 2;
 700      }
 701  
 702      iframe.style.width = width + "px";
 703      if (this.config.sizeIncludesToolbar) {
 704          // substract toolbar height

 705          height -= this._toolbar.offsetHeight;
 706          height -= this._statusBar.offsetHeight;
 707      }
 708      if (height < 0) {
 709          height = 0;
 710      }
 711      iframe.style.height = height + "px";
 712  
 713      // the editor including the toolbar now have the same size as the

 714      // original textarea.. which means that we need to reduce that a bit.

 715      textarea.style.width = iframe.style.width;
 716       textarea.style.height = iframe.style.height;
 717  
 718      // IMPORTANT: we have to allow Mozilla a short time to recognize the

 719      // new frame.  Otherwise we get a stupid exception.

 720  	function initIframe() {
 721          var doc = editor._iframe.contentWindow.document;
 722          if (!doc) {
 723              // Try again..

 724              // FIXME: don't know what else to do here.  Normally

 725              // we'll never reach this point.

 726              if (HTMLArea.is_gecko) {
 727                  setTimeout(initIframe, 100);
 728                  return false;
 729              } else {
 730                  alert("ERROR: IFRAME can't be initialized.");
 731              }
 732          }
 733          if (HTMLArea.is_gecko) {
 734              // enable editable mode for Mozilla

 735              doc.designMode = "on";
 736          }
 737          editor._doc = doc;
 738          if (!editor.config.fullPage) {
 739              doc.open();
 740              var html = "<html>\n";
 741              html += "<head>\n";
 742              if (editor.config.baseURL)
 743                  html += '<base href="' + editor.config.baseURL + '" />';
 744              html += "<style> html,body { border: 0px; } " +
 745                  editor.config.pageStyle + "</style>\n";
 746              html += "</head>\n";
 747              html += "<body>\n";
 748              html += editor._textArea.value;
 749              html += "</body>\n";
 750              html += "</html>";
 751              doc.write(html);
 752              doc.close();
 753          } else {
 754              var html = editor._textArea.value;
 755              if (html.match(HTMLArea.RE_doctype)) {
 756                  editor.setDoctype(RegExp.$1);
 757                  html = html.replace(HTMLArea.RE_doctype, "");
 758              }
 759              doc.open();
 760              doc.write(html);
 761              doc.close();
 762          }
 763  
 764          if (HTMLArea.is_ie) {
 765              // enable editable mode for IE.     For some reason this

 766              // doesn't work if done in the same place as for Gecko

 767              // (above).

 768              doc.body.contentEditable = true;
 769          }
 770  
 771          editor.focusEditor();
 772          // intercept some events; for updating the toolbar & keyboard handlers

 773          HTMLArea._addEvents
 774              (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
 775               function (event) {
 776                   return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
 777               });
 778  
 779          // check if any plugins have registered refresh handlers

 780          for (var i in editor.plugins) {
 781              var plugin = editor.plugins[i].instance;
 782              if (typeof plugin.onGenerate == "function")
 783                  plugin.onGenerate();
 784          }
 785  
 786          setTimeout(function() {
 787              editor.updateToolbar();
 788          }, 250);
 789  
 790          if (typeof editor.onGenerate == "function")
 791              editor.onGenerate();
 792      };
 793      setTimeout(initIframe, 100);
 794  };
 795  
 796  // Switches editor mode; parameter can be "textmode" or "wysiwyg".  If no

 797  // parameter was passed this function toggles between modes.

 798  HTMLArea.prototype.setMode = function(mode) {
 799      if (typeof mode == "undefined") {
 800          mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode");
 801      }
 802      switch (mode) {
 803          case "textmode":
 804          this._textArea.value = this.getHTML();
 805          this._iframe.style.display = "none";
 806          this._textArea.style.display = "block";
 807          if (this.config.statusBar) {
 808              this._statusBar.innerHTML = HTMLArea.I18N.msg["TEXT_MODE"];
 809          }
 810          break;
 811          case "wysiwyg":
 812          if (HTMLArea.is_gecko) {
 813              // disable design mode before changing innerHTML

 814              try {
 815                  this._doc.designMode = "off";
 816              } catch(e) {};
 817          }
 818          if (!this.config.fullPage)
 819              this._doc.body.innerHTML = this.getHTML();
 820          else
 821              this.setFullHTML(this.getHTML());
 822          this._iframe.style.display = "block";
 823          this._textArea.style.display = "none";
 824          if (HTMLArea.is_gecko) {
 825              // we need to refresh that info for Moz-1.3a

 826              try {
 827                  this._doc.designMode = "on";
 828              } catch(e) {};
 829          }
 830          if (this.config.statusBar) {
 831              this._statusBar.innerHTML = '';
 832              this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
 833              this._statusBar.appendChild(this._statusBarTree);
 834          }
 835          break;
 836          default:
 837          alert("Mode <" + mode + "> not defined!");
 838          return false;
 839      }
 840      this._editMode = mode;
 841      this.focusEditor();
 842  };
 843  
 844  HTMLArea.prototype.setFullHTML = function(html) {
 845      var save_multiline = RegExp.multiline;
 846      RegExp.multiline = true;
 847      if (html.match(HTMLArea.RE_doctype)) {
 848          this.setDoctype(RegExp.$1);
 849          html = html.replace(HTMLArea.RE_doctype, "");
 850      }
 851      RegExp.multiline = save_multiline;
 852      if (!HTMLArea.is_ie) {
 853          if (html.match(HTMLArea.RE_head))
 854              this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
 855          if (html.match(HTMLArea.RE_body))
 856              this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
 857      } else {
 858          var html_re = /<html>((.|\n)*?)<\/html>/i;
 859          html = html.replace(html_re, "$1");
 860          this._doc.open();
 861          this._doc.write(html);
 862          this._doc.close();
 863          this._doc.body.contentEditable = true;
 864          return true;
 865      }
 866  };
 867  
 868  /***************************************************

 869   *  Category: PLUGINS

 870   ***************************************************/
 871  
 872  // this is the variant of the function above where the plugin arguments are

 873  // already packed in an array.  Externally, it should be only used in the

 874  // full-screen editor code, in order to initialize plugins with the same

 875  // parameters as in the opener window.

 876  HTMLArea.prototype.registerPlugin2 = function(plugin, args) {
 877      if (typeof plugin == "string")
 878          plugin = eval(plugin);
 879      var obj = new plugin(this, args);
 880      if (obj) {
 881          var clone = {};
 882          var info = plugin._pluginInfo;
 883          for (var i in info)
 884              clone[i] = info[i];
 885          clone.instance = obj;
 886          clone.args = args;
 887          this.plugins[plugin._pluginInfo.name] = clone;
 888      } else
 889          alert("Can't register plugin " + plugin.toString() + ".");
 890  };
 891  
 892  // Create the specified plugin and register it with this HTMLArea

 893  HTMLArea.prototype.registerPlugin = function() {
 894      var plugin = arguments[0];
 895      var args = [];
 896      for (var i = 1; i < arguments.length; ++i)
 897          args.push(arguments[i]);
 898      this.registerPlugin2(plugin, args);
 899  };
 900  
 901  // static function that loads the required plugin and lang file, based on the

 902  // language loaded already for HTMLArea.  You better make sure that the plugin

 903  // _has_ that language, otherwise shit might happen ;-)

 904  HTMLArea.loadPlugin = function(pluginName) {
 905      var dir = _editor_url + "plugins/" + pluginName;
 906      var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g,
 907                      function (str, l1, l2, l3) {
 908                          return l1 + "-" + l2.toLowerCase() + l3;
 909                      }).toLowerCase() + ".js";
 910      var plugin_file = dir + "/" + plugin;
 911      var plugin_lang = dir + "/lang/" + HTMLArea.I18N.lang + ".js";
 912      HTMLArea._scripts.push(plugin_file, plugin_lang);
 913      document.write("<script type='text/javascript' src='" + plugin_file + "'></script>");
 914      document.write("<script type='text/javascript' src='" + plugin_lang + "'></script>");
 915  };
 916  
 917  HTMLArea.loadStyle = function(style, plugin) {
 918      var url = _editor_url || '';
 919      if (typeof plugin != "undefined") {
 920          url += "plugins/" + plugin + "/";
 921      }
 922      url += style;
 923      document.write("<style type='text/css'>@import url(" + url + ");</style>");
 924  };
 925  HTMLArea.loadStyle("htmlarea.css");
 926  
 927  /***************************************************

 928   *  Category: EDITOR UTILITIES

 929   ***************************************************/
 930  
 931  // The following function is a slight variation of the word cleaner code posted

 932  // by Weeezl (user @ InteractiveTools forums).

 933  HTMLArea.prototype._wordClean = function() {
 934      var D = this.getInnerHTML();
 935      if (D.indexOf('class=Mso') >= 0) {
 936  
 937          // make one line

 938          D = D.replace(/\r\n/g, ' ').
 939              replace(/\n/g, ' ').
 940              replace(/\r/g, ' ').
 941              replace(/\&nbsp\;/g,' ');
 942  
 943          // keep tags, strip attributes

 944          D = D.replace(/ class=[^\s|>]*/gi,'').
 945              //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">').

 946              replace(/ style=\"[^>]*\"/gi,'').
 947              replace(/ align=[^\s|>]*/gi,'');
 948  
 949          //clean up tags

 950          D = D.replace(/<b [^>]*>/gi,'<b>').
 951              replace(/<i [^>]*>/gi,'<i>').
 952              replace(/<li [^>]*>/gi,'<li>').
 953              replace(/<ul [^>]*>/gi,'<ul>');
 954  
 955          // replace outdated tags

 956          D = D.replace(/<b>/gi,'<strong>').
 957              replace(/<\/b>/gi,'</strong>');
 958  
 959          // mozilla doesn't like <em> tags

 960          D = D.replace(/<em>/gi,'<i>').
 961              replace(/<\/em>/gi,'</i>');
 962  
 963          // kill unwanted tags

 964          D = D.replace(/<\?xml:[^>]*>/g, '').       // Word xml
 965              replace(/<\/?st1:[^>]*>/g,'').     // Word SmartTags
 966              replace(/<\/?[a-z]\:[^>]*>/g,'').  // All other funny Word non-HTML stuff
 967              replace(/<\/?font[^>]*>/gi,'').    // Disable if you want to keep font formatting
 968              replace(/<\/?span[^>]*>/gi,' ').
 969              replace(/<\/?div[^>]*>/gi,' ').
 970              replace(/<\/?pre[^>]*>/gi,' ').
 971              replace(/<\/?h[1-6][^>]*>/gi,' ');
 972  
 973          //remove empty tags

 974          //D = D.replace(/<strong><\/strong>/gi,'').

 975          //replace(/<i><\/i>/gi,'').

 976          //replace(/<P[^>]*><\/P>/gi,'');

 977  
 978          // nuke double tags

 979          oldlen = D.length + 1;
 980          while(oldlen > D.length) {
 981              oldlen = D.length;
 982              // join us now and free the tags, we'll be free hackers, we'll be free... ;-)

 983              D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' ').
 984                  replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>');
 985          }
 986          D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>').
 987              replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>');
 988  
 989          // nuke double spaces

 990          D = D.replace(/  */gi,' ');
 991  
 992          this.setHTML(D);
 993          this.updateToolbar();
 994      }
 995  };
 996  
 997  HTMLArea.prototype.forceRedraw = function() {
 998      this._doc.body.style.visibility = "hidden";
 999      this._doc.body.style.visibility = "visible";
1000      // this._doc.body.innerHTML = this.getInnerHTML();

1001  };
1002  
1003  // focuses the iframe window.  returns a reference to the editor document.

1004  HTMLArea.prototype.focusEditor = function() {
1005      switch (this._editMode) {
1006          case "wysiwyg" : this._iframe.contentWindow.focus(); break;
1007          case "textmode": this._textArea.focus(); break;
1008          default       : alert("ERROR: mode " + this._editMode + " is not defined");
1009      }
1010      return this._doc;
1011  };
1012  
1013  // takes a snapshot of the current text (for undo)

1014  HTMLArea.prototype._undoTakeSnapshot = function() {
1015      ++this._undoPos;
1016      if (this._undoPos >= this.config.undoSteps) {
1017          // remove the first element

1018          this._undoQueue.shift();
1019          --this._undoPos;
1020      }
1021      // use the fasted method (getInnerHTML);

1022      var take = true;
1023      var txt = this.getInnerHTML();
1024      if (this._undoPos > 0)
1025          take = (this._undoQueue[this._undoPos - 1] != txt);
1026      if (take) {
1027          this._undoQueue[this._undoPos] = txt;
1028      } else {
1029          this._undoPos--;
1030      }
1031  };
1032  
1033  HTMLArea.prototype.undo = function() {
1034      if (this._undoPos > 0) {
1035          var txt = this._undoQueue[--this._undoPos];
1036          if (txt) this.setHTML(txt);
1037          else ++this._undoPos;
1038      }
1039  };
1040  
1041  HTMLArea.prototype.redo = function() {
1042      if (this._undoPos < this._undoQueue.length - 1) {
1043          var txt = this._undoQueue[++this._undoPos];
1044          if (txt) this.setHTML(txt);
1045          else --this._undoPos;
1046      }
1047  };
1048  
1049  // updates enabled/disable/active state of the toolbar elements

1050  HTMLArea.prototype.updateToolbar = function(noStatus) {
1051      var doc = this._doc;
1052      var text = (this._editMode == "textmode");
1053      var ancestors = null;
1054      if (!text) {
1055          ancestors = this.getAllAncestors();
1056          if (this.config.statusBar && !noStatus) {
1057              this._statusBarTree.innerHTML = HTMLArea.I18N.msg["Path"] + ": "; // clear

1058              for (var i = ancestors.length; --i >= 0;) {
1059                  var el = ancestors[i];
1060                  if (!el) {
1061                      // hell knows why we get here; this

1062                      // could be a classic example of why

1063                      // it's good to check for conditions

1064                      // that are impossible to happen ;-)

1065                      continue;
1066                  }
1067                  var a = document.createElement("a");
1068                  a.href = "#";
1069                  a.el = el;
1070                  a.editor = this;
1071                  a.onclick = function() {
1072                      this.blur();
1073                      this.editor.selectNodeContents(this.el);
1074                      this.editor.updateToolbar(true);
1075                      return false;
1076                  };
1077                  a.oncontextmenu = function() {
1078                      // TODO: add context menu here

1079                      this.blur();
1080                      var info = "Inline style:\n\n";
1081                      info += this.el.style.cssText.split(/;\s*/).join(";\n");
1082                      alert(info);
1083                      return false;
1084                  };
1085                  var txt = el.tagName.toLowerCase();
1086                  a.title = el.style.cssText;
1087                  if (el.id) {
1088                      txt += "#" + el.id;
1089                  }
1090                  if (el.className) {
1091                      txt += "." + el.className;
1092                  }
1093                  a.appendChild(document.createTextNode(txt));
1094                  this._statusBarTree.appendChild(a);
1095                  if (i != 0) {
1096                      this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
1097                  }
1098              }
1099          }
1100      }
1101      for (var i in this._toolbarObjects) {
1102          var btn = this._toolbarObjects[i];
1103          var cmd = i;
1104          var inContext = true;
1105          if (btn.context && !text) {
1106              inContext = false;
1107              var context = btn.context;
1108              var attrs = [];
1109              if (/(.*)\[(.*?)\]/.test(context)) {
1110                  context = RegExp.$1;
1111                  attrs = RegExp.$2.split(",");
1112              }
1113              context = context.toLowerCase();
1114              var match = (context == "*");
1115              for (var k in ancestors) {
1116                  if (!ancestors[k]) {
1117                      // the impossible really happens.

1118                      continue;
1119                  }
1120                  if (match || (ancestors[k].tagName.toLowerCase() == context)) {
1121                      inContext = true;
1122                      for (var ka in attrs) {
1123                          if (!eval("ancestors[k]." + attrs[ka])) {
1124                              inContext = false;
1125                              break;
1126                          }
1127                      }
1128                      if (inContext) {
1129                          break;
1130                      }
1131                  }
1132              }
1133          }
1134          btn.state("enabled", (!text || btn.text) && inContext);
1135          if (typeof cmd == "function") {
1136              continue;
1137          }
1138          // look-it-up in the custom dropdown boxes

1139          var dropdown = this.config.customSelects[cmd];
1140          if ((!text || btn.text) && (typeof dropdown != "undefined")) {
1141              dropdown.refresh(this);
1142              continue;
1143          }
1144          switch (cmd) {
1145              case "fontname":
1146              case "fontsize":
1147              case "formatblock":
1148              if (!text) try {
1149                  var value = ("" + doc.queryCommandValue(cmd)).toLowerCase();
1150                  if (!value) {
1151                      // FIXME: what do we do here?

1152                      break;
1153                  }
1154                  // HACK -- retrieve the config option for this

1155                  // combo box.  We rely on the fact that the

1156                  // variable in config has the same name as

1157                  // button name in the toolbar.

1158                  var options = this.config[cmd];
1159                  var k = 0;
1160                  // btn.element.selectedIndex = 0;

1161                  for (var j in options) {
1162                      // FIXME: the following line is scary.

1163                      if ((j.toLowerCase() == value) ||
1164                          (options[j].substr(0, value.length).toLowerCase() == value)) {
1165                          btn.element.selectedIndex = k;
1166                          break;
1167                      }
1168                      ++k;
1169                  }
1170              } catch(e) {};
1171              break;
1172              case "textindicator":
1173              if (!text) {
1174                  try {with (btn.element.style) {
1175                      backgroundColor = HTMLArea._makeColor(
1176                          doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor"));
1177                      if (/transparent/i.test(backgroundColor)) {
1178                          // Mozilla

1179                          backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor"));
1180                      }
1181                      color = HTMLArea._makeColor(doc.queryCommandValue("forecolor"));
1182                      fontFamily = doc.queryCommandValue("fontname");
1183                      fontWeight = doc.queryCommandState("bold") ? "bold" : "normal";
1184                      fontStyle = doc.queryCommandState("italic") ? "italic" : "normal";
1185                  }} catch (e) {
1186                      // alert(e + "\n\n" + cmd);

1187                  }
1188              }
1189              break;
1190              case "htmlmode": btn.state("active", text); break;
1191              case "lefttoright":
1192              case "righttoleft":
1193              var el = this.getParentElement();
1194              while (el && !HTMLArea.isBlockElement(el))
1195                  el = el.parentNode;
1196              if (el)
1197                  btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr")));
1198              break;
1199              default:
1200              try {
1201                  btn.state("active", (!text && doc.queryCommandState(cmd)));
1202              } catch (e) {}
1203          }
1204      }
1205      // take undo snapshots

1206      if (this._customUndo && !this._timerUndo) {
1207          this._undoTakeSnapshot();
1208          var editor = this;
1209          this._timerUndo = setTimeout(function() {
1210              editor._timerUndo = null;
1211          }, this.config.undoTimeout);
1212      }
1213      // check if any plugins have registered refresh handlers

1214      for (var i in this.plugins) {
1215          var plugin = this.plugins[i].instance;
1216          if (typeof plugin.onUpdateToolbar == "function")
1217              plugin.onUpdateToolbar();
1218      }
1219  };
1220  
1221  /** Returns a node after which we can insert other nodes, in the current

1222   * selection.  The selection is removed.  It splits a text node, if needed.

1223   */
1224  HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
1225      if (!HTMLArea.is_ie) {
1226          var sel = this._getSelection();
1227          var range = this._createRange(sel);
1228          // remove the current selection

1229          sel.removeAllRanges();
1230          range.deleteContents();
1231          var node = range.startContainer;
1232          var pos = range.startOffset;
1233          switch (node.nodeType) {
1234              case 3: // Node.TEXT_NODE
1235              // we have to split it at the caret position.

1236              if (toBeInserted.nodeType == 3) {
1237                  // do optimized insertion

1238                  node.insertData(pos, toBeInserted.data);
1239                  range = this._createRange();
1240                  range.setEnd(node, pos + toBeInserted.length);
1241                  range.setStart(node, pos + toBeInserted.length);
1242                  sel.addRange(range);
1243              } else {
1244                  node = node.splitText(pos);
1245                  var selnode = toBeInserted;
1246                  if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1247                      selnode = selnode.firstChild;
1248                  }
1249                  node.parentNode.insertBefore(toBeInserted, node);
1250                  this.selectNodeContents(selnode);
1251                  this.updateToolbar();
1252              }
1253              break;
1254              case 1: // Node.ELEMENT_NODE
1255              var selnode = toBeInserted;
1256              if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1257                  selnode = selnode.firstChild;
1258              }
1259              node.insertBefore(toBeInserted, node.childNodes[pos]);
1260              this.selectNodeContents(selnode);
1261              this.updateToolbar();
1262              break;
1263          }
1264      } else {
1265          return null;    // this function not yet used for IE <FIXME>

1266      }
1267  };
1268  
1269  // Returns the deepest node that contains both endpoints of the selection.

1270  HTMLArea.prototype.getParentElement = function() {
1271      var sel = this._getSelection();
1272      var range = this._createRange(sel);
1273      if (HTMLArea.is_ie) {
1274          switch (sel.type) {
1275              case "Text":
1276              case "None":
1277              // It seems that even for selection of type "None",

1278              // there _is_ a parent element and it's value is not

1279              // only correct, but very important to us.  MSIE is

1280              // certainly the buggiest browser in the world and I

1281              // wonder, God, how can Earth stand it?

1282              return range.parentElement();
1283              case "Control":
1284              return range.item(0);
1285              default:
1286              return this._doc.body;
1287          }
1288      } else try {
1289          var p = range.commonAncestorContainer;
1290          if (!range.collapsed && range.startContainer == range.endContainer &&
1291              range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes())
1292              p = range.startContainer.childNodes[range.startOffset];
1293          /*

1294          alert(range.startContainer + ":" + range.startOffset + "\n" +

1295                range.endContainer + ":" + range.endOffset);

1296          */
1297          while (p.nodeType == 3) {
1298              p = p.parentNode;
1299          }
1300          return p;
1301      } catch (e) {
1302          return null;
1303      }
1304  };
1305  
1306  // Returns an array with all the ancestor nodes of the selection.

1307  HTMLArea.prototype.getAllAncestors = function() {
1308      var p = this.getParentElement();
1309      var a = [];
1310      while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1311          a.push(p);
1312          p = p.parentNode;
1313      }
1314      a.push(this._doc.body);
1315      return a;
1316  };
1317  
1318  // Selects the contents inside the given node

1319  HTMLArea.prototype.selectNodeContents = function(node, pos) {
1320      this.focusEditor();
1321      this.forceRedraw();
1322      var range;
1323      var collapsed = (typeof pos != "undefined");
1324      if (HTMLArea.is_ie) {
1325          range = this._doc.body.createTextRange();
1326          range.moveToElementText(node);
1327          (collapsed) && range.collapse(pos);
1328          range.select();
1329      } else {
1330          var sel = this._getSelection();
1331          range = this._doc.createRange();
1332          range.selectNodeContents(node);
1333          (collapsed) && range.collapse(pos);
1334          sel.removeAllRanges();
1335          sel.addRange(range);
1336      }
1337  };
1338  
1339  /** Call this function to insert HTML code at the current position.  It deletes

1340   * the selection, if any.

1341   */
1342  HTMLArea.prototype.insertHTML = function(html) {
1343      var sel = this._getSelection();
1344      var range = this._createRange(sel);
1345      if (HTMLArea.is_ie) {
1346          range.pasteHTML(html);
1347      } else {
1348          // construct a new document fragment with the given HTML

1349          var fragment = this._doc.createDocumentFragment();
1350          var div = this._doc.createElement("div");
1351          div.innerHTML = html;
1352          while (div.firstChild) {
1353              // the following call also removes the node from div

1354              fragment.appendChild(div.firstChild);
1355          }
1356          // this also removes the selection

1357          var node = this.insertNodeAtSelection(fragment);
1358      }
1359  };
1360  
1361  /**

1362   *  Call this function to surround the existing HTML code in the selection with

1363   *  your tags.  FIXME: buggy!  This function will be deprecated "soon".

1364   */
1365  HTMLArea.prototype.surroundHTML = function(startTag, endTag) {
1366      var html = this.getSelectedHTML();
1367      // the following also deletes the selection

1368      this.insertHTML(startTag + html + endTag);
1369  };
1370  
1371  /// Retrieve the selected block

1372  HTMLArea.prototype.getSelectedHTML = function() {
1373      var sel = this._getSelection();
1374      var range = this._createRange(sel);
1375      var existing = null;
1376      if (HTMLArea.is_ie) {
1377          existing = range.htmlText;
1378      } else {
1379          existing = HTMLArea.getHTML(range.cloneContents(), false, this);
1380      }
1381      return existing;
1382  };
1383  
1384  /// Return true if we have some selection

1385  HTMLArea.prototype.hasSelectedText = function() {
1386      // FIXME: come _on_ mishoo, you can do better than this ;-)

1387      return this.getSelectedHTML() != '';
1388  };
1389  
1390  HTMLArea.prototype._createLink = function(link) {
1391      var editor = this;
1392      var outparam = null;
1393      if (typeof link == "undefined") {
1394          link = this.getParentElement();
1395          if (link && !/^a$/i.test(link.tagName))
1396              link = null;
1397      }
1398      if (link) outparam = {
1399          f_href   : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"),
1400          f_title  : link.title,
1401          f_target : link.target
1402      };
1403      this._popupDialog("link.html", function(param) {
1404          if (!param)
1405              return false;
1406          var a = link;
1407          if (!a) {
1408              editor._doc.execCommand("createlink", false, param.f_href);
1409              a = editor.getParentElement();
1410              var sel = editor._getSelection();
1411              var range = editor._createRange(sel);
1412              if (!HTMLArea.is_ie) {
1413                  a = range.startContainer;
1414                  if (!/^a$/i.test(a.tagName))
1415                      a = a.nextSibling;
1416              }
1417          } else a.href = param.f_href.trim();
1418          if (!/^a$/i.test(a.tagName))
1419              return false;
1420          a.target = param.f_target.trim();
1421          a.title = param.f_title.trim();
1422          editor.selectNodeContents(a);
1423          editor.updateToolbar();
1424      }, outparam);
1425  };
1426  
1427  // Called when the user clicks on "InsertImage" button.  If an image is already

1428  // there, it will just modify it's properties.

1429  HTMLArea.prototype._insertImage = function(image) {
1430      var editor = this;    // for nested functions

1431      var outparam = null;
1432      if (typeof image == "undefined") {
1433          image = this.getParentElement();
1434          if (image && !/^img$/i.test(image.tagName))
1435              image = null;
1436      }
1437      if (image) outparam = {
1438          f_url    : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"),
1439          f_alt    : image.alt,
1440          f_border : image.border,
1441          f_align  : image.align,
1442          f_vert   : image.vspace,
1443          f_horiz  : image.hspace
1444      };
1445      this._popupDialog("insert_image.html", function(param) {
1446          if (!param) {    // user must have pressed Cancel
1447              return false;
1448          }
1449          var img = image;
1450          if (!img) {
1451              var sel = editor._getSelection();
1452              var range = editor._createRange(sel);
1453              editor._doc.execCommand("insertimage", false, param.f_url);
1454              if (HTMLArea.is_ie) {
1455                  img = range.parentElement();
1456                  // wonder if this works...

1457                  if (img.tagName.toLowerCase() != "img") {
1458                      img = img.previousSibling;
1459                  }
1460              } else {
1461                  img = range.startContainer.previousSibling;
1462              }
1463          } else {
1464              img.src = param.f_url;
1465          }
1466          for (field in param) {
1467              var value = param[field];
1468              switch (field) {
1469                  case "f_alt"    : img.alt     = value; break;
1470                  case "f_border" : img.border = parseInt(value || "0"); break;
1471                  case "f_align"  : img.align     = value; break;
1472                  case "f_vert"   : img.vspace = parseInt(value || "0"); break;
1473                  case "f_horiz"  : img.hspace = parseInt(value || "0"); break;
1474              }
1475          }
1476      }, outparam);
1477  };
1478  
1479  // Called when the user clicks the Insert Table button

1480  HTMLArea.prototype._insertTable = function() {
1481      var sel = this._getSelection();
1482      var range = this._createRange(sel);
1483      var editor = this;    // for nested functions

1484      this._popupDialog("insert_table.html", function(param) {
1485          if (!param) {    // user must have pressed Cancel
1486              return false;
1487          }
1488          var doc = editor._doc;
1489          // create the table element

1490          var table = doc.createElement("table");
1491          // assign the given arguments

1492          for (var field in param) {
1493              var value = param[field];
1494              if (!value) {
1495                  continue;
1496              }
1497              switch (field) {
1498                  case "f_width"   : table.style.width = value + param["f_unit"]; break;
1499                  case "f_align"   : table.align     = value; break;
1500                  case "f_border"  : table.border     = parseInt(value); break;
1501                  case "f_spacing" : table.cellspacing = parseInt(value); break;
1502                  case "f_padding" : table.cellpadding = parseInt(value); break;
1503              }
1504          }
1505          var tbody = doc.createElement("tbody");
1506          table.appendChild(tbody);
1507          for (var i = 0; i < param["f_rows"]; ++i) {
1508              var tr = doc.createElement("tr");
1509              tbody.appendChild(tr);
1510              for (var j = 0; j < param["f_cols"]; ++j) {
1511                  var td = doc.createElement("td");
1512                  tr.appendChild(td);
1513                  // Mozilla likes to see something inside the cell.

1514                  (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br"));
1515              }
1516          }
1517          if (HTMLArea.is_ie) {
1518              range.pasteHTML(table.outerHTML);
1519          } else {
1520              // insert the table

1521              editor.insertNodeAtSelection(table);
1522          }
1523          return true;
1524      }, null);
1525  };
1526  
1527  /***************************************************

1528   *  Category: EVENT HANDLERS

1529   ***************************************************/
1530  
1531  // el is reference to the SELECT object

1532  // txt is the name of the select field, as in config.toolbar

1533  HTMLArea.prototype._comboSelected = function(el, txt) {
1534      this.focusEditor();
1535      var value = el.options[el.selectedIndex].value;
1536      switch (txt) {
1537          case "fontname":
1538          case "fontsize": this.execCommand(txt, false, value); break;
1539          case "formatblock":
1540          (HTMLArea.is_ie) && (value = "<" + value + ">");
1541          this.execCommand(txt, false, value);
1542          break;
1543          default:
1544          // try to look it up in the registered dropdowns

1545          var dropdown = this.config.customSelects[txt];
1546          if (typeof dropdown != "undefined") {
1547              dropdown.action(this);
1548          } else {
1549              alert("FIXME: combo box " + txt + " not implemented");
1550          }
1551      }
1552  };
1553  
1554  // the execCommand function (intercepts some commands and replaces them with

1555  // our own implementation)

1556  HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
1557      var editor = this;    // for nested functions

1558      this.focusEditor();
1559      cmdID = cmdID.toLowerCase();
1560      switch (cmdID) {
1561          case "htmlmode" : this.setMode(); break;
1562          case "hilitecolor":
1563          (HTMLArea.is_ie) && (cmdID = "backcolor");
1564          case "forecolor":
1565          this._popupDialog("select_color.html", function(color) {
1566              if (color) { // selection not canceled
1567                  editor._doc.execCommand(cmdID, false, "#" + color);
1568              }
1569          }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID)));
1570          break;
1571          case "createlink":
1572          this._createLink();
1573          break;
1574          case "popupeditor":
1575          // this object will be passed to the newly opened window

1576          HTMLArea._object = this;
1577          if (HTMLArea.is_ie) {
1578              //if (confirm(HTMLArea.I18N.msg["IE-sucks-full-screen"]))

1579              {
1580                  window.open(this.popupURL("fullscreen.html"), "ha_fullscreen",
1581                          "toolbar=no,location=no,directories=no,status=no,menubar=no," +
1582                          "scrollbars=no,resizable=yes,width=640,height=480");
1583              }
1584          } else {
1585              window.open(this.popupURL("fullscreen.html"), "ha_fullscreen",
1586                      "toolbar=no,menubar=no,personalbar=no,width=640,height=480," +
1587                      "scrollbars=no,resizable=yes");
1588          }
1589          break;
1590          case "undo":
1591          case "redo":
1592          if (this._customUndo)
1593              this[cmdID]();
1594          else
1595              this._doc.execCommand(cmdID, UI, param);
1596          break;
1597          case "inserttable": this._insertTable(); break;
1598          case "insertimage": this._insertImage(); break;
1599          case "about"    : this._popupDialog("about.html", null, this); break;
1600          case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break;
1601  
1602          case "killword": this._wordClean(); break;
1603  
1604          case "cut":
1605          case "copy":
1606          case "paste":
1607          try {
1608              if (this.config.killWordOnPaste)
1609                  this._wordClean();
1610              this._doc.execCommand(cmdID, UI, param);
1611          } catch (e) {
1612              if (HTMLArea.is_gecko) {
1613                  if (confirm("Unprivileged scripts cannot access Cut/Copy/Paste programatically " +
1614                          "for security reasons.  Click OK to see a technical note at mozilla.org " +
1615                          "which shows you how to allow a script to access the clipboard."))
1616                      window.open("http://mozilla.org/editor/midasdemo/securityprefs.html");
1617              }
1618          }
1619          break;
1620          case "lefttoright":
1621          case "righttoleft":
1622          var dir = (cmdID == "righttoleft") ? "rtl" : "ltr";
1623          var el = this.getParentElement();
1624          while (el && !HTMLArea.isBlockElement(el))
1625              el = el.parentNode;
1626          if (el) {
1627              if (el.style.direction == dir)
1628                  el.style.direction = "";
1629              else
1630                  el.style.direction = dir;
1631          }
1632          break;
1633          default: this._doc.execCommand(cmdID, UI, param);
1634      }
1635      this.updateToolbar();
1636      return false;
1637  };
1638  
1639  /** A generic event handler for things that happen in the IFRAME's document.

1640   * This function also handles key bindings. */
1641  HTMLArea.prototype._editorEvent = function(ev) {
1642      var editor = this;
1643      var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress");
1644      if (keyEvent) {
1645          for (var i in editor.plugins) {
1646              var plugin = editor.plugins[i].instance;
1647              if (typeof plugin.onKeyPress == "function") plugin.onKeyPress(ev);
1648          }
1649      }
1650      if (keyEvent && ev.ctrlKey) {
1651          var sel = null;
1652          var range = null;
1653          var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
1654          var cmd = null;
1655          var value = null;
1656          switch (key) {
1657              case 'a':
1658              if (!HTMLArea.is_ie) {
1659                  // KEY select all

1660                  sel = this._getSelection();
1661                  sel.removeAllRanges();
1662                  range = this._createRange();
1663                  range.selectNodeContents(this._doc.body);
1664                  sel.addRange(range);
1665                  HTMLArea._stopEvent(ev);
1666              }
1667              break;
1668  
1669              // simple key commands follow

1670  
1671              case 'b': cmd = "bold"; break;
1672              case 'i': cmd = "italic"; break;
1673              case 'u': cmd = "underline"; break;
1674              case 's': cmd = "strikethrough"; break;
1675              case 'l': cmd = "justifyleft"; break;
1676              case 'e': cmd = "justifycenter"; break;
1677              case 'r': cmd = "justifyright"; break;
1678              case 'j': cmd = "justifyfull"; break;
1679              case 'z': cmd = "undo"; break;
1680              case 'y': cmd = "redo"; break;
1681              case 'v': cmd = "paste"; break;
1682  
1683              case '0': cmd = "killword"; break;
1684  
1685              // headings

1686              case '1':
1687              case '2':
1688              case '3':
1689              case '4':
1690              case '5':
1691              case '6':
1692              cmd = "formatblock";
1693              value = "h" + key;
1694              if (HTMLArea.is_ie) {
1695                  value = "<" + value + ">";
1696              }
1697              break;
1698          }
1699          if (cmd) {
1700              // execute simple command

1701              this.execCommand(cmd, false, value);
1702              HTMLArea._stopEvent(ev);
1703          }
1704      }
1705      /*

1706      else if (keyEvent) {

1707          // other keys here

1708          switch (ev.keyCode) {

1709              case 13: // KEY enter

1710              // if (HTMLArea.is_ie) {

1711              this.insertHTML("<br />");

1712              HTMLArea._stopEvent(ev);

1713              // }

1714              break;

1715          }

1716      }

1717      */
1718      // update the toolbar state after some time

1719      if (editor._timerToolbar) {
1720          clearTimeout(editor._timerToolbar);
1721      }
1722      editor._timerToolbar = setTimeout(function() {
1723          editor.updateToolbar();
1724          editor._timerToolbar = null;
1725      }, 50);
1726  };
1727  
1728  // retrieve the HTML

1729  HTMLArea.prototype.getHTML = function() {
1730      switch (this._editMode) {
1731          case "wysiwyg"  :
1732          if (!this.config.fullPage) {
1733              return HTMLArea.getHTML(this._doc.body, false, this);
1734          } else
1735              return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this);
1736          case "textmode" : return this._textArea.value;
1737          default        : alert("Mode <" + mode + "> not defined!");
1738      }
1739      return false;
1740  };
1741  
1742  // retrieve the HTML (fastest version, but uses innerHTML)

1743  HTMLArea.prototype.getInnerHTML = function() {
1744      switch (this._editMode) {
1745          case "wysiwyg"  :
1746          if (!this.config.fullPage)
1747              return this._doc.body.innerHTML;
1748          else
1749              return this.doctype + "\n" + this._doc.documentElement.innerHTML;
1750          case "textmode" : return this._textArea.value;
1751          default        : alert("Mode <" + mode + "> not defined!");
1752      }
1753      return false;
1754  };
1755  
1756  // completely change the HTML inside

1757  HTMLArea.prototype.setHTML = function(html) {
1758      switch (this._editMode) {
1759          case "wysiwyg"  :
1760          if (!this.config.fullPage)
1761              this._doc.body.innerHTML = html;
1762          else
1763              // this._doc.documentElement.innerHTML = html;

1764              this._doc.body.innerHTML = html;
1765          break;
1766          case "textmode" : this._textArea.value = html; break;
1767          default        : alert("Mode <" + mode + "> not defined!");
1768      }
1769      return false;
1770  };
1771  
1772  // sets the given doctype (useful when config.fullPage is true)

1773  HTMLArea.prototype.setDoctype = function(doctype) {
1774      this.doctype = doctype;
1775  };
1776  
1777  /***************************************************

1778   *  Category: UTILITY FUNCTIONS

1779   ***************************************************/
1780  
1781  // browser identification

1782  
1783  HTMLArea.agt = navigator.userAgent.toLowerCase();
1784  HTMLArea.is_ie       = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
1785  HTMLArea.is_opera  = (HTMLArea.agt.indexOf("opera") != -1);
1786  HTMLArea.is_mac       = (HTMLArea.agt.indexOf("mac") != -1);
1787  HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
1788  HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
1789  HTMLArea.is_gecko  = (navigator.product == "Gecko");
1790  
1791  // variable used to pass the object to the popup editor window.

1792  HTMLArea._object = null;
1793  
1794  // function that returns a clone of the given object

1795  HTMLArea.cloneObject = function(obj) {
1796      var newObj = new Object;
1797  
1798      // check for array objects

1799      if (obj.constructor.toString().indexOf("function Array(") == 1) {
1800          newObj = obj.constructor();
1801      }
1802  
1803      // check for function objects (as usual, IE is fucked up)

1804      if (obj.constructor.toString().indexOf("function Function(") == 1) {
1805          newObj = obj; // just copy reference to it

1806      } else for (var n in obj) {
1807          var node = obj[n];
1808          if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); }
1809          else                         { newObj[n] = node; }
1810      }
1811  
1812      return newObj;
1813  };
1814  
1815  // FIXME!!! this should return false for IE < 5.5

1816  HTMLArea.checkSupportedBrowser = function() {
1817      if (HTMLArea.is_gecko) {
1818          if (navigator.productSub < 20021201) {
1819              alert("You need at least Mozilla-1.3 Alpha.\n" +
1820                    "Sorry, your Gecko is not supported.");
1821              return false;
1822          }
1823          if (navigator.productSub < 20030210) {
1824              alert("Mozilla < 1.3 Beta is not supported!\n" +
1825                    "I'll try, though, but it might not work.");
1826          }
1827      }
1828      return HTMLArea.is_gecko || HTMLArea.is_ie;
1829  };
1830  
1831  // selection & ranges

1832  
1833  // returns the current selection object

1834  HTMLArea.prototype._getSelection = function() {
1835      if (HTMLArea.is_ie) {
1836          return this._doc.selection;
1837      } else {
1838          return this._iframe.contentWindow.getSelection();
1839      }
1840  };
1841  
1842  // returns a range for the current selection

1843  HTMLArea.prototype._createRange = function(sel) {
1844      if (HTMLArea.is_ie) {
1845          return sel.createRange();
1846      } else {
1847          this.focusEditor();
1848          if (typeof sel != "undefined") {
1849              try {
1850                  return sel.getRangeAt(0);
1851              } catch(e) {
1852                  return this._doc.createRange();
1853              }
1854          } else {
1855              return this._doc.createRange();
1856          }
1857      }
1858  };
1859  
1860  // event handling

1861  
1862  HTMLArea._addEvent = function(el, evname, func) {
1863      if (HTMLArea.is_ie) {
1864          el.attachEvent("on" + evname, func);
1865      } else {
1866          el.addEventListener(evname, func, true);
1867      }
1868  };
1869  
1870  HTMLArea._addEvents = function(el, evs, func) {
1871      for (var i in evs) {
1872          HTMLArea._addEvent(el, evs[i], func);
1873      }
1874  };
1875  
1876  HTMLArea._removeEvent = function(el, evname, func) {
1877      if (HTMLArea.is_ie) {
1878          el.detachEvent("on" + evname, func);
1879      } else {
1880          el.removeEventListener(evname, func, true);
1881      }
1882  };
1883  
1884  HTMLArea._removeEvents = function(el, evs, func) {
1885      for (var i in evs) {
1886          HTMLArea._removeEvent(el, evs[i], func);
1887      }
1888  };
1889  
1890  HTMLArea._stopEvent = function(ev) {
1891      if (HTMLArea.is_ie) {
1892          ev.cancelBubble = true;
1893          ev.returnValue = false;
1894      } else {
1895          ev.preventDefault();
1896          ev.stopPropagation();
1897      }
1898  };
1899  
1900  HTMLArea._removeClass = function(el, className) {
1901      if (!(el && el.className)) {
1902          return;
1903      }
1904      var cls = el.className.split(" ");
1905      var ar = new Array();
1906      for (var i = cls.length; i > 0;) {
1907          if (cls[--i] != className) {
1908              ar[ar.length] = cls[i];
1909          }
1910      }
1911      el.className = ar.join(" ");
1912  };
1913  
1914  HTMLArea._addClass = function(el, className) {
1915      // remove the class first, if already there

1916      HTMLArea._removeClass(el, className);
1917      el.className += " " + className;
1918  };
1919  
1920  HTMLArea._hasClass = function(el, className) {
1921      if (!(el && el.className)) {
1922          return false;
1923      }
1924      var cls = el.className.split(" ");
1925      for (var i = cls.length; i > 0;) {
1926          if (cls[--i] == className) {
1927              return true;
1928          }
1929      }
1930      return false;
1931  };
1932  
1933  HTMLArea.isBlockElement = function(el) {
1934      var blockTags = " body form textarea fieldset ul ol dl li div " +
1935          "p h1 h2 h3 h4 h5 h6 quote pre table thead " +
1936          "tbody tfoot tr td iframe address ";
1937      return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
1938  };
1939  
1940  HTMLArea.needsClosingTag = function(el) {
1941      var closingTags = " head script style div span tr td tbody table em strong font a title ";
1942      return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
1943  };
1944  
1945  // performs HTML encoding of some given string

1946  HTMLArea.htmlEncode = function(str) {
1947      // we don't need regexp for that, but.. so be it for now.

1948      str = str.replace(/&/ig, "&amp;");
1949      str = str.replace(/</ig, "&lt;");
1950      str = str.replace(/>/ig, "&gt;");
1951      str = str.replace(/\x22/ig, "&quot;");
1952      // \x22 means '"' -- we use hex reprezentation so that we don't disturb

1953      // JS compressors (well, at least mine fails.. ;)

1954      return str;
1955  };
1956  
1957  // Retrieves the HTML code from the given node.     This is a replacement for

1958  // getting innerHTML, using standard DOM calls.

1959  HTMLArea.getHTML = function(root, outputRoot, editor) {
1960      var html = "";
1961      switch (root.nodeType) {
1962          case 1: // Node.ELEMENT_NODE
1963          case 11: // Node.DOCUMENT_FRAGMENT_NODE
1964          var closed;
1965          var i;
1966          var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
1967          if (HTMLArea.is_ie && root_tag == "head") {
1968              if (outputRoot)
1969                  html += "<head>";
1970              // lowercasize

1971              var save_multiline = RegExp.multiline;
1972              RegExp.multiline = true;
1973              var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
1974                  return p1 + p2.toLowerCase();
1975              });
1976              RegExp.multiline = save_multiline;
1977              html += txt;
1978              if (outputRoot)
1979                  html += "</head>";
1980              break;
1981          } else if (outputRoot) {
1982              closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root)));
1983              html = "<" + root.tagName.toLowerCase();
1984              var attrs = root.attributes;
1985              for (i = 0; i < attrs.length; ++i) {
1986                  var a = attrs.item(i);
1987                  if (!a.specified) {
1988                      continue;
1989                  }
1990                  var name = a.nodeName.toLowerCase();
1991                  if (/_moz|contenteditable|_msh/.test(name)) {
1992                      // avoid certain attributes

1993                      continue;
1994                  }
1995                  var value;
1996                  if (name != "style") {
1997                      // IE5.5 reports 25 when cellSpacing is

1998                      // 1; other values might be doomed too.

1999                      // For this reason we extract the

2000                      // values directly from the root node.

2001                      // I'm starting to HATE JavaScript

2002                      // development.  Browser differences

2003                      // suck.

2004                      //

2005                      // Using Gecko the values of href and src are converted to absolute links

2006                      // unless we get them using nodeValue()

2007                      if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") {
2008                          value = root[a.nodeName];
2009                      } else {
2010                          value = a.nodeValue;
2011                          // IE seems not willing to return the original values - it converts to absolute

2012                          // links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href")

2013                          // So we have to strip the baseurl manually -/

2014                          if (HTMLArea.is_ie && (name == "href" || name == "src")) {
2015                              value = editor.stripBaseURL(value);
2016                          }
2017                      }
2018                  } else { // IE fails to put style in attributes list
2019                      // FIXME: cssText reported by IE is UPPERCASE

2020                      value = root.style.cssText;
2021                  }
2022                  if (/(_moz|^$)/.test(value)) {
2023                      // Mozilla reports some special tags

2024                      // here; we don't need them.

2025                      continue;
2026                  }
2027                  html += " " + name + '="' + value + '"';
2028              }
2029              html += closed ? " />" : ">";
2030          }
2031          for (i = root.firstChild; i; i = i.nextSibling) {
2032              html += HTMLArea.getHTML(i, true, editor);
2033          }
2034          if (outputRoot && !closed) {
2035              html += "</" + root.tagName.toLowerCase() + ">";
2036          }
2037          break;
2038          case 3: // Node.TEXT_NODE
2039          // If a text node is alone in an element and all spaces, replace it with an non breaking one

2040          // This partially undoes the damage done by moz, which translates '&nbsp;'s into spaces in the data element

2041          if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) ) html = '&nbsp;';
2042          else html = HTMLArea.htmlEncode(root.data);
2043          break;
2044          case 8: // Node.COMMENT_NODE
2045          html = "<!--" + root.data + "-->";
2046          break;        // skip comments, for now.

2047      }
2048      return html;
2049  };
2050  
2051  HTMLArea.prototype.stripBaseURL = function(string) {
2052      var baseurl = this.config.baseURL;
2053  
2054      // strip to last directory in case baseurl points to a file

2055      baseurl = baseurl.replace(/[^\/]+$/, '');
2056      var basere = new RegExp(baseurl);
2057      string = string.replace(basere, "");
2058  
2059      // strip host-part of URL which is added by MSIE to links relative to server root

2060      baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
2061      basere = new RegExp(baseurl);
2062      return string.replace(basere, "");
2063  };
2064  
2065  String.prototype.trim = function() {
2066      a = this.replace(/^\s+/, '');
2067      return a.replace(/\s+$/, '');
2068  };
2069  
2070  // creates a rgb-style color from a number

2071  HTMLArea._makeColor = function(v) {
2072      if (typeof v != "number") {
2073          // already in rgb (hopefully); IE doesn't get here.

2074          return v;
2075      }
2076      // IE sends number; convert to rgb.

2077      var r = v & 0xFF;
2078      var g = (v >> 8) & 0xFF;
2079      var b = (v >> 16) & 0xFF;
2080      return "rgb(" + r + "," + g + "," + b + ")";
2081  };
2082  
2083  // returns hexadecimal color representation from a number or a rgb-style color.

2084  HTMLArea._colorToRgb = function(v) {
2085      if (!v)
2086          return '';
2087  
2088      // returns the hex representation of one byte (2 digits)

2089  	function hex(d) {
2090          return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
2091      };
2092  
2093      if (typeof v == "number") {
2094          // we're talking to IE here

2095          var r = v & 0xFF;
2096          var g = (v >> 8) & 0xFF;
2097          var b = (v >> 16) & 0xFF;
2098          return "#" + hex(r) + hex(g) + hex(b);
2099      }
2100  
2101      if (v.substr(0, 3) == "rgb") {
2102          // in rgb(...) form -- Mozilla

2103          var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2104          if (v.match(re)) {
2105              var r = parseInt(RegExp.$1);
2106              var g = parseInt(RegExp.$2);
2107              var b = parseInt(RegExp.$3);
2108              return "#" + hex(r) + hex(g) + hex(b);
2109          }
2110          // doesn't match RE?!  maybe uses percentages or float numbers

2111          // -- FIXME: not yet implemented.

2112          return null;
2113      }
2114  
2115      if (v.substr(0, 1) == "#") {
2116          // already hex rgb (hopefully :D )

2117          return v;
2118      }
2119  
2120      // if everything else fails ;)

2121      return null;
2122  };
2123  
2124  // modal dialogs for Mozilla (for IE we're using the showModalDialog() call).

2125  
2126  // receives an URL to the popup dialog and a function that receives one value;

2127  // this function will get called after the dialog is closed, with the return

2128  // value of the dialog.

2129  HTMLArea.prototype._popupDialog = function(url, action, init) {
2130      Dialog(this.popupURL(url), action, init);
2131  };
2132  
2133  // paths

2134  
2135  HTMLArea.prototype.imgURL = function(file, plugin) {
2136      if (typeof plugin == "undefined")
2137          return _editor_url + file;
2138      else
2139          return _editor_url + "plugins/" + plugin + "/img/" + file;
2140  };
2141  
2142  HTMLArea.prototype.popupURL = function(file) {
2143      var url = "";
2144      if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
2145          var plugin = RegExp.$1;
2146          var popup = RegExp.$2;
2147          if (!/\.html$/.test(popup))
2148              popup += ".html";
2149          url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
2150      } else
2151          url = _editor_url + this.config.popupURL + file;
2152      return url;
2153  };
2154  
2155  /**

2156   * FIX: Internet Explorer returns an item having the _name_ equal to the given

2157   * id, even if it's not having any id.  This way it can return a different form

2158   * field even if it's not a textarea.  This workarounds the problem by

2159   * specifically looking to search only elements having a certain tag name.

2160   */
2161  HTMLArea.getElementById = function(tag, id) {
2162      var el, i, objs = document.getElementsByTagName(tag);
2163      for (i = objs.length; --i >= 0 && (el = objs[i]);)
2164          if (el.id == id)
2165              return el;
2166      return null;
2167  };
2168  
2169  
2170  
2171  // EOF

2172  // Local variables: //

2173  // c-basic-offset:8 //

2174  // indent-tabs-mode:t //

2175  // End: //



Généré le : Sat Feb 24 14:40:03 2007 par Balluche grâce à PHPXref 0.7