glossary->id for block is now properly recoded by restore. MDL-4934 ; merged from...
[moodle-linuxchix.git] / lib / editor / htmlarea / htmlarea.php
blob83aaf533b272267b387bc9a725d6e2ea6ce4f4ce
1 <?php
2 include("../../../config.php");
3 require_once($CFG->dirroot.'/lib/languages.php');
5 $id = optional_param('id', SITEID, PARAM_INT);
6 $httpsrequired = optional_param('httpsrequired', 0, PARAM_BOOL); //flag indicating editor on page with required https
8 require_course_login($id);
10 $lastmodified = filemtime("htmlarea.php");
11 $lifetime = 1800;
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 //}
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: ");
26 $lang = current_language();
28 if (empty($lang)) {
29 $lang = "en";
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/';
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');
48 // htmlArea v3.0 - Copyright (c) 2002, 2003 interactivetools.com, inc.
49 // This copyright notice MUST stay intact for use (see license.txt).
51 // Portions (c) dynarch.com, 2003-2004
53 // A free WYSIWYG editor replacement for <textarea> fields.
54 // For full source code and docs, visit http://www.interactivetools.com/
56 // Version 3.0 developed by Mihai Bazon.
57 // http://dynarch.com/mishoo
59 // $Id$
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
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";
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;
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
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);
112 })();
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;
120 HTMLArea.Config = function () {
121 this.version = "3.0";
123 this.width = "auto";
124 this.height = "auto";
126 // enable creation of a status bar?
127 this.statusBar = true;
129 // maximum size of the undo queue
130 this.undoSteps = 20;
132 // the time interval at which undo samples are taken
133 this.undoTimeout = 500; // 1/2 sec.
135 // the next parameter specifies whether the toolbar should be included
136 // in the size or not.
137 this.sizeIncludesToolbar = true;
139 // if true then HTMLArea will retrieve the full HTML, starting with the
140 // <HTML> tag.
141 this.fullPage = false;
143 // style included in the iframe document
144 this.pageStyle = "body { background-color: #fff; font-family: 'Times New Roman', Times; } \n .lang { background-color: #dee; }";
146 // set to true if you want Word code to be cleaned upon Paste
147 this.killWordOnPaste = true;
149 // BaseURL included in the iframe document
150 this.baseURL = document.baseURI || document.URL;
151 if (this.baseURL && this.baseURL.match(/(.*)\/([^\/]+)/))
152 this.baseURL = RegExp.$1 + "/";
154 // URL-s
155 this.imgURL = "images/";
156 this.popupURL = "popups/";
158 this.toolbar = [
159 [ "fontname", "space",
160 "fontsize", "space",
161 "formatblock", "space",
162 "language", "space",
163 "bold", "italic", "underline", "strikethrough", "separator",
164 "subscript", "superscript", "separator",
165 "clean", "separator", "undo", "redo" ],
167 [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator",
168 "lefttoright", "righttoleft", "separator",
169 "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator",
170 "forecolor", "hilitecolor", "separator",
171 "inserthorizontalrule", "createanchor", "createlink", "unlink", "nolink", "separator",
172 "insertimage", "inserttable",
173 "insertsmile", "insertchar", "search_replace",
174 <?php if (!empty($CFG->aspellpath) && file_exists($CFG->aspellpath) && !empty($CFG->editorspelling)) {
175 echo '"separator","spellcheck",';
176 } ?>
177 "separator", "htmlmode", "separator", "popupeditor"]
180 this.fontname = {
181 "Arial": 'arial,helvetica,sans-serif',
182 "Courier New": 'courier new,courier,monospace',
183 "Georgia": 'georgia,times new roman,times,serif',
184 "Tahoma": 'tahoma,arial,helvetica,sans-serif',
185 "Times New Roman": 'times new roman,times,serif',
186 "Verdana": 'verdana,arial,helvetica,sans-serif',
187 "Impact": 'impact',
188 "WingDings": 'wingdings'
191 this.fontsize = {
192 "1 (8 pt)": "1",
193 "2 (10 pt)": "2",
194 "3 (12 pt)": "3",
195 "4 (14 pt)": "4",
196 "5 (18 pt)": "5",
197 "6 (24 pt)": "6",
198 "7 (36 pt)": "7"
201 this.formatblock = {
202 "":"",
203 "<?php echo $strheading ?> 1": "h1",
204 "<?php echo $strheading ?> 2": "h2",
205 "<?php echo $strheading ?> 3": "h3",
206 "<?php echo $strheading ?> 4": "h4",
207 "<?php echo $strheading ?> 5": "h5",
208 "<?php echo $strheading ?> 6": "h6",
209 "<?php echo $strnormal ?>": "p",
210 "<?php echo $straddress ?>": "address",
211 "<?php echo $strpreformatted ?>": "pre"
214 this.language = {
215 "<?php echo $strlang; ?>":"",
216 <?php
217 $strlangarray = '';
218 foreach ($LANGUAGES as $key => $name) {
219 $key = str_replace('_', '-', $key);
220 $strlangarray .= '"'.$key.'": "'.$key.'",';
222 $strlangarray .= '"'.$strmulti.'": "multi",';
224 foreach ($LANGUAGES as $key => $name) {
225 $key = str_replace('_', '-', $key);
226 $strlangarray .= '"'.$key.' ": "'.$key.'_ML",';
228 $strlangarray = substr($strlangarray, 0, -1);
229 echo $strlangarray;
233 this.customSelects = {};
235 function cut_copy_paste(e, cmd, obj) {
236 e.execCommand(cmd);
239 this.btnList = {
240 bold: [ "Bold", "ed_format_bold.gif", false, function(e) {e.execCommand("bold");} ],
241 italic: [ "Italic", "ed_format_italic.gif", false, function(e) {e.execCommand("italic");} ],
242 underline: [ "Underline", "ed_format_underline.gif", false, function(e) {e.execCommand("underline");} ],
243 strikethrough: [ "Strikethrough", "ed_format_strike.gif", false, function(e) {e.execCommand("strikethrough");} ],
244 subscript: [ "Subscript", "ed_format_sub.gif", false, function(e) {e.execCommand("subscript");} ],
245 superscript: [ "Superscript", "ed_format_sup.gif", false, function(e) {e.execCommand("superscript");} ],
246 justifyleft: [ "Justify Left", "ed_align_left.gif", false, function(e) {e.execCommand("justifyleft");} ],
247 justifycenter: [ "Justify Center", "ed_align_center.gif", false, function(e) {e.execCommand("justifycenter");} ],
248 justifyright: [ "Justify Right", "ed_align_right.gif", false, function(e) {e.execCommand("justifyright");} ],
249 justifyfull: [ "Justify Full", "ed_align_justify.gif", false, function(e) {e.execCommand("justifyfull");} ],
250 insertorderedlist: [ "Ordered List", "ed_list_num.gif", false, function(e) {e.execCommand("insertorderedlist");} ],
251 insertunorderedlist: [ "Bulleted List", "ed_list_bullet.gif", false, function(e) {e.execCommand("insertunorderedlist");} ],
252 outdent: [ "Decrease Indent", "ed_indent_less.gif", false, function(e) {e.execCommand("outdent");} ],
253 indent: [ "Increase Indent", "ed_indent_more.gif", false, function(e) {e.execCommand("indent");} ],
254 forecolor: [ "Font Color", "ed_color_fg.gif", false, function(e) {e.execCommand("forecolor");} ],
255 hilitecolor: [ "Background Color", "ed_color_bg.gif", false, function(e) {e.execCommand("hilitecolor");} ],
256 inserthorizontalrule: [ "Horizontal Rule", "ed_hr.gif", false, function(e) {e.execCommand("inserthorizontalrule");} ],
257 createanchor: [ "Create anchor", "ed_anchor.gif", false, function(e) {e.execCommand("createanchor", true);} ],
258 createlink: [ "Insert Web Link", "ed_link.gif", false, function(e) {e.execCommand("createlink", true);} ],
259 unlink: [ "Remove Link", "ed_unlink.gif", false, function(e) {e.execCommand("unlink");} ],
260 nolink: [ "No link", "ed_nolink.gif", false, function(e) {e.execCommand("nolink");} ],
261 insertimage: [ "Insert/Modify Image", "ed_image.gif", false, function(e) {e.execCommand("insertimage");} ],
262 inserttable: [ "Insert Table", "insert_table.gif", false, function(e) {e.execCommand("inserttable");} ],
263 htmlmode: [ "Toggle HTML Source", "ed_html.gif", true, function(e) {e.execCommand("htmlmode");} ],
264 popupeditor: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e) {e.execCommand("popupeditor");} ],
265 about: [ "About this editor", "ed_about.gif", true, function(e) {e.execCommand("about");} ],
266 showhelp: [ "Help using editor", "ed_help.gif", true, function(e) {e.execCommand("showhelp");} ],
267 undo: [ "Undoes your last action", "ed_undo.gif", false, function(e) {e.execCommand("undo");} ],
268 redo: [ "Redoes your last action", "ed_redo.gif", false, function(e) {e.execCommand("redo");} ],
269 clean: [ "Clean Word HTML", "ed_wordclean.gif", false, function(e) {e.execCommand("killword"); }],
270 lefttoright: [ "Direction left to right", "ed_left_to_right.gif", false, function(e) {e.execCommand("lefttoright");} ],
271 righttoleft: [ "Direction right to left", "ed_right_to_left.gif", false, function(e) {e.execCommand("righttoleft");} ],
272 <?php if (!empty($CFG->aspellpath) && file_exists($CFG->aspellpath) && !empty($CFG->editorspelling)) {
273 echo 'spellcheck: ["Spell-check", "spell-check.gif", false, spellClickHandler ],'."\n";
275 insertsmile: ["Insert Smiley", "em.icon.smile.gif", false, function(e) {e.execCommand("insertsmile");} ],
276 insertchar: [ "Insert Char", "icon_ins_char.gif", false, function(e) {e.execCommand("insertchar");} ],
277 search_replace: [ "Search and replace", "ed_replace.gif", false, function(e) {e.execCommand("searchandreplace");} ]
280 // initialize tooltips from the I18N module and generate correct image path
281 for (var i in this.btnList) {
282 var btn = this.btnList[i];
283 btn[1] = _editor_url + this.imgURL + btn[1];
284 if (typeof HTMLArea.I18N.tooltips[i] != "undefined") {
285 btn[0] = HTMLArea.I18N.tooltips[i];
290 HTMLArea.Config.prototype.registerButton = function(id, tooltip, image, textMode, action, context) {
291 var the_id;
292 if (typeof id == "string") {
293 the_id = id;
294 } else if (typeof id == "object") {
295 the_id = id.id;
296 } else {
297 alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
298 return false;
300 // check for existing id
301 if (typeof this.customSelects[the_id] != "undefined") {
302 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
304 if (typeof this.btnList[the_id] != "undefined") {
305 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
307 switch (typeof id) {
308 case "string": this.btnList[id] = [ tooltip, image, textMode, action, context ]; break;
309 case "object": this.btnList[id.id] = [ id.tooltip, id.image, id.textMode, id.action, id.context ]; break;
313 HTMLArea.Config.prototype.registerDropdown = function(object) {
314 // check for existing id
315 if (typeof this.customSelects[object.id] != "undefined") {
316 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
318 if (typeof this.btnList[object.id] != "undefined") {
319 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
321 this.customSelects[object.id] = object;
324 HTMLArea.Config.prototype.hideSomeButtons = function(remove) {
325 var toolbar = this.toolbar;
326 for (var i in toolbar) {
327 var line = toolbar[i];
328 for (var j = line.length; --j >= 0; ) {
329 if (remove.indexOf(" " + line[j] + " ") >= 0) {
330 var len = 1;
331 if (/separator|space/.test(line[j + 1])) {
332 len = 2;
334 line.splice(j, len);
340 /** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
341 HTMLArea.replaceAll = function(config) {
342 var tas = document.getElementsByTagName("textarea");
343 for (var i = tas.length; i > 0; (new HTMLArea(tas[--i], config)).generate());
346 /** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
347 HTMLArea.replace = function(id, config) {
348 var ta = HTMLArea.getElementById("textarea", id);
349 return ta ? (new HTMLArea(ta, config)).generate() : null;
352 // Creates the toolbar and appends it to the _htmlarea
353 HTMLArea.prototype._createToolbar = function () {
354 var editor = this; // to access this in nested functions
356 var toolbar = document.createElement("div");
357 this._toolbar = toolbar;
358 toolbar.className = "toolbar";
359 toolbar.unselectable = "1";
360 var tb_row = null;
361 var tb_objects = new Object();
362 this._toolbarObjects = tb_objects;
364 // creates a new line in the toolbar
365 function newLine() {
366 var table = document.createElement("table");
367 table.border = "0px";
368 table.cellSpacing = "0px";
369 table.cellPadding = "0px";
370 toolbar.appendChild(table);
371 // TBODY is required for IE, otherwise you don't see anything
372 // in the TABLE.
373 var tb_body = document.createElement("tbody");
374 table.appendChild(tb_body);
375 tb_row = document.createElement("tr");
376 tb_body.appendChild(tb_row);
377 }; // END of function: newLine
378 // init first line
379 newLine();
381 function setButtonStatus(id, newval) {
382 var oldval = this[id];
383 var el = this.element;
384 if (oldval != newval) {
385 switch (id) {
386 case "enabled":
387 if (newval) {
388 HTMLArea._removeClass(el, "buttonDisabled");
389 el.disabled = false;
390 } else {
391 HTMLArea._addClass(el, "buttonDisabled");
392 el.disabled = true;
394 break;
395 case "active":
396 if (newval) {
397 HTMLArea._addClass(el, "buttonPressed");
398 } else {
399 HTMLArea._removeClass(el, "buttonPressed");
401 break;
403 this[id] = newval;
405 }; // END of function: setButtonStatus
407 function createSelect(txt) {
408 var options = null;
409 var el = null;
410 var cmd = null;
411 var customSelects = editor.config.customSelects;
412 var context = null;
413 switch (txt) {
414 case "fontsize":
415 case "fontname":
416 case "formatblock":
417 case "language":
418 options = editor.config[txt];
419 cmd = txt;
420 break;
421 default:
422 // try to fetch it from the list of registered selects
423 cmd = txt;
424 var dropdown = customSelects[cmd];
425 if (typeof dropdown != "undefined") {
426 options = dropdown.options;
427 context = dropdown.context;
428 } else {
429 alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
431 break;
433 if (options) {
434 el = document.createElement("select");
435 var obj = {
436 name : txt, // field name
437 element : el, // the UI element (SELECT)
438 enabled : true, // is it enabled?
439 text : false, // enabled in text mode?
440 cmd : cmd, // command ID
441 state : setButtonStatus, // for changing state
442 context : context
444 tb_objects[txt] = obj;
445 for (var i in options) {
446 var op = document.createElement("option");
447 op.appendChild(document.createTextNode(i));
448 op.value = options[i];
449 el.appendChild(op);
451 HTMLArea._addEvent(el, "change", function () {
452 editor._comboSelected(el, txt);
455 editor.dropdowns[txt] = el; // Keep track of the element for keyboard
456 // access later.
457 return el;
458 }; // END of function: createSelect
460 // appends a new button to toolbar
461 function createButton(txt) {
462 // the element that will be created
463 var el = null;
464 var btn = null;
465 switch (txt) {
466 case "separator":
467 el = document.createElement("div");
468 el.className = "separator";
469 break;
470 case "space":
471 el = document.createElement("div");
472 el.className = "space";
473 break;
474 case "linebreak":
475 newLine();
476 return false;
477 case "textindicator":
478 el = document.createElement("div");
479 el.appendChild(document.createTextNode("A"));
480 el.className = "indicator";
481 el.title = HTMLArea.I18N.tooltips.textindicator;
482 var obj = {
483 name : txt, // the button name (i.e. 'bold')
484 element : el, // the UI element (DIV)
485 enabled : true, // is it enabled?
486 active : false, // is it pressed?
487 text : false, // enabled in text mode?
488 cmd : "textindicator", // the command ID
489 state : setButtonStatus // for changing state
491 tb_objects[txt] = obj;
492 break;
493 default:
494 btn = editor.config.btnList[txt];
496 if (!el && btn) {
497 el = document.createElement("div");
498 el.title = btn[0];
499 el.className = "button";
500 // let's just pretend we have a button object, and
501 // assign all the needed information to it.
502 var obj = {
503 name : txt, // the button name (i.e. 'bold')
504 element : el, // the UI element (DIV)
505 enabled : true, // is it enabled?
506 active : false, // is it pressed?
507 text : btn[2], // enabled in text mode?
508 cmd : btn[3], // the command ID
509 state : setButtonStatus, // for changing state
510 context : btn[4] || null // enabled in a certain context?
512 tb_objects[txt] = obj;
513 // handlers to emulate nice flat toolbar buttons
514 HTMLArea._addEvent(el, "mouseover", function () {
515 if (obj.enabled) {
516 HTMLArea._addClass(el, "buttonHover");
519 HTMLArea._addEvent(el, "mouseout", function () {
520 if (obj.enabled) with (HTMLArea) {
521 _removeClass(el, "buttonHover");
522 _removeClass(el, "buttonActive");
523 (obj.active) && _addClass(el, "buttonPressed");
526 HTMLArea._addEvent(el, "mousedown", function (ev) {
527 if (obj.enabled) with (HTMLArea) {
528 _addClass(el, "buttonActive");
529 _removeClass(el, "buttonPressed");
530 _stopEvent(is_ie ? window.event : ev);
533 // when clicked, do the following:
534 HTMLArea._addEvent(el, "click", function (ev) {
535 if (obj.enabled) with (HTMLArea) {
536 _removeClass(el, "buttonActive");
537 _removeClass(el, "buttonHover");
538 obj.cmd(editor, obj.name, obj);
539 _stopEvent(is_ie ? window.event : ev);
542 var img = document.createElement("img");
543 img.src = btn[1];
544 img.style.width = "18px";
545 img.style.height = "18px";
546 el.appendChild(img);
547 } else if (!el) {
548 el = createSelect(txt);
550 if (el) {
551 var tb_cell = document.createElement("td");
552 tb_row.appendChild(tb_cell);
553 tb_cell.appendChild(el);
554 } else {
555 alert("FIXME: Unknown toolbar item: " + txt);
557 return el;
560 var first = true;
561 for (var i in this.config.toolbar) {
562 if (this.config.toolbar.propertyIsEnumerable(i)) { // fix for prototype.js compatibility
563 if (!first) {
564 createButton("linebreak");
565 } else {
566 first = false;
568 var group = this.config.toolbar[i];
569 for (var j in group) {
570 if (group.propertyIsEnumerable(j)) { // fix for prototype.js compatibility
571 var code = group[j];
572 if (/^([IT])\[(.*?)\]/.test(code)) {
573 // special case, create text label
574 var l7ed = RegExp.$1 == "I"; // localized?
575 var label = RegExp.$2;
576 if (l7ed) {
577 label = HTMLArea.I18N.custom[label];
579 var tb_cell = document.createElement("td");
580 tb_row.appendChild(tb_cell);
581 tb_cell.className = "label";
582 tb_cell.innerHTML = label;
583 } else {
584 createButton(code);
591 this._htmlArea.appendChild(toolbar);
594 HTMLArea.prototype._createStatusBar = function() {
595 var statusbar = document.createElement("div");
596 statusbar.className = "statusBar";
597 this._htmlArea.appendChild(statusbar);
598 this._statusBar = statusbar;
599 // statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
600 // creates a holder for the path view
601 div = document.createElement("span");
602 div.className = "statusBarTree";
603 div.innerHTML = HTMLArea.I18N.msg["Path"] + ": ";
604 this._statusBarTree = div;
605 this._statusBar.appendChild(div);
606 if (!this.config.statusBar) {
607 // disable it...
608 statusbar.style.display = "none";
612 // Creates the HTMLArea object and replaces the textarea with it.
613 HTMLArea.prototype.generate = function () {
614 var editor = this; // we'll need "this" in some nested functions
616 // get the textarea
617 var textarea = this._textArea;
618 if (typeof textarea == "string") {
619 // it's not element but ID
620 this._textArea = textarea = HTMLArea.getElementById("textarea", textarea);
622 // Fix for IE's sticky bug. Editor doesn't load
623 // editing area.
624 var height;
625 if ( textarea.offsetHeight && textarea.offsetHeight > 0 ) {
626 height = textarea.offsetHeight;
627 } else {
628 height = 300;
630 this._ta_size = {
631 w: textarea.offsetWidth,
632 h: height
634 textarea.style.display = "none";
636 // create the editor framework
637 var htmlarea = document.createElement("div");
638 htmlarea.className = "htmlarea";
639 this._htmlArea = htmlarea;
641 // insert the editor before the textarea.
642 //Bug fix - unless the textarea is nested within its label, in which case insert editor before label.
643 if (textarea.parentNode.nodeName.toLowerCase()=='label') {
644 textarea.parentNode.parentNode.insertBefore(htmlarea,textarea.parentNode);
646 else {
647 textarea.parentNode.insertBefore(htmlarea, textarea);
650 if (textarea.form) {
651 // we have a form, on submit get the HTMLArea content and
652 // update original textarea.
653 var f = textarea.form;
654 if (typeof f.onsubmit == "function") {
655 var funcref = f.onsubmit;
656 if (typeof f.__msh_prevOnSubmit == "undefined") {
657 f.__msh_prevOnSubmit = [];
659 f.__msh_prevOnSubmit.push(funcref);
661 f.onsubmit = function() {
662 // Moodle hack. Bug fix #2736
663 var test = editor.getHTML();
664 test = test.replace(/<br \/>/gi, '');
665 test = test.replace(/\&nbsp\;/gi, '');
666 test = test.trim();
667 //alert(test + test.length);
668 if (test.length < 1) {
669 editor._textArea.value = test.trim();
670 } else {
671 editor._textArea.value = editor.getHTML();
673 // Moodle hack end.
674 var a = this.__msh_prevOnSubmit;
675 var ret = true;
676 // call previous submit methods if they were there.
677 if (typeof a != "undefined") {
678 for (var i = a.length; --i >= 0;) {
679 ret = a[i]() && ret;
682 return ret;
684 if (typeof f.onreset == "function") {
685 var funcref = f.onreset;
686 if (typeof f.__msh_prevOnReset == "undefined") {
687 f.__msh_prevOnReset = [];
689 f.__msh_prevOnReset.push(funcref);
691 f.onreset = function() {
692 editor.setHTML(editor._textArea.value);
693 editor.updateToolbar();
694 var a = this.__msh_prevOnReset;
695 // call previous reset methods if they were there.
696 if (typeof a != "undefined") {
697 for (var i = a.length; --i >= 0;) {
698 a[i]();
704 // add a handler for the "back/forward" case -- on body.unload we save
705 // the HTML content into the original textarea.
706 try {
707 window.onunload = function() {
708 editor._textArea.value = editor.getHTML();
710 } catch(e) {};
712 // creates & appends the toolbar
713 this._createToolbar();
715 // create the IFRAME
716 var iframe = document.createElement("iframe");
718 iframe.src = "about:blank";
720 iframe.className = "iframe";
722 htmlarea.appendChild(iframe);
724 var editor = this
725 editor._iframe = iframe;
726 var doc = editor._iframe.contentWindow.document;
727 editor._doc = doc;
729 // Generate iframe content
730 var html = ""
731 if (!editor.config.fullPage) {
732 html = "<html>\n";
733 html += "<head>\n";
734 html += '<meta http-equiv="content-type" content="text/html; charset=utf-8" />\n';
735 if (editor.config.baseURL)
736 html += '<base href="' + editor.config.baseURL + '" />';
737 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
738 html += "</head>\n";
739 html += '<body>\n';
740 html += editor._textArea.value;
741 html = html.replace(/<nolink>/gi, '<span class="nolink">').
742 replace(/<\/nolink>/gi, '</span>');
743 html += "</body>\n";
744 html += "</html>";
745 } else {
746 html = editor._textArea.value;
747 if (html.match(HTMLArea.RE_doctype)) {
748 editor.setDoctype(RegExp.$1);
749 html = html.replace(HTMLArea.RE_doctype, "");
753 // Write content to iframe
754 doc.open();
755 doc.write(html);
756 doc.close();
758 // The magic: onClick the designMode is set to 'on'
759 // This one is for click on HTMLarea toolbar and else
760 if(HTMLArea.is_gecko) {
761 HTMLArea._addEvents(
762 this._htmlArea,
763 ["mousedown"],
764 function(event) {
765 if(editor.designModeIsOn != true)
767 editor.designModeIsOn = true;
768 try {
769 doc.designMode = "on";
770 } catch (e) {
771 alert(e)
777 // This one is for click in iframe
778 HTMLArea._addEvents(
779 editor._iframe.contentWindow,
780 ["mousedown"],
781 function(event) {
782 if(editor.designModeIsOn != true)
784 editor.designModeIsOn = true;
785 try {
786 doc.designMode = "on";
787 } catch (e) {
788 alert(e)
794 // creates & appends the status bar, if the case
795 this._createStatusBar();
797 // remove the default border as it keeps us from computing correctly
798 // the sizes. (somebody tell me why doesn't this work in IE)
800 if (!HTMLArea.is_ie) {
801 iframe.style.borderWidth = "1px";
804 // size the IFRAME according to user's prefs or initial textarea
805 var height = (this.config.height == "auto" ? (this._ta_size.h) : this.config.height);
806 height = parseInt(height);
807 var width = (this.config.width == "auto" ? (this._toolbar.offsetWidth) : this.config.width);
808 width = (width == 0 ? 598 : width);
809 //width = Math.max(parseInt(width), 598);
811 width = String(width);
812 if (width.match(/^\d+$/)) { // is this a pure int? if so, let it be in px, and remove 2px
813 height -= 2;
814 width -= 2;
815 width=width+"px";
818 iframe.style.width = width;
820 if (this.config.sizeIncludesToolbar) {
821 // substract toolbar height
822 height -= this._toolbar.offsetHeight;
823 height -= this._statusBar.offsetHeight;
825 if (height < 0) {
826 height = 0;
828 iframe.style.height = height + "px";
830 // the editor including the toolbar now have the same size as the
831 // original textarea.. which means that we need to reduce that a bit.
832 textarea.style.width = iframe.style.width;
833 textarea.style.height = iframe.style.height;
835 if (HTMLArea.is_ie) {
836 doc.body.contentEditable = true;
839 // intercept some events; for updating the toolbar & keyboard handlers
840 HTMLArea._addEvents
841 (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
842 function (event) {
843 return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
846 // check if any plugins have registered refresh handlers
847 for (var i in editor.plugins) {
848 var plugin = editor.plugins[i].instance;
849 if (typeof plugin.onGenerate == "function") {
850 plugin.onGenerate();
852 if (typeof plugin.onGenerateOnce == "function") {
853 plugin.onGenerateOnce();
854 plugin.onGenerateOnce = null;
858 // Moodle fix for bug Bug #2521 Too long statusbar line in IE
860 //setTimeout(function() {
861 // editor.updateToolbar();
862 //}, 250);
864 if (typeof editor.onGenerate == "function") {
865 editor.onGenerate();
870 // Switches editor mode; parameter can be "textmode" or "wysiwyg". If no
871 // parameter was passed this function toggles between modes.
872 HTMLArea.prototype.setMode = function(mode) {
873 if (typeof mode == "undefined") {
874 mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode");
876 switch (mode) {
877 case "textmode":
878 this._textArea.value = this.getHTML();
879 this._iframe.style.display = "none";
880 this._textArea.style.display = "block";
881 if (this.config.statusBar) {
882 while(this._statusBar.childNodes.length>0) {
883 this._statusBar.removeChild(this._statusBar.childNodes[0]);
886 this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["TEXT_MODE"]));
888 break;
889 case "wysiwyg":
890 if (HTMLArea.is_gecko) {
891 // disable design mode before changing innerHTML
892 try {
893 this._doc.designMode = "off";
894 } catch(e) {};
896 if (!this.config.fullPage)
897 this._doc.body.innerHTML = this.getHTML();
898 else
899 this.setFullHTML(this.getHTML());
900 this._iframe.style.display = "block";
901 this._textArea.style.display = "none";
902 if (HTMLArea.is_gecko) {
903 // we need to refresh that info for Moz-1.3a
904 try {
905 this._doc.designMode = "on";
906 //this._doc.focus();
907 } catch(e) {};
909 if (this.config.statusBar) {
910 this._statusBar.innerHTML = '';
911 this._statusBar.appendChild(this._statusBarTree);
913 break;
914 default:
915 alert("Mode <" + mode + "> not defined!");
916 return false;
918 this._editMode = mode;
919 this.focusEditor();
922 HTMLArea.prototype.setFullHTML = function(html) {
923 var save_multiline = RegExp.multiline;
924 RegExp.multiline = true;
925 if (html.match(HTMLArea.RE_doctype)) {
926 this.setDoctype(RegExp.$1);
927 html = html.replace(HTMLArea.RE_doctype, "");
929 RegExp.multiline = save_multiline;
930 if (!HTMLArea.is_ie) {
931 if (html.match(HTMLArea.RE_head))
932 this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
933 if (html.match(HTMLArea.RE_body))
934 this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
935 } else {
936 var html_re = /<html>((.|\n)*?)<\/html>/i;
937 html = html.replace(html_re, "$1");
938 this._doc.open();
939 this._doc.write(html);
940 this._doc.close();
941 this._doc.body.contentEditable = true;
942 return true;
946 // Category: PLUGINS
948 HTMLArea.prototype.registerPlugin2 = function(plugin, args) {
949 if (typeof plugin == "string")
950 plugin = eval(plugin);
951 var obj = new plugin(this, args);
952 if (obj) {
953 var clone = {};
954 var info = plugin._pluginInfo;
955 for (var i in info)
956 clone[i] = info[i];
957 clone.instance = obj;
958 clone.args = args;
959 this.plugins[plugin._pluginInfo.name] = clone;
960 } else
961 alert("Can't register plugin " + plugin.toString() + ".");
964 // Create the specified plugin and register it with this HTMLArea
965 HTMLArea.prototype.registerPlugin = function() {
966 var plugin = arguments[0];
967 var args = [];
968 for (var i = 1; i < arguments.length; ++i)
969 args.push(arguments[i]);
970 this.registerPlugin2(plugin, args);
973 HTMLArea.loadPlugin = function(pluginName) {
974 var dir = _editor_url + "plugins/" + pluginName;
975 var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g,
976 function (str, l1, l2, l3) {
977 return l1 + "-" + l2.toLowerCase() + l3;
978 }).toLowerCase() + ".js";
979 var plugin_file = dir + "/" + plugin;
980 var plugin_lang = dir + "/lang/" + HTMLArea.I18N.lang + ".js";
981 HTMLArea._scripts.push(plugin_file, plugin_lang);
982 document.write("<script type='text/javascript' src='" + plugin_file + "'></script>");
983 document.write("<script type='text/javascript' src='" + plugin_lang + "'></script>");
986 HTMLArea.loadStyle = function(style, plugin) {
987 var url = _editor_url || '';
988 if (typeof plugin != "undefined") {
989 url += "plugins/" + plugin + "/";
991 url += style;
992 document.write("<style type='text/css'>@import url(" + url + ");</style>");
994 HTMLArea.loadStyle("htmlarea.css");
996 // Category: EDITOR UTILITIES
998 // The following function is a slight variation of the word cleaner code posted
999 // by Weeezl (user @ InteractiveTools forums).
1000 HTMLArea.prototype._wordClean = function() {
1001 var D = this.getInnerHTML();
1002 if (D.indexOf("class=Mso") >= 0 || D.indexOf("mso") >= 0 || D.indexOf("Mso") >= 0) {
1004 // make one line
1005 D = D.replace(/\r\n/g, '\[br\]').
1006 replace(/\n/g, '').
1007 replace(/\r/g, '').
1008 replace(/\&nbsp\;/g,' ');
1010 // keep tags, strip attributes
1011 D = D.replace(/ class=[^\s|>]*/gi,'').
1012 //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">').
1013 replace(/ style=\"[^>]*\"/gi,'').
1014 replace(/ align=[^\s|>]*/gi,'');
1016 //clean up tags
1017 D = D.replace(/<b [^>]*>/gi,'<b>').
1018 replace(/<i [^>]*>/gi,'<i>').
1019 replace(/<li [^>]*>/gi,'<li>').
1020 replace(/<ul [^>]*>/gi,'<ul>');
1022 // replace outdated tags
1023 D = D.replace(/<b>/gi,'<strong>').
1024 replace(/<\/b>/gi,'</strong>');
1026 // mozilla doesn't like <em> tags
1027 D = D.replace(/<em>/gi,'<i>').
1028 replace(/<\/em>/gi,'</i>');
1030 // kill unwanted tags
1031 D = D.replace(/<\?xml:[^>]*>/g, ''). // Word xml
1032 replace(/<\/?st1:[^>]*>/g,''). // Word SmartTags
1033 replace(/<\/?[a-z]\:[^>]*>/g,''). // All other funny Word non-HTML stuff
1034 replace(/<\/?font[^>]*>/gi,''). // Disable if you want to keep font formatting
1035 replace(/<\/?span[^>]*>/gi,' ').
1036 replace(/<\/?div[^>]*>/gi,' ').
1037 replace(/<\/?pre[^>]*>/gi,' ').
1038 replace(/<(\/?)(h[1-6]+)[^>]*>/gi,'<$1$2>');
1040 // Lorenzo Nicola's addition
1041 // to get rid off silly word generated tags.
1042 D = D.replace(/<!--\[[^\]]*\]-->/gi,' ');
1044 //remove empty tags
1045 //D = D.replace(/<strong><\/strong>/gi,'').
1046 //replace(/<i><\/i>/gi,'').
1047 //replace(/<P[^>]*><\/P>/gi,'');
1048 D = D.replace(/<h[1-6]+>\s?<\/h[1-6]+>/gi, ''); // Remove empty headings
1050 // nuke double tags
1051 oldlen = D.length + 1;
1052 while(oldlen > D.length) {
1053 oldlen = D.length;
1054 // join us now and free the tags, we'll be free hackers, we'll be free... ;-)
1055 D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' ').
1056 replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>');
1058 D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>').
1059 replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>');
1061 // nuke double spaces
1062 D = D.replace(/ */gi,' ');
1064 // Split into lines and remove
1065 // empty lines and add carriage returns back
1066 var splitter = /\[br\]/g;
1067 var emptyLine = /^\s+\s+$/g;
1068 var strHTML = '';
1069 var toLines = D.split(splitter);
1070 for (var i = 0; i < toLines.length; i++) {
1071 var line = toLines[i];
1072 if (line.length < 1) {
1073 continue;
1076 if (emptyLine.test(line)) {
1077 continue;
1080 line = line.replace(/^\s+\s+$/g, '');
1081 strHTML += line + '\n';
1083 D = strHTML;
1084 strHTML = '';
1086 this.setHTML(D);
1087 this.updateToolbar();
1091 HTMLArea.prototype.forceRedraw = function() {
1092 this._doc.body.style.visibility = "hidden";
1093 this._doc.body.style.visibility = "visible";
1094 // this._doc.body.innerHTML = this.getInnerHTML();
1097 // focuses the iframe window. returns a reference to the editor document.
1098 HTMLArea.prototype.focusEditor = function() {
1099 switch (this._editMode) {
1100 case "wysiwyg" : this._iframe.contentWindow.focus(); break;
1101 case "textmode": this._textArea.focus(); break;
1102 default : alert("ERROR: mode " + this._editMode + " is not defined");
1104 return this._doc;
1107 // takes a snapshot of the current text (for undo)
1108 HTMLArea.prototype._undoTakeSnapshot = function() {
1109 ++this._undoPos;
1110 if (this._undoPos >= this.config.undoSteps) {
1111 // remove the first element
1112 this._undoQueue.shift();
1113 --this._undoPos;
1115 // use the fasted method (getInnerHTML);
1116 var take = true;
1117 var txt = this.getInnerHTML();
1118 if (this._undoPos > 0)
1119 take = (this._undoQueue[this._undoPos - 1] != txt);
1120 if (take) {
1121 this._undoQueue[this._undoPos] = txt;
1122 } else {
1123 this._undoPos--;
1127 HTMLArea.prototype.undo = function() {
1128 if (this._undoPos > 0) {
1129 var txt = this._undoQueue[--this._undoPos];
1130 if (txt) this.setHTML(txt);
1131 else ++this._undoPos;
1135 HTMLArea.prototype.redo = function() {
1136 if (this._undoPos < this._undoQueue.length - 1) {
1137 var txt = this._undoQueue[++this._undoPos];
1138 if (txt) this.setHTML(txt);
1139 else --this._undoPos;
1143 // updates enabled/disable/active state of the toolbar elements
1144 HTMLArea.prototype.updateToolbar = function(noStatus) {
1145 var doc = this._doc;
1146 var text = (this._editMode == "textmode");
1147 var ancestors = null;
1148 if (!text) {
1149 ancestors = this.getAllAncestors();
1150 if (this.config.statusBar && !noStatus) {
1152 while(this._statusBarTree.childNodes.length>0) {
1153 this._statusBarTree.removeChild(this._statusBarTree.childNodes[0]);
1156 this._statusBarTree.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
1158 for (var i = ancestors.length; --i >= 0;) {
1159 var el = ancestors[i];
1160 if (!el) {
1161 // hell knows why we get here; this
1162 // could be a classic example of why
1163 // it's good to check for conditions
1164 // that are impossible to happen ;-)
1165 continue;
1167 var a = document.createElement("a");
1168 a.href = "#";
1169 a.el = el;
1170 a.editor = this;
1171 a.onclick = function() {
1172 this.blur();
1173 this.editor.selectNodeContents(this.el);
1174 this.editor.updateToolbar(true);
1175 return false;
1177 a.oncontextmenu = function() {
1178 // TODO: add context menu here
1179 this.blur();
1180 var info = "Inline style:\n\n";
1181 info += this.el.style.cssText.split(/;\s*/).join(";\n");
1182 alert(info);
1183 return false;
1185 var txt = el.tagName.toLowerCase();
1186 a.title = el.style.cssText;
1187 if (el.id) {
1188 txt += "#" + el.id;
1190 if (el.className) {
1191 txt += "." + el.className;
1193 a.appendChild(document.createTextNode(txt));
1194 this._statusBarTree.appendChild(a);
1195 if (i != 0) {
1196 this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
1201 for (var i in this._toolbarObjects) {
1202 var btn = this._toolbarObjects[i];
1203 var cmd = i;
1204 var inContext = true;
1205 if (btn.context && !text) {
1206 inContext = false;
1207 var context = btn.context;
1208 var attrs = [];
1209 if (/(.*)\[(.*?)\]/.test(context)) {
1210 context = RegExp.$1;
1211 attrs = RegExp.$2.split(",");
1213 context = context.toLowerCase();
1214 var match = (context == "*");
1215 for (var k in ancestors) {
1216 if (!ancestors[k]) {
1217 // the impossible really happens.
1218 continue;
1220 if (match || (ancestors[k].tagName.toLowerCase() == context)) {
1221 inContext = true;
1222 for (var ka in attrs) {
1223 if (!eval("ancestors[k]." + attrs[ka])) {
1224 inContext = false;
1225 break;
1228 if (inContext) {
1229 break;
1234 btn.state("enabled", (!text || btn.text) && inContext);
1235 if (typeof cmd == "function") {
1236 continue;
1238 // look-it-up in the custom dropdown boxes
1239 var dropdown = this.config.customSelects[cmd];
1240 if ((!text || btn.text) && (typeof dropdown != "undefined")) {
1241 dropdown.refresh(this);
1242 continue;
1244 switch (cmd) {
1245 case "fontname":
1246 case "fontsize":
1247 case "formatblock":
1248 if (!text) try {
1249 var value = ("" + doc.queryCommandValue(cmd)).toLowerCase();
1250 if (!value) {
1251 // FIXME: what do we do here?
1252 break;
1254 var options = this.config[cmd];
1255 var k = 0;
1256 // btn.element.selectedIndex = 0;
1257 for (var j in options) {
1258 // FIXME: the following line is scary.
1259 if ((j.toLowerCase() == value) ||
1260 (options[j].substr(0, value.length).toLowerCase() == value)) {
1261 btn.element.selectedIndex = k;
1262 break;
1264 ++k;
1266 } catch(e) {};
1267 break;
1268 case "language":
1269 if (!text) try {
1270 var value;
1271 parentEl = this.getParentElement();
1272 if (parentEl.getAttribute('lang')) {
1273 // A language was previously defined for the block.
1274 if (parentEl.getAttribute('class') == 'multilang') {
1275 value = parentEl.getAttribute('lang')+'_ML';
1276 } else {
1277 value = parentEl.getAttribute('lang');
1279 } else {
1280 value = '';
1282 var options = this.config[cmd];
1283 var k = 0;
1284 for (var j in options) {
1285 // FIXME: the following line is scary.
1286 if ((j.toLowerCase() == value) ||
1287 (options[j].substr(0, value.length).toLowerCase() == value)) {
1288 btn.element.selectedIndex = k;
1289 break;
1291 ++k;
1293 } catch(e) {};
1294 break;
1295 case "textindicator":
1296 if (!text) {
1297 try {with (btn.element.style) {
1298 backgroundColor = HTMLArea._makeColor(
1299 doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor"));
1300 if (/transparent/i.test(backgroundColor)) {
1301 // Mozilla
1302 backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor"));
1304 color = HTMLArea._makeColor(doc.queryCommandValue("forecolor"));
1305 fontFamily = doc.queryCommandValue("fontname");
1306 fontWeight = doc.queryCommandState("bold") ? "bold" : "normal";
1307 fontStyle = doc.queryCommandState("italic") ? "italic" : "normal";
1308 }} catch (e) {
1309 // alert(e + "\n\n" + cmd);
1312 break;
1313 case "htmlmode": btn.state("active", text); break;
1314 case "lefttoright":
1315 case "righttoleft":
1316 var el = this.getParentElement();
1317 while (el && !HTMLArea.isBlockElement(el))
1318 el = el.parentNode;
1319 if (el)
1320 btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr")));
1321 break;
1322 default:
1323 try {
1324 btn.state("active", (!text && doc.queryCommandState(cmd)));
1325 } catch (e) {}
1328 // take undo snapshots
1329 if (this._customUndo && !this._timerUndo) {
1330 this._undoTakeSnapshot();
1331 var editor = this;
1332 this._timerUndo = setTimeout(function() {
1333 editor._timerUndo = null;
1334 }, this.config.undoTimeout);
1336 // check if any plugins have registered refresh handlers
1337 for (var i in this.plugins) {
1338 var plugin = this.plugins[i].instance;
1339 if (typeof plugin.onUpdateToolbar == "function")
1340 plugin.onUpdateToolbar();
1344 /** Returns a node after which we can insert other nodes, in the current
1345 * selection. The selection is removed. It splits a text node, if needed.
1347 HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
1348 if (!HTMLArea.is_ie) {
1349 var sel = this._getSelection();
1350 var range = this._createRange(sel);
1351 // remove the current selection
1352 sel.removeAllRanges();
1353 range.deleteContents();
1354 var node = range.startContainer;
1355 var pos = range.startOffset;
1356 switch (node.nodeType) {
1357 case 3: // Node.TEXT_NODE
1358 // we have to split it at the caret position.
1359 if (toBeInserted.nodeType == 3) {
1360 // do optimized insertion
1361 node.insertData(pos, toBeInserted.data);
1362 range = this._createRange();
1363 range.setEnd(node, pos + toBeInserted.length);
1364 range.setStart(node, pos + toBeInserted.length);
1365 sel.addRange(range);
1366 } else {
1367 node = node.splitText(pos);
1368 var selnode = toBeInserted;
1369 if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1370 selnode = selnode.firstChild;
1372 node.parentNode.insertBefore(toBeInserted, node);
1373 this.selectNodeContents(selnode);
1374 this.updateToolbar();
1376 break;
1377 case 1: // Node.ELEMENT_NODE
1378 var selnode = toBeInserted;
1379 if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1380 selnode = selnode.firstChild;
1382 node.insertBefore(toBeInserted, node.childNodes[pos]);
1383 this.selectNodeContents(selnode);
1384 this.updateToolbar();
1385 break;
1387 } else {
1388 return null; // this function not yet used for IE <FIXME>
1392 // Returns the deepest node that contains both endpoints of the selection.
1393 HTMLArea.prototype.getParentElement = function() {
1394 var sel = this._getSelection();
1395 var range = this._createRange(sel);
1396 if (HTMLArea.is_ie) {
1397 switch (sel.type) {
1398 case "Text":
1399 case "None":
1400 return range.parentElement();
1401 case "Control":
1402 return range.item(0);
1403 default:
1404 return this._doc.body;
1406 } else try {
1407 var p = range.commonAncestorContainer;
1408 if (!range.collapsed && range.startContainer == range.endContainer &&
1409 range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes())
1410 p = range.startContainer.childNodes[range.startOffset];
1412 alert(range.startContainer + ":" + range.startOffset + "\n" +
1413 range.endContainer + ":" + range.endOffset);
1415 while (p.nodeType == 3) {
1416 p = p.parentNode;
1418 return p;
1419 } catch (e) {
1420 return null;
1424 // Returns an array with all the ancestor nodes of the selection.
1425 HTMLArea.prototype.getAllAncestors = function() {
1426 var p = this.getParentElement();
1427 var a = [];
1428 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1429 a.push(p);
1430 p = p.parentNode;
1432 a.push(this._doc.body);
1433 return a;
1436 // Selects the contents inside the given node
1437 HTMLArea.prototype.selectNodeContents = function(node, pos) {
1438 this.focusEditor();
1439 this.forceRedraw();
1440 var range;
1441 var collapsed = (typeof pos != "undefined");
1442 if (HTMLArea.is_ie) {
1443 range = this._doc.body.createTextRange();
1444 range.moveToElementText(node);
1445 (collapsed) && range.collapse(pos);
1446 range.select();
1447 } else {
1448 var sel = this._getSelection();
1449 range = this._doc.createRange();
1450 range.selectNodeContents(node);
1451 (collapsed) && range.collapse(pos);
1452 sel.removeAllRanges();
1453 sel.addRange(range);
1457 // Call this function to insert HTML code at the current position. It deletes
1458 // the selection, if any.
1459 HTMLArea.prototype.insertHTML = function(html) {
1460 var sel = this._getSelection();
1461 var range = this._createRange(sel);
1462 if (HTMLArea.is_ie) {
1463 range.pasteHTML(html);
1464 } else {
1465 // construct a new document fragment with the given HTML
1466 var fragment = this._doc.createDocumentFragment();
1467 var div = this._doc.createElement("div");
1468 div.innerHTML = html;
1469 while (div.firstChild) {
1470 // the following call also removes the node from div
1471 fragment.appendChild(div.firstChild);
1473 // this also removes the selection
1474 var node = this.insertNodeAtSelection(fragment);
1478 // Call this function to surround the existing HTML code in the selection with
1479 // your tags. FIXME: buggy! This function will be deprecated "soon".
1480 HTMLArea.prototype.surroundHTML = function(startTag, endTag) {
1481 var html = this.getSelectedHTML();
1482 // the following also deletes the selection
1483 this.insertHTML(startTag + html + endTag);
1486 /// Retrieve the selected block
1487 HTMLArea.prototype.getSelectedHTML = function() {
1488 var sel = this._getSelection();
1489 var range = this._createRange(sel);
1490 var existing = null;
1491 if (HTMLArea.is_ie) {
1492 existing = range.htmlText;
1493 } else {
1494 existing = HTMLArea.getHTML(range.cloneContents(), false, this);
1496 return existing;
1499 /// Return true if we have some selection
1500 HTMLArea.prototype.hasSelectedText = function() {
1501 // FIXME: come _on_ mishoo, you can do better than this ;-)
1502 return this.getSelectedHTML() != '';
1505 HTMLArea.prototype._createLink = function(link) {
1506 var editor = this;
1507 var allinks = editor._doc.getElementsByTagName('A');
1508 var anchors = new Array();
1509 for(var i = 0; i < allinks.length; i++) {
1510 var attrname = allinks[i].getAttribute('name');
1511 if((HTMLArea.is_ie ? attrname.length > 0 : attrname != null)) {
1512 anchors[i] = allinks[i].getAttribute('name');
1515 var outparam = null;
1516 if (typeof link == "undefined") {
1517 link = this.getParentElement();
1518 if (link && !/^a$/i.test(link.tagName)) {
1519 if(link.tagName.toLowerCase() != 'img') {
1520 link = null;
1521 var sel = this._getSelection();
1522 var rng = this._createRange(sel);
1523 var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1524 if(len < 1) {
1525 alert("<?php print_string("alertnoselectedtext","editor");?>");
1526 return false;
1529 link = null;
1532 if (link) {
1533 outparam = {
1534 f_href : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"),
1535 f_title : link.title,
1536 f_target : link.target,
1537 f_anchors: anchors
1539 } else {
1540 outparam = {
1541 f_anchors:anchors };
1543 this._popupDialog("link_std.php?id=<?php echo $id; ?>", function(param) {
1544 if (!param) {
1545 return false;
1547 var a = link;
1548 if (!a) {
1549 // Create a temporary unique link, insert it then find it and set the correct parameters
1550 var tmpLink = 'http://www.moodle.org/'+Math.random();
1551 var elm = editor._doc.execCommand("createlink",false,tmpLink);
1552 var links=editor._doc.getElementsByTagName("a");
1553 for(var i=0;i<links.length;i++){
1554 var link=links[i];
1555 if(link.href==tmpLink) {
1556 link.href=param.f_href.trim();
1557 if(param.f_target){
1558 link.target=param.f_target.trim();
1560 if(param.f_title){
1561 link.title=param.f_title.trim();
1563 break;
1566 } else {
1567 var href = param.f_href.trim();
1568 editor.selectNodeContents(a);
1569 if (href == "") {
1570 editor._doc.execCommand("unlink", false, null);
1571 editor.updateToolbar();
1572 return false;
1573 } else {
1574 a.href = href;
1577 if (!(a && /^a$/i.test(a.tagName))) {
1578 return false;
1580 a.target = param.f_target.trim();
1581 a.title = param.f_title.trim();
1582 editor.selectNodeContents(a);
1583 editor.updateToolbar();
1584 }, outparam);
1587 // Called when the user clicks on "InsertImage" button. If an image is already
1588 // there, it will just modify it's properties.
1589 HTMLArea.prototype._insertImage = function(image) {
1591 // Make sure that editor has focus
1592 this.focusEditor();
1593 var editor = this; // for nested functions
1594 var outparam = null;
1595 if (typeof image == "undefined") {
1596 image = this.getParentElement();
1597 if (image && !/^img$/i.test(image.tagName))
1598 image = null;
1600 if (image) outparam = {
1601 f_url : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"),
1602 f_alt : image.alt,
1603 f_border : image.border,
1604 f_align : image.align,
1605 f_vert : image.vspace,
1606 f_horiz : image.hspace,
1607 f_width : image.width,
1608 f_height : image.height
1610 this._popupDialog("<?php
1611 if(!empty($id) and has_capability('moodle/course:managefiles', get_context_instance(CONTEXT_COURSE, $id))) {
1612 echo "insert_image.php?id=$id";
1613 } else {
1614 echo "insert_image_std.php?id=$id";
1615 }?>", function(param) {
1616 if (!param) { // user must have pressed Cancel
1617 return false;
1619 var img = image;
1620 if (!img) {
1621 var sel = editor._getSelection();
1622 var range = editor._createRange(sel);
1623 if (HTMLArea.is_ie) {
1624 editor._doc.execCommand("insertimage", false, param.f_url);
1626 if (HTMLArea.is_ie) {
1627 img = range.parentElement();
1628 // wonder if this works...
1629 if (img.tagName.toLowerCase() != "img") {
1630 img = img.previousSibling;
1632 } else {
1633 // MOODLE HACK: startContainer.perviousSibling
1634 // Doesn't work so we'll use createElement and
1635 // insertNodeAtSelection
1636 //img = range.startContainer.previousSibling;
1637 var img = editor._doc.createElement("img");
1638 img.setAttribute("src",""+ param.f_url +"");
1639 img.setAttribute("alt",""+ param.f_alt +"");
1640 editor.insertNodeAtSelection(img);
1642 } else {
1643 img.src = param.f_url;
1645 for (field in param) {
1646 var value = param[field];
1647 switch (field) {
1648 case "f_alt" : img.alt = value; img.title = value; break;
1649 case "f_border" : img.border = parseInt(value || "0"); break;
1650 case "f_align" : img.align = value; break;
1651 case "f_vert" : img.vspace = parseInt(value || "0"); break;
1652 case "f_horiz" : img.hspace = parseInt(value || "0"); break;
1653 case "f_width" :
1654 if(value != 0) {
1655 img.width = parseInt(value);
1656 } else {
1657 break;
1659 break;
1660 case "f_height" :
1661 if(value != 0) {
1662 img.height = parseInt(value);
1663 } else {
1664 break;
1666 break;
1669 }, outparam);
1672 // Called when the user clicks the Insert Table button
1673 HTMLArea.prototype._insertTable = function() {
1674 var sel = this._getSelection();
1675 var range = this._createRange(sel);
1676 var editor = this; // for nested functions
1677 this._popupDialog("insert_table.php?id=<?php echo $id; ?>", function(param) {
1678 if (!param) { // user must have pressed Cancel
1679 return false;
1681 var doc = editor._doc;
1682 // create the table element
1683 var table = doc.createElement("table");
1684 // assign the given arguments
1685 for (var field in param) {
1686 var value = param[field];
1687 if (!value) {
1688 continue;
1690 switch (field) {
1691 case "f_width" : table.width = value + param["f_unit"]; break;
1692 case "f_align" : table.align = value; break;
1693 case "f_border" : table.border = parseInt(value); break;
1694 case "f_spacing" : table.cellspacing = parseInt(value); break;
1695 case "f_padding" : table.cellpadding = parseInt(value); break;
1698 var tbody = doc.createElement("tbody");
1699 table.appendChild(tbody);
1700 for (var i = 0; i < param["f_rows"]; ++i) {
1701 var tr = doc.createElement("tr");
1702 tbody.appendChild(tr);
1703 for (var j = 0; j < param["f_cols"]; ++j) {
1704 var td = doc.createElement("td");
1705 /// Moodle hack
1706 if(param["f_unit"] == "px") {
1707 tdwidth = Math.round(table.width / param["f_cols"]);
1708 } else {
1709 tdwidth = Math.round(100 / param["f_cols"]);
1711 td.setAttribute("width",tdwidth + param["f_unit"]);
1712 td.setAttribute("valign","top");
1713 /// Moodle hack -ends
1714 tr.appendChild(td);
1715 // Mozilla likes to see something inside the cell.
1716 (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br"));
1719 if (HTMLArea.is_ie) {
1720 range.pasteHTML(table.outerHTML);
1721 } else {
1722 // insert the table
1723 editor.insertNodeAtSelection(table);
1725 return true;
1726 }, null);
1729 /// Moodle hack - insertSmile
1730 HTMLArea.prototype._insertSmile = function() {
1731 // Make sure that editor has focus
1732 this.focusEditor();
1733 var sel = this._getSelection();
1734 var range = this._createRange(sel);
1735 var editor = this; // for nested functions
1736 this._popupDialog("dlg_ins_smile.php?id=<?php echo $id; ?>", function(imgString) {
1737 if(!imgString) {
1738 return false;
1740 if (HTMLArea.is_ie) {
1741 range.pasteHTML(imgString);
1742 } else {
1743 editor.insertHTML(imgString);
1745 return true;
1746 }, null);
1749 HTMLArea.prototype._insertChar = function() {
1750 var sel = this._getSelection();
1751 var range = this._createRange(sel);
1752 var editor = this; // for nested functions
1753 this._popupDialog("dlg_ins_char.php?id=<?php echo $id; ?>", function(sChar) {
1754 if(!sChar) {
1755 return false;
1757 if (HTMLArea.is_ie) {
1758 range.pasteHTML(sChar);
1759 } else {
1760 // insert the table
1761 editor.insertHTML(sChar);
1763 return true;
1764 }, null);
1767 HTMLArea.prototype._removelink = function() {
1768 var editor = this;
1769 link = this.getParentElement();
1770 editor.selectNodeContents(link);
1772 this._doc.execCommand("unlink", false, null);
1773 this.focusEditor();
1776 HTMLArea.prototype._createanchor = function () {
1777 var editor = this;
1778 var sel = this._getSelection();
1779 var rng = this._createRange(sel);
1780 var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1781 if(len < 1) {
1782 alert("<?php print_string("alertnoselectedtext","editor");?>");
1783 return false;
1785 this._popupDialog("createanchor.php?id=<?php echo $id; ?>", function(objAn) {
1786 if(!objAn) {
1787 return false;
1789 var str = '<a name="'+ objAn.anchor+'">';
1790 str += HTMLArea.is_ie ? rng.text : sel ;
1791 str += '</a>';
1792 editor.insertHTML(str);
1793 },null);
1796 HTMLArea.prototype._nolinktag = function () {
1798 var editor = this;
1799 var sel = this._getSelection();
1800 var rng = this._createRange(sel);
1801 var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1803 if (len < 1) {
1804 alert("<?php print_string("alertnoselectedtext","editor");?>");
1805 return false;
1807 var str = '<span class="nolink">';
1808 str += HTMLArea.is_ie ? rng.text : sel;
1809 str += '</span>';
1810 editor.insertHTML(str);
1811 this.focusEditor();
1815 HTMLArea.prototype._searchReplace = function() {
1817 var editor = this;
1818 var selectedtxt = "";
1819 <?php
1820 $strreplaced = addslashes(get_string('itemsreplaced','editor'));
1821 $strnotfound = addslashes(get_string('searchnotfound','editor'));
1823 var strReplaced = '<?php echo $strreplaced ?>';
1824 var strNotfound = '<?php echo $strnotfound ?>';
1825 var ile;
1827 //in source mode mozilla show errors, try diffrent method
1828 if (editor._editMode == "wysiwyg") {
1829 selectedtxt = editor.getSelectedHTML();
1830 } else {
1831 if (HTMLArea.is_ie) {
1832 selectedtxt = document.selection.createRange().text;
1833 } else {
1834 selectedtxt = getMozSelection(editor._textArea);
1838 outparam = {
1839 f_search : selectedtxt
1842 //Call Search And Replace popup window
1843 editor._popupDialog( "searchandreplace.php?id=<?php echo $id; ?>", function( entity ) {
1844 if ( !entity ) {
1845 //user must have pressed Cancel
1846 return false;
1848 var text = editor.getHTML();
1849 var search = entity[0];
1850 var replace = entity[1];
1851 var delim = entity[2];
1852 var regularx = entity[3];
1853 var closesar = entity[4];
1854 ile = 0;
1855 if (search.length < 1) {
1856 alert ("Enter a search word! \n search for: " + entity[0]);
1857 } else {
1858 if (regularx) {
1859 var regX = new RegExp (search, delim) ;
1860 var text = text.replace ( regX,
1861 function (str, n) {
1862 // Increment our counter variable.
1863 ile++ ;
1864 //return replace ;
1865 return str.replace( regX, replace) ;
1869 } else {
1870 while (text.indexOf(search)>-1) {
1871 pos = text.indexOf(search);
1872 text = "" + (text.substring(0, pos) + replace + text.substring((pos + search.length), text.length));
1873 ile++;
1877 editor.setHTML(text);
1878 editor.forceRedraw();
1879 if (ile > 0) {
1880 alert(ile + ' ' + strReplaced);
1881 } else {
1882 alert (strNotfound + "\n");
1885 }, outparam);
1887 function getMozSelection(txtarea) {
1888 var selLength = txtarea.textLength;
1889 var selStart = txtarea.selectionStart;
1890 var selEnd = txtarea.selectionEnd;
1891 if (selEnd==1 || selEnd==2) selEnd=selLength;
1892 return (txtarea.value).substring(selStart, selEnd);
1896 /// Moodle hack's ends
1898 // Category: EVENT HANDLERS
1900 // el is reference to the SELECT object
1901 // txt is the name of the select field, as in config.toolbar
1902 HTMLArea.prototype._comboSelected = function(el, txt) {
1903 this.focusEditor();
1904 var value = el.options[el.selectedIndex].value;
1905 switch (txt) {
1906 case "fontname":
1907 case "fontsize": this.execCommand(txt, false, value); break;
1908 case "language":
1909 this.setLang(value);
1910 break;
1911 case "formatblock":
1912 (HTMLArea.is_ie) && (value = "<" + value + ">");
1913 this.execCommand(txt, false, value);
1914 break;
1915 default:
1916 // try to look it up in the registered dropdowns
1917 var dropdown = this.config.customSelects[txt];
1918 if (typeof dropdown != "undefined") {
1919 dropdown.action(this);
1920 } else {
1921 alert("FIXME: combo box " + txt + " not implemented");
1928 * Used to set the language for the selected content.
1929 * We use the <span lang="en" class="multilang">content</span> format for
1930 * content that should be marked for multilang filter use, and
1931 * <span lang="en">content</span> for normal content for which we want to
1932 * set the language (for screen reader usage, for example).
1934 HTMLArea.prototype.setLang = function(lang) {
1936 if (lang == 'multi') {
1937 // This is just the separator in the dropdown. Does nothing.
1938 return;
1941 var editor = this;
1942 var selectedHTML = editor.getSelectedHTML();
1943 var multiLang = false;
1945 var re = new RegExp('_ML', 'g');
1946 if (lang.match(re)) {
1947 multiLang = true;
1948 lang = lang.replace(re, '');
1951 // Remove all lang attributes from span tags in selected html.
1952 selectedHTML = selectedHTML.replace(/(<span[^>]*)lang="[^"]*"([^>]*>)/, "$1$2");
1953 selectedHTML = selectedHTML.replace(/(<span[^>]*)class="multilang"([^>]*>)/, "$1$2");
1955 // If a span tag is now empty, delete it.
1956 selectedHTML = selectedHTML.replace(/<span\s*>(.*?)<\/span>/, "$1");
1959 var parentEl = this.getParentElement();
1960 var insertNewSpan = false;
1962 if (parentEl.nodeName == 'SPAN' && parentEl.getAttribute('lang')) {
1963 // A language was previously defined for the current block.
1964 // Check whether the selected text makes up the whole of the block
1965 // contents.
1966 var re = new RegExp(parentEl.innerHTML);
1968 if (selectedHTML.match(re)) {
1969 // The selected text makes up the whole of the span block.
1970 if (lang != '') {
1971 parentEl.setAttribute('lang', lang);
1972 if (multiLang) {
1973 parentEl.setAttribute('class', 'multilang');
1975 } else {
1976 parentEl.removeAttribute('lang');
1978 var classAttr = parentEl.getAttribute('class');
1979 if (classAttr) {
1980 classAttr = classAttr.replace(/multilang/, '').trim();
1982 if (classAttr == '') {
1983 parentEl.removeAttribute('class');
1985 if (parentEl.attributes.length == 0) {
1986 // The span is no longer needed.
1987 for (i=0; i<parentEl.childNodes.length; i++) {
1988 parentEl.parentNode.insertBefore(parentEl.childNodes[i], parentEl);
1990 parentEl.parentNode.removeChild(parentEl);
1993 } else {
1994 insertNewSpan = true;
1996 } else {
1997 insertNewSpan = true;
2000 if (insertNewSpan && lang != '') {
2001 var str = '<span lang="'+lang.trim()+'"';
2002 str += ' class="multilang"';
2003 str += '>';
2004 str += selectedHTML;
2005 str += '</span>';
2006 editor.insertHTML(str);
2011 // the execCommand function (intercepts some commands and replaces them with
2012 // our own implementation)
2013 HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
2014 var editor = this; // for nested functions
2015 this.focusEditor();
2016 cmdID = cmdID.toLowerCase();
2017 switch (cmdID) {
2018 case "htmlmode" : this.setMode(); break;
2019 case "hilitecolor":
2020 (HTMLArea.is_ie) && (cmdID = "backcolor");
2021 case "forecolor":
2022 this._popupDialog("select_color.php?id=<?php echo $id; ?>", function(color) {
2023 if (color) { // selection not canceled
2024 editor._doc.execCommand(cmdID, false, "#" + color);
2026 }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID)));
2027 break;
2028 case "createanchor": this._createanchor(); break;
2029 case "createlink":
2030 this._createLink();
2031 break;
2032 case "unlink": this._removelink(); break;
2033 case "nolink": this._nolinktag(); break;
2034 case "popupeditor":
2035 // this object will be passed to the newly opened window
2036 HTMLArea._object = this;
2037 if (HTMLArea.is_ie) {
2039 window.open(this.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen",
2040 "toolbar=no,location=no,directories=no,status=no,menubar=no," +
2041 "scrollbars=no,resizable=yes,width=800,height=600");
2043 } else {
2044 window.open(this.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen",
2045 "toolbar=no,menubar=no,personalbar=no,width=800,height=600," +
2046 "scrollbars=no,resizable=yes");
2048 break;
2049 case "undo":
2050 case "redo":
2051 if (this._customUndo)
2052 this[cmdID]();
2053 else
2054 this._doc.execCommand(cmdID, UI, param);
2055 break;
2056 case "inserttable": this._insertTable(); break;
2057 case "insertimage": this._insertImage(); break;
2058 case "insertsmile": this._insertSmile(); break;
2059 case "insertchar": this._insertChar(); break;
2060 case "searchandreplace": this._searchReplace(); break;
2061 case "about" : this._popupDialog("about.html", null, this); break;
2062 case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break;
2064 case "killword": this._wordClean(); break;
2066 case "cut":
2067 case "copy":
2068 case "paste":
2069 try {
2070 // Paste first then clean
2071 this._doc.execCommand(cmdID, UI, param);
2072 if (this.config.killWordOnPaste) {
2073 this._wordClean();
2075 } catch (e) {
2076 if (HTMLArea.is_gecko) {
2077 if (confirm("<?php
2078 $strmoz = get_string('cutpastemozilla','editor');
2079 $strmoz = preg_replace("/[\n|\r]+/", "", $strmoz);
2080 $strmoz = str_replace('<br />', '\\n', $strmoz);
2082 echo addslashes($strmoz);
2084 ?>"))
2085 window.open("http://moodle.org/mozillahelp");
2088 break;
2089 case "lefttoright":
2090 case "righttoleft":
2091 var dir = (cmdID == "righttoleft") ? "rtl" : "ltr";
2092 var el = this.getParentElement();
2093 while (el && !HTMLArea.isBlockElement(el))
2094 el = el.parentNode;
2095 if (el) {
2096 if (el.style.direction == dir)
2097 el.style.direction = "";
2098 else
2099 el.style.direction = dir;
2101 break;
2102 default: this._doc.execCommand(cmdID, UI, param);
2104 this.updateToolbar();
2105 return false;
2110 * A generic event handler for things that happen in the IFRAME's document.
2111 * This function also handles key bindings.
2113 HTMLArea.prototype._editorEvent = function(ev) {
2115 var editor = this;
2116 var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress");
2118 if (keyEvent) {
2120 for (var i in editor.plugins) {
2121 var plugin = editor.plugins[i].instance;
2122 if (typeof plugin.onKeyPress == "function") plugin.onKeyPress(ev);
2125 var sel = null;
2126 var range = null;
2127 var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
2128 var cmd = null;
2129 var value = null;
2131 if (ev.ctrlKey && !ev.altKey) {
2133 * Ctrl modifier only.
2134 * We use these for shortcuts that change existing content,
2135 * e.g. make text bold.
2137 switch (key) {
2139 case 'a':
2140 // Select all.
2141 if (!HTMLArea.is_ie) {
2142 // KEY select all
2143 sel = this._getSelection();
2144 sel.removeAllRanges();
2145 range = this._createRange();
2146 range.selectNodeContents(this._doc.body);
2147 sel.addRange(range);
2148 HTMLArea._stopEvent(ev);
2150 break;
2152 // For the dropdowns, we assign focus to them so that they are
2153 // keyboard accessible.
2154 case 'o':
2155 editor.dropdowns['fontname'].focus();
2156 break;
2157 case 'p':
2158 editor.dropdowns['fontsize'].focus();
2159 break;
2160 case 'h':
2161 editor.dropdowns['formatblock'].focus();
2162 break;
2163 case '=':
2164 editor.dropdowns['language'].focus();
2165 break;
2167 case 'b': cmd = "bold"; break;
2168 case 'i': cmd = "italic"; break;
2169 case 'u': cmd = "underline"; break;
2170 case 's': cmd = "strikethrough"; break;
2171 case ',': cmd = "subscript"; break;
2172 case '.': cmd = "superscript"; break;
2174 case 'v':
2175 if (! HTMLArea.is_gecko ) {
2176 cmd = "paste";
2178 break;
2180 case '0': cmd = "killword"; break;
2181 case 'z': cmd = "undo"; break;
2182 case 'y': cmd = "redo"; break;
2183 case 'l': cmd = "justifyleft"; break;
2184 case 'e': cmd = "justifycenter"; break;
2185 case 'r': cmd = "justifyright"; break;
2186 case 'j': cmd = "justifyfull"; break;
2187 case '/': cmd = "lefttoright"; break;
2188 case '|': cmd = "righttoleft"; break;
2189 case ';': cmd = "outdent"; break;
2190 case "'": cmd = "indent"; break;
2191 case 'g': cmd = "forecolor"; break;
2192 case 'k': cmd = "hilitecolor"; break;
2193 case 'f': cmd = "searchandreplace"; break;
2194 case '`': cmd = "htmlmode"; break; // FIXME: can't toggle from source code to wysiwyg
2196 case 'm':
2197 // Toggle fullscreen on or off.
2198 if (this.config.btnList['popupeditor'][0] == 'Enlarge Editor') {
2199 cmd = 'popupeditor';
2200 } else {
2201 window.close();
2203 break;
2205 // Headings.
2206 case '1':
2207 case '2':
2208 case '3':
2209 case '4':
2210 case '5':
2211 case '6':
2212 cmd = "formatblock";
2213 value = "h" + key;
2214 if (HTMLArea.is_ie) {
2215 value = "<" + value + ">";
2217 break;
2219 } // End switch (key)
2222 } else if (ev.ctrlKey && ev.altKey) {
2224 * Ctrl + Alt modifiers.
2225 * We use these for shortcuts that insert stuff, e.g. images.
2227 switch (key) {
2228 case 'o': cmd = "insertorderedlist"; break;
2229 case 'u': cmd = "insertunorderedlist"; break;
2230 case 'r': cmd = "inserthorizontalrule"; break;
2231 case 'a': cmd = "createanchor"; break;
2232 case 'l': cmd = "createlink"; break;
2233 case 'd': cmd = "unlink"; break;
2234 case 'n': cmd = "nolink"; break;
2235 case 'i': cmd = 'insertimage'; break;
2236 case 't': cmd = 'inserttable'; break;
2237 case 's': cmd = 'insertsmile'; break;
2238 case 'c': cmd = 'insertchar'; break;
2242 if (cmd) {
2243 // execute simple command
2244 this.execCommand(cmd, false, value);
2245 HTMLArea._stopEvent(ev);
2247 } // End if (keyEvent)
2250 else if (keyEvent) {
2251 // other keys here
2252 switch (ev.keyCode) {
2253 case 13: // KEY enter
2254 // if (HTMLArea.is_ie) {
2255 this.insertHTML("<br />");
2256 HTMLArea._stopEvent(ev);
2257 // }
2258 break;
2263 // Update the toolbar state after some time.
2264 if (editor._timerToolbar) {
2265 clearTimeout(editor._timerToolbar);
2267 editor._timerToolbar = setTimeout(function() {
2268 editor.updateToolbar();
2269 editor._timerToolbar = null;
2270 }, 50);
2274 // retrieve the HTML
2275 HTMLArea.prototype.getHTML = function() {
2276 switch (this._editMode) {
2277 case "wysiwyg" :
2278 if (!this.config.fullPage) {
2279 return HTMLArea.getHTML(this._doc.body, false, this);
2280 } else
2281 return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this);
2282 case "textmode" : return this._textArea.value;
2283 default : alert("Mode <" + mode + "> not defined!");
2285 return false;
2288 // retrieve the HTML (fastest version, but uses innerHTML)
2289 HTMLArea.prototype.getInnerHTML = function() {
2290 switch (this._editMode) {
2291 case "wysiwyg" :
2292 if (!this.config.fullPage)
2293 return this._doc.body.innerHTML;
2294 else
2295 return this.doctype + "\n" + this._doc.documentElement.innerHTML;
2296 case "textmode" : return this._textArea.value;
2297 default : alert("Mode <" + mode + "> not defined!");
2299 return false;
2302 // completely change the HTML inside
2303 HTMLArea.prototype.setHTML = function(html) {
2304 switch (this._editMode) {
2305 case "wysiwyg" :
2306 if (!this.config.fullPage)
2307 this._doc.body.innerHTML = html;
2308 else
2309 // this._doc.documentElement.innerHTML = html;
2310 this._doc.body.innerHTML = html;
2311 break;
2312 case "textmode" : this._textArea.value = html; break;
2313 default : alert("Mode <" + mode + "> not defined!");
2315 return false;
2318 // sets the given doctype (useful when config.fullPage is true)
2319 HTMLArea.prototype.setDoctype = function(doctype) {
2320 this.doctype = doctype;
2323 /***************************************************
2324 * Category: UTILITY FUNCTIONS
2325 ***************************************************/
2327 // browser identification
2329 HTMLArea.agt = navigator.userAgent.toLowerCase();
2330 HTMLArea.is_ie = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
2331 HTMLArea.is_opera = (HTMLArea.agt.indexOf("opera") != -1);
2332 HTMLArea.is_mac = (HTMLArea.agt.indexOf("mac") != -1);
2333 HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
2334 HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
2335 HTMLArea.is_gecko = (navigator.product == "Gecko");
2336 HTMLArea.is_safari = (HTMLArea.agt.indexOf("safari") != -1);
2338 // variable used to pass the object to the popup editor window.
2339 HTMLArea._object = null;
2341 // function that returns a clone of the given object
2342 HTMLArea.cloneObject = function(obj) {
2343 var newObj = new Object;
2345 // check for array objects
2346 if (obj.constructor.toString().indexOf("function Array(") >= 0) {
2347 newObj = obj.constructor();
2350 // check for function objects (as usual, IE is phucked up)
2351 if (obj.constructor.toString().indexOf("function Function(") >= 0) {
2352 newObj = obj; // just copy reference to it
2353 } else for (var n in obj) {
2354 var node = obj[n];
2355 if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); }
2356 else { newObj[n] = node; }
2359 return newObj;
2362 // FIXME!!! this should return false for IE < 5.5
2363 HTMLArea.checkSupportedBrowser = function() {
2364 if (HTMLArea.is_gecko) {
2365 if (navigator.productSub < 20021201) {
2366 alert("You need at least Mozilla-1.3 Alpha.\n" +
2367 "Sorry, your Gecko is not supported.");
2368 return false;
2370 if (navigator.productSub < 20030210) {
2371 alert("Mozilla < 1.3 Beta is not supported!\n" +
2372 "I'll try, though, but it might not work.");
2375 if(HTMLArea.is_safari) {
2376 return false;
2378 return HTMLArea.is_gecko || HTMLArea.is_ie;
2381 // selection & ranges
2383 // returns the current selection object
2384 HTMLArea.prototype._getSelection = function() {
2385 if (HTMLArea.is_ie) {
2386 return this._doc.selection;
2387 } else {
2388 return this._iframe.contentWindow.getSelection();
2392 // returns a range for the current selection
2393 HTMLArea.prototype._createRange = function(sel) {
2394 if (HTMLArea.is_ie) {
2395 return sel.createRange();
2396 } else {
2397 // Commented out because we need the dropdowns to be able to keep
2398 // focus for keyboard accessibility. Comment by Vy-Shane Sin Fat.
2399 //this.focusEditor();
2400 if (typeof sel != "undefined") {
2401 try {
2402 return sel.getRangeAt(0);
2403 } catch(e) {
2404 return this._doc.createRange();
2406 } else {
2407 return this._doc.createRange();
2412 // event handling
2414 HTMLArea._addEvent = function(el, evname, func) {
2415 if (HTMLArea.is_ie) {
2416 el.attachEvent("on" + evname, func);
2417 } else {
2418 el.addEventListener(evname, func, true);
2422 HTMLArea._addEvents = function(el, evs, func) {
2423 for (var i in evs) {
2424 HTMLArea._addEvent(el, evs[i], func);
2428 HTMLArea._removeEvent = function(el, evname, func) {
2429 if (HTMLArea.is_ie) {
2430 el.detachEvent("on" + evname, func);
2431 } else {
2432 el.removeEventListener(evname, func, true);
2436 HTMLArea._removeEvents = function(el, evs, func) {
2437 for (var i in evs) {
2438 HTMLArea._removeEvent(el, evs[i], func);
2442 HTMLArea._stopEvent = function(ev) {
2443 if (HTMLArea.is_ie) {
2444 ev.cancelBubble = true;
2445 ev.returnValue = false;
2446 } else {
2447 ev.preventDefault();
2448 ev.stopPropagation();
2452 HTMLArea._removeClass = function(el, className) {
2453 if (!(el && el.className)) {
2454 return;
2456 var cls = el.className.split(" ");
2457 var ar = new Array();
2458 for (var i = cls.length; i > 0;) {
2459 if (cls[--i] != className) {
2460 ar[ar.length] = cls[i];
2463 el.className = ar.join(" ");
2466 HTMLArea._addClass = function(el, className) {
2467 // remove the class first, if already there
2468 HTMLArea._removeClass(el, className);
2469 el.className += " " + className;
2472 HTMLArea._hasClass = function(el, className) {
2473 if (!(el && el.className)) {
2474 return false;
2476 var cls = el.className.split(" ");
2477 for (var i = cls.length; i > 0;) {
2478 if (cls[--i] == className) {
2479 return true;
2482 return false;
2485 HTMLArea.isBlockElement = function(el) {
2487 var blockTags = " body form textarea fieldset ul ol dl li div " +
2488 "p h1 h2 h3 h4 h5 h6 quote pre table thead " +
2489 "tbody tfoot tr td iframe address ";
2490 try {
2491 return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
2492 } catch (e) {}
2496 HTMLArea.needsClosingTag = function(el) {
2497 var closingTags = " head script style div span tr td tbody table em strong font a title iframe object applet ";
2498 return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
2501 // performs HTML encoding of some given string
2502 HTMLArea.htmlEncode = function(str) {
2503 // we don't need regexp for that, but.. so be it for now.
2504 str = str.replace(/&/ig, "&amp;");
2505 str = str.replace(/</ig, "&lt;");
2506 str = str.replace(/>/ig, "&gt;");
2507 str = str.replace(/\x22/ig, "&quot;");
2508 // \x22 means '"' -- we use hex reprezentation so that we don't disturb
2509 // JS compressors (well, at least mine fails.. ;)
2510 return str;
2512 // Moodle hack for special tags. Note that in IE you cannot start
2513 // content with special tag ( innerHTML issue ).
2514 HTMLArea.isSpecialTag = function (el) {
2515 var tags = new Array();
2516 tags[0] = /^\/?(nolink|lang|tex|algebra|math|mi|mn|mo|mtext|mspace)$/i;
2517 tags[1] = /^\/?(ms|mrow|mfrac|msqrt|mroot|mstyle|merror|mpadded|mphantom)$/i;
2518 tags[2] = /^\/?(mfenced|msub|msup|msubsup|munder|mover|munderover|mmultiscripts)$/i;
2519 tags[3] = /^\/?(mtable|mtr|mtd|maligngroup|malignmark|maction|cn|ci|apply|reln)$/i;
2520 tags[4] = /^\/?(fn|interval|inverse|sep|condition|declare|lambda|compose|ident)$/i;
2521 tags[5] = /^\/?(quotient|exp|factorial|divide|max|min|minus|plus|power|rem|times)$/i;
2522 tags[6] = /^\/?(root|gcd|and|or|xor|not|implies|forall|exists|abs|conjugate|eq|neq)$/i;
2523 tags[7] = /^\/?(gt|lt|geq|leq|ln|log|int|diff|partialdiff|lowlimit|uplimit|bvar)$/i;
2524 tags[8] = /^\/?(degree|set|list|union|intersect|in|notin|subset|prsubset|notsubset)$/i;
2525 tags[9] = /^\/?(notprsubset|setdiff|sum|product|limit|tendsto|mean|sdev|variance|median)$/i;
2526 tags[10] = /^\/?(mode|moment|vector|matrix|matrixrow|determinant|transpose|selector)$/i;
2527 tags[11] = /^\/?(annotation|semantics|annotation-xml)$/i;
2528 for ( var i = 0; i < tags.length; i++ ) {
2529 if ( tags[i].test(el.tagName.toLowerCase()) ) {
2530 return true;
2533 return false;
2535 HTMLArea.isSingleTag = function (el) {
2536 var re = /^(br|hr|img|input|link|meta|param|embed|area)$/i;
2537 return re.test(el.tagName.toLowerCase());
2539 // Retrieves the HTML code from the given node. This is a replacement for
2540 // getting innerHTML, using standard DOM calls.
2541 HTMLArea.getHTML = function(root, outputRoot, editor) {
2542 var html = "";
2543 switch (root.nodeType) {
2544 case 1: // Node.ELEMENT_NODE
2545 case 11: // Node.DOCUMENT_FRAGMENT_NODE
2546 var closed;
2547 var i;
2548 var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
2549 if (HTMLArea.is_ie && root_tag == "head") {
2550 if (outputRoot)
2551 html += "<head>";
2552 // lowercasize
2553 var save_multiline = RegExp.multiline;
2554 RegExp.multiline = true;
2555 var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
2556 return p1 + p2.toLowerCase();
2558 RegExp.multiline = save_multiline;
2559 html += txt;
2560 if (outputRoot)
2561 html += "</head>";
2562 break;
2563 } else if (outputRoot) {
2564 closed = (!(root.hasChildNodes() || !HTMLArea.isSingleTag(root)));
2565 html = "<" + root.tagName.toLowerCase();
2566 var attrs = root.attributes;
2567 for (i = 0; i < attrs.length; ++i) {
2568 var a = attrs.item(i);
2569 if (!a.specified) {
2570 continue;
2572 var name = a.nodeName.toLowerCase();
2573 if (/_moz|contenteditable|_msh/.test(name)) {
2574 // avoid certain attributes
2575 continue;
2577 var value;
2578 if (name != "style") {
2580 // Using Gecko the values of href and src are converted to absolute links
2581 // unless we get them using nodeValue()
2582 if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") {
2583 value = root[a.nodeName];
2584 } else {
2585 // This seems to be working, but if it does cause
2586 // problems later on return the old value...
2587 if (name.toLowerCase() == "href" && name.toLowerCase() == "src") {
2588 value = root[a.nodeName];
2589 } else {
2590 value = a.nodeValue;
2592 if (HTMLArea.is_ie && (name == "href" || name == "src")) {
2593 value = editor.stripBaseURL(value);
2596 } else { // IE fails to put style in attributes list
2597 // FIXME: cssText reported by IE is UPPERCASE
2598 value = root.style.cssText.toLowerCase();
2600 if (/(_moz|^$)/.test(value)) {
2601 // Mozilla reports some special tags
2602 // here; we don't need them.
2603 continue;
2605 html += " " + name + '="' + value + '"';
2607 html += closed ? " />" : ">";
2609 for (i = root.firstChild; i; i = i.nextSibling) {
2610 html += HTMLArea.getHTML(i, true, editor);
2612 if (outputRoot && !closed) {
2613 if ( HTMLArea.is_ie && HTMLArea.isSpecialTag(root) ) {
2614 html += '';
2615 } else {
2616 html += "</" + root.tagName.toLowerCase() + ">";
2619 break;
2620 case 3: // Node.TEXT_NODE
2621 // If a text node is alone in an element and all spaces, replace it with an non breaking one
2622 // This partially undoes the damage done by moz, which translates '&nbsp;'s into spaces in the data element
2623 if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) && root.data.length > 1 ) html = '&nbsp;';
2624 else html = HTMLArea.htmlEncode(root.data);
2625 break;
2626 case 8: // Node.COMMENT_NODE
2627 html = "<!--" + root.data + "-->";
2628 break; // skip comments, for now.
2631 return HTMLArea.indent(html);
2634 HTMLArea.prototype.stripBaseURL = function(string) {
2635 var baseurl = this.config.baseURL;
2637 // IE adds the path to an anchor, converting #anchor
2638 // to path/#anchor which of course needs to be fixed
2639 var index = string.indexOf("/#")+1;
2640 if ((index > 0) && (string.indexOf(baseurl) > -1)) {
2641 return string.substr(index);
2643 return string; // Moodle doesn't use the code below because
2644 // Moodle likes to keep absolute links
2646 // strip to last directory in case baseurl points to a file
2647 baseurl = baseurl.replace(/[^\/]+$/, '');
2648 var basere = new RegExp(baseurl);
2649 string = string.replace(basere, "");
2651 // strip host-part of URL which is added by MSIE to links relative to server root
2652 baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
2653 basere = new RegExp(baseurl);
2654 return string.replace(basere, "");
2657 String.prototype.trim = function() {
2658 a = this.replace(/^\s+/, '');
2659 return a.replace(/\s+$/, '');
2662 // creates a rgb-style color from a number
2663 HTMLArea._makeColor = function(v) {
2664 if (typeof v != "number") {
2665 // already in rgb (hopefully); IE doesn't get here.
2666 return v;
2668 // IE sends number; convert to rgb.
2669 var r = v & 0xFF;
2670 var g = (v >> 8) & 0xFF;
2671 var b = (v >> 16) & 0xFF;
2672 return "rgb(" + r + "," + g + "," + b + ")";
2675 // returns hexadecimal color representation from a number or a rgb-style color.
2676 HTMLArea._colorToRgb = function(v) {
2677 if (!v)
2678 return '';
2680 // returns the hex representation of one byte (2 digits)
2681 function hex(d) {
2682 return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
2685 if (typeof v == "number") {
2686 // we're talking to IE here
2687 var r = v & 0xFF;
2688 var g = (v >> 8) & 0xFF;
2689 var b = (v >> 16) & 0xFF;
2690 return "#" + hex(r) + hex(g) + hex(b);
2693 if (v.substr(0, 3) == "rgb") {
2694 // in rgb(...) form -- Mozilla
2695 var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2696 if (v.match(re)) {
2697 var r = parseInt(RegExp.$1);
2698 var g = parseInt(RegExp.$2);
2699 var b = parseInt(RegExp.$3);
2700 return "#" + hex(r) + hex(g) + hex(b);
2702 // doesn't match RE?! maybe uses percentages or float numbers
2703 // -- FIXME: not yet implemented.
2704 return null;
2707 if (v.substr(0, 1) == "#") {
2708 // already hex rgb (hopefully :D )
2709 return v;
2712 // if everything else fails ;)
2713 return null;
2716 HTMLArea.prototype._popupDialog = function(url, action, init) {
2717 Dialog(this.popupURL(url), action, init);
2720 // paths
2722 HTMLArea.prototype.imgURL = function(file, plugin) {
2723 if (typeof plugin == "undefined")
2724 return _editor_url + file;
2725 else
2726 return _editor_url + "plugins/" + plugin + "/img/" + file;
2729 HTMLArea.prototype.popupURL = function(file) {
2730 var url = "";
2731 if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
2732 var plugin = RegExp.$1;
2733 var popup = RegExp.$2;
2734 if (!/\.html$/.test(popup))
2735 popup += ".html";
2736 url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
2737 } else
2738 url = _editor_url + this.config.popupURL + file;
2739 return url;
2743 * FIX: Internet Explorer returns an item having the _name_ equal to the given
2744 * id, even if it's not having any id. This way it can return a different form
2745 * field even if it's not a textarea. This workarounds the problem by
2746 * specifically looking to search only elements having a certain tag name.
2748 HTMLArea.getElementById = function(tag, id) {
2749 var el, i, objs = document.getElementsByTagName(tag);
2750 for (i = objs.length; --i >= 0 && (el = objs[i]);)
2751 if (el.id == id)
2752 return el;
2753 return null;
2755 // Modified version of GetHtml plugin's indent.
2756 HTMLArea.indent = function(s, sindentChar) {
2757 var c = [
2758 /*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),
2759 /*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
2760 /*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
2761 /*3*/ new RegExp().compile(/<(br|hr|img|embed|param|pre|meta|link|title|area)[^>]*>/g),//singlet tag
2762 /*4*/ new RegExp().compile(/(^|<\/(pre|script)>)(\s|[^\s])*?(<(pre|script)[^>]*>|$)/g),//find content NOT inside pre and script tags
2763 /*5*/ new RegExp().compile(/(<pre[^>]*>)(\s|[^\s])*?(<\/pre>)/g),//find content inside pre tags
2764 /*6*/ new RegExp().compile(/(^|<!--(\s|\S)*?-->)((\s|\S)*?)(?=<!--(\s|\S)*?-->|$)/g),//find content NOT inside comments
2765 /*7*/ new RegExp().compile(/<\/(table|tbody|tr|td|th|ul|ol|object|html|head|body)( [^>]*)?>/g),//blocklevel closing tag
2767 HTMLArea.__nindent = 0;
2768 HTMLArea.__sindent = "";
2769 HTMLArea.__sindentChar = (typeof sindentChar == "undefined") ? " " : sindentChar;
2771 if(HTMLArea.is_gecko) { //moz changes returns into <br> inside <pre> tags
2772 s = s.replace(c[5], function(str){return str.replace(/<br \/>/g,"\n")});
2774 s = s.replace(c[4], function(strn) { //skip pre and script tags
2775 strn = strn.replace(c[6], function(st,$1,$2,$3) { //exclude comments
2776 string = $3.replace(/[\n\r]/gi, " ").replace(/\s+/gi," ").replace(c[0], function(str) {
2777 if (str.match(c[2])) {
2778 var s = "\n" + HTMLArea.__sindent + str;
2779 // blocklevel openingtag - increase indent
2780 HTMLArea.__sindent += HTMLArea.__sindentChar;
2781 ++HTMLArea.__nindent;
2782 return s;
2783 } else if (str.match(c[1])) {
2784 // blocklevel closingtag - decrease indent
2785 --HTMLArea.__nindent;
2786 HTMLArea.__sindent = "";
2787 for (var i=HTMLArea.__nindent;i>0;--i) {
2788 HTMLArea.__sindent += HTMLArea.__sindentChar;
2790 return (str.match(c[7]) ? "\n" + HTMLArea.__sindent : "") + str;
2792 return str; // this won't actually happen
2794 return $1 + string;
2795 });return strn;
2797 if (s.charAt(0) == "\n") {
2798 return s.substring(1, s.length);
2800 s = s.replace(/ *\n/g,'\n');//strip spaces at end of lines
2801 return s;