Automatic installer.php lang files by installer_builder (20070726)
[moodle-linuxchix.git] / lib / editor / htmlarea / htmlarea.php
blobf3bbf8b908c9fe86d70033d539843b029e5cdbe6
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; }</style>\n";
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 = Math.max(parseInt(width), 588);
810 if (!HTMLArea.is_ie) {
811 height -= 2;
812 width -= 2;
815 iframe.style.width = width + "px";
816 if (this.config.sizeIncludesToolbar) {
817 // substract toolbar height
818 height -= this._toolbar.offsetHeight;
819 height -= this._statusBar.offsetHeight;
821 if (height < 0) {
822 height = 0;
824 iframe.style.height = height + "px";
826 // the editor including the toolbar now have the same size as the
827 // original textarea.. which means that we need to reduce that a bit.
828 textarea.style.width = iframe.style.width;
829 textarea.style.height = iframe.style.height;
831 if (HTMLArea.is_ie) {
832 doc.body.contentEditable = true;
835 // intercept some events; for updating the toolbar & keyboard handlers
836 HTMLArea._addEvents
837 (doc, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
838 function (event) {
839 return editor._editorEvent(HTMLArea.is_ie ? editor._iframe.contentWindow.event : event);
842 // check if any plugins have registered refresh handlers
843 for (var i in editor.plugins) {
844 var plugin = editor.plugins[i].instance;
845 if (typeof plugin.onGenerate == "function") {
846 plugin.onGenerate();
848 if (typeof plugin.onGenerateOnce == "function") {
849 plugin.onGenerateOnce();
850 plugin.onGenerateOnce = null;
854 // Moodle fix for bug Bug #2521 Too long statusbar line in IE
856 //setTimeout(function() {
857 // editor.updateToolbar();
858 //}, 250);
860 if (typeof editor.onGenerate == "function") {
861 editor.onGenerate();
866 // Switches editor mode; parameter can be "textmode" or "wysiwyg". If no
867 // parameter was passed this function toggles between modes.
868 HTMLArea.prototype.setMode = function(mode) {
869 if (typeof mode == "undefined") {
870 mode = ((this._editMode == "textmode") ? "wysiwyg" : "textmode");
872 switch (mode) {
873 case "textmode":
874 this._textArea.value = this.getHTML();
875 this._iframe.style.display = "none";
876 this._textArea.style.display = "block";
877 if (this.config.statusBar) {
878 while(this._statusBar.childNodes.length>0) {
879 this._statusBar.removeChild(this._statusBar.childNodes[0]);
882 this._statusBar.appendChild(document.createTextNode(HTMLArea.I18N.msg["TEXT_MODE"]));
884 break;
885 case "wysiwyg":
886 if (HTMLArea.is_gecko) {
887 // disable design mode before changing innerHTML
888 try {
889 this._doc.designMode = "off";
890 } catch(e) {};
892 if (!this.config.fullPage)
893 this._doc.body.innerHTML = this.getHTML();
894 else
895 this.setFullHTML(this.getHTML());
896 this._iframe.style.display = "block";
897 this._textArea.style.display = "none";
898 if (HTMLArea.is_gecko) {
899 // we need to refresh that info for Moz-1.3a
900 try {
901 this._doc.designMode = "on";
902 //this._doc.focus();
903 } catch(e) {};
905 if (this.config.statusBar) {
906 this._statusBar.innerHTML = '';
907 this._statusBar.appendChild(this._statusBarTree);
909 break;
910 default:
911 alert("Mode <" + mode + "> not defined!");
912 return false;
914 this._editMode = mode;
915 this.focusEditor();
918 HTMLArea.prototype.setFullHTML = function(html) {
919 var save_multiline = RegExp.multiline;
920 RegExp.multiline = true;
921 if (html.match(HTMLArea.RE_doctype)) {
922 this.setDoctype(RegExp.$1);
923 html = html.replace(HTMLArea.RE_doctype, "");
925 RegExp.multiline = save_multiline;
926 if (!HTMLArea.is_ie) {
927 if (html.match(HTMLArea.RE_head))
928 this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
929 if (html.match(HTMLArea.RE_body))
930 this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
931 } else {
932 var html_re = /<html>((.|\n)*?)<\/html>/i;
933 html = html.replace(html_re, "$1");
934 this._doc.open();
935 this._doc.write(html);
936 this._doc.close();
937 this._doc.body.contentEditable = true;
938 return true;
942 // Category: PLUGINS
944 HTMLArea.prototype.registerPlugin2 = function(plugin, args) {
945 if (typeof plugin == "string")
946 plugin = eval(plugin);
947 var obj = new plugin(this, args);
948 if (obj) {
949 var clone = {};
950 var info = plugin._pluginInfo;
951 for (var i in info)
952 clone[i] = info[i];
953 clone.instance = obj;
954 clone.args = args;
955 this.plugins[plugin._pluginInfo.name] = clone;
956 } else
957 alert("Can't register plugin " + plugin.toString() + ".");
960 // Create the specified plugin and register it with this HTMLArea
961 HTMLArea.prototype.registerPlugin = function() {
962 var plugin = arguments[0];
963 var args = [];
964 for (var i = 1; i < arguments.length; ++i)
965 args.push(arguments[i]);
966 this.registerPlugin2(plugin, args);
969 HTMLArea.loadPlugin = function(pluginName) {
970 var dir = _editor_url + "plugins/" + pluginName;
971 var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g,
972 function (str, l1, l2, l3) {
973 return l1 + "-" + l2.toLowerCase() + l3;
974 }).toLowerCase() + ".js";
975 var plugin_file = dir + "/" + plugin;
976 var plugin_lang = dir + "/lang/" + HTMLArea.I18N.lang + ".js";
977 HTMLArea._scripts.push(plugin_file, plugin_lang);
978 document.write("<script type='text/javascript' src='" + plugin_file + "'></script>");
979 document.write("<script type='text/javascript' src='" + plugin_lang + "'></script>");
982 HTMLArea.loadStyle = function(style, plugin) {
983 var url = _editor_url || '';
984 if (typeof plugin != "undefined") {
985 url += "plugins/" + plugin + "/";
987 url += style;
988 document.write("<style type='text/css'>@import url(" + url + ");</style>");
990 HTMLArea.loadStyle("htmlarea.css");
992 // Category: EDITOR UTILITIES
994 // The following function is a slight variation of the word cleaner code posted
995 // by Weeezl (user @ InteractiveTools forums).
996 HTMLArea.prototype._wordClean = function() {
997 var D = this.getInnerHTML();
998 if (D.indexOf("class=Mso") >= 0 || D.indexOf("mso") >= 0 || D.indexOf("Mso") >= 0) {
1000 // make one line
1001 D = D.replace(/\r\n/g, '\[br\]').
1002 replace(/\n/g, '').
1003 replace(/\r/g, '').
1004 replace(/\&nbsp\;/g,' ');
1006 // keep tags, strip attributes
1007 D = D.replace(/ class=[^\s|>]*/gi,'').
1008 //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">').
1009 replace(/ style=\"[^>]*\"/gi,'').
1010 replace(/ align=[^\s|>]*/gi,'');
1012 //clean up tags
1013 D = D.replace(/<b [^>]*>/gi,'<b>').
1014 replace(/<i [^>]*>/gi,'<i>').
1015 replace(/<li [^>]*>/gi,'<li>').
1016 replace(/<ul [^>]*>/gi,'<ul>');
1018 // replace outdated tags
1019 D = D.replace(/<b>/gi,'<strong>').
1020 replace(/<\/b>/gi,'</strong>');
1022 // mozilla doesn't like <em> tags
1023 D = D.replace(/<em>/gi,'<i>').
1024 replace(/<\/em>/gi,'</i>');
1026 // kill unwanted tags
1027 D = D.replace(/<\?xml:[^>]*>/g, ''). // Word xml
1028 replace(/<\/?st1:[^>]*>/g,''). // Word SmartTags
1029 replace(/<\/?[a-z]\:[^>]*>/g,''). // All other funny Word non-HTML stuff
1030 replace(/<\/?font[^>]*>/gi,''). // Disable if you want to keep font formatting
1031 replace(/<\/?span[^>]*>/gi,' ').
1032 replace(/<\/?div[^>]*>/gi,' ').
1033 replace(/<\/?pre[^>]*>/gi,' ').
1034 replace(/<(\/?)(h[1-6]+)[^>]*>/gi,'<$1$2>');
1036 // Lorenzo Nicola's addition
1037 // to get rid off silly word generated tags.
1038 D = D.replace(/<!--\[[^\]]*\]-->/gi,' ');
1040 //remove empty tags
1041 //D = D.replace(/<strong><\/strong>/gi,'').
1042 //replace(/<i><\/i>/gi,'').
1043 //replace(/<P[^>]*><\/P>/gi,'');
1044 D = D.replace(/<h[1-6]+>\s?<\/h[1-6]+>/gi, ''); // Remove empty headings
1046 // nuke double tags
1047 oldlen = D.length + 1;
1048 while(oldlen > D.length) {
1049 oldlen = D.length;
1050 // join us now and free the tags, we'll be free hackers, we'll be free... ;-)
1051 D = D.replace(/<([a-z][a-z]*)> *<\/\1>/gi,' ').
1052 replace(/<([a-z][a-z]*)> *<([a-z][^>]*)> *<\/\1>/gi,'<$2>');
1054 D = D.replace(/<([a-z][a-z]*)><\1>/gi,'<$1>').
1055 replace(/<\/([a-z][a-z]*)><\/\1>/gi,'<\/$1>');
1057 // nuke double spaces
1058 D = D.replace(/ */gi,' ');
1060 // Split into lines and remove
1061 // empty lines and add carriage returns back
1062 var splitter = /\[br\]/g;
1063 var emptyLine = /^\s+\s+$/g;
1064 var strHTML = '';
1065 var toLines = D.split(splitter);
1066 for (var i = 0; i < toLines.length; i++) {
1067 var line = toLines[i];
1068 if (line.length < 1) {
1069 continue;
1072 if (emptyLine.test(line)) {
1073 continue;
1076 line = line.replace(/^\s+\s+$/g, '');
1077 strHTML += line + '\n';
1079 D = strHTML;
1080 strHTML = '';
1082 this.setHTML(D);
1083 this.updateToolbar();
1087 HTMLArea.prototype.forceRedraw = function() {
1088 this._doc.body.style.visibility = "hidden";
1089 this._doc.body.style.visibility = "visible";
1090 // this._doc.body.innerHTML = this.getInnerHTML();
1093 // focuses the iframe window. returns a reference to the editor document.
1094 HTMLArea.prototype.focusEditor = function() {
1095 switch (this._editMode) {
1096 case "wysiwyg" : this._iframe.contentWindow.focus(); break;
1097 case "textmode": this._textArea.focus(); break;
1098 default : alert("ERROR: mode " + this._editMode + " is not defined");
1100 return this._doc;
1103 // takes a snapshot of the current text (for undo)
1104 HTMLArea.prototype._undoTakeSnapshot = function() {
1105 ++this._undoPos;
1106 if (this._undoPos >= this.config.undoSteps) {
1107 // remove the first element
1108 this._undoQueue.shift();
1109 --this._undoPos;
1111 // use the fasted method (getInnerHTML);
1112 var take = true;
1113 var txt = this.getInnerHTML();
1114 if (this._undoPos > 0)
1115 take = (this._undoQueue[this._undoPos - 1] != txt);
1116 if (take) {
1117 this._undoQueue[this._undoPos] = txt;
1118 } else {
1119 this._undoPos--;
1123 HTMLArea.prototype.undo = function() {
1124 if (this._undoPos > 0) {
1125 var txt = this._undoQueue[--this._undoPos];
1126 if (txt) this.setHTML(txt);
1127 else ++this._undoPos;
1131 HTMLArea.prototype.redo = function() {
1132 if (this._undoPos < this._undoQueue.length - 1) {
1133 var txt = this._undoQueue[++this._undoPos];
1134 if (txt) this.setHTML(txt);
1135 else --this._undoPos;
1139 // updates enabled/disable/active state of the toolbar elements
1140 HTMLArea.prototype.updateToolbar = function(noStatus) {
1141 var doc = this._doc;
1142 var text = (this._editMode == "textmode");
1143 var ancestors = null;
1144 if (!text) {
1145 ancestors = this.getAllAncestors();
1146 if (this.config.statusBar && !noStatus) {
1148 while(this._statusBarTree.childNodes.length>0) {
1149 this._statusBarTree.removeChild(this._statusBarTree.childNodes[0]);
1152 this._statusBarTree.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
1154 for (var i = ancestors.length; --i >= 0;) {
1155 var el = ancestors[i];
1156 if (!el) {
1157 // hell knows why we get here; this
1158 // could be a classic example of why
1159 // it's good to check for conditions
1160 // that are impossible to happen ;-)
1161 continue;
1163 var a = document.createElement("a");
1164 a.href = "#";
1165 a.el = el;
1166 a.editor = this;
1167 a.onclick = function() {
1168 this.blur();
1169 this.editor.selectNodeContents(this.el);
1170 this.editor.updateToolbar(true);
1171 return false;
1173 a.oncontextmenu = function() {
1174 // TODO: add context menu here
1175 this.blur();
1176 var info = "Inline style:\n\n";
1177 info += this.el.style.cssText.split(/;\s*/).join(";\n");
1178 alert(info);
1179 return false;
1181 var txt = el.tagName.toLowerCase();
1182 a.title = el.style.cssText;
1183 if (el.id) {
1184 txt += "#" + el.id;
1186 if (el.className) {
1187 txt += "." + el.className;
1189 a.appendChild(document.createTextNode(txt));
1190 this._statusBarTree.appendChild(a);
1191 if (i != 0) {
1192 this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
1197 for (var i in this._toolbarObjects) {
1198 var btn = this._toolbarObjects[i];
1199 var cmd = i;
1200 var inContext = true;
1201 if (btn.context && !text) {
1202 inContext = false;
1203 var context = btn.context;
1204 var attrs = [];
1205 if (/(.*)\[(.*?)\]/.test(context)) {
1206 context = RegExp.$1;
1207 attrs = RegExp.$2.split(",");
1209 context = context.toLowerCase();
1210 var match = (context == "*");
1211 for (var k in ancestors) {
1212 if (!ancestors[k]) {
1213 // the impossible really happens.
1214 continue;
1216 if (match || (ancestors[k].tagName.toLowerCase() == context)) {
1217 inContext = true;
1218 for (var ka in attrs) {
1219 if (!eval("ancestors[k]." + attrs[ka])) {
1220 inContext = false;
1221 break;
1224 if (inContext) {
1225 break;
1230 btn.state("enabled", (!text || btn.text) && inContext);
1231 if (typeof cmd == "function") {
1232 continue;
1234 // look-it-up in the custom dropdown boxes
1235 var dropdown = this.config.customSelects[cmd];
1236 if ((!text || btn.text) && (typeof dropdown != "undefined")) {
1237 dropdown.refresh(this);
1238 continue;
1240 switch (cmd) {
1241 case "fontname":
1242 case "fontsize":
1243 case "formatblock":
1244 if (!text) try {
1245 var value = ("" + doc.queryCommandValue(cmd)).toLowerCase();
1246 if (!value) {
1247 // FIXME: what do we do here?
1248 break;
1250 var options = this.config[cmd];
1251 var k = 0;
1252 // btn.element.selectedIndex = 0;
1253 for (var j in options) {
1254 // FIXME: the following line is scary.
1255 if ((j.toLowerCase() == value) ||
1256 (options[j].substr(0, value.length).toLowerCase() == value)) {
1257 btn.element.selectedIndex = k;
1258 break;
1260 ++k;
1262 } catch(e) {};
1263 break;
1264 case "language":
1265 if (!text) try {
1266 var value;
1267 parentEl = this.getParentElement();
1268 if (parentEl.getAttribute('lang')) {
1269 // A language was previously defined for the block.
1270 if (parentEl.getAttribute('class') == 'multilang') {
1271 value = parentEl.getAttribute('lang')+'_ML';
1272 } else {
1273 value = parentEl.getAttribute('lang');
1275 } else {
1276 value = '';
1278 var options = this.config[cmd];
1279 var k = 0;
1280 for (var j in options) {
1281 // FIXME: the following line is scary.
1282 if ((j.toLowerCase() == value) ||
1283 (options[j].substr(0, value.length).toLowerCase() == value)) {
1284 btn.element.selectedIndex = k;
1285 break;
1287 ++k;
1289 } catch(e) {};
1290 break;
1291 case "textindicator":
1292 if (!text) {
1293 try {with (btn.element.style) {
1294 backgroundColor = HTMLArea._makeColor(
1295 doc.queryCommandValue(HTMLArea.is_ie ? "backcolor" : "hilitecolor"));
1296 if (/transparent/i.test(backgroundColor)) {
1297 // Mozilla
1298 backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("backcolor"));
1300 color = HTMLArea._makeColor(doc.queryCommandValue("forecolor"));
1301 fontFamily = doc.queryCommandValue("fontname");
1302 fontWeight = doc.queryCommandState("bold") ? "bold" : "normal";
1303 fontStyle = doc.queryCommandState("italic") ? "italic" : "normal";
1304 }} catch (e) {
1305 // alert(e + "\n\n" + cmd);
1308 break;
1309 case "htmlmode": btn.state("active", text); break;
1310 case "lefttoright":
1311 case "righttoleft":
1312 var el = this.getParentElement();
1313 while (el && !HTMLArea.isBlockElement(el))
1314 el = el.parentNode;
1315 if (el)
1316 btn.state("active", (el.style.direction == ((cmd == "righttoleft") ? "rtl" : "ltr")));
1317 break;
1318 default:
1319 try {
1320 btn.state("active", (!text && doc.queryCommandState(cmd)));
1321 } catch (e) {}
1324 // take undo snapshots
1325 if (this._customUndo && !this._timerUndo) {
1326 this._undoTakeSnapshot();
1327 var editor = this;
1328 this._timerUndo = setTimeout(function() {
1329 editor._timerUndo = null;
1330 }, this.config.undoTimeout);
1332 // check if any plugins have registered refresh handlers
1333 for (var i in this.plugins) {
1334 var plugin = this.plugins[i].instance;
1335 if (typeof plugin.onUpdateToolbar == "function")
1336 plugin.onUpdateToolbar();
1340 /** Returns a node after which we can insert other nodes, in the current
1341 * selection. The selection is removed. It splits a text node, if needed.
1343 HTMLArea.prototype.insertNodeAtSelection = function(toBeInserted) {
1344 if (!HTMLArea.is_ie) {
1345 var sel = this._getSelection();
1346 var range = this._createRange(sel);
1347 // remove the current selection
1348 sel.removeAllRanges();
1349 range.deleteContents();
1350 var node = range.startContainer;
1351 var pos = range.startOffset;
1352 switch (node.nodeType) {
1353 case 3: // Node.TEXT_NODE
1354 // we have to split it at the caret position.
1355 if (toBeInserted.nodeType == 3) {
1356 // do optimized insertion
1357 node.insertData(pos, toBeInserted.data);
1358 range = this._createRange();
1359 range.setEnd(node, pos + toBeInserted.length);
1360 range.setStart(node, pos + toBeInserted.length);
1361 sel.addRange(range);
1362 } else {
1363 node = node.splitText(pos);
1364 var selnode = toBeInserted;
1365 if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1366 selnode = selnode.firstChild;
1368 node.parentNode.insertBefore(toBeInserted, node);
1369 this.selectNodeContents(selnode);
1370 this.updateToolbar();
1372 break;
1373 case 1: // Node.ELEMENT_NODE
1374 var selnode = toBeInserted;
1375 if (toBeInserted.nodeType == 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1376 selnode = selnode.firstChild;
1378 node.insertBefore(toBeInserted, node.childNodes[pos]);
1379 this.selectNodeContents(selnode);
1380 this.updateToolbar();
1381 break;
1383 } else {
1384 return null; // this function not yet used for IE <FIXME>
1388 // Returns the deepest node that contains both endpoints of the selection.
1389 HTMLArea.prototype.getParentElement = function() {
1390 var sel = this._getSelection();
1391 var range = this._createRange(sel);
1392 if (HTMLArea.is_ie) {
1393 switch (sel.type) {
1394 case "Text":
1395 case "None":
1396 return range.parentElement();
1397 case "Control":
1398 return range.item(0);
1399 default:
1400 return this._doc.body;
1402 } else try {
1403 var p = range.commonAncestorContainer;
1404 if (!range.collapsed && range.startContainer == range.endContainer &&
1405 range.startOffset - range.endOffset <= 1 && range.startContainer.hasChildNodes())
1406 p = range.startContainer.childNodes[range.startOffset];
1408 alert(range.startContainer + ":" + range.startOffset + "\n" +
1409 range.endContainer + ":" + range.endOffset);
1411 while (p.nodeType == 3) {
1412 p = p.parentNode;
1414 return p;
1415 } catch (e) {
1416 return null;
1420 // Returns an array with all the ancestor nodes of the selection.
1421 HTMLArea.prototype.getAllAncestors = function() {
1422 var p = this.getParentElement();
1423 var a = [];
1424 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1425 a.push(p);
1426 p = p.parentNode;
1428 a.push(this._doc.body);
1429 return a;
1432 // Selects the contents inside the given node
1433 HTMLArea.prototype.selectNodeContents = function(node, pos) {
1434 this.focusEditor();
1435 this.forceRedraw();
1436 var range;
1437 var collapsed = (typeof pos != "undefined");
1438 if (HTMLArea.is_ie) {
1439 range = this._doc.body.createTextRange();
1440 range.moveToElementText(node);
1441 (collapsed) && range.collapse(pos);
1442 range.select();
1443 } else {
1444 var sel = this._getSelection();
1445 range = this._doc.createRange();
1446 range.selectNodeContents(node);
1447 (collapsed) && range.collapse(pos);
1448 sel.removeAllRanges();
1449 sel.addRange(range);
1453 // Call this function to insert HTML code at the current position. It deletes
1454 // the selection, if any.
1455 HTMLArea.prototype.insertHTML = function(html) {
1456 var sel = this._getSelection();
1457 var range = this._createRange(sel);
1458 if (HTMLArea.is_ie) {
1459 range.pasteHTML(html);
1460 } else {
1461 // construct a new document fragment with the given HTML
1462 var fragment = this._doc.createDocumentFragment();
1463 var div = this._doc.createElement("div");
1464 div.innerHTML = html;
1465 while (div.firstChild) {
1466 // the following call also removes the node from div
1467 fragment.appendChild(div.firstChild);
1469 // this also removes the selection
1470 var node = this.insertNodeAtSelection(fragment);
1474 // Call this function to surround the existing HTML code in the selection with
1475 // your tags. FIXME: buggy! This function will be deprecated "soon".
1476 HTMLArea.prototype.surroundHTML = function(startTag, endTag) {
1477 var html = this.getSelectedHTML();
1478 // the following also deletes the selection
1479 this.insertHTML(startTag + html + endTag);
1482 /// Retrieve the selected block
1483 HTMLArea.prototype.getSelectedHTML = function() {
1484 var sel = this._getSelection();
1485 var range = this._createRange(sel);
1486 var existing = null;
1487 if (HTMLArea.is_ie) {
1488 existing = range.htmlText;
1489 } else {
1490 existing = HTMLArea.getHTML(range.cloneContents(), false, this);
1492 return existing;
1495 /// Return true if we have some selection
1496 HTMLArea.prototype.hasSelectedText = function() {
1497 // FIXME: come _on_ mishoo, you can do better than this ;-)
1498 return this.getSelectedHTML() != '';
1501 HTMLArea.prototype._createLink = function(link) {
1502 var editor = this;
1503 var allinks = editor._doc.getElementsByTagName('A');
1504 var anchors = new Array();
1505 for(var i = 0; i < allinks.length; i++) {
1506 var attrname = allinks[i].getAttribute('name');
1507 if((HTMLArea.is_ie ? attrname.length > 0 : attrname != null)) {
1508 anchors[i] = allinks[i].getAttribute('name');
1511 var outparam = null;
1512 if (typeof link == "undefined") {
1513 link = this.getParentElement();
1514 if (link && !/^a$/i.test(link.tagName)) {
1515 if(link.tagName.toLowerCase() != 'img') {
1516 link = null;
1517 var sel = this._getSelection();
1518 var rng = this._createRange(sel);
1519 var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1520 if(len < 1) {
1521 alert("<?php print_string("alertnoselectedtext","editor");?>");
1522 return false;
1525 link = null;
1528 if (link) {
1529 outparam = {
1530 f_href : HTMLArea.is_ie ? editor.stripBaseURL(link.href) : link.getAttribute("href"),
1531 f_title : link.title,
1532 f_target : link.target,
1533 f_anchors: anchors
1535 } else {
1536 outparam = {
1537 f_anchors:anchors };
1539 this._popupDialog("link_std.php?id=<?php echo $id; ?>", function(param) {
1540 if (!param) {
1541 return false;
1543 var a = link;
1544 if (!a) {
1545 // Since startContainer check does not work
1546 // very well in Moz use just insertHTML.
1547 var sel = editor._getSelection();
1548 var range = editor._createRange(sel);
1549 var strLink = '<a href="'+ param.f_href.trim() +'"';
1550 if ( param.f_title != "" ) {
1551 strLink += ' title="'+ param.f_title.trim() +'"';
1553 if ( param.f_target != "" ) {
1554 strLink += ' target="'+ param.f_target.trim() +'"';
1556 strLink += '>';
1557 strLink += (!HTMLArea.is_ie) ? sel : range.text;
1558 strLink += '</a>';
1559 editor.insertHTML(strLink);
1560 } else {
1561 var href = param.f_href.trim();
1562 editor.selectNodeContents(a);
1563 if (href == "") {
1564 editor._doc.execCommand("unlink", false, null);
1565 editor.updateToolbar();
1566 return false;
1567 } else {
1568 a.href = href;
1571 if (!(a && /^a$/i.test(a.tagName))) {
1572 return false;
1574 a.target = param.f_target.trim();
1575 a.title = param.f_title.trim();
1576 editor.selectNodeContents(a);
1577 editor.updateToolbar();
1578 }, outparam);
1581 // Called when the user clicks on "InsertImage" button. If an image is already
1582 // there, it will just modify it's properties.
1583 HTMLArea.prototype._insertImage = function(image) {
1585 // Make sure that editor has focus
1586 this.focusEditor();
1587 var editor = this; // for nested functions
1588 var outparam = null;
1589 if (typeof image == "undefined") {
1590 image = this.getParentElement();
1591 if (image && !/^img$/i.test(image.tagName))
1592 image = null;
1594 if (image) outparam = {
1595 f_url : HTMLArea.is_ie ? editor.stripBaseURL(image.src) : image.getAttribute("src"),
1596 f_alt : image.alt,
1597 f_border : image.border,
1598 f_align : image.align,
1599 f_vert : image.vspace,
1600 f_horiz : image.hspace,
1601 f_width : image.width,
1602 f_height : image.height
1604 this._popupDialog("<?php
1605 if(!empty($id) and has_capability('moodle/course:managefiles', get_context_instance(CONTEXT_COURSE, $id))) {
1606 echo "insert_image.php?id=$id";
1607 } else {
1608 echo "insert_image_std.php?id=$id";
1609 }?>", function(param) {
1610 if (!param) { // user must have pressed Cancel
1611 return false;
1613 var img = image;
1614 if (!img) {
1615 var sel = editor._getSelection();
1616 var range = editor._createRange(sel);
1617 if (HTMLArea.is_ie) {
1618 editor._doc.execCommand("insertimage", false, param.f_url);
1620 if (HTMLArea.is_ie) {
1621 img = range.parentElement();
1622 // wonder if this works...
1623 if (img.tagName.toLowerCase() != "img") {
1624 img = img.previousSibling;
1626 } else {
1627 // MOODLE HACK: startContainer.perviousSibling
1628 // Doesn't work so we'll use createElement and
1629 // insertNodeAtSelection
1630 //img = range.startContainer.previousSibling;
1631 var img = editor._doc.createElement("img");
1632 img.setAttribute("src",""+ param.f_url +"");
1633 img.setAttribute("alt",""+ param.f_alt +"");
1634 editor.insertNodeAtSelection(img);
1636 } else {
1637 img.src = param.f_url;
1639 for (field in param) {
1640 var value = param[field];
1641 switch (field) {
1642 case "f_alt" : img.alt = value; img.title = value; break;
1643 case "f_border" : img.border = parseInt(value || "0"); break;
1644 case "f_align" : img.align = value; break;
1645 case "f_vert" : img.vspace = parseInt(value || "0"); break;
1646 case "f_horiz" : img.hspace = parseInt(value || "0"); break;
1647 case "f_width" :
1648 if(value != 0) {
1649 img.width = parseInt(value);
1650 } else {
1651 break;
1653 break;
1654 case "f_height" :
1655 if(value != 0) {
1656 img.height = parseInt(value);
1657 } else {
1658 break;
1660 break;
1663 }, outparam);
1666 // Called when the user clicks the Insert Table button
1667 HTMLArea.prototype._insertTable = function() {
1668 var sel = this._getSelection();
1669 var range = this._createRange(sel);
1670 var editor = this; // for nested functions
1671 this._popupDialog("insert_table.php?id=<?php echo $id; ?>", function(param) {
1672 if (!param) { // user must have pressed Cancel
1673 return false;
1675 var doc = editor._doc;
1676 // create the table element
1677 var table = doc.createElement("table");
1678 // assign the given arguments
1679 for (var field in param) {
1680 var value = param[field];
1681 if (!value) {
1682 continue;
1684 switch (field) {
1685 case "f_width" : table.width = value + param["f_unit"]; break;
1686 case "f_align" : table.align = value; break;
1687 case "f_border" : table.border = parseInt(value); break;
1688 case "f_spacing" : table.cellspacing = parseInt(value); break;
1689 case "f_padding" : table.cellpadding = parseInt(value); break;
1692 var tbody = doc.createElement("tbody");
1693 table.appendChild(tbody);
1694 for (var i = 0; i < param["f_rows"]; ++i) {
1695 var tr = doc.createElement("tr");
1696 tbody.appendChild(tr);
1697 for (var j = 0; j < param["f_cols"]; ++j) {
1698 var td = doc.createElement("td");
1699 /// Moodle hack
1700 if(param["f_unit"] == "px") {
1701 tdwidth = Math.round(table.width / param["f_cols"]);
1702 } else {
1703 tdwidth = Math.round(100 / param["f_cols"]);
1705 td.setAttribute("width",tdwidth + param["f_unit"]);
1706 td.setAttribute("valign","top");
1707 /// Moodle hack -ends
1708 tr.appendChild(td);
1709 // Mozilla likes to see something inside the cell.
1710 (HTMLArea.is_gecko) && td.appendChild(doc.createElement("br"));
1713 if (HTMLArea.is_ie) {
1714 range.pasteHTML(table.outerHTML);
1715 } else {
1716 // insert the table
1717 editor.insertNodeAtSelection(table);
1719 return true;
1720 }, null);
1723 /// Moodle hack - insertSmile
1724 HTMLArea.prototype._insertSmile = function() {
1725 // Make sure that editor has focus
1726 this.focusEditor();
1727 var sel = this._getSelection();
1728 var range = this._createRange(sel);
1729 var editor = this; // for nested functions
1730 this._popupDialog("dlg_ins_smile.php?id=<?php echo $id; ?>", function(imgString) {
1731 if(!imgString) {
1732 return false;
1734 if (HTMLArea.is_ie) {
1735 range.pasteHTML(imgString);
1736 } else {
1737 editor.insertHTML(imgString);
1739 return true;
1740 }, null);
1743 HTMLArea.prototype._insertChar = function() {
1744 var sel = this._getSelection();
1745 var range = this._createRange(sel);
1746 var editor = this; // for nested functions
1747 this._popupDialog("dlg_ins_char.php?id=<?php echo $id; ?>", function(sChar) {
1748 if(!sChar) {
1749 return false;
1751 if (HTMLArea.is_ie) {
1752 range.pasteHTML(sChar);
1753 } else {
1754 // insert the table
1755 editor.insertHTML(sChar);
1757 return true;
1758 }, null);
1761 HTMLArea.prototype._removelink = function() {
1762 var editor = this;
1763 link = this.getParentElement();
1764 editor.selectNodeContents(link);
1766 this._doc.execCommand("unlink", false, null);
1767 this.focusEditor();
1770 HTMLArea.prototype._createanchor = function () {
1771 var editor = this;
1772 var sel = this._getSelection();
1773 var rng = this._createRange(sel);
1774 var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1775 if(len < 1) {
1776 alert("<?php print_string("alertnoselectedtext","editor");?>");
1777 return false;
1779 this._popupDialog("createanchor.php?id=<?php echo $id; ?>", function(objAn) {
1780 if(!objAn) {
1781 return false;
1783 var str = '<a name="'+ objAn.anchor+'">';
1784 str += HTMLArea.is_ie ? rng.text : sel ;
1785 str += '</a>';
1786 editor.insertHTML(str);
1787 },null);
1790 HTMLArea.prototype._nolinktag = function () {
1792 var editor = this;
1793 var sel = this._getSelection();
1794 var rng = this._createRange(sel);
1795 var len = HTMLArea.is_ie ? rng.text.toString().length : sel.toString().length;
1797 if (len < 1) {
1798 alert("<?php print_string("alertnoselectedtext","editor");?>");
1799 return false;
1801 var str = '<span class="nolink">';
1802 str += HTMLArea.is_ie ? rng.text : sel;
1803 str += '</span>';
1804 editor.insertHTML(str);
1805 this.focusEditor();
1809 HTMLArea.prototype._searchReplace = function() {
1811 var editor = this;
1812 var selectedtxt = "";
1813 <?php
1814 $strreplaced = addslashes(get_string('itemsreplaced','editor'));
1815 $strnotfound = addslashes(get_string('searchnotfound','editor'));
1817 var strReplaced = '<?php echo $strreplaced ?>';
1818 var strNotfound = '<?php echo $strnotfound ?>';
1819 var ile;
1821 //in source mode mozilla show errors, try diffrent method
1822 if (editor._editMode == "wysiwyg") {
1823 selectedtxt = editor.getSelectedHTML();
1824 } else {
1825 if (HTMLArea.is_ie) {
1826 selectedtxt = document.selection.createRange().text;
1827 } else {
1828 selectedtxt = getMozSelection(editor._textArea);
1832 outparam = {
1833 f_search : selectedtxt
1836 //Call Search And Replace popup window
1837 editor._popupDialog( "searchandreplace.php?id=<?php echo $id; ?>", function( entity ) {
1838 if ( !entity ) {
1839 //user must have pressed Cancel
1840 return false;
1842 var text = editor.getHTML();
1843 var search = entity[0];
1844 var replace = entity[1];
1845 var delim = entity[2];
1846 var regularx = entity[3];
1847 var closesar = entity[4];
1848 ile = 0;
1849 if (search.length < 1) {
1850 alert ("Enter a search word! \n search for: " + entity[0]);
1851 } else {
1852 if (regularx) {
1853 var regX = new RegExp (search, delim) ;
1854 var text = text.replace ( regX,
1855 function (str, n) {
1856 // Increment our counter variable.
1857 ile++ ;
1858 //return replace ;
1859 return str.replace( regX, replace) ;
1863 } else {
1864 while (text.indexOf(search)>-1) {
1865 pos = text.indexOf(search);
1866 text = "" + (text.substring(0, pos) + replace + text.substring((pos + search.length), text.length));
1867 ile++;
1871 editor.setHTML(text);
1872 editor.forceRedraw();
1873 if (ile > 0) {
1874 alert(ile + ' ' + strReplaced);
1875 } else {
1876 alert (strNotfound + "\n");
1879 }, outparam);
1881 function getMozSelection(txtarea) {
1882 var selLength = txtarea.textLength;
1883 var selStart = txtarea.selectionStart;
1884 var selEnd = txtarea.selectionEnd;
1885 if (selEnd==1 || selEnd==2) selEnd=selLength;
1886 return (txtarea.value).substring(selStart, selEnd);
1890 /// Moodle hack's ends
1892 // Category: EVENT HANDLERS
1894 // el is reference to the SELECT object
1895 // txt is the name of the select field, as in config.toolbar
1896 HTMLArea.prototype._comboSelected = function(el, txt) {
1897 this.focusEditor();
1898 var value = el.options[el.selectedIndex].value;
1899 switch (txt) {
1900 case "fontname":
1901 case "fontsize": this.execCommand(txt, false, value); break;
1902 case "language":
1903 this.setLang(value);
1904 break;
1905 case "formatblock":
1906 (HTMLArea.is_ie) && (value = "<" + value + ">");
1907 this.execCommand(txt, false, value);
1908 break;
1909 default:
1910 // try to look it up in the registered dropdowns
1911 var dropdown = this.config.customSelects[txt];
1912 if (typeof dropdown != "undefined") {
1913 dropdown.action(this);
1914 } else {
1915 alert("FIXME: combo box " + txt + " not implemented");
1922 * Used to set the language for the selected content.
1923 * We use the <span lang="en" class="multilang">content</span> format for
1924 * content that should be marked for multilang filter use, and
1925 * <span lang="en">content</span> for normal content for which we want to
1926 * set the language (for screen reader usage, for example).
1928 HTMLArea.prototype.setLang = function(lang) {
1930 if (lang == 'multi') {
1931 // This is just the separator in the dropdown. Does nothing.
1932 return;
1935 var editor = this;
1936 var selectedHTML = editor.getSelectedHTML();
1937 var multiLang = false;
1939 var re = new RegExp('_ML', 'g');
1940 if (lang.match(re)) {
1941 multiLang = true;
1942 lang = lang.replace(re, '');
1945 // Remove all lang attributes from span tags in selected html.
1946 selectedHTML = selectedHTML.replace(/(<span[^>]*)lang="[^"]*"([^>]*>)/, "$1$2");
1947 selectedHTML = selectedHTML.replace(/(<span[^>]*)class="multilang"([^>]*>)/, "$1$2");
1949 // If a span tag is now empty, delete it.
1950 selectedHTML = selectedHTML.replace(/<span\s*>(.*?)<\/span>/, "$1");
1953 var parentEl = this.getParentElement();
1954 var insertNewSpan = false;
1956 if (parentEl.nodeName == 'SPAN' && parentEl.getAttribute('lang')) {
1957 // A language was previously defined for the current block.
1958 // Check whether the selected text makes up the whole of the block
1959 // contents.
1960 var re = new RegExp(parentEl.innerHTML);
1962 if (selectedHTML.match(re)) {
1963 // The selected text makes up the whole of the span block.
1964 if (lang != '') {
1965 parentEl.setAttribute('lang', lang);
1966 if (multiLang) {
1967 parentEl.setAttribute('class', 'multilang');
1969 } else {
1970 parentEl.removeAttribute('lang');
1972 var classAttr = parentEl.getAttribute('class');
1973 if (classAttr) {
1974 classAttr = classAttr.replace(/multilang/, '').trim();
1976 if (classAttr == '') {
1977 parentEl.removeAttribute('class');
1979 if (parentEl.attributes.length == 0) {
1980 // The span is no longer needed.
1981 for (i=0; i<parentEl.childNodes.length; i++) {
1982 parentEl.parentNode.insertBefore(parentEl.childNodes[i], parentEl);
1984 parentEl.parentNode.removeChild(parentEl);
1987 } else {
1988 insertNewSpan = true;
1990 } else {
1991 insertNewSpan = true;
1994 if (insertNewSpan && lang != '') {
1995 var str = '<span lang="'+lang.trim()+'"';
1996 if (multiLang) {
1997 str += ' class="multilang"';
1999 str += '>';
2000 str += selectedHTML;
2001 str += '</span>';
2002 editor.insertHTML(str);
2007 // the execCommand function (intercepts some commands and replaces them with
2008 // our own implementation)
2009 HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
2010 var editor = this; // for nested functions
2011 this.focusEditor();
2012 cmdID = cmdID.toLowerCase();
2013 switch (cmdID) {
2014 case "htmlmode" : this.setMode(); break;
2015 case "hilitecolor":
2016 (HTMLArea.is_ie) && (cmdID = "backcolor");
2017 case "forecolor":
2018 this._popupDialog("select_color.php?id=<?php echo $id; ?>", function(color) {
2019 if (color) { // selection not canceled
2020 editor._doc.execCommand(cmdID, false, "#" + color);
2022 }, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID)));
2023 break;
2024 case "createanchor": this._createanchor(); break;
2025 case "createlink":
2026 this._createLink();
2027 break;
2028 case "unlink": this._removelink(); break;
2029 case "nolink": this._nolinktag(); break;
2030 case "popupeditor":
2031 // this object will be passed to the newly opened window
2032 HTMLArea._object = this;
2033 if (HTMLArea.is_ie) {
2035 window.open(this.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen",
2036 "toolbar=no,location=no,directories=no,status=no,menubar=no," +
2037 "scrollbars=no,resizable=yes,width=800,height=600");
2039 } else {
2040 window.open(this.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen",
2041 "toolbar=no,menubar=no,personalbar=no,width=800,height=600," +
2042 "scrollbars=no,resizable=yes");
2044 break;
2045 case "undo":
2046 case "redo":
2047 if (this._customUndo)
2048 this[cmdID]();
2049 else
2050 this._doc.execCommand(cmdID, UI, param);
2051 break;
2052 case "inserttable": this._insertTable(); break;
2053 case "insertimage": this._insertImage(); break;
2054 case "insertsmile": this._insertSmile(); break;
2055 case "insertchar": this._insertChar(); break;
2056 case "searchandreplace": this._searchReplace(); break;
2057 case "about" : this._popupDialog("about.html", null, this); break;
2058 case "showhelp" : window.open(_editor_url + "reference.html", "ha_help"); break;
2060 case "killword": this._wordClean(); break;
2062 case "cut":
2063 case "copy":
2064 case "paste":
2065 try {
2066 // Paste first then clean
2067 this._doc.execCommand(cmdID, UI, param);
2068 if (this.config.killWordOnPaste) {
2069 this._wordClean();
2071 } catch (e) {
2072 if (HTMLArea.is_gecko) {
2073 if (confirm("<?php
2074 $strmoz = get_string('cutpastemozilla','editor');
2075 $strmoz = preg_replace("/[\n|\r]+/", "", $strmoz);
2076 $strmoz = str_replace('<br />', '\\n', $strmoz);
2078 echo addslashes($strmoz);
2080 ?>"))
2081 window.open("http://moodle.org/mozillahelp");
2084 break;
2085 case "lefttoright":
2086 case "righttoleft":
2087 var dir = (cmdID == "righttoleft") ? "rtl" : "ltr";
2088 var el = this.getParentElement();
2089 while (el && !HTMLArea.isBlockElement(el))
2090 el = el.parentNode;
2091 if (el) {
2092 if (el.style.direction == dir)
2093 el.style.direction = "";
2094 else
2095 el.style.direction = dir;
2097 break;
2098 default: this._doc.execCommand(cmdID, UI, param);
2100 this.updateToolbar();
2101 return false;
2106 * A generic event handler for things that happen in the IFRAME's document.
2107 * This function also handles key bindings.
2109 HTMLArea.prototype._editorEvent = function(ev) {
2111 var editor = this;
2112 var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (ev.type == "keypress");
2114 if (keyEvent) {
2116 for (var i in editor.plugins) {
2117 var plugin = editor.plugins[i].instance;
2118 if (typeof plugin.onKeyPress == "function") plugin.onKeyPress(ev);
2121 var sel = null;
2122 var range = null;
2123 var key = String.fromCharCode(HTMLArea.is_ie ? ev.keyCode : ev.charCode).toLowerCase();
2124 var cmd = null;
2125 var value = null;
2127 if (ev.ctrlKey && !ev.altKey) {
2129 * Ctrl modifier only.
2130 * We use these for shortcuts that change existing content,
2131 * e.g. make text bold.
2133 switch (key) {
2135 case 'a':
2136 // Select all.
2137 if (!HTMLArea.is_ie) {
2138 // KEY select all
2139 sel = this._getSelection();
2140 sel.removeAllRanges();
2141 range = this._createRange();
2142 range.selectNodeContents(this._doc.body);
2143 sel.addRange(range);
2144 HTMLArea._stopEvent(ev);
2146 break;
2148 // For the dropdowns, we assign focus to them so that they are
2149 // keyboard accessible.
2150 case 'o':
2151 editor.dropdowns['fontname'].focus();
2152 break;
2153 case 'p':
2154 editor.dropdowns['fontsize'].focus();
2155 break;
2156 case 'h':
2157 editor.dropdowns['formatblock'].focus();
2158 break;
2159 case '=':
2160 editor.dropdowns['language'].focus();
2161 break;
2163 case 'b': cmd = "bold"; break;
2164 case 'i': cmd = "italic"; break;
2165 case 'u': cmd = "underline"; break;
2166 case 's': cmd = "strikethrough"; break;
2167 case ',': cmd = "subscript"; break;
2168 case '.': cmd = "superscript"; break;
2170 case 'v':
2171 if (! HTMLArea.is_gecko ) {
2172 cmd = "paste";
2174 break;
2176 case '0': cmd = "killword"; break;
2177 case 'z': cmd = "undo"; break;
2178 case 'y': cmd = "redo"; break;
2179 case 'l': cmd = "justifyleft"; break;
2180 case 'e': cmd = "justifycenter"; break;
2181 case 'r': cmd = "justifyright"; break;
2182 case 'j': cmd = "justifyfull"; break;
2183 case '/': cmd = "lefttoright"; break;
2184 case '|': cmd = "righttoleft"; break;
2185 case ';': cmd = "outdent"; break;
2186 case "'": cmd = "indent"; break;
2187 case 'g': cmd = "forecolor"; break;
2188 case 'k': cmd = "hilitecolor"; break;
2189 case 'f': cmd = "searchandreplace"; break;
2190 case '`': cmd = "htmlmode"; break; // FIXME: can't toggle from source code to wysiwyg
2192 case 'm':
2193 // Toggle fullscreen on or off.
2194 if (this.config.btnList['popupeditor'][0] == 'Enlarge Editor') {
2195 cmd = 'popupeditor';
2196 } else {
2197 window.close();
2199 break;
2201 // Headings.
2202 case '1':
2203 case '2':
2204 case '3':
2205 case '4':
2206 case '5':
2207 case '6':
2208 cmd = "formatblock";
2209 value = "h" + key;
2210 if (HTMLArea.is_ie) {
2211 value = "<" + value + ">";
2213 break;
2215 } // End switch (key)
2218 } else if (ev.ctrlKey && ev.altKey) {
2220 * Ctrl + Alt modifiers.
2221 * We use these for shortcuts that insert stuff, e.g. images.
2223 switch (key) {
2224 case 'o': cmd = "insertorderedlist"; break;
2225 case 'u': cmd = "insertunorderedlist"; break;
2226 case 'r': cmd = "inserthorizontalrule"; break;
2227 case 'a': cmd = "createanchor"; break;
2228 case 'l': cmd = "createlink"; break;
2229 case 'd': cmd = "unlink"; break;
2230 case 'n': cmd = "nolink"; break;
2231 case 'i': cmd = 'insertimage'; break;
2232 case 't': cmd = 'inserttable'; break;
2233 case 's': cmd = 'insertsmile'; break;
2234 case 'c': cmd = 'insertchar'; break;
2238 if (cmd) {
2239 // execute simple command
2240 this.execCommand(cmd, false, value);
2241 HTMLArea._stopEvent(ev);
2243 } // End if (keyEvent)
2246 else if (keyEvent) {
2247 // other keys here
2248 switch (ev.keyCode) {
2249 case 13: // KEY enter
2250 // if (HTMLArea.is_ie) {
2251 this.insertHTML("<br />");
2252 HTMLArea._stopEvent(ev);
2253 // }
2254 break;
2259 // Update the toolbar state after some time.
2260 if (editor._timerToolbar) {
2261 clearTimeout(editor._timerToolbar);
2263 editor._timerToolbar = setTimeout(function() {
2264 editor.updateToolbar();
2265 editor._timerToolbar = null;
2266 }, 50);
2270 // retrieve the HTML
2271 HTMLArea.prototype.getHTML = function() {
2272 switch (this._editMode) {
2273 case "wysiwyg" :
2274 if (!this.config.fullPage) {
2275 return HTMLArea.getHTML(this._doc.body, false, this);
2276 } else
2277 return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement, true, this);
2278 case "textmode" : return this._textArea.value;
2279 default : alert("Mode <" + mode + "> not defined!");
2281 return false;
2284 // retrieve the HTML (fastest version, but uses innerHTML)
2285 HTMLArea.prototype.getInnerHTML = function() {
2286 switch (this._editMode) {
2287 case "wysiwyg" :
2288 if (!this.config.fullPage)
2289 return this._doc.body.innerHTML;
2290 else
2291 return this.doctype + "\n" + this._doc.documentElement.innerHTML;
2292 case "textmode" : return this._textArea.value;
2293 default : alert("Mode <" + mode + "> not defined!");
2295 return false;
2298 // completely change the HTML inside
2299 HTMLArea.prototype.setHTML = function(html) {
2300 switch (this._editMode) {
2301 case "wysiwyg" :
2302 if (!this.config.fullPage)
2303 this._doc.body.innerHTML = html;
2304 else
2305 // this._doc.documentElement.innerHTML = html;
2306 this._doc.body.innerHTML = html;
2307 break;
2308 case "textmode" : this._textArea.value = html; break;
2309 default : alert("Mode <" + mode + "> not defined!");
2311 return false;
2314 // sets the given doctype (useful when config.fullPage is true)
2315 HTMLArea.prototype.setDoctype = function(doctype) {
2316 this.doctype = doctype;
2319 /***************************************************
2320 * Category: UTILITY FUNCTIONS
2321 ***************************************************/
2323 // browser identification
2325 HTMLArea.agt = navigator.userAgent.toLowerCase();
2326 HTMLArea.is_ie = ((HTMLArea.agt.indexOf("msie") != -1) && (HTMLArea.agt.indexOf("opera") == -1));
2327 HTMLArea.is_opera = (HTMLArea.agt.indexOf("opera") != -1);
2328 HTMLArea.is_mac = (HTMLArea.agt.indexOf("mac") != -1);
2329 HTMLArea.is_mac_ie = (HTMLArea.is_ie && HTMLArea.is_mac);
2330 HTMLArea.is_win_ie = (HTMLArea.is_ie && !HTMLArea.is_mac);
2331 HTMLArea.is_gecko = (navigator.product == "Gecko");
2332 HTMLArea.is_safari = (HTMLArea.agt.indexOf("safari") != -1);
2334 // variable used to pass the object to the popup editor window.
2335 HTMLArea._object = null;
2337 // function that returns a clone of the given object
2338 HTMLArea.cloneObject = function(obj) {
2339 var newObj = new Object;
2341 // check for array objects
2342 if (obj.constructor.toString().indexOf("function Array(") >= 0) {
2343 newObj = obj.constructor();
2346 // check for function objects (as usual, IE is phucked up)
2347 if (obj.constructor.toString().indexOf("function Function(") >= 0) {
2348 newObj = obj; // just copy reference to it
2349 } else for (var n in obj) {
2350 var node = obj[n];
2351 if (typeof node == 'object') { newObj[n] = HTMLArea.cloneObject(node); }
2352 else { newObj[n] = node; }
2355 return newObj;
2358 // FIXME!!! this should return false for IE < 5.5
2359 HTMLArea.checkSupportedBrowser = function() {
2360 if (HTMLArea.is_gecko) {
2361 if (navigator.productSub < 20021201) {
2362 alert("You need at least Mozilla-1.3 Alpha.\n" +
2363 "Sorry, your Gecko is not supported.");
2364 return false;
2366 if (navigator.productSub < 20030210) {
2367 alert("Mozilla < 1.3 Beta is not supported!\n" +
2368 "I'll try, though, but it might not work.");
2371 if(HTMLArea.is_safari) {
2372 return false;
2374 return HTMLArea.is_gecko || HTMLArea.is_ie;
2377 // selection & ranges
2379 // returns the current selection object
2380 HTMLArea.prototype._getSelection = function() {
2381 if (HTMLArea.is_ie) {
2382 return this._doc.selection;
2383 } else {
2384 return this._iframe.contentWindow.getSelection();
2388 // returns a range for the current selection
2389 HTMLArea.prototype._createRange = function(sel) {
2390 if (HTMLArea.is_ie) {
2391 return sel.createRange();
2392 } else {
2393 // Commented out because we need the dropdowns to be able to keep
2394 // focus for keyboard accessibility. Comment by Vy-Shane Sin Fat.
2395 //this.focusEditor();
2396 if (typeof sel != "undefined") {
2397 try {
2398 return sel.getRangeAt(0);
2399 } catch(e) {
2400 return this._doc.createRange();
2402 } else {
2403 return this._doc.createRange();
2408 // event handling
2410 HTMLArea._addEvent = function(el, evname, func) {
2411 if (HTMLArea.is_ie) {
2412 el.attachEvent("on" + evname, func);
2413 } else {
2414 el.addEventListener(evname, func, true);
2418 HTMLArea._addEvents = function(el, evs, func) {
2419 for (var i in evs) {
2420 HTMLArea._addEvent(el, evs[i], func);
2424 HTMLArea._removeEvent = function(el, evname, func) {
2425 if (HTMLArea.is_ie) {
2426 el.detachEvent("on" + evname, func);
2427 } else {
2428 el.removeEventListener(evname, func, true);
2432 HTMLArea._removeEvents = function(el, evs, func) {
2433 for (var i in evs) {
2434 HTMLArea._removeEvent(el, evs[i], func);
2438 HTMLArea._stopEvent = function(ev) {
2439 if (HTMLArea.is_ie) {
2440 ev.cancelBubble = true;
2441 ev.returnValue = false;
2442 } else {
2443 ev.preventDefault();
2444 ev.stopPropagation();
2448 HTMLArea._removeClass = function(el, className) {
2449 if (!(el && el.className)) {
2450 return;
2452 var cls = el.className.split(" ");
2453 var ar = new Array();
2454 for (var i = cls.length; i > 0;) {
2455 if (cls[--i] != className) {
2456 ar[ar.length] = cls[i];
2459 el.className = ar.join(" ");
2462 HTMLArea._addClass = function(el, className) {
2463 // remove the class first, if already there
2464 HTMLArea._removeClass(el, className);
2465 el.className += " " + className;
2468 HTMLArea._hasClass = function(el, className) {
2469 if (!(el && el.className)) {
2470 return false;
2472 var cls = el.className.split(" ");
2473 for (var i = cls.length; i > 0;) {
2474 if (cls[--i] == className) {
2475 return true;
2478 return false;
2481 HTMLArea.isBlockElement = function(el) {
2483 var blockTags = " body form textarea fieldset ul ol dl li div " +
2484 "p h1 h2 h3 h4 h5 h6 quote pre table thead " +
2485 "tbody tfoot tr td iframe address ";
2486 try {
2487 return (blockTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
2488 } catch (e) {}
2492 HTMLArea.needsClosingTag = function(el) {
2493 var closingTags = " head script style div span tr td tbody table em strong font a title iframe object applet ";
2494 return (closingTags.indexOf(" " + el.tagName.toLowerCase() + " ") != -1);
2497 // performs HTML encoding of some given string
2498 HTMLArea.htmlEncode = function(str) {
2499 // we don't need regexp for that, but.. so be it for now.
2500 str = str.replace(/&/ig, "&amp;");
2501 str = str.replace(/</ig, "&lt;");
2502 str = str.replace(/>/ig, "&gt;");
2503 str = str.replace(/\x22/ig, "&quot;");
2504 // \x22 means '"' -- we use hex reprezentation so that we don't disturb
2505 // JS compressors (well, at least mine fails.. ;)
2506 return str;
2508 // Moodle hack for special tags. Note that in IE you cannot start
2509 // content with special tag ( innerHTML issue ).
2510 HTMLArea.isSpecialTag = function (el) {
2511 var tags = new Array();
2512 tags[0] = /^\/?(nolink|lang|tex|algebra|math|mi|mn|mo|mtext|mspace)$/i;
2513 tags[1] = /^\/?(ms|mrow|mfrac|msqrt|mroot|mstyle|merror|mpadded|mphantom)$/i;
2514 tags[2] = /^\/?(mfenced|msub|msup|msubsup|munder|mover|munderover|mmultiscripts)$/i;
2515 tags[3] = /^\/?(mtable|mtr|mtd|maligngroup|malignmark|maction|cn|ci|apply|reln)$/i;
2516 tags[4] = /^\/?(fn|interval|inverse|sep|condition|declare|lambda|compose|ident)$/i;
2517 tags[5] = /^\/?(quotient|exp|factorial|divide|max|min|minus|plus|power|rem|times)$/i;
2518 tags[6] = /^\/?(root|gcd|and|or|xor|not|implies|forall|exists|abs|conjugate|eq|neq)$/i;
2519 tags[7] = /^\/?(gt|lt|geq|leq|ln|log|int|diff|partialdiff|lowlimit|uplimit|bvar)$/i;
2520 tags[8] = /^\/?(degree|set|list|union|intersect|in|notin|subset|prsubset|notsubset)$/i;
2521 tags[9] = /^\/?(notprsubset|setdiff|sum|product|limit|tendsto|mean|sdev|variance|median)$/i;
2522 tags[10] = /^\/?(mode|moment|vector|matrix|matrixrow|determinant|transpose|selector)$/i;
2523 tags[11] = /^\/?(annotation|semantics|annotation-xml)$/i;
2524 for ( var i = 0; i < tags.length; i++ ) {
2525 if ( tags[i].test(el.tagName.toLowerCase()) ) {
2526 return true;
2529 return false;
2531 HTMLArea.isSingleTag = function (el) {
2532 var re = /^(br|hr|img|input|link|meta|param|embed|area)$/i;
2533 return re.test(el.tagName.toLowerCase());
2535 // Retrieves the HTML code from the given node. This is a replacement for
2536 // getting innerHTML, using standard DOM calls.
2537 HTMLArea.getHTML = function(root, outputRoot, editor) {
2538 var html = "";
2539 switch (root.nodeType) {
2540 case 1: // Node.ELEMENT_NODE
2541 case 11: // Node.DOCUMENT_FRAGMENT_NODE
2542 var closed;
2543 var i;
2544 var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
2545 if (HTMLArea.is_ie && root_tag == "head") {
2546 if (outputRoot)
2547 html += "<head>";
2548 // lowercasize
2549 var save_multiline = RegExp.multiline;
2550 RegExp.multiline = true;
2551 var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
2552 return p1 + p2.toLowerCase();
2554 RegExp.multiline = save_multiline;
2555 html += txt;
2556 if (outputRoot)
2557 html += "</head>";
2558 break;
2559 } else if (outputRoot) {
2560 closed = (!(root.hasChildNodes() || !HTMLArea.isSingleTag(root)));
2561 html = "<" + root.tagName.toLowerCase();
2562 var attrs = root.attributes;
2563 for (i = 0; i < attrs.length; ++i) {
2564 var a = attrs.item(i);
2565 if (!a.specified) {
2566 continue;
2568 var name = a.nodeName.toLowerCase();
2569 if (/_moz|contenteditable|_msh/.test(name)) {
2570 // avoid certain attributes
2571 continue;
2573 var value;
2574 if (name != "style") {
2576 // Using Gecko the values of href and src are converted to absolute links
2577 // unless we get them using nodeValue()
2578 if (typeof root[a.nodeName] != "undefined" && name != "href" && name != "src") {
2579 value = root[a.nodeName];
2580 } else {
2581 // This seems to be working, but if it does cause
2582 // problems later on return the old value...
2583 if (name.toLowerCase() == "href" && name.toLowerCase() == "src") {
2584 value = root[a.nodeName];
2585 } else {
2586 value = a.nodeValue;
2588 if (HTMLArea.is_ie && (name == "href" || name == "src")) {
2589 value = editor.stripBaseURL(value);
2592 } else { // IE fails to put style in attributes list
2593 // FIXME: cssText reported by IE is UPPERCASE
2594 value = root.style.cssText.toLowerCase();
2596 if (/(_moz|^$)/.test(value)) {
2597 // Mozilla reports some special tags
2598 // here; we don't need them.
2599 continue;
2601 html += " " + name + '="' + value + '"';
2603 html += closed ? " />" : ">";
2605 for (i = root.firstChild; i; i = i.nextSibling) {
2606 html += HTMLArea.getHTML(i, true, editor);
2608 if (outputRoot && !closed) {
2609 if ( HTMLArea.is_ie && HTMLArea.isSpecialTag(root) ) {
2610 html += '';
2611 } else {
2612 html += "</" + root.tagName.toLowerCase() + ">";
2615 break;
2616 case 3: // Node.TEXT_NODE
2617 // If a text node is alone in an element and all spaces, replace it with an non breaking one
2618 // This partially undoes the damage done by moz, which translates '&nbsp;'s into spaces in the data element
2619 if ( !root.previousSibling && !root.nextSibling && root.data.match(/^\s*$/i) && root.data.length > 1 ) html = '&nbsp;';
2620 else html = HTMLArea.htmlEncode(root.data);
2621 break;
2622 case 8: // Node.COMMENT_NODE
2623 html = "<!--" + root.data + "-->";
2624 break; // skip comments, for now.
2627 return HTMLArea.indent(html);
2630 HTMLArea.prototype.stripBaseURL = function(string) {
2631 var baseurl = this.config.baseURL;
2633 // IE adds the path to an anchor, converting #anchor
2634 // to path/#anchor which of course needs to be fixed
2635 var index = string.indexOf("/#")+1;
2636 if ((index > 0) && (string.indexOf(baseurl) > -1)) {
2637 return string.substr(index);
2639 return string; // Moodle doesn't use the code below because
2640 // Moodle likes to keep absolute links
2642 // strip to last directory in case baseurl points to a file
2643 baseurl = baseurl.replace(/[^\/]+$/, '');
2644 var basere = new RegExp(baseurl);
2645 string = string.replace(basere, "");
2647 // strip host-part of URL which is added by MSIE to links relative to server root
2648 baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
2649 basere = new RegExp(baseurl);
2650 return string.replace(basere, "");
2653 String.prototype.trim = function() {
2654 a = this.replace(/^\s+/, '');
2655 return a.replace(/\s+$/, '');
2658 // creates a rgb-style color from a number
2659 HTMLArea._makeColor = function(v) {
2660 if (typeof v != "number") {
2661 // already in rgb (hopefully); IE doesn't get here.
2662 return v;
2664 // IE sends number; convert to rgb.
2665 var r = v & 0xFF;
2666 var g = (v >> 8) & 0xFF;
2667 var b = (v >> 16) & 0xFF;
2668 return "rgb(" + r + "," + g + "," + b + ")";
2671 // returns hexadecimal color representation from a number or a rgb-style color.
2672 HTMLArea._colorToRgb = function(v) {
2673 if (!v)
2674 return '';
2676 // returns the hex representation of one byte (2 digits)
2677 function hex(d) {
2678 return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
2681 if (typeof v == "number") {
2682 // we're talking to IE here
2683 var r = v & 0xFF;
2684 var g = (v >> 8) & 0xFF;
2685 var b = (v >> 16) & 0xFF;
2686 return "#" + hex(r) + hex(g) + hex(b);
2689 if (v.substr(0, 3) == "rgb") {
2690 // in rgb(...) form -- Mozilla
2691 var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2692 if (v.match(re)) {
2693 var r = parseInt(RegExp.$1);
2694 var g = parseInt(RegExp.$2);
2695 var b = parseInt(RegExp.$3);
2696 return "#" + hex(r) + hex(g) + hex(b);
2698 // doesn't match RE?! maybe uses percentages or float numbers
2699 // -- FIXME: not yet implemented.
2700 return null;
2703 if (v.substr(0, 1) == "#") {
2704 // already hex rgb (hopefully :D )
2705 return v;
2708 // if everything else fails ;)
2709 return null;
2712 HTMLArea.prototype._popupDialog = function(url, action, init) {
2713 Dialog(this.popupURL(url), action, init);
2716 // paths
2718 HTMLArea.prototype.imgURL = function(file, plugin) {
2719 if (typeof plugin == "undefined")
2720 return _editor_url + file;
2721 else
2722 return _editor_url + "plugins/" + plugin + "/img/" + file;
2725 HTMLArea.prototype.popupURL = function(file) {
2726 var url = "";
2727 if (file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
2728 var plugin = RegExp.$1;
2729 var popup = RegExp.$2;
2730 if (!/\.html$/.test(popup))
2731 popup += ".html";
2732 url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
2733 } else
2734 url = _editor_url + this.config.popupURL + file;
2735 return url;
2739 * FIX: Internet Explorer returns an item having the _name_ equal to the given
2740 * id, even if it's not having any id. This way it can return a different form
2741 * field even if it's not a textarea. This workarounds the problem by
2742 * specifically looking to search only elements having a certain tag name.
2744 HTMLArea.getElementById = function(tag, id) {
2745 var el, i, objs = document.getElementsByTagName(tag);
2746 for (i = objs.length; --i >= 0 && (el = objs[i]);)
2747 if (el.id == id)
2748 return el;
2749 return null;
2751 // Modified version of GetHtml plugin's indent.
2752 HTMLArea.indent = function(s, sindentChar) {
2753 var c = [
2754 /*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),
2755 /*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
2756 /*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
2757 /*3*/ new RegExp().compile(/<(br|hr|img|embed|param|pre|meta|link|title|area)[^>]*>/g),//singlet tag
2758 /*4*/ new RegExp().compile(/(^|<\/(pre|script)>)(\s|[^\s])*?(<(pre|script)[^>]*>|$)/g),//find content NOT inside pre and script tags
2759 /*5*/ new RegExp().compile(/(<pre[^>]*>)(\s|[^\s])*?(<\/pre>)/g),//find content inside pre tags
2760 /*6*/ new RegExp().compile(/(^|<!--(\s|\S)*?-->)((\s|\S)*?)(?=<!--(\s|\S)*?-->|$)/g),//find content NOT inside comments
2761 /*7*/ new RegExp().compile(/<\/(table|tbody|tr|td|th|ul|ol|object|html|head|body)( [^>]*)?>/g),//blocklevel closing tag
2763 HTMLArea.__nindent = 0;
2764 HTMLArea.__sindent = "";
2765 HTMLArea.__sindentChar = (typeof sindentChar == "undefined") ? " " : sindentChar;
2767 if(HTMLArea.is_gecko) { //moz changes returns into <br> inside <pre> tags
2768 s = s.replace(c[5], function(str){return str.replace(/<br \/>/g,"\n")});
2770 s = s.replace(c[4], function(strn) { //skip pre and script tags
2771 strn = strn.replace(c[6], function(st,$1,$2,$3) { //exclude comments
2772 string = $3.replace(/[\n\r]/gi, " ").replace(/\s+/gi," ").replace(c[0], function(str) {
2773 if (str.match(c[2])) {
2774 var s = "\n" + HTMLArea.__sindent + str;
2775 // blocklevel openingtag - increase indent
2776 HTMLArea.__sindent += HTMLArea.__sindentChar;
2777 ++HTMLArea.__nindent;
2778 return s;
2779 } else if (str.match(c[1])) {
2780 // blocklevel closingtag - decrease indent
2781 --HTMLArea.__nindent;
2782 HTMLArea.__sindent = "";
2783 for (var i=HTMLArea.__nindent;i>0;--i) {
2784 HTMLArea.__sindent += HTMLArea.__sindentChar;
2786 return (str.match(c[7]) ? "\n" + HTMLArea.__sindent : "") + str;
2788 return str; // this won't actually happen
2790 return $1 + string;
2791 });return strn;
2793 if (s.charAt(0) == "\n") {
2794 return s.substring(1, s.length);
2796 s = s.replace(/ *\n/g,'\n');//strip spaces at end of lines
2797 return s;