[phpBB Debug] PHP Warning: in file [ROOT]/includes/crs/crs_misc_functions.php on line 37: mime_content_type(): Empty filename or path
[phpBB Debug] PHP Warning: in file [ROOT]/includes/crs/crs_misc_functions.php on line 37: mime_content_type(): Empty filename or path
Zen Cart 源代码 htmlarea.js

Zen Cart 源代码 htmlarea.js




下载文件

文件名: htmlarea.js
文件类型: Javascript文件
文件大小: 69.87 KiB
MD5: c4acb7a4032730f1300d9a9eea847b01

htmlarea.js - 关闭高亮
  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 17858 2010-10-08 04:28:34Z 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_webkit = (HTMLArea.agt.indexOf('applewebkit') != -1);
  1794. HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
  1795. HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
  1796. HTMLArea.is_gecko  = (navigator.product == "Gecko");
  1797.  
  1798. // variable used to pass the object to the popup editor window.
  1799. HTMLArea._object = null;
  1800.  
  1801. // function that returns a clone of the given object
  1802. HTMLArea.cloneObject = function(obj) {
  1803.     var newObj = new Object;
  1804.  
  1805.     // check for array objects
  1806.     if (obj.constructor.toString().indexOf("function Array(") == 1) {
  1807.         newObj = obj.constructor();
  1808.     }
  1809.  
  1810.     // check for function objects (as usual, IE is fucked up)
  1811.     if (obj.constructor.toString().indexOf("function Function(") == 1) {
  1812.         newObj = obj; // just copy reference to it
  1813.     } else for (var n in obj) {
  1814.         var node = obj[n];
  1815.         if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); }
  1816.         else                         { newObj[n] = node; }
  1817.     }
  1818.  
  1819.     return newObj;
  1820. };
  1821.  
  1822. // FIXME!!! this should return false for IE < 5.5
  1823. HTMLArea.checkSupportedBrowser = function() {
  1824.     if (HTMLArea.is_gecko && !HTMLArea.is_opera && !HTMLArea.is_webkit) {
  1825.         if (navigator.productSub < 20021201) {
  1826.             alert("You need at least Mozilla-1.3 Alpha.\n" +
  1827.                   "Sorry, your Gecko is not supported.");
  1828.             return false;
  1829.         }
  1830.         if (navigator.productSub < 20030210) {
  1831.             alert("Mozilla < 1.3 Beta is not supported!\n" +
  1832.                   "I'll try, though, but it might not work.");
  1833.         }
  1834.     }
  1835.     return HTMLArea.is_gecko || HTMLArea.is_ie;
  1836. };
  1837.  
  1838. // selection & ranges
  1839.  
  1840. // returns the current selection object
  1841. HTMLArea.prototype._getSelection = function() {
  1842.     if (HTMLArea.is_ie) {
  1843.         return this._doc.selection;
  1844.     } else {
  1845.         return this._iframe.contentWindow.getSelection();
  1846.     }
  1847. };
  1848.  
  1849. // returns a range for the current selection
  1850. HTMLArea.prototype._createRange = function(sel) {
  1851.     if (HTMLArea.is_ie) {
  1852.         return sel.createRange();
  1853.     } else {
  1854.         this.focusEditor();
  1855.         if (typeof sel != "undefined") {
  1856.             try {
  1857.                 return sel.getRangeAt(0);
  1858.             } catch(e) {
  1859.                 return this._doc.createRange();
  1860.             }
  1861.         } else {
  1862.             return this._doc.createRange();
  1863.         }
  1864.     }
  1865. };
  1866.  
  1867. // event handling
  1868.  
  1869. HTMLArea._addEvent = function(el, evname, func) {
  1870.     if (HTMLArea.is_ie) {
  1871.         el.attachEvent("on" + evname, func);
  1872.     } else {
  1873.         el.addEventListener(evname, func, true);
  1874.     }
  1875. };
  1876.  
  1877. HTMLArea._addEvents = function(el, evs, func) {
  1878.     for (var i in evs) {
  1879.         HTMLArea._addEvent(el, evs[i], func);
  1880.     }
  1881. };
  1882.  
  1883. HTMLArea._removeEvent = function(el, evname, func) {
  1884.     if (HTMLArea.is_ie) {
  1885.         el.detachEvent("on" + evname, func);
  1886.     } else {
  1887.         el.removeEventListener(evname, func, true);
  1888.     }
  1889. };
  1890.  
  1891. HTMLArea._removeEvents = function(el, evs, func) {
  1892.     for (var i in evs) {
  1893.         HTMLArea._removeEvent(el, evs[i], func);
  1894.     }
  1895. };
  1896.  
  1897. HTMLArea._stopEvent = function(ev) {
  1898.     if (HTMLArea.is_ie) {
  1899.         ev.cancelBubble = true;
  1900.         ev.returnValue = false;
  1901.     } else {
  1902.         ev.preventDefault();
  1903.         ev.stopPropagation();
  1904.     }
  1905. };
  1906.  
  1907. HTMLArea._removeClass = function(el, className) {
  1908.     if (!(el && el.className)) {
  1909.         return;
  1910.     }
  1911.     var cls = el.className.split(" ");
  1912.     var ar = new Array();
  1913.     for (var i = cls.length; i > 0;) {
  1914.         if (cls[--i] != className) {
  1915.             ar[ar.length] = cls[i];
  1916.         }
  1917.     }
  1918.     el.className = ar.join(" ");
  1919. };
  1920.  
  1921. HTMLArea._addClass = function(el, className) {
  1922.     // remove the class first, if already there
  1923.     HTMLArea._removeClass(el, className);
  1924.     el.className += " " + className;
  1925. };
  1926.  
  1927. HTMLArea._hasClass = function(el, className) {
  1928.     if (!(el && el.className)) {
  1929.         return false;
  1930.     }
  1931.     var cls = el.className.split(" ");
  1932.     for (var i = cls.length; i > 0;) {
  1933.         if (cls[--i] == className) {
  1934.             return true;
  1935.         }
  1936.     }
  1937.     return false;
  1938. };
  1939.  
  1940. HTMLArea.isBlockElement = function(el) {
  1941.     var blockTags = " body form textarea fieldset ul ol dl li div " +
  1942.         "p h1 h2 h3 h4 h5 h6 quote pre table thead " +
  1943.         "tbody tfoot tr td iframe address ";
  1944.     return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
  1945. };
  1946.  
  1947. HTMLArea.needsClosingTag = function(el) {
  1948.     var closingTags = " head script style div span tr td tbody table em strong font a title ";
  1949.     return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
  1950. };
  1951.  
  1952. // performs HTML encoding of some given string
  1953. HTMLArea.htmlEncode = function(str) {
  1954.     // we don't need regexp for that, but.. so be it for now.
  1955.     str = str.replace(/&/ig, "&amp;");
  1956.     str = str.replace(/</ig, "&lt;");
  1957.     str = str.replace(/>/ig, "&gt;");
  1958.     str = str.replace(/\x22/ig, "&quot;");
  1959.     // \x22 means '"' -- we use hex reprezentation so that we don't disturb
  1960.     // JS compressors (well, at least mine fails.. ;)
  1961.     return str;
  1962. };
  1963.  
  1964. // Retrieves the HTML code from the given node.  This is a replacement for
  1965. // getting innerHTML, using standard DOM calls.
  1966. HTMLArea.getHTML = function(root, outputRoot, editor) {
  1967.     var html = "";
  1968.     switch (root.nodeType) {
  1969.         case 1: // Node.ELEMENT_NODE
  1970.         case 11: // Node.DOCUMENT_FRAGMENT_NODE
  1971.         var closed;
  1972.         var i;
  1973.         var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
  1974.         if (HTMLArea.is_ie && root_tag == "head") {
  1975.             if (outputRoot)
  1976.                 html += "<head>";
  1977.             // lowercasize
  1978.             var save_multiline = RegExp.multiline;
  1979.             RegExp.multiline = true;
  1980.             var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
  1981.                 return p1 + p2.toLowerCase();
  1982.             });
  1983.             RegExp.multiline = save_multiline;
  1984.             html += txt;
  1985.             if (outputRoot)
  1986.                 html += "</head>";
  1987.             break;
  1988.         } else if (outputRoot) {
  1989.             closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root)));
  1990.             html = "<" + root.tagName.toLowerCase();
  1991.             var attrs = root.attributes;
  1992.             for (i = 0; i < attrs.length; ++i) {
  1993.                 var a = attrs.item(i);
  1994.                 if (!a.specified) {
  1995.                     continue;
  1996.                 }
  1997.                 var name = a.nodeName.toLowerCase();
  1998.                 if (/_moz|contenteditable|_msh/.test(name)) {
  1999.                     // avoid certain attributes
  2000.                     continue;
  2001.                 }
  2002.                 var value;
  2003.                 if (name != "style") {
  2004.                     // IE5.5 reports 25 when cellSpacing is
  2005.                     // 1; other values might be doomed too.
  2006.                     // For this reason we extract the
  2007.                     // values directly from the root node.
  2008.                     // I'm starting to HATE JavaScript
  2009.                     // development.  Browser differences
  2010.                     // suck.
  2011.                     //
  2012.                     // Using Gecko the values of href and src are converted to absolute links
  2013.                     // unless we get them using nodeValue()
  2014.                     if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") {
  2015.                         value = root[a.nodeName];
  2016.                     } else {
  2017.                         value = a.nodeValue;
  2018.                         // IE seems not willing to return the original values - it converts to absolute
  2019.                         // links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href")
  2020.                         // So we have to strip the baseurl manually -/
  2021.                         if (HTMLArea.is_ie && (name == "href" || name == "src")) {
  2022.                             value = editor.stripBaseURL(value);
  2023.                         }
  2024.                     }
  2025.                 } else { // IE fails to put style in attributes list
  2026.                     // FIXME: cssText reported by IE is UPPERCASE
  2027.                     value = root.style.cssText;
  2028.                 }
  2029.                 if (/(_moz|^$)/.test(value)) {
  2030.                     // Mozilla reports some special tags
  2031.                     // here; we don't need them.
  2032.                     continue;
  2033.                 }
  2034.                 html += " " + name + '="' + value + '"';
  2035.             }
  2036.             html += closed ? " />" : ">";
  2037.         }
  2038.         for (i = root.firstChild; i; i = i.nextSibling) {
  2039.             html += HTMLArea.getHTML(i, true, editor);
  2040.         }
  2041.         if (outputRoot && !closed) {
  2042.             html += "</" + root.tagName.toLowerCase() + ">";
  2043.         }
  2044.         break;
  2045.         case 3: // Node.TEXT_NODE
  2046.         // If a text node is alone in an element and all spaces, replace it with an non breaking one
  2047.         // This partially undoes the damage done by moz, which translates '&nbsp;'s into spaces in the data element
  2048.         if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) ) html = '&nbsp;';
  2049.         else html = HTMLArea.htmlEncode(root.data);
  2050.         break;
  2051.         case 8: // Node.COMMENT_NODE
  2052.         html = "<!--" + root.data + "-->";
  2053.         break;      // skip comments, for now.
  2054.     }
  2055.     return html;
  2056. };
  2057.  
  2058. HTMLArea.prototype.stripBaseURL = function(string) {
  2059.     var baseurl = this.config.baseURL;
  2060.  
  2061.     // strip to last directory in case baseurl points to a file
  2062.     baseurl = baseurl.replace(/[^\/]+$/, '');
  2063.     var basere = new RegExp(baseurl);
  2064. //  string = string.replace(basere, "");
  2065.  
  2066.     // strip host-part of URL which is added by MSIE to links relative to server root
  2067.     baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
  2068.     basere = new RegExp(baseurl);
  2069.     return string; //.replace(basere, "");
  2070. };
  2071.  
  2072. String.prototype.trim = function() {
  2073.     a = this.replace(/^\s+/, '');
  2074.     return a.replace(/\s+$/, '');
  2075. };
  2076.  
  2077. // creates a rgb-style color from a number
  2078. HTMLArea._makeColor = function(v) {
  2079.     if (typeof v != "number") {
  2080.         // already in rgb (hopefully); IE doesn't get here.
  2081.         return v;
  2082.     }
  2083.     // IE sends number; convert to rgb.
  2084.     var r = v & 0xFF;
  2085.     var g = (v >> 8) & 0xFF;
  2086.     var b = (v >> 16) & 0xFF;
  2087.     return "rgb(" + r + "," + g + "," + b + ")";
  2088. };
  2089.  
  2090. // returns hexadecimal color representation from a number or a rgb-style color.
  2091. HTMLArea._colorToRgb = function(v) {
  2092.     if (!v)
  2093.         return '';
  2094.  
  2095.     // returns the hex representation of one byte (2 digits)
  2096.     function hex(d) {
  2097.         return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
  2098.     };
  2099.  
  2100.     if (typeof v == "number") {
  2101.         // we're talking to IE here
  2102.         var r = v & 0xFF;
  2103.         var g = (v >> 8) & 0xFF;
  2104.         var b = (v >> 16) & 0xFF;
  2105.         return "#" + hex(r) + hex(g) + hex(b);
  2106.     }
  2107.  
  2108.     if (v.substr(0, 3) == "rgb") {
  2109.         // in rgb(...) form -- Mozilla
  2110.         var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
  2111.         if (v.match(re)) {
  2112.             var r = parseInt(RegExp.$1);
  2113.             var g = parseInt(RegExp.$2);
  2114.             var b = parseInt(RegExp.$3);
  2115.             return "#" + hex(r) + hex(g) + hex(b);
  2116.         }
  2117.         // doesn't match RE?!  maybe uses percentages or float numbers
  2118.         // -- FIXME: not yet implemented.
  2119.         return null;
  2120.     }
  2121.  
  2122.     if (v.substr(0, 1) == "#") {
  2123.         // already hex rgb (hopefully :D )
  2124.         return v;
  2125.     }
  2126.  
  2127.     // if everything else fails ;)
  2128.     return null;
  2129. };
  2130.  
  2131. // modal dialogs for Mozilla (for IE we're using the showModalDialog() call).
  2132.  
  2133. // receives an URL to the popup dialog and a function that receives one value;
  2134. // this function will get called after the dialog is closed, with the return
  2135. // value of the dialog.
  2136. HTMLArea.prototype._popupDialog = function(url, action, init) {
  2137.     Dialog(this.popupURL(url), action, init);
  2138. };
  2139.  
  2140. // paths
  2141.  
  2142. HTMLArea.prototype.imgURL = function(file, plugin) {
  2143.     if (typeof plugin == "undefined")
  2144.         return _editor_url + file;
  2145.     else
  2146.         return _editor_url + "plugins/" + plugin + "/img/" + file;
  2147. };
  2148.  
  2149. HTMLArea.prototype.popupURL = function(file) {
  2150.     var url = "";
  2151.     if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
  2152.         var plugin = RegExp.$1;
  2153.         var popup = RegExp.$2;
  2154.         if (!/\.html$/.test(popup))
  2155.             popup += ".html";
  2156.         url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
  2157.     } else
  2158.         url = _editor_url + this.config.popupURL + file;
  2159.     return url;
  2160. };
  2161.  
  2162. /**
  2163.  * FIX: Internet Explorer returns an item having the _name_ equal to the given
  2164.  * id, even if it's not having any id.  This way it can return a different form
  2165.  * field even if it's not a textarea.  This workarounds the problem by
  2166.  * specifically looking to search only elements having a certain tag name.
  2167.  */
  2168. HTMLArea.getElementById = function(tag, id) {
  2169.     var el, i, objs = document.getElementsByTagName(tag);
  2170.     for (i = objs.length; --i >= 0 && (el = objs[i]);)
  2171.         if (el.id == id)
  2172.             return el;
  2173.     return null;
  2174. };
  2175.  
  2176.  
  2177.  
  2178. // EOF
  2179. // Local variables: //
  2180. // c-basic-offset:8 //
  2181. // indent-tabs-mode:t //
  2182. // End: //
  2183.