[ Index ]
 

Code source de Zen Cart E-Commerce Shopping Cart 1.3.7.1

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

/editors/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  // Zen Cart customization version info:

  13  // $Id: htmlarea.js 4367 2006-09-03 19:30:17Z drbyte $

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

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

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

  31  // ID with it.

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

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

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

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

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

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

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

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

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

  90      // in the size or not.

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

  94      // <HTML> tag.

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

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

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

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

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

 113       * -------------------------

 114       *

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

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

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

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

 119       * upgrading HTMLArea.

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

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

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

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

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

 181      //              it has the following prototype:

 182      //                 function(editor, buttonName)

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

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

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

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

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

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

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

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

 224       * ---------------------

 225       *

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

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

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

 229       * likely that you will have problems upgrading HTMLArea.

 230       *

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

 232       *

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

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

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

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

 237       *      "Highlight selection", // tooltip

 238       *      "my_hilite.gif", // image

 239       *      false // disabled in text mode

 240       *    ];

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

 242       *

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

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

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

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

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

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

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

 260   *

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

 262   * 2. config.registerButton({

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

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

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

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

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

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

 269   *                 },

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

 271   *    });

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

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

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

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

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

 298   * buttons.  Call it like this:

 299   *

 300   * FIXME: add example

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

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

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

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

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

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

 316   * and end with a space.  Example:

 317   *

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

 319   *

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

 321   * create a brand new toolbar ;-)

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

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

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

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

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

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

 371          // in the TABLE.

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

 377      // init first line

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

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

 382      // createSelect functions below).

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

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

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

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

 412      // doesn't match a button.

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

 424              // configuration option because the variable name

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

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

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

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

 429              // config["formatblock"].

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

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

 469  
 470      // appends a new button to toolbar

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

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

 511              // assign all the needed information to it.

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

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

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

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

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

 606      // creates a holder for the path view

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

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

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

 621      // get the textarea

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

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

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

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

 643          // update original textarea.

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

 656              var previous_return = true;     //fix onsubmit bug where interaction with other javascript fails if "submit=fail"

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

 667      // the HTML content into the original textarea.

 668      window.onunload = function() {
 669          editor._textArea.value = editor.getHTML();
 670      };
 671  
 672      // creates & appends the toolbar

 673      this._createToolbar();
 674  
 675      // create the IFRAME

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

 682      this._createStatusBar();
 683  
 684      // remove the default border as it keeps us from computing correctly

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

 686  
 687      if (!HTMLArea.is_ie) {
 688          iframe.style.borderWidth = "1px";
 689      // iframe.frameBorder = "1";

 690      // iframe.marginHeight = "0";

 691      // iframe.marginWidth = "0";

 692      }
 693  
 694      // size the IFRAME according to user's prefs or initial textarea

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

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

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

 718      textarea.style.width = iframe.style.width;
 719       textarea.style.height = iframe.style.height;
 720  
 721      // IMPORTANT: we have to allow Mozilla a short time to recognize the

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

 723  	function initIframe() {
 724          var doc = editor._iframe.contentWindow.document;
 725          if (!doc) {
 726              // Try again..

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

 728              // we'll never reach this point.

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

 738              doc.designMode = "on";
 739          }
 740          editor._doc = doc;
 741          if (!editor.config.fullPage) {
 742              doc.open();
 743              var html = "<html>\n";
 744              html += "<head>\n";
 745              if (editor.config.baseURL)
 746                  html += '<base href="' + editor.config.baseURL + '" />';
 747  html += "<style>" + editor.config.pageStyle + 
 748  " html,body { border: 0px; }</style>\n"; 
 749  //used above lines to fix bug based on info on htmlarea support forum

 750  //html += "<style> html,body { border: 0px; } " +

 751  //                editor.config.pageStyle + "</style>\n";

 752              html += "</head>\n";
 753              html += "<body>\n";
 754              html += editor._textArea.value;
 755              html += "</body>\n";
 756              html += "</html>";
 757              doc.write(html);
 758              doc.close();
 759          } else {
 760              var html = editor._textArea.value;
 761              if (html.match(HTMLArea.RE_doctype)) {
 762                  editor.setDoctype(RegExp.$1);
 763                  html = html.replace(HTMLArea.RE_doctype, "");
 764              }
 765              doc.open();
 766              doc.write(html);
 767              doc.close();
 768          }
 769  
 770          if (HTMLArea.is_ie) {
 771              // enable editable mode for IE.     For some reason this

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

 773              // (above).

 774              doc.body.contentEditable = true;
 775          }
 776  
 777  //        editor.focusEditor();

 778          // intercept some events; for updating the toolbar & keyboard handlers

 779          HTMLArea._addEvents
 780              (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
 781               function (event) {
 782                   return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
 783               });
 784  
 785          // check if any plugins have registered refresh handlers

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

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

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

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

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

 875   *  Category: PLUGINS

 876   ***************************************************/
 877  
 878  // this is the variant of the function above where the plugin arguments are

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

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

 881  // parameters as in the opener window.

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

 899  HTMLArea.prototype.registerPlugin = function() {
 900      var plugin = arguments[0];
 901      var args = [];
 902      for (var i = 1; i < arguments.length; ++i)
 903          args.push(arguments[i]);
 904      this.registerPlugin2(plugin, args);
 905  };
 906  
 907  // static function that loads the required plugin and lang file, based on the

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

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

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

 934   *  Category: EDITOR UTILITIES

 935   ***************************************************/
 936  
 937  // The following function is a slight variation of the word cleaner code posted

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

 939  HTMLArea.prototype._wordClean = function() {
 940      var D = this.getInnerHTML();
 941      if (D.indexOf('class=Mso') >= 0) {
 942  
 943          // make one line

 944          D = D.replace(/\r\n/g, ' ').
 945              replace(/\n/g, ' ').
 946              replace(/\r/g, ' ').
 947              replace(/\&nbsp\;/g,' ');
 948  
 949          // keep tags, strip attributes

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

 952              replace(/ style=\"[^>]*\"/gi,'').
 953              replace(/ align=[^\s|>]*/gi,'');
 954  
 955          //clean up tags

 956          D = D.replace(/<b [^>]*>/gi,'<b>').
 957              replace(/<i [^>]*>/gi,'<i>').
 958              replace(/<li [^>]*>/gi,'<li>').
 959              replace(/<ul [^>]*>/gi,'<ul>');
 960  
 961          // replace outdated tags

 962          D = D.replace(/<b>/gi,'<strong>').
 963              replace(/<\/b>/gi,'</strong>');
 964  
 965          // mozilla doesn't like <em> tags

 966          D = D.replace(/<em>/gi,'<i>').
 967              replace(/<\/em>/gi,'</i>');
 968  
 969          // kill unwanted tags

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

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

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

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

 983  
 984          // nuke double tags

 985          oldlen = D.length + 1;
 986          while(oldlen > D.length) {
 987              oldlen = D.length;
 988              // join us now and free the tags, we'll be free hackers, we'll be free... ;-)

 989              D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' ').
 990                  replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>');
 991          }
 992          D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>').
 993              replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>');
 994  
 995          // nuke double spaces

 996          D = D.replace(/  */gi,' ');
 997  
 998          this.setHTML(D);
 999          this.updateToolbar();
1000      }
1001  };
1002  
1003  HTMLArea.prototype.forceRedraw = function() {
1004      this._doc.body.style.visibility = "hidden";
1005      this._doc.body.style.visibility = "visible";
1006      // this._doc.body.innerHTML = this.getInnerHTML();

1007  };
1008  
1009  // focuses the iframe window.  returns a reference to the editor document.

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

1020  HTMLArea.prototype._undoTakeSnapshot = function() {
1021      ++this._undoPos;
1022      if (this._undoPos >= this.config.undoSteps) {
1023          // remove the first element

1024          this._undoQueue.shift();
1025          --this._undoPos;
1026      }
1027      // use the fasted method (getInnerHTML);

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

1056  HTMLArea.prototype.updateToolbar = function(noStatus) {
1057      var doc = this._doc;
1058      var text = (this._editMode == "textmode");
1059      var ancestors = null;
1060      if (!text) {
1061          ancestors = this.getAllAncestors();
1062          if (this.config.statusBar && !noStatus) {
1063              this._statusBarTree.innerHTML = HTMLArea.I18N.msg["Path"] + ": "; // clear

1064              for (var i = ancestors.length; --i >= 0;) {
1065                  var el = ancestors[i];
1066                  if (!el) {
1067                      // hell knows why we get here; this

1068                      // could be a classic example of why

1069                      // it's good to check for conditions

1070                      // that are impossible to happen ;-)

1071                      continue;
1072                  }
1073                  var a = document.createElement("a");
1074                  a.href = "#";
1075                  a.el = el;
1076                  a.editor = this;
1077                  a.onclick = function() {
1078                      this.blur();
1079                      this.editor.selectNodeContents(this.el);
1080                      this.editor.updateToolbar(true);
1081                      return false;
1082                  };
1083                  a.oncontextmenu = function() {
1084                      // TODO: add context menu here

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

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

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

1158                      break;
1159                  }
1160                  // HACK -- retrieve the config option for this

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

1162                  // variable in config has the same name as

1163                  // button name in the toolbar.

1164                  var options = this.config[cmd];
1165                  var k = 0;
1166                  // btn.element.selectedIndex = 0;

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

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

1185                          backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor"));
1186                      }
1187                      color = HTMLArea._makeColor(doc.queryCommandValue("forecolor"));
1188                      fontFamily = doc.queryCommandValue("fontname");
1189                      fontWeight = doc.queryCommandState("bold") ? "bold" : "normal";
1190                      fontStyle = doc.queryCommandState("italic") ? "italic" : "normal";
1191                  }} catch (e) {
1192                      // alert(e + "\n\n" + cmd);

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

1212      if (this._customUndo && !this._timerUndo) {
1213          this._undoTakeSnapshot();
1214          var editor = this;
1215          this._timerUndo = setTimeout(function() {
1216              editor._timerUndo = null;
1217          }, this.config.undoTimeout);
1218      }
1219      // check if any plugins have registered refresh handlers

1220      for (var i in this.plugins) {
1221          var plugin = this.plugins[i].instance;
1222          if (typeof plugin.onUpdateToolbar == "function")
1223              plugin.onUpdateToolbar();
1224      }
1225  };
1226  
1227  /** Returns a node after which we can insert other nodes, in the current

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

1229   */
1230  HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
1231      if (!HTMLArea.is_ie) {
1232          var sel = this._getSelection();
1233          var range = this._createRange(sel);
1234          // remove the current selection

1235          sel.removeAllRanges();
1236          range.deleteContents();
1237          var node = range.startContainer;
1238          var pos = range.startOffset;
1239          switch (node.nodeType) {
1240              case 3: // Node.TEXT_NODE
1241              // we have to split it at the caret position.

1242              if (toBeInserted.nodeType == 3) {
1243                  // do optimized insertion

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

1272      }
1273  };
1274  
1275  // Returns the deepest node that contains both endpoints of the selection.

1276  HTMLArea.prototype.getParentElement = function() {
1277      var sel = this._getSelection();
1278      var range = this._createRange(sel);
1279      if (HTMLArea.is_ie) {
1280          switch (sel.type) {
1281              case "Text":
1282              case "None":
1283              // It seems that even for selection of type "None",

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

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

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

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

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

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

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

1302          */
1303          while (p.nodeType == 3) {
1304              p = p.parentNode;
1305          }
1306          return p;
1307      } catch (e) {
1308          return null;
1309      }
1310  };
1311  
1312  // Returns an array with all the ancestor nodes of the selection.

1313  HTMLArea.prototype.getAllAncestors = function() {
1314      var p = this.getParentElement();
1315      var a = [];
1316      while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1317          a.push(p);
1318          p = p.parentNode;
1319      }
1320      a.push(this._doc.body);
1321      return a;
1322  };
1323  
1324  // Selects the contents inside the given node

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

1346   * the selection, if any.

1347   */
1348  HTMLArea.prototype.insertHTML = function(html) {
1349      var sel = this._getSelection();
1350      var range = this._createRange(sel);
1351      if (HTMLArea.is_ie) {
1352          range.pasteHTML(html);
1353      } else {
1354          // construct a new document fragment with the given HTML

1355          var fragment = this._doc.createDocumentFragment();
1356          var div = this._doc.createElement("div");
1357          div.innerHTML = html;
1358          while (div.firstChild) {
1359              // the following call also removes the node from div

1360              fragment.appendChild(div.firstChild);
1361          }
1362          // this also removes the selection

1363          var node = this.insertNodeAtSelection(fragment);
1364      }
1365  };
1366  
1367  /**

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

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

1370   */
1371  HTMLArea.prototype.surroundHTML = function(startTag, endTag) {
1372      var html = this.getSelectedHTML();
1373      // the following also deletes the selection

1374      this.insertHTML(startTag + html + endTag);
1375  };
1376  
1377  /// Retrieve the selected block

1378  HTMLArea.prototype.getSelectedHTML = function() {
1379      var sel = this._getSelection();
1380      var range = this._createRange(sel);
1381      var existing = null;
1382      if (HTMLArea.is_ie) {
1383          existing = range.htmlText;
1384      } else {
1385          existing = HTMLArea.getHTML(range.cloneContents(), false, this);
1386      }
1387      return existing;
1388  };
1389  
1390  /// Return true if we have some selection

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

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

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

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

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

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

1486  HTMLArea.prototype._insertTable = function() {
1487      var sel = this._getSelection();
1488      var range = this._createRange(sel);
1489      var editor = this;    // for nested functions

1490      this._popupDialog("insert_table.html", function(param) {
1491          if (!param) {    // user must have pressed Cancel
1492              return false;
1493          }
1494          var doc = editor._doc;
1495          // create the table element

1496          var table = doc.createElement("table");
1497          // assign the given arguments

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

1520                  (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br"));
1521              }
1522          }
1523          if (HTMLArea.is_ie) {
1524              range.pasteHTML(table.outerHTML);
1525          } else {
1526              // insert the table

1527              editor.insertNodeAtSelection(table);
1528          }
1529          return true;
1530      }, null);
1531  };
1532  
1533  /***************************************************

1534   *  Category: EVENT HANDLERS

1535   ***************************************************/
1536  
1537  // el is reference to the SELECT object

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

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

1551          var dropdown = this.config.customSelects[txt];
1552          if (typeof dropdown != "undefined") {
1553              dropdown.action(this);
1554          } else {
1555              alert("FIXME: combo box " + txt + " not implemented");
1556          }
1557      }
1558  };
1559  
1560  // the execCommand function (intercepts some commands and replaces them with

1561  // our own implementation)

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

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

1582          HTMLArea._object = this;
1583          if (HTMLArea.is_ie) {
1584              //if (confirm(HTMLArea.I18N.msg["IE-sucks-full-screen"]))

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

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

1666                  sel = this._getSelection();
1667                  sel.removeAllRanges();
1668                  range = this._createRange();
1669                  range.selectNodeContents(this._doc.body);
1670                  sel.addRange(range);
1671                  HTMLArea._stopEvent(ev);
1672              }
1673              break;
1674  
1675              // simple key commands follow

1676  
1677              case 'b': cmd = "bold"; break;
1678              case 'i': cmd = "italic"; break;
1679              case 'u': cmd = "underline"; break;
1680              case 's': cmd = "strikethrough"; break;
1681              case 'l': cmd = "justifyleft"; break;
1682              case 'e': cmd = "justifycenter"; break;
1683              case 'r': cmd = "justifyright"; break;
1684              case 'j': cmd = "justifyfull"; break;
1685              case 'z': cmd = "undo"; break;
1686              case 'y': cmd = "redo"; break;
1687              case 'v': cmd = "paste"; break;
1688  
1689              case '0': cmd = "killword"; break;
1690  
1691              // headings

1692              case '1':
1693              case '2':
1694              case '3':
1695              case '4':
1696              case '5':
1697              case '6':
1698              cmd = "formatblock";
1699              value = "h" + key;
1700              if (HTMLArea.is_ie) {
1701                  value = "<" + value + ">";
1702              }
1703              break;
1704          }
1705          if (cmd) {
1706              // execute simple command

1707              this.execCommand(cmd, false, value);
1708              HTMLArea._stopEvent(ev);
1709          }
1710      }
1711      /*

1712      else if (keyEvent) {

1713          // other keys here

1714          switch (ev.keyCode) {

1715              case 13: // KEY enter

1716              // if (HTMLArea.is_ie) {

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

1718              HTMLArea._stopEvent(ev);

1719              // }

1720              break;

1721          }

1722      }

1723      */
1724      // update the toolbar state after some time

