| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php 2 include("../../../config.php"); 3 require_once($CFG->dirroot.'/lib/languages.php'); 4 5 $id = optional_param('id', SITEID, PARAM_INT); 6 $httpsrequired = optional_param('httpsrequired', 0, PARAM_BOOL); //flag indicating editor on page with required https 7 8 require_course_login($id); 9 10 $lastmodified = filemtime("htmlarea.php"); 11 $lifetime = 1800; 12 13 // Commenting this out since it's creating problems 14 // where solution seem to be hard to find... 15 // http://moodle.org/mod/forum/discuss.php?d=34376 16 //if ( function_exists('ob_gzhandler') ) { 17 // ob_start("ob_gzhandler"); 18 //} 19 20 header("Content-type: application/x-javascript; charset: utf-8"); // Correct MIME type 21 header("Last-Modified: " . gmdate("D, d M Y H:i:s", $lastmodified) . " GMT"); 22 header("Expires: " . gmdate("D, d M Y H:i:s", time() + $lifetime) . " GMT"); 23 header("Cache-control: max_age = $lifetime"); 24 header("Pragma: "); 25 26 $lang = current_language(); 27 28 if (empty($lang)) { 29 $lang = "en"; 30 } 31 32 if ($httpsrequired) { 33 // this is an ugly hack to allow partial operation of editor on pages that require https when loginhttps enabled 34 // please note that some popups still show nonsecurre items and fullscreen may not function properly in IE 35 $url = preg_replace('|https?://[^/]+|', '', $CFG->wwwroot).'/lib/editor/htmlarea/'; 36 } else { 37 $url = $CFG->wwwroot.'/lib/editor/htmlarea/'; 38 } 39 40 $strheading = get_string("heading", "editor"); 41 $strnormal = get_string("normal", "editor"); 42 $straddress = get_string("address", "editor"); 43 $strpreformatted = get_string("preformatted", "editor"); 44 $strlang = get_string('lang', 'editor'); 45 $strmulti = get_string('multi', 'editor'); 46 ?> 47 48 // htmlArea v3.0 - Copyright (c) 2002, 2003 interactivetools.com, inc. 49 // This copyright notice MUST stay intact for use (see license.txt). 50 // 51 // Portions (c) dynarch.com, 2003-2004 52 // 53 // A free WYSIWYG editor replacement for <textarea> fields. 54 // For full source code and docs, visit http://www.interactivetools.com/ 55 // 56 // Version 3.0 developed by Mihai Bazon. 57 // http://dynarch.com/mishoo 58 // 59 // $Id: htmlarea.php,v 1.24.2.5 2008/07/10 04:11:34 scyrma Exp $ 60 61 if (typeof _editor_url == "string") { 62 // Leave exactly one backslash at the end of _editor_url 63 _editor_url = _editor_url.replace(/\x2f*$/, '/'); 64 } else { 65 //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."); 66 _editor_url = '<?php echo $url; ?>';// we need relative path to site root for editor in pages wit hrequired https 67 } 68 69 // make sure we have a language 70 if (typeof _editor_lang == "string") { 71 _editor_lang = "en"; // should always be english in moodle. 72 } else { 73 _editor_lang = "en"; 74 } 75 76 // Creates a new HTMLArea object. Tries to replace the textarea with the given 77 // ID with it. 78 function HTMLArea(textarea, config) { 79 if (HTMLArea.checkSupportedBrowser()) { 80 if (typeof config == "undefined") { 81 this.config = new HTMLArea.Config(); 82 } else { 83 this.config = config; 84 } 85 this._htmlArea = null; 86 this._textArea = textarea; 87 this._editMode = "wysiwyg"; 88 this.plugins = {}; 89 this._timerToolbar = null; 90 this._timerUndo = null; 91 this._undoQueue = new Array(this.config.undoSteps); 92 this._undoPos = -1; 93 this._customUndo = true; 94 this._mdoc = document; // cache the document, we need it in plugins 95 this.doctype = ''; 96 this.dropdowns = []; // Array of select elements in the toolbar 97 } 98 }; 99 100 // load some scripts 101 (function() { 102 var scripts = HTMLArea._scripts = [ _editor_url + "htmlarea.js", 103 _editor_url + "dialog.js", 104 _editor_url + "popupwin.js" ]; 105 var head = document.getElementsByTagName("head")[0]; 106 // start from 1, htmlarea.js is already loaded 107 for (var i = 1; i < scripts.length; ++i) { 108 var script = document.createElement("script"); 109 script.src = scripts[i]; 110 head.appendChild(script); 111 } 112 })(); 113 114 // cache some regexps 115 HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig; 116 HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i; 117 HTMLArea.RE_head = /<head>((.|\n)*?)<\/head>/i; 118 HTMLArea.RE_body = /<body>((.|\n)*?)<\/body>/i; 119 HTMLArea.RE_blocktag = /^(h1|h2|h3|h4|h5|h6|p|address|pre)$/i; 120 HTMLArea.RE_junktag = /^\/($|\/)/; 121 // Hopefully a complete list of tags that MSIEs parser will consider 122 // as possible content tags. Retrieved from 123 // http://www.echoecho.com/htmlreference.htm 124 HTMLArea.RE_msietag = /^\/?(a|abbr|acronym|address|applet|area|b|base|basefont|bdo|bgsound|big|blink|blockquote|body|br|button|caption|center|cite|code|col|colgroup|comment|dd|del|dfn|dir|div|dl|dt|em|embed|fieldset|font|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|hr|html|i|iframe|ilayer|img|input|ins|isindex|kbd|keygen|label|layer|legend|li|link|map|marquee|menu|meta|multicol|nobr|noembed|noframes|nolayer|noscript|object|ol|optgroup|option|p|param|plaintext|pre|q|s|samp|script|select|server|small|spacer|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|ul|var)$/i 125 126 HTMLArea.Config = function () { 127 this.version = "3.0"; 128 129 this.width = "auto"; 130 this.height = "auto"; 131 132 // enable creation of a status bar? 133 this.statusBar = true; 134 135 // maximum size of the undo queue 136 this.undoSteps = 20; 137 138 // the time interval at which undo samples are taken 139 this.undoTimeout = 500; // 1/2 sec. 140 141 // the next parameter specifies whether the toolbar should be included 142 // in the size or not. 143 this.sizeIncludesToolbar = true; 144 145 // if true then HTMLArea will retrieve the full HTML, starting with the 146 // <HTML> tag. 147 this.fullPage = false; 148 149 // style included in the iframe document 150 this.pageStyle = "body { background-color: #fff; font-family: 'Times New Roman', Times; } \n .lang { background-color: #dee; }"; 151 152 // set to true if you want Word code to be cleaned upon Paste 153 this.killWordOnPaste = true; 154 155 // BaseURL included in the iframe document 156 this.baseURL = document.baseURI || document.URL; 157 if (this.baseURL && this.baseURL.match(/(.*)\/([^\/]+)/)) 158 this.baseURL = RegExp.$1 + "/"; 159 160 // URL-s 161 this.imgURL = "images/"; 162 this.popupURL = "popups/"; 163 164 this.toolbar = [ 165 [ "fontname", "space", 166 "fontsize", "space", 167 "formatblock", "space", 168 "language", "space", 169 "bold", "italic", "underline", "strikethrough", "separator", 170 "subscript", "superscript", "separator", 171 "clean", "separator", "undo", "redo" ], 172 173 [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator", 174 "lefttoright", "righttoleft", "separator", 175 "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator", 176 "forecolor", "hilitecolor", "separator", 177 "inserthorizontalrule", "createanchor", "createlink", "unlink", "nolink", "separator", 178 "insertimage", "inserttable", 179 "insertsmile", "insertchar", "search_replace", 180 <?php if (!empty($CFG->aspellpath) && file_exists($CFG->aspellpath) && !empty($CFG->editorspelling)) { 181 echo '"separator","spellcheck",'; 182 } ?> 183 "separator", "htmlmode", "separator", "popupeditor"] 184 ]; 185 186 this.fontname = { 187 "Arial": 'arial,helvetica,sans-serif', 188 "Courier New": 'courier new,courier,monospace', 189 "Georgia": 'georgia,times new roman,times,serif', 190 "Tahoma": 'tahoma,arial,helvetica,sans-serif', 191 "Times New Roman": 'times new roman,times,serif', 192 "Verdana": 'verdana,arial,helvetica,sans-serif', 193 "Impact": 'impact', 194 "WingDings": 'wingdings' 195 }; 196 197 this.fontsize = { 198 "1 (8 pt)": "1", 199 "2 (10 pt)": "2", 200 "3 (12 pt)": "3", 201 "4 (14 pt)": "4", 202 "5 (18 pt)": "5", 203 "6 (24 pt)": "6", 204 "7 (36 pt)": "7" 205 }; 206 207 this.formatblock = { 208 "":"", 209 "<?php echo $strheading ?> 1": "h1", 210 "<?php echo $strheading ?> 2": "h2", 211 "<?php echo $strheading ?> 3": "h3", 212 "<?php echo $strheading ?> 4": "h4", 213 "<?php echo $strheading ?> 5": "h5", 214 "<?php echo $strheading ?> 6": "h6", 215 "<?php echo $strnormal ?>": "p", 216 "<?php echo $straddress ?>": "address", 217 "<?php echo $strpreformatted ?>": "pre" 218 }; 219 220 this.language = { 221 "<?php echo $strlang; ?>":"", 222 <?php 223 $strlangarray = ''; 224 foreach ($LANGUAGES as $key => $name) { 225 $key = str_replace('_', '-', $key); 226 $strlangarray .= '"'.$key.'": "'.$key.'",'; 227 } 228 $strlangarray .= '"'.$strmulti.'": "multi",'; 229 230 foreach ($LANGUAGES as $key => $name) { 231 $key = str_replace('_', '-', $key); 232 $strlangarray .= '"'.$key.' ": "'.$key.'_ML",'; 233 } 234 $strlangarray = substr($strlangarray, 0, -1); 235 echo $strlangarray; 236 ?> 237 }; 238 239 this.customSelects = {}; 240 241 function cut_copy_paste(e, cmd, obj) { 242 e.execCommand(cmd); 243 }; 244 245 this.btnList = { 246 bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ], 247 italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ], 248 underline: [ "Underline", "ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ], 249 strikethrough: [ "Strikethrough", "ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ], 250 subscript: [ "Subscript", "ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ], 251 superscript: [ "Superscript", "ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ], 252 justifyleft: [ "Justify Left", "ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ], 253 justifycenter: [ "Justify Center", "ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ], 254 justifyright: [ "Justify Right", "ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ], 255 justifyfull: [ "Justify Full", "ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ], 256 insertorderedlist: [ "Ordered List", "ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ], 257 insertunorderedlist: [ "Bulleted List", "ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ], 258 outdent: [ "Decrease Indent", "ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ], 259 indent: [ "Increase Indent", "ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ], 260 forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ], 261 hilitecolor: [ "Background Color", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ], 262 inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ], 263 createanchor: [ "Create anchor", "ed_anchor.gif", false, function(e) {e.execCommand("createanchor", true);} ], 264 createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ], 265 unlink: [ "Remove Link", "ed_unlink.gif", false, function(e) {e.execCommand("unlink");} ], 266 nolink: [ "No link", "ed_nolink.gif", false, function(e) {e.execCommand("nolink");} ], 267 insertimage: [ "Insert/Modify Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ], 268 inserttable: [ "Insert Table", "insert_table.gif", false, function(e) {e.execCommand("inserttable");} ], 269 htmlmode: [ "Toggle HTML Source", "ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ], 270 popupeditor: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ], 271 about: [ "About this editor", "ed_about.gif", true, function(e) {e.execCommand("about");} ], 272 showhelp: [ "Help using editor", "ed_help.gif", true, function(e) {e.execCommand("showhelp");} ], 273 undo: [ "Undoes your last action", "ed_undo.gif", false, function(e) {e.execCommand("undo");} ], 274 redo: [ "Redoes your last action", "ed_redo.gif", false, function(e) {e.execCommand("redo");} ], 275 clean: [ "Clean Word HTML", "ed_wordclean.gif", false, function(e) {e.execCommand("killword"); }], 276 lefttoright: [ "Direction left to right", "ed_left_to_right.gif", false, function(e) {e.execCommand("lefttoright");} ], 277 righttoleft: [ "Direction right to left", "ed_right_to_left.gif", false, function(e) {e.execCommand("righttoleft");} ], 278 <?php if (!empty($CFG->aspellpath) && file_exists($CFG->aspellpath) && !empty($CFG->editorspelling)) { 279 echo 'spellcheck: ["Spell-check", "spell-check.gif", false, spellClickHandler ],'."\n"; 280 }?> 281 insertsmile: ["Insert Smiley", "em.icon.smile.gif", false, function(e) {e.execCommand("insertsmile");} ], 282 insertchar: [ "Insert Char", "icon_ins_char.gif", false, function(e) {e.execCommand("insertchar");} ], 283 search_replace: [ "Search and replace", "ed_replace.gif", false, function(e) {e.execCommand("searchandreplace");} ] 284 }; 285 286 // initialize tooltips from the I18N module and generate correct image path 287 for (var i in this.btnList) { 288 var btn = this.btnList[i]; 289 btn[1] = _editor_url + this.imgURL + btn[1]; 290 if (typeof HTMLArea.I18N.tooltips[i] != "undefined") { 291 btn[0] = HTMLArea.I18N.tooltips[i]; 292 } 293 } 294 }; 295 296 HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) { 297 var the_id; 298 if (typeof id == "string") { 299 the_id = id; 300 } else if (typeof id == "object") { 301 the_id = id.id; 302 } else { 303 alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments"); 304 return false; 305 } 306 // check for existing id 307 if (typeof this.customSelects[the_id] != "undefined") { 308 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists."); 309 } 310 if (typeof this.btnList[the_id] != "undefined") { 311 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists."); 312 } 313 switch (typeof id) { 314 case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break; 315 case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break; 316 } 317 }; 318 319 HTMLArea.Config.prototype.registerDropdown = function(object) { 320 // check for existing id 321 if (typeof this.customSelects[object.id] != "undefined") { 322 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists."); 323 } 324 if (typeof this.btnList[object.id] != "undefined") { 325 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists."); 326 } 327 this.customSelects[object.id] = object; 328 }; 329 330 HTMLArea.Config.prototype.hideSomeButtons = function(remove) { 331 var toolbar = this.toolbar; 332 for (var i in toolbar) { 333 var line = toolbar[i]; 334 for (var j = line.length; --j >= 0; ) { 335 if (remove.indexOf(" " + line[j] + " ") >= 0) { 336 var len = 1; 337 if (/separator|space/.test(line[j + 1])) { 338 len = 2; 339 } 340 line.splice(j, len); 341 } 342 } 343 } 344 }; 345 346 /** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */ 347 HTMLArea.replaceAll = function(config) { 348 var tas = document.getElementsByTagName("textarea"); 349 for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate()); 350 }; 351 352 /** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */ 353 HTMLArea.replace = function(id, config) { 354 var ta = HTMLArea.getElementById("textarea", id); 355 return ta ? (new HTMLArea(ta, config)).generate() : null; 356 }; 357 358 // Creates the toolbar and appends it to the _htmlarea 359 HTMLArea.prototype._createToolbar = function () { 360 var editor = this; // to access this in nested functions 361 362 var toolbar = document.createElement("div"); 363 this._toolbar = toolbar; 364 toolbar.className = "toolbar"; 365 toolbar.unselectable = "1"; 366 var tb_row = null; 367 var tb_objects = new Object(); 368 this._toolbarObjects = tb_objects; 369 370 // creates a new line in the toolbar 371 function newLine() { 372 var table = document.createElement("table"); 373 table.border = "0px"; 374 table.cellSpacing = "0px"; 375 table.cellPadding = "0px"; 376 toolbar.appendChild(table); 377 // TBODY is required for IE, otherwise you don't see anything 378 // in the TABLE. 379 var tb_body = document.createElement("tbody"); 380 table.appendChild(tb_body); 381 tb_row = document.createElement("tr"); 382 tb_body.appendChild(tb_row); 383 }; // END of function: newLine 384 // init first line 385 newLine(); 386 387 function setButtonStatus(id, newval) { 388 var oldval = this[id]; 389 var el = this.element; 390 if (oldval != newval) { 391 switch (id) { 392 case "enabled": 393 if (newval) { 394 HTMLArea._removeClass(el, "buttonDisabled"); 395 el.disabled = false; 396 } else { 397 HTMLArea._addClass(el, "buttonDisabled"); 398 el.disabled = true; 399 } 400 break; 401 case "active": 402 if (newval) { 403 HTMLArea._addClass(el, "buttonPressed"); 404 } else { 405 HTMLArea._removeClass(el, "buttonPressed"); 406 } 407 break; 408 } 409 this[id] = newval; 410 } 411 }; // END of function: setButtonStatus 412 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 case "language": 424 options = editor.config[txt]; 425 cmd = txt; 426 break; 427 default: 428 // try to fetch it from the list of registered selects 429 cmd = txt; 430 var dropdown = customSelects[cmd]; 431 if (typeof dropdown != "undefined") { 432 options = dropdown.options; 433 context = dropdown.context; 434 } else { 435 alert("ERROR [createSelect]:\nCan't find the requested dropdown definition"); 436 } 437 break; 438 } 439 if (options) { 440 el = document.createElement("select"); 441 var obj = { 442 name : txt, // field name 443 element : el, // the UI element (SELECT) 444 enabled : true, // is it enabled? 445 text : false, // enabled in text mode? 446 cmd : cmd, // command ID 447 state : setButtonStatus, // for changing state 448 context : context 449 }; 450 tb_objects[txt] = obj; 451 for (var i in options) { 452 var op = document.createElement("option"); 453 op.appendChild(document.createTextNode(i)); 454 op.value = options[i]; 455 el.appendChild(op); 456 } 457 HTMLArea._addEvent(el, "change", function () { 458 editor._comboSelected(el, txt); 459 }); 460 } 461 editor.dropdowns[txt] = el; // Keep track of the element for keyboard 462 // access later. 463 return el; 464 }; // END of function: createSelect 465 466 // appends a new button to toolbar 467 function createButton(txt) { 468 // the element that will be created 469 var el = null; 470 var btn = null; 471 switch (txt) { 472 case "separator": 473 el = document.createElement("div"); 474 el.className = "separator"; 475 break; 476 case "space": 477 el = document.createElement("div"); 478 el.className = "space"; 479 break; 480 case "linebreak": 481 newLine(); 482 return false; 483 case "textindicator": 484 el = document.createElement("div"); 485 el.appendChild(document.createTextNode("A")); 486 el.className = "indicator"; 487 el.title = HTMLArea.I18N.tooltips.textindicator; 488 var obj = { 489 name : txt, // the button name (i.e. 'bold') 490 element : el, // the UI element (DIV) 491 enabled : true, // is it enabled? 492 active : false, // is it pressed? 493 text : false, // enabled in text mode? 494 cmd : "textindicator", // the command ID 495 state : setButtonStatus // for changing state 496 }; 497 tb_objects[txt] = obj; 498 break; 499 default: 500 btn = editor.config.btnList[txt]; 501 } 502 if (!el && btn) { 503 el = document.createElement("div"); 504 el.title = btn[0]; 505 el.className = "button"; 506 // let's just pretend we have a button object, and 507 // assign all the needed information to it. 508 var obj = { 509 name : txt, // the button name (i.e. 'bold') 510 element : el, // the UI element (DIV) 511 enabled : true, // is it enabled? 512 active : false, // is it pressed? 513 text : btn[2], // enabled in text mode? 514 cmd : btn[3], // the command ID 515 state : setButtonStatus, // for changing state 516 context : btn[4] || null // enabled in a certain context? 517 }; 518 tb_objects[txt] = obj; 519 // handlers to emulate nice flat toolbar buttons 520 HTMLArea._addEvent(el, "mouseover", function () { 521 if (obj.enabled) { 522 HTMLArea._addClass(el, "buttonHover"); 523 } 524 }); 525 HTMLArea._addEvent(el, "mouseout", function () { 526 if (obj.enabled) with (HTMLArea) { 527 _removeClass(el, "buttonHover"); 528 _removeClass(el, "buttonActive"); 529 (obj.active) && _addClass(el, "buttonPressed"); 530 } 531 }); 532 HTMLArea._addEvent(el, "mousedown", function (ev) { 533 if (obj.enabled) with (HTMLArea) { 534 _addClass(el, "buttonActive"); 535 _removeClass(el, "buttonPressed"); 536 _stopEvent(is_ie ? window.event : ev); 537 } 538 }); 539 // when clicked, do the following: 540 HTMLArea._addEvent(el, "click", function (ev) { 541 if (obj.enabled) with (HTMLArea) { 542 _removeClass(el, "buttonActive"); 543 _removeClass(el, "buttonHover"); 544 obj.cmd(editor, obj.name, obj); 545 _stopEvent(is_ie ? window.event : ev); 546 } 547 }); 548 var img = document.createElement("img"); 549 img.src = btn[1]; 550 img.style.width = "18px"; 551 img.style.height = "18px"; 552 el.appendChild(img); 553 } else if (!el) { 554 el = createSelect(txt); 555 } 556 if (el) { 557 var tb_cell = document.createElement("td"); 558 tb_row.appendChild(tb_cell); 559 tb_cell.appendChild(el); 560 } else { 561 alert("FIXME: Unknown toolbar item: " + txt); 562 } 563 return el; 564 }; 565 566 var first = true; 567 for (var i in this.config.toolbar) { 568 if (this.config.toolbar.propertyIsEnumerable(i)) { // fix for prototype.js compatibility 569 if (!first) { 570 createButton("linebreak"); 571 } else { 572 first = false; 573 } 574 var group = this.config.toolbar[i]; 575 for (var j in group) { 576 if (group.propertyIsEnumerable(j)) { // fix for prototype.js compatibility 577 var code = group[j]; 578 if (/^([IT])\[(.*?)\]/.test(code)) { 579 // special case, create text label 580 var l7ed = RegExp.$1 == "I"; // localized? 581 var label = RegExp.$2; 582 if (l7ed) { 583 label = HTMLArea.I18N.custom[label]; 584 } 585 var tb_cell = document.createElement("td"); 586 tb_row.appendChild(tb_cell); 587 tb_cell.className = "label"; 588 tb_cell.innerHTML = label; 589 } else { 590 createButton(code); 591 } 592 } 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 622 // get the textarea 623 var textarea = this._textArea; 624 if (typeof textarea == "string") { 625 // it's not element but ID 626 this._textArea = textarea = HTMLArea.getElementById("textarea", textarea); 627 } 628 // Fix for IE's sticky bug. Editor doesn't load 629 // editing area. 630 var height; 631 if ( textarea.offsetHeight && textarea.offsetHeight > 0 ) { 632 height = textarea.offsetHeight; 633 } else { 634 height = 300; 635 } 636 this._ta_size = { 637 w: textarea.offsetWidth, 638 h: height 639 }; 640 textarea.style.display = "none"; 641 642 // create the editor framework 643 var htmlarea = document.createElement("div"); 644 htmlarea.className = "htmlarea"; 645 this._htmlArea = htmlarea; 646 647 // insert the editor before the textarea. 648 //Bug fix - unless the textarea is nested within its label, in which case insert editor before label. 649 if (textarea.parentNode.nodeName.toLowerCase()=='label') { 650 textarea.parentNode.parentNode.insertBefore(htmlarea,textarea.parentNode); 651 } 652 else { 653 textarea.parentNode.insertBefore(htmlarea, textarea); 654 } 655 656 if (textarea.form) { 657 // we have a form, on submit get the HTMLArea content and 658 // update original textarea. 659 var f = textarea.form; 660 if (typeof f.onsubmit == "function") { 661 var funcref = f.onsubmit; 662 if (typeof f.__msh_prevOnSubmit == "undefined") { 663 f.__msh_prevOnSubmit = []; 664 } 665 f.__msh_prevOnSubmit.push(funcref); 666 } 667 f.onsubmit = function() { 668 // Moodle hack. Bug fix #2736 669 var test = editor.getHTML(); 670 test = test.replace(/<br \/>/gi, ''); 671 test = test.replace(/\ \;/gi, ''); 672 test = test.trim(); 673 //alert(test + test.length); 674 if (test.length < 1) { 675 editor._textArea.value = test.trim(); 676 } else { 677 editor._textArea.value = editor.getHTML(); 678 } 679 // Moodle hack end. 680 var a = this.__msh_prevOnSubmit; 681 var ret = true; 682 // call previous submit methods if they were there. 683 if (typeof a != "undefined") { 684 for (var i = a.length; --i >= 0;) { 685 ret = a[i]() && ret; 686 } 687 } 688 return ret; 689 }; 690 if (typeof f.onreset == "function") { 691 var funcref = f.onreset; 692 if (typeof f.__msh_prevOnReset == "undefined") { 693 f.__msh_prevOnReset = []; 694 } 695 f.__msh_prevOnReset.push(funcref); 696 } 697 f.onreset = function() { 698 editor.setHTML(editor._textArea.value); 699 editor.updateToolbar(); 700 var a = this.__msh_prevOnReset; 701 // call previous reset methods if they were there. 702 if (typeof a != "undefined") { 703 for (var i = a.length; --i >= 0;) { 704 a[i](); 705 } 706 } 707 }; 708 } 709 710 // add a handler for the "back/forward" case -- on body.unload we save 711 // the HTML content into the original textarea. 712 try { 713 window.onunload = function() { 714 editor._textArea.value = editor.getHTML(); 715 }; 716 } catch(e) {}; 717 718 // creates & appends the toolbar 719 this._createToolbar(); 720 721 // create the IFRAME 722 var iframe = document.createElement("iframe"); 723 724 iframe.src = "about:blank"; 725 726 iframe.className = "iframe"; 727 728 htmlarea.appendChild(iframe); 729 730 var editor = this 731 editor._iframe = iframe; 732 var doc = editor._iframe.contentWindow.document; 733 editor._doc = doc; 734 735 // Generate iframe content 736 var html = "" 737 if (!editor.config.fullPage) { 738 html = "<html>\n"; 739 html += "<head>\n"; 740 html += '<meta http-equiv="content-type" content="text/html; charset=utf-8" />\n'; 741 if (editor.config.baseURL) 742 html += '<base href="' + editor.config.baseURL + '" />'; 743 html += '<style type="text/css">\n' + editor.config.pageStyle + "td { border: 1px dotted gray; } body { direction: <?php echo get_string('thisdirection')?>; } </style>\n"; // RTL support: direction added for RTL support 744 html += "</head>\n"; 745 html += '<body>\n'; 746 html += editor._textArea.value; 747 html = html.replace(/<nolink>/gi, '<span class="nolink">'). 748 replace(/<\/nolink>/gi, '</span>'); 749 html += "</body>\n"; 750 html += "</html>"; 751 } else { 752 html = editor._textArea.value; 753 if (html.match(HTMLArea.RE_doctype)) { 754 editor.setDoctype(RegExp.$1); 755 html = html.replace(HTMLArea.RE_doctype, ""); 756 } 757 } 758 759 // Write content to iframe 760 doc.open(); 761 doc.write(html); 762 doc.close(); 763 764 // The magic: onClick the designMode is set to 'on' 765 // This one is for click on HTMLarea toolbar and else 766 if(HTMLArea.is_gecko) { 767 HTMLArea._addEvents( 768 this._htmlArea, 769 ["mousedown"], 770 function(event) { 771 if(editor.designModeIsOn != true) 772 { 773 editor.designModeIsOn = true; 774 try { 775 doc.designMode = "on"; 776 } catch (e) { 777 alert(e) 778 }; 779 } 780 } 781 ); 782 783 // This one is for click in iframe 784 HTMLArea._addEvents( 785 editor._iframe.contentWindow, 786 ["mousedown"], 787 function(event) { 788 if(editor.designModeIsOn != true) 789 { 790 editor.designModeIsOn = true; 791 try { 792 doc.designMode = "on"; 793 } catch (e) { 794 alert(e) 795 }; 796 } 797 } 798 ); 799 } 800 // creates & appends the status bar, if the case 801 this._createStatusBar(); 802 803 // remove the default border as it keeps us from computing correctly 804 // the sizes. (somebody tell me why doesn't this work in IE) 805 806 if (!HTMLArea.is_ie) { 807 iframe.style.borderWidth = "1px"; 808 } 809 810 // size the IFRAME according to user's prefs or initial textarea 811 var height = (this.config.height == "auto" ? (this._ta_size.h) : this.config.height); 812 height = parseInt(height); 813 var width = (this.config.width == "auto" ? (this._toolbar.offsetWidth) : this.config.width); 814 width = (width == 0 ? 598 : width); 815 //width = Math.max(parseInt(width), 598); 816 817 width = String(width); 818 if (width.match(/^\d+$/)) { // is this a pure int? if so, let it be in px, and remove 2px 819 height -= 2; 820 width -= 2; 821 width=width+"px"; 822 } 823 824 iframe.style.width = width; 825 826 if (this.config.sizeIncludesToolbar) { 827 // substract toolbar height 828 height -= this._toolbar.offsetHeight; 829 height -= this._statusBar.offsetHeight; 830 } 831 if (height < 0) { 832 height = 0; 833 } 834 iframe.style.height = height + "px"; 835 836 // the editor including the toolbar now have the same size as the 837 // original textarea.. which means that we need to reduce that a bit. 838 textarea.style.width = iframe.style.width; 839 textarea.style.height = iframe.style.height; 840 841 if (HTMLArea.is_ie) { 842 doc.body.contentEditable = true; 843 } 844 845 // intercept some events; for updating the toolbar & keyboard handlers 846 HTMLArea._addEvents 847 (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"], 848 function (event) { 849 return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event); 850 }); 851 852 // check if any plugins have registered refresh handlers 853 for (var i in editor.plugins) { 854 var plugin = editor.plugins[i].instance; 855 if (typeof plugin.onGenerate == "function") { 856 plugin.onGenerate(); 857 } 858 if (typeof plugin.onGenerateOnce == "function") { 859 plugin.onGenerateOnce(); 860 plugin.onGenerateOnce = null; 861 } 862 } 863 864 // Moodle fix for bug Bug #2521 Too long statusbar line in IE 865 // 866 //setTimeout(function() { 867 // editor.updateToolbar(); 868 //}, 250); 869 870 if (typeof editor.onGenerate == "function") { 871 editor.onGenerate(); 872 } 873 }; 874 875 876 // Switches editor mode; parameter can be "textmode" or "wysiwyg". If no 877 // parameter was passed this function toggles between modes. 878 HTMLArea.prototype.setMode = function(mode) { 879 if (typeof mode == "undefined") { 880 mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode"); 881 } 882 switch (mode) { 883 case "textmode": 884 this._textArea.value = this.getHTML(); 885 this._iframe.style.display = "none"; 886 this._textArea.style.display = "block"; 887 if (this.config.statusBar) { 888 while(this._statusBar.childNodes.length>0) { 889 this._statusBar.removeChild(this._statusBar.childNodes[0]); 890 } 891 892 this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["TEXT_MODE"])); 893 } 894 break; 895 case "wysiwyg": 896 if (HTMLArea.is_gecko) { 897 // disable design mode before changing innerHTML 898 try { 899 this._doc.designMode = "off"; 900 } catch(e) {}; 901 } 902 if (!this.config.fullPage) 903 this._doc.body.innerHTML = this.getHTML(); 904 else 905 this.setFullHTML(this.getHTML()); 906 this._iframe.style.display = "block"; 907 this._textArea.style.display = "none"; 908 if (HTMLArea.is_gecko) { 909 // we need to refresh that info for Moz-1.3a 910 try { 911 this._doc.designMode = "on"; 912 //this._doc.focus(); 913 } catch(e) {}; 914 } 915 if (this.config.statusBar) { 916 this._statusBar.innerHTML = ''; 917 this._statusBar.appendChild(this._statusBarTree); 918 } 919 break; 920 default: 921 alert("Mode <" + mode + "> not defined!"); 922 return false; 923 } 924 this._editMode = mode; 925 this.focusEditor(); 926 }; 927 928 HTMLArea.prototype.setFullHTML = function(html) { 929 var save_multiline = RegExp.multiline; 930 RegExp.multiline = true; 931 if (html.match(HTMLArea.RE_doctype)) { 932 this.setDoctype(RegExp.$1); 933 html = html.replace(HTMLArea.RE_doctype, ""); 934 } 935 RegExp.multiline = save_multiline; 936 if (!HTMLArea.is_ie) { 937 if (html.match(HTMLArea.RE_head)) 938 this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1; 939 if (html.match(HTMLArea.RE_body)) 940 this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1; 941 } else { 942 var html_re = /<html>((.|\n)*?)<\/html>/i; 943 html = html.replace(html_re, "$1"); 944 this._doc.open(); 945 this._doc.write(html); 946 this._doc.close(); 947 this._doc.body.contentEditable = true; 948 return true; 949 } 950 }; 951 952 // Category: PLUGINS 953 954 HTMLArea.prototype.registerPlugin2 = function(plugin, args) { 955 if (typeof plugin == "string") 956 plugin = eval(plugin); 957 var obj = new plugin(this, args); 958 if (obj) { 959 var clone = {}; 960 var info = plugin._pluginInfo; 961 for (var i in info) 962 clone[i] = info[i]; 963 clone.instance = obj; 964 clone.args = args; 965 this.plugins[plugin._pluginInfo.name] = clone; 966 } else 967 alert("Can't register plugin " + plugin.toString() + "."); 968 }; 969 970 // Create the specified plugin and register it with this HTMLArea 971 HTMLArea.prototype.registerPlugin = function() { 972 var plugin = arguments[0]; 973 var args = []; 974 for (var i = 1; i < arguments.length; ++i) 975 args.push(arguments[i]); 976 this.registerPlugin2(plugin, args); 977 }; 978 979 HTMLArea.loadPlugin = function(pluginName) { 980 var dir = _editor_url + "plugins/" + pluginName; 981 var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, 982 function (str, l1, l2, l3) { 983 return l1 + "-" + l2.toLowerCase() + l3; 984 }).toLowerCase() + ".js"; 985 var plugin_file = dir + "/" + plugin; 986 var plugin_lang = dir + "/lang/" + HTMLArea.I18N.lang + ".js"; 987 HTMLArea._scripts.push(plugin_file, plugin_lang); 988 document.write("<script type='text/javascript' src='" + plugin_file + "'></script>"); 989 document.write("<script type='text/javascript' src='" + plugin_lang + "'></script>"); 990 }; 991 992 HTMLArea.loadStyle = function(style, plugin) { 993 var url = _editor_url || ''; 994 if (typeof plugin != "undefined") { 995 url += "plugins/" + plugin + "/"; 996 } 997 url += style; 998 document.write("<style type='text/css'>@import url(" + url + ");</style>"); 999 }; 1000 HTMLArea.loadStyle("htmlarea.css"); 1001 1002 // Category: EDITOR UTILITIES 1003 1004 // The following function is a slight variation of the word cleaner code posted 1005 // by Weeezl (user @ InteractiveTools forums). 1006 HTMLArea.prototype._wordClean = function() { 1007 this._unnestBlocks(); 1008 1009 var D = this.getInnerHTML(); 1010 if (/[Mm]so/.test(D)) { 1011 1012 // make one line 1013 D = D.replace(/\r\n/g, '\[br\]'). 1014 replace(/\n/g, ''). 1015 replace(/\r/g, ''). 1016 replace(/\ \;/g,' '); 1017 1018 // keep tags, strip attributes 1019 D = D.replace(/ class=[^\s|>]*/gi,''). 1020 //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">'). 1021 replace(/ style=\"[^>]*\"/gi,''). 1022 replace(/ align=[^\s|>]*/gi,''); 1023 1024 //clean up tags 1025 D = D.replace(/<b [^>]*>/gi,'<b>'). 1026 replace(/<i [^>]*>/gi,'<i>'). 1027 replace(/<li [^>]*>/gi,'<li>'). 1028 replace(/<ul [^>]*>/gi,'<ul>'); 1029 1030 // replace outdated tags 1031 D = D.replace(/<b>/gi,'<strong>'). 1032 replace(/<\/b>/gi,'</strong>'); 1033 1034 // mozilla doesn't like <em> tags 1035 D = D.replace(/<em>/gi,'<i>'). 1036 replace(/<\/em>/gi,'</i>'); 1037 1038 // kill unwanted tags 1039 D = D.replace(/<\?xml:[^>]*>/g, ''). // Word xml 1040 replace(/<\/?st1:[^>]*>/g,''). // Word SmartTags 1041 replace(/<\/?[a-z]\:[^>]*>/g,''). // All other funny Word non-HTML stuff 1042 replace(/<\/?personname[^>]*>/gi,''). 1043 replace(/<\/?font[^>]*>/gi,''). // Disable if you want to keep font formatting 1044 replace(/<\/?span[^>]*>/gi,' '). 1045 replace(/<\/?div[^>]*>/gi,' '). 1046 replace(/<\/?pre[^>]*>/gi,' '). 1047 replace(/<(\/?)(h[1-6]+)[^>]*>/gi,'<$1$2>'); 1048 1049 // Lorenzo Nicola's addition 1050 // to get rid off silly word generated tags. 1051 D = D.replace(/<!--\[[^\]]*\]-->/gi,' '); 1052 1053 //remove empty tags 1054 //D = D.replace(/<strong><\/strong>/gi,''). 1055 //replace(/<i><\/i>/gi,''). 1056 //replace(/<P[^>]*><\/P>/gi,''); 1057 D = D.replace(/<h[1-6]+>\s?<\/h[1-6]+>/gi, ''); // Remove empty headings 1058 1059 // nuke double tags 1060 oldlen = D.length + 1; 1061 while(oldlen > D.length) { 1062 oldlen = D.length; 1063 // join us now and free the tags, we'll be free hackers, we'll be free... ;-) 1064 D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' '). 1065 replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>'); 1066 } 1067 D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>'). 1068 replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>'); 1069 1070 // nuke double spaces 1071 D = D.replace(/ */gi,' '); 1072 1073 // Split into lines and remove 1074 // empty lines and add carriage returns back 1075 var splitter = /\[br\]/g; 1076 var emptyLine = /^\s+\s+$/g; 1077 var strHTML = ''; 1078 var toLines = D.split(splitter); 1079 for (var i = 0; i < toLines.length; i++) { 1080 var line = toLines[i]; 1081 if (line.length < 1) { 1082 continue; 1083 } 1084 1085 if (emptyLine.test(line)) { 1086 continue; 1087 } 1088 1089 line = line.replace(/^\s+\s+$/g, ''); 1090 strHTML += line + '\n'; 1091 } 1092 D = strHTML; 1093 strHTML = ''; 1094 1095 this.setHTML(D); 1096 this.updateToolbar(); 1097 } 1098 }; 1099 1100 HTMLArea.prototype._unnestBlockWalk = function(node, unnestingParent) { 1101 if (HTMLArea.RE_blocktag.test(node.nodeName)) { 1102 if (unnestingParent) { 1103 if (node.nextSibling) { 1104 var splitNode = this._doc.createElement(unnestingParent.nodeName.toLowerCase()); 1105 while (node.nextSibling) { 1106 splitNode.appendChild(node.nextSibling); 1107 } 1108 unnestingParent.parentNode.insertBefore(splitNode, unnestingParent.nextSibling); 1109 } 1110 unnestingParent.parentNode.insertBefore(node, unnestingParent.nextSibling); 1111 return; 1112 } 1113 else if (node.firstChild) { 1114 this._unnestBlockWalk(node.firstChild, node); 1115 } 1116 } else { 1117 if (node.firstChild) { 1118 this._unnestBlockWalk(node.firstChild, null); 1119 } 1120 } 1121 if (node.nextSibling) { 1122 this._unnestBlockWalk(node.nextSibling, unnestingParent); 1123 } 1124 } 1125 1126 HTMLArea.prototype._unnestBlocks = function() { 1127 this._unnestBlockWalk(this._doc.documentElement, null); 1128 } 1129 1130 HTMLArea.prototype.forceRedraw = function() { 1131 this._doc.body.style.visibility = "hidden"; 1132 this._doc.body.style.visibility = "visible"; 1133 // this._doc.body.innerHTML = this.getInnerHTML(); 1134 }; 1135 1136 // focuses the iframe window. returns a reference to the editor document. 1137 HTMLArea.prototype.focusEditor = function() { 1138 switch (this._editMode) { 1139 case "wysiwyg" : this._iframe.contentWindow.focus(); break; 1140 case "textmode": this._textArea.focus(); break; 1141 default : alert("ERROR: mode " + this._editMode + " is not defined"); 1142 } 1143 return this._doc; 1144 }; 1145 1146 // takes a snapshot of the current text (for undo) 1147 HTMLArea.prototype._undoTakeSnapshot = function() { 1148 ++this._undoPos; 1149 if (this._undoPos >= this.config.undoSteps) { 1150 // remove the first element 1151 this._undoQueue.shift(); 1152 --this._undoPos; 1153 } 1154 // use the fasted method (getInnerHTML); 1155 var take = true; 1156 var txt = this.getInnerHTML(); 1157 if (this._undoPos > 0) 1158 take = (this._undoQueue[this._undoPos - 1] != txt); 1159 if (take) { 1160 this._undoQueue[this._undoPos] = txt; 1161 } else { 1162 this._undoPos--; 1163 } 1164 }; 1165 1166 HTMLArea.prototype.undo = function() { 1167 if (this._undoPos > 0) { 1168 var txt = this._undoQueue[--this._undoPos]; 1169 if (txt) this.setHTML(txt); 1170 else ++this._undoPos; 1171 } 1172 }; 1173 1174 HTMLArea.prototype.redo = function() { 1175 if (this._undoPos < this._undoQueue.length - 1) { 1176 var txt = this._undoQueue[++this._undoPos]; 1177 if (txt) this.setHTML(txt); 1178 else --this._undoPos; 1179 } 1180 }; 1181 1182 // updates enabled/disable/active state of the toolbar elements 1183 HTMLArea.prototype.updateToolbar = function(noStatus) { 1184 var doc = this._doc; 1185 var text = (this._editMode == "textmode"); 1186 var ancestors = null; 1187 if (!text) { 1188 ancestors = this.getAllAncestors(); 1189 if (this.config.statusBar && !noStatus) { 1190 1191 while(this._statusBarTree.childNodes.length>0) { 1192 this._statusBarTree.removeChild(this._statusBarTree.childNodes[0]); 1193 } 1194 1195 this._statusBarTree.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); 1196 1197 for (var i = ancestors.length; --i >= 0;) { 1198 var el = ancestors[i]; 1199 if (!el) { 1200 // hell knows why we get here; this 1201 // could be a classic example of why 1202 // it's good to check for conditions 1203 // that are impossible to happen ;-) 1204 continue; 1205 } 1206 var a = document.createElement("a"); 1207 a.href = "#"; 1208 a.el = el; 1209 a.editor = this; 1210 a.onclick = function() { 1211 this.blur(); 1212 this.editor.selectNodeContents(this.el); 1213 this.editor.updateToolbar(true); 1214 return false; 1215 }; 1216 a.oncontextmenu = function() { 1217 // TODO: add context menu here 1218 this.blur(); 1219 var info = "Inline style:\n\n"; 1220 info += this.el.style.cssText.split(/;\s*/).join(";\n"); 1221 alert(info); 1222 return false; 1223 }; 1224 var txt = el.tagName.toLowerCase(); 1225 a.title = el.style.cssText; 1226 if (el.id) { 1227 txt += "#" + el.id; 1228 } 1229 if (el.className) { 1230 txt += "." + el.className; 1231 } 1232 a.appendChild(document.createTextNode(txt)); 1233 this._statusBarTree.appendChild(a); 1234 if (i != 0) { 1235 this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb))); 1236 } 1237 } 1238 } 1239 } 1240 for (var i in this._toolbarObjects) { 1241 var btn = this._toolbarObjects[i]; 1242 var cmd = i; 1243 var inContext = true; 1244 if (btn.context && !text) { 1245 inContext = false; 1246 var context = btn.context; 1247 var attrs = []; 1248 if (/(.*)\[(.*?)\]/.test(context)) { 1249 context = RegExp.$1; 1250 attrs = RegExp.$2.split(","); 1251 } 1252 context = context.toLowerCase(); 1253 var match = (context == "*"); 1254 for (var k in ancestors) { 1255 if (!ancestors[k]) { 1256 // the impossible really happens. 1257 continue; 1258 } 1259 if (match || (ancestors[k].tagName.toLowerCase() == context)) { 1260 inContext = true; 1261 for (var ka in attrs) { 1262 if (!eval("ancestors[k]." + attrs[ka])) { 1263 inContext = false; 1264 break; 1265 } 1266 } 1267 if (inContext) { 1268 break; 1269 } 1270 } 1271 } 1272 } 1273 btn.state("enabled", (!text || btn.text) && inContext); 1274 if (typeof cmd == "function") { 1275 continue; 1276 } 1277 // look-it-up in the custom dropdown boxes 1278 var dropdown = this.config.customSelects[cmd]; 1279 if ((!text || btn.text) && (typeof dropdown != "undefined")) { 1280 dropdown.refresh(this); 1281 continue; 1282 } 1283 switch (cmd) { 1284 case "fontname": 1285 case "fontsize": 1286 case "formatblock": 1287 if (!text) try { 1288 var value = ("" + doc.queryCommandValue(cmd)).toLowerCase(); 1289 if (!value) { 1290 // FIXME: what do we do here? 1291 break; 1292 } 1293 var options = this.config[cmd]; 1294 var k = 0; 1295 // btn.element.selectedIndex = 0; 1296 for (var j in options) { 1297 // FIXME: the following line is scary. 1298 if ((j.toLowerCase() == value) || 1299 (options[j].substr(0, value.length).toLowerCase() == value)) { 1300 btn.element.selectedIndex = k; 1301 break; 1302 } 1303 ++k; 1304 } 1305 } catch(e) {}; 1306 break; 1307 case "language": 1308 if (!text) try { 1309 var value; 1310 parentEl = this.getParentElement(); 1311 if (parentEl.getAttribute('lang')) { 1312 // A language was previously defined for the block. 1313 if (parentEl.getAttribute('class') == 'multilang') { 1314 value = parentEl.getAttribute('lang')+'_ML'; 1315 } else { 1316 value = parentEl.getAttribute('lang'); 1317 } 1318 } else { 1319 value = ''; 1320 } 1321 var options = this.config[cmd]; 1322 var k = 0; 1323 for (var j in options) { 1324 // FIXME: the following line is scary. 1325 if ((j.toLowerCase() == value) || 1326 (options[j].substr(0, value.length).toLowerCase() == value)) { 1327 btn.element.selectedIndex = k; 1328 break; 1329 } 1330 ++k; 1331 } 1332 } catch(e) {}; 1333 break; 1334 case "textindicator": 1335 if (!text) { 1336 try {with (btn.element.style) { 1337 backgroundColor = HTMLArea._makeColor( 1338 doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor")); 1339 if (/transparent/i.test(backgroundColor)) { 1340 // Mozilla 1341 backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor")); 1342 } 1343 color = HTMLArea._makeColor(doc.queryCommandValue("forecolor")); 1344 fontFamily = doc.queryCommandValue("fontname"); 1345 fontWeight = doc.queryCommandState("bold") ? "bold" : "normal"; 1346 fontStyle = doc.queryCommandState("italic") ? "italic" : "normal"; 1347 }} catch (e) { 1348 // alert(e + "\n\n" + cmd); 1349 } 1350 } 1351 break; 1352 case "htmlmode": btn.state("active", text); break; 1353 case "lefttoright": 1354 case "righttoleft": 1355 var el = this.getParentElement(); 1356 while (el && !HTMLArea.isBlockElement(el)) 1357 el = el.parentNode; 1358 if (el) 1359 btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr"))); 1360 break; 1361 default: 1362 try { 1363 btn.state("active", (!text && doc.queryCommandState(cmd))); 1364 } catch (e) {} 1365 } 1366 } 1367 // take undo snapshots 1368 if (this._customUndo && !this._timerUndo) { 1369 this._undoTakeSnapshot(); 1370 var editor = this; 1371 this._timerUndo = setTimeout(function() { 1372 editor._timerUndo = null; 1373 }, this.config.undoTimeout); 1374 } 1375 // check if any plugins have registered refresh handlers 1376 for (var i in this.plugins) { 1377 var plugin = this.plugins[i].instance; 1378 if (typeof plugin.onUpdateToolbar == "function") 1379 plugin.onUpdateToolbar(); 1380 } 1381 }; 1382 1383 /** Returns a node after which we can insert other nodes, in the current 1384 * selection. The selection is removed. It splits a text node, if needed. 1385 */ 1386 HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) { 1387 if (!HTMLArea.is_ie) { 1388 var sel = this._getSelection(); 1389 var range = this._createRange(sel); 1390 // remove the current selection 1391 sel.removeAllRanges(); 1392 range.deleteContents(); 1393 var node = range.startContainer; 1394 var pos = range.startOffset; 1395 switch (node.nodeType) { 1396 case 3: // Node.TEXT_NODE 1397 // we have to split it at the caret position. 1398 if (toBeInserted.nodeType == 3) { 1399 // do optimized insertion 1400 node.insertData(pos, toBeInserted.data); 1401 range = this._createRange(); 1402 range.setEnd(node, pos + toBeInserted.length); 1403 range.setStart(node, pos + toBeInserted.length); 1404 sel.addRange(range); 1405 } else { 1406 node = node.splitText(pos); 1407 var selnode = toBeInserted; 1408 if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { 1409 selnode = selnode.firstChild; 1410 } 1411 node.parentNode.insertBefore(toBeInserted, node); 1412 this.selectNodeContents(selnode); 1413 this.updateToolbar(); 1414 } 1415 break; 1416 case 1: // Node.ELEMENT_NODE 1417 var selnode = toBeInserted; 1418 if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) { 1419 selnode = selnode.firstChild; 1420 } 1421 node.insertBefore(toBeInserted, node.childNodes[pos]); 1422 this.selectNodeContents(selnode); 1423 this.updateToolbar(); 1424 break; 1425 } 1426 } else { 1427 return null; // this function not yet used for IE <FIXME> 1428 } 1429 }; 1430 1431 // Returns the deepest node that contains both endpoints of the selection. 1432 HTMLArea.prototype.getParentElement = function() { 1433 var sel = this._getSelection(); 1434 var range = this._createRange(sel); 1435 if (HTMLArea.is_ie) { 1436 switch (sel.type) { 1437 case "Text": 1438 case "None": 1439 return range.parentElement(); 1440 case "Control": 1441 return range.item(0); 1442 default: 1443 return this._doc.body; 1444 } 1445 } else try { 1446 var p = range.commonAncestorContainer; 1447 if (!range.collapsed && range.startContainer == range.endContainer && 1448 range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes()) 1449 p = range.startContainer.childNodes[range.startOffset]; 1450 /* 1451 alert(range.startContainer + ":" + range.startOffset + "\n" + 1452 range.endContainer + ":" + range.endOffset); 1453 */ 1454 while (p.nodeType == 3) { 1455 p = p.parentNode; 1456 } 1457 return p; 1458 } catch (e) { 1459 return null; 1460 } 1461 }; 1462 1463 // Returns an array with all the ancestor nodes of the selection. 1464 HTMLArea.prototype.getAllAncestors = function() { 1465 var p = this.getParentElement(); 1466 var a = []; 1467 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) { 1468 a.push(p); 1469 p = p.parentNode; 1470 } 1471 a.push(this._doc.body); 1472 return a; 1473 }; 1474 1475 // Selects the contents inside the given node 1476 HTMLArea.prototype.selectNodeContents = function(node, pos) { 1477 this.focusEditor(); 1478 this.forceRedraw(); 1479 var range; 1480 var collapsed = (typeof pos != "undefined"); 1481 if (HTMLArea.is_ie) { 1482 range = this._doc.body.createTextRange(); 1483 range.moveToElementText(node); 1484 (collapsed) && range.collapse(pos); 1485 range.select(); 1486 } else { 1487 var sel = this._getSelection(); 1488 range = this._doc.createRange(); 1489 range.selectNodeContents(node); 1490 (collapsed) && range.collapse(pos); 1491 sel.removeAllRanges(); 1492 sel.addRange(range); 1493 } 1494 }; 1495 1496 // Call this function to insert HTML code at the current position. It deletes 1497 // the selection, if any. 1498 HTMLArea.prototype.insertHTML = function(html) { 1499 var sel = this._getSelection(); 1500 var range = this._createRange(sel); 1501 if (HTMLArea.is_ie) { 1502 range.pasteHTML(html); 1503 } else { 1504 // construct a new document fragment with the given HTML 1505 var fragment = this._doc.createDocumentFragment(); 1506 var div = this._doc.createElement("div"); 1507 div.innerHTML = html; 1508 while (div.firstChild) { 1509 // the following call also removes the node from div 1510 fragment.appendChild(div.firstChild); 1511 } 1512 // this also removes the selection 1513 var node = this.insertNodeAtSelection(fragment); 1514 } 1515 }; 1516 1517 // Call this function to surround the existing HTML code in the selection with 1518 // your tags. FIXME: buggy! This function will be deprecated "soon". 1519 HTMLArea.prototype.surroundHTML = function(startTag, endTag) { 1520 var html = this.getSelectedHTML(); 1521 // the following also deletes the selection 1522 this.insertHTML(startTag + html + endTag); 1523 }; 1524 1525 /// Retrieve the selected block 1526 HTMLArea.prototype.getSelectedHTML = function() { 1527 var sel = this._getSelection(); 1528 var range = this._createRange(sel); 1529 var existing = null; 1530 if (HTMLArea.is_ie) { 1531 existing = range.htmlText; 1532 } else { 1533 existing = HTMLArea.getHTML(range.cloneContents(), false, this); 1534 } 1535 return existing; 1536 }; 1537 1538 /// Return true if we have some selection 1539 HTMLArea.prototype.hasSelectedText = function() { 1540 // FIXME: come _on_ mishoo, you can do better than this ;-) 1541 return this.getSelectedHTML() != ''; 1542 }; 1543 1544 HTMLArea.prototype._createLink = function(link) { 1545 var editor = this; 1546 var allinks = editor._doc.getElementsByTagName('A'); 1547 var anchors = new Array(); 1548 for(var i = 0; i < allinks.length; i++) { 1549 var attrname = allinks[i].getAttribute('name'); 1550 if((HTMLArea.is_ie ? attrname.length > 0 : attrname != null)) { 1551 anchors[i] = allinks[i].getAttribute('name'); 1552 } 1553 } 1554 var outparam = null; 1555 if (typeof link == "undefined") { 1556 link = this.getParentElement(); 1557 if (link && !/^a$/i.test(link.tagName)) { 1558 if(link.tagName.toLowerCase() != 'img') { 1559 link = null; 1560 var sel = this._getSelection(); 1561 var rng = this._createRange(sel); 1562 var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length; 1563 if(len < 1) { 1564 alert("<?php print_string("alertnoselectedtext","editor");?>"); 1565 return false; 1566 } 1567 } 1568 link = null; 1569 } 1570 } 1571 if (link) { 1572 outparam = { 1573 f_href : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"), 1574 f_title : link.title, 1575 f_target : link.target, 1576 f_anchors: anchors 1577 }; 1578 } else { 1579 outparam = { 1580 f_anchors:anchors }; 1581 } 1582 this._popupDialog("link_std.php?id=<?php echo $id; ?>", function(param) { 1583 if (!param) { 1584 return false; 1585 } 1586 var a = link; 1587 if (!a) { 1588 // Create a temporary unique link, insert it then find it and set the correct parameters 1589 var tmpLink = 'http://www.moodle.org/'+Math.random(); 1590 var elm = editor._doc.execCommand("createlink",false,tmpLink); 1591 var links=editor._doc.getElementsByTagName("a"); 1592 for(var i=0;i<links.length;i++){ 1593 var link=links[i]; 1594 if(link.href==tmpLink) { 1595 link.href=param.f_href.trim(); 1596 if(param.f_target){ 1597 link.target=param.f_target.trim(); 1598 } 1599 if(param.f_title){ 1600 link.title=param.f_title.trim(); 1601 } 1602 break; 1603 } 1604 } 1605 } else { 1606 var href = param.f_href.trim(); 1607 editor.selectNodeContents(a); 1608 if (href == "") { 1609 editor._doc.execCommand("unlink", false, null); 1610 editor.updateToolbar(); 1611 return false; 1612 } else { 1613 a.href = href; 1614 } 1615 } 1616 if (!(a && /^a$/i.test(a.tagName))) { 1617 return false; 1618 } 1619 a.target = param.f_target.trim(); 1620 a.title = param.f_title.trim(); 1621 editor.selectNodeContents(a); 1622 editor.updateToolbar(); 1623 }, outparam); 1624 }; 1625 1626 // Called when the user clicks on "InsertImage" button. If an image is already 1627 // there, it will just modify it's properties. 1628 HTMLArea.prototype._insertImage = function(image) { 1629 1630 // Make sure that editor has focus 1631 this.focusEditor(); 1632 var editor = this; // for nested functions 1633 var outparam = null; 1634 if (typeof image == "undefined") { 1635 image = this.getParentElement(); 1636 if (image && !/^img$/i.test(image.tagName)) 1637 image = null; 1638 } 1639 if (image) outparam = { 1640 f_url : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"), 1641 f_alt : image.alt, 1642 f_border : image.border, 1643 f_align : image.align, 1644 f_vert : image.vspace, 1645 f_horiz : image.hspace, 1646 f_width : image.width, 1647 f_height : image.height 1648 }; 1649 this._popupDialog("<?php 1650 if(!empty($id) and has_capability('moodle/course:managefiles', get_context_instance(CONTEXT_COURSE, $id))) { 1651 echo "insert_image.php?id=$id"; 1652 } else { 1653 echo "insert_image_std.php?id=$id"; 1654 }?>", function(param) { 1655 if (!param) { // user must have pressed Cancel 1656 return false; 1657 } 1658 var img = image; 1659 if (!img) { 1660 var sel = editor._getSelection(); 1661 var range = editor._createRange(sel); 1662 if (HTMLArea.is_ie) { 1663 editor._doc.execCommand("insertimage", false, param.f_url); 1664 } 1665 if (HTMLArea.is_ie) { 1666 img = range.parentElement(); 1667 // wonder if this works... 1668 if (img.tagName.toLowerCase() != "img") { 1669 img = img.previousSibling; 1670 } 1671 } else { 1672 // MOODLE HACK: startContainer.perviousSibling 1673 // Doesn't work so we'll use createElement and 1674 // insertNodeAtSelection 1675 //img = range.startContainer.previousSibling; 1676 var img = editor._doc.createElement("img"); 1677 img.setAttribute("src",""+ param.f_url +""); 1678 img.setAttribute("alt",""+ param.f_alt +""); 1679 editor.insertNodeAtSelection(img); 1680 } 1681 } else { 1682 img.src = param.f_url; 1683 } 1684 for (field in param) { 1685 var value = param[field]; 1686 switch (field) { 1687 case "f_alt" : img.alt = value; img.title = value; break; 1688 case "f_border" : img.border = parseInt(value || "0"); break; 1689 case "f_align" : img.align = value; break; 1690 case "f_vert" : img.vspace = parseInt(value || "0"); break; 1691 case "f_horiz" : img.hspace = parseInt(value || "0"); break; 1692 case "f_width" : 1693 if(value != 0) { 1694 img.width = parseInt(value); 1695 } else { 1696 break; 1697 } 1698 break; 1699 case "f_height" : 1700 if(value != 0) { 1701 img.height = parseInt(value); 1702 } else { 1703 break; 1704 } 1705 break; 1706 } 1707 } 1708 }, outparam); 1709 }; 1710 1711 // Called when the user clicks the Insert Table button 1712 HTMLArea.prototype._insertTable = function() { 1713 var sel = this._getSelection(); 1714 var range = this._createRange(sel); 1715 var editor = this; // for nested functions 1716 this._popupDialog("insert_table.php?id=<?php echo $id; ?>", function(param) { 1717 if (!param) { // user must have pressed Cancel 1718 return false; 1719 } 1720 var doc = editor._doc; 1721 // create the table element 1722 var table = doc.createElement("table"); 1723 // assign the given arguments 1724 for (var field in param) { 1725 var value = param[field]; 1726 if (!value) { 1727 continue; 1728 } 1729 switch (field) { 1730 case "f_width" : table.width = value + param["f_unit"]; break; 1731 case "f_align" : table.align = value; break; 1732 case "f_border" : table.border = parseInt(value); break; 1733 case "f_spacing" : table.cellspacing = parseInt(value); break; 1734 case "f_padding" : table.cellpadding = parseInt(value); break; 1735 } 1736 } 1737 var tbody = doc.createElement("tbody"); 1738 table.appendChild(tbody); 1739 for (var i = 0; i < param["f_rows"]; ++i) { 1740 var tr = doc.createElement("tr"); 1741 tbody.appendChild(tr); 1742 for (var j = 0; j < param["f_cols"]; ++j) { 1743 var td = doc.createElement("td"); 1744 /// Moodle hack 1745 if(param["f_unit"] == "px") { 1746 tdwidth = Math.round(table.width / param["f_cols"]); 1747 } else { 1748 tdwidth = Math.round(100 / param["f_cols"]); 1749 } 1750 td.setAttribute("width",tdwidth + param["f_unit"]); 1751 td.setAttribute("valign","top"); 1752 /// Moodle hack -ends 1753 tr.appendChild(td); 1754 // Mozilla likes to see something inside the cell. 1755 (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br")); 1756 } 1757 } 1758 if (HTMLArea.is_ie) { 1759 range.pasteHTML(table.outerHTML); 1760 } else { 1761 // insert the table 1762 editor.insertNodeAtSelection(table); 1763 } 1764 return true; 1765 }, null); 1766 }; 1767 1768 /// Moodle hack - insertSmile 1769 HTMLArea.prototype._insertSmile = function() { 1770 // Make sure that editor has focus 1771 this.focusEditor(); 1772 var sel = this._getSelection(); 1773 var range = this._createRange(sel); 1774 var editor = this; // for nested functions 1775 this._popupDialog("dlg_ins_smile.php?id=<?php echo $id; ?>", function(imgString) { 1776 if(!imgString) { 1777 return false; 1778 } 1779 if (HTMLArea.is_ie) { 1780 range.pasteHTML(imgString); 1781 } else { 1782 editor.insertHTML(imgString); 1783 } 1784 return true; 1785 }, null); 1786 }; 1787 1788 HTMLArea.prototype._insertChar = function() { 1789 var sel = this._getSelection(); 1790 var range = this._createRange(sel); 1791 var editor = this; // for nested functions 1792 this._popupDialog("dlg_ins_char.php?id=<?php echo $id; ?>", function(sChar) { 1793 if(!sChar) { 1794 return false; 1795 } 1796 if (HTMLArea.is_ie) { 1797 range.pasteHTML(sChar); 1798 } else { 1799 // insert the table 1800 editor.insertHTML(sChar); 1801 } 1802 return true; 1803 }, null); 1804 }; 1805 1806 HTMLArea.prototype._removelink = function() { 1807 var editor = this; 1808 link = this.getParentElement(); 1809 editor.selectNodeContents(link); 1810 1811 this._doc.execCommand("unlink", false, null); 1812 this.focusEditor(); 1813 }; 1814 1815 HTMLArea.prototype._createanchor = function () { 1816 var editor = this; 1817 var sel = this._getSelection(); 1818 var rng = this._createRange(sel); 1819 var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length; 1820 if(len < 1) { 1821 alert("<?php print_string("alertnoselectedtext","editor");?>"); 1822 return false; 1823 } 1824 this._popupDialog("createanchor.php?id=<?php echo $id; ?>", function(objAn) { 1825 if(!objAn) { 1826 return false; 1827 } 1828 var str = '<a name="'+ objAn.anchor+'">'; 1829 str += HTMLArea.is_ie ? rng.text : sel ; 1830 str += '</a>'; 1831 editor.insertHTML(str); 1832 },null); 1833 }; 1834 1835 HTMLArea.prototype._nolinktag = function () { 1836 1837 var editor = this; 1838 var sel = this._getSelection(); 1839 var rng = this._createRange(sel); 1840 var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length; 1841 1842 if (len < 1) { 1843 alert("<?php print_string("alertnoselectedtext","editor");?>"); 1844 return false; 1845 } 1846 var str = '<span class="nolink">'; 1847 str += HTMLArea.is_ie ? rng.text : sel; 1848 str += '</span>'; 1849 editor.insertHTML(str); 1850 this.focusEditor(); 1851 1852 }; 1853 1854 HTMLArea.prototype._searchReplace = function() { 1855 1856 var editor = this; 1857 var selectedtxt = ""; 1858 <?php 1859 $strreplaced = addslashes(get_string('itemsreplaced','editor')); 1860 $strnotfound = addslashes(get_string('searchnotfound','editor')); 1861 ?> 1862 var strReplaced = '<?php echo $strreplaced ?>'; 1863 var strNotfound = '<?php echo $strnotfound ?>'; 1864 var ile; 1865 1866 //in source mode mozilla show errors, try diffrent method 1867 if (editor._editMode == "wysiwyg") { 1868 selectedtxt = editor.getSelectedHTML(); 1869 } else { 1870 if (HTMLArea.is_ie) { 1871 selectedtxt = document.selection.createRange().text; 1872 } else { 1873 selectedtxt = getMozSelection(editor._textArea); 1874 } 1875 } 1876 1877 outparam = { 1878 f_search : selectedtxt 1879 }; 1880 1881 //Call Search And Replace popup window 1882 editor._popupDialog( "searchandreplace.php?id=<?php echo $id; ?>", function( entity ) { 1883 if ( !entity ) { 1884 //user must have pressed Cancel 1885 return false; 1886 } 1887 var text = editor.getHTML(); 1888 var search = entity[0]; 1889 var replace = entity[1]; 1890 var delim = entity[2]; 1891 var regularx = entity[3]; 1892 var closesar = entity[4]; 1893 ile = 0; 1894 if (search.length < 1) { 1895 alert ("Enter a search word! \n search for: " + entity[0]); 1896 } else { 1897 if (regularx) { 1898 var regX = new RegExp (search, delim) ; 1899 var text = text.replace ( regX, 1900 function (str, n) { 1901 // Increment our counter variable. 1902 ile++ ; 1903 //return replace ; 1904 return str.replace( regX, replace) ; 1905 } 1906 ) 1907 1908 } else { 1909 while (text.indexOf(search)>-1) { 1910 pos = text.indexOf(search); 1911 text = "" + (text.substring(0, pos) + replace + text.substring((pos + search.length), text.length)); 1912 ile++; 1913 } 1914 } 1915 1916 editor.setHTML(text); 1917 editor.forceRedraw(); 1918 if (ile > 0) { 1919 alert(ile + ' ' + strReplaced); 1920 } else { 1921 alert (strNotfound + "\n"); 1922 } 1923 } 1924 }, outparam); 1925 1926 function getMozSelection(txtarea) { 1927 var selLength = txtarea.textLength; 1928 var selStart = txtarea.selectionStart; 1929 var selEnd = txtarea.selectionEnd; 1930 if (selEnd==1 || selEnd==2) selEnd=selLength; 1931 return (txtarea.value).substring(selStart, selEnd); 1932 } 1933 }; 1934 1935 /// Moodle hack's ends 1936 // 1937 // Category: EVENT HANDLERS 1938 1939 // el is reference to the SELECT object 1940 // txt is the name of the select field, as in config.toolbar 1941 HTMLArea.prototype._comboSelected = function(el, txt) { 1942 this.focusEditor(); 1943 var value = el.options[el.selectedIndex].value; 1944 switch (txt) { 1945 case "fontname": 1946 case "fontsize": this.execCommand(txt, false, value); break; 1947 case "language": 1948 this.setLang(value); 1949 break; 1950 case "formatblock": 1951 (HTMLArea.is_ie) && (value = "<" + value + ">"); 1952 this.execCommand(txt, false, value); 1953 break; 1954 default: 1955 // try to look it up in the registered dropdowns 1956 var dropdown = this.config.customSelects[txt]; 1957 if (typeof dropdown != "undefined") { 1958 dropdown.action(this); 1959 } else { 1960 alert("FIXME: combo box " + txt + " not implemented"); 1961 } 1962 } 1963 }; 1964 1965 1966 /** 1967 * Used to set the language for the selected content. 1968 * We use the <span lang="en" class="multilang">content</span> format for 1969 * content that should be marked for multilang filter use, and 1970 * <span lang="en">content</span> for normal content for which we want to 1971 * set the language (for screen reader usage, for example). 1972 */ 1973 HTMLArea.prototype.setLang = function(lang) { 1974 1975 if (lang == 'multi') { 1976 // This is just the separator in the dropdown. Does nothing. 1977 return; 1978 } 1979 1980 var editor = this; 1981 var selectedHTML = editor.getSelectedHTML(); 1982 var multiLang = false; 1983 1984 var re = new RegExp('_ML', 'g'); 1985 if (lang.match(re)) { 1986 multiLang = true; 1987 lang = lang.replace(re, ''); 1988 } 1989 1990 // Remove all lang attributes from span tags in selected html. 1991 selectedHTML = selectedHTML.replace(/(<span[^>]*)lang="[^"]*"([^>]*>)/, "$1$2"); 1992 selectedHTML = selectedHTML.replace(/(<span[^>]*)class="multilang"([^>]*>)/, "$1$2"); 1993 1994 // If a span tag is now empty, delete it. 1995 selectedHTML = selectedHTML.replace(/<span\s*>(.*?)<\/span>/, "$1"); 1996 1997 1998 var parentEl = this.getParentElement(); 1999 var insertNewSpan = false; 2000 2001 if (parentEl.nodeName == 'SPAN' && parentEl.getAttribute('lang')) { 2002 // A language was previously defined for the current block. 2003 // Check whether the selected text makes up the whole of the block 2004 // contents. 2005 var re = new RegExp(parentEl.innerHTML); 2006 2007 if (selectedHTML.match(re)) { 2008 // The selected text makes up the whole of the span block. 2009 if (lang != '') { 2010 parentEl.setAttribute('lang', lang); 2011 if (multiLang) { 2012 parentEl.setAttribute('class', 'multilang'); 2013 } 2014 } else { 2015 parentEl.removeAttribute('lang'); 2016 2017 var classAttr = parentEl.getAttribute('class'); 2018 if (classAttr) { 2019 classAttr = classAttr.replace(/multilang/, '').trim(); 2020 } 2021 if (classAttr == '') { 2022 parentEl.removeAttribute('class'); 2023 } 2024 if (parentEl.attributes.length == 0) { 2025 // The span is no longer needed. 2026 for (i=0; i<parentEl.childNodes.length; i++) { 2027 parentEl.parentNode.insertBefore(parentEl.childNodes[i], parentEl); 2028 } 2029 parentEl.parentNode.removeChild(parentEl); 2030 } 2031 } 2032 } else { 2033 insertNewSpan = true; 2034 } 2035 } else { 2036 insertNewSpan = true; 2037 } 2038 2039 if (insertNewSpan && lang != '') { 2040 var str = '<span lang="'+lang.trim()+'"'; 2041 str += ' class="multilang"'; 2042 str += '>'; 2043 str += selectedHTML; 2044 str += '</span>'; 2045 editor.insertHTML(str); 2046 } 2047 } 2048 2049 2050 // the execCommand function (intercepts some commands and replaces them with 2051 // our own implementation) 2052 HTMLArea.prototype.execCommand = function(cmdID, UI, param) { 2053 var editor = this; // for nested functions 2054 this.focusEditor(); 2055 cmdID = cmdID.toLowerCase(); 2056 switch (cmdID) { 2057 case "htmlmode" : this.setMode(); break; 2058 case "hilitecolor": 2059 (HTMLArea.is_ie) && (cmdID = "backcolor"); 2060 case "forecolor": 2061 this._popupDialog("select_color.php?id=<?php echo $id; ?>", function(color) { 2062 if (color) { // selection not canceled 2063 editor._doc.execCommand(cmdID, false, "#" + color); 2064 } 2065 }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID))); 2066 break; 2067 case "createanchor": this._createanchor(); break; 2068 case "createlink": 2069 this._createLink(); 2070 break; 2071 case "unlink": this._removelink(); break; 2072 case "nolink": this._nolinktag(); break; 2073 case "popupeditor": 2074 // this object will be passed to the newly opened window 2075 HTMLArea._object = this; 2076 if (HTMLArea.is_ie) { 2077 { 2078 window.open(this.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen", 2079 "toolbar=no,location=no,directories=no,status=no,menubar=no," + 2080 "scrollbars=no,resizable=yes,width=800,height=600"); 2081 } 2082 } else { 2083 window.open(this.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen", 2084 "toolbar=no,menubar=no,personalbar=no,width=800,height=600," + 2085 "scrollbars=no,resizable=yes"); 2086 } 2087 break; 2088 case "undo": 2089 case "redo": 2090 if (this._customUndo) 2091 this[cmdID](); 2092 else 2093 this._doc.execCommand(cmdID, UI, param); 2094 break; 2095 case "inserttable": this._insertTable(); break; 2096 case "insertimage": this._insertImage(); break; 2097 case "insertsmile": this._insertSmile(); break; 2098 case "insertchar": this._insertChar(); break; 2099 case "searchandreplace": this._searchReplace(); break; 2100 case "about" : this._popupDialog("about.html", null, this); break; 2101 case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break; 2102 2103 case "killword": this._wordClean(); break; 2104 2105 case "cut": 2106 case "copy": 2107 case "paste": 2108 try { 2109 // Paste first then clean 2110 this._doc.execCommand(cmdID, UI, param); 2111 if (this.config.killWordOnPaste) { 2112 this._wordClean(); 2113 } 2114 } catch (e) { 2115 if (HTMLArea.is_gecko) { 2116 if (confirm("<?php 2117 $strmoz = get_string('cutpastemozilla','editor'); 2118 $strmoz = preg_replace("/[\n|\r]+/", "", $strmoz); 2119 $strmoz = str_replace('<br />', '\\n', $strmoz); 2120 2121 echo addslashes($strmoz); 2122 2123 ?>")) 2124 window.open("http://moodle.org/mozillahelp"); 2125 } 2126 } 2127 break; 2128 case "lefttoright": 2129 case "righttoleft": 2130 var dir = (cmdID == "righttoleft") ? "rtl" : "ltr"; 2131 var el = this.getParentElement(); 2132 while (el && !HTMLArea.isBlockElement(el)) 2133 el = el.parentNode; 2134 if (el) { 2135 if (el.style.direction == dir) 2136 el.style.direction = ""; 2137 else 2138 el.style.direction = dir; 2139 } 2140 break; 2141 default: this._doc.execCommand(cmdID, UI, param); 2142 } 2143 this.updateToolbar(); 2144 return false; 2145 }; 2146 2147 2148 /** 2149 * A generic event handler for things that happen in the IFRAME's document. 2150 * This function also handles key bindings. 2151 */ 2152 HTMLArea.prototype._editorEvent = function(ev) { 2153 2154 var editor = this; 2155 var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress"); 2156 2157 if (keyEvent) { 2158 2159 for (var i in editor.plugins) { 2160 var plugin = editor.plugins[i].instance; 2161 if (typeof plugin.onKeyPress == "function") plugin.onKeyPress(ev); 2162 } 2163 2164 var sel = null; 2165 var range = null; 2166 var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase(); 2167 var cmd = null; 2168 var value = null; 2169 2170 if (ev.ctrlKey && !ev.altKey) { 2171 /** 2172 * Ctrl modifier only. 2173 * We use these for shortcuts that change existing content, 2174 * e.g. make text bold. 2175 */ 2176 switch (key) { 2177 2178 case 'a': 2179 // Select all. 2180 if (!HTMLArea.is_ie) { 2181 // KEY select all 2182 sel = this._getSelection(); 2183 sel.removeAllRanges(); 2184 range = this._createRange(); 2185 range.selectNodeContents(this._doc.body); 2186 sel.addRange(range); 2187 HTMLArea._stopEvent(ev); 2188 } 2189 break; 2190 2191 // For the dropdowns, we assign focus to them so that they are 2192 // keyboard accessible. 2193 case 'o': 2194 editor.dropdowns['fontname'].focus(); 2195 break; 2196 case 'p': 2197 editor.dropdowns['fontsize'].focus(); 2198 break; 2199 case 'h': 2200 editor.dropdowns['formatblock'].focus(); 2201 break; 2202 case '=': 2203 editor.dropdowns['language'].focus(); 2204 break; 2205 2206 case 'b': cmd = "bold"; break; 2207 case 'i': cmd = "italic"; break; 2208 case 'u': cmd = "underline"; break; 2209 case 's': cmd = "strikethrough"; break; 2210 case ',': cmd = "subscript"; break; 2211 case '.': cmd = "superscript"; break; 2212 2213 case 'v': 2214 if (! HTMLArea.is_gecko ) { 2215 cmd = "paste"; 2216 } 2217 break; 2218 2219 case '0': cmd = "killword"; break; 2220 case 'z': cmd = "undo"; break; 2221 case 'y': cmd = "redo"; break; 2222 case 'l': cmd = "justifyleft"; break; 2223 case 'e': cmd = "justifycenter"; break; 2224 case 'r': cmd = "justifyright"; break; 2225 case 'j': cmd = "justifyfull"; break; 2226 case '/': cmd = "lefttoright"; break; 2227 case '|': cmd = "righttoleft"; break; 2228 case ';': cmd = "outdent"; break; 2229 case "'": cmd = "indent"; break; 2230 case 'g': cmd = "forecolor"; break; 2231 case 'k': cmd = "hilitecolor"; break; 2232 case 'f': cmd = "searchandreplace"; break; 2233 case '`': cmd = "htmlmode"; break; // FIXME: can't toggle from source code to wysiwyg 2234 2235 case 'm': 2236 // Toggle fullscreen on or off. 2237 if (this.config.btnList['popupeditor'][0] == 'Enlarge Editor') { 2238 cmd = 'popupeditor'; 2239 } else { 2240 window.close(); 2241 } 2242 break; 2243 2244 // Headings. 2245 case '1': 2246 case '2': 2247 case '3': 2248 case '4': 2249 case '5': 2250 case '6': 2251 cmd = "formatblock"; 2252 value = "h" + key; 2253 if (HTMLArea.is_ie) { 2254 value = "<" + value + ">"; 2255 } 2256 break; 2257 2258 } // End switch (key) 2259 2260 2261 } else if (ev.ctrlKey && ev.altKey) { 2262 /** 2263 * Ctrl + Alt modifiers. 2264 * We use these for shortcuts that insert stuff, e.g. images. 2265 */ 2266 switch (key) { 2267 case 'o': cmd = "insertorderedlist"; break; 2268 case 'u': cmd = "insertunorderedlist"; break; 2269 case 'r': cmd = "inserthorizontalrule"; break; 2270 case 'a': cmd = "createanchor"; break; 2271 case 'l': cmd = "createlink"; break; 2272 case 'd': cmd = "unlink"; break; 2273 case 'n': cmd = "nolink"; break; 2274 case 'i': cmd = 'insertimage'; break; 2275 case 't': cmd = 'inserttable'; break; 2276 case 's': cmd = 'insertsmile'; break; 2277 case 'c': cmd = 'insertchar'; break; 2278 } 2279 } 2280 2281 if (cmd) { 2282 // execute simple command 2283 this.execCommand(cmd, false, value); 2284 HTMLArea._stopEvent(ev); 2285 } 2286 } // End if (keyEvent) 2287 2288 /* 2289 else if (keyEvent) { 2290 // other keys here 2291 switch (ev.keyCode) { 2292 case 13: // KEY enter 2293 // if (HTMLArea.is_ie) { 2294 this.insertHTML("<br />"); 2295 HTMLArea._stopEvent(ev); 2296 // } 2297 break; 2298 } 2299 } 2300 */ 2301 2302 // Update the toolbar state after some time. 2303 if (editor._timerToolbar) { 2304 clearTimeout(editor._timerToolbar); 2305 } 2306 editor._timerToolbar = setTimeout(function() { 2307 editor.updateToolbar(); 2308 editor._timerToolbar = null; 2309 }, 50); 2310 }; 2311 2312 2313 // retrieve the HTML 2314 HTMLArea.prototype.getHTML = function() { 2315 switch (this._editMode) { 2316 case "wysiwyg" : 2317 if (!this.config.fullPage) { 2318 return HTMLArea.getHTML(this._doc.body, false, this); 2319 } else 2320 return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this); 2321 case "textmode" : return this._textArea.value; 2322 default : alert("Mode <" + mode + "> not defined!"); 2323 } 2324 return false; 2325 }; 2326 2327 // retrieve the HTML (fastest version, but uses innerHTML) 2328 HTMLArea.prototype.getInnerHTML = function() { 2329 switch (this._editMode) { 2330 case "wysiwyg" : 2331 if (!this.config.fullPage) 2332 return this._doc.body.innerHTML; 2333 else 2334 return this.doctype + "\n" + this._doc.documentElement.innerHTML; 2335 case "textmode" : return this._textArea.value; 2336 default : alert("Mode <" + mode + "> not defined!"); 2337 } 2338 return false; 2339 }; 2340 2341 // completely change the HTML inside 2342 HTMLArea.prototype.setHTML = function(html) { 2343 switch (this._editMode) { 2344 case "wysiwyg" : 2345 if (!this.config.fullPage) 2346 this._doc.body.innerHTML = html; 2347 else 2348 // this._doc.documentElement.innerHTML = html; 2349 this._doc.body.innerHTML = html; 2350 break; 2351 case "textmode" : this._textArea.value = html; break; 2352 default : alert("Mode <" + mode + "> not defined!"); 2353 } 2354 return false; 2355 }; 2356 2357 // sets the given doctype (useful when config.fullPage is true) 2358 HTMLArea.prototype.setDoctype = function(doctype) { 2359 this.doctype = doctype; 2360 }; 2361 2362 /*************************************************** 2363 * Category: UTILITY FUNCTIONS 2364 ***************************************************/ 2365 2366 // browser identification 2367 2368 HTMLArea.agt = navigator.userAgent.toLowerCase(); 2369 HTMLArea.is_ie = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1)); 2370 HTMLArea.is_opera = (HTMLArea.agt.indexOf("opera") != -1); 2371 HTMLArea.is_mac = (HTMLArea.agt.indexOf("mac") != -1); 2372 HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac); 2373 HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac); 2374 HTMLArea.is_gecko = (navigator.product == "Gecko"); 2375 HTMLArea.is_safari = (HTMLArea.agt.indexOf("safari") != -1); 2376 2377 // variable used to pass the object to the popup editor window. 2378 HTMLArea._object = null; 2379 2380 // function that returns a clone of the given object 2381 HTMLArea.cloneObject = function(obj) { 2382 var newObj = new Object; 2383 2384 // check for array objects 2385 if (obj.constructor.toString().indexOf("function Array(") >= 0) { 2386 newObj = obj.constructor(); 2387 } 2388 2389 // check for function objects (as usual, IE is phucked up) 2390 if (obj.constructor.toString().indexOf("function Function(") >= 0) { 2391 newObj = obj; // just copy reference to it 2392 } else for (var n in obj) { 2393 var node = obj[n]; 2394 if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); } 2395 else { newObj[n] = node; } 2396 } 2397 2398 return newObj; 2399 }; 2400 2401 // FIXME!!! this should return false for IE < 5.5 2402 HTMLArea.checkSupportedBrowser = function() { 2403 if (HTMLArea.is_gecko) { 2404 if (navigator.productSub < 20021201) { 2405 alert("You need at least Mozilla-1.3 Alpha.\n" + 2406 "Sorry, your Gecko is not supported."); 2407 return false; 2408 } 2409 if (navigator.productSub < 20030210) { 2410 alert("Mozilla < 1.3 Beta is not supported!\n" + 2411 "I'll try, though, but it might not work."); 2412 } 2413 } 2414 if(HTMLArea.is_safari) { 2415 return false; 2416 } 2417 return HTMLArea.is_gecko || HTMLArea.is_ie; 2418 }; 2419 2420 // selection & ranges 2421 2422 // returns the current selection object 2423 HTMLArea.prototype._getSelection = function() { 2424 if (HTMLArea.is_ie) { 2425 return this._doc.selection; 2426 } else { 2427 return this._iframe.contentWindow.getSelection(); 2428 } 2429 }; 2430 2431 // returns a range for the current selection 2432 HTMLArea.prototype._createRange = function(sel) { 2433 if (HTMLArea.is_ie) { 2434 return sel.createRange(); 2435 } else { 2436 // Commented out because we need the dropdowns to be able to keep 2437 // focus for keyboard accessibility. Comment by Vy-Shane Sin Fat. 2438 //this.focusEditor(); 2439 if (typeof sel != "undefined") { 2440 try { 2441 return sel.getRangeAt(0); 2442 } catch(e) { 2443 return this._doc.createRange(); 2444 } 2445 } else { 2446 return this._doc.createRange(); 2447 } 2448 } 2449 }; 2450 2451 // event handling 2452 2453 HTMLArea._addEvent = function(el, evname, func) { 2454 if (HTMLArea.is_ie) { 2455 el.attachEvent("on" + evname, func); 2456 } else { 2457 el.addEventListener(evname, func, true); 2458 } 2459 }; 2460 2461 HTMLArea._addEvents = function(el, evs, func) { 2462 for (var i in evs) { 2463 HTMLArea._addEvent(el, evs[i], func); 2464 } 2465 }; 2466 2467 HTMLArea._removeEvent = function(el, evname, func) { 2468 if (HTMLArea.is_ie) { 2469 el.detachEvent("on" + evname, func); 2470 } else { 2471 el.removeEventListener(evname, func, true); 2472 } 2473 }; 2474 2475 HTMLArea._removeEvents = function(el, evs, func) { 2476 for (var i in evs) { 2477 HTMLArea._removeEvent(el, evs[i], func); 2478 } 2479 }; 2480 2481 HTMLArea._stopEvent = function(ev) { 2482 if (HTMLArea.is_ie) { 2483 ev.cancelBubble = true; 2484 ev.returnValue = false; 2485 } else { 2486 ev.preventDefault(); 2487 ev.stopPropagation(); 2488 } 2489 }; 2490 2491 HTMLArea._removeClass = function(el, className) { 2492 if (!(el && el.className)) { 2493 return; 2494 } 2495 var cls = el.className.split(" "); 2496 var ar = new Array(); 2497 for (var i = cls.length; i > 0;) { 2498 if (cls[--i] != className) { 2499 ar[ar.length] = cls[i]; 2500 } 2501 } 2502 el.className = ar.join(" "); 2503 }; 2504 2505 HTMLArea._addClass = function(el, className) { 2506 // remove the class first, if already there 2507 HTMLArea._removeClass(el, className); 2508 el.className += " " + className; 2509 }; 2510 2511 HTMLArea._hasClass = function(el, className) { 2512 if (!(el && el.className)) { 2513 return false; 2514 } 2515 var cls = el.className.split(" "); 2516 for (var i = cls.length; i > 0;) { 2517 if (cls[--i] == className) { 2518 return true; 2519 } 2520 } 2521 return false; 2522 }; 2523 2524 HTMLArea.isBlockElement = function(el) { 2525 2526 var blockTags = " body form textarea fieldset ul ol dl li div " + 2527 "p h1 h2 h3 h4 h5 h6 quote pre table thead " + 2528 "tbody tfoot tr td iframe address "; 2529 try { 2530 return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); 2531 } catch (e) {} 2532 2533 }; 2534 2535 HTMLArea.needsClosingTag = function(el) { 2536 var closingTags = " head script style div span tr td tbody table em strong font a title iframe object applet "; 2537 return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1); 2538 }; 2539 2540 // performs HTML encoding of some given string 2541 HTMLArea.htmlEncode = function(str) { 2542 // we don't need regexp for that, but.. so be it for now. 2543 str = str.replace(/&/ig, "&"); 2544 str = str.replace(/</ig, "<"); 2545 str = str.replace(/>/ig, ">"); 2546 str = str.replace(/\x22/ig, """); 2547 // \x22 means '"' -- we use hex reprezentation so that we don't disturb 2548 // JS compressors (well, at least mine fails.. ;) 2549 return str; 2550 }; 2551 2552 HTMLArea.isStandardTag = function (el) { 2553 return HTMLArea.RE_msietag.test(el.tagName); 2554 }; 2555 HTMLArea.isSingleTag = function (el) { 2556 var re = /^(br|hr|img|input|link|meta|param|embed|area)$/i; 2557 return re.test(el.tagName.toLowerCase()); 2558 }; 2559 // Retrieves the HTML code from the given node. This is a replacement for 2560 // getting innerHTML, using standard DOM calls. 2561 HTMLArea.getHTML = function(root, outputRoot, editor) { 2562 var html = ""; 2563 switch (root.nodeType) { 2564 case 1: // Node.ELEMENT_NODE 2565 case 11: // Node.DOCUMENT_FRAGMENT_NODE 2566 var closed; 2567 var i; 2568 var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : ''; 2569 if (HTMLArea.RE_junktag.test(root_tag)) { 2570 return ''; 2571 } 2572 if (HTMLArea.is_ie && root_tag == "head") { 2573 if (outputRoot) 2574 html += "<head>"; 2575 // lowercasize 2576 var save_multiline = RegExp.multiline; 2577 RegExp.multiline = true; 2578 var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) { 2579 return p1 + p2.toLowerCase(); 2580 }); 2581 RegExp.multiline = save_multiline; 2582 html += txt; 2583 if (outputRoot) 2584 html += "</head>"; 2585 break; 2586 } else if (outputRoot) { 2587 closed = (!(root.hasChildNodes() || !HTMLArea.isSingleTag(root))); 2588 html = "<" + root.tagName.toLowerCase(); 2589 var attrs = root.attributes; 2590 for (i = 0; i < attrs.length; ++i) { 2591 var a = attrs.item(i); 2592 if (!a.specified) { 2593 continue; 2594 } 2595 var name = a.nodeName.toLowerCase(); 2596 if (/_moz|contenteditable|_msh/.test(name)) { 2597 // avoid certain attributes 2598 continue; 2599 } 2600 var value; 2601 if (name != "style") { 2602 // 2603 // Using Gecko the values of href and src are converted to absolute links 2604 // unless we get them using nodeValue() 2605 if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") { 2606 value = root[a.nodeName]; 2607 } else { 2608 // This seems to be working, but if it does cause 2609 // problems later on return the old value... 2610 if (name.toLowerCase() == "href" && name.toLowerCase() == "src") { 2611 value = root[a.nodeName]; 2612 } else { 2613 value = a.nodeValue; 2614 } 2615 if (HTMLArea.is_ie && (name == "href" || name == "src")) { 2616 value = editor.stripBaseURL(value); 2617 } 2618 } 2619 } else { // IE fails to put style in attributes list 2620 // FIXME: cssText reported by IE is UPPERCASE 2621 value = root.style.cssText.toLowerCase(); 2622 } 2623 if (/(_moz|^$)/.test(value)) { 2624 // Mozilla reports some special tags 2625 // here; we don't need them. 2626 continue; 2627 } 2628 html += " " + name + '="' + value + '"'; 2629 } 2630 html += closed ? " />" : ">"; 2631 } 2632 for (i = root.firstChild; i; i = i.nextSibling) { 2633 html += HTMLArea.getHTML(i, true, editor); 2634 } 2635 if (outputRoot && !closed) { 2636 if ( HTMLArea.is_ie && !HTMLArea.isStandardTag(root) ) { 2637 html += ''; 2638 } else { 2639 html += "</" + root.tagName.toLowerCase() + ">"; 2640 } 2641 } 2642 break; 2643 case 3: // Node.TEXT_NODE 2644 // If a text node is alone in an element and all spaces, replace it with an non breaking one 2645 // This partially undoes the damage done by moz, which translates ' 's into spaces in the data element 2646 if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) && root.data.length > 1 ) html = ' '; 2647 else html = HTMLArea.htmlEncode(root.data); 2648 break; 2649 case 8: // Node.COMMENT_NODE 2650 html = "<!--" + root.data + "-->"; 2651 break; // skip comments, for now. 2652 } 2653 2654 return HTMLArea.indent(html); 2655 }; 2656 2657 HTMLArea.prototype.stripBaseURL = function(string) { 2658 var baseurl = this.config.baseURL; 2659 2660 // IE adds the path to an anchor, converting #anchor 2661 // to path/#anchor which of course needs to be fixed 2662 var index = string.indexOf("/#")+1; 2663 if ((index > 0) && (string.indexOf(baseurl) > -1)) { 2664 return string.substr(index); 2665 } 2666 return string; // Moodle doesn't use the code below because 2667 // Moodle likes to keep absolute links 2668 2669 // strip to last directory in case baseurl points to a file 2670 baseurl = baseurl.replace(/[^\/]+$/, ''); 2671 var basere = new RegExp(baseurl); 2672 string = string.replace(basere, ""); 2673 2674 // strip host-part of URL which is added by MSIE to links relative to server root 2675 baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1'); 2676 basere = new RegExp(baseurl); 2677 return string.replace(basere, ""); 2678 }; 2679 2680 String.prototype.trim = function() { 2681 a = this.replace(/^\s+/, ''); 2682 return a.replace(/\s+$/, ''); 2683 }; 2684 2685 // creates a rgb-style color from a number 2686 HTMLArea._makeColor = function(v) { 2687 if (typeof v != "number") { 2688 // already in rgb (hopefully); IE doesn't get here. 2689 return v; 2690 } 2691 // IE sends number; convert to rgb. 2692 var r = v & 0xFF; 2693 var g = (v >> 8) & 0xFF; 2694 var b = (v >> 16) & 0xFF; 2695 return "rgb(" + r + "," + g + "," + b + ")"; 2696 }; 2697 2698 // returns hexadecimal color representation from a number or a rgb-style color. 2699 HTMLArea._colorToRgb = function(v) { 2700 if (!v) 2701 return ''; 2702 2703 // returns the hex representation of one byte (2 digits) 2704 function hex(d) { 2705 return (d < 16) ? ("0" + d.toString(16)) : d.toString(16); 2706 }; 2707 2708 if (typeof v == "number") { 2709 // we're talking to IE here 2710 var r = v & 0xFF; 2711 var g = (v >> 8) & 0xFF; 2712 var b = (v >> 16) & 0xFF; 2713 return "#" + hex(r) + hex(g) + hex(b); 2714 } 2715 2716 if (v.substr(0, 3) == "rgb") { 2717 // in rgb(...) form -- Mozilla 2718 var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/; 2719 if (v.match(re)) { 2720 var r = parseInt(RegExp.$1); 2721 var g = parseInt(RegExp.$2); 2722 var b = parseInt(RegExp.$3); 2723 return "#" + hex(r) + hex(g) + hex(b); 2724 } 2725 // doesn't match RE?! maybe uses percentages or float numbers 2726 // -- FIXME: not yet implemented. 2727 return null; 2728 } 2729 2730 if (v.substr(0, 1) == "#") { 2731 // already hex rgb (hopefully :D ) 2732 return v; 2733 } 2734 2735 // if everything else fails ;) 2736 return null; 2737 }; 2738 2739 HTMLArea.prototype._popupDialog = function(url, action, init) { 2740 Dialog(this.popupURL(url), action, init); 2741 }; 2742 2743 // paths 2744 2745 HTMLArea.prototype.imgURL = function(file, plugin) { 2746 if (typeof plugin == "undefined") 2747 return _editor_url + file; 2748 else 2749 return _editor_url + "plugins/" + plugin + "/img/" + file; 2750 }; 2751 2752 HTMLArea.prototype.popupURL = function(file) { 2753 var url = ""; 2754 if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) { 2755 var plugin = RegExp.$1; 2756 var popup = RegExp.$2; 2757 if (!/\.html$/.test(popup)) 2758 popup += ".html"; 2759 url = _editor_url + "plugins/" + plugin + "/popups/" + popup; 2760 } else 2761 url = _editor_url + this.config.popupURL + file; 2762 return url; 2763 }; 2764 2765 /** 2766 * FIX: Internet Explorer returns an item having the _name_ equal to the given 2767 * id, even if it's not having any id. This way it can return a different form 2768 * field even if it's not a textarea. This workarounds the problem by 2769 * specifically looking to search only elements having a certain tag name. 2770 */ 2771 HTMLArea.getElementById = function(tag, id) { 2772 var el, i, objs = document.getElementsByTagName(tag); 2773 for (i = objs.length; --i >= 0 && (el = objs[i]);) 2774 if (el.id == id) 2775 return el; 2776 return null; 2777 }; 2778 // Modified version of GetHtml plugin's indent. 2779 HTMLArea.indent = function(s, sindentChar) { 2780 var c = [ 2781 /*0*/ new RegExp().compile(/<\/?(div|p|h[1-6]|table|tr|td|th|ul|ol|li|blockquote|object|br|hr|img|embed|param|pre|script|html|head|body|meta|link|title|area)[^>]*>/g), 2782 /*1*/ new RegExp().compile(/<\/(div|p|h[1-6]|table|tr|td|th|ul|ol|li|blockquote|object|html|head|body|script)( [^>]*)?>/g),//blocklevel closing tag 2783 /*2*/ new RegExp().compile(/<(div|p|h[1-6]|table|tr|td|th|ul|ol|li|blockquote|object|html|head|body|script)( [^>]*)?>/g),//blocklevel opening tag 2784 /*3*/ new RegExp().compile(/<(br|hr|img|embed|param|pre|meta|link|title|area)[^>]*>/g),//singlet tag 2785 /*4*/ new RegExp().compile(/(^|<\/(pre|script)>)(\s|[^\s])*?(<(pre|script)[^>]*>|$)/g),//find content NOT inside pre and script tags 2786 /*5*/ new RegExp().compile(/(<pre[^>]*>)(\s|[^\s])*?(<\/pre>)/g),//find content inside pre tags 2787 /*6*/ new RegExp().compile(/(^|<!--(\s|\S)*?-->)((\s|\S)*?)(?=<!--(\s|\S)*?-->|$)/g),//find content NOT inside comments 2788 /*7*/ new RegExp().compile(/<\/(table|tbody|tr|td|th|ul|ol|object|html|head|body)( [^>]*)?>/g),//blocklevel closing tag 2789 ]; 2790 HTMLArea.__nindent = 0; 2791 HTMLArea.__sindent = ""; 2792 HTMLArea.__sindentChar = (typeof sindentChar == "undefined") ? " " : sindentChar; 2793 2794 if(HTMLArea.is_gecko) { //moz changes returns into <br> inside <pre> tags 2795 s = s.replace(c[5], function(str){return str.replace(/<br \/>/g,"\n")}); 2796 } 2797 s = s.replace(c[4], function(strn) { //skip pre and script tags 2798 strn = strn.replace(c[6], function(st,$1,$2,$3) { //exclude comments 2799 string = $3.replace(/[\n\r]/gi, " ").replace(/\s+/gi," ").replace(c[0], function(str) { 2800 if (str.match(c[2])) { 2801 var s = "\n" + HTMLArea.__sindent + str; 2802 // blocklevel openingtag - increase indent 2803 HTMLArea.__sindent += HTMLArea.__sindentChar; 2804 ++HTMLArea.__nindent; 2805 return s; 2806 } else if (str.match(c[1])) { 2807 // blocklevel closingtag - decrease indent 2808 --HTMLArea.__nindent; 2809 HTMLArea.__sindent = ""; 2810 for (var i=HTMLArea.__nindent;i>0;--i) { 2811 HTMLArea.__sindent += HTMLArea.__sindentChar; 2812 } 2813 return (str.match(c[7]) ? "\n" + HTMLArea.__sindent : "") + str; 2814 } 2815 return str; // this won't actually happen 2816 }); 2817 return $1 + string; 2818 });return strn; 2819 }); 2820 if (s.charAt(0) == "\n") { 2821 return s.substring(1, s.length); 2822 } 2823 s = s.replace(/ *\n/g,'\n');//strip spaces at end of lines 2824 return s; 2825 };
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |