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");
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");
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");
26 $lang = current_language();
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/';
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
61 if (typeof _editor_url
== "string") {
62 // Leave exactly one backslash at the end of _editor_url
63 _editor_url
= _editor_url
.replace(/\x2f*$
/, '/');
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.
76 // Creates a new HTMLArea object. Tries to replace the textarea with the given
78 function HTMLArea(textarea
, config
) {
79 if (HTMLArea
.checkSupportedBrowser()) {
80 if (typeof config
== "undefined") {
81 this
.config
= new HTMLArea
.Config();
85 this
._htmlArea
= null;
86 this
._textArea
= textarea
;
87 this
._editMode
= "wysiwyg";
89 this
._timerToolbar
= null;
90 this
._timerUndo
= null;
91 this
._undoQueue
= new Array(this
.config
.undoSteps
);
93 this
._customUndo
= true;
94 this
._mdoc
= document
; // cache the document, we need it in plugins
96 this
.dropdowns
= []; // Array of select elements in the toolbar
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
);
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";
124 this
.height
= "auto";
126 // enable creation of a status bar?
127 this
.statusBar
= true;
129 // maximum size of the undo queue
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
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 +
"/";
155 this
.imgURL
= "images/";
156 this
.popupURL
= "popups/";
159 [ "fontname", "space",
161 "formatblock", "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",';
177 "separator", "htmlmode", "separator", "popupeditor"]
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',
188 "WingDings": 'wingdings'
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"
215 "<?php echo $strlang; ?>":"",
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);
233 this
.customSelects
= {};
235 function cut_copy_paste(e
, cmd
, obj
) {
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
) {
292 if (typeof id
== "string") {
294 } else if (typeof id
== "object") {
297 alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
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.");
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) {
331 if (/separator|space
/.test(line
[j +
1])) {
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";
361 var tb_objects
= new Object();
362 this
._toolbarObjects
= tb_objects
;
364 // creates a new line in the toolbar
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
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
381 function setButtonStatus(id
, newval
) {
382 var oldval
= this
[id
];
383 var el
= this
.element
;
384 if (oldval
!= newval
) {
388 HTMLArea
._removeClass(el
, "buttonDisabled");
391 HTMLArea
._addClass(el
, "buttonDisabled");
397 HTMLArea
._addClass(el
, "buttonPressed");
399 HTMLArea
._removeClass(el
, "buttonPressed");
405 }; // END of function: setButtonStatus
407 function createSelect(txt
) {
411 var customSelects
= editor
.config
.customSelects
;
418 options
= editor
.config
[txt
];
422 // try to fetch it from the list of registered selects
424 var dropdown
= customSelects
[cmd
];
425 if (typeof dropdown
!= "undefined") {
426 options
= dropdown
.options
;
427 context
= dropdown
.context
;
429 alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
434 el
= document
.createElement("select");
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
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
];
451 HTMLArea
._addEvent(el
, "change", function () {
452 editor
._comboSelected(el
, txt
);
455 editor
.dropdowns
[txt
] = el
; // Keep track of the element for keyboard
458 }; // END of function: createSelect
460 // appends a new button to toolbar
461 function createButton(txt
) {
462 // the element that will be created
467 el
= document
.createElement("div");
468 el
.className
= "separator";
471 el
= document
.createElement("div");
472 el
.className
= "space";
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
;
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
;
494 btn
= editor
.config
.btnList
[txt
];
497 el
= document
.createElement("div");
499 el
.className
= "button";
500 // let's just pretend we have a button object, and
501 // assign all the needed information to it.
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 () {
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");
544 img
.style
.width
= "18px";
545 img
.style
.height
= "18px";
548 el
= createSelect(txt
);
551 var tb_cell
= document
.createElement("td");
552 tb_row
.appendChild(tb_cell
);
553 tb_cell
.appendChild(el
);
555 alert("FIXME: Unknown toolbar item: " + txt
);
561 for (var i in this
.config
.toolbar
) {
562 if (this
.config
.toolbar
.propertyIsEnumerable(i
)) { // fix for prototype.js compatibility
564 createButton("linebreak");
568 var group
= this
.config
.toolbar
[i
];
569 for (var j in group
) {
570 if (group
.propertyIsEnumerable(j
)) { // fix for prototype.js compatibility
572 if (/^
([IT
])\
[(.*?
)\
]/.test(code
)) {
573 // special case, create text label
574 var l7ed
= RegExp
.$1 == "I"; // localized?
575 var label
= RegExp
.$2;
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
;
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
) {
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
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
625 if ( textarea
.offsetHeight
&& textarea
.offsetHeight
> 0 ) {
626 height
= textarea
.offsetHeight
;
631 w
: textarea
.offsetWidth
,
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
);
647 textarea
.parentNode
.insertBefore(htmlarea
, textarea
);
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(/\
 \
;/gi
, '');
667 //alert(test + test.length);
668 if (test
.length
< 1) {
669 editor
._textArea
.value
= test
.trim();
671 editor
._textArea
.value
= editor
.getHTML();
674 var a
= this
.__msh_prevOnSubmit
;
676 // call previous submit methods if they were there.
677 if (typeof a
!= "undefined") {
678 for (var i
= a
.length
; --i
>= 0;) {
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;) {
704 // add a handler for the "back/forward" case -- on body.unload we save
705 // the HTML content into the original textarea.
707 window
.onunload
= function() {
708 editor
._textArea
.value
= editor
.getHTML();
712 // creates & appends the toolbar
713 this
._createToolbar();
716 var iframe
= document
.createElement("iframe");
718 iframe
.src
= "about:blank";
720 iframe
.className
= "iframe";
722 htmlarea
.appendChild(iframe
);
725 editor
._iframe
= iframe
;
726 var doc
= editor
._iframe
.contentWindow
.document
;
729 // Generate iframe content
731 if (!editor
.config
.fullPage
) {
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";
740 html +
= editor
._textArea
.value
;
741 html
= html
.replace(/<nolink
>/gi
, '<span class="nolink">').
742 replace(/<\
/nolink
>/gi
, '</span>');
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
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
) {
765 if(editor
.designModeIsOn
!= true)
767 editor
.designModeIsOn
= true;
769 doc
.designMode
= "on";
777 // This one is for click in iframe
779 editor
._iframe
.contentWindow
,
782 if(editor
.designModeIsOn
!= true)
784 editor
.designModeIsOn
= true;
786 doc
.designMode
= "on";
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
) {
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
;
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
837 (doc
, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
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") {
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();
860 if (typeof editor
.onGenerate
== "function") {
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");
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"]));
886 if (HTMLArea
.is_gecko
) {
887 // disable design mode before changing innerHTML
889 this
._doc
.designMode
= "off";
892 if (!this
.config
.fullPage
)
893 this
._doc
.body
.innerHTML
= this
.getHTML();
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
901 this
._doc
.designMode
= "on";
905 if (this
.config
.statusBar
) {
906 this
._statusBar
.innerHTML
= '';
907 this
._statusBar
.appendChild(this
._statusBarTree
);
911 alert("Mode <" + mode +
"> not defined!");
914 this
._editMode
= mode
;
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;
932 var html_re
= /<html
>((.|\n
)*?
)<\
/html
>/i
;
933 html
= html
.replace(html_re
, "$1");
935 this
._doc
.write(html
);
937 this
._doc
.body
.contentEditable
= true;
944 HTMLArea
.prototype
.registerPlugin2
= function(plugin
, args
) {
945 if (typeof plugin
== "string")
946 plugin
= eval(plugin
);
947 var obj
= new plugin(this
, args
);
950 var info
= plugin
._pluginInfo
;
953 clone.instance
= obj
;
955 this
.plugins
[plugin
._pluginInfo
.name
] = clone;
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];
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 +
"/";
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) {
1001 D
= D
.replace(/\r\n/g
, '\[br\]').
1004 replace(/\
 \
;/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
,'');
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
,' ');
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
1047 oldlen
= D
.length +
1;
1048 while(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
;
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) {
1072 if (emptyLine
.test(line
)) {
1076 line
= line
.replace(/^\s+\s+$
/g
, '');
1077 strHTML +
= line +
'\n';
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");
1103 // takes a snapshot of the current text (for undo)
1104 HTMLArea
.prototype
._undoTakeSnapshot
= function() {
1106 if (this
._undoPos
>= this
.config
.undoSteps
) {
1107 // remove the first element
1108 this
._undoQueue
.shift();
1111 // use the fasted method (getInnerHTML);
1113 var txt
= this
.getInnerHTML();
1114 if (this
._undoPos
> 0)
1115 take
= (this
._undoQueue
[this
._undoPos
- 1] != txt
);
1117 this
._undoQueue
[this
._undoPos
] = txt
;
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;
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
];
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 ;-)
1163 var a
= document
.createElement("a");
1167 a
.onclick
= function() {
1169 this
.editor
.selectNodeContents(this
.el
);
1170 this
.editor
.updateToolbar(true);
1173 a
.oncontextmenu
= function() {
1174 // TODO: add context menu here
1176 var info
= "Inline style:\n\n";
1177 info +
= this
.el
.style
.cssText
.split(/;\s*/
).join(";\n");
1181 var txt
= el
.tagName
.toLowerCase();
1182 a
.title
= el
.style
.cssText
;
1187 txt +
= "." + el
.className
;
1189 a
.appendChild(document
.createTextNode(txt
));
1190 this
._statusBarTree
.appendChild(a
);
1192 this
._statusBarTree
.appendChild(document
.createTextNode(String.fromCharCode(0xbb)));
1197 for (var i in this
._toolbarObjects
) {
1198 var btn
= this
._toolbarObjects
[i
];
1200 var inContext
= true;
1201 if (btn
.context
&& !text
) {
1203 var context
= btn
.context
;
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.
1216 if (match ||
(ancestors
[k
].tagName
.toLowerCase() == context
)) {
1218 for (var ka in attrs
) {
1219 if (!eval("ancestors[k]." + attrs
[ka
])) {
1230 btn
.state("enabled", (!text || btn
.text
) && inContext
);
1231 if (typeof cmd
== "function") {
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
);
1245 var value
= ("" + doc
.queryCommandValue(cmd
)).toLowerCase();
1247 // FIXME: what do we do here?
1250 var options
= this
.config
[cmd
];
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
;
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';
1273 value
= parentEl
.getAttribute('lang');
1278 var options
= this
.config
[cmd
];
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
;
1291 case "textindicator":
1293 try {with (btn
.element
.style
) {
1294 backgroundColor
= HTMLArea
._makeColor(
1295 doc
.queryCommandValue(HTMLArea
.is_ie ?
"backcolor" : "hilitecolor"));
1296 if (/transparent
/i
.test(backgroundColor
)) {
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";
1305 // alert(e + "\n\n" + cmd);
1309 case "htmlmode": btn
.state("active", text
); break;
1312 var el
= this
.getParentElement();
1313 while (el
&& !HTMLArea
.isBlockElement(el
))
1316 btn
.state("active", (el
.style
.direction
== ((cmd
== "righttoleft") ?
"rtl" : "ltr")));
1320 btn
.state("active", (!text
&& doc
.queryCommandState(cmd
)));
1324 // take undo snapshots
1325 if (this
._customUndo
&& !this
._timerUndo
) {
1326 this
._undoTakeSnapshot();
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
);
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();
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();
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
) {
1396 return range
.parentElement();
1398 return range
.item(0);
1400 return this
._doc
.body
;
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) {
1420 // Returns an array with all the ancestor nodes of the selection.
1421 HTMLArea
.prototype
.getAllAncestors
= function() {
1422 var p
= this
.getParentElement();
1424 while (p
&& (p
.nodeType
== 1) && (p
.tagName
.toLowerCase() != 'body')) {
1428 a
.push(this
._doc
.body
);
1432 // Selects the contents inside the given node
1433 HTMLArea
.prototype
.selectNodeContents
= function(node
, pos
) {
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
);
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
);
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
;
1490 existing
= HTMLArea
.getHTML(range
.cloneContents(), false, this
);
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
) {
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') {
1517 var sel
= this
._getSelection();
1518 var rng
= this
._createRange(sel
);
1519 var len
= HTMLArea
.is_ie ? rng
.text
.toString().length
: sel
.toString().length
;
1521 alert("<?php print_string("alertnoselectedtext
","editor
");?>");
1530 f_href
: HTMLArea
.is_ie ? editor
.stripBaseURL(link
.href
) : link
.getAttribute("href"),
1531 f_title
: link
.title
,
1532 f_target
: link
.target
,
1537 f_anchors
:anchors
};
1539 this
._popupDialog("link_std.php?id=<?php echo $id; ?>", function(param
) {
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() +
'"';
1557 strLink +
= (!HTMLArea
.is_ie
) ? sel
: range
.text
;
1559 editor
.insertHTML(strLink
);
1561 var href
= param
.f_href
.trim();
1562 editor
.selectNodeContents(a
);
1564 editor
._doc
.execCommand("unlink", false, null);
1565 editor
.updateToolbar();
1571 if (!(a
&& /^a$
/i
.test(a
.tagName
))) {
1574 a
.target
= param
.f_target
.trim();
1575 a
.title
= param
.f_title
.trim();
1576 editor
.selectNodeContents(a
);
1577 editor
.updateToolbar();
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
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
))
1594 if (image
) outparam
= {
1595 f_url
: HTMLArea
.is_ie ? editor
.stripBaseURL(image
.src
) : image
.getAttribute("src"),
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";
1608 echo "insert_image_std
.php?id
=$id";
1609 }?>", function(param
) {
1610 if (!param
) { // user must have pressed Cancel
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
;
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
);
1637 img
.src
= param
.f_url
;
1639 for (field in param
) {
1640 var value
= param
[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;
1649 img
.width
= parseInt(value
);
1656 img
.height
= parseInt(value
);
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
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
];
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");
1700 if(param
["f_unit"] == "px") {
1701 tdwidth
= Math
.round(table
.width
/ param
["f_cols"]);
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
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
);
1717 editor
.insertNodeAtSelection(table
);
1723 /// Moodle hack - insertSmile
1724 HTMLArea
.prototype
._insertSmile
= function() {
1725 // Make sure that editor has focus
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
) {
1734 if (HTMLArea
.is_ie
) {
1735 range
.pasteHTML(imgString
);
1737 editor
.insertHTML(imgString
);
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
) {
1751 if (HTMLArea
.is_ie
) {
1752 range
.pasteHTML(sChar
);
1755 editor
.insertHTML(sChar
);
1761 HTMLArea
.prototype
._removelink
= function() {
1763 link
= this
.getParentElement();
1764 editor
.selectNodeContents(link
);
1766 this
._doc
.execCommand("unlink", false, null);
1770 HTMLArea
.prototype
._createanchor
= function () {
1772 var sel
= this
._getSelection();
1773 var rng
= this
._createRange(sel
);
1774 var len
= HTMLArea
.is_ie ? rng
.text
.toString().length
: sel
.toString().length
;
1776 alert("<?php print_string("alertnoselectedtext
","editor
");?>");
1779 this
._popupDialog("createanchor.php?id=<?php echo $id; ?>", function(objAn
) {
1783 var str
= '<a name="'+ objAn
.anchor+
'">';
1784 str +
= HTMLArea
.is_ie ? rng
.text
: sel
;
1786 editor
.insertHTML(str
);
1790 HTMLArea
.prototype
._nolinktag
= function () {
1793 var sel
= this
._getSelection();
1794 var rng
= this
._createRange(sel
);
1795 var len
= HTMLArea
.is_ie ? rng
.text
.toString().length
: sel
.toString().length
;
1798 alert("<?php print_string("alertnoselectedtext
","editor
");?>");
1801 var str
= '<span class="nolink">';
1802 str +
= HTMLArea
.is_ie ? rng
.text
: sel
;
1804 editor
.insertHTML(str
);
1809 HTMLArea
.prototype
._searchReplace
= function() {
1812 var selectedtxt
= "";
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 ?>';
1821 //in source mode mozilla show errors, try diffrent method
1822 if (editor
._editMode
== "wysiwyg") {
1823 selectedtxt
= editor
.getSelectedHTML();
1825 if (HTMLArea
.is_ie
) {
1826 selectedtxt
= document
.selection
.createRange().text
;
1828 selectedtxt
= getMozSelection(editor
._textArea
);
1833 f_search
: selectedtxt
1836 //Call Search And Replace popup window
1837 editor
._popupDialog( "searchandreplace.php?id=<?php echo $id; ?>", function( entity
) {
1839 //user must have pressed Cancel
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];
1849 if (search
.length
< 1) {
1850 alert ("Enter a search word! \n search for: " + entity
[0]);
1853 var regX
= new RegExp (search
, delim
) ;
1854 var text
= text
.replace ( regX
,
1856 // Increment our counter variable.
1859 return str
.replace( regX
, replace
) ;
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
));
1871 editor
.setHTML(text
);
1872 editor
.forceRedraw();
1874 alert(ile +
' ' + strReplaced
);
1876 alert (strNotfound +
"\n");
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
) {
1898 var value
= el
.options
[el
.selectedIndex
].value
;
1901 case "fontsize": this
.execCommand(txt
, false, value
); break;
1903 this
.setLang(value
);
1906 (HTMLArea
.is_ie
) && (value
= "<" + value +
">");
1907 this
.execCommand(txt
, false, value
);
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
);
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.
1936 var selectedHTML
= editor
.getSelectedHTML();
1937 var multiLang
= false;
1939 var re
= new RegExp('_ML', 'g');
1940 if (lang
.match(re
)) {
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
1960 var re = new RegExp(parentEl.innerHTML);
1962 if (selectedHTML.match(re)) {
1963 // The selected text makes up the whole of the span block.
1965 parentEl.setAttribute('lang', lang);
1967 parentEl.setAttribute('class', 'multilang');
1970 parentEl.removeAttribute('lang');
1972 var classAttr = parentEl.getAttribute('class');
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);
1988 insertNewSpan = true;
1991 insertNewSpan = true;
1994 if (insertNewSpan && lang != '') {
1995 var str = '<span lang="'+lang.trim()+'"';
1997 str += ' class="multilang
"';
2000 str += selectedHTML;
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
2012 cmdID = cmdID.toLowerCase();
2014 case "htmlmode
" : this.setMode(); break;
2016 (HTMLArea.is_ie) && (cmdID = "backcolor
");
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
)));
2024 case "createanchor": this
._createanchor(); break;
2028 case "unlink": this
._removelink(); break;
2029 case "nolink": this
._nolinktag(); break;
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");
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");
2047 if (this
._customUndo
)
2050 this
._doc
.execCommand(cmdID
, UI
, param
);
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;
2066 // Paste first then clean
2067 this
._doc
.execCommand(cmdID
, UI
, param
);
2068 if (this
.config
.killWordOnPaste
) {
2072 if (HTMLArea
.is_gecko
) {
2074 $strmoz = get_string('cutpastemozilla','editor');
2075 $strmoz = preg_replace("/[\n|\r
]+
/", "", $strmoz);
2076 $strmoz = str_replace('<br />', '\\n', $strmoz);
2078 echo addslashes($strmoz);
2081 window
.open("http://moodle.org/mozillahelp");
2087 var dir
= (cmdID
== "righttoleft") ?
"rtl" : "ltr";
2088 var el
= this
.getParentElement();
2089 while (el
&& !HTMLArea
.isBlockElement(el
))
2092 if (el
.style
.direction
== dir
)
2093 el
.style
.direction
= "";
2095 el
.style
.direction
= dir
;
2098 default: this
._doc
.execCommand(cmdID
, UI
, param
);
2100 this
.updateToolbar();
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
) {
2112 var keyEvent
= (HTMLArea
.is_ie
&& ev
.type
== "keydown") ||
(ev
.type
== "keypress");
2116 for (var i in editor
.plugins
) {
2117 var plugin
= editor
.plugins
[i
].instance
;
2118 if (typeof plugin
.onKeyPress
== "function") plugin
.onKeyPress(ev
);
2123 var key
= String.fromCharCode(HTMLArea
.is_ie ? ev
.keyCode
: ev
.charCode
).toLowerCase();
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.
2137 if (!HTMLArea
.is_ie
) {
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
);
2148 // For the dropdowns, we assign focus to them so that they are
2149 // keyboard accessible.
2151 editor
.dropdowns
['fontname'].focus();
2154 editor
.dropdowns
['fontsize'].focus();
2157 editor
.dropdowns
['formatblock'].focus();
2160 editor
.dropdowns
['language'].focus();
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;
2171 if (! HTMLArea
.is_gecko
) {
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
2193 // Toggle fullscreen on or off.
2194 if (this
.config
.btnList
['popupeditor'][0] == 'Enlarge Editor') {
2195 cmd
= 'popupeditor';
2208 cmd
= "formatblock";
2210 if (HTMLArea
.is_ie
) {
2211 value
= "<" + value +
">";
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.
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;
2239 // execute simple command
2240 this
.execCommand(cmd
, false, value
);
2241 HTMLArea
._stopEvent(ev
);
2243 } // End if (keyEvent)
2246 else if (keyEvent) {
2248 switch (ev.keyCode) {
2249 case 13: // KEY enter
2250 // if (HTMLArea.is_ie) {
2251 this.insertHTML("<br />");
2252 HTMLArea._stopEvent(ev);
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;
2270 // retrieve the HTML
2271 HTMLArea
.prototype
.getHTML
= function() {
2272 switch (this
._editMode
) {
2274 if (!this
.config
.fullPage
) {
2275 return HTMLArea
.getHTML(this
._doc
.body
, false, this
);
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!");
2284 // retrieve the HTML (fastest version, but uses innerHTML)
2285 HTMLArea
.prototype
.getInnerHTML
= function() {
2286 switch (this
._editMode
) {
2288 if (!this
.config
.fullPage
)
2289 return this
._doc
.body
.innerHTML
;
2291 return this
.doctype +
"\n" + this
._doc
.documentElement
.innerHTML
;
2292 case "textmode" : return this
._textArea
.value
;
2293 default : alert("Mode <" + mode +
"> not defined!");
2298 // completely change the HTML inside
2299 HTMLArea
.prototype
.setHTML
= function(html
) {
2300 switch (this
._editMode
) {
2302 if (!this
.config
.fullPage
)
2303 this
._doc
.body
.innerHTML
= html
;
2305 // this._doc.documentElement.innerHTML = html;
2306 this
._doc
.body
.innerHTML
= html
;
2308 case "textmode" : this
._textArea
.value
= html
; break;
2309 default : alert("Mode <" + mode +
"> not defined!");
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
) {
2351 if (typeof node
== 'object') { newObj
[n
] = HTMLArea
.cloneObject(node
); }
2352 else { newObj
[n
] = node
; }
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.");
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
) {
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
;
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();
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") {
2398 return sel
.getRangeAt(0);
2400 return this
._doc
.createRange();
2403 return this
._doc
.createRange();
2410 HTMLArea
._addEvent
= function(el
, evname
, func
) {
2411 if (HTMLArea
.is_ie
) {
2412 el
.attachEvent("on" + evname
, func
);
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
);
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;
2443 ev
.preventDefault();
2444 ev
.stopPropagation();
2448 HTMLArea
._removeClass
= function(el
, className
) {
2449 if (!(el
&& el
.className
)) {
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
)) {
2472 var cls
= el
.className
.split(" ");
2473 for (var i
= cls
.length
; i
> 0;) {
2474 if (cls
[--i
] == className
) {
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 ";
2487 return (blockTags
.indexOf(" " + el
.tagName
.toLowerCase() +
" ") != -1);
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
, "&");
2501 str
= str
.replace(/</ig
, "<");
2502 str
= str
.replace(/>/ig
, ">");
2503 str
= str
.replace(/\x22/ig
, """);
2504 // \x22 means '"' -- we use hex reprezentation so that we don't disturb
2505 // JS compressors (well, at least mine fails.. ;)
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()) ) {
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
) {
2539 switch (root
.nodeType
) {
2540 case 1: // Node.ELEMENT_NODE
2541 case 11: // Node.DOCUMENT_FRAGMENT_NODE
2544 var root_tag
= (root
.nodeType
== 1) ? root
.tagName
.toLowerCase() : '';
2545 if (HTMLArea
.is_ie
&& root_tag
== "head") {
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
;
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
);
2568 var name
= a
.nodeName
.toLowerCase();
2569 if (/_moz|contenteditable|_msh
/.test(name
)) {
2570 // avoid certain attributes
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
];
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
];
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.
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
) ) {
2612 html +
= "</" + root
.tagName
.toLowerCase() +
">";
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 ' 's into spaces in the data element
2619 if ( !root
.previousSibling
&& !root
.nextSibling
&& root
.data
.match(/^\s
*$
/i
) && root
.data
.length
> 1 ) html
= ' ';
2620 else html
= HTMLArea
.htmlEncode(root
.data
);
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.
2664 // IE sends number; convert to rgb.
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
) {
2676 // returns the hex representation of one byte (2 digits)
2678 return (d
< 16) ?
("0" + d
.toString(16)) : d
.toString(16);
2681 if (typeof v
== "number") {
2682 // we're talking to IE here
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
*\
)/;
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.
2703 if (v
.substr(0, 1) == "#") {
2704 // already hex rgb (hopefully :D )
2708 // if everything else fails ;)
2712 HTMLArea
.prototype
._popupDialog
= function(url
, action
, init
) {
2713 Dialog(this
.popupURL(url
), action
, init
);
2718 HTMLArea
.prototype
.imgURL
= function(file
, plugin
) {
2719 if (typeof plugin
== "undefined")
2720 return _editor_url + file
;
2722 return _editor_url +
"plugins/" + plugin +
"/img/" + file
;
2725 HTMLArea
.prototype
.popupURL
= function(file
) {
2727 if (file
.match(/^plugin
:\
/\
/(.*?
)\
/(.*)/)) {
2728 var plugin
= RegExp
.$1;
2729 var popup
= RegExp
.$2;
2730 if (!/\
.html$
/.test(popup
))
2732 url
= _editor_url +
"plugins/" + plugin +
"/popups/" + popup
;
2734 url
= _editor_url + this
.config
.popupURL + file
;
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
]);)
2751 // Modified version of GetHtml plugin's indent.
2752 HTMLArea
.indent
= function(s
, sindentChar
) {
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
;
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
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