1725      if (editor._timerToolbar) {
1726          clearTimeout(editor._timerToolbar);
1727      }
1728      editor._timerToolbar = setTimeout(function() {
1729          editor.updateToolbar();
1730          editor._timerToolbar = null;
1731      }, 50);
1732  };
1733  
1734  // retrieve the HTML

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

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

1763  HTMLArea.prototype.setHTML = function(html) {
1764      switch (this._editMode) {
1765          case "wysiwyg"  :
1766          if (!this.config.fullPage)
1767              this._doc.body.innerHTML = html;
1768          else
1769              // this._doc.documentElement.innerHTML = html;

1770              this._doc.body.innerHTML = html;
1771          break;
1772          case "textmode" : this._textArea.value = html; break;
1773          default        : alert("Mode <" + mode + "> not defined!");
1774      }
1775      return false;
1776  };
1777  
1778  // sets the given doctype (useful when config.fullPage is true)

1779  HTMLArea.prototype.setDoctype = function(doctype) {
1780      this.doctype = doctype;
1781  };
1782  
1783  /***************************************************

1784   *  Category: UTILITY FUNCTIONS

1785   ***************************************************/
1786  
1787  // browser identification

1788  
1789  HTMLArea.agt = navigator.userAgent.toLowerCase();
1790  HTMLArea.is_ie       = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
1791  HTMLArea.is_opera  = (HTMLArea.agt.indexOf("opera") != -1);
1792  HTMLArea.is_mac       = (HTMLArea.agt.indexOf("mac") != -1);
1793  HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
1794  HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
1795  HTMLArea.is_gecko  = (navigator.product == "Gecko");
1796  
1797  // variable used to pass the object to the popup editor window.

