// $Id: htmlbox.js,v 1.1.2.1 2008/07/03 23:41:45 poetro Exp $

/**
 * Copyright (c) 2008 Peter Galiba <pgaliba@nowpublic.com>
 *
 *  This file is free software: you may copy, redistribute and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation, either version 2 of the License, or (at your
 *  option) any later version.
 *
 *  This file is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 *     Copyright (c) 2008 Remiya Solutions (http://remiya.com)
 *
 *     Permission to use, copy, modify, and/or distribute this software
 *     for any purpose with or without fee is hereby granted, provided
 *     that the above copyright notice and this permission notice appear
 *     in all copies.
 *
 *     THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 *     WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 *     WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 *     AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
 *     CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 *     OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 *     NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 *     CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

$.fn.htmlbox = function() {
  if (undefined === window.glob_ha) {
    glob_ha = [];
  }

  var d = {
    buttons: [],
    rows: [],
    images: [],
    dir: "./images/",
    output: "xhtml"
  };

  var xhtml = '<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head><body>INITIAL_CONTENT</body></html>';

  if (!$(this).attr("id")) {
    $(this).attr("id", "jqha_"+ glob_ha.length);
    d.id = "jqha_"+ glob_ha.length;
    glob_ha[glob_ha] = glob_ha;
  } else {
    d.id = $(this).attr("id");
  }

  if (undefined === glob_ha[d.id]) {
    glob_ha[d.id] = this;
  }

  /**
   * Initialize the editor
   * @param is_init Specifies, whether the editor should initialize at once, or after some timeout.
   */
  this.init = function(is_init) {
    var exception;
    if ($(this).size() === 0) {
      return this;
    }
    // START: Timeout to allow creation of DesignMode
    if (undefined === is_init) {
      setTimeout("glob_ha['"+ d.id +"'].init(true)", 250);
      return false;
    }
    // END: Timeout to allow creation of DesignMode
    
    if (Drupal !== undefined && Drupal.settings != undefined && Drupal.settings.htmlbox !== undefined) {
      this.newline_removal = Drupal.settings.htmlbox.newline_removal;
    }
    else {
      this.newline_removal = true;
    }

    // Prepare the HTMLBox area
    var w = $(this).width();
    var h = $(this).height();
    $(this).wrap("<div class='htmlbox' style='width: "+ w +"px; height:"+ h +"px;'><div class='htmlbox-area'></div></div>");
    // START: Appending toolbar
    $(this).parent().append(toolbar());
    $("."+ d.id +"_tb").css('width', w-6 +"px").find("button").each(function() {
      $(this).mouseover(function() {
        $(this).css("border", "1px solid #BFCAFF").css("background-color", "#EFF2FF");
      });
      $(this).mouseout(function() {
        $(this).css("border", "1px solid #E9EAEF").css("background-color", "transparent");
      });
    }).end().find("select").each(function() {
      $(this).css("border", "1px solid #E9EAEF").css("background-color", "transparent").css("margin", "2px 2px 3px 2px");
      if ($.browser.mozilla) {
        $(this).css("padding", "0").css("position", "relative").css("top", "-2px");
      }
    });
    // END: Appending toolbar

    try {
      var iframe = document.createElement("IFRAME");
      var doc = null;
      $(iframe).attr("width", "100%").css("height", h +"px").attr("id", d.id +"_html").css("border", "none");
      $(this).parent().prepend(iframe);
    } catch(eIframe) {
      alert("This rich text component is not supported by your browser.");
      $(this).show();
      return false;
    }

    d.iframe = iframe;
    var idoc = iframe.contentWindow.document;
    if ($.browser.msie === false) {
      idoc.body.contentEditable = true;
    } else {
      setTimeout(function() { try {$(idoc.body).css('border', 'none'); } catch (exception) {}}, 10);
    }
    
    this.initResizing($(iframe));
    this.reAlign();

    // Prepare the text
    var text = $.trim($(this).val());
    text = xhtml.replace(/INITIAL_CONTENT/, ($.browser.mozilla && text === "") ? '' : text);

    if ($.browser.mozilla) {
      idoc.open('text/html', 'replace');
      idoc.write(text);
      idoc.close();
    } else {
      idoc.write(text);
    }

    try {
      idoc.designMode = "on";
      idoc.execCommand("undo", false, null);
    }
    catch (exception) {
      try {
        $(idoc).focus(function() {
          try {
            this.designMode = "on";
          } catch (exception) {}
        });
      } catch (exception) {}
    }

    // START: Adding events
    var keyupHandler = function(e) {
      var html = ($("#"+ d.id).is(":visible")) ? $("#"+ d.id).val() : html = idoc.body.innerHTML;
      html = (typeof HTMLtoXML === 'function') ? HTMLtoXML(html) : html;
      $("#"+ d.id).val(html);
    };

    try {
      if (idoc.attachEvent) {
        idoc.attachEvent("onkeyup", keyupHandler);
      } else {
        idoc.addEventListener("keyup", keyupHandler, false);
      }
    }
    catch (exception) {}

    $(idoc).keypress = function (e) {
      switch ((e.which) ? e.which : e.keyCode) {
        case 13: // ENTER
          if (!$.browser.opera && !e.ctrlKey && !e.shiftKey) {
            try {
              var el = ta.getEventElement(iframe.contentWindow, e);
              if (el.nodeName == "BODY") {
                glob_ha[d.id].cmd("formatblock", "<p>");
              }
            }
            catch (exception) {}
          }
          break;
        case 61: // =
          if (e.ctrlKey) {
            if (e.shiftKey) {
              glob_ha[d.id].cmd("superscript");
            }
            else {
              glob_ha[d.id].cmd("subscript");
            }
          }
          break;
        case 66: // B
          if (e.ctrlKey && !e.shiftKey) {
            glob_ha[d.id].cmd("bold");
          }
          break;
        case 73: // I
          if (e.ctrlKey && !e.shiftKey) {
            glob_ha[d.id].cmd("italic");
          }
          break;
        case 74: // J
          if (e.ctrlKey && !e.shiftKey) {
            glob_ha[d.id].cmd("unlink");
          }
          break;
        case 75: // K
          if (e.ctrlKey && !e.shiftKey) {
            glob_ha[d.id].cmd("createlink");
          }
          break;
        case 85: // U
          if (e.ctrlKey && !e.shiftKey) {
            glob_ha[d.id].cmd("italic");
          }
          break;
        default:
          break;
      }
    };
    // END: Adding events
    $(this).hide();
  };
  
  this.reAlign = function() {
    var toolbar = $(this).parents("div.htmlbox:first").find("div.htmlbox-toolbar");
    var width = 0;
    toolbar.children().each(function () {
      width += parseInt($(this).width(), 10) + parseInt($(this).css("margin-left"), 10) + parseInt($(this).css("margin-right"), 10);
    }).end();
    var height = 27;
    var tWidth = toolbar.width();
    while (width > toolbar.width()) {
      height += 27;
      toolbar.css("height", height +"px");
      width -= tWidth;
    }
    $(this).parents("div.htmlbox:first").css('padding-top', (height + 3) +"px");
  };

  /**
   * Fetch the current cursor's / selection's parent item.
   * @param contentWindow The IFRAME / Window the cursor is in.
   * @param e JavaScript Event object.
   */
  this.getEventElement = function(contentWindow, e) {
    var eTarget;    // Current Real cursor position DOM Element
    var selection;  // Selection object
    var range;      // Range object
    if (window.getSelection) {
      selection = contentWindow.getSelection();
      eTarget = (e && e.type=='mouseup') ? e.target :
        ((selection.anchorNode.nodeName == '#text') ? selection.anchorNode.parentNode : selection.anchorNode);
    }
    else {
      selection = contentWindow.document.selection;
      range = selection.createRange();
      eTarget = (e && e.type=='mouseup') ? e.srcElement :
        ((selection.type == "Control") ? range.item(0) : selection.parentElement());
    }
    return eTarget;
  };
  
  /**
   * Initalize and apply the regular textarea resizing capability of Drupal to the HTMLBox
   */
  this.initResizing = function(iframe) {
    var grippie = $(this).parents('div.resizable-textarea:first').find('div.grippie:last');
    // If we have the grippie, replace it's functionality
    if (grippie.size()) {
      var textarea = $(this);
      var htmlbox = $(this).parents('div.htmlbox:first');

      function startDrag(e) {
        staticOffset = textarea.height() - Drupal.mousePosition(e).y;
        $(document).mousemove(performDrag).mouseup(endDrag);
        return false;
      }

      function performDrag(e) {
        var height = Math.max(32, staticOffset + Drupal.mousePosition(e).y) + 'px';
        textarea.height(height);
        iframe.height(height);
        htmlbox.height(height);
        return false;
      }

      function endDrag(e) {
        $(document).unmousemove(performDrag).unmouseup(endDrag);
      }

      grippie.unbind('mousedown').mousedown(startDrag);
    }
  };

  /**
   * Generate a toolbar based on the added buttons / separators
   */
  var toolbar = function() {
    var h = "";
    var colors = ['white', 'FFFFFF', 'ivory', 'FFFFF0', 'lightyellow', 'FFFFE0', 'yellow', 'FFFF00', 'snow', 'FFFAFA', 'floralwhite', 'FFFAF0', 'lemonchiffon', 'FFFACD', 'cornsilk', 'FFF8DC', 'seashell', 'FFF5EE', 'lavenderblush', 'FFF0F5', 'papayawhip', 'FFEFD5', 'blanchedalmond', 'FFEBCD', 'mistyrose', 'F FE4E1', 'bisque', 'FFE4C4', 'moccasin', 'FFE4B5', 'navajowhite', 'FFDEAD', 'peachpuff', 'FFDAB9', 'gold', 'FFD700', 'pink', 'FFC0CB', 'lightpink ', 'FFB6C1', 'orange', 'FFA500', 'lightsalmon', 'FFA07A', 'darkorange', 'FF8C00', 'coral', 'FF7F50', 'hotpink', 'FF69B4', 'tomato', 'FF6347', 'orangered', 'FF4500', 'deeppink', 'FF1493', 'magenta', 'FF00FF', 'fuchsia', 'FF00FF', 'red', 'FF0000', 'oldlace', 'FDF5E6', 'lightgoldenrodyellow', 'FAFAD2', ' linen', 'FAF0E6', 'antiquewhite', 'FAEBD7', 'salmon', 'FA8072', 'ghostwhite', 'F8F8FF', 'mintcream', 'F5FFFA', 'whitesmoke', 'F5F5F5', 'beige', ' F5F5DC', 'wheat', 'F5DEB3', 'sandybrown', 'F4A460', 'azure', 'F0FFFF', 'honeydew', 'F0FFF0', 'aliceblue', 'F0F8FF', 'khaki', 'F0E68C', 'lightcoral ', 'F08080', 'palegoldenrod', 'EEE8AA', 'violet', 'EE82EE', 'darksalmon', 'E9967A', 'lavender', 'E6E6FA', 'lightcyan', 'E0FFFF', 'burlywood', 'DEB887', 'plum', 'DDA0DD', 'gainsboro', 'DCDCDC', 'crimson', 'DC143C', 'palevioletred', 'DB7093', 'goldenrod', 'DAA520', 'orchid', 'DA70D6', 'thistle', 'D8BFD8', 'lightgrey', 'D3D3D3', 'tan', 'D2B48C', 'chocolate', 'D2691E', 'peru', 'CD853F', 'indianred', 'CD5C5C', 'mediumvioletred', 'C71585', 'silver', 'C0C0C0', 'darkkhaki', 'BDB76B', 'rosybrown', 'BC8F8F', 'mediumorchid', 'BA55D3', 'darkgoldenrod', 'B8860B', 'firebrick', 'B22222', 'powderblue', 'B0E0E6', 'lightsteelblue', 'B0C4DE', 'paleturquoise', 'AFEEEE', 'greenyellow', 'ADFF2F', 'lightblue', 'ADD8E6', 'darkgray', 'A9A9A9', 'brown', 'A52A2A', 'sienna', "A0522D", 'yellowgreen', "9ACD32", 'darkorchid', '9932CC', 'palegreen', '98FB98', 'darkviolet', '9400D3', 'mediumpurple', '9370DB', 'lightgreen', '90EE90', 'darkseagreen', '8FBC8F', 'saddlebrown', '8B4513', 'darkmagenta', '8B008B', 'darkred', '8B0000', 'blueviolet', '8A2BE2', 'lightskyblue', '87CEFA', 'skyblue', '87CEEB', 'gray', '808080', 'olive', '808000', 'purple', '800080', 'maroon', '800000', 'aquamarine', '7FFFD4', 'chartreuse', '7FFF00', 'lawngreen', '7CFC00', 'mediumslateblue', '7B68EE', 'lightslategray', '778899', 'slategray', '708090', 'olivedrab', '6B8E23', 'slateblue', '6A5ACD', 'dimgray', '696969', 'mediumaquamarine', '66CDAA', 'cornflowerblue', '6495ED', 'cadetblue', '5F9EA0', 'darkolivegreen', '556B2F', 'indigo', '4B0082', 'mediumturquoise', '48D1CC', 'darkslateblue', '483D8B', 'steelblue', '4682B4', 'royalblue', '4169E1', 'turquoise', '40E0D0', 'mediumseagreen', '3CB371', 'limegreen', '32CD32', 'darkslategray', '2F4F4F', 'seagreen', '2E8B57', 'forestgreen', '228B22', 'lightseagreen', '20B2AA', 'dodgerblue', '1E90FF', 'midnightblue', '191970', 'cyan', '00FFFF', 'aqua', '00FFFF', 'springgreen', '00FF7F', 'lime', '00FF00', 'mediumspringgreen', '00FA9A', 'darkturquoise', '00CED1', 'deepskyblue', '00BFFF', 'darkcyan', "008B8B", 'teal', "008080", 'green', '008000', 'darkgreen', '006400', 'blue', '0000FF', 'mediumblue', '0000CD', 'darkblue', '00008B', 'navy', '000080', 'black', '000000'];

    for (var k = 1; k < d.rows.length; k++) {
      if (undefined === d.rows[k]) {
        continue;
      }

      var buttons = d.rows[k].split(",");
      h += "<div class='"+ d.id +"_tb htmlbox-toolbar'>";

      /**
       * Add a toolbar item to the toolbar generating string.
       * @param type The item type to add. Available options are
       *  - button: a button. This case the event is 'onclick'
       *  - select: a dropdown select box. This case the event is 'onchange'
       * @param cmd The command to execute when the event happens.
       * @param title Optional title for the tooltip.
       * @param data Based on the type:
       *  - button: The class for the span to add the button image.
       *  - select: Available <option>s as a string.
       * @return An HTML string.
       */
      var addToolbarItem = function (type, cmd, title, data, arg) {
        switch (type) {
          case 'button':
            return '<button type="button" onclick="glob_ha[\''+ d.id +'\'].cmd(\''+ cmd + (arg !== undefined ? '\',\''+ arg : '') +'\')" title="'+ title +'"><span class="'+ data +'">&nbsp;</span></button>';
          case 'select':
            return '<select id="'+ d.id +'_'+ cmd +'" onchange="glob_ha[\''+ d.id +'\'].cmd(\''+ cmd +'\',this.options[this.selectedIndex].value);this.selectedIndex=0;">'+ data +'</select>';
          default:
            return '';
        }
      };
      var m, options;
      for (var i = 0; i < (buttons.length + 1); i++) {
        switch (d.buttons[buttons[i]]) {
          case "cut":
            h += addToolbarItem("button", "cut", "cut", "cut");
            break;
          case "copy":
            h += addToolbarItem("button", "copy", "copy", "copy");
            break;
          case "paste":
            h += addToolbarItem("button", "cut", "paste", "paste");
            break;
          case "undo":
            h += addToolbarItem("button", "undo", "undo", "undo");
            break;
          case "redo":
            h += addToolbarItem("button", "redo", "redo", "redo");
            break;
          case "bold":
            h += addToolbarItem("button", "bold", "bold", "b");
            break;
          case "italic":
            h += addToolbarItem("button", "italic", "italic", "i");
            break;
          case "underline":
            h += addToolbarItem("button", "underline", "undelrine", "u");
            break;
          case "sup":
            h += addToolbarItem("button", "superscript", "superscript", "sup");
            break;
          case "sub":
            h += addToolbarItem("button", "subscript", "subscript", "sub");
            break;
          case "left":
            h += addToolbarItem("button", "justifyleft", "align left", "left");
            break;
          case "center":
            h += addToolbarItem("button", "justifycenter", "align center", "center");
            break;
          case "right":
            h += addToolbarItem("button", "justifyright", "align right", "right");
            break;
          case "justify":
            h += addToolbarItem("button", "justifyfull", "justify", "justify");
            break;
          case "ol":
            h += addToolbarItem("button", "insertorderedlist", "ordered list", "ol");
            break;
          case "ul":
            h += addToolbarItem("button", "insertunorderedlist", "unordered list", "ul");
            break;
          case "indent":
            h += addToolbarItem("button", "indent", "indent", "indent");
            break;
          case "outdent":
            h += addToolbarItem("button", "outdent", "outdent", "outdent");
            break;
          case "hyperlink":
            h += addToolbarItem("button", "createlink", "hyperlink", "hyperlink");
            h += addToolbarItem("button", "unlink", "remove hyperlink", "unlink");
            break;
          case "image":
            h += addToolbarItem("button", "insertimage", "image", "img");
            break;
          case "html":
            h += addToolbarItem("button", "html", "html source", "html");
            break;
          case "separator_dots":
            h += "<span class='separator_dots' title='separator_dots'>&nbsp;</span>";
            break;
          case "separator_basic":
            h += "<span class='separator_basic' title='separator_basic'>&nbsp;</span>";
            break;
          case "fontsize":
            h += addToolbarItem("select", "fontsize", "font size", '<option value="" selected="selected">- font size -</option><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option><option value="6">6</option><option value="7">7</option>');
            break;
          case "fontfamily":
            h += addToolbarItem("select", "fontname", "font name", '<option value="" selected="selected">- font -</option><option value="arial" style="font-family:arial;">Arial</option><option value="courier" style="font-family:courier;">Courier</option><option value="cursive" style="font-family:cursive;">Cursive</option><option value="georgia" style="font-family:georgia;">Georgia</option><option value="monospace" style="font-family:monospace;">Monospace</option><option value="tahoma" style="font-family:tahoma;">Tahoma</option><option value="verdana" style="font-family:verdana;">Verdana</option>');
            break;
          case "fontcolor":
            options = '<option value="" selected="selected">- color -</option>';
            for (m = 0; m < colors.length; m++) {
              if (1 === (m & 1)) {
                continue;
              }
              options += "<option value='"+ colors[m] +"' style='background:"+ colors[m] +";color:"+ colors[m] +";'>"+ colors[m] +"</option>";
            }
            h += addToolbarItem("select", "fontcolor", "font color", options);
            break;
          case "highlight":
            options = '<option value="" selected="selected">- highlight -</option>';
            for (m = 0; m < colors.length; m++) {
              if (1 === (m & 1)) {
                continue;
              }
              options += "<option value='"+ colors[m] +"' style='background:"+ colors[m] +";color:"+ colors[m] +";'>"+ colors[m] +"</option>";
            }
            h += addToolbarItem("select", "backcolor", "background color", options);
            break;
          case "headers":
            options = '<option value="" selected="selected">- headers -</option>';
            for (m = 1; m <= 6; m++) {
              options += "<option value='&lt;H"+ m +"&gt;'>H"+ m +"</option>";
            }
            h += addToolbarItem("select", "formatblock", "Header format", options);
            break;
          case "p":
            h += addToolbarItem("button", "formatblock", "p", "paragraph", "<P>");
            break;
          case "code":
            h += addToolbarItem("button", "formatblock", "code", "code", "<CODE>");
            break;
          case "pre":
            h += addToolbarItem("button", "formatblock", "preformatted", "pre", "<PRE>");
            break;
          default:
            break;
        }
      }
      h += "</div>";
    }
    return h;
  };

  /**
   * Handle commands fired from the buttons.
   * @param cmd The command to execute.
   * @param arg1 Optional arguments for the command.
   */
  this.cmd = function(cmd, arg1) {
    // When user clicks toolbar button make sure it always targets its respective WYSIWYG
    d.iframe.contentWindow.focus();
    var idoc = d.iframe.contentWindow.document;
    if ($.browser.mozilla) {
      idoc.execCommand("styleWithCSS", false, false);
    }
    if (cmd === "html") {
      var text = this.get_html();
      if ($("#"+ d.id).is(":visible")) {
        $("#"+ d.id).hide();
        $("#"+ d.id +"_html").show();
        if ($.browser.opera) {
          $(window).scrollTop($('body').scrollTop());
        }
        this.set_text(text);
      } else {
        $("#"+ d.id).show();
        $("#"+ d.id +"_html").hide();
        this.set_text(text);
        $("#"+ d.id).focus();
      }
    } else if (cmd === "createlink") {
      arg1 = $.trim(prompt("Paste Web Address URL Here:", "http://"));
      if (arg1 !== "") {
        idoc.execCommand(cmd, false, arg1);
      }
      else {
        idoc.execCommand("unlink", false, null);
      }
    } else if (cmd === "insertimage") {
      idoc.execCommand(cmd, false, prompt("Paste Image URL Here:", "http://"));
    } else if (cmd === "fontsize") {
      idoc.execCommand(cmd, false, arg1);
    } else if (cmd === "backcolor") {
      if ($.browser.msie) {
        idoc.execCommand("backcolor", false, arg1);
      } else {
        idoc.execCommand("hilitecolor", false, arg1);
      }
    } else if (cmd === "fontcolor") {
      idoc.execCommand("forecolor", false, arg1);
    } else if (cmd === "fontname") {
      idoc.execCommand(cmd, false, arg1);
    } else if (cmd === "formatblock") {
      idoc.execCommand(cmd, false, arg1);
    } else if(cmd==="cut") {
      if ($.browser.msie === false) {
        alert("Available in IExplore only.\nUse CTRL+X to cut text!");
      } else {
        idoc.execCommand('Cut');
      }
    } else if (cmd==="copy"){
      if ($.browser.msie === false) {
        alert("Available in IExplore only.\nUse CTRL+C to copy text!");
      } else {
        idoc.execCommand('Copy');
      }
    } else if (cmd==="paste") {
      if($.browser.msie === false) {
        alert("Available in IExplore only.\nUse CTRL+V to paste text!");
      } else {
        idoc.execCommand('Paste');
      }
    } else {
      idoc.execCommand(cmd, false, null);
    }

    if ($("#"+ d.id).is(":visible") === false) {
      $("#"+ d.id).val(this.get_html());
    }
    return true;
  };

  /**
   * Get text (HTML) of the textarea, or the HTML editor, which ever is visible.
   * @return The text (HTML) of the textarea, or the HTML editor, which ever is visible.
   */
  this.get_text = function() {
    if ($("#"+ d.id).is(":visible")) {
      return $("#"+ d.id).val();
    }

    var text;

    if ($.browser.msie) {
      text = d.iframe.contentWindow.document.body.innerText;
    } else {
      var html = d.iframe.contentWindow.document.body.ownerDocument.createRange();
      html.selectNodeContents(d.iframe.contentWindow.document.body);
      text = html;
    }

    return text;
  };

  /**
   * Set text (HTML) of the textarea, or the HTML editor, which ever is visible.
   * @param txt The text (HTML) to replace the current content.
   * @return The currently selected object for chaining.
   */
  this.set_text = function(txt) {
    var text = (undefined === txt) ? "": txt;
    if ($("#"+ d.id).is(":visible")) {
      $("#"+ d.id).val(text);
    } else if ($.browser.mozilla || $.browser.safari) {
      if ($.trim(text) === "") {
        text = "&nbsp;";
      }
      text = xhtml.replace(/INITIAL_CONTENT/, text);
      d.iframe.contentWindow.document.open('text/html', 'replace');
      d.iframe.contentWindow.document.write(text);
      d.iframe.contentWindow.document.close();
    } else {
      d.iframe.contentWindow.document.body.innerText = "";
      if (text !== "") {
        d.iframe.contentWindow.document.write(text);
      }
    }

    return this;
  };
  /**
   * Clean up some unwanted HTML items, by replacing them.
   *
   * Currently it replaces <span style=...> to it's <strong> and <em> counterpart.
   */
  this._mozcleanup = function () {
    if ($(this).size() === 0) {
      return;
    }

    if ($(this).attr("style") && $(this).attr("style").match(/font-weight:\s*bold\s*/i)) {
      if ($(this).attr("style").match(/font-style:\s*italic\s*/i)) {
        $(this).replaceWith('<em><strong>'+ this.innerHTML +'</strong></em>');
      }
      else {
        $(this).replaceWith('<strong>'+ this.innerHTML +'</strong>');
      }
    }
    else {
      if ($(this).attr("style") && $(this).attr("style").match(/font-style:\s*italic\s*/i)) {
        $(this).replaceWith('<em>'+ this.innerHTML +'</em>');
      }
      else {
        $(this).replaceWith(this.innerHTML);
      }
    }
  };

  /**
   * Clean up some unwanted HTML items, by replacing them.
   *
   * Currently it replaces <b> and <i> items with <strong> and <em>
   */
  this._cleanup = function () {
    if ($(this).size() === 0) {
      return;
    }

    if ($(this).is('b')) {
      $(this).replaceWith('<strong>'+ this.innerHTML +'</strong>');
    }
    else if ($(this).is('i')){
      $(this).replaceWith('<em>'+ this.innerHTML +'</em>');
    }
  };

  /**
   * Get HTML out of the textarea, or from the HTML editor, which ever is visible.
   */
  this.get_html = function() {
    var html;

    if ($("#"+ d.id).is(":visible")) {
      html = $("#"+ d.id).val();
    } else {
      html = d.iframe.contentWindow.document.body.innerHTML;
      if (html.match(/<span[^>]*\s+style=[^>]*>/)) {
        html = $(document.createElement("DIV")).html(html).find('span span').each(this._mozcleanup).end().find('span').each(this._mozcleanup).end().get(0).innerHTML;
      }
      html = $(document.createElement("DIV")).html(html).find('i, b').each(this._cleanup).end().get(0).innerHTML;

      html = html.replace(/<(em|strong|span)[^>]*>(\s*)<\/\1>/ig, '$2').replace(/<\/(em|strong)><\1[^>]*>/ig, '').replace(/<br(?:\s+[^>]+)?>/ig, '<br />');
      if (this.newline_removal) {
        html = html.replace(/[\n\r]+/g, ' ');
      }
    }

    if (typeof HTMLtoXML === 'function') {
      html = HTMLtoXML(html);
    }

    return html;
  };

  /**
   * Add a button (or separator @see separator()) to the editor.
   * @param name Name of the button (or separator).
   * @param row The row, where the button (or separator) should be added (Optional, default is 1).
   * @return The currently selected object for chaining.
   */
  this.button = function(name, row) {
    d.buttons[d.buttons.length] = name;

    if (undefined === row) {
      if (undefined === d.rows[1]) {
        d.rows[1] = "";
      }
      d.rows[1] = d.rows[1] +","+ (d.buttons.length - 1);
    } else {
      if (undefined === d.rows[row]) {
        d.rows[row] = "";
      }
      d.rows[row] = d.rows[row] +","+ (d.buttons.length - 1);
    }

    return this;
  };

  /**
   * Add a separator to the editor.
   * @param type Type of the separator (Optional, default is 'basic').
   * @param row The row, where the separator should be added (Optional, default is 1).
   * @return The currently selected object for chaining.
   */
  this.separator = function(type, row) {
    if (undefined === type) {
      type = "basic";
    }

    return this.button("separator_"+ type, row);
  };
  // For chaining
  return this;
};

// In case we are using an old jQuery we'll need the replaceWith function.
jQuery.fn.replaceWith = function(value) {
  return this.after( value ).remove();
};
