Annotation of loncom/html/htmlarea/htmlarea.js, revision 1.1
1.1 ! www 1: //
! 2: // htmlArea v3.0 - Copyright (c) 2002 interactivetools.com, inc.
! 3: // This copyright notice MUST stay intact for use (see license.txt).
! 4: //
! 5: // A free WYSIWYG editor replacement for <textarea> fields.
! 6: // For full source code and docs, visit http://www.interactivetools.com/
! 7: //
! 8: // Version 3.0 developed by Mihai Bazon for InteractiveTools.
! 9: // http://students.infoiasi.ro/~mishoo
! 10: //
! 11: // $Id: htmlarea.js,v 1.19 2003/08/10 15:56:34 mishoo Exp $
! 12:
! 13: // Creates a new HTMLArea object. Tries to replace the textarea with the given
! 14: // ID with it.
! 15: function HTMLArea(textarea, config) {
! 16: if (HTMLArea.checkSupportedBrowser()) {
! 17: if (typeof config == "undefined") {
! 18: this.config = new HTMLArea.Config();
! 19: } else {
! 20: this.config = config;
! 21: }
! 22: this._htmlArea = null;
! 23: this._textArea = textarea;
! 24: this._editMode = "wysiwyg";
! 25: this.plugins = {};
! 26: this._timerToolbar = null;
! 27: this._mdoc = document; // cache the document, we need it in plugins
! 28: }
! 29: };
! 30:
! 31: HTMLArea.Config = function () {
! 32: this.version = "3.0";
! 33:
! 34: this.width = "auto";
! 35: this.height = "auto";
! 36:
! 37: // enable creation of a status bar?
! 38: this.statusBar = true;
! 39:
! 40: // the next parameter specifies whether the toolbar should be included
! 41: // in the size or not.
! 42: this.sizeIncludesToolbar = true;
! 43:
! 44: // style included in the iframe document
! 45: this.pageStyle = "body { background-color: #fff; font-family: verdana,sans-serif; }";
! 46: if (typeof _editor_url != "undefined") {
! 47: this.editorURL = _editor_url;
! 48: } else {
! 49: this.editorURL = "";
! 50: }
! 51:
! 52: // URL-s
! 53: this.imgURL = "images/";
! 54: this.popupURL = "popups/";
! 55:
! 56: // configuration for plugins
! 57: this.plugins = {};
! 58:
! 59: /** CUSTOMIZING THE TOOLBAR
! 60: * -------------------------
! 61: *
! 62: * It is recommended that you customize the toolbar contents in an
! 63: * external file (i.e. the one calling HTMLArea) and leave this one
! 64: * unchanged. That's because when we (InteractiveTools.com) release a
! 65: * new official version, it's less likely that you will have problems
! 66: * upgrading HTMLArea.
! 67: */
! 68: this.toolbar = [
! 69: [ "fontname", "space",
! 70: "fontsize", "space",
! 71: "formatblock", "space",
! 72: "bold", "italic", "underline", "separator",
! 73: "strikethrough", "subscript", "superscript", "separator",
! 74: "copy", "cut", "paste", "space", "undo", "redo" ],
! 75:
! 76: [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator",
! 77: "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator",
! 78: "forecolor", "hilitecolor", "textindicator", "separator",
! 79: "inserthorizontalrule", "createlink", "insertimage", "inserttable", "htmlmode", "separator",
! 80: "popupeditor", "separator", "showhelp", "about" ]
! 81: ];
! 82:
! 83: this.fontname = {
! 84: "Arial": 'arial,helvetica,sans-serif',
! 85: "Courier New": 'courier new,courier,monospace',
! 86: "Georgia": 'georgia,times new roman,times,serif',
! 87: "Tahoma": 'tahoma,arial,helvetica,sans-serif',
! 88: "Times New Roman": 'times new roman,times,serif',
! 89: "Verdana": 'verdana,arial,helvetica,sans-serif',
! 90: "impact": 'impact',
! 91: "WingDings": 'wingdings'
! 92: };
! 93:
! 94: this.fontsize = {
! 95: "1 (8 pt)": "1",
! 96: "2 (10 pt)": "2",
! 97: "3 (12 pt)": "3",
! 98: "4 (14 pt)": "4",
! 99: "5 (18 pt)": "5",
! 100: "6 (24 pt)": "6",
! 101: "7 (36 pt)": "7"
! 102: };
! 103:
! 104: this.formatblock = {
! 105: "Heading 1": "h1",
! 106: "Heading 2": "h2",
! 107: "Heading 3": "h3",
! 108: "Heading 4": "h4",
! 109: "Heading 5": "h5",
! 110: "Heading 6": "h6",
! 111: "Normal": "p",
! 112: "Address": "address",
! 113: "Formatted": "pre"
! 114: };
! 115:
! 116: this.customSelects = {};
! 117:
! 118: function cut_copy_paste(e, cmd, obj) {
! 119: try {
! 120: e.execCommand(cmd);
! 121: } catch (e) {
! 122: if (HTMLArea.is_gecko) {
! 123: alert("Some revisions of Mozilla/Gecko do not support programatic " +
! 124: "access to cut/copy/paste functions, for security reasons. " +
! 125: "Your browser is one of them. Please use the standard key combinations:\n" +
! 126: "CTRL-X for cut, CTRL-C for copy, CTRL-V for paste.");
! 127: obj.element.style.display = "none";
! 128: }
! 129: }
! 130: };
! 131:
! 132: // ADDING CUSTOM BUTTONS: please read below!
! 133: // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]"
! 134: // - ID: unique ID for the button. If the button calls document.execCommand
! 135: // it's wise to give it the same name as the called command.
! 136: // - ACTION: function that gets called when the button is clicked.
! 137: // it has the following prototype:
! 138: // function(editor, buttonName)
! 139: // - editor is the HTMLArea object that triggered the call
! 140: // - buttonName is the ID of the clicked button
! 141: // These 2 parameters makes it possible for you to use the same
! 142: // handler for more HTMLArea objects or for more different buttons.
! 143: // - ToolTip: default tooltip, for cases when it is not defined in the -lang- file (HTMLArea.I18N)
! 144: // - Icon: path to an icon image file for the button (TODO: use one image for all buttons!)
! 145: // - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time.
! 146: this.btnList = {
! 147: bold: [ "Bold", "images/ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ],
! 148: italic: [ "Italic", "images/ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ],
! 149: underline: [ "Underline", "images/ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ],
! 150: strikethrough: [ "Strikethrough", "images/ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ],
! 151: subscript: [ "Subscript", "images/ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ],
! 152: superscript: [ "Superscript", "images/ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ],
! 153: justifyleft: [ "Justify Left", "images/ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ],
! 154: justifycenter: [ "Justify Center", "images/ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ],
! 155: justifyright: [ "Justify Right", "images/ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ],
! 156: justifyfull: [ "Justify Full", "images/ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ],
! 157: insertorderedlist: [ "Ordered List", "images/ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ],
! 158: insertunorderedlist: [ "Bulleted List", "images/ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ],
! 159: outdent: [ "Decrease Indent", "images/ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ],
! 160: indent: [ "Increase Indent", "images/ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ],
! 161: forecolor: [ "Font Color", "images/ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ],
! 162: hilitecolor: [ "Background Color", "images/ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ],
! 163: inserthorizontalrule: [ "Horizontal Rule", "images/ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ],
! 164: createlink: [ "Insert Web Link", "images/ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ],
! 165: insertimage: [ "Insert Image", "images/ed_image.gif", false, function(e) {e.execCommand("insertimage");} ],
! 166: inserttable: [ "Insert Table", "images/insert_table.gif", false, function(e) {e.execCommand("inserttable");} ],
! 167: htmlmode: [ "Toggle HTML Source", "images/ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ],
! 168: popupeditor: [ "Enlarge Editor", "images/fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ],
! 169: about: [ "About this editor", "images/ed_about.gif", true, function(e) {e.execCommand("about");} ],
! 170: showhelp: [ "Help using editor", "images/ed_help.gif", true, function(e) {e.execCommand("showhelp");} ],
! 171: undo: [ "Undoes your last action", "images/ed_undo.gif", false, function(e) {e.execCommand("undo");} ],
! 172: redo: [ "Redoes your last action", "images/ed_redo.gif", false, function(e) {e.execCommand("redo");} ],
! 173: cut: [ "Cut selection", "images/ed_cut.gif", false, cut_copy_paste ],
! 174: copy: [ "Copy selection", "images/ed_copy.gif", false, cut_copy_paste ],
! 175: paste: [ "Paste from clipboard", "images/ed_paste.gif", false, cut_copy_paste ]
! 176: };
! 177: /* ADDING CUSTOM BUTTONS
! 178: * ---------------------
! 179: *
! 180: * It is recommended that you add the custom buttons in an external
! 181: * file and leave this one unchanged. That's because when we
! 182: * (InteractiveTools.com) release a new official version, it's less
! 183: * likely that you will have problems upgrading HTMLArea.
! 184: *
! 185: * Example on how to add a custom button when you construct the HTMLArea:
! 186: *
! 187: * var editor = new HTMLArea("your_text_area_id");
! 188: * var cfg = editor.config; // this is the default configuration
! 189: * cfg.btnList["my-hilite"] =
! 190: * [ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action
! 191: * "Highlight selection", // tooltip
! 192: * "my_hilite.gif", // image
! 193: * false // disabled in text mode
! 194: * ];
! 195: * cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar
! 196: *
! 197: * An alternate (also more convenient and recommended) way to
! 198: * accomplish this is to use the registerButton function below.
! 199: */
! 200: // initialize tooltips from the I18N module
! 201: for (var i in this.btnList) {
! 202: var btn = this.btnList[i];
! 203: if (typeof HTMLArea.I18N.tooltips[i] != "undefined") {
! 204: btn[0] = HTMLArea.I18N.tooltips[i];
! 205: }
! 206: }
! 207: };
! 208:
! 209: /** Helper function: register a new button with the configuration. It can be
! 210: * called with all 5 arguments, or with only one (first one). When called with
! 211: * only one argument it must be an object with the following properties: id,
! 212: * tooltip, image, textMode, action. Examples:
! 213: *
! 214: * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...});
! 215: * 2. config.registerButton({
! 216: * id : "my-hilite", // the ID of your button
! 217: * tooltip : "Hilite text", // the tooltip
! 218: * image : "my-hilite.gif", // image to be displayed in the toolbar
! 219: * textMode : false, // disabled in text mode
! 220: * action : function(editor) { // called when the button is clicked
! 221: * editor.surroundHTML('<span class="hilite">', '</span>');
! 222: * },
! 223: * context : "p" // will be disabled if outside a <p> element
! 224: * });
! 225: */
! 226: HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) {
! 227: var the_id;
! 228: if (typeof id == "string") {
! 229: the_id = id;
! 230: } else if (typeof id == "object") {
! 231: the_id = id.id;
! 232: } else {
! 233: alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
! 234: return false;
! 235: }
! 236: // check for existing id
! 237: if (typeof this.customSelects[the_id] != "undefined") {
! 238: alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
! 239: }
! 240: if (typeof this.btnList[the_id] != "undefined") {
! 241: alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
! 242: }
! 243: switch (typeof id) {
! 244: case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break;
! 245: case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break;
! 246: }
! 247: };
! 248:
! 249: /** The following helper function registers a dropdown box with the editor
! 250: * configuration. You still have to add it to the toolbar, same as with the
! 251: * buttons. Call it like this:
! 252: *
! 253: * FIXME: add example
! 254: */
! 255: HTMLArea.Config.prototype.registerDropdown = function(object) {
! 256: // check for existing id
! 257: if (typeof this.customSelects[object.id] != "undefined") {
! 258: alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
! 259: }
! 260: if (typeof this.btnList[object.id] != "undefined") {
! 261: alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
! 262: }
! 263: this.customSelects[object.id] = object;
! 264: };
! 265:
! 266: /** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
! 267: HTMLArea.replaceAll = function(config) {
! 268: var tas = document.getElementsByTagName("textarea");
! 269: for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate());
! 270: };
! 271:
! 272: /** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
! 273: HTMLArea.replace = function(id, config) {
! 274: var ta = document.getElementById(id);
! 275: return ta ? (new HTMLArea(ta, config)).generate() : null;
! 276: };
! 277:
! 278: // Creates the toolbar and appends it to the _htmlarea
! 279: HTMLArea.prototype._createToolbar = function () {
! 280: var editor = this; // to access this in nested functions
! 281:
! 282: var toolbar = document.createElement("div");
! 283: this._toolbar = toolbar;
! 284: toolbar.className = "toolbar";
! 285: toolbar.unselectable = "1";
! 286: var tb_row = null;
! 287: var tb_objects = new Object();
! 288: this._toolbarObjects = tb_objects;
! 289:
! 290: // creates a new line in the toolbar
! 291: function newLine() {
! 292: var table = document.createElement("table");
! 293: table.border = "0px";
! 294: table.cellSpacing = "0px";
! 295: table.cellPadding = "0px";
! 296: toolbar.appendChild(table);
! 297: // TBODY is required for IE, otherwise you don't see anything
! 298: // in the TABLE.
! 299: var tb_body = document.createElement("tbody");
! 300: table.appendChild(tb_body);
! 301: tb_row = document.createElement("tr");
! 302: tb_body.appendChild(tb_row);
! 303: }; // END of function: newLine
! 304: // init first line
! 305: newLine();
! 306:
! 307: // updates the state of a toolbar element. This function is member of
! 308: // a toolbar element object (unnamed objects created by createButton or
! 309: // createSelect functions below).
! 310: function setButtonStatus(id, newval) {
! 311: var oldval = this[id];
! 312: var el = this.element;
! 313: if (oldval != newval) {
! 314: switch (id) {
! 315: case "enabled":
! 316: if (newval) {
! 317: HTMLArea._removeClass(el, "buttonDisabled");
! 318: el.disabled = false;
! 319: } else {
! 320: HTMLArea._addClass(el, "buttonDisabled");
! 321: el.disabled = true;
! 322: }
! 323: break;
! 324: case "active":
! 325: if (newval) {
! 326: HTMLArea._addClass(el, "buttonPressed");
! 327: } else {
! 328: HTMLArea._removeClass(el, "buttonPressed");
! 329: }
! 330: break;
! 331: }
! 332: this[id] = newval;
! 333: }
! 334: }; // END of function: setButtonStatus
! 335:
! 336: // this function will handle creation of combo boxes. Receives as
! 337: // parameter the name of a button as defined in the toolBar config.
! 338: // This function is called from createButton, above, if the given "txt"
! 339: // doesn't match a button.
! 340: function createSelect(txt) {
! 341: var options = null;
! 342: var el = null;
! 343: var cmd = null;
! 344: var customSelects = editor.config.customSelects;
! 345: var context = null;
! 346: switch (txt) {
! 347: case "fontsize":
! 348: case "fontname":
! 349: case "formatblock":
! 350: // the following line retrieves the correct
! 351: // configuration option because the variable name
! 352: // inside the Config object is named the same as the
! 353: // button/select in the toolbar. For instance, if txt
! 354: // == "formatblock" we retrieve config.formatblock (or
! 355: // a different way to write it in JS is
! 356: // config["formatblock"].
! 357: options = editor.config[txt];
! 358: cmd = txt;
! 359: break;
! 360: default:
! 361: // try to fetch it from the list of registered selects
! 362: cmd = txt;
! 363: var dropdown = customSelects[cmd];
! 364: if (typeof dropdown != "undefined") {
! 365: options = dropdown.options;
! 366: context = dropdown.context;
! 367: } else {
! 368: alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
! 369: }
! 370: break;
! 371: }
! 372: if (options) {
! 373: el = document.createElement("select");
! 374: var obj = {
! 375: name : txt, // field name
! 376: element : el, // the UI element (SELECT)
! 377: enabled : true, // is it enabled?
! 378: text : false, // enabled in text mode?
! 379: cmd : cmd, // command ID
! 380: state : setButtonStatus, // for changing state
! 381: context : context
! 382: };
! 383: tb_objects[txt] = obj;
! 384: for (var i in options) {
! 385: var op = document.createElement("option");
! 386: op.appendChild(document.createTextNode(i));
! 387: op.value = options[i];
! 388: el.appendChild(op);
! 389: }
! 390: HTMLArea._addEvent(el, "change", function () {
! 391: editor._comboSelected(el, txt);
! 392: });
! 393: }
! 394: return el;
! 395: }; // END of function: createSelect
! 396:
! 397: // appends a new button to toolbar
! 398: function createButton(txt) {
! 399: // the element that will be created
! 400: var el = null;
! 401: var btn = null;
! 402: switch (txt) {
! 403: case "separator":
! 404: el = document.createElement("div");
! 405: el.className = "separator";
! 406: break;
! 407: case "space":
! 408: el = document.createElement("div");
! 409: el.className = "space";
! 410: break;
! 411: case "linebreak":
! 412: newLine();
! 413: return false;
! 414: case "textindicator":
! 415: el = document.createElement("div");
! 416: el.appendChild(document.createTextNode("A"));
! 417: el.className = "indicator";
! 418: el.title = HTMLArea.I18N.tooltips.textindicator;
! 419: var obj = {
! 420: name : txt, // the button name (i.e. 'bold')
! 421: element : el, // the UI element (DIV)
! 422: enabled : true, // is it enabled?
! 423: active : false, // is it pressed?
! 424: text : false, // enabled in text mode?
! 425: cmd : "textindicator", // the command ID
! 426: state : setButtonStatus // for changing state
! 427: };
! 428: tb_objects[txt] = obj;
! 429: break;
! 430: default:
! 431: btn = editor.config.btnList[txt];
! 432: }
! 433: if (!el && btn) {
! 434: el = document.createElement("div");
! 435: el.title = btn[0];
! 436: el.className = "button";
! 437: // let's just pretend we have a button object, and
! 438: // assign all the needed information to it.
! 439: var obj = {
! 440: name : txt, // the button name (i.e. 'bold')
! 441: element : el, // the UI element (DIV)
! 442: enabled : true, // is it enabled?
! 443: active : false, // is it pressed?
! 444: text : btn[2], // enabled in text mode?
! 445: cmd : btn[3], // the command ID
! 446: state : setButtonStatus, // for changing state
! 447: context : btn[4] || null // enabled in a certain context?
! 448: };
! 449: tb_objects[txt] = obj;
! 450: // handlers to emulate nice flat toolbar buttons
! 451: HTMLArea._addEvent(el, "mouseover", function () {
! 452: if (obj.enabled) {
! 453: HTMLArea._addClass(el, "buttonHover");
! 454: }
! 455: });
! 456: HTMLArea._addEvent(el, "mouseout", function () {
! 457: if (obj.enabled) with (HTMLArea) {
! 458: _removeClass(el, "buttonHover");
! 459: _removeClass(el, "buttonActive");
! 460: (obj.active) && _addClass(el, "buttonPressed");
! 461: }
! 462: });
! 463: HTMLArea._addEvent(el, "mousedown", function (ev) {
! 464: if (obj.enabled) with (HTMLArea) {
! 465: _addClass(el, "buttonActive");
! 466: _removeClass(el, "buttonPressed");
! 467: _stopEvent(is_ie ? window.event : ev);
! 468: }
! 469: });
! 470: // when clicked, do the following:
! 471: HTMLArea._addEvent(el, "click", function (ev) {
! 472: if (obj.enabled) with (HTMLArea) {
! 473: _removeClass(el, "buttonActive");
! 474: _removeClass(el, "buttonHover");
! 475: obj.cmd(editor, obj.name, obj);
! 476: _stopEvent(is_ie ? window.event : ev);
! 477: }
! 478: });
! 479: var img = document.createElement("img");
! 480: img.src = editor.imgURL(btn[1]);
! 481: img.style.width = "18px";
! 482: img.style.height = "18px";
! 483: el.appendChild(img);
! 484: } else if (!el) {
! 485: el = createSelect(txt);
! 486: }
! 487: if (el) {
! 488: var tb_cell = document.createElement("td");
! 489: tb_row.appendChild(tb_cell);
! 490: tb_cell.appendChild(el);
! 491: } else {
! 492: alert("FIXME: Unknown toolbar item: " + txt);
! 493: }
! 494: return el;
! 495: };
! 496:
! 497: var first = true;
! 498: for (var i in this.config.toolbar) {
! 499: if (!first) {
! 500: createButton("linebreak");
! 501: } else {
! 502: first = false;
! 503: }
! 504: var group = this.config.toolbar[i];
! 505: for (var j in group) {
! 506: var code = group[j];
! 507: if (/^([IT])\[(.*?)\]/.test(code)) {
! 508: // special case, create text label
! 509: var l7ed = RegExp.$1 == "I"; // localized?
! 510: var label = RegExp.$2;
! 511: if (l7ed) {
! 512: label = HTMLArea.I18N.custom[label];
! 513: }
! 514: var tb_cell = document.createElement("td");
! 515: tb_row.appendChild(tb_cell);
! 516: tb_cell.className = "label";
! 517: tb_cell.innerHTML = label;
! 518: } else {
! 519: createButton(code);
! 520: }
! 521: }
! 522: }
! 523:
! 524: this._htmlArea.appendChild(toolbar);
! 525: };
! 526:
! 527: HTMLArea.prototype._createStatusBar = function() {
! 528: var div = document.createElement("div");
! 529: div.className = "statusBar";
! 530: this._htmlArea.appendChild(div);
! 531: this._statusBar = div;
! 532: div.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
! 533: // creates a holder for the path view
! 534: div = document.createElement("span");
! 535: div.className = "statusBarTree";
! 536: this._statusBarTree = div;
! 537: this._statusBar.appendChild(div);
! 538: if (!this.config.statusBar) {
! 539: // disable it...
! 540: div.style.display = "none";
! 541: }
! 542: };
! 543:
! 544: // Creates the HTMLArea object and replaces the textarea with it.
! 545: HTMLArea.prototype.generate = function () {
! 546: var editor = this; // we'll need "this" in some nested functions
! 547: // get the textarea
! 548: var textarea = this._textArea;
! 549: if (typeof textarea == "string") {
! 550: // it's not element but ID
! 551: this._textArea = textarea = document.getElementById(textarea);
! 552: }
! 553: this._ta_size = {
! 554: w: textarea.offsetWidth,
! 555: h: textarea.offsetHeight
! 556: };
! 557: textarea.style.display = "none";
! 558:
! 559: // create the editor framework
! 560: var htmlarea = document.createElement("div");
! 561: htmlarea.className = "htmlarea";
! 562: this._htmlArea = htmlarea;
! 563:
! 564: // insert the editor before the textarea.
! 565: textarea.parentNode.insertBefore(htmlarea, textarea);
! 566:
! 567: if (textarea.form) {
! 568: // we have a form, on submit get the HTMLArea content and
! 569: // update original textarea.
! 570: textarea.form.onsubmit = function() {
! 571: editor._textArea.value = editor.getHTML();
! 572: };
! 573: }
! 574:
! 575: // add a handler for the "back/forward" case -- on body.unload we save
! 576: // the HTML content into the original textarea.
! 577: window.onunload = function() {
! 578: editor._textArea.value = editor.getHTML();
! 579: };
! 580:
! 581: // creates & appends the toolbar
! 582: this._createToolbar();
! 583:
! 584: // create the IFRAME
! 585: var iframe = document.createElement("iframe");
! 586: htmlarea.appendChild(iframe);
! 587:
! 588: this._iframe = iframe;
! 589:
! 590: // creates & appends the status bar, if the case
! 591: this._createStatusBar();
! 592:
! 593: // remove the default border as it keeps us from computing correctly
! 594: // the sizes. (somebody tell me why doesn't this work in IE)
! 595:
! 596: if (!HTMLArea.is_ie) {
! 597: iframe.style.borderWidth = "1px";
! 598: // iframe.frameBorder = "1";
! 599: // iframe.marginHeight = "0";
! 600: // iframe.marginWidth = "0";
! 601: }
! 602:
! 603: // size the IFRAME according to user's prefs or initial textarea
! 604: var height = (this.config.height == "auto" ? (this._ta_size.h + "px") : this.config.height);
! 605: height = parseInt(height);
! 606: var width = (this.config.width == "auto" ? (this._ta_size.w + "px") : this.config.width);
! 607: width = parseInt(width);
! 608:
! 609: if (!HTMLArea.is_ie) {
! 610: height -= 2;
! 611: width -= 2;
! 612: }
! 613:
! 614: iframe.style.width = width + "px";
! 615: if (this.config.sizeIncludesToolbar) {
! 616: // substract toolbar height
! 617: height -= this._toolbar.offsetHeight;
! 618: height -= this._statusBar.offsetHeight;
! 619: }
! 620: if (height < 0) {
! 621: height = 0;
! 622: }
! 623: iframe.style.height = height + "px";
! 624:
! 625: // the editor including the toolbar now have the same size as the
! 626: // original textarea.. which means that we need to reduce that a bit.
! 627: textarea.style.width = iframe.style.width;
! 628: textarea.style.height = iframe.style.height;
! 629:
! 630: // IMPORTANT: we have to allow Mozilla a short time to recognize the
! 631: // new frame. Otherwise we get a stupid exception.
! 632: function initIframe() {
! 633: var doc = editor._iframe.contentWindow.document;
! 634: if (!doc) {
! 635: // Try again..
! 636: // FIXME: don't know what else to do here. Normally
! 637: // we'll never reach this point.
! 638: if (HTMLArea.is_gecko) {
! 639: setTimeout(initIframe, 10);
! 640: return false;
! 641: } else {
! 642: alert("ERROR: IFRAME can't be initialized.");
! 643: }
! 644: }
! 645: if (HTMLArea.is_gecko) {
! 646: // enable editable mode for Mozilla
! 647: doc.designMode = "on";
! 648: }
! 649: editor._doc = doc;
! 650: doc.open();
! 651: var html = "<html>\n";
! 652: html += "<head>\n";
! 653: html += "<style>" + editor.config.pageStyle + "</style>\n";
! 654: html += "</head>\n";
! 655: html += "<body>\n";
! 656: html += editor._textArea.value;
! 657: html += "</body>\n";
! 658: html += "</html>";
! 659: doc.write(html);
! 660: doc.close();
! 661:
! 662: if (HTMLArea.is_ie) {
! 663: // enable editable mode for IE. For some reason this
! 664: // doesn't work if done in the same place as for Gecko
! 665: // (above).
! 666: doc.body.contentEditable = true;
! 667: }
! 668:
! 669: editor.focusEditor();
! 670: // intercept some events; for updating the toolbar & keyboard handlers
! 671: HTMLArea._addEvents
! 672: (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
! 673: function (event) {
! 674: return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
! 675: });
! 676: editor.updateToolbar();
! 677: };
! 678: setTimeout(initIframe, HTMLArea.is_gecko ? 10 : 0);
! 679: };
! 680:
! 681: // Switches editor mode; parameter can be "textmode" or "wysiwyg". If no
! 682: // parameter was passed this function toggles between modes.
! 683: HTMLArea.prototype.setMode = function(mode) {
! 684: if (typeof mode == "undefined") {
! 685: mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode");
! 686: }
! 687: switch (mode) {
! 688: case "textmode":
! 689: this._textArea.value = this.getHTML();
! 690: this._iframe.style.display = "none";
! 691: this._textArea.style.display = "block";
! 692: if (this.config.statusBar) {
! 693: this._statusBar.innerHTML = HTMLArea.I18N.msg["TEXT_MODE"];
! 694: }
! 695: break;
! 696: case "wysiwyg":
! 697: if (HTMLArea.is_gecko) {
! 698: // disable design mode before changing innerHTML
! 699: this._doc.designMode = "off";
! 700: }
! 701: this._doc.body.innerHTML = this.getHTML();
! 702: this._iframe.style.display = "block";
! 703: this._textArea.style.display = "none";
! 704: if (HTMLArea.is_gecko) {
! 705: // we need to refresh that info for Moz-1.3a
! 706: this._doc.designMode = "on";
! 707: }
! 708: if (this.config.statusBar) {
! 709: this._statusBar.innerHTML = '';
! 710: this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
! 711: this._statusBar.appendChild(this._statusBarTree);
! 712: }
! 713: break;
! 714: default:
! 715: alert("Mode <" + mode + "> not defined!");
! 716: return false;
! 717: }
! 718: this._editMode = mode;
! 719: this.focusEditor();
! 720: };
! 721:
! 722: /***************************************************
! 723: * Category: PLUGINS
! 724: ***************************************************/
! 725:
! 726: // Create the specified plugin and register it with this HTMLArea
! 727: HTMLArea.prototype.registerPlugin = function(pluginName) {
! 728: this.plugins[pluginName] = eval("new " + pluginName + "(this);");
! 729: };
! 730:
! 731: // static function that loads the required plugin and lang file, based on the
! 732: // language loaded already for HTMLArea. You better make sure that the plugin
! 733: // _has_ that language, otherwise shit might happen ;-)
! 734: HTMLArea.loadPlugin = function(pluginName) {
! 735: var editorurl = '';
! 736: if (typeof _editor_url != "undefined") {
! 737: editorurl = _editor_url + "/";
! 738: }
! 739: var dir = editorurl + "plugins/" + pluginName;
! 740: var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g,
! 741: function (str, l1, l2, l3) {
! 742: return l1 + "-" + l2.toLowerCase() + l3;
! 743: }).toLowerCase() + ".js";
! 744: document.write("<script type='text/javascript' src='" + dir + "/" + plugin + "'></script>");
! 745: document.write("<script type='text/javascript' src='" + dir + "/lang/" + HTMLArea.I18N.lang + ".js'></script>");
! 746: };
! 747:
! 748: /***************************************************
! 749: * Category: EDITOR UTILITIES
! 750: ***************************************************/
! 751:
! 752: HTMLArea.prototype.forceRedraw = function() {
! 753: this._doc.body.style.visibility = "hidden";
! 754: this._doc.body.style.visibility = "visible";
! 755: // this._doc.body.innerHTML = this.getInnerHTML();
! 756: };
! 757:
! 758: // focuses the iframe window. returns a reference to the editor document.
! 759: HTMLArea.prototype.focusEditor = function() {
! 760: switch (this._editMode) {
! 761: case "wysiwyg" : this._iframe.contentWindow.focus(); break;
! 762: case "textmode": this._textArea.focus(); break;
! 763: default : alert("ERROR: mode " + this._editMode + " is not defined");
! 764: }
! 765: return this._doc;
! 766: };
! 767:
! 768: // updates enabled/disable/active state of the toolbar elements
! 769: HTMLArea.prototype.updateToolbar = function(noStatus) {
! 770: var doc = this._doc;
! 771: var text = (this._editMode == "textmode");
! 772: var ancestors = null;
! 773: if (!text) {
! 774: ancestors = this.getAllAncestors();
! 775: if (this.config.statusBar && !noStatus) {
! 776: this._statusBarTree.innerHTML = ''; // clear
! 777: for (var i = ancestors.length; --i >= 0;) {
! 778: var el = ancestors[i];
! 779: if (!el) {
! 780: // hell knows why we get here; this
! 781: // could be a classic example of why
! 782: // it's good to check for conditions
! 783: // that are impossible to happen ;-)
! 784: continue;
! 785: }
! 786: var a = document.createElement("a");
! 787: a.href = "#";
! 788: a.el = el;
! 789: a.editor = this;
! 790: a.onclick = function() {
! 791: this.blur();
! 792: this.editor.selectNodeContents(this.el);
! 793: this.editor.updateToolbar(true);
! 794: return false;
! 795: };
! 796: a.oncontextmenu = function() {
! 797: // TODO: add context menu here
! 798: this.blur();
! 799: var info = "Inline style:\n\n";
! 800: info += this.el.style.cssText.split(/;\s*/).join(";\n");
! 801: alert(info);
! 802: return false;
! 803: };
! 804: var txt = el.tagName.toLowerCase();
! 805: a.title = el.style.cssText;
! 806: if (el.id) {
! 807: txt += "#" + el.id;
! 808: }
! 809: if (el.className) {
! 810: txt += "." + el.className;
! 811: }
! 812: a.appendChild(document.createTextNode(txt));
! 813: this._statusBarTree.appendChild(a);
! 814: if (i != 0) {
! 815: this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
! 816: }
! 817: }
! 818: }
! 819: }
! 820: for (var i in this._toolbarObjects) {
! 821: var btn = this._toolbarObjects[i];
! 822: var cmd = i;
! 823: var inContext = true;
! 824: if (btn.context && !text) {
! 825: inContext = false;
! 826: var context = btn.context;
! 827: var attrs = [];
! 828: if (/(.*)\[(.*?)\]/.test(context)) {
! 829: context = RegExp.$1;
! 830: attrs = RegExp.$2.split(",");
! 831: }
! 832: context = context.toLowerCase();
! 833: var match = (context == "*");
! 834: for (var k in ancestors) {
! 835: if (!ancestors[k]) {
! 836: // the impossible really happens.
! 837: continue;
! 838: }
! 839: if (match || (ancestors[k].tagName.toLowerCase() == context)) {
! 840: inContext = true;
! 841: for (var ka in attrs) {
! 842: if (!eval("ancestors[k]." + attrs[ka])) {
! 843: inContext = false;
! 844: break;
! 845: }
! 846: }
! 847: if (inContext) {
! 848: break;
! 849: }
! 850: }
! 851: }
! 852: }
! 853: btn.state("enabled", (!text || btn.text) && inContext);
! 854: if (typeof cmd == "function") {
! 855: continue;
! 856: }
! 857: // look-it-up in the custom dropdown boxes
! 858: var dropdown = this.config.customSelects[cmd];
! 859: if ((!text || btn.text) && (typeof dropdown != "undefined")) {
! 860: dropdown.refresh(this);
! 861: continue;
! 862: }
! 863: switch (cmd) {
! 864: case "fontname":
! 865: case "fontsize":
! 866: case "formatblock":
! 867: if (!text) {
! 868: var value = ("" + doc.queryCommandValue(cmd)).toLowerCase();
! 869: if (!value) {
! 870: // FIXME: what do we do here?
! 871: break;
! 872: }
! 873: // HACK -- retrieve the config option for this
! 874: // combo box. We rely on the fact that the
! 875: // variable in config has the same name as
! 876: // button name in the toolbar.
! 877: var options = this.config[cmd];
! 878: var k = 0;
! 879: // btn.element.selectedIndex = 0;
! 880: for (var j in options) {
! 881: // FIXME: the following line is scary.
! 882: if ((j.toLowerCase() == value) ||
! 883: (options[j].substr(0, value.length).toLowerCase() == value)) {
! 884: btn.element.selectedIndex = k;
! 885: break;
! 886: }
! 887: ++k;
! 888: }
! 889: }
! 890: break;
! 891: case "textindicator":
! 892: if (!text) {
! 893: try {with (btn.element.style) {
! 894: backgroundColor = HTMLArea._makeColor(
! 895: doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor"));
! 896: if (/transparent/i.test(backgroundColor)) {
! 897: // Mozilla
! 898: backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor"));
! 899: }
! 900: color = HTMLArea._makeColor(doc.queryCommandValue("forecolor"));
! 901: fontFamily = doc.queryCommandValue("fontname");
! 902: fontWeight = doc.queryCommandState("bold") ? "bold" : "normal";
! 903: fontStyle = doc.queryCommandState("italic") ? "italic" : "normal";
! 904: }} catch (e) {
! 905: // alert(e + "\n\n" + cmd);
! 906: }
! 907: }
! 908: break;
! 909: case "htmlmode": btn.state("active", text); break;
! 910: default:
! 911: try {
! 912: btn.state("active", (!text && doc.queryCommandState(cmd)));
! 913: } catch (e) {}
! 914: }
! 915: }
! 916: };
! 917:
! 918: /** Returns a node after which we can insert other nodes, in the current
! 919: * selection. The selection is removed. It splits a text node, if needed.
! 920: */
! 921: HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
! 922: if (!HTMLArea.is_ie) {
! 923: var sel = this._getSelection();
! 924: var range = this._createRange(sel);
! 925: // remove the current selection
! 926: sel.removeAllRanges();
! 927: range.deleteContents();
! 928: var node = range.startContainer;
! 929: var pos = range.startOffset;
! 930: switch (node.nodeType) {
! 931: case 3: // Node.TEXT_NODE
! 932: // we have to split it at the caret position.
! 933: if (toBeInserted.nodeType == 3) {
! 934: // do optimized insertion
! 935: node.insertData(pos, toBeInserted.data);
! 936: range = this._createRange();
! 937: range.setEnd(node, pos + toBeInserted.length);
! 938: range.setStart(node, pos + toBeInserted.length);
! 939: sel.addRange(range);
! 940: } else {
! 941: node = node.splitText(pos);
! 942: var selnode = toBeInserted;
! 943: if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
! 944: selnode = selnode.firstChild;
! 945: }
! 946: node.parentNode.insertBefore(toBeInserted, node);
! 947: this.selectNodeContents(selnode);
! 948: this.updateToolbar();
! 949: }
! 950: break;
! 951: case 1: // Node.ELEMENT_NODE
! 952: var selnode = toBeInserted;
! 953: if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
! 954: selnode = selnode.firstChild;
! 955: }
! 956: node.insertBefore(toBeInserted, node.childNodes[pos]);
! 957: this.selectNodeContents(selnode);
! 958: this.updateToolbar();
! 959: break;
! 960: }
! 961: } else {
! 962: return null; // this function not yet used for IE <FIXME>
! 963: }
! 964: };
! 965:
! 966: // Returns the deepest node that contains both endpoints of the selection.
! 967: HTMLArea.prototype.getParentElement = function() {
! 968: var sel = this._getSelection();
! 969: var range = this._createRange(sel);
! 970: if (HTMLArea.is_ie) {
! 971: return range.parentElement ? range.parentElement() : this._doc.body;
! 972: } else {
! 973: var p = range.commonAncestorContainer;
! 974: while (p.nodeType == 3) {
! 975: p = p.parentNode;
! 976: }
! 977: return p;
! 978: }
! 979: };
! 980:
! 981: // Returns an array with all the ancestor nodes of the selection.
! 982: HTMLArea.prototype.getAllAncestors = function() {
! 983: var p = this.getParentElement();
! 984: var a = [];
! 985: while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
! 986: a.push(p);
! 987: p = p.parentNode;
! 988: }
! 989: a.push(this._doc.body);
! 990: return a;
! 991: };
! 992:
! 993: // Selects the contents inside the given node
! 994: HTMLArea.prototype.selectNodeContents = function(node, pos) {
! 995: this.focusEditor();
! 996: this.forceRedraw();
! 997: var range;
! 998: var collapsed = (typeof pos != "undefined");
! 999: if (HTMLArea.is_ie) {
! 1000: range = this._doc.body.createTextRange();
! 1001: range.moveToElementText(node);
! 1002: (collapsed) && range.collapse(pos);
! 1003: range.select();
! 1004: } else {
! 1005: var sel = this._getSelection();
! 1006: range = this._doc.createRange();
! 1007: range.selectNodeContents(node);
! 1008: (collapsed) && range.collapse(pos);
! 1009: sel.removeAllRanges();
! 1010: sel.addRange(range);
! 1011: }
! 1012: };
! 1013:
! 1014: /** Call this function to insert HTML code at the current position. It deletes
! 1015: * the selection, if any.
! 1016: */
! 1017: HTMLArea.prototype.insertHTML = function(html) {
! 1018: var sel = this._getSelection();
! 1019: var range = this._createRange(sel);
! 1020: if (HTMLArea.is_ie) {
! 1021: range.pasteHTML(html);
! 1022: } else {
! 1023: // construct a new document fragment with the given HTML
! 1024: var fragment = this._doc.createDocumentFragment();
! 1025: var div = this._doc.createElement("div");
! 1026: div.innerHTML = html;
! 1027: while (div.firstChild) {
! 1028: // the following call also removes the node from div
! 1029: fragment.appendChild(div.firstChild);
! 1030: }
! 1031: // this also removes the selection
! 1032: var node = this.insertNodeAtSelection(fragment);
! 1033: }
! 1034: };
! 1035:
! 1036: /**
! 1037: * Call this function to surround the existing HTML code in the selection with
! 1038: * your tags. FIXME: buggy! This function will be deprecated "soon".
! 1039: */
! 1040: HTMLArea.prototype.surroundHTML = function(startTag, endTag) {
! 1041: var html = this.getSelectedHTML();
! 1042: // the following also deletes the selection
! 1043: this.insertHTML(startTag + html + endTag);
! 1044: };
! 1045:
! 1046: /// Retrieve the selected block
! 1047: HTMLArea.prototype.getSelectedHTML = function() {
! 1048: var sel = this._getSelection();
! 1049: var range = this._createRange(sel);
! 1050: var existing = null;
! 1051: if (HTMLArea.is_ie) {
! 1052: existing = range.htmlText;
! 1053: } else {
! 1054: existing = HTMLArea.getHTML(range.cloneContents(), false);
! 1055: }
! 1056: return existing;
! 1057: };
! 1058:
! 1059: // Called when the user clicks on "InsertImage" button
! 1060: HTMLArea.prototype._insertImage = function() {
! 1061: var editor = this; // for nested functions
! 1062: this._popupDialog("insert_image.html", function(param) {
! 1063: if (!param) { // user must have pressed Cancel
! 1064: return false;
! 1065: }
! 1066: var sel = editor._getSelection();
! 1067: var range = editor._createRange(sel);
! 1068: editor._doc.execCommand("insertimage", false, param["f_url"]);
! 1069: var img = null;
! 1070: if (HTMLArea.is_ie) {
! 1071: img = range.parentElement();
! 1072: // wonder if this works...
! 1073: if (img.tagName.toLowerCase() != "img") {
! 1074: img = img.previousSibling;
! 1075: }
! 1076: } else {
! 1077: img = range.startContainer.previousSibling;
! 1078: }
! 1079: for (field in param) {
! 1080: var value = param[field];
! 1081: if (!value) {
! 1082: continue;
! 1083: }
! 1084: switch (field) {
! 1085: case "f_alt" : img.alt = value; break;
! 1086: case "f_border" : img.border = parseInt(value); break;
! 1087: case "f_align" : img.align = value; break;
! 1088: case "f_vert" : img.vspace = parseInt(value); break;
! 1089: case "f_horiz" : img.hspace = parseInt(value); break;
! 1090: }
! 1091: }
! 1092: }, null);
! 1093: };
! 1094:
! 1095: // Called when the user clicks the Insert Table button
! 1096: HTMLArea.prototype._insertTable = function() {
! 1097: var sel = this._getSelection();
! 1098: var range = this._createRange(sel);
! 1099: var editor = this; // for nested functions
! 1100: this._popupDialog("insert_table.html", function(param) {
! 1101: if (!param) { // user must have pressed Cancel
! 1102: return false;
! 1103: }
! 1104: var doc = editor._doc;
! 1105: // create the table element
! 1106: var table = doc.createElement("table");
! 1107: // assign the given arguments
! 1108: for (var field in param) {
! 1109: var value = param[field];
! 1110: if (!value) {
! 1111: continue;
! 1112: }
! 1113: switch (field) {
! 1114: case "f_width" : table.style.width = value + param["f_unit"]; break;
! 1115: case "f_align" : table.align = value; break;
! 1116: case "f_border" : table.border = parseInt(value); break;
! 1117: case "f_spacing" : table.cellspacing = parseInt(value); break;
! 1118: case "f_padding" : table.cellpadding = parseInt(value); break;
! 1119: }
! 1120: }
! 1121: var tbody = doc.createElement("tbody");
! 1122: table.appendChild(tbody);
! 1123: for (var i = 0; i < param["f_rows"]; ++i) {
! 1124: var tr = doc.createElement("tr");
! 1125: tbody.appendChild(tr);
! 1126: for (var j = 0; j < param["f_cols"]; ++j) {
! 1127: var td = doc.createElement("td");
! 1128: tr.appendChild(td);
! 1129: // Mozilla likes to see something inside the cell.
! 1130: (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br"));
! 1131: }
! 1132: }
! 1133: if (HTMLArea.is_ie) {
! 1134: range.pasteHTML(table.outerHTML);
! 1135: } else {
! 1136: // insert the table
! 1137: editor.insertNodeAtSelection(table);
! 1138: }
! 1139: return true;
! 1140: }, null);
! 1141: };
! 1142:
! 1143: /***************************************************
! 1144: * Category: EVENT HANDLERS
! 1145: ***************************************************/
! 1146:
! 1147: // el is reference to the SELECT object
! 1148: // txt is the name of the select field, as in config.toolbar
! 1149: HTMLArea.prototype._comboSelected = function(el, txt) {
! 1150: this.focusEditor();
! 1151: var value = el.options[el.selectedIndex].value;
! 1152: switch (txt) {
! 1153: case "fontname":
! 1154: case "fontsize": this.execCommand(txt, false, value); break;
! 1155: case "formatblock":
! 1156: (HTMLArea.is_ie) && (value = "<" + value + ">");
! 1157: this.execCommand(txt, false, value);
! 1158: break;
! 1159: default:
! 1160: // try to look it up in the registered dropdowns
! 1161: var dropdown = this.config.customSelects[txt];
! 1162: if (typeof dropdown != "undefined") {
! 1163: dropdown.action(this);
! 1164: } else {
! 1165: alert("FIXME: combo box " + txt + " not implemented");
! 1166: }
! 1167: }
! 1168: };
! 1169:
! 1170: // the execCommand function (intercepts some commands and replaces them with
! 1171: // our own implementation)
! 1172: HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
! 1173: var editor = this; // for nested functions
! 1174: this.focusEditor();
! 1175: switch (cmdID.toLowerCase()) {
! 1176: case "htmlmode" : this.setMode(); break;
! 1177: case "hilitecolor":
! 1178: (HTMLArea.is_ie) && (cmdID = "backcolor");
! 1179: case "forecolor":
! 1180: this._popupDialog("select_color.html", function(color) {
! 1181: if (color) { // selection not canceled
! 1182: editor._doc.execCommand(cmdID, false, "#" + color);
! 1183: }
! 1184: }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID)));
! 1185: break;
! 1186: case "createlink":
! 1187: if (HTMLArea.is_ie || !UI) {
! 1188: this._doc.execCommand(cmdID, UI, param);
! 1189: } else {
! 1190: // browser is Mozilla & wants UI
! 1191: var param;
! 1192: if ((param = prompt("Enter URL"))) {
! 1193: this._doc.execCommand(cmdID, false, param);
! 1194: }
! 1195: }
! 1196: break;
! 1197: case "popupeditor":
! 1198: if (HTMLArea.is_ie) {
! 1199: window.open(this.popupURL("fullscreen.html"), "ha_fullscreen",
! 1200: "toolbar=no,location=no,directories=no,status=no,menubar=no," +
! 1201: "scrollbars=no,resizable=yes,width=640,height=480");
! 1202: } else {
! 1203: window.open(this.popupURL("fullscreen.html"), "ha_fullscreen",
! 1204: "toolbar=no,menubar=no,personalbar=no,width=640,height=480," +
! 1205: "scrollbars=no,resizable=yes");
! 1206: }
! 1207: // pass this object to the newly opened window
! 1208: HTMLArea._object = this;
! 1209: break;
! 1210: case "inserttable": this._insertTable(); break;
! 1211: case "insertimage": this._insertImage(); break;
! 1212: case "about" : this._popupDialog("about.html", null, null); break;
! 1213: case "showhelp" : window.open("reference.html", "ha_help"); break;
! 1214: default: this._doc.execCommand(cmdID, UI, param);
! 1215: }
! 1216: this.updateToolbar();
! 1217: return false;
! 1218: };
! 1219:
! 1220: /** A generic event handler for things that happen in the IFRAME's document.
! 1221: * This function also handles key bindings. */
! 1222: HTMLArea.prototype._editorEvent = function(ev) {
! 1223: var editor = this;
! 1224: var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress");
! 1225: if (keyEvent && ev.ctrlKey) {
! 1226: var sel = null;
! 1227: var range = null;
! 1228: var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
! 1229: var cmd = null;
! 1230: var value = null;
! 1231: switch (key) {
! 1232: case 'a':
! 1233: if (!HTMLArea.is_ie) {
! 1234: // KEY select all
! 1235: sel = this._getSelection();
! 1236: sel.removeAllRanges();
! 1237: range = this._createRange();
! 1238: range.selectNodeContents(this._doc.body);
! 1239: sel.addRange(range);
! 1240: HTMLArea._stopEvent(ev);
! 1241: }
! 1242: break;
! 1243:
! 1244: // simple key commands follow
! 1245:
! 1246: case 'b': cmd = "bold"; break;
! 1247: case 'i': cmd = "italic"; break;
! 1248: case 'u': cmd = "underline"; break;
! 1249: case 's': cmd = "strikethrough"; break;
! 1250: case 'l': cmd = "justifyleft"; break;
! 1251: case 'e': cmd = "justifycenter"; break;
! 1252: case 'r': cmd = "justifyright"; break;
! 1253: case 'j': cmd = "justifyfull"; break;
! 1254:
! 1255: // headings
! 1256: case '1':
! 1257: case '2':
! 1258: case '3':
! 1259: case '4':
! 1260: case '5':
! 1261: case '6':
! 1262: cmd = "formatblock";
! 1263: value = "h" + key;
! 1264: if (HTMLArea.is_ie) {
! 1265: value = "<" + value + ">";
! 1266: }
! 1267: break;
! 1268: }
! 1269: if (cmd) {
! 1270: // execute simple command
! 1271: this.execCommand(cmd, false, value);
! 1272: HTMLArea._stopEvent(ev);
! 1273: }
! 1274: }
! 1275: /*
! 1276: else if (keyEvent) {
! 1277: // other keys here
! 1278: switch (ev.keyCode) {
! 1279: case 13: // KEY enter
! 1280: // if (HTMLArea.is_ie) {
! 1281: this.insertHTML("<br />");
! 1282: HTMLArea._stopEvent(ev);
! 1283: // }
! 1284: break;
! 1285: }
! 1286: }
! 1287: */
! 1288: // update the toolbar state after some time
! 1289: if (editor._timerToolbar) {
! 1290: clearTimeout(editor._timerToolbar);
! 1291: }
! 1292: editor._timerToolbar = setTimeout(function() {
! 1293: editor.updateToolbar();
! 1294: editor._timerToolbar = null;
! 1295: }, 50);
! 1296: };
! 1297:
! 1298: // retrieve the HTML
! 1299: HTMLArea.prototype.getHTML = function() {
! 1300: switch (this._editMode) {
! 1301: case "wysiwyg" : return HTMLArea.getHTML(this._doc.body, false);
! 1302: case "textmode" : return this._textArea.value;
! 1303: default : alert("Mode <" + mode + "> not defined!");
! 1304: }
! 1305: return false;
! 1306: };
! 1307:
! 1308: // retrieve the HTML (fastest version, but uses innerHTML)
! 1309: HTMLArea.prototype.getInnerHTML = function() {
! 1310: switch (this._editMode) {
! 1311: case "wysiwyg" : return this._doc.body.innerHTML;
! 1312: case "textmode" : return this._textArea.value;
! 1313: default : alert("Mode <" + mode + "> not defined!");
! 1314: }
! 1315: return false;
! 1316: };
! 1317:
! 1318: // completely change the HTML inside
! 1319: HTMLArea.prototype.setHTML = function(html) {
! 1320: switch (this._editMode) {
! 1321: case "wysiwyg" : this._doc.body.innerHTML = html; break;
! 1322: case "textmode" : this._textArea.value = html; break;
! 1323: default : alert("Mode <" + mode + "> not defined!");
! 1324: }
! 1325: return false;
! 1326: };
! 1327:
! 1328: /***************************************************
! 1329: * Category: UTILITY FUNCTIONS
! 1330: ***************************************************/
! 1331:
! 1332: // browser identification
! 1333:
! 1334: HTMLArea.agt = navigator.userAgent.toLowerCase();
! 1335: HTMLArea.is_ie = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
! 1336: HTMLArea.is_opera = (HTMLArea.agt.indexOf("opera") != -1);
! 1337: HTMLArea.is_mac = (HTMLArea.agt.indexOf("mac") != -1);
! 1338: HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
! 1339: HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
! 1340: HTMLArea.is_gecko = (navigator.product == "Gecko");
! 1341:
! 1342: // variable used to pass the object to the popup editor window.
! 1343: HTMLArea._object = null;
! 1344:
! 1345: // FIXME!!! this should return false for IE < 5.5
! 1346: HTMLArea.checkSupportedBrowser = function() {
! 1347: if (HTMLArea.is_gecko) {
! 1348: if (navigator.productSub < 20021201) {
! 1349: alert("You need at least Mozilla-1.3 Alpha.\n" +
! 1350: "Sorry, your Gecko is not supported.");
! 1351: return false;
! 1352: }
! 1353: if (navigator.productSub < 20030210) {
! 1354: alert("Mozilla < 1.3 Beta is not supported!\n" +
! 1355: "I'll try, though, but it might not work.");
! 1356: }
! 1357: }
! 1358: return HTMLArea.is_gecko || HTMLArea.is_ie;
! 1359: };
! 1360:
! 1361: // selection & ranges
! 1362:
! 1363: // returns the current selection object
! 1364: HTMLArea.prototype._getSelection = function() {
! 1365: if (HTMLArea.is_ie) {
! 1366: return this._doc.selection;
! 1367: } else {
! 1368: return this._iframe.contentWindow.getSelection();
! 1369: }
! 1370: };
! 1371:
! 1372: // returns a range for the current selection
! 1373: HTMLArea.prototype._createRange = function(sel) {
! 1374: if (HTMLArea.is_ie) {
! 1375: return sel.createRange();
! 1376: } else {
! 1377: this.focusEditor();
! 1378: if (typeof sel != "undefined") {
! 1379: return sel.getRangeAt(0);
! 1380: } else {
! 1381: return this._doc.createRange();
! 1382: }
! 1383: }
! 1384: };
! 1385:
! 1386: // event handling
! 1387:
! 1388: HTMLArea._addEvent = function(el, evname, func) {
! 1389: if (HTMLArea.is_ie) {
! 1390: el.attachEvent("on" + evname, func);
! 1391: } else {
! 1392: el.addEventListener(evname, func, true);
! 1393: }
! 1394: };
! 1395:
! 1396: HTMLArea._addEvents = function(el, evs, func) {
! 1397: for (var i in evs) {
! 1398: HTMLArea._addEvent(el, evs[i], func);
! 1399: }
! 1400: };
! 1401:
! 1402: HTMLArea._removeEvent = function(el, evname, func) {
! 1403: if (HTMLArea.is_ie) {
! 1404: el.detachEvent("on" + evname, func);
! 1405: } else {
! 1406: el.removeEventListener(evname, func, true);
! 1407: }
! 1408: };
! 1409:
! 1410: HTMLArea._removeEvents = function(el, evs, func) {
! 1411: for (var i in evs) {
! 1412: HTMLArea._removeEvent(el, evs[i], func);
! 1413: }
! 1414: };
! 1415:
! 1416: HTMLArea._stopEvent = function(ev) {
! 1417: if (HTMLArea.is_ie) {
! 1418: ev.cancelBubble = true;
! 1419: ev.returnValue = false;
! 1420: } else {
! 1421: ev.preventDefault();
! 1422: ev.stopPropagation();
! 1423: }
! 1424: };
! 1425:
! 1426: HTMLArea._removeClass = function(el, className) {
! 1427: if (!(el && el.className)) {
! 1428: return;
! 1429: }
! 1430: var cls = el.className.split(" ");
! 1431: var ar = new Array();
! 1432: for (var i = cls.length; i > 0;) {
! 1433: if (cls[--i] != className) {
! 1434: ar[ar.length] = cls[i];
! 1435: }
! 1436: }
! 1437: el.className = ar.join(" ");
! 1438: };
! 1439:
! 1440: HTMLArea._addClass = function(el, className) {
! 1441: // remove the class first, if already there
! 1442: HTMLArea._removeClass(el, className);
! 1443: el.className += " " + className;
! 1444: };
! 1445:
! 1446: HTMLArea._hasClass = function(el, className) {
! 1447: if (!(el && el.className)) {
! 1448: return false;
! 1449: }
! 1450: var cls = el.className.split(" ");
! 1451: for (var i = cls.length; i > 0;) {
! 1452: if (cls[--i] == className) {
! 1453: return true;
! 1454: }
! 1455: }
! 1456: return false;
! 1457: };
! 1458:
! 1459: HTMLArea.isBlockElement = function(el) {
! 1460: var blockTags = " body form textarea fieldset ul ol dl li div " +
! 1461: "p h1 h2 h3 h4 h5 h6 quote pre table thead " +
! 1462: "tbody tfoot tr td iframe address ";
! 1463: return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
! 1464: };
! 1465:
! 1466: HTMLArea.needsClosingTag = function(el) {
! 1467: var closingTags = " script style div span tr td tbody table em strong font a ";
! 1468: return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
! 1469: };
! 1470:
! 1471: // performs HTML encoding of some given string
! 1472: HTMLArea.htmlEncode = function(str) {
! 1473: // we don't need regexp for that, but.. so be it for now.
! 1474: str = str.replace(/&/ig, "&");
! 1475: str = str.replace(/</ig, "<");
! 1476: str = str.replace(/>/ig, ">");
! 1477: str = str.replace(/\x22/ig, """);
! 1478: // \x22 means '"' -- we use hex reprezentation so that we don't disturb
! 1479: // JS compressors (well, at least mine fails.. ;)
! 1480: return str;
! 1481: };
! 1482:
! 1483: // Retrieves the HTML code from the given node. This is a replacement for
! 1484: // getting innerHTML, using standard DOM calls.
! 1485: HTMLArea.getHTML = function(root, outputRoot) {
! 1486: var html = "";
! 1487: switch (root.nodeType) {
! 1488: case 1: // Node.ELEMENT_NODE
! 1489: case 11: // Node.DOCUMENT_FRAGMENT_NODE
! 1490: var closed;
! 1491: var i;
! 1492: if (outputRoot) {
! 1493: closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root)));
! 1494: html = "<" + root.tagName.toLowerCase();
! 1495: var attrs = root.attributes;
! 1496: for (i = 0; i < attrs.length; ++i) {
! 1497: var a = attrs.item(i);
! 1498: if (!a.specified) {
! 1499: continue;
! 1500: }
! 1501: var name = a.nodeName.toLowerCase();
! 1502: if (/_moz/.test(name)) {
! 1503: // Mozilla reports some special tags
! 1504: // here; we don't need them.
! 1505: continue;
! 1506: }
! 1507: var value;
! 1508: if (name != "style") {
! 1509: // IE5.5 reports 25 when cellSpacing is
! 1510: // 1; other values might be doomed too.
! 1511: // For this reason we extract the
! 1512: // values directly from the root node.
! 1513: // I'm starting to HATE JavaScript
! 1514: // development. Browser differences
! 1515: // suck.
! 1516: if (typeof root[a.nodeName] != "undefined") {
! 1517: value = root[a.nodeName];
! 1518: } else {
! 1519: value = a.nodeValue;
! 1520: }
! 1521: } else { // IE fails to put style in attributes list
! 1522: // FIXME: cssText reported by IE is UPPERCASE
! 1523: value = root.style.cssText;
! 1524: }
! 1525: if (/_moz/.test(value)) {
! 1526: // Mozilla reports some special tags
! 1527: // here; we don't need them.
! 1528: continue;
! 1529: }
! 1530: html += " " + name + '="' + value + '"';
! 1531: }
! 1532: html += closed ? " />" : ">";
! 1533: }
! 1534: for (i = root.firstChild; i; i = i.nextSibling) {
! 1535: html += HTMLArea.getHTML(i, true);
! 1536: }
! 1537: if (outputRoot && !closed) {
! 1538: html += "</" + root.tagName.toLowerCase() + ">";
! 1539: }
! 1540: break;
! 1541: case 3: // Node.TEXT_NODE
! 1542: html = HTMLArea.htmlEncode(root.data);
! 1543: break;
! 1544: case 8: // Node.COMMENT_NODE
! 1545: html = "<!--" + root.data + "-->";
! 1546: break; // skip comments, for now.
! 1547: }
! 1548: return html;
! 1549: };
! 1550:
! 1551: // creates a rgb-style color from a number
! 1552: HTMLArea._makeColor = function(v) {
! 1553: if (typeof v != "number") {
! 1554: // already in rgb (hopefully); IE doesn't get here.
! 1555: return v;
! 1556: }
! 1557: // IE sends number; convert to rgb.
! 1558: var r = v & 0xFF;
! 1559: var g = (v >> 8) & 0xFF;
! 1560: var b = (v >> 16) & 0xFF;
! 1561: return "rgb(" + r + "," + g + "," + b + ")";
! 1562: };
! 1563:
! 1564: // returns hexadecimal color representation from a number or a rgb-style color.
! 1565: HTMLArea._colorToRgb = function(v) {
! 1566: // returns the hex representation of one byte (2 digits)
! 1567: function hex(d) {
! 1568: return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
! 1569: };
! 1570:
! 1571: if (typeof v == "number") {
! 1572: // we're talking to IE here
! 1573: var r = v & 0xFF;
! 1574: var g = (v >> 8) & 0xFF;
! 1575: var b = (v >> 16) & 0xFF;
! 1576: return "#" + hex(r) + hex(g) + hex(b);
! 1577: }
! 1578:
! 1579: if (v.substr(0, 3) == "rgb") {
! 1580: // in rgb(...) form -- Mozilla
! 1581: var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
! 1582: if (v.match(re)) {
! 1583: var r = parseInt(RegExp.$1);
! 1584: var g = parseInt(RegExp.$2);
! 1585: var b = parseInt(RegExp.$3);
! 1586: return "#" + hex(r) + hex(g) + hex(b);
! 1587: }
! 1588: // doesn't match RE?! maybe uses percentages or float numbers
! 1589: // -- FIXME: not yet implemented.
! 1590: return null;
! 1591: }
! 1592:
! 1593: if (v[0] == "#") {
! 1594: // already hex rgb (hopefully :D )
! 1595: return v;
! 1596: }
! 1597:
! 1598: // if everything else fails ;)
! 1599: return null;
! 1600: };
! 1601:
! 1602: // modal dialogs for Mozilla (for IE we're using the showModalDialog() call).
! 1603:
! 1604: // receives an URL to the popup dialog and a function that receives one value;
! 1605: // this function will get called after the dialog is closed, with the return
! 1606: // value of the dialog.
! 1607: HTMLArea.prototype._popupDialog = function(url, action, init) {
! 1608: Dialog(this.popupURL(url), action, init);
! 1609: };
! 1610:
! 1611: // paths
! 1612:
! 1613: HTMLArea.prototype.imgURL = function(file, plugin) {
! 1614: if (typeof plugin == "undefined") {
! 1615: return this.config.editorURL + file;
! 1616: } else {
! 1617: return this.config.editorURL + "plugins/" + plugin + "/img/" + file;
! 1618: }
! 1619: };
! 1620:
! 1621: HTMLArea.prototype.popupURL = function(file) {
! 1622: return this.config.editorURL + this.config.popupURL + file;
! 1623: };
! 1624:
! 1625: // EOF
! 1626: // Local variables: //
! 1627: // c-basic-offset:8 //
! 1628: // indent-tabs-mode:t //
! 1629: // End: //
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>