1798  HTMLArea._object = null;
1799  
1800  // function that returns a clone of the given object

1801  HTMLArea.cloneObject = function(obj) {
1802      var newObj = new Object;
1803  
1804      // check for array objects

1805      if (obj.constructor.toString().indexOf("function Array(") == 1) {
1806          newObj = obj.constructor();
1807      }
1808  
1809      // check for function objects (as usual, IE is fucked up)

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

1812      } else for (var n in obj) {
1813          var node = obj[n];
1814          if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); }
1815          else                         { newObj[n] = node; }
1816      }
1817  
1818      return newObj;
1819  };
1820  
1821  // FIXME!!! this should return false for IE < 5.5

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

1838  
1839  // returns the current selection object

1840  HTMLArea.prototype._getSelection = function() {
1841      if (HTMLArea.is_ie) {
1842          return this._doc.selection;
1843      } else {
1844          return this._iframe.contentWindow.getSelection();
1845      }
1846  };
1847  
1848  // returns a range for the current selection

1849  HTMLArea.prototype._createRange = function(sel) {
1850      if (HTMLArea.is_ie) {
1851          return sel.createRange();
1852      } else {
1853          this.focusEditor();
1854          if (typeof sel != "undefined") {
1855              try {
1856                  return sel.getRangeAt(0);
1857              } catch(e) {
1858                  return this._doc.createRange();
1859              }
1860          } else {
1861              return this._doc.createRange();
1862          }
1863      }
1864  };
1865  
1866  // event handling

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

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

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

