[ Index ] |
|
Code source de Zen Cart E-Commerce Shopping Cart 1.3.7.1 |
1 // htmlArea v3.0 - Copyright (c) 2002-2004 interactivetools.com, inc. 2 // This copyright notice MUST stay intact for use (see license.txt). 3 // 4 // Portions (c) dynarch.com, 2003-2004 5 // 6 // A free WYSIWYG editor replacement for <textarea> fields. 7 // For full source code and docs, visit http://www.interactivetools.com/ 8 // 9 // Version 3.0 developed by Mihai Bazon. 10 // http://dynarch.com/mishoo 11 // 12 // Zen Cart customization version info: 13 // $Id: htmlarea.js 4367 2006-09-03 19:30:17Z drbyte $ 14 15 if (typeof _editor_url == "string") { 16 // Leave exactly one backslash at the end of _editor_url 17 _editor_url = _editor_url.replace(/\x2f*$/, '/'); 18 } else { 19 alert("WARNING: _editor_url is not set! You should set this variable to the editor files path; it should preferably be an absolute path, like in '/htmlarea', but it can be relative if you prefer. Further we will try to load the editor files correctly but we'll probably fail."); 20 _editor_url = ''; 21 } 22 23 // make sure we have a language 24 if (typeof _editor_lang == "string") { 25 _editor_lang = _editor_lang.toLowerCase(); 26 } else { 27 _editor_lang = "en"; 28 } 29 30 // Creates a new HTMLArea object. Tries to replace the textarea with the given 31 // ID with it. 32 function HTMLArea(textarea, config) { 33 if (HTMLArea.checkSupportedBrowser()) { 34 if (typeof config == "undefined") { 35 this.config = new HTMLArea.Config(); 36 } else { 37 this.config = config; 38 } 39 this._htmlArea = null; 40 this._textArea = textarea; 41 this._editMode = "wysiwyg"; 42 this.plugins = {}; 43 this._timerToolbar = null; 44 this._timerUndo = null; 45 this._undoQueue = new Array(this.config.undoSteps); 46 this._undoPos = -1; 47 this._customUndo = false; 48 this._mdoc = document; // cache the document, we need it in plugins 49 this.doctype = ''; 50 } 51 }; 52 53 // load some scripts 54 (function() { 55 var scripts = HTMLArea._scripts = [ _editor_url + "htmlarea.js", 56 _editor_url + "dialog.js", 57 _editor_url + "popupwin.js", 58 _editor_url + "lang/" + _editor_lang + ".js" ]; 59 var head = document.getElementsByTagName("head")[0]; 60 // start from 1, htmlarea.js is already loaded 61 for (var i = 1; i < scripts.length; ++i) { 62 var script = document.createElement("script"); 63 script.src = scripts[i]; 64 head.appendChild(script); 65 } 66 })(); 67 68 // cache some regexps 69 HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig; 70 HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i; 71 HTMLArea.RE_head = /<head>((.|\n)*?)<\/head>/i; 72 HTMLArea.RE_body = /<body>((.|\n)*?)<\/body>/i; 73 74 HTMLArea.Config = function () { 75 this.version = "3.0"; 76 77 this.width = "auto"; 78 this.height = "auto"; 79 80 // enable creation of a status bar? 81 this.statusBar = true; 82 83 // maximum size of the undo queue 84 this.undoSteps = 20; 85 86 // the time interval at which undo samples are taken 87 this.undoTimeout = 500; // 1/2 sec. 88 89 // the next parameter specifies whether the toolbar should be included 90 // in the size or not. 91 this.sizeIncludesToolbar = true; 92 93 // if true then HTMLArea will retrieve the full HTML, starting with the 94 // <HTML> tag. 95 this.fullPage = false; 96 97 // style included in the iframe document 98 this.pageStyle = ""; 99 100 // set to true if you want Word code to be cleaned upon Paste 101 this.killWordOnPaste = false; 102 103 // BaseURL included in the iframe document 104 this.baseURL = document.baseURI || document.URL; 105 if (this.baseURL && this.baseURL.match(/(.*)\/([^\/]+)/)) 106 this.baseURL = RegExp.$1 + "/"; 107 108 // URL-s 109 this.imgURL = "images/"; 110 this.popupURL = "popups/"; 111 112 /** CUSTOMIZING THE TOOLBAR 113 * ------------------------- 114 * 115 * It is recommended that you customize the toolbar contents in an 116 * external file (i.e. the one calling HTMLArea) and leave this one 117 * unchanged. That's because when we (InteractiveTools.com) release a 118 * new official version, it's less likely that you will have problems 119 * upgrading HTMLArea. 120 */ 121 this.toolbar = [ 122 [ "fontname", "space", 123 "fontsize", "space", 124 "formatblock", "space", 125 "bold", "italic", "underline", "strikethrough", "separator", 126 "subscript", "superscript", "separator", 127 "copy", "cut", "paste" ], 128 129 [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator", 130 "lefttoright", "righttoleft", "separator", 131 "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator", 132 "forecolor", "hilitecolor", "separator", 133 "inserthorizontalrule", "createlink", "insertimage", "inserttable", "htmlmode", "separator", 134 "popupeditor", "separator", "showhelp", "about" ] 135 ]; 136 137 this.fontname = { 138 "Arial": 'arial,helvetica,sans-serif', 139 "Courier New": 'courier new,courier,monospace', 140 "Georgia": 'georgia,times new roman,times,serif', 141 "Tahoma": 'tahoma,arial,helvetica,sans-serif', 142 "Times New Roman": 'times new roman,times,serif', 143 "Verdana": 'verdana,arial,helvetica,sans-serif', 144 "impact": 'impact', 145 "WingDings": 'wingdings' 146 }; 147 148 this.fontsize = { 149 "1 (8 pt)": "1", 150 "2 (10 pt)": "2", 151 "3 (12 pt)": "3", 152 "4 (14 pt)": "4", 153 "5 (18 pt)": "5", 154 "6 (24 pt)": "6", 155 "7 (36 pt)": "7" 156 }; 157 158 this.formatblock = { 159 "Heading 1": "h1", 160 "Heading 2": "h2", 161 "Heading 3": "h3", 162 "Heading 4": "h4", 163 "Heading 5": "h5", 164 "Heading 6": "h6", 165 "Normal": "p", 166 "Address": "address", 167 "Formatted": "pre" 168 }; 169 170 this.customSelects = {}; 171 172 function cut_copy_paste(e, cmd, obj) { 173 e.execCommand(cmd); 174 }; 175 176 // ADDING CUSTOM BUTTONS: please read below! 177 // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]" 178 // - ID: unique ID for the button. If the button calls document.execCommand 179 // it's wise to give it the same name as the called command. 180 // - ACTION: function that gets called when the button is clicked. 181 // it has the following prototype: 182 // function(editor, buttonName) 183 // - editor is the HTMLArea object that triggered the call 184 // - buttonName is the ID of the clicked button 185 // These 2 parameters makes it possible for you to use the same 186 // handler for more HTMLArea objects or for more different buttons. 187 // - ToolTip: default tooltip, for cases when it is not defined in the -lang- file (HTMLArea.I18N) 188 // - Icon: path to an icon image file for the button (TODO: use one image for all buttons!) 189 // - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time. 190 this.btnList = { 191 bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ], 192 italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ], 193 underline: [ "Underline", "ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ], 194 strikethrough: [ "Strikethrough", "ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ], 195 subscript: [ "Subscript", "ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ], 196 superscript: [ "Superscript", "ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ], 197 justifyleft: [ "Justify Left", "ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ], 198 justifycenter: [ "Justify Center", "ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ], 199 justifyright: [ "Justify Right", "ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ], 200 justifyfull: [ "Justify Full", "ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ], 201 insertorderedlist: [ "Ordered List", "ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ], 202 insertunorderedlist: [ "Bulleted List", "ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ], 203 outdent: [ "Decrease Indent", "ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ], 204 indent: [ "Increase Indent", "ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ], 205 forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ], 206 hilitecolor: [ "Background Color", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ], 207 inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ], 208 createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ], 209 insertimage: [ "Insert/Modify Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ], 210 inserttable: [ "Insert Table", "insert_table.gif", false, function(e) {e.execCommand("inserttable");} ], 211 htmlmode: [ "Toggle HTML Source", "ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ], 212 popupeditor: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ], 213 about: [ "About this editor", "ed_about.gif", true, function(e) {e.execCommand("about");} ], 214 showhelp: [ "Help using editor", "ed_help.gif", true, function(e) {e.execCommand("showhelp");} ], 215 undo: [ "Undoes your last action", "ed_undo.gif", false, function(e) {e.execCommand("undo");} ], 216 redo: [ "Redoes your last action", "ed_redo.gif", false, function(e) {e.execCommand("redo");} ], 217 cut: [ "Cut selection", "ed_cut.gif", false, cut_copy_paste ], 218 copy: [ "Copy selection", "ed_copy.gif", false, cut_copy_paste ], 219 paste: [ "Paste from clipboard", "ed_paste.gif", false, cut_copy_paste ], 220 lefttoright: [ "Direction left to right", "ed_left_to_right.gif", false, function(e) {e.execCommand("lefttoright");} ], 221 righttoleft: [ "Direction right to left", "ed_right_to_left.gif", false, function(e) {e.execCommand("righttoleft");} ] 222 }; 223 /* ADDING CUSTOM BUTTONS 224 * --------------------- 225 * 226 * It is recommended that you add the custom buttons in an external 227 * file and leave this one unchanged. That's because when we 228 * (InteractiveTools.com) release a new official version, it's less 229 * likely that you will have problems upgrading HTMLArea. 230 * 231 * Example on how to add a custom button when you construct the HTMLArea: 232 * 233 * var editor = new HTMLArea("your_text_area_id"); 234 * var cfg = editor.config; // this is the default configuration 235 * cfg.btnList["my-hilite"] = 236 * [ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action 237 * "Highlight selection", // tooltip 238 * "my_hilite.gif", // image 239 * false // disabled in text mode 240 * ]; 241 * cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar 242 * 243 * An alternate (also more convenient and recommended) way to 244 * accomplish this is to use the registerButton function below. 245 */ 246 // initialize tooltips from the I18N module and generate correct image path 247 for (var i in this.btnList) { 248 var btn = this.btnList[i]; 249 btn[1] = _editor_url + this.imgURL + btn[1]; 250 if (typeof HTMLArea.I18N.tooltips[i] != "undefined") { 251 btn[0] = HTMLArea.I18N.tooltips[i]; 252 } 253 } 254 }; 255 256 /** Helper function: register a new button with the configuration. It can be 257 * called with all 5 arguments, or with only one (first one). When called with 258 * only one argument it must be an object with the following properties: id, 259 * tooltip, image, textMode, action. Examples: 260 * 261 * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...}); 262 * 2. config.registerButton({ 263 * id : "my-hilite", // the ID of your button 264 * tooltip : "Hilite text", // the tooltip 265 * image : "my-hilite.gif", // image to be displayed in the toolbar 266 * textMode : false, // disabled in text mode 267 * action : function(editor) { // called when the button is clicked 268 * editor.surroundHTML('<span class="hilite">', '</span>'); 269 * }, 270 * context : "p" // will be disabled if outside a <p> element 271 * }); 272 */ 273 HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) { 274 var the_id; 275 if (typeof id == "string") { 276 the_id = id; 277 } else if (typeof id == "object") { 278 the_id = id.id; 279 } else { 280 alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments"); 281 return false; 282 } 283 // check for existing id 284 if (typeof this.customSelects[the_id] != "undefined") { 285 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists."); 286 } 287 if (typeof this.btnList[the_id] != "undefined") { 288 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists."); 289 } 290 switch (typeof id) { 291 case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break; 292 case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break; 293 } 294 }; 295 296 /** The following helper function registers a dropdown box with the editor 297 * configuration. You still have to add it to the toolbar, same as with the 298 * buttons. Call it like this: 299 * 300 * FIXME: add example 301 */ 302 HTMLArea.Config.prototype.registerDropdown = function(object) { 303 // check for existing id 304 if (typeof this.customSelects[object.id] != "undefined") { 305 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists."); 306 } 307 if (typeof this.btnList[object.id] != "undefined") { 308 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists."); 309 } 310 this.customSelects[object.id] = object; 311 }; 312 313 /** Call this function to remove some buttons/drop-down boxes from the toolbar. 314 * Pass as the only parameter a string containing button/drop-down names 315 * delimited by spaces. Note that the string should also begin with a space 316 * and end with a space. Example: 317 * 318 * config.hideSomeButtons(" fontname fontsize textindicator "); 319 * 320 * It's useful because it's easier to remove stuff from the defaul toolbar than 321 * create a brand new toolbar ;-) 322 */ 323 HTMLArea.Config.prototype.hideSomeButtons = function(remove) { 324 var toolbar = this.toolbar; 325 for (var i in toolbar) { 326 var line = toolbar[i]; 327 for (var j = line.length; --j >= 0; ) { 328 if (remove.indexOf(" " + line[j] + " ") >= 0) { 329 var len = 1; 330 if (/separator|space/.test(line[j + 1])) { 331 len = 2; 332 } 333 line.splice(j, len); 334 } 335 } 336 } 337 }; 338 339 /** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */ 340 HTMLArea.replaceAll = function(config) { 341 var tas = document.getElementsByTagName("textarea"); 342 for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate()); 343 }; 344 345 /** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */ 346 HTMLArea.replace = function(id, config) { 347 var ta = HTMLArea.getElementById("textarea", id); 348 return ta ? (new HTMLArea(ta, config)).generate() : null; 349 }; 350 351 // Creates the toolbar and appends it to the _htmlarea 352 HTMLArea.prototype._createToolbar = function () { 353 var editor = this; // to access this in nested functions 354 355 var toolbar = document.createElement("div"); 356 this._toolbar = toolbar; 357 toolbar.className = "toolbar"; 358 toolbar.unselectable = "1"; 359 var tb_row = null; 360 var tb_objects = new Object(); 361 this._toolbarObjects = tb_objects; 362 363 // creates a new line in the toolbar 364 function newLine() { 365 var table = document.createElement("table"); 366 table.border = "0px"; 367 table.cellSpacing = "0px"; 368 table.cellPadding = "0px"; 369 toolbar.appendChild(table); 370 // TBODY is required for IE, otherwise you don't see anything 371 // in the TABLE. 372 var tb_body = document.createElement("tbody"); 373 table.appendChild(tb_body); 374 tb_row = document.createElement("tr"); 375 tb_body.appendChild(tb_row); 376 }; // END of function: newLine 377 // init first line 378 newLine(); 379 380 // updates the state of a toolbar element. This function is member of 381 // a toolbar element object (unnamed objects created by createButton or 382 // createSelect functions below). 383 function setButtonStatus(id, newval) { 384 var oldval = this[id]; 385 var el = this.element; 386 if (oldval != newval) { 387 switch (id) { 388 case "enabled": 389 if (newval) { 390 HTMLArea._removeClass(el, "buttonDisabled"); 391 el.disabled = false; 392 } else { 393 HTMLArea._addClass(el, "buttonDisabled"); 394 el.disabled = true; 395 } 396 break; 397 case "active": 398 if (newval) { 399 HTMLArea._addClass(el, "buttonPressed"); 400 } else { 401 HTMLArea._removeClass(el, "buttonPressed"); 402 } 403 break; 404 } 405 this[id] = newval; 406 } 407 }; // END of function: setButtonStatus 408 409 // this function will handle creation of combo boxes. Receives as 410 // parameter the name of a button as defined in the toolBar config. 411 // This function is called from createButton, above, if the given "txt" 412 // doesn't match a button. 413 function createSelect(txt) { 414 var options = null; 415 var el = null; 416 var cmd = null; 417 var customSelects = editor.config.customSelects; 418 var context = null; 419 switch (txt) { 420 case "fontsize": 421 case "fontname": 422 case "formatblock": 423 // the following line retrieves the correct 424 // configuration option because the variable name 425 // inside the Config object is named the same as the 426 // button/select in the toolbar. For instance, if txt 427 // == "formatblock" we retrieve config.formatblock (or 428 // a different way to write it in JS is 429 // config["formatblock"]. 430 options = editor.config[txt]; 431 cmd = txt; 432 break; 433 default: 434 // try to fetch it from the list of registered selects 435 cmd = txt; 436 var dropdown = customSelects[cmd]; 437 if (typeof dropdown != "undefined") { 438 options = dropdown.options; 439 context = dropdown.context; 440 } else { 441 alert("ERROR [createSelect]:\nCan't find the requested dropdown definition"); 442 } 443 break; 444 } 445 if (options) { 446 el = document.createElement("select"); 447 var obj = { 448 name : txt, // field name 449 element : el, // the UI element (SELECT) 450 enabled : true, // is it enabled? 451 text : false, // enabled in text mode? 452 cmd : cmd, // command ID 453 state : setButtonStatus, // for changing state 454 context : context 455 }; 456 tb_objects[txt] = obj; 457 for (var i in options) { 458 var op = document.createElement("option"); 459 op.appendChild(document.createTextNode(i)); 460 op.value = options[i]; 461 el.appendChild(op); 462 } 463 HTMLArea._addEvent(el, "change", function () { 464 editor._comboSelected(el, txt); 465 }); 466 } 467 return el; 468 }; // END of function: createSelect 469 470 // appends a new button to toolbar 471 function createButton(txt) { 472 // the element that will be created 473 var el = null; 474 var btn = null; 475 switch (txt) { 476 case "separator": 477 el = document.createElement("div"); 478 el.className = "separator"; 479 break; 480 case "space": 481 el = document.createElement("div"); 482 el.className = "space"; 483 break; 484 case "linebreak": 485 newLine(); 486 return false; 487 case "textindicator": 488 el = document.createElement("div"); 489 el.appendChild(document.createTextNode("A")); 490 el.className = "indicator"; 491 el.title = HTMLArea.I18N.tooltips.textindicator; 492 var obj = { 493 name : txt, // the button name (i.e. 'bold') 494 element : el, // the UI element (DIV) 495 enabled : true, // is it enabled? 496 active : false, // is it pressed? 497 text : false, // enabled in text mode? 498 cmd : "textindicator", // the command ID 499 state : setButtonStatus // for changing state 500 }; 501 tb_objects[txt] = obj; 502 break; 503 default: 504 btn = editor.config.btnList[txt]; 505 } 506 if (!el && btn) { 507 el = document.createElement("div"); 508 el.title = btn[0]; 509 el.className = "button"; 510 // let's just pretend we have a button object, and 511 // assign all the needed information to it. 512 var obj = { 513 name : txt, // the button name (i.e. 'bold') 514 element : el, // the UI element (DIV) 515 enabled : true, // is it enabled? 516 active : false, // is it pressed? 517 text : btn[2], // enabled in text mode? 518 cmd : btn[3], // the command ID 519 state : setButtonStatus, // for changing state 520 context : btn[4] || null // enabled in a certain context? 521 }; 522 tb_objects[txt] = obj; 523 // handlers to emulate nice flat toolbar buttons 524 HTMLArea._addEvent(el, "mouseover", function () { 525 if (obj.enabled) { 526 HTMLArea._addClass(el, "buttonHover"); 527 } 528 }); 529 HTMLArea._addEvent(el, "mouseout", function () { 530 if (obj.enabled) with (HTMLArea) { 531 _removeClass(el, "buttonHover"); 532 _removeClass(el, "buttonActive"); 533 (obj.active) && _addClass(el, "buttonPressed"); 534 } 535 }); 536 HTMLArea._addEvent(el, "mousedown", function (ev) { 537 if (obj.enabled) with (HTMLArea) { 538 _addClass(el, "buttonActive"); 539 _removeClass(el, "buttonPressed"); 540 _stopEvent(is_ie ? window.event : ev); 541 } 542 }); 543 // when clicked, do the following: 544 HTMLArea._addEvent(el, "click", function (ev) { 545 if (obj.enabled) with (HTMLArea) { 546 _removeClass(el, "buttonActive"); 547 _removeClass(el, "buttonHover"); 548 obj.cmd(editor, obj.name, obj); 549 _stopEvent(is_ie ? window.event : ev); 550 } 551 }); 552 var img = document.createElement("img"); 553 img.src = btn[1]; 554 img.style.width = "18px"; 555 img.style.height = "18px"; 556 el.appendChild(img); 557 } else if (!el) { 558 el = createSelect(txt); 559 } 560 if (el) { 561 var tb_cell = document.createElement("td"); 562 tb_row.appendChild(tb_cell); 563 tb_cell.appendChild(el); 564 } else { 565 alert("FIXME: Unknown toolbar item: " + txt); 566 } 567 return el; 568 }; 569 570 var first = true; 571 for (var i in this.config.toolbar) { 572 if (!first) { 573 createButton("linebreak"); 574 } else { 575 first = false; 576 } 577 var group = this.config.toolbar[i]; 578 for (var j in group) { 579 var code = group[j]; 580 if (/^([IT])\[(.*?)\]/.test(code)) { 581 // special case, create text label 582 var l7ed = RegExp.$1 == "I"; // localized? 583 var label = RegExp.$2; 584 if (l7ed) { 585 label = HTMLArea.I18N.custom[label]; 586 } 587 var tb_cell = document.createElement("td"); 588 tb_row.appendChild(tb_cell); 589 tb_cell.className = "label"; 590 tb_cell.innerHTML = label; 591 } else { 592 createButton(code); 593 } 594 } 595 } 596 597 this._htmlArea.appendChild(toolbar); 598 }; 599 600 HTMLArea.prototype._createStatusBar = function() { 601 var statusbar = document.createElement("div"); 602 statusbar.className = "statusBar"; 603 this._htmlArea.appendChild(statusbar); 604 this._statusBar = statusbar; 605 // statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); 606 // creates a holder for the path view 607 div = document.createElement("span"); 608 div.className = "statusBarTree"; 609 div.innerHTML = HTMLArea.I18N.msg["Path"] + ": "; 610 this._statusBarTree = div; 611 this._statusBar.appendChild(div); 612 if (!this.config.statusBar) { 613 // disable it... 614 statusbar.style.display = "none"; 615 } 616 }; 617 618 // Creates the HTMLArea object and replaces the textarea with it. 619 HTMLArea.prototype.generate = function () { 620 var editor = this; // we'll need "this" in some nested functions 621 // get the textarea 622 var textarea = this._textArea; 623 if (typeof textarea == "string") { 624 // it's not element but ID 625 this._textArea = textarea = HTMLArea.getElementById("textarea", textarea); 626 } 627 this._ta_size = { 628 w: textarea.offsetWidth, 629 h: textarea.offsetHeight 630 }; 631 textarea.style.display = "none"; 632 633 // create the editor framework 634 var htmlarea = document.createElement("div"); 635 htmlarea.className = "htmlarea"; 636 this._htmlArea = htmlarea; 637 638 // insert the editor before the textarea. 639 textarea.parentNode.insertBefore(htmlarea, textarea); 640 641 if (textarea.form) { 642 // we have a form, on submit get the HTMLArea content and 643 // update original textarea. 644 var f = textarea.form; 645 if (typeof f.onsubmit == "function") { 646 var funcref = f.onsubmit; 647 if (typeof f.__msh_prevOnSubmit == "undefined") { 648 f.__msh_prevOnSubmit = []; 649 } 650 f.__msh_prevOnSubmit.push(funcref); 651 } 652 f.onsubmit = function() { 653 editor._textArea.value = editor.getHTML(); 654 var a = this.__msh_prevOnSubmit; 655 // call previous submit methods if they were there. 656 var previous_return = true; //fix onsubmit bug where interaction with other javascript fails if "submit=fail" 657 if (typeof a != "undefined") { 658 for (var i in a) { 659 previous_return = previous_return && a[i](); 660 } 661 } 662 return previous_return; 663 }; 664 } 665 666 // add a handler for the "back/forward" case -- on body.unload we save 667 // the HTML content into the original textarea. 668 window.onunload = function() { 669 editor._textArea.value = editor.getHTML(); 670 }; 671 672 // creates & appends the toolbar 673 this._createToolbar(); 674 675 // create the IFRAME 676 var iframe = document.createElement("iframe"); 677 htmlarea.appendChild(iframe); 678 679 this._iframe = iframe; 680 681 // creates & appends the status bar, if the case 682 this._createStatusBar(); 683 684 // remove the default border as it keeps us from computing correctly 685 // the sizes. (somebody tell me why doesn't this work in IE) 686 687 if (!HTMLArea.is_ie) { 688 iframe.style.borderWidth = "1px"; 689 // iframe.frameBorder = "1"; 690 // iframe.marginHeight = "0"; 691 // iframe.marginWidth = "0"; 692 } 693 694 // size the IFRAME according to user's prefs or initial textarea 695 var height = (this.config.height == "auto" ? (this._ta_size.h + "px") : this.config.height); 696 height = parseInt(height); 697 var width = (this.config.width == "auto" ? (this._ta_size.w + "px") : this.config.width); 698 width = parseInt(width); 699 700 if (!HTMLArea.is_ie) { 701 height -= 2; 702 width -= 2; 703 } 704 705 iframe.style.width = width + "px"; 706 if (this.config.sizeIncludesToolbar) { 707 // substract toolbar height 708 height -= this._toolbar.offsetHeight; 709 height -= this._statusBar.offsetHeight; 710 } 711 if (height < 0) { 712 height = 0; 713 } 714 iframe.style.height = height + "px"; 715 716 // the editor including the toolbar now have the same size as the 717 // original textarea.. which means that we need to reduce that a bit. 718 textarea.style.width = iframe.style.width; 719 textarea.style.height = iframe.style.height; 720 721 // IMPORTANT: we have to allow Mozilla a short time to recognize the 722 // new frame. Otherwise we get a stupid exception. 723 function initIframe() { 724 var doc = editor._iframe.contentWindow.document; 725 if (!doc) { 726 // Try again.. 727 // FIXME: don't know what else to do here. Normally 728 // we'll never reach this point. 729 if (HTMLArea.is_gecko) { 730 setTimeout(initIframe, 100); 731 return false; 732 } else { 733 alert("ERROR: IFRAME can't be initialized."); 734 } 735 } 736 if (HTMLArea.is_gecko) { 737 // enable editable mode for Mozilla 738 doc.designMode = "on"; 739 } 740 editor._doc = doc; 741 if (!editor.config.fullPage) { 742 doc.open(); 743 var html = "<html>\n"; 744 html += "<head>\n"; 745 if (editor.config.baseURL) 746 html += '<base href="' + editor.config.baseURL + '" />'; 747 html += "<style>" + editor.config.pageStyle + 748 " html,body { border: 0px; }</style>\n"; 749 //used above lines to fix bug based on info on htmlarea support forum 750 //html += "<style> html,body { border: 0px; } " + 751 // editor.config.pageStyle + "</style>\n"; 752 html += "</head>\n"; 753 html += "<body>\n"; 754 html += editor._textArea.value; 755 html += "</body>\n"; 756 html += "</html>"; 757 doc.write(html); 758 doc.close(); 759 } else { 760 var html = editor._textArea.value; 761 if (html.match(HTMLArea.RE_doctype)) { 762 editor.setDoctype(RegExp.$1); 763 html = html.replace(HTMLArea.RE_doctype, ""); 764 } 765 doc.open(); 766 doc.write(html); 767 doc.close(); 768 } 769 770 if (HTMLArea.is_ie) { 771 // enable editable mode for IE. For some reason this 772 // doesn't work if done in the same place as for Gecko 773 // (above). 774 doc.body.contentEditable = true; 775 } 776 777 // editor.focusEditor(); 778 // intercept some events; for updating the toolbar & keyboard handlers 779 HTMLArea._addEvents 780 (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"], 781 function (event) { 782 return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event); 783 }); 784 785 // check if any plugins have registered refresh handlers 786 for (var i in editor.plugins) { 787 var plugin = editor.plugins[i].instance; 788 if (typeof plugin.onGenerate == "function") 789 plugin.onGenerate(); 790 } 791 792 setTimeout(function() { 793 editor.updateToolbar(); 794 }, 250); 795 796 if (typeof editor.onGenerate == "function") 797 editor.onGenerate(); 798 }; 799 setTimeout(initIframe, 100); 800 }; 801 802 // Switches editor mode; parameter can be "textmode" or "wysiwyg". If no 803 // parameter was passed this function toggles between modes. 804 HTMLArea.prototype.setMode = function(mode) { 805 if (typeof mode == "undefined") { 806 mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode"); 807 } 808 switch (mode) { 809 case "textmode": 810 this._textArea.value = this.getHTML(); 811 this._iframe.style.display = "none"; 812 this._textArea.style.display = "block"; 813 if (this.config.statusBar) { 814 this._statusBar.innerHTML = HTMLArea.I18N.msg["TEXT_MODE"]; 815 } 816 break; 817 case "wysiwyg": 818 if (HTMLArea.is_gecko) { 819 // disable design mode before changing innerHTML 820 try { 821 this._doc.designMode = "off"; 822 } catch(e) {}; 823 } 824 if (!this.config.fullPage) 825 this._doc.body.innerHTML = this.getHTML(); 826 else 827 this.setFullHTML(this.getHTML()); 828 this._iframe.style.display = "block"; 829 this._textArea.style.display = "none"; 830 if (HTMLArea.is_gecko) { 831 // we need to refresh that info for Moz-1.3a 832 try { 833 this._doc.designMode = "on"; 834 } catch(e) {}; 835 } 836 if (this.config.statusBar) { 837 this._statusBar.innerHTML = ''; 838 this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); 839 this._statusBar.appendChild(this._statusBarTree); 840 } 841 break; 842 default: 843 alert("Mode <" + mode + "> not defined!"); 844 return false; 845 } 846 this._editMode = mode; 847 this.focusEditor(); 848 }; 849 850 HTMLArea.prototype.setFullHTML = function(html) { 851 var save_multiline = RegExp.multiline; 852 RegExp.multiline = true; 853 if (html.match(HTMLArea.RE_doctype)) { 854 this.setDoctype(RegExp.$1); 855 html = html.replace(HTMLArea.RE_doctype, ""); 856 } 857 RegExp.multiline = save_multiline; 858 if (!HTMLArea.is_ie) { 859 if (html.match(HTMLArea.RE_head)) 860 this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1; 861 if (html.match(HTMLArea.RE_body)) 862 this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1; 863 } else { 864 var html_re = /<html>((.|\n)*?)<\/html>/i; 865 html = html.replace(html_re, "$1"); 866 this._doc.open(); 867 this._doc.write(html); 868 this._doc.close(); 869 this._doc.body.contentEditable = true; 870 return true; 871 } 872 }; 873 874 /*************************************************** 875 * Category: PLUGINS 876 ***************************************************/ 877 878 // this is the variant of the function above where the plugin arguments are 879 // already packed in an array. Externally, it should be only used in the 880 // full-screen editor code, in order to initialize plugins with the same 881 // parameters as in the opener window. 882 HTMLArea.prototype.registerPlugin2 = function(plugin, args) { 883 if (typeof plugin == "string") 884 plugin = eval(plugin); 885 var obj = new plugin(this, args); 886 if (obj) { 887 var clone = {}; 888 var info = plugin._pluginInfo; 889 for (var i in info) 890 clone[i] = info[i]; 891 clone.instance = obj; 892 clone.args = args; 893 this.plugins[plugin._pluginInfo.name] = clone; 894 } else 895 alert("Can't register plugin " + plugin.toString() + "."); 896 }; 897 898 // Create the specified plugin and register it with this HTMLArea 899 HTMLArea.prototype.registerPlugin = function() { 900 var plugin = arguments[0]; 901 var args = []; 902 for (var i = 1; i < arguments.length; ++i) 903 args.push(arguments[i]); 904 this.registerPlugin2(plugin, args); 905 }; 906 907 // static function that loads the required plugin and lang file, based on the 908 // language loaded already for HTMLArea. You better make sure that the plugin 909 // _has_ that language, otherwise shit might happen ;-) 910 HTMLArea.loadPlugin = function(pluginName) { 911 var dir = _editor_url + "plugins/" + pluginName; 912 var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, 913 function (str, l1, l2, l3) { 914 return l1 + "-" + l2.toLowerCase() + l3; 915 }).toLowerCase() + ".js"; 916 var plugin_file = dir + "/" + plugin; 917 var plugin_lang = dir + "/lang/" + HTMLArea.I18N.lang + ".js"; 918 HTMLArea._scripts.push(plugin_file, plugin_lang); 919 document.write("<script type='text/javascript' src='" + plugin_file + "'></script>"); 920 document.write("<script type='text/javascript' src='" + plugin_lang + "'></script>"); 921 }; 922 923 HTMLArea.loadStyle = function(style, plugin) { 924 var url = _editor_url || ''; 925 if (typeof plugin != "undefined") { 926 url += "plugins/" + plugin + "/"; 927 } 928 url += style; 929 document.write("<style type='text/css'>@import url(" + url + ");</style>"); 930 }; 931 HTMLArea.loadStyle("htmlarea.css"); 932 933 /*************************************************** 934 * Category: EDITOR UTILITIES 935 ***************************************************/ 936 937 // The following function is a slight variation of the word cleaner code posted 938 // by Weeezl (user @ InteractiveTools forums). 939 HTMLArea.prototype._wordClean = function() { 940 var D = this.getInnerHTML(); 941 if (D.indexOf('class=Mso') >= 0) { 942 943 // make one line 944 D = D.replace(/\r\n/g, ' '). 945 replace(/\n/g, ' '). 946 replace(/\r/g, ' '). 947 replace(/\ \;/g,' '); 948 949 // keep tags, strip attributes 950 D = D.replace(/ class=[^\s|>]*/gi,''). 951 //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">'). 952 replace(/ style=\"[^>]*\"/gi,''). 953 replace(/ align=[^\s|>]*/gi,''); 954 955 //clean up tags 956 D = D.replace(/<b [^>]*>/gi,'<b>'). 957 replace(/<i [^>]*>/gi,'<i>'). 958 replace(/<li [^>]*>/gi,'<li>'). 959 replace(/<ul [^>]*>/gi,'<ul>'); 960 961 // replace outdated tags 962 D = D.replace(/<b>/gi,'<strong>'). 963 replace(/<\/b>/gi,'</strong>'); 964 965 // mozilla doesn't like <em> tags 966 D = D.replace(/<em>/gi,'<i>'). 967 replace(/<\/em>/gi,'</i>'); 968 969 // kill unwanted tags 970 D = D.replace(/<\?xml:[^>]*>/g, ''). // Word xml 971 replace(/<\/?st1:[^>]*>/g,''). // Word SmartTags 972 replace(/<\/?[a-z]\:[^>]*>/g,''). // All other funny Word non-HTML stuff 973 replace(/<\/?font[^>]*>/gi,''). // Disable if you want to keep font formatting 974 replace(/<\/?span[^>]*>/gi,' '). 975 replace(/<\/?div[^>]*>/gi,' '). 976 replace(/<\/?pre[^>]*>/gi,' '). 977 replace(/<\/?h[1-6][^>]*>/gi,' '); 978 979 //remove empty tags 980 //D = D.replace(/<strong><\/strong>/gi,''). 981 //replace(/<i><\/i>/gi,''). 982 //replace(/<P[^>]*><\/P>/gi,''); 983 984 // nuke double tags 985 oldlen = D.length + 1; 986 while(oldlen > D.length) { 987 oldlen = D.length; 988 // join us now and free the tags, we'll be free hackers, we'll be free... ;-) 989 D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' '). 990 replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>'); 991 } 992 D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>'). 993 replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>'); 994 995 // nuke double spaces 996 D = D.replace(/ */gi,' '); 997 998 this.setHTML(D); 999 this.updateToolbar(); 1000 } 1001 }; 1002 1003 HTMLArea.prototype.forceRedraw = function() { 1004 this._doc.body.style.visibility = "hidden"; 1005 this._doc.body.style.visibility = "visible"; 1006 // this._doc.body.innerHTML = this.getInnerHTML(); 1007 }; 1008 1009 // focuses the iframe window. returns a reference to the editor document. 1010 HTMLArea.prototype.focusEditor = function() { 1011 switch (this._editMode) { 1012 case "wysiwyg" : this._iframe.contentWindow.focus(); break; 1013 case "textmode": this._textArea.focus(); break; 1014 default : alert("ERROR: mode " + this._editMode + " is not defined"); 1015 } 1016 return this._doc; 1017 }; 1018 1019 // takes a snapshot of the current text (for undo) 1020 HTMLArea.prototype._undoTakeSnapshot = function() { 1021 ++this._undoPos; 1022 if (this._undoPos >= this.config.undoSteps) { 1023 // remove the first element 1024 this._undoQueue.shift(); 1025 --this._undoPos; 1026 } 1027 // use the fasted method (getInnerHTML); 1028 var take = true; 1029 var txt = this.getInnerHTML(); 1030 if (this._undoPos > 0) 1031 take = (this._undoQueue[this._undoPos - 1] != txt); 1032 if (take) { 1033 this._undoQueue[this._undoPos] = txt; 1034 } else { 1035 this._undoPos--; 1036 } 1037 }; 1038 1039 HTMLArea.prototype.undo = function() { 1040 if (this._undoPos > 0) { 1041 var txt = this._undoQueue[--this._undoPos]; 1042 if (txt) this.setHTML(txt); 1043 else ++this._undoPos; 1044 } 1045 }; 1046 1047 HTMLArea.prototype.redo = function() { 1048 if (this._undoPos < this._undoQueue.length - 1) { 1049 var txt = this._undoQueue[++this._undoPos]; 1050 if (txt) this.setHTML(txt); 1051 else --this._undoPos; 1052 } 1053 }; 1054 1055 // updates enabled/disable/active state of the toolbar elements 1056 HTMLArea.prototype.updateToolbar = function(noStatus) { 1057 var doc = this._doc; 1058 var text = (this._editMode == "textmode"); 1059 var ancestors = null; 1060 if (!text) { 1061 ancestors = this.getAllAncestors(); 1062 if (this.config.statusBar && !noStatus) { 1063 this._statusBarTree.innerHTML = HTMLArea.I18N.msg["Path"] + ": "; // clear 1064 for (var i = ancestors.length; --i >= 0;) { 1065 var el = ancestors[i]; 1066 if (!el) { 1067 // hell knows why we get here; this 1068 // could be a classic example of why 1069 // it's good to check for conditions 1070 // that are impossible to happen ;-) 1071 continue; 1072 } 1073 var a = document.createElement("a"); 1074 a.href = "#"; 1075 a.el = el; 1076 a.editor = this; 1077 a.onclick = function() { 1078 this.blur(); 1079 this.editor.selectNodeContents(this.el); 1080 this.editor.updateToolbar(true); 1081 return false; 1082 }; 1083 a.oncontextmenu = function() { 1084 // TODO: add context menu here 1085 this.blur(); 1086 var info = "Inline style:\n\n"; 1087 info += this.el.style.cssText.split(/;\s*/).join(";\n"); 1088 alert(info); 1089 return false; 1090 }; 1091 var txt = el.tagName.toLowerCase(); 1092 a.title = el.style.cssText; 1093 if (el.id) { 1094 txt += "#" + el.id; 1095 } 1096 if (el.className) { 1097 txt += "." + el.className; 1098 } 1099 a.appendChild(document.createTextNode(txt)); 1100 this._statusBarTree.appendChild(a); 1101 if (i != 0) { 1102 this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb))); 1103 } 1104 } 1105 } 1106 } 1107 for (var i in this._toolbarObjects) { 1108 var btn = this._toolbarObjects[i]; 1109 var cmd = i; 1110 var inContext = true; 1111 if (btn.context && !text) { 1112 inContext = false; 1113 var context = btn.context; 1114 var attrs = []; 1115 if (/(.*)\[(.*?)\]/.test(context)) { 1116 context = RegExp.$1; 1117 attrs = RegExp.$2.split(","); 1118 } 1119 context = context.toLowerCase(); 1120 var match = (context == "*"); 1121 for (var k in ancestors) { 1122 if (!ancestors[k]) { 1123 // the impossible really happens. 1124 continue; 1125 } 1126 if (match || (ancestors[k].tagName.toLowerCase() == context)) { 1127 inContext = true; 1128 for (var ka in attrs) { 1129 if (!eval("ancestors[k]." + attrs[ka])) { 1130 inContext = false; 1131 break; 1132 } 1133 } 1134 if (inContext) { 1135 break; 1136 } 1137 } 1138 } 1139 } 1140 btn.state("enabled", (!text || btn.text) && inContext); 1141 if (typeof cmd == "function") { 1142 continue; 1143 } 1144 // look-it-up in the custom dropdown boxes 1145 var dropdown = this.config.customSelects[cmd]; 1146 if ((!text || btn.text) && (typeof dropdown != "undefined")) { 1147 dropdown.refresh(this); 1148 continue; 1149 } 1150 switch (cmd) { 1151 case "fontname": 1152 case "fontsize": 1153 case "formatblock": 1154 if (!text) try { 1155 var value = ("" + doc.queryCommandValue(cmd)).toLowerCase(); 1156 if (!value) { 1157 // FIXME: what do we do here? 1158 break; 1159 } 1160 // HACK -- retrieve the config option for this 1161 // combo box. We rely on the fact that the 1162 // variable in config has the same name as 1163 // button name in the toolbar. 1164 var options = this.config[cmd]; 1165 var k = 0; 1166 // btn.element.selectedIndex = 0; 1167 for (var j in options) { 1168 // FIXME: the following line is scary. 1169 if ((j.toLowerCase() == value) || 1170 (options[j].substr(0, value.length).toLowerCase() == value)) { 1171 btn.element.selectedIndex = k; 1172 break; 1173 } 1174 ++k; 1175 } 1176 } catch(e) {}; 1177 break; 1178 case "textindicator": 1179 if (!text) { 1180 try {with (btn.element.style) { 1181 backgroundColor = HTMLArea._makeColor( 1182 doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor")); 1183 if (/transparent/i.test(backgroundColor)) { 1184 // Mozilla 1185 backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor")); 1186 } 1187 color = HTMLArea._makeColor(doc.queryCommandValue("forecolor")); 1188 fontFamily = doc.queryCommandValue("fontname"); 1189 fontWeight = doc.queryCommandState("bold") ? "bold" : "normal"; 1190 fontStyle = doc.queryCommandState("italic") ? "italic" : "normal"; 1191 }} catch (e) { 1192 // alert(e + "\n\n" + cmd); 1193 } 1194 } 1195 break; 1196 case "htmlmode": btn.state("active", text); break; 1197 case "lefttoright": 1198 case "righttoleft": 1199 var el = this.getParentElement(); 1200 while (el && !HTMLArea.isBlockElement(el)) 1201 el = el.parentNode; 1202 if (el) 1203 btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr"))); 1204 break; 1205 default: 1206 try { 1207 btn.state("active", (!text && doc.queryCommandState(cmd))); 1208 } catch (e) {} 1209 } 1210 } 1211 // take undo snapshots 1212 if (this._customUndo && !this._timerUndo) { 1213 this._undoTakeSnapshot(); 1214 var editor = this; 1215 this._timerUndo = setTimeout(function() { 1216 editor._timerUndo = null; 1217 }, this.config.undoTimeout); 1218 } 1219 // check if any plugins have registered refresh handlers 1220 for (var i in this.plugins) { 1221 var plugin = this.plugins[i].instance; 1222 if (typeof plugin.onUpdateToolbar == "function") 1223 plugin.onUpdateToolbar(); 1224 } 1225 }; 1226 1227 /** Returns a node after which we can insert other nodes, in the current 1228 * selection. The selection is removed. It splits a text node, if needed. 1229 */ 1230 HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) { 1231 if (!HTMLArea.is_ie) { 1232 var sel = this._getSelection(); 1233 var range = this._createRange(sel); 1234 // remove the current selection 1235 sel.removeAllRanges(); 1236 range.deleteContents(); 1237 var node = range.startContainer; 1238 var pos = range.startOffset; 1239 switch (node.nodeType) { 1240 case 3: // Node.TEXT_NODE 1241 // we have to split it at the caret position. 1242 if (toBeInserted.nodeType == 3) { 1243 // do optimized insertion 1244 node.insertData(pos, toBeInserted.data); 1245 range = this._createRange(); 1246 range.setEnd(node, pos + toBeInserted.length); 1247 range.setStart(node, pos + toBeInserted.length); 1248 sel.addRange(range); 1249 } else { 1250 node = node.splitText(pos); 1251 var selnode = toBeInserted; 1252 if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { 1253 selnode = selnode.firstChild; 1254 } 1255 node.parentNode.insertBefore(toBeInserted, node); 1256 this.selectNodeContents(selnode); 1257 this.updateToolbar(); 1258 } 1259 break; 1260 case 1: // Node.ELEMENT_NODE 1261 var selnode = toBeInserted; 1262 if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { 1263 selnode = selnode.firstChild; 1264 } 1265 node.insertBefore(toBeInserted, node.childNodes[pos]); 1266 this.selectNodeContents(selnode); 1267 this.updateToolbar(); 1268 break; 1269 } 1270 } else { 1271 return null; // this function not yet used for IE <FIXME> 1272 } 1273 }; 1274 1275 // Returns the deepest node that contains both endpoints of the selection. 1276 HTMLArea.prototype.getParentElement = function() { 1277 var sel = this._getSelection(); 1278 var range = this._createRange(sel); 1279 if (HTMLArea.is_ie) { 1280 switch (sel.type) { 1281 case "Text": 1282 case "None": 1283 // It seems that even for selection of type "None", 1284 // there _is_ a parent element and it's value is not 1285 // only correct, but very important to us. MSIE is 1286 // certainly the buggiest browser in the world and I 1287 // wonder, God, how can Earth stand it? 1288 return range.parentElement(); 1289 case "Control": 1290 return range.item(0); 1291 default: 1292 return this._doc.body; 1293 } 1294 } else try { 1295 var p = range.commonAncestorContainer; 1296 if (!range.collapsed && range.startContainer == range.endContainer && 1297 range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes()) 1298 p = range.startContainer.childNodes[range.startOffset]; 1299 /* 1300 alert(range.startContainer + ":" + range.startOffset + "\n" + 1301 range.endContainer + ":" + range.endOffset); 1302 */ 1303 while (p.nodeType == 3) { 1304 p = p.parentNode; 1305 } 1306 return p; 1307 } catch (e) { 1308 return null; 1309 } 1310 }; 1311 1312 // Returns an array with all the ancestor nodes of the selection. 1313 HTMLArea.prototype.getAllAncestors = function() { 1314 var p = this.getParentElement(); 1315 var a = []; 1316 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) { 1317 a.push(p); 1318 p = p.parentNode; 1319 } 1320 a.push(this._doc.body); 1321 return a; 1322 }; 1323 1324 // Selects the contents inside the given node 1325 HTMLArea.prototype.selectNodeContents = function(node, pos) { 1326 this.focusEditor(); 1327 this.forceRedraw(); 1328 var range; 1329 var collapsed = (typeof pos != "undefined"); 1330 if (HTMLArea.is_ie) { 1331 range = this._doc.body.createTextRange(); 1332 range.moveToElementText(node); 1333 (collapsed) && range.collapse(pos); 1334 range.select(); 1335 } else { 1336 var sel = this._getSelection(); 1337 range = this._doc.createRange(); 1338 range.selectNodeContents(node); 1339 (collapsed) && range.collapse(pos); 1340 sel.removeAllRanges(); 1341 sel.addRange(range); 1342 } 1343 }; 1344 1345 /** Call this function to insert HTML code at the current position. It deletes 1346 * the selection, if any. 1347 */ 1348 HTMLArea.prototype.insertHTML = function(html) { 1349 var sel = this._getSelection(); 1350 var range = this._createRange(sel); 1351 if (HTMLArea.is_ie) { 1352 range.pasteHTML(html); 1353 } else { 1354 // construct a new document fragment with the given HTML 1355 var fragment = this._doc.createDocumentFragment(); 1356 var div = this._doc.createElement("div"); 1357 div.innerHTML = html; 1358 while (div.firstChild) { 1359 // the following call also removes the node from div 1360 fragment.appendChild(div.firstChild); 1361 } 1362 // this also removes the selection 1363 var node = this.insertNodeAtSelection(fragment); 1364 } 1365 }; 1366 1367 /** 1368 * Call this function to surround the existing HTML code in the selection with 1369 * your tags. FIXME: buggy! This function will be deprecated "soon". 1370 */ 1371 HTMLArea.prototype.surroundHTML = function(startTag, endTag) { 1372 var html = this.getSelectedHTML(); 1373 // the following also deletes the selection 1374 this.insertHTML(startTag + html + endTag); 1375 }; 1376 1377 /// Retrieve the selected block 1378 HTMLArea.prototype.getSelectedHTML = function() { 1379 var sel = this._getSelection(); 1380 var range = this._createRange(sel); 1381 var existing = null; 1382 if (HTMLArea.is_ie) { 1383 existing = range.htmlText; 1384 } else { 1385 existing = HTMLArea.getHTML(range.cloneContents(), false, this); 1386 } 1387 return existing; 1388 }; 1389 1390 /// Return true if we have some selection 1391 HTMLArea.prototype.hasSelectedText = function() { 1392 // FIXME: come _on_ mishoo, you can do better than this ;-) 1393 return this.getSelectedHTML() != ''; 1394 }; 1395 1396 HTMLArea.prototype._createLink = function(link) { 1397 var editor = this; 1398 var outparam = null; 1399 if (typeof link == "undefined") { 1400 link = this.getParentElement(); 1401 if (link && !/^a$/i.test(link.tagName)) 1402 link = null; 1403 } 1404 if (link) outparam = { 1405 f_href : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"), 1406 f_title : link.title, 1407 f_target : link.target 1408 }; 1409 this._popupDialog("link.html", function(param) { 1410 if (!param) 1411 return false; 1412 var a = link; 1413 if (!a) { 1414 editor._doc.execCommand("createlink", false, param.f_href); 1415 a = editor.getParentElement(); 1416 var sel = editor._getSelection(); 1417 var range = editor._createRange(sel); 1418 if (!HTMLArea.is_ie) { 1419 a = range.startContainer; 1420 if (!/^a$/i.test(a.tagName)) 1421 a = a.nextSibling; 1422 } 1423 } else a.href = param.f_href.trim(); 1424 if (!/^a$/i.test(a.tagName)) 1425 return false; 1426 a.target = param.f_target.trim(); 1427 a.title = param.f_title.trim(); 1428 editor.selectNodeContents(a); 1429 editor.updateToolbar(); 1430 }, outparam); 1431 }; 1432 1433 // Called when the user clicks on "InsertImage" button. If an image is already 1434 // there, it will just modify it's properties. 1435 HTMLArea.prototype._insertImage = function(image) { 1436 var editor = this; // for nested functions 1437 var outparam = null; 1438 if (typeof image == "undefined") { 1439 image = this.getParentElement(); 1440 if (image && !/^img$/i.test(image.tagName)) 1441 image = null; 1442 } 1443 if (image) outparam = { 1444 f_url : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"), 1445 f_alt : image.alt, 1446 f_border : image.border, 1447 f_align : image.align, 1448 f_vert : image.vspace, 1449 f_horiz : image.hspace 1450 }; 1451 this._popupDialog("insert_image.html", function(param) { 1452 if (!param) { // user must have pressed Cancel 1453 return false; 1454 } 1455 var img = image; 1456 if (!img) { 1457 var sel = editor._getSelection(); 1458 var range = editor._createRange(sel); 1459 editor._doc.execCommand("insertimage", false, param.f_url); 1460 if (HTMLArea.is_ie) { 1461 img = range.parentElement(); 1462 // wonder if this works... 1463 if (img.tagName.toLowerCase() != "img") { 1464 img = img.previousSibling; 1465 } 1466 } else { 1467 img = range.startContainer.previousSibling; 1468 } 1469 } else { 1470 img.src = param.f_url; 1471 } 1472 for (field in param) { 1473 var value = param[field]; 1474 switch (field) { 1475 case "f_alt" : img.alt = value; break; 1476 case "f_border" : img.border = parseInt(value || "0"); break; 1477 case "f_align" : img.align = value; break; 1478 case "f_vert" : img.vspace = parseInt(value || "0"); break; 1479 case "f_horiz" : img.hspace = parseInt(value || "0"); break; 1480 } 1481 } 1482 }, outparam); 1483 }; 1484 1485 // Called when the user clicks the Insert Table button 1486 HTMLArea.prototype._insertTable = function() { 1487 var sel = this._getSelection(); 1488 var range = this._createRange(sel); 1489 var editor = this; // for nested functions 1490 this._popupDialog("insert_table.html", function(param) { 1491 if (!param) { // user must have pressed Cancel 1492 return false; 1493 } 1494 var doc = editor._doc; 1495 // create the table element 1496 var table = doc.createElement("table"); 1497 // assign the given arguments 1498 for (var field in param) { 1499 var value = param[field]; 1500 if (!value) { 1501 continue; 1502 } 1503 switch (field) { 1504 case "f_width" : table.style.width = value + param["f_unit"]; break; 1505 case "f_align" : table.align = value; break; 1506 case "f_border" : table.border = parseInt(value); break; 1507 case "f_spacing" : table.cellspacing = parseInt(value); break; 1508 case "f_padding" : table.cellpadding = parseInt(value); break; 1509 } 1510 } 1511 var tbody = doc.createElement("tbody"); 1512 table.appendChild(tbody); 1513 for (var i = 0; i < param["f_rows"]; ++i) { 1514 var tr = doc.createElement("tr"); 1515 tbody.appendChild(tr); 1516 for (var j = 0; j < param["f_cols"]; ++j) { 1517 var td = doc.createElement("td"); 1518 tr.appendChild(td); 1519 // Mozilla likes to see something inside the cell. 1520 (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br")); 1521 } 1522 } 1523 if (HTMLArea.is_ie) { 1524 range.pasteHTML(table.outerHTML); 1525 } else { 1526 // insert the table 1527 editor.insertNodeAtSelection(table); 1528 } 1529 return true; 1530 }, null); 1531 }; 1532 1533 /*************************************************** 1534 * Category: EVENT HANDLERS 1535 ***************************************************/ 1536 1537 // el is reference to the SELECT object 1538 // txt is the name of the select field, as in config.toolbar 1539 HTMLArea.prototype._comboSelected = function(el, txt) { 1540 this.focusEditor(); 1541 var value = el.options[el.selectedIndex].value; 1542 switch (txt) { 1543 case "fontname": 1544 case "fontsize": this.execCommand(txt, false, value); break; 1545 case "formatblock": 1546 (HTMLArea.is_ie) && (value = "<" + value + ">"); 1547 this.execCommand(txt, false, value); 1548 break; 1549 default: 1550 // try to look it up in the registered dropdowns 1551 var dropdown = this.config.customSelects[txt]; 1552 if (typeof dropdown != "undefined") { 1553 dropdown.action(this); 1554 } else { 1555 alert("FIXME: combo box " + txt + " not implemented"); 1556 } 1557 } 1558 }; 1559 1560 // the execCommand function (intercepts some commands and replaces them with 1561 // our own implementation) 1562 HTMLArea.prototype.execCommand = function(cmdID, UI, param) { 1563 var editor = this; // for nested functions 1564 this.focusEditor(); 1565 cmdID = cmdID.toLowerCase(); 1566 switch (cmdID) { 1567 case "htmlmode" : this.setMode(); break; 1568 case "hilitecolor": 1569 (HTMLArea.is_ie) && (cmdID = "backcolor"); 1570 case "forecolor": 1571 this._popupDialog("select_color.html", function(color) { 1572 if (color) { // selection not canceled 1573 editor._doc.execCommand(cmdID, false, "#" + color); 1574 } 1575 }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID))); 1576 break; 1577 case "createlink": 1578 this._createLink(); 1579 break; 1580 case "popupeditor": 1581 // this object will be passed to the newly opened window 1582 HTMLArea._object = this; 1583 if (HTMLArea.is_ie) { 1584 //if (confirm(HTMLArea.I18N.msg["IE-sucks-full-screen"])) 1585 { 1586 window.open(this.popupURL("fullscreen.html"), "ha_fullscreen", 1587 "toolbar=no,location=no,directories=no,status=no,menubar=no," + 1588 "scrollbars=no,resizable=yes,width=640,height=480"); 1589 } 1590 } else { 1591 window.open(this.popupURL("fullscreen.html"), "ha_fullscreen", 1592 "toolbar=no,menubar=no,personalbar=no,width=640,height=480," + 1593 "scrollbars=no,resizable=yes"); 1594 } 1595 break; 1596 case "undo": 1597 case "redo": 1598 if (this._customUndo) 1599 this[cmdID](); 1600 else 1601 this._doc.execCommand(cmdID, UI, param); 1602 break; 1603 case "inserttable": this._insertTable(); break; 1604 case "insertimage": this._insertImage(); break; 1605 case "about" : this._popupDialog("about.html", null, this); break; 1606 case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break; 1607 1608 case "killword": this._wordClean(); break; 1609 1610 case "cut": 1611 case "copy": 1612 case "paste": 1613 try { 1614 if (this.config.killWordOnPaste) 1615 this._wordClean(); 1616 this._doc.execCommand(cmdID, UI, param); 1617 } catch (e) { 1618 if (HTMLArea.is_gecko) { 1619 if (confirm("Unprivileged scripts cannot access Cut/Copy/Paste programatically " + 1620 "for security reasons. Click OK to see a technical note at mozilla.org " + 1621 "which shows you how to allow a script to access the clipboard.")) 1622 window.open("http://mozilla.org/editor/midasdemo/securityprefs.html"); 1623 } 1624 } 1625 break; 1626 case "lefttoright": 1627 case "righttoleft": 1628 var dir = (cmdID == "righttoleft") ? "rtl" : "ltr"; 1629 var el = this.getParentElement(); 1630 while (el && !HTMLArea.isBlockElement(el)) 1631 el = el.parentNode; 1632 if (el) { 1633 if (el.style.direction == dir) 1634 el.style.direction = ""; 1635 else 1636 el.style.direction = dir; 1637 } 1638 break; 1639 default: this._doc.execCommand(cmdID, UI, param); 1640 } 1641 this.updateToolbar(); 1642 return false; 1643 }; 1644 1645 /** A generic event handler for things that happen in the IFRAME's document. 1646 * This function also handles key bindings. */ 1647 HTMLArea.prototype._editorEvent = function(ev) { 1648 var editor = this; 1649 var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress"); 1650 if (keyEvent) { 1651 for (var i in editor.plugins) { 1652 var plugin = editor.plugins[i].instance; 1653 if (typeof plugin.onKeyPress == "function") plugin.onKeyPress(ev); 1654 } 1655 } 1656 if (keyEvent && ev.ctrlKey) { 1657 var sel = null; 1658 var range = null; 1659 var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase(); 1660 var cmd = null; 1661 var value = null; 1662 switch (key) { 1663 case 'a': 1664 if (!HTMLArea.is_ie) { 1665 // KEY select all 1666 sel = this._getSelection(); 1667 sel.removeAllRanges(); 1668 range = this._createRange(); 1669 range.selectNodeContents(this._doc.body); 1670 sel.addRange(range); 1671 HTMLArea._stopEvent(ev); 1672 } 1673 break; 1674 1675 // simple key commands follow 1676 1677 case 'b': cmd = "bold"; break; 1678 case 'i': cmd = "italic"; break; 1679 case 'u': cmd = "underline"; break; 1680 case 's': cmd = "strikethrough"; break; 1681 case 'l': cmd = "justifyleft"; break; 1682 case 'e': cmd = "justifycenter"; break; 1683 case 'r': cmd = "justifyright"; break; 1684 case 'j': cmd = "justifyfull"; break; 1685 case 'z': cmd = "undo"; break; 1686 case 'y': cmd = "redo"; break; 1687 case 'v': cmd = "paste"; break; 1688 1689 case '0': cmd = "killword"; break; 1690 1691 // headings 1692 case '1': 1693 case '2': 1694 case '3': 1695 case '4': 1696 case '5': 1697 case '6': 1698 cmd = "formatblock"; 1699 value = "h" + key; 1700 if (HTMLArea.is_ie) { 1701 value = "<" + value + ">"; 1702 } 1703 break; 1704 } 1705 if (cmd) { 1706 // execute simple command 1707 this.execCommand(cmd, false, value); 1708 HTMLArea._stopEvent(ev); 1709 } 1710 } 1711 /* 1712 else if (keyEvent) { 1713 // other keys here 1714 switch (ev.keyCode) { 1715 case 13: // KEY enter 1716 // if (HTMLArea.is_ie) { 1717 this.insertHTML("<br />"); 1718 HTMLArea._stopEvent(ev); 1719 // } 1720 break; 1721 } 1722 } 1723 */ 1724 // update the toolbar state after some time 1725 if (editor._timerToolbar) { 1726 clearTimeout(editor._timerToolbar); 1727 } 1728 editor._timerToolbar = setTimeout(function() { 1729 editor.updateToolbar(); 1730 editor._timerToolbar = null; 1731 }, 50); 1732 }; 1733 1734 // retrieve the HTML 1735 HTMLArea.prototype.getHTML = function() { 1736 switch (this._editMode) { 1737 case "wysiwyg" : 1738 if (!this.config.fullPage) { 1739 return HTMLArea.getHTML(this._doc.body, false, this); 1740 } else 1741 return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this); 1742 case "textmode" : return this._textArea.value; 1743 default : alert("Mode <" + mode + "> not defined!"); 1744 } 1745 return false; 1746 }; 1747 1748 // retrieve the HTML (fastest version, but uses innerHTML) 1749 HTMLArea.prototype.getInnerHTML = function() { 1750 switch (this._editMode) { 1751 case "wysiwyg" : 1752 if (!this.config.fullPage) 1753 return this._doc.body.innerHTML; 1754 else 1755 return this.doctype + "\n" + this._doc.documentElement.innerHTML; 1756 case "textmode" : return this._textArea.value; 1757 default : alert("Mode <" + mode + "> not defined!"); 1758 } 1759 return false; 1760 }; 1761 1762 // completely change the HTML inside 1763 HTMLArea.prototype.setHTML = function(html) { 1764 switch (this._editMode) { 1765 case "wysiwyg" : 1766 if (!this.config.fullPage) 1767 this._doc.body.innerHTML = html; 1768 else 1769 // this._doc.documentElement.innerHTML = html; 1770 this._doc.body.innerHTML = html; 1771 break; 1772 case "textmode" : this._textArea.value = html; break; 1773 default : alert("Mode <" + mode + "> not defined!"); 1774 } 1775 return false; 1776 }; 1777 1778 // sets the given doctype (useful when config.fullPage is true) 1779 HTMLArea.prototype.setDoctype = function(doctype) { 1780 this.doctype = doctype; 1781 }; 1782 1783 /*************************************************** 1784 * Category: UTILITY FUNCTIONS 1785 ***************************************************/ 1786 1787 // browser identification 1788 1789 HTMLArea.agt = navigator.userAgent.toLowerCase(); 1790 HTMLArea.is_ie = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1)); 1791 HTMLArea.is_opera = (HTMLArea.agt.indexOf("opera") != -1); 1792 HTMLArea.is_mac = (HTMLArea.agt.indexOf("mac") != -1); 1793 HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac); 1794 HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac); 1795 HTMLArea.is_gecko = (navigator.product == "Gecko"); 1796 1797 // variable used to pass the object to the popup editor window. 1798 HTMLArea._object = null; 1799 1800 // function that returns a clone of the given object 1801 HTMLArea.cloneObject = function(obj) { 1802 var newObj = new Object; 1803 1804 // check for array objects 1805 if (obj.constructor.toString().indexOf("function Array(") == 1) { 1806 newObj = obj.constructor(); 1807 } 1808 1809 // check for function objects (as usual, IE is fucked up) 1810 if (obj.constructor.toString().indexOf("function Function(") == 1) { 1811 newObj = obj; // just copy reference to it 1812 } else for (var n in obj) { 1813 var node = obj[n]; 1814 if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); } 1815 else { newObj[n] = node; } 1816 } 1817 1818 return newObj; 1819 }; 1820 1821 // FIXME!!! this should return false for IE < 5.5 1822 HTMLArea.checkSupportedBrowser = function() { 1823 if (HTMLArea.is_gecko) { 1824 if (navigator.productSub < 20021201) { 1825 alert("You need at least Mozilla-1.3 Alpha.\n" + 1826 "Sorry, your Gecko is not supported."); 1827 return false; 1828 } 1829 if (navigator.productSub < 20030210) { 1830 alert("Mozilla < 1.3 Beta is not supported!\n" + 1831 "I'll try, though, but it might not work."); 1832 } 1833 } 1834 return HTMLArea.is_gecko || HTMLArea.is_ie; 1835 }; 1836 1837 // selection & ranges 1838 1839 // returns the current selection object 1840 HTMLArea.prototype._getSelection = function() { 1841 if (HTMLArea.is_ie) { 1842 return this._doc.selection; 1843 } else { 1844 return this._iframe.contentWindow.getSelection(); 1845 } 1846 }; 1847 1848 // returns a range for the current selection 1849 HTMLArea.prototype._createRange = function(sel) { 1850 if (HTMLArea.is_ie) { 1851 return sel.createRange(); 1852 } else { 1853 this.focusEditor(); 1854 if (typeof sel != "undefined") { 1855 try { 1856 return sel.getRangeAt(0); 1857 } catch(e) { 1858 return this._doc.createRange(); 1859 } 1860 } else { 1861 return this._doc.createRange(); 1862 } 1863 } 1864 }; 1865 1866 // event handling 1867 1868 HTMLArea._addEvent = function(el, evname, func) { 1869 if (HTMLArea.is_ie) { 1870 el.attachEvent("on" + evname, func); 1871 } else { 1872 el.addEventListener(evname, func, true); 1873 } 1874 }; 1875 1876 HTMLArea._addEvents = function(el, evs, func) { 1877 for (var i in evs) { 1878 HTMLArea._addEvent(el, evs[i], func); 1879 } 1880 }; 1881 1882 HTMLArea._removeEvent = function(el, evname, func) { 1883 if (HTMLArea.is_ie) { 1884 el.detachEvent("on" + evname, func); 1885 } else { 1886 el.removeEventListener(evname, func, true); 1887 } 1888 }; 1889 1890 HTMLArea._removeEvents = function(el, evs, func) { 1891 for (var i in evs) { 1892 HTMLArea._removeEvent(el, evs[i], func); 1893 } 1894 }; 1895 1896 HTMLArea._stopEvent = function(ev) { 1897 if (HTMLArea.is_ie) { 1898 ev.cancelBubble = true; 1899 ev.returnValue = false; 1900 } else { 1901 ev.preventDefault(); 1902 ev.stopPropagation(); 1903 } 1904 }; 1905 1906 HTMLArea._removeClass = function(el, className) { 1907 if (!(el && el.className)) { 1908 return; 1909 } 1910 var cls = el.className.split(" "); 1911 var ar = new Array(); 1912 for (var i = cls.length; i > 0;) { 1913 if (cls[--i] != className) { 1914 ar[ar.length] = cls[i]; 1915 } 1916 } 1917 el.className = ar.join(" "); 1918 }; 1919 1920 HTMLArea._addClass = function(el, className) { 1921 // remove the class first, if already there 1922 HTMLArea._removeClass(el, className); 1923 el.className += " " + className; 1924 }; 1925 1926 HTMLArea._hasClass = function(el, className) { 1927 if (!(el && el.className)) { 1928 return false; 1929 } 1930 var cls = el.className.split(" "); 1931 for (var i = cls.length; i > 0;) { 1932 if (cls[--i] == className) { 1933 return true; 1934 } 1935 } 1936 return false; 1937 }; 1938 1939 HTMLArea.isBlockElement = function(el) { 1940 var blockTags = " body form textarea fieldset ul ol dl li div " + 1941 "p h1 h2 h3 h4 h5 h6 quote pre table thead " + 1942 "tbody tfoot tr td iframe address "; 1943 return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); 1944 }; 1945 1946 HTMLArea.needsClosingTag = function(el) { 1947 var closingTags = " head script style div span tr td tbody table em strong font a title "; 1948 return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); 1949 }; 1950 1951 // performs HTML encoding of some given string 1952 HTMLArea.htmlEncode = function(str) { 1953 // we don't need regexp for that, but.. so be it for now. 1954 str = str.replace(/&/ig, "&"); 1955 str = str.replace(/</ig, "<"); 1956 str = str.replace(/>/ig, ">"); 1957 str = str.replace(/\x22/ig, """); 1958 // \x22 means '"' -- we use hex reprezentation so that we don't disturb 1959 // JS compressors (well, at least mine fails.. ;) 1960 return str; 1961 }; 1962 1963 // Retrieves the HTML code from the given node. This is a replacement for 1964 // getting innerHTML, using standard DOM calls. 1965 HTMLArea.getHTML = function(root, outputRoot, editor) { 1966 var html = ""; 1967 switch (root.nodeType) { 1968 case 1: // Node.ELEMENT_NODE 1969 case 11: // Node.DOCUMENT_FRAGMENT_NODE 1970 var closed; 1971 var i; 1972 var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : ''; 1973 if (HTMLArea.is_ie && root_tag == "head") { 1974 if (outputRoot) 1975 html += "<head>"; 1976 // lowercasize 1977 var save_multiline = RegExp.multiline; 1978 RegExp.multiline = true; 1979 var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) { 1980 return p1 + p2.toLowerCase(); 1981 }); 1982 RegExp.multiline = save_multiline; 1983 html += txt; 1984 if (outputRoot) 1985 html += "</head>"; 1986 break; 1987 } else if (outputRoot) { 1988 closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root))); 1989 html = "<" + root.tagName.toLowerCase(); 1990 var attrs = root.attributes; 1991 for (i = 0; i < attrs.length; ++i) { 1992 var a = attrs.item(i); 1993 if (!a.specified) { 1994 continue; 1995 } 1996 var name = a.nodeName.toLowerCase(); 1997 if (/_moz|contenteditable|_msh/.test(name)) { 1998 // avoid certain attributes 1999 continue; 2000 } 2001 var value; 2002 if (name != "style") { 2003 // IE5.5 reports 25 when cellSpacing is 2004 // 1; other values might be doomed too. 2005 // For this reason we extract the 2006 // values directly from the root node. 2007 // I'm starting to HATE JavaScript 2008 // development. Browser differences 2009 // suck. 2010 // 2011 // Using Gecko the values of href and src are converted to absolute links 2012 // unless we get them using nodeValue() 2013 if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") { 2014 value = root[a.nodeName]; 2015 } else { 2016 value = a.nodeValue; 2017 // IE seems not willing to return the original values - it converts to absolute 2018 // links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href") 2019 // So we have to strip the baseurl manually -/ 2020 if (HTMLArea.is_ie && (name == "href" || name == "src")) { 2021 value = editor.stripBaseURL(value); 2022 } 2023 } 2024 } else { // IE fails to put style in attributes list 2025 // FIXME: cssText reported by IE is UPPERCASE 2026 value = root.style.cssText; 2027 } 2028 if (/(_moz|^$)/.test(value)) { 2029 // Mozilla reports some special tags 2030 // here; we don't need them. 2031 continue; 2032 } 2033 html += " " + name + '="' + value + '"'; 2034 } 2035 html += closed ? " />" : ">"; 2036 } 2037 for (i = root.firstChild; i; i = i.nextSibling) { 2038 html += HTMLArea.getHTML(i, true, editor); 2039 } 2040 if (outputRoot && !closed) { 2041 html += "</" + root.tagName.toLowerCase() + ">"; 2042 } 2043 break; 2044 case 3: // Node.TEXT_NODE 2045 // If a text node is alone in an element and all spaces, replace it with an non breaking one 2046 // This partially undoes the damage done by moz, which translates ' 's into spaces in the data element 2047 if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) ) html = ' '; 2048 else html = HTMLArea.htmlEncode(root.data); 2049 break; 2050 case 8: // Node.COMMENT_NODE 2051 html = "<!--" + root.data + "-->"; 2052 break; // skip comments, for now. 2053 } 2054 return html; 2055 }; 2056 2057 HTMLArea.prototype.stripBaseURL = function(string) { 2058 var baseurl = this.config.baseURL; 2059 2060 // strip to last directory in case baseurl points to a file 2061 baseurl = baseurl.replace(/[^\/]+$/, ''); 2062 var basere = new RegExp(baseurl); 2063 // string = string.replace(basere, ""); 2064 2065 // strip host-part of URL which is added by MSIE to links relative to server root 2066 baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1'); 2067 basere = new RegExp(baseurl); 2068 return string; //.replace(basere, ""); 2069 }; 2070 2071 String.prototype.trim = function() { 2072 a = this.replace(/^\s+/, ''); 2073 return a.replace(/\s+$/, ''); 2074 }; 2075 2076 // creates a rgb-style color from a number 2077 HTMLArea._makeColor = function(v) { 2078 if (typeof v != "number") { 2079 // already in rgb (hopefully); IE doesn't get here. 2080 return v; 2081 } 2082 // IE sends number; convert to rgb. 2083 var r = v & 0xFF; 2084 var g = (v >> 8) & 0xFF; 2085 var b = (v >> 16) & 0xFF; 2086 return "rgb(" + r + "," + g + "," + b + ")"; 2087 }; 2088 2089 // returns hexadecimal color representation from a number or a rgb-style color. 2090 HTMLArea._colorToRgb = function(v) { 2091 if (!v) 2092 return ''; 2093 2094 // returns the hex representation of one byte (2 digits) 2095 function hex(d) { 2096 return (d < 16) ? ("0" + d.toString(16)) : d.toString(16); 2097 }; 2098 2099 if (typeof v == "number") { 2100 // we're talking to IE here 2101 var r = v & 0xFF; 2102 var g = (v >> 8) & 0xFF; 2103 var b = (v >> 16) & 0xFF; 2104 return "#" + hex(r) + hex(g) + hex(b); 2105 } 2106 2107 if (v.substr(0, 3) == "rgb") { 2108 // in rgb(...) form -- Mozilla 2109 var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/; 2110 if (v.match(re)) { 2111 var r = parseInt(RegExp.$1); 2112 var g = parseInt(RegExp.$2); 2113 var b = parseInt(RegExp.$3); 2114 return "#" + hex(r) + hex(g) + hex(b); 2115 } 2116 // doesn't match RE?! maybe uses percentages or float numbers 2117 // -- FIXME: not yet implemented. 2118 return null; 2119 } 2120 2121 if (v.substr(0, 1) == "#") { 2122 // already hex rgb (hopefully :D ) 2123 return v; 2124 } 2125 2126 // if everything else fails ;) 2127 return null; 2128 }; 2129 2130 // modal dialogs for Mozilla (for IE we're using the showModalDialog() call). 2131 2132 // receives an URL to the popup dialog and a function that receives one value; 2133 // this function will get called after the dialog is closed, with the return 2134 // value of the dialog. 2135 HTMLArea.prototype._popupDialog = function(url, action, init) { 2136 Dialog(this.popupURL(url), action, init); 2137 }; 2138 2139 // paths 2140 2141 HTMLArea.prototype.imgURL = function(file, plugin) { 2142 if (typeof plugin == "undefined") 2143 return _editor_url + file; 2144 else 2145 return _editor_url + "plugins/" + plugin + "/img/" + file; 2146 }; 2147 2148 HTMLArea.prototype.popupURL = function(file) { 2149 var url = ""; 2150 if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) { 2151 var plugin = RegExp.$1; 2152 var popup = RegExp.$2; 2153 if (!/\.html$/.test(popup)) 2154 popup += ".html"; 2155 url = _editor_url + "plugins/" + plugin + "/popups/" + popup; 2156 } else 2157 url = _editor_url + this.config.popupURL + file; 2158 return url; 2159 }; 2160 2161 /** 2162 * FIX: Internet Explorer returns an item having the _name_ equal to the given 2163 * id, even if it's not having any id. This way it can return a different form 2164 * field even if it's not a textarea. This workarounds the problem by 2165 * specifically looking to search only elements having a certain tag name. 2166 */ 2167 HTMLArea.getElementById = function(tag, id) { 2168 var el, i, objs = document.getElementsByTagName(tag); 2169 for (i = objs.length; --i >= 0 && (el = objs[i]);) 2170 if (el.id == id) 2171 return el; 2172 return null; 2173 }; 2174 2175 2176 2177 // EOF 2178 // Local variables: // 2179 // c-basic-offset:8 // 2180 // indent-tabs-mode:t // 2181 // End: //
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Mon Nov 26 16:45:43 2007 | par Balluche grâce à PHPXref 0.7 |
![]() |