1954      str = str.replace(/&/ig, "&amp;");
1955      str = str.replace(/</ig, "&lt;");
1956      str = str.replace(/>/ig, "&gt;");
1957      str = str.replace(/\x22/ig, "&quot;");
1958      // \x22 means '"' -- we use hex reprezentation so that we don't disturb

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

1960      return str;
1961  };
1962  
1963  // Retrieves the HTML code from the given node.     This is a replacement for

1964  // getting innerHTML, using standard DOM calls.

1965  HTMLArea.getHTML = function(root, outputRoot, editor) {
1966      var html = "";
1967      switch (root.nodeType) {
1968          case 1: // Node.ELEMENT_NODE
1969          case 11: // Node.DOCUMENT_FRAGMENT_NODE
1970          var closed;
1971          var i;
1972          var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
1973          if (HTMLArea.is_ie && root_tag == "head") {
1974              if (outputRoot)
1975                  html += "<head>";
1976              // lowercasize

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

1999                      continue;
2000                  }
2001                  var value;
2002                  if (name != "style") {
2003                      // IE5.5 reports 25 when cellSpacing is

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

2005                      // For this reason we extract the

2006                      // values directly from the root node.

2007                      // I'm starting to HATE JavaScript

2008                      // development.  Browser differences

2009                      // suck.

2010                      //

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

2012                      // unless we get them using nodeValue()

2013                      if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") {
2014                          value = root[a.nodeName];
2015                      } else {
2016                          value = a.nodeValue;
2017                          // IE seems not willing to return the original values - it converts to absolute

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

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

2020                          if (HTMLArea.is_ie && (name == "href" || name == "src")) {
2021                              value = editor.stripBaseURL(value);
2022                          }
2023                      }
2024                  } else { // IE fails to put style in attributes list
2025                      // FIXME: cssText reported by IE is UPPERCASE

2026                      value = root.style.cssText;
2027                  }
2028                  if (/(_moz|^$)/.test(value)) {
2029                      // Mozilla reports some special tags

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

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

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

2047          if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) ) html = '&nbsp;';
2048          else html = HTMLArea.htmlEncode(root.data);
2049          break;
2050          case 8: // Node.COMMENT_NODE
2051          html = "<!--" + root.data + "-->";
2052          break;        // skip comments, for now.

2053      }
2054      return html;
2055  };
2056  
2057  HTMLArea.prototype.stripBaseURL = function(string) {
2058      var baseurl = this.config.baseURL;
2059  
2060      // strip to last directory in case baseurl points to a file

2061      baseurl = baseurl.replace(/[^\/]+$/, '');
2062      var basere = new RegExp(baseurl);
2063  //    string = string.replace(basere, "");

2064  
2065      // strip host-part of URL which is added by MSIE to links relative to server root

2066      baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
2067      basere = new RegExp(baseurl);
2068      return string; //.replace(basere, "");

2069  };
2070  
2071  String.prototype.trim = function() {
2072      a = this.replace(/^\s+/, '');
2073      return a.replace(/\s+$/, '');
2074  };
2075  
2076  // creates a rgb-style color from a number

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

2080          return v;
2081      }
2082      // IE sends number; convert to rgb.

2083      var r = v & 0xFF;
2084      var g = (v >> 8) & 0xFF;
2085      var b = (v >> 16) & 0xFF;
2086      return "rgb(" + r + "," + g + "," + b + ")";
2087  };
2088  
2089  // returns hexadecimal color representation from a number or a rgb-style color.

2090  HTMLArea._colorToRgb = function(v) {
2091      if (!v)
2092          return '';
2093  
2094      // returns the hex representation of one byte (2 digits)

2095  	function hex(d) {
2096          return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
2097      };
2098  
2099      if (typeof v == "number") {
2100          // we're talking to IE here

2101          var r = v & 0xFF;
2102          var g = (v >> 8) & 0xFF;
2103          var b = (v >> 16) & 0xFF;
2104          return "#" + hex(r) + hex(g) + hex(b);
2105      }
2106  
2107      if (v.substr(0, 3) == "rgb") {
2108          // in rgb(...) form -- Mozilla

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

2117          // -- FIXME: not yet implemented.

2118          return null;
2119      }
2120  
2121      if (v.substr(0, 1) == "#") {
2122          // already hex rgb (hopefully :D )

2123          return v;
2124      }
2125  
2126      // if everything else fails ;)

2127      return null;
2128  };
2129  
2130  // modal dialogs for Mozilla (for IE we're using the showModalDialog() call).

2131  
2132  // receives an URL to the popup dialog and a function that receives one value;

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

2134  // value of the dialog.

2135  HTMLArea.prototype._popupDialog = function(url, action, init) {
2136      Dialog(this.popupURL(url), action, init);
2137  };
2138  
2139  // paths

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

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

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

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

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

2166   */
2167  HTMLArea.getElementById = function(tag, id) {
2168      var el, i, objs = document.getElementsByTagName(tag);
2169      for (i = objs.length; --i >= 0 && (el = objs[i]);)
2170          if (el.id == id)
2171              return el;
2172      return null;
2173  };
2174  
2175  
2176  
2177  // EOF

2178  // Local variables: //

2179  // c-basic-offset:8 //

2180  // indent-tabs-mode:t //

2181  // End: //



Généré le : Mon Nov 26 16:45:43 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics