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
;
119 HTMLArea
.RE_blocktag
= /^
(h1|h2|h3|h4|h5|h6|p|address|pre
)$
/i
;
120 HTMLArea
.RE_junktag
= /^\
/($|\
/)/;
121 // Hopefully a complete list of tags that MSIEs parser will consider
122 // as possible content tags. Retrieved from
123 // http://www.echoecho.com/htmlreference.htm
124 HTMLArea
.RE_msietag
= /^\
/?
(a|abbr|acronym|address|applet|area|b|base|basefont|bdo|bgsound|big|blink|blockquote|body|br|button|caption|center|cite|code|col|colgroup|comment|dd|del|dfn|dir|div|dl|dt|em|embed|fieldset|font|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|hr|html|i|iframe|ilayer|img|input|ins|isindex|kbd|keygen|label|layer|legend|li|link|map|marquee|menu|meta|multicol|nobr|noembed|noframes|nolayer|noscript|
object|ol|optgroup|option|p|param|plaintext|pre|q|s|samp|script|select|server|small|spacer|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|ul|
var)$
/i
126 HTMLArea
.Config
= function () {
127 this
.version
= "3.0";
130 this
.height
= "auto";
132 // enable creation of a status bar?
133 this
.statusBar
= true;
135 // maximum size of the undo queue
138 // the time interval at which undo samples are taken
139 this
.undoTimeout
= 500; // 1/2 sec.
141 // the next parameter specifies whether the toolbar should be included
142 // in the size or not.
143 this
.sizeIncludesToolbar
= true;
145 // if true then HTMLArea will retrieve the full HTML, starting with the
147 this
.fullPage
= false;
149 // style included in the iframe document
150 this
.pageStyle
= "body { background-color: #fff; font-family: 'Times New Roman', Times; } \n .lang { background-color: #dee; }";
152 // set to true if you want Word code to be cleaned upon Paste
153 this
.killWordOnPaste
= true;
155 // BaseURL included in the iframe document
156 this
.baseURL
= document
.baseURI || document
.URL
;
157 if (this
.baseURL
&& this
.baseURL
.match(/(.*)\
/([^\
/]+
)/))
158 this
.baseURL
= RegExp
.$1 +
"/";
161 this
.imgURL
= "images/";
162 this
.popupURL
= "popups/";
165 [ "fontname", "space",
167 "formatblock", "space",
169 "bold", "italic", "underline", "strikethrough", "separator",
170 "subscript", "superscript", "separator",
171 "clean", "separator", "undo", "redo" ],
173 [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator",
174 "lefttoright", "righttoleft", "separator",
175 "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator",
176 "forecolor", "hilitecolor", "separator",
177 "inserthorizontalrule", "createanchor", "createlink", "unlink", "nolink", "separator",
178 "insertimage", "inserttable",
179 "insertsmile", "insertchar", "search_replace",
180 <?php
if (!empty($CFG->aspellpath
) && file_exists($CFG->aspellpath
) && !empty($CFG->editorspelling
)) {
181 echo '"separator","spellcheck",';
183 "separator", "htmlmode", "separator", "popupeditor"]
187 "Arial": 'arial,helvetica,sans-serif',
188 "Courier New": 'courier new,courier,monospace',
189 "Georgia": 'georgia,times new roman,times,serif',
190 "Tahoma": 'tahoma,arial,helvetica,sans-serif',
191 "Times New Roman": 'times new roman,times,serif',
192 "Verdana": 'verdana,arial,helvetica,sans-serif',
194 "WingDings": 'wingdings'
209 "<?php echo $strheading ?> 1": "h1",
210 "<?php echo $strheading ?> 2": "h2",
211 "<?php echo $strheading ?> 3": "h3",
212 "<?php echo $strheading ?> 4": "h4",
213 "<?php echo $strheading ?> 5": "h5",
214 "<?php echo $strheading ?> 6": "h6",
215 "<?php echo $strnormal ?>": "p",
216 "<?php echo $straddress ?>": "address",
217 "<?php echo $strpreformatted ?>": "pre"
221 "<?php echo $strlang; ?>":"",
224 foreach ($LANGUAGES as $key => $name) {
225 $key = str_replace('_', '-', $key);
226 $strlangarray .= '"'.$key.'": "'.$key.'",';
228 $strlangarray .= '"'.$strmulti.'": "multi",';
230 foreach ($LANGUAGES as $key => $name) {
231 $key = str_replace('_', '-', $key);
232 $strlangarray .= '"'.$key.' ": "'.$key.'_ML",';
234 $strlangarray = substr($strlangarray, 0, -1);
239 this
.customSelects
= {};
241 function cut_copy_paste(e
, cmd
, obj
) {
246 bold
: [ "Bold", "ed_format_bold.gif", false, function(e
) {e
.execCommand("bold");} ],
247 italic
: [ "Italic", "ed_format_italic.gif", false, function(e
) {e
.execCommand("italic");} ],
248 underline
: [ "Underline", "ed_format_underline.gif", false, function(e
) {e
.execCommand("underline");} ],
249 strikethrough
: [ "Strikethrough", "ed_format_strike.gif", false, function(e
) {e
.execCommand("strikethrough");} ],
250 subscript
: [ "Subscript", "ed_format_sub.gif", false, function(e
) {e
.execCommand("subscript");} ],
251 superscript
: [ "Superscript", "ed_format_sup.gif", false, function(e
) {e
.execCommand("superscript");} ],
252 justifyleft
: [ "Justify Left", "ed_align_left.gif", false, function(e
) {e
.execCommand("justifyleft");} ],
253 justifycenter
: [ "Justify Center", "ed_align_center.gif", false, function(e
) {e
.execCommand("justifycenter");} ],
254 justifyright
: [ "Justify Right", "ed_align_right.gif", false, function(e
) {e
.execCommand("justifyright");} ],
255 justifyfull
: [ "Justify Full", "ed_align_justify.gif", false, function(e
) {e
.execCommand("justifyfull");} ],
256 insertorderedlist
: [ "Ordered List", "ed_list_num.gif", false, function(e
) {e
.execCommand("insertorderedlist");} ],
257 insertunorderedlist
: [ "Bulleted List", "ed_list_bullet.gif", false, function(e
) {e
.execCommand("insertunorderedlist");} ],
258 outdent
: [ "Decrease Indent", "ed_indent_less.gif", false, function(e
) {e
.execCommand("outdent");} ],
259 indent
: [ "Increase Indent", "ed_indent_more.gif", false, function(e
) {e
.execCommand("indent");} ],
260 forecolor
: [ "Font Color", "ed_color_fg.gif", false, function(e
) {e
.execCommand("forecolor");} ],
261 hilitecolor
: [ "Background Color", "ed_color_bg.gif", false, function(e
) {e
.execCommand("hilitecolor");} ],
262 inserthorizontalrule
: [ "Horizontal Rule", "ed_hr.gif", false, function(e
) {e
.execCommand("inserthorizontalrule");} ],
263 createanchor
: [ "Create anchor", "ed_anchor.gif", false, function(e
) {e
.execCommand("createanchor", true);} ],
264 createlink
: [ "Insert Web Link", "ed_link.gif", false, function(e
) {e
.execCommand("createlink", true);} ],
265 unlink
: [ "Remove Link", "ed_unlink.gif", false, function(e
) {e
.execCommand("unlink");} ],
266 nolink
: [ "No link", "ed_nolink.gif", false, function(e
) {e
.execCommand("nolink");} ],
267 insertimage
: [ "Insert/Modify Image", "ed_image.gif", false, function(e
) {e
.execCommand("insertimage");} ],
268 inserttable
: [ "Insert Table", "insert_table.gif", false, function(e
) {e
.execCommand("inserttable");} ],
269 htmlmode
: [ "Toggle HTML Source", "ed_html.gif", true, function(e
) {e
.execCommand("htmlmode");} ],
270 popupeditor
: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e
) {e
.execCommand("popupeditor");} ],
271 about
: [ "About this editor", "ed_about.gif", true, function(e
) {e
.execCommand("about");} ],
272 showhelp
: [ "Help using editor", "ed_help.gif", true, function(e
) {e
.execCommand("showhelp");} ],
273 undo
: [ "Undoes your last action", "ed_undo.gif", false, function(e
) {e
.execCommand("undo");} ],
274 redo
: [ "Redoes your last action", "ed_redo.gif", false, function(e
) {e
.execCommand("redo");} ],
275 clean
: [ "Clean Word HTML", "ed_wordclean.gif", false, function(e
) {e
.execCommand("killword"); }],
276 lefttoright
: [ "Direction left to right", "ed_left_to_right.gif", false, function(e
) {e
.execCommand("lefttoright");} ],
277 righttoleft
: [ "Direction right to left", "ed_right_to_left.gif", false, function(e
) {e
.execCommand("righttoleft");} ],
278 <?php
if (!empty($CFG->aspellpath
) && file_exists($CFG->aspellpath
) && !empty($CFG->editorspelling
)) {
279 echo 'spellcheck: ["Spell-check", "spell-check.gif", false, spellClickHandler ],'."\n";
281 insertsmile
: ["Insert Smiley", "em.icon.smile.gif", false, function(e
) {e
.execCommand("insertsmile");} ],
282 insertchar
: [ "Insert Char", "icon_ins_char.gif", false, function(e
) {e
.execCommand("insertchar");} ],
283 search_replace
: [ "Search and replace", "ed_replace.gif", false, function(e
) {e
.execCommand("searchandreplace");} ]
286 // initialize tooltips from the I18N module and generate correct image path
287 for (var i in this
.btnList
) {
288 var btn
= this
.btnList
[i
];
289 btn
[1] = _editor_url + this
.imgURL + btn
[1];
290 if (typeof HTMLArea
.I18N
.tooltips
[i
] != "undefined") {
291 btn
[0] = HTMLArea
.I18N
.tooltips
[i
];
296 HTMLArea
.Config
.prototype
.registerButton
= function(id
, tooltip
, image
, textMode
, action
, context
) {
298 if (typeof id
== "string") {
300 } else if (typeof id
== "object") {
303 alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
306 // check for existing id
307 if (typeof this
.customSelects
[the_id
] != "undefined") {
308 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
310 if (typeof this
.btnList
[the_id
] != "undefined") {
311 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
314 case "string": this
.btnList
[id
] = [ tooltip
, image
, textMode
, action
, context
]; break;
315 case "object": this
.btnList
[id
.id
] = [ id
.tooltip
, id
.image
, id
.textMode
, id
.action
, id
.context
]; break;
319 HTMLArea
.Config
.prototype
.registerDropdown
= function(object) {
320 // check for existing id
321 if (typeof this
.customSelects
[object.id
] != "undefined") {
322 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
324 if (typeof this
.btnList
[object.id
] != "undefined") {
325 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
327 this
.customSelects
[object.id
] = object;
330 HTMLArea
.Config
.prototype
.hideSomeButtons
= function(remove
) {
331 var toolbar
= this
.toolbar
;
332 for (var i in toolbar
) {
333 var line
= toolbar
[i
];
334 for (var j
= line
.length
; --j
>= 0; ) {
335 if (remove
.indexOf(" " + line
[j
] +
" ") >= 0) {
337 if (/separator|space
/.test(line
[j +
1])) {
346 /** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
347 HTMLArea
.replaceAll
= function(config
) {
348 var tas
= document
.getElementsByTagName("textarea");
349 for (var i
= tas
.length
; i
> 0; (new HTMLArea(tas
[--i
], config
)).generate());
352 /** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
353 HTMLArea
.replace
= function(id
, config
) {
354 var ta
= HTMLArea
.getElementById("textarea", id
);
355 return ta ?
(new HTMLArea(ta
, config
)).generate() : null;
358 // Creates the toolbar and appends it to the _htmlarea
359 HTMLArea
.prototype
._createToolbar
= function () {
360 var editor
= this
; // to access this in nested functions
362 var toolbar
= document
.createElement("div");
363 this
._toolbar
= toolbar
;
364 toolbar
.className
= "toolbar";
365 toolbar
.unselectable
= "1";
367 var tb_objects
= new Object();
368 this
._toolbarObjects
= tb_objects
;
370 // creates a new line in the toolbar
372 var table
= document
.createElement("table");
373 table
.border
= "0px";
374 table
.cellSpacing
= "0px";
375 table
.cellPadding
= "0px";
376 toolbar
.appendChild(table
);
377 // TBODY is required for IE, otherwise you don't see anything
379 var tb_body
= document
.createElement("tbody");
380 table
.appendChild(tb_body
);
381 tb_row
= document
.createElement("tr");
382 tb_body
.appendChild(tb_row
);
383 }; // END of function: newLine
387 function setButtonStatus(id
, newval
) {
388 var oldval
= this
[id
];
389 var el
= this
.element
;
390 if (oldval
!= newval
) {
394 HTMLArea
._removeClass(el
, "buttonDisabled");
397 HTMLArea
._addClass(el
, "buttonDisabled");
403 HTMLArea
._addClass(el
, "buttonPressed");
405 HTMLArea
._removeClass(el
, "buttonPressed");
411 }; // END of function: setButtonStatus
413 function createSelect(txt
) {
417 var customSelects
= editor
.config
.customSelects
;
424 options
= editor
.config
[txt
];
428 // try to fetch it from the list of registered selects
430 var dropdown
= customSelects
[cmd
];
431 if (typeof dropdown
!= "undefined") {
432 options
= dropdown
.options
;
433 context
= dropdown
.context
;
435 alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
440 el
= document
.createElement("select");
442 name
: txt
, // field name
443 element
: el
, // the UI element (SELECT)
444 enabled
: true, // is it enabled?
445 text
: false, // enabled in text mode?
446 cmd
: cmd
, // command ID
447 state
: setButtonStatus
, // for changing state
450 tb_objects
[txt
] = obj
;
451 for (var i in options
) {
452 var op
= document
.createElement("option");
453 op
.appendChild(document
.createTextNode(i
));
454 op
.value
= options
[i
];
457 HTMLArea
._addEvent(el
, "change", function () {
458 editor
._comboSelected(el
, txt
);
461 editor
.dropdowns
[txt
] = el
; // Keep track of the element for keyboard
464 }; // END of function: createSelect
466 // appends a new button to toolbar
467 function createButton(txt
) {
468 // the element that will be created
473 el
= document
.createElement("div");
474 el
.className
= "separator";
477 el
= document
.createElement("div");
478 el
.className
= "space";
483 case "textindicator":
484 el
= document
.createElement("div");
485 el
.appendChild(document
.createTextNode("A"));
486 el
.className
= "indicator";
487 el
.title
= HTMLArea
.I18N
.tooltips
.textindicator
;
489 name
: txt
, // the button name (i.e. 'bold')
490 element
: el
, // the UI element (DIV)
491 enabled
: true, // is it enabled?
492 active
: false, // is it pressed?
493 text
: false, // enabled in text mode?
494 cmd
: "textindicator", // the command ID
495 state
: setButtonStatus
// for changing state
497 tb_objects
[txt
] = obj
;
500 btn
= editor
.config
.btnList
[txt
];
503 el
= document
.createElement("div");
505 el
.className
= "button";
506 // let's just pretend we have a button object, and
507 // assign all the needed information to it.
509 name
: txt
, // the button name (i.e. 'bold')
510 element
: el
, // the UI element (DIV)
511 enabled
: true, // is it enabled?
512 active
: false, // is it pressed?
513 text
: btn
[2], // enabled in text mode?
514 cmd
: btn
[3], // the command ID
515 state
: setButtonStatus
, // for changing state
516 context
: btn
[4] ||
null // enabled in a certain context?
518 tb_objects
[txt
] = obj
;
519 // handlers to emulate nice flat toolbar buttons
520 HTMLArea
._addEvent(el
, "mouseover", function () {
522 HTMLArea
._addClass(el
, "buttonHover");
525 HTMLArea
._addEvent(el
, "mouseout", function () {
526 if (obj
.enabled
) with (HTMLArea
) {
527 _removeClass(el
, "buttonHover");
528 _removeClass(el
, "buttonActive");
529 (obj
.active
) && _addClass(el
, "buttonPressed");
532 HTMLArea
._addEvent(el
, "mousedown", function (ev
) {
533 if (obj
.enabled
) with (HTMLArea
) {
534 _addClass(el
, "buttonActive");
535 _removeClass(el
, "buttonPressed");
536 _stopEvent(is_ie ? window
.event
: ev
);
539 // when clicked, do the following:
540 HTMLArea
._addEvent(el
, "click", function (ev
) {
541 if (obj
.enabled
) with (HTMLArea
) {
542 _removeClass(el
, "buttonActive");
543 _removeClass(el
, "buttonHover");
544 obj
.cmd(editor
, obj
.name
, obj
);
545 _stopEvent(is_ie ? window
.event
: ev
);
548 var img
= document
.createElement("img");
550 img
.style
.width
= "18px";
551 img
.style
.height
= "18px";
554 el
= createSelect(txt
);
557 var tb_cell
= document
.createElement("td");
558 tb_row
.appendChild(tb_cell
);
559 tb_cell
.appendChild(el
);
561 alert("FIXME: Unknown toolbar item: " + txt
);
567 for (var i in this
.config
.toolbar
) {
568 if (this
.config
.toolbar
.propertyIsEnumerable(i
)) { // fix for prototype.js compatibility
570 createButton("linebreak");
574 var group
= this
.config
.toolbar
[i
];
575 for (var j in group
) {
576 if (group
.propertyIsEnumerable(j
)) { // fix for prototype.js compatibility
578 if (/^
([IT
])\
[(.*?
)\
]/.test(code
)) {
579 // special case, create text label
580 var l7ed
= RegExp
.$1 == "I"; // localized?
581 var label
= RegExp
.$2;
583 label
= HTMLArea
.I18N
.custom
[label
];
585 var tb_cell
= document
.createElement("td");
586 tb_row
.appendChild(tb_cell
);
587 tb_cell
.className
= "label";
588 tb_cell
.innerHTML
= label
;
597 this
._htmlArea
.appendChild(toolbar
);
600 HTMLArea
.prototype
._createStatusBar
= function() {
601 var statusbar
= document
.createElement("div");
602 statusbar
.className
= "statusBar";
603 this
._htmlArea
.appendChild(statusbar
);
604 this
._statusBar
= statusbar
;
605 // statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
606 // creates a holder for the path view
607 div
= document
.createElement("span");
608 div
.className
= "statusBarTree";
609 div
.innerHTML
= HTMLArea
.I18N
.msg
["Path"] +
": ";
610 this
._statusBarTree
= div
;
611 this
._statusBar
.appendChild(div
);
612 if (!this
.config
.statusBar
) {
614 statusbar
.style
.display
= "none";
618 // Creates the HTMLArea object and replaces the textarea with it.
619 HTMLArea
.prototype
.generate
= function () {
620 var editor
= this
; // we'll need "this" in some nested functions
623 var textarea
= this
._textArea
;
624 if (typeof textarea
== "string") {
625 // it's not element but ID
626 this
._textArea
= textarea
= HTMLArea
.getElementById("textarea", textarea
);
628 // Fix for IE's sticky bug. Editor doesn't load
631 if ( textarea
.offsetHeight
&& textarea
.offsetHeight
> 0 ) {
632 height
= textarea
.offsetHeight
;
637 w
: textarea
.offsetWidth
,
640 textarea
.style
.display
= "none";
642 // create the editor framework
643 var htmlarea
= document
.createElement("div");
644 htmlarea
.className
= "htmlarea";
645 this
._htmlArea
= htmlarea
;
647 // insert the editor before the textarea.
648 //Bug fix - unless the textarea is nested within its label, in which case insert editor before label.
649 if (textarea
.parentNode
.nodeName
.toLowerCase()=='label') {
650 textarea
.parentNode
.parentNode
.insertBefore(htmlarea
,textarea
.parentNode
);
653 textarea
.parentNode
.insertBefore(htmlarea
, textarea
);
657 // we have a form, on submit get the HTMLArea content and
658 // update original textarea.
659 var f
= textarea
.form
;
660 if (typeof f
.onsubmit
== "function") {
661 var funcref
= f
.onsubmit
;
662 if (typeof f
.__msh_prevOnSubmit
== "undefined") {
663 f
.__msh_prevOnSubmit
= [];
665 f
.__msh_prevOnSubmit
.push(funcref
);
667 f
.onsubmit
= function() {
668 // Moodle hack. Bug fix #2736
669 var test
= editor
.getHTML();
670 test
= test
.replace(/<br \
/>/gi
, '');
671 test
= test
.replace(/\
 \
;/gi
, '');
673 //alert(test + test.length);
674 if (test
.length
< 1) {
675 editor
._textArea
.value
= test
.trim();
677 editor
._textArea
.value
= editor
.getHTML();
680 var a
= this
.__msh_prevOnSubmit
;
682 // call previous submit methods if they were there.
683 if (typeof a
!= "undefined") {
684 for (var i
= a
.length
; --i
>= 0;) {
690 if (typeof f
.onreset
== "function") {
691 var funcref
= f
.onreset
;
692 if (typeof f
.__msh_prevOnReset
== "undefined") {
693 f
.__msh_prevOnReset
= [];
695 f
.__msh_prevOnReset
.push(funcref
);
697 f
.onreset
= function() {
698 editor
.setHTML(editor
._textArea
.value
);
699 editor
.updateToolbar();
700 var a
= this
.__msh_prevOnReset
;
701 // call previous reset methods if they were there.
702 if (typeof a
!= "undefined") {
703 for (var i
= a
.length
; --i
>= 0;) {
710 // add a handler for the "back/forward" case -- on body.unload we save
711 // the HTML content into the original textarea.
713 window
.onunload
= function() {
714 editor
._textArea
.value
= editor
.getHTML();
718 // creates & appends the toolbar
719 this
._createToolbar();
722 var iframe
= document
.createElement("iframe");
724 iframe
.src
= "about:blank";
726 iframe
.className
= "iframe";
728 htmlarea
.appendChild(iframe
);
731 editor
._iframe
= iframe
;
732 var doc
= editor
._iframe
.contentWindow
.document
;
735 // Generate iframe content
737 if (!editor
.config
.fullPage
) {
740 html +
= '<meta http-equiv="content-type" content="text/html; charset=utf-8" />\n';
741 if (editor
.config
.baseURL
)
742 html +
= '<base href="' + editor
.config
.baseURL +
'" />';
743 html +
= '<style type="text/css">\n' + editor
.config
.pageStyle +
"td { border: 1px dotted gray; } body { direction: <?php echo get_string('thisdirection')?>; } </style>\n"; // RTL support: direction added for RTL support
746 html +
= editor
._textArea
.value
;
747 html
= html
.replace(/<nolink
>/gi
, '<span class="nolink">').
748 replace(/<\
/nolink
>/gi
, '</span>');
752 html
= editor
._textArea
.value
;
753 if (html
.match(HTMLArea
.RE_doctype
)) {
754 editor
.setDoctype(RegExp
.$1);
755 html
= html
.replace(HTMLArea
.RE_doctype
, "");
759 // Write content to iframe
764 // The magic: onClick the designMode is set to 'on'
765 // This one is for click on HTMLarea toolbar and else
766 if(HTMLArea
.is_gecko
) {
771 if(editor
.designModeIsOn
!= true)
773 editor
.designModeIsOn
= true;
775 doc
.designMode
= "on";
783 // This one is for click in iframe
785 editor
._iframe
.contentWindow
,
788 if(editor
.designModeIsOn
!= true)
790 editor
.designModeIsOn
= true;
792 doc
.designMode
= "on";
800 // creates & appends the status bar, if the case
801 this
._createStatusBar();
803 // remove the default border as it keeps us from computing correctly
804 // the sizes. (somebody tell me why doesn't this work in IE)
806 if (!HTMLArea
.is_ie
) {
807 iframe
.style
.borderWidth
= "1px";
810 // size the IFRAME according to user's prefs or initial textarea
811 var height
= (this
.config
.height
== "auto" ?
(this
._ta_size
.h
) : this
.config
.height
);
812 height
= parseInt(height
);
813 var width
= (this
.config
.width
== "auto" ?
(this
._toolbar
.offsetWidth
) : this
.config
.width
);
814 width
= (width
== 0 ?
598 : width
);
815 //width = Math.max(parseInt(width), 598);
817 width
= String(width
);
818 if (width
.match(/^\d+$
/)) { // is this a pure int? if so, let it be in px, and remove 2px
824 iframe
.style
.width
= width
;
826 if (this
.config
.sizeIncludesToolbar
) {
827 // substract toolbar height
828 height
-= this
._toolbar
.offsetHeight
;
829 height
-= this
._statusBar
.offsetHeight
;
834 iframe
.style
.height
= height +
"px";
836 // the editor including the toolbar now have the same size as the
837 // original textarea.. which means that we need to reduce that a bit.
838 textarea
.style
.width
= iframe
.style
.width
;
839 textarea
.style
.height
= iframe
.style
.height
;
841 if (HTMLArea
.is_ie
) {
842 doc
.body
.contentEditable
= true;
845 // intercept some events; for updating the toolbar & keyboard handlers
847 (doc
, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
849 return editor
._editorEvent(HTMLArea
.is_ie ? editor
._iframe
.contentWindow
.event
: event
);
852 // check if any plugins have registered refresh handlers
853 for (var i in editor
.plugins
) {
854 var plugin
= editor
.plugins
[i
].instance
;
855 if (typeof plugin
.onGenerate
== "function") {
858 if (typeof plugin
.onGenerateOnce
== "function") {
859 plugin
.onGenerateOnce();
860 plugin
.onGenerateOnce
= null;
864 // Moodle fix for bug Bug #2521 Too long statusbar line in IE
866 //setTimeout(function() {
867 // editor.updateToolbar();
870 if (typeof editor
.onGenerate
== "function") {
876 // Switches editor mode; parameter can be "textmode" or "wysiwyg". If no
877 // parameter was passed this function toggles between modes.
878 HTMLArea
.prototype
.setMode
= function(mode
) {
879 if (typeof mode
== "undefined") {
880 mode
= ((this
._editMode
== "textmode") ?
"wysiwyg" : "textmode");
884 this
._textArea
.value
= this
.getHTML();
885 this
._iframe
.style
.display
= "none";
886 this
._textArea
.style
.display
= "block";
887 if (this
.config
.statusBar
) {
888 while(this
._statusBar
.childNodes
.length
>0) {
889 this
._statusBar
.removeChild(this
._statusBar
.childNodes
[0]);
892 this
._statusBar
.appendChild(document
.createTextNode(HTMLArea
.I18N
.msg
["TEXT_MODE"]));
896 if (HTMLArea
.is_gecko
) {
897 // disable design mode before changing innerHTML
899 this
._doc
.designMode
= "off";
902 if (!this
.config
.fullPage
)
903 this
._doc
.body
.innerHTML
= this
.getHTML();
905 this
.setFullHTML(this
.getHTML());
906 this
._iframe
.style
.display
= "block";
907 this
._textArea
.style
.display
= "none";
908 if (HTMLArea
.is_gecko
) {
909 // we need to refresh that info for Moz-1.3a
911 this
._doc
.designMode
= "on";
915 if (this
.config
.statusBar
) {
916 this
._statusBar
.innerHTML
= '';
917 this
._statusBar
.appendChild(this
._statusBarTree
);
921 alert("Mode <" + mode +
"> not defined!");
924 this
._editMode
= mode
;
928 HTMLArea
.prototype
.setFullHTML
= function(html
) {
929 var save_multiline
= RegExp
.multiline
;
930 RegExp
.multiline
= true;
931 if (html
.match(HTMLArea
.RE_doctype
)) {
932 this
.setDoctype(RegExp
.$1);
933 html
= html
.replace(HTMLArea
.RE_doctype
, "");
935 RegExp
.multiline
= save_multiline
;
936 if (!HTMLArea
.is_ie
) {
937 if (html
.match(HTMLArea
.RE_head
))
938 this
._doc
.getElementsByTagName("head")[0].innerHTML
= RegExp
.$1;
939 if (html
.match(HTMLArea
.RE_body
))
940 this
._doc
.getElementsByTagName("body")[0].innerHTML
= RegExp
.$1;
942 var html_re
= /<html
>((.|\n
)*?
)<\
/html
>/i
;
943 html
= html
.replace(html_re
, "$1");
945 this
._doc
.write(html
);
947 this
._doc
.body
.contentEditable
= true;
954 HTMLArea
.prototype
.registerPlugin2
= function(plugin
, args
) {
955 if (typeof plugin
== "string")
956 plugin
= eval(plugin
);
957 var obj
= new plugin(this
, args
);
960 var info
= plugin
._pluginInfo
;
963 clone.instance
= obj
;
965 this
.plugins
[plugin
._pluginInfo
.name
] = clone;
967 alert("Can't register plugin " + plugin
.toString() +
".");
970 // Create the specified plugin and register it with this HTMLArea
971 HTMLArea
.prototype
.registerPlugin
= function() {
972 var plugin
= arguments
[0];
974 for (var i
= 1; i
< arguments
.length
; ++i
)
975 args
.push(arguments
[i
]);
976 this
.registerPlugin2(plugin
, args
);
979 HTMLArea
.loadPlugin
= function(pluginName
) {
980 var dir
= _editor_url +
"plugins/" + pluginName
;
981 var plugin
= pluginName
.replace(/([a
-z
])([A
-Z
])([a
-z
])/g
,
982 function (str
, l1
, l2
, l3
) {
983 return l1 +
"-" + l2
.toLowerCase() + l3
;
984 }).toLowerCase() +
".js";
985 var plugin_file
= dir +
"/" + plugin
;
986 var plugin_lang
= dir +
"/lang/" + HTMLArea
.I18N
.lang +
".js";
987 HTMLArea
._scripts
.push(plugin_file
, plugin_lang
);
988 document
.write("<script type='text/javascript' src='" + plugin_file +
"'></script>");
989 document
.write("<script type='text/javascript' src='" + plugin_lang +
"'></script>");
992 HTMLArea
.loadStyle
= function(style
, plugin
) {
993 var url
= _editor_url ||
'';
994 if (typeof plugin
!= "undefined") {
995 url +
= "plugins/" + plugin +
"/";
998 document
.write("<style type='text/css'>@import url(" + url +
");</style>");
1000 HTMLArea
.loadStyle("htmlarea.css");
1002 // Category: EDITOR UTILITIES
1004 // The following function is a slight variation of the word cleaner code posted
1005 // by Weeezl (user @ InteractiveTools forums).
1006 HTMLArea
.prototype
._wordClean
= function() {
1007 this
._unnestBlocks();
1009 var D
= this
.getInnerHTML();
1010 if (/[Mm
]so
/.test(D
)) {
1013 D
= D
.replace(/\r\n/g
, '\[br\]').
1016 replace(/\
 \
;/g
,' ');
1018 // keep tags, strip attributes
1019 D
= D
.replace(/ class=[^\s|
>]*/gi
,'').
1020 //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">').
1021 replace(/ style
=\"[^
>]*\"/gi
,'').
1022 replace(/ align
=[^\s|
>]*/gi
,'');
1025 D
= D
.replace(/<b
[^
>]*>/gi
,'<b>').
1026 replace(/<i
[^
>]*>/gi
,'<i>').
1027 replace(/<li
[^
>]*>/gi
,'<li>').
1028 replace(/<ul
[^
>]*>/gi
,'<ul>');
1030 // replace outdated tags
1031 D
= D
.replace(/<b
>/gi
,'<strong>').
1032 replace(/<\
/b
>/gi
,'</strong>');
1034 // mozilla doesn't like <em> tags
1035 D
= D
.replace(/<em
>/gi
,'<i>').
1036 replace(/<\
/em
>/gi
,'</i>');
1038 // kill unwanted tags
1039 D
= D
.replace(/<\?xml
:[^
>]*>/g
, ''). // Word xml
1040 replace(/<\
/?st1
:[^
>]*>/g
,''). // Word SmartTags
1041 replace(/<\
/?
[a
-z
]\
:[^
>]*>/g
,''). // All other funny Word non-HTML stuff
1042 replace(/<\
/?personname
[^
>]*>/gi
,'').
1043 replace(/<\
/?font
[^
>]*>/gi
,''). // Disable if you want to keep font formatting
1044 replace(/<\
/?span
[^
>]*>/gi
,' ').
1045 replace(/<\
/?div
[^
>]*>/gi
,' ').
1046 replace(/<\
/?pre
[^
>]*>/gi
,' ').
1047 replace(/<(\
/?
)(h
[1-6]+
)[^
>]*>/gi
,'<$1$2>');
1049 // Lorenzo Nicola's addition
1050 // to get rid off silly word generated tags.
1051 D
= D
.replace(/<!--\
[[^\
]]*\
]-->/gi
,' ');
1054 //D = D.replace(/<strong><\/strong>/gi,'').
1055 //replace(/<i><\/i>/gi,'').
1056 //replace(/<P[^>]*><\/P>/gi,'');
1057 D
= D
.replace(/<h
[1-6]+
>\s?
<\
/h
[1-6]+
>/gi
, ''); // Remove empty headings
1060 oldlen
= D
.length +
1;
1061 while(oldlen
> D
.length
) {
1063 // join us now and free the tags, we'll be free hackers, we'll be free... ;-)
1064 D
= D
.replace(/<([a
-z
][a
-z
]*)> *<\
/\
1>/gi
,' ').
1065 replace(/<([a
-z
][a
-z
]*)> *<([a
-z
][^
>]*)> *<\
/\
1>/gi
,'<$2>');
1067 D
= D
.replace(/<([a
-z
][a
-z
]*)><\
1>/gi
,'<$1>').
1068 replace(/<\
/([a
-z
][a
-z
]*)><\
/\
1>/gi
,'<\/$1>');
1070 // nuke double spaces
1071 D
= D
.replace(/ */gi
,' ');
1073 // Split into lines and remove
1074 // empty lines and add carriage returns back
1075 var splitter
= /\
[br\
]/g
;
1076 var emptyLine
= /^\s+\s+$
/g
;
1078 var toLines
= D
.split(splitter
);
1079 for (var i
= 0; i
< toLines
.length
; i++
) {
1080 var line
= toLines
[i
];
1081 if (line
.length
< 1) {
1085 if (emptyLine
.test(line
)) {
1089 line
= line
.replace(/^\s+\s+$
/g
, '');
1090 strHTML +
= line +
'\n';
1096 this
.updateToolbar();
1100 HTMLArea
.prototype
._unnestBlockWalk
= function(node
, unnestingParent
) {
1101 if (HTMLArea
.RE_blocktag
.test(node
.nodeName
)) {
1102 if (unnestingParent
) {
1103 if (node
.nextSibling
) {
1104 var splitNode
= this
._doc
.createElement(unnestingParent
.nodeName
.toLowerCase());
1105 while (node
.nextSibling
) {
1106 splitNode
.appendChild(node
.nextSibling
);
1108 unnestingParent
.parentNode
.insertBefore(splitNode
, unnestingParent
.nextSibling
);
1110 unnestingParent
.parentNode
.insertBefore(node
, unnestingParent
.nextSibling
);
1113 else if (node
.firstChild
) {
1114 this
._unnestBlockWalk(node
.firstChild
, node
);
1117 if (node
.firstChild
) {
1118 this
._unnestBlockWalk(node
.firstChild
, null);
1121 if (node
.nextSibling
) {
1122 this
._unnestBlockWalk(node
.nextSibling
, unnestingParent
);
1126 HTMLArea
.prototype
._unnestBlocks
= function() {
1127 this
._unnestBlockWalk(this
._doc
.documentElement
, null);
1130 HTMLArea
.prototype
.forceRedraw
= function() {
1131 this
._doc
.body
.style
.visibility
= "hidden";
1132 this
._doc
.body
.style
.visibility
= "visible";
1133 // this._doc.body.innerHTML = this.getInnerHTML();
1136 // focuses the iframe window. returns a reference to the editor document.
1137 HTMLArea
.prototype
.focusEditor
= function() {
1138 switch (this
._editMode
) {
1139 case "wysiwyg" : this
._iframe
.contentWindow
.focus(); break;
1140 case "textmode": this
._textArea
.focus(); break;
1141 default : alert("ERROR: mode " + this
._editMode +
" is not defined");
1146 // takes a snapshot of the current text (for undo)
1147 HTMLArea
.prototype
._undoTakeSnapshot
= function() {
1149 if (this
._undoPos
>= this
.config
.undoSteps
) {
1150 // remove the first element
1151 this
._undoQueue
.shift();
1154 // use the fasted method (getInnerHTML);
1156 var txt
= this
.getInnerHTML();
1157 if (this
._undoPos
> 0)
1158 take
= (this
._undoQueue
[this
._undoPos
- 1] != txt
);
1160 this
._undoQueue
[this
._undoPos
] = txt
;
1166 HTMLArea
.prototype
.undo
= function() {
1167 if (this
._undoPos
> 0) {
1168 var txt
= this
._undoQueue
[--this
._undoPos
];
1169 if (txt
) this
.setHTML(txt
);
1170 else ++this
._undoPos
;
1174 HTMLArea
.prototype
.redo
= function() {
1175 if (this
._undoPos
< this
._undoQueue
.length
- 1) {
1176 var txt
= this
._undoQueue
[++this
._undoPos
];
1177 if (txt
) this
.setHTML(txt
);
1178 else --this
._undoPos
;
1182 // updates enabled/disable/active state of the toolbar elements
1183 HTMLArea
.prototype
.updateToolbar
= function(noStatus
) {
1184 var doc
= this
._doc
;
1185 var text
= (this
._editMode
== "textmode");
1186 var ancestors
= null;
1188 ancestors
= this
.getAllAncestors();
1189 if (this
.config
.statusBar
&& !noStatus
) {
1191 while(this
._statusBarTree
.childNodes
.length
>0) {
1192 this
._statusBarTree
.removeChild(this
._statusBarTree
.childNodes
[0]);
1195 this
._statusBarTree
.appendChild(document
.createTextNode(HTMLArea
.I18N
.msg
["Path"] +
": "));
1197 for (var i
= ancestors
.length
; --i
>= 0;) {
1198 var el
= ancestors
[i
];
1200 // hell knows why we get here; this
1201 // could be a classic example of why
1202 // it's good to check for conditions
1203 // that are impossible to happen ;-)
1206 var a
= document
.createElement("a");
1210 a
.onclick
= function() {
1212 this
.editor
.selectNodeContents(this
.el
);
1213 this
.editor
.updateToolbar(true);
1216 a
.oncontextmenu
= function() {
1217 // TODO: add context menu here
1219 var info
= "Inline style:\n\n";
1220 info +
= this
.el
.style
.cssText
.split(/;\s*/
).join(";\n");
1224 var txt
= el
.tagName
.toLowerCase();
1225 a
.title
= el
.style
.cssText
;
1230 txt +
= "." + el
.className
;
1232 a
.appendChild(document
.createTextNode(txt
));
1233 this
._statusBarTree
.appendChild(a
);
1235 this
._statusBarTree
.appendChild(document
.createTextNode(String.fromCharCode(0xbb)));
1240 for (var i in this
._toolbarObjects
) {
1241 var btn
= this
._toolbarObjects
[i
];
1243 var inContext
= true;
1244 if (btn
.context
&& !text
) {
1246 var context
= btn
.context
;
1248 if (/(.*)\
[(.*?
)\
]/.test(context
)) {
1249 context
= RegExp
.$1;
1250 attrs
= RegExp
.$2.split(",");
1252 context
= context
.toLowerCase();
1253 var match
= (context
== "*");
1254 for (var k in ancestors
) {
1255 if (!ancestors
[k
]) {
1256 // the impossible really happens.
1259 if (match ||
(ancestors
[k
].tagName
.toLowerCase() == context
)) {
1261 for (var ka in attrs
) {
1262 if (!eval("ancestors[k]." + attrs
[ka
])) {
1273 btn
.state("enabled", (!text || btn
.text
) && inContext
);
1274 if (typeof cmd
== "function") {
1277 // look-it-up in the custom dropdown boxes
1278 var dropdown
= this
.config
.customSelects
[cmd
];
1279 if ((!text || btn
.text
) && (typeof dropdown
!= "undefined")) {
1280 dropdown
.refresh(this
);
1288 var value
= ("" + doc
.queryCommandValue(cmd
)).toLowerCase();
1290 // FIXME: what do we do here?
1293 var options
= this
.config
[cmd
];
1295 // btn.element.selectedIndex = 0;
1296 for (var j in options
) {
1297 // FIXME: the following line is scary.
1298 if ((j
.toLowerCase() == value
) ||
1299 (options
[j
].substr(0, value
.length
).toLowerCase() == value
)) {
1300 btn
.element
.selectedIndex
= k
;
1310 parentEl
= this
.getParentElement();
1311 if (parentEl
.getAttribute('lang')) {
1312 // A language was previously defined for the block.
1313 if (parentEl
.getAttribute('class') == 'multilang') {
1314 value
= parentEl
.getAttribute('lang')+
'_ML';
1316 value
= parentEl
.getAttribute('lang');
1321 var options
= this
.config
[cmd
];
1323 for (var j in options
) {
1324 // FIXME: the following line is scary.
1325 if ((j
.toLowerCase() == value
) ||
1326 (options
[j
].substr(0, value
.length
).toLowerCase() == value
)) {
1327 btn
.element
.selectedIndex
= k
;
1334 case "textindicator":
1336 try {with (btn
.element
.style
) {
1337 backgroundColor
= HTMLArea
._makeColor(
1338 doc
.queryCommandValue(HTMLArea
.is_ie ?
"backcolor" : "hilitecolor"));
1339 if (/transparent
/i
.test(backgroundColor
)) {
1341 backgroundColor
= HTMLArea
._makeColor(doc
.queryCommandValue("backcolor"));
1343 color
= HTMLArea
._makeColor(doc
.queryCommandValue("forecolor"));
1344 fontFamily
= doc
.queryCommandValue("fontname");
1345 fontWeight
= doc
.queryCommandState("bold") ?
"bold" : "normal";
1346 fontStyle
= doc
.queryCommandState("italic") ?
"italic" : "normal";
1348 // alert(e + "\n\n" + cmd);
1352 case "htmlmode": btn
.state("active", text
); break;
1355 var el
= this
.getParentElement();
1356 while (el
&& !HTMLArea
.isBlockElement(el
))
1359 btn
.state("active", (el
.style
.direction
== ((cmd
== "righttoleft") ?
"rtl" : "ltr")));
1363 btn
.state("active", (!text
&& doc
.queryCommandState(cmd
)));
1367 // take undo snapshots
1368 if (this
._customUndo
&& !this
._timerUndo
) {
1369 this
._undoTakeSnapshot();
1371 this
._timerUndo
= setTimeout(function() {
1372 editor
._timerUndo
= null;
1373 }, this
.config
.undoTimeout
);
1375 // check if any plugins have registered refresh handlers
1376 for (var i in this
.plugins
) {
1377 var plugin
= this
.plugins
[i
].instance
;
1378 if (typeof plugin
.onUpdateToolbar
== "function")
1379 plugin
.onUpdateToolbar();
1383 /** Returns a node after which we can insert other nodes, in the current
1384 * selection. The selection is removed. It splits a text node, if needed.
1386 HTMLArea
.prototype
.insertNodeAtSelection
= function(toBeInserted
) {
1387 if (!HTMLArea
.is_ie
) {
1388 var sel
= this
._getSelection();
1389 var range
= this
._createRange(sel
);
1390 // remove the current selection
1391 sel
.removeAllRanges();
1392 range
.deleteContents();
1393 var node
= range
.startContainer
;
1394 var pos
= range
.startOffset
;
1395 switch (node
.nodeType
) {
1396 case 3: // Node.TEXT_NODE
1397 // we have to split it at the caret position.
1398 if (toBeInserted
.nodeType
== 3) {
1399 // do optimized insertion
1400 node
.insertData(pos
, toBeInserted
.data
);
1401 range
= this
._createRange();
1402 range
.setEnd(node
, pos + toBeInserted
.length
);
1403 range
.setStart(node
, pos + toBeInserted
.length
);
1404 sel
.addRange(range
);
1406 node
= node
.splitText(pos
);
1407 var selnode
= toBeInserted
;
1408 if (toBeInserted
.nodeType
== 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1409 selnode
= selnode
.firstChild
;
1411 node
.parentNode
.insertBefore(toBeInserted
, node
);
1412 this
.selectNodeContents(selnode
);
1413 this
.updateToolbar();
1416 case 1: // Node.ELEMENT_NODE
1417 var selnode
= toBeInserted
;
1418 if (toBeInserted
.nodeType
== 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1419 selnode
= selnode
.firstChild
;
1421 node
.insertBefore(toBeInserted
, node
.childNodes
[pos
]);
1422 this
.selectNodeContents(selnode
);
1423 this
.updateToolbar();
1427 return null; // this function not yet used for IE <FIXME>
1431 // Returns the deepest node that contains both endpoints of the selection.
1432 HTMLArea
.prototype
.getParentElement
= function() {
1433 var sel
= this
._getSelection();
1434 var range
= this
._createRange(sel
);
1435 if (HTMLArea
.is_ie
) {
1439 return range
.parentElement();
1441 return range
.item(0);
1443 return this
._doc
.body
;
1446 var p
= range
.commonAncestorContainer
;
1447 if (!range
.collapsed
&& range
.startContainer
== range
.endContainer
&&
1448 range
.startOffset
- range
.endOffset
<= 1 && range
.startContainer
.hasChildNodes())
1449 p
= range
.startContainer
.childNodes
[range
.startOffset
];
1451 alert(range.startContainer + ":" + range.startOffset + "\n" +
1452 range.endContainer + ":" + range.endOffset);
1454 while (p
.nodeType
== 3) {
1463 // Returns an array with all the ancestor nodes of the selection.
1464 HTMLArea
.prototype
.getAllAncestors
= function() {
1465 var p
= this
.getParentElement();
1467 while (p
&& (p
.nodeType
== 1) && (p
.tagName
.toLowerCase() != 'body')) {
1471 a
.push(this
._doc
.body
);
1475 // Selects the contents inside the given node
1476 HTMLArea
.prototype
.selectNodeContents
= function(node
, pos
) {
1480 var collapsed
= (typeof pos
!= "undefined");
1481 if (HTMLArea
.is_ie
) {
1482 range
= this
._doc
.body
.createTextRange();
1483 range
.moveToElementText(node
);
1484 (collapsed
) && range
.collapse(pos
);
1487 var sel
= this
._getSelection();
1488 range
= this
._doc
.createRange();
1489 range
.selectNodeContents(node
);
1490 (collapsed
) && range
.collapse(pos
);
1491 sel
.removeAllRanges();
1492 sel
.addRange(range
);
1496 // Call this function to insert HTML code at the current position. It deletes
1497 // the selection, if any.
1498 HTMLArea
.prototype
.insertHTML
= function(html
) {
1499 var sel
= this
._getSelection();
1500 var range
= this
._createRange(sel
);
1501 if (HTMLArea
.is_ie
) {
1502 range
.pasteHTML(html
);
1504 // construct a new document fragment with the given HTML
1505 var fragment
= this
._doc
.createDocumentFragment();
1506 var div
= this
._doc
.createElement("div");
1507 div
.innerHTML
= html
;
1508 while (div
.firstChild
) {
1509 // the following call also removes the node from div
1510 fragment
.appendChild(div
.firstChild
);
1512 // this also removes the selection
1513 var node
= this
.insertNodeAtSelection(fragment
);
1517 // Call this function to surround the existing HTML code in the selection with
1518 // your tags. FIXME: buggy! This function will be deprecated "soon".
1519 HTMLArea
.prototype
.surroundHTML
= function(startTag
, endTag
) {
1520 var html
= this
.getSelectedHTML();
1521 // the following also deletes the selection
1522 this
.insertHTML(startTag + html + endTag
);
1525 /// Retrieve the selected block
1526 HTMLArea
.prototype
.getSelectedHTML
= function() {
1527 var sel
= this
._getSelection();
1528 var range
= this
._createRange(sel
);
1529 var existing
= null;
1530 if (HTMLArea
.is_ie
) {
1531 existing
= range
.htmlText
;
1533 existing
= HTMLArea
.getHTML(range
.cloneContents(), false, this
);
1538 /// Return true if we have some selection
1539 HTMLArea
.prototype
.hasSelectedText
= function() {
1540 // FIXME: come _on_ mishoo, you can do better than this ;-)
1541 return this
.getSelectedHTML() != '';
1544 HTMLArea
.prototype
._createLink
= function(link
) {
1546 var allinks
= editor
._doc
.getElementsByTagName('A');
1547 var anchors
= new Array();
1548 for(var i
= 0; i
< allinks
.length
; i++
) {
1549 var attrname
= allinks
[i
].getAttribute('name');
1550 if((HTMLArea
.is_ie ? attrname
.length
> 0 : attrname
!= null)) {
1551 anchors
[i
] = allinks
[i
].getAttribute('name');
1554 var outparam
= null;
1555 if (typeof link
== "undefined") {
1556 link
= this
.getParentElement();
1557 if (link
&& !/^a$
/i
.test(link
.tagName
)) {
1558 if(link
.tagName
.toLowerCase() != 'img') {
1560 var sel
= this
._getSelection();
1561 var rng
= this
._createRange(sel
);
1562 var len
= HTMLArea
.is_ie ? rng
.text
.toString().length
: sel
.toString().length
;
1564 alert("<?php print_string("alertnoselectedtext
","editor
");?>");
1573 f_href
: HTMLArea
.is_ie ? editor
.stripBaseURL(link
.href
) : link
.getAttribute("href"),
1574 f_title
: link
.title
,
1575 f_target
: link
.target
,
1580 f_anchors
:anchors
};
1582 this
._popupDialog("link_std.php?id=<?php echo $id; ?>", function(param
) {
1588 // Create a temporary unique link, insert it then find it and set the correct parameters
1589 var tmpLink
= 'http://www.moodle.org/'+Math
.random();
1590 var elm
= editor
._doc
.execCommand("createlink",false,tmpLink
);
1591 var links
=editor
._doc
.getElementsByTagName("a");
1592 for(var i
=0;i
<links
.length
;i++
){
1594 if(link
.href
==tmpLink
) {
1595 link
.href
=param
.f_href
.trim();
1597 link
.target
=param
.f_target
.trim();
1600 link
.title
=param
.f_title
.trim();
1606 var href
= param
.f_href
.trim();
1607 editor
.selectNodeContents(a
);
1609 editor
._doc
.execCommand("unlink", false, null);
1610 editor
.updateToolbar();
1616 if (!(a
&& /^a$
/i
.test(a
.tagName
))) {
1619 a
.target
= param
.f_target
.trim();
1620 a
.title
= param
.f_title
.trim();
1621 editor
.selectNodeContents(a
);
1622 editor
.updateToolbar();
1626 // Called when the user clicks on "InsertImage" button. If an image is already
1627 // there, it will just modify it's properties.
1628 HTMLArea
.prototype
._insertImage
= function(image
) {
1630 // Make sure that editor has focus
1632 var editor
= this
; // for nested functions
1633 var outparam
= null;
1634 if (typeof image
== "undefined") {
1635 image
= this
.getParentElement();
1636 if (image
&& !/^img$
/i
.test(image
.tagName
))
1639 if (image
) outparam
= {
1640 f_url
: HTMLArea
.is_ie ? editor
.stripBaseURL(image
.src
) : image
.getAttribute("src"),
1642 f_border
: image
.border
,
1643 f_align
: image
.align
,
1644 f_vert
: image
.vspace
,
1645 f_horiz
: image
.hspace
,
1646 f_width
: image
.width
,
1647 f_height
: image
.height
1649 this
._popupDialog("<?php
1650 if(!empty($id) and has_capability('moodle/course:managefiles', get_context_instance(CONTEXT_COURSE, $id))) {
1651 echo "insert_image
.php?id
=$id";
1653 echo "insert_image_std
.php?id
=$id";
1654 }?>", function(param
) {
1655 if (!param
) { // user must have pressed Cancel
1660 var sel
= editor
._getSelection();
1661 var range
= editor
._createRange(sel
);
1662 if (HTMLArea
.is_ie
) {
1663 editor
._doc
.execCommand("insertimage", false, param
.f_url
);
1665 if (HTMLArea
.is_ie
) {
1666 img
= range
.parentElement();
1667 // wonder if this works...
1668 if (img
.tagName
.toLowerCase() != "img") {
1669 img
= img
.previousSibling
;
1672 // MOODLE HACK: startContainer.perviousSibling
1673 // Doesn't work so we'll use createElement and
1674 // insertNodeAtSelection
1675 //img = range.startContainer.previousSibling;
1676 var img
= editor
._doc
.createElement("img");
1677 img
.setAttribute("src",""+ param
.f_url +
"");
1678 img
.setAttribute("alt",""+ param
.f_alt +
"");
1679 editor
.insertNodeAtSelection(img
);
1682 img
.src
= param
.f_url
;
1684 for (field in param
) {
1685 var value
= param
[field
];
1687 case "f_alt" : img
.alt
= value
; img
.title
= value
; break;
1688 case "f_border" : img
.border
= parseInt(value ||
"0"); break;
1689 case "f_align" : img
.align
= value
; break;
1690 case "f_vert" : img
.vspace
= parseInt(value ||
"0"); break;
1691 case "f_horiz" : img
.hspace
= parseInt(value ||
"0"); break;
1694 img
.width
= parseInt(value
);
1701 img
.height
= parseInt(value
);
1711 // Called when the user clicks the Insert Table button
1712 HTMLArea
.prototype
._insertTable
= function() {
1713 var sel
= this
._getSelection();
1714 var range
= this
._createRange(sel
);
1715 var editor
= this
; // for nested functions
1716 this
._popupDialog("insert_table.php?id=<?php echo $id; ?>", function(param
) {
1717 if (!param
) { // user must have pressed Cancel
1720 var doc
= editor
._doc
;
1721 // create the table element
1722 var table
= doc
.createElement("table");
1723 // assign the given arguments
1724 for (var field in param
) {
1725 var value
= param
[field
];
1730 case "f_width" : table
.width
= value + param
["f_unit"]; break;
1731 case "f_align" : table
.align
= value
; break;
1732 case "f_border" : table
.border
= parseInt(value
); break;
1733 case "f_spacing" : table
.cellspacing
= parseInt(value
); break;
1734 case "f_padding" : table
.cellpadding
= parseInt(value
); break;
1737 var tbody
= doc
.createElement("tbody");
1738 table
.appendChild(tbody
);
1739 for (var i
= 0; i
< param
["f_rows"]; ++i
) {
1740 var tr
= doc
.createElement("tr");
1741 tbody
.appendChild(tr
);
1742 for (var j
= 0; j
< param
["f_cols"]; ++j
) {
1743 var td
= doc
.createElement("td");
1745 if(param
["f_unit"] == "px") {
1746 tdwidth
= Math
.round(table
.width
/ param
["f_cols"]);
1748 tdwidth
= Math
.round(100 / param
["f_cols"]);
1750 td
.setAttribute("width",tdwidth + param
["f_unit"]);
1751 td
.setAttribute("valign","top");
1752 /// Moodle hack -ends
1754 // Mozilla likes to see something inside the cell.
1755 (HTMLArea
.is_gecko
) && td
.appendChild(doc
.createElement("br"));
1758 if (HTMLArea
.is_ie
) {
1759 range
.pasteHTML(table
.outerHTML
);
1762 editor
.insertNodeAtSelection(table
);
1768 /// Moodle hack - insertSmile
1769 HTMLArea
.prototype
._insertSmile
= function() {
1770 // Make sure that editor has focus
1772 var sel
= this
._getSelection();
1773 var range
= this
._createRange(sel
);
1774 var editor
= this
; // for nested functions
1775 this
._popupDialog("dlg_ins_smile.php?id=<?php echo $id; ?>", function(imgString
) {
1779 if (HTMLArea
.is_ie
) {
1780 range
.pasteHTML(imgString
);
1782 editor
.insertHTML(imgString
);
1788 HTMLArea
.prototype
._insertChar
= function() {
1789 var sel
= this
._getSelection();
1790 var range
= this
._createRange(sel
);
1791 var editor
= this
; // for nested functions
1792 this
._popupDialog("dlg_ins_char.php?id=<?php echo $id; ?>", function(sChar
) {
1796 if (HTMLArea
.is_ie
) {
1797 range
.pasteHTML(sChar
);
1800 editor
.insertHTML(sChar
);
1806 HTMLArea
.prototype
._removelink
= function() {
1808 link
= this
.getParentElement();
1809 editor
.selectNodeContents(link
);
1811 this
._doc
.execCommand("unlink", false, null);
1815 HTMLArea
.prototype
._createanchor
= function () {
1817 var sel
= this
._getSelection();
1818 var rng
= this
._createRange(sel
);
1819 var len
= HTMLArea
.is_ie ? rng
.text
.toString().length
: sel
.toString().length
;
1821 alert("<?php print_string("alertnoselectedtext
","editor
");?>");
1824 this
._popupDialog("createanchor.php?id=<?php echo $id; ?>", function(objAn
) {
1828 var str
= '<a name="'+ objAn
.anchor+
'">';
1829 str +
= HTMLArea
.is_ie ? rng
.text
: sel
;
1831 editor
.insertHTML(str
);
1835 HTMLArea
.prototype
._nolinktag
= function () {
1838 var sel
= this
._getSelection();
1839 var rng
= this
._createRange(sel
);
1840 var len
= HTMLArea
.is_ie ? rng
.text
.toString().length
: sel
.toString().length
;
1843 alert("<?php print_string("alertnoselectedtext
","editor
");?>");
1846 var str
= '<span class="nolink">';
1847 str +
= HTMLArea
.is_ie ? rng
.text
: sel
;
1849 editor
.insertHTML(str
);
1854 HTMLArea
.prototype
._searchReplace
= function() {
1857 var selectedtxt
= "";
1859 $strreplaced = addslashes(get_string('itemsreplaced','editor'));
1860 $strnotfound = addslashes(get_string('searchnotfound','editor'));
1862 var strReplaced
= '<?php echo $strreplaced ?>';
1863 var strNotfound
= '<?php echo $strnotfound ?>';
1866 //in source mode mozilla show errors, try diffrent method
1867 if (editor
._editMode
== "wysiwyg") {
1868 selectedtxt
= editor
.getSelectedHTML();
1870 if (HTMLArea
.is_ie
) {
1871 selectedtxt
= document
.selection
.createRange().text
;
1873 selectedtxt
= getMozSelection(editor
._textArea
);
1878 f_search
: selectedtxt
1881 //Call Search And Replace popup window
1882 editor
._popupDialog( "searchandreplace.php?id=<?php echo $id; ?>", function( entity
) {
1884 //user must have pressed Cancel
1887 var text
= editor
.getHTML();
1888 var search
= entity
[0];
1889 var replace
= entity
[1];
1890 var delim
= entity
[2];
1891 var regularx
= entity
[3];
1892 var closesar
= entity
[4];
1894 if (search
.length
< 1) {
1895 alert ("Enter a search word! \n search for: " + entity
[0]);
1898 var regX
= new RegExp (search
, delim
) ;
1899 var text
= text
.replace ( regX
,
1901 // Increment our counter variable.
1904 return str
.replace( regX
, replace
) ;
1909 while (text
.indexOf(search
)>-1) {
1910 pos
= text
.indexOf(search
);
1911 text
= "" +
(text
.substring(0, pos
) + replace + text
.substring((pos + search
.length
), text
.length
));
1916 editor
.setHTML(text
);
1917 editor
.forceRedraw();
1919 alert(ile +
' ' + strReplaced
);
1921 alert (strNotfound +
"\n");
1926 function getMozSelection(txtarea
) {
1927 var selLength
= txtarea
.textLength
;
1928 var selStart
= txtarea
.selectionStart
;
1929 var selEnd
= txtarea
.selectionEnd
;
1930 if (selEnd
==1 || selEnd
==2) selEnd
=selLength
;
1931 return (txtarea
.value
).substring(selStart
, selEnd
);
1935 /// Moodle hack's ends
1937 // Category: EVENT HANDLERS
1939 // el is reference to the SELECT object
1940 // txt is the name of the select field, as in config.toolbar
1941 HTMLArea
.prototype
._comboSelected
= function(el
, txt
) {
1943 var value
= el
.options
[el
.selectedIndex
].value
;
1946 case "fontsize": this
.execCommand(txt
, false, value
); break;
1948 this
.setLang(value
);
1951 (HTMLArea
.is_ie
) && (value
= "<" + value +
">");
1952 this
.execCommand(txt
, false, value
);
1955 // try to look it up in the registered dropdowns
1956 var dropdown
= this
.config
.customSelects
[txt
];
1957 if (typeof dropdown
!= "undefined") {
1958 dropdown
.action(this
);
1960 alert("FIXME: combo box " + txt +
" not implemented");
1967 * Used to set the language for the selected content.
1968 * We use the <span lang="en" class="multilang">content</span> format for
1969 * content that should be marked for multilang filter use, and
1970 * <span lang="en">content</span> for normal content for which we want to
1971 * set the language (for screen reader usage, for example).
1973 HTMLArea
.prototype
.setLang
= function(lang
) {
1975 if (lang
== 'multi') {
1976 // This is just the separator in the dropdown. Does nothing.
1981 var selectedHTML
= editor
.getSelectedHTML();
1982 var multiLang
= false;
1984 var re
= new RegExp('_ML', 'g');
1985 if (lang
.match(re
)) {
1987 lang
= lang
.replace(re
, '');
1990 // Remove all lang attributes from span tags in selected html.
1991 selectedHTML
= selectedHTML
.replace(/(<span
[^
>]*)lang
="[^"]*"([^>]*>)/, "$1$2");
1992 selectedHTML = selectedHTML.replace(/(<span[^>]*)class="multilang
"([^>]*>)/, "$1$2");
1994 // If a span tag is now empty, delete it.
1995 selectedHTML = selectedHTML.replace(/<span\s*>(.*?)<\/span>/, "$1");
1998 var parentEl = this.getParentElement();
1999 var insertNewSpan = false;
2001 if (parentEl.nodeName == 'SPAN' && parentEl.getAttribute('lang')) {
2002 // A language was previously defined for the current block.
2003 // Check whether the selected text makes up the whole of the block
2005 var re = new RegExp(parentEl.innerHTML);
2007 if (selectedHTML.match(re)) {
2008 // The selected text makes up the whole of the span block.
2010 parentEl.setAttribute('lang', lang);
2012 parentEl.setAttribute('class', 'multilang');
2015 parentEl.removeAttribute('lang');
2017 var classAttr = parentEl.getAttribute('class');
2019 classAttr = classAttr.replace(/multilang/, '').trim();
2021 if (classAttr == '') {
2022 parentEl.removeAttribute('class');
2024 if (parentEl.attributes.length == 0) {
2025 // The span is no longer needed.
2026 for (i=0; i<parentEl.childNodes.length; i++) {
2027 parentEl.parentNode.insertBefore(parentEl.childNodes[i], parentEl);
2029 parentEl.parentNode.removeChild(parentEl);
2033 insertNewSpan = true;
2036 insertNewSpan = true;
2039 if (insertNewSpan && lang != '') {
2040 var str = '<span lang="'+lang.trim()+'"';
2041 str += ' class="multilang
"';
2043 str += selectedHTML;
2045 editor.insertHTML(str);
2050 // the execCommand function (intercepts some commands and replaces them with
2051 // our own implementation)
2052 HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
2053 var editor = this; // for nested functions
2055 cmdID = cmdID.toLowerCase();
2057 case "htmlmode
" : this.setMode(); break;
2059 (HTMLArea.is_ie) && (cmdID = "backcolor
");
2061 this._popupDialog("select_color
.php?id
=<?php
echo $id; ?
>", function(color) {
2062 if (color) { // selection not canceled
2063 editor._doc.execCommand(cmdID, false, "#" + color);
2065 }, HTMLArea
._colorToRgb(this
._doc
.queryCommandValue(cmdID
)));
2067 case "createanchor": this
._createanchor(); break;
2071 case "unlink": this
._removelink(); break;
2072 case "nolink": this
._nolinktag(); break;
2074 // this object will be passed to the newly opened window
2075 HTMLArea
._object
= this
;
2076 if (HTMLArea
.is_ie
) {
2078 window
.open(this
.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen",
2079 "toolbar=no,location=no,directories=no,status=no,menubar=no," +
2080 "scrollbars=no,resizable=yes,width=800,height=600");
2083 window
.open(this
.popupURL("fullscreen.php?id=<?php echo $id;?>"), "ha_fullscreen",
2084 "toolbar=no,menubar=no,personalbar=no,width=800,height=600," +
2085 "scrollbars=no,resizable=yes");
2090 if (this
._customUndo
)
2093 this
._doc
.execCommand(cmdID
, UI
, param
);
2095 case "inserttable": this
._insertTable(); break;
2096 case "insertimage": this
._insertImage(); break;
2097 case "insertsmile": this
._insertSmile(); break;
2098 case "insertchar": this
._insertChar(); break;
2099 case "searchandreplace": this
._searchReplace(); break;
2100 case "about" : this
._popupDialog("about.html", null, this
); break;
2101 case "showhelp" : window
.open(_editor_url +
"reference.html", "ha_help"); break;
2103 case "killword": this
._wordClean(); break;
2109 // Paste first then clean
2110 this
._doc
.execCommand(cmdID
, UI
, param
);
2111 if (this
.config
.killWordOnPaste
) {
2115 if (HTMLArea
.is_gecko
) {
2117 $strmoz = get_string('cutpastemozilla','editor');
2118 $strmoz = preg_replace("/[\n|\r
]+
/", "", $strmoz);
2119 $strmoz = str_replace('<br />', '\\n', $strmoz);
2121 echo addslashes($strmoz);
2124 window
.open("http://moodle.org/mozillahelp");
2130 var dir
= (cmdID
== "righttoleft") ?
"rtl" : "ltr";
2131 var el
= this
.getParentElement();
2132 while (el
&& !HTMLArea
.isBlockElement(el
))
2135 if (el
.style
.direction
== dir
)
2136 el
.style
.direction
= "";
2138 el
.style
.direction
= dir
;
2141 default: this
._doc
.execCommand(cmdID
, UI
, param
);
2143 this
.updateToolbar();
2149 * A generic event handler for things that happen in the IFRAME's document.
2150 * This function also handles key bindings.
2152 HTMLArea
.prototype
._editorEvent
= function(ev
) {
2155 var keyEvent
= (HTMLArea
.is_ie
&& ev
.type
== "keydown") ||
(ev
.type
== "keypress");
2159 for (var i in editor
.plugins
) {
2160 var plugin
= editor
.plugins
[i
].instance
;
2161 if (typeof plugin
.onKeyPress
== "function") plugin
.onKeyPress(ev
);
2166 var key
= String.fromCharCode(HTMLArea
.is_ie ? ev
.keyCode
: ev
.charCode
).toLowerCase();
2170 if (ev
.ctrlKey
&& !ev
.altKey
) {
2172 * Ctrl modifier only.
2173 * We use these for shortcuts that change existing content,
2174 * e.g. make text bold.
2180 if (!HTMLArea
.is_ie
) {
2182 sel
= this
._getSelection();
2183 sel
.removeAllRanges();
2184 range
= this
._createRange();
2185 range
.selectNodeContents(this
._doc
.body
);
2186 sel
.addRange(range
);
2187 HTMLArea
._stopEvent(ev
);
2191 // For the dropdowns, we assign focus to them so that they are
2192 // keyboard accessible.
2194 editor
.dropdowns
['fontname'].focus();
2197 editor
.dropdowns
['fontsize'].focus();
2200 editor
.dropdowns
['formatblock'].focus();
2203 editor
.dropdowns
['language'].focus();
2206 case 'b': cmd
= "bold"; break;
2207 case 'i': cmd
= "italic"; break;
2208 case 'u': cmd
= "underline"; break;
2209 case 's': cmd
= "strikethrough"; break;
2210 case ',': cmd
= "subscript"; break;
2211 case '.': cmd
= "superscript"; break;
2214 if (! HTMLArea
.is_gecko
) {
2219 case '0': cmd
= "killword"; break;
2220 case 'z': cmd
= "undo"; break;
2221 case 'y': cmd
= "redo"; break;
2222 case 'l': cmd
= "justifyleft"; break;
2223 case 'e': cmd
= "justifycenter"; break;
2224 case 'r': cmd
= "justifyright"; break;
2225 case 'j': cmd
= "justifyfull"; break;
2226 case '/': cmd
= "lefttoright"; break;
2227 case '|': cmd
= "righttoleft"; break;
2228 case ';': cmd
= "outdent"; break;
2229 case "'": cmd
= "indent"; break;
2230 case 'g': cmd
= "forecolor"; break;
2231 case 'k': cmd
= "hilitecolor"; break;
2232 case 'f': cmd
= "searchandreplace"; break;
2233 case '`': cmd
= "htmlmode"; break; // FIXME: can't toggle from source code to wysiwyg
2236 // Toggle fullscreen on or off.
2237 if (this
.config
.btnList
['popupeditor'][0] == 'Enlarge Editor') {
2238 cmd
= 'popupeditor';
2251 cmd
= "formatblock";
2253 if (HTMLArea
.is_ie
) {
2254 value
= "<" + value +
">";
2258 } // End switch (key)
2261 } else if (ev
.ctrlKey
&& ev
.altKey
) {
2263 * Ctrl + Alt modifiers.
2264 * We use these for shortcuts that insert stuff, e.g. images.
2267 case 'o': cmd
= "insertorderedlist"; break;
2268 case 'u': cmd
= "insertunorderedlist"; break;
2269 case 'r': cmd
= "inserthorizontalrule"; break;
2270 case 'a': cmd
= "createanchor"; break;
2271 case 'l': cmd
= "createlink"; break;
2272 case 'd': cmd
= "unlink"; break;
2273 case 'n': cmd
= "nolink"; break;
2274 case 'i': cmd
= 'insertimage'; break;
2275 case 't': cmd
= 'inserttable'; break;
2276 case 's': cmd
= 'insertsmile'; break;
2277 case 'c': cmd
= 'insertchar'; break;
2282 // execute simple command
2283 this
.execCommand(cmd
, false, value
);
2284 HTMLArea
._stopEvent(ev
);
2286 } // End if (keyEvent)
2289 else if (keyEvent) {
2291 switch (ev.keyCode) {
2292 case 13: // KEY enter
2293 // if (HTMLArea.is_ie) {
2294 this.insertHTML("<br />");
2295 HTMLArea._stopEvent(ev);
2302 // Update the toolbar state after some time.
2303 if (editor
._timerToolbar
) {
2304 clearTimeout(editor
._timerToolbar
);
2306 editor
._timerToolbar
= setTimeout(function() {
2307 editor
.updateToolbar();
2308 editor
._timerToolbar
= null;
2313 // retrieve the HTML
2314 HTMLArea
.prototype
.getHTML
= function() {
2315 switch (this
._editMode
) {
2317 if (!this
.config
.fullPage
) {
2318 return HTMLArea
.getHTML(this
._doc
.body
, false, this
);
2320 return this
.doctype +
"\n" + HTMLArea
.getHTML(this
._doc
.documentElement
, true, this
);
2321 case "textmode" : return this
._textArea
.value
;
2322 default : alert("Mode <" + mode +
"> not defined!");
2327 // retrieve the HTML (fastest version, but uses innerHTML)
2328 HTMLArea
.prototype
.getInnerHTML
= function() {
2329 switch (this
._editMode
) {
2331 if (!this
.config
.fullPage
)
2332 return this
._doc
.body
.innerHTML
;
2334 return this
.doctype +
"\n" + this
._doc
.documentElement
.innerHTML
;
2335 case "textmode" : return this
._textArea
.value
;
2336 default : alert("Mode <" + mode +
"> not defined!");
2341 // completely change the HTML inside
2342 HTMLArea
.prototype
.setHTML
= function(html
) {
2343 switch (this
._editMode
) {
2345 if (!this
.config
.fullPage
)
2346 this
._doc
.body
.innerHTML
= html
;
2348 // this._doc.documentElement.innerHTML = html;
2349 this
._doc
.body
.innerHTML
= html
;
2351 case "textmode" : this
._textArea
.value
= html
; break;
2352 default : alert("Mode <" + mode +
"> not defined!");
2357 // sets the given doctype (useful when config.fullPage is true)
2358 HTMLArea
.prototype
.setDoctype
= function(doctype
) {
2359 this
.doctype
= doctype
;
2362 /***************************************************
2363 * Category: UTILITY FUNCTIONS
2364 ***************************************************/
2366 // browser identification
2368 HTMLArea
.agt
= navigator
.userAgent
.toLowerCase();
2369 HTMLArea
.is_ie
= ((HTMLArea
.agt
.indexOf("msie") != -1) && (HTMLArea
.agt
.indexOf("opera") == -1));
2370 HTMLArea
.is_opera
= (HTMLArea
.agt
.indexOf("opera") != -1);
2371 HTMLArea
.is_mac
= (HTMLArea
.agt
.indexOf("mac") != -1);
2372 HTMLArea
.is_mac_ie
= (HTMLArea
.is_ie
&& HTMLArea
.is_mac
);
2373 HTMLArea
.is_win_ie
= (HTMLArea
.is_ie
&& !HTMLArea
.is_mac
);
2374 HTMLArea
.is_gecko
= (navigator
.product
== "Gecko");
2375 HTMLArea
.is_safari
= (HTMLArea
.agt
.indexOf("safari") != -1);
2377 // variable used to pass the object to the popup editor window.
2378 HTMLArea
._object
= null;
2380 // function that returns a clone of the given object
2381 HTMLArea
.cloneObject
= function(obj
) {
2382 var newObj
= new Object;
2384 // check for array objects
2385 if (obj
.constructor
.toString().indexOf("function Array(") >= 0) {
2386 newObj
= obj
.constructor();
2389 // check for function objects (as usual, IE is phucked up)
2390 if (obj
.constructor
.toString().indexOf("function Function(") >= 0) {
2391 newObj
= obj
; // just copy reference to it
2392 } else for (var n in obj
) {
2394 if (typeof node
== 'object') { newObj
[n
] = HTMLArea
.cloneObject(node
); }
2395 else { newObj
[n
] = node
; }
2401 // FIXME!!! this should return false for IE < 5.5
2402 HTMLArea
.checkSupportedBrowser
= function() {
2403 if (HTMLArea
.is_gecko
) {
2404 if (navigator
.productSub
< 20021201) {
2405 alert("You need at least Mozilla-1.3 Alpha.\n" +
2406 "Sorry, your Gecko is not supported.");
2409 if (navigator
.productSub
< 20030210) {
2410 alert("Mozilla < 1.3 Beta is not supported!\n" +
2411 "I'll try, though, but it might not work.");
2414 if(HTMLArea
.is_safari
) {
2417 return HTMLArea
.is_gecko || HTMLArea
.is_ie
;
2420 // selection & ranges
2422 // returns the current selection object
2423 HTMLArea
.prototype
._getSelection
= function() {
2424 if (HTMLArea
.is_ie
) {
2425 return this
._doc
.selection
;
2427 return this
._iframe
.contentWindow
.getSelection();
2431 // returns a range for the current selection
2432 HTMLArea
.prototype
._createRange
= function(sel
) {
2433 if (HTMLArea
.is_ie
) {
2434 return sel
.createRange();
2436 // Commented out because we need the dropdowns to be able to keep
2437 // focus for keyboard accessibility. Comment by Vy-Shane Sin Fat.
2438 //this.focusEditor();
2439 if (typeof sel
!= "undefined") {
2441 return sel
.getRangeAt(0);
2443 return this
._doc
.createRange();
2446 return this
._doc
.createRange();
2453 HTMLArea
._addEvent
= function(el
, evname
, func
) {
2454 if (HTMLArea
.is_ie
) {
2455 el
.attachEvent("on" + evname
, func
);
2457 el
.addEventListener(evname
, func
, true);
2461 HTMLArea
._addEvents
= function(el
, evs
, func
) {
2462 for (var i in evs
) {
2463 HTMLArea
._addEvent(el
, evs
[i
], func
);
2467 HTMLArea
._removeEvent
= function(el
, evname
, func
) {
2468 if (HTMLArea
.is_ie
) {
2469 el
.detachEvent("on" + evname
, func
);
2471 el
.removeEventListener(evname
, func
, true);
2475 HTMLArea
._removeEvents
= function(el
, evs
, func
) {
2476 for (var i in evs
) {
2477 HTMLArea
._removeEvent(el
, evs
[i
], func
);
2481 HTMLArea
._stopEvent
= function(ev
) {
2482 if (HTMLArea
.is_ie
) {
2483 ev
.cancelBubble
= true;
2484 ev
.returnValue
= false;
2486 ev
.preventDefault();
2487 ev
.stopPropagation();
2491 HTMLArea
._removeClass
= function(el
, className
) {
2492 if (!(el
&& el
.className
)) {
2495 var cls
= el
.className
.split(" ");
2496 var ar
= new Array();
2497 for (var i
= cls
.length
; i
> 0;) {
2498 if (cls
[--i
] != className
) {
2499 ar
[ar
.length
] = cls
[i
];
2502 el
.className
= ar
.join(" ");
2505 HTMLArea
._addClass
= function(el
, className
) {
2506 // remove the class first, if already there
2507 HTMLArea
._removeClass(el
, className
);
2508 el
.className +
= " " + className
;
2511 HTMLArea
._hasClass
= function(el
, className
) {
2512 if (!(el
&& el
.className
)) {
2515 var cls
= el
.className
.split(" ");
2516 for (var i
= cls
.length
; i
> 0;) {
2517 if (cls
[--i
] == className
) {
2524 HTMLArea
.isBlockElement
= function(el
) {
2526 var blockTags
= " body form textarea fieldset ul ol dl li div " +
2527 "p h1 h2 h3 h4 h5 h6 quote pre table thead " +
2528 "tbody tfoot tr td iframe address ";
2530 return (blockTags
.indexOf(" " + el
.tagName
.toLowerCase() +
" ") != -1);
2535 HTMLArea
.needsClosingTag
= function(el
) {
2536 var closingTags
= " head script style div span tr td tbody table em strong font a title iframe object applet ";
2537 return (closingTags
.indexOf(" " + el
.tagName
.toLowerCase() +
" ") != -1);
2540 // performs HTML encoding of some given string
2541 HTMLArea
.htmlEncode
= function(str
) {
2542 // we don't need regexp for that, but.. so be it for now.
2543 str
= str
.replace(/&/ig
, "&");
2544 str
= str
.replace(/</ig
, "<");
2545 str
= str
.replace(/>/ig
, ">");
2546 str
= str
.replace(/\x22/ig
, """);
2547 // \x22 means '"' -- we use hex reprezentation so that we don't disturb
2548 // JS compressors (well, at least mine fails.. ;)
2552 HTMLArea
.isStandardTag
= function (el
) {
2553 return HTMLArea
.RE_msietag
.test(el
.tagName
);
2555 HTMLArea
.isSingleTag
= function (el
) {
2556 var re
= /^
(br|hr|img|input|link|meta|param|embed|area
)$
/i
;
2557 return re
.test(el
.tagName
.toLowerCase());
2559 // Retrieves the HTML code from the given node. This is a replacement for
2560 // getting innerHTML, using standard DOM calls.
2561 HTMLArea
.getHTML
= function(root
, outputRoot
, editor
) {
2563 switch (root
.nodeType
) {
2564 case 1: // Node.ELEMENT_NODE
2565 case 11: // Node.DOCUMENT_FRAGMENT_NODE
2568 var root_tag
= (root
.nodeType
== 1) ? root
.tagName
.toLowerCase() : '';
2569 if (HTMLArea
.RE_junktag
.test(root_tag
)) {
2572 if (HTMLArea
.is_ie
&& root_tag
== "head") {
2576 var save_multiline
= RegExp
.multiline
;
2577 RegExp
.multiline
= true;
2578 var txt
= root
.innerHTML
.replace(HTMLArea
.RE_tagName
, function(str
, p1
, p2
) {
2579 return p1 + p2
.toLowerCase();
2581 RegExp
.multiline
= save_multiline
;
2586 } else if (outputRoot
) {
2587 closed
= (!(root
.hasChildNodes() ||
!HTMLArea
.isSingleTag(root
)));
2588 html
= "<" + root
.tagName
.toLowerCase();
2589 var attrs
= root
.attributes
;
2590 for (i
= 0; i
< attrs
.length
; ++i
) {
2591 var a
= attrs
.item(i
);
2595 var name
= a
.nodeName
.toLowerCase();
2596 if (/_moz|contenteditable|_msh
/.test(name
)) {
2597 // avoid certain attributes
2601 if (name
!= "style") {
2603 // Using Gecko the values of href and src are converted to absolute links
2604 // unless we get them using nodeValue()
2605 if (typeof root
[a
.nodeName
] != "undefined" && name
!= "href" && name
!= "src") {
2606 value
= root
[a
.nodeName
];
2608 // This seems to be working, but if it does cause
2609 // problems later on return the old value...
2610 if (name
.toLowerCase() == "href" && name
.toLowerCase() == "src") {
2611 value
= root
[a
.nodeName
];
2613 value
= a
.nodeValue
;
2615 if (HTMLArea
.is_ie
&& (name
== "href" || name
== "src")) {
2616 value
= editor
.stripBaseURL(value
);
2619 } else { // IE fails to put style in attributes list
2620 // FIXME: cssText reported by IE is UPPERCASE
2621 value
= root
.style
.cssText
.toLowerCase();
2623 if (/(_moz|^$
)/.test(value
)) {
2624 // Mozilla reports some special tags
2625 // here; we don't need them.
2628 html +
= " " + name +
'="' + value +
'"';
2630 html +
= closed ?
" />" : ">";
2632 for (i
= root
.firstChild
; i
; i
= i
.nextSibling
) {
2633 html +
= HTMLArea
.getHTML(i
, true, editor
);
2635 if (outputRoot
&& !closed
) {
2636 if ( HTMLArea
.is_ie
&& !HTMLArea
.isStandardTag(root
) ) {
2639 html +
= "</" + root
.tagName
.toLowerCase() +
">";
2643 case 3: // Node.TEXT_NODE
2644 // If a text node is alone in an element and all spaces, replace it with an non breaking one
2645 // This partially undoes the damage done by moz, which translates ' 's into spaces in the data element
2646 if ( !root
.previousSibling
&& !root
.nextSibling
&& root
.data
.match(/^\s
*$
/i
) && root
.data
.length
> 1 ) html
= ' ';
2647 else html
= HTMLArea
.htmlEncode(root
.data
);
2649 case 8: // Node.COMMENT_NODE
2650 html
= "<!--" + root
.data +
"-->";
2651 break; // skip comments, for now.
2654 return HTMLArea
.indent(html
);
2657 HTMLArea
.prototype
.stripBaseURL
= function(string) {
2658 var baseurl
= this
.config
.baseURL
;
2660 // IE adds the path to an anchor, converting #anchor
2661 // to path/#anchor which of course needs to be fixed
2662 var index
= string.indexOf("/#")+
1;
2663 if ((index
> 0) && (string.indexOf(baseurl
) > -1)) {
2664 return string.substr(index
);
2666 return string; // Moodle doesn't use the code below because
2667 // Moodle likes to keep absolute links
2669 // strip to last directory in case baseurl points to a file
2670 baseurl
= baseurl
.replace(/[^\
/]+$
/, '');
2671 var basere
= new RegExp(baseurl
);
2672 string = string.replace(basere
, "");
2674 // strip host-part of URL which is added by MSIE to links relative to server root
2675 baseurl
= baseurl
.replace(/^
(https?
:\
/\
/[^\
/]+
)(.*)$
/, '$1');
2676 basere
= new RegExp(baseurl
);
2677 return string.replace(basere
, "");
2680 String.prototype
.trim
= function() {
2681 a
= this
.replace(/^\s+
/, '');
2682 return a
.replace(/\s+$
/, '');
2685 // creates a rgb-style color from a number
2686 HTMLArea
._makeColor
= function(v
) {
2687 if (typeof v
!= "number") {
2688 // already in rgb (hopefully); IE doesn't get here.
2691 // IE sends number; convert to rgb.
2693 var g
= (v
>> 8) & 0xFF;
2694 var b
= (v
>> 16) & 0xFF;
2695 return "rgb(" + r +
"," + g +
"," + b +
")";
2698 // returns hexadecimal color representation from a number or a rgb-style color.
2699 HTMLArea
._colorToRgb
= function(v
) {
2703 // returns the hex representation of one byte (2 digits)
2705 return (d
< 16) ?
("0" + d
.toString(16)) : d
.toString(16);
2708 if (typeof v
== "number") {
2709 // we're talking to IE here
2711 var g
= (v
>> 8) & 0xFF;
2712 var b
= (v
>> 16) & 0xFF;
2713 return "#" +
hex(r
) +
hex(g
) +
hex(b
);
2716 if (v
.substr(0, 3) == "rgb") {
2717 // in rgb(...) form -- Mozilla
2718 var re
= /rgb\s
*\
(\s
*([0-9]+
)\s
*,\s
*([0-9]+
)\s
*,\s
*([0-9]+
)\s
*\
)/;
2720 var r
= parseInt(RegExp
.$1);
2721 var g
= parseInt(RegExp
.$2);
2722 var b
= parseInt(RegExp
.$3);
2723 return "#" +
hex(r
) +
hex(g
) +
hex(b
);
2725 // doesn't match RE?! maybe uses percentages or float numbers
2726 // -- FIXME: not yet implemented.
2730 if (v
.substr(0, 1) == "#") {
2731 // already hex rgb (hopefully :D )
2735 // if everything else fails ;)
2739 HTMLArea
.prototype
._popupDialog
= function(url
, action
, init
) {
2740 Dialog(this
.popupURL(url
), action
, init
);
2745 HTMLArea
.prototype
.imgURL
= function(file
, plugin
) {
2746 if (typeof plugin
== "undefined")
2747 return _editor_url + file
;
2749 return _editor_url +
"plugins/" + plugin +
"/img/" + file
;
2752 HTMLArea
.prototype
.popupURL
= function(file
) {
2754 if (file
.match(/^plugin
:\
/\
/(.*?
)\
/(.*)/)) {
2755 var plugin
= RegExp
.$1;
2756 var popup
= RegExp
.$2;
2757 if (!/\
.html$
/.test(popup
))
2759 url
= _editor_url +
"plugins/" + plugin +
"/popups/" + popup
;
2761 url
= _editor_url + this
.config
.popupURL + file
;
2766 * FIX: Internet Explorer returns an item having the _name_ equal to the given
2767 * id, even if it's not having any id. This way it can return a different form
2768 * field even if it's not a textarea. This workarounds the problem by
2769 * specifically looking to search only elements having a certain tag name.
2771 HTMLArea
.getElementById
= function(tag
, id
) {
2772 var el
, i
, objs
= document
.getElementsByTagName(tag
);
2773 for (i
= objs
.length
; --i
>= 0 && (el
= objs
[i
]);)
2778 // Modified version of GetHtml plugin's indent.
2779 HTMLArea
.indent
= function(s
, sindentChar
) {
2781 /*0*/ new RegExp().compile(/<\
/?
(div|p|h
[1-6]|table|tr|td|th|ul|ol|li|blockquote|
object|br|hr|img|embed|param|pre|script|html|head|body|meta|link|title|area
)[^
>]*>/g
),
2782 /*1*/ new RegExp().compile(/<\
/(div|p|h
[1-6]|table|tr|td|th|ul|ol|li|blockquote|
object|html|head|body|script
)( [^
>]*)?
>/g
),//blocklevel closing tag
2783 /*2*/ new RegExp().compile(/<(div|p|h
[1-6]|table|tr|td|th|ul|ol|li|blockquote|
object|html|head|body|script
)( [^
>]*)?
>/g
),//blocklevel opening tag
2784 /*3*/ new RegExp().compile(/<(br|hr|img|embed|param|pre|meta|link|title|area
)[^
>]*>/g
),//singlet tag
2785 /*4*/ new RegExp().compile(/(^|
<\
/(pre|script
)>)(\s|
[^\s
])*?
(<(pre|script
)[^
>]*>|$
)/g
),//find content NOT inside pre and script tags
2786 /*5*/ new RegExp().compile(/(<pre
[^
>]*>)(\s|
[^\s
])*?
(<\
/pre
>)/g
),//find content inside pre tags
2787 /*6*/ new RegExp().compile(/(^|
<!--(\s|\S
)*?
-->)((\s|\S
)*?
)(?
=<!--(\s|\S
)*?
-->|$
)/g
),//find content NOT inside comments
2788 /*7*/ new RegExp().compile(/<\
/(table|tbody|tr|td|th|ul|ol|
object|html|head|body
)( [^
>]*)?
>/g
),//blocklevel closing tag
2790 HTMLArea
.__nindent
= 0;
2791 HTMLArea
.__sindent
= "";
2792 HTMLArea
.__sindentChar
= (typeof sindentChar
== "undefined") ?
" " : sindentChar
;
2794 if(HTMLArea
.is_gecko
) { //moz changes returns into <br> inside <pre> tags
2795 s
= s
.replace(c
[5], function(str
){return str
.replace(/<br \
/>/g
,"\n")});
2797 s
= s
.replace(c
[4], function(strn
) { //skip pre and script tags
2798 strn
= strn
.replace(c
[6], function(st
,$1,$2,$3) { //exclude comments
2799 string = $3.replace(/[\n\r]/gi
, " ").replace(/\s+
/gi
," ").replace(c
[0], function(str
) {
2800 if (str
.match(c
[2])) {
2801 var s
= "\n" + HTMLArea
.__sindent + str
;
2802 // blocklevel openingtag - increase indent
2803 HTMLArea
.__sindent +
= HTMLArea
.__sindentChar
;
2804 ++HTMLArea
.__nindent
;
2806 } else if (str
.match(c
[1])) {
2807 // blocklevel closingtag - decrease indent
2808 --HTMLArea
.__nindent
;
2809 HTMLArea
.__sindent
= "";
2810 for (var i
=HTMLArea
.__nindent
;i
>0;--i
) {
2811 HTMLArea
.__sindent +
= HTMLArea
.__sindentChar
;
2813 return (str
.match(c
[7]) ?
"\n" + HTMLArea
.__sindent
: "") + str
;
2815 return str
; // this won't actually happen
2820 if (s
.charAt(0) == "\n") {
2821 return s
.substring(1, s
.length
);
2823 s
= s
.replace(/ *\n/g
,'\n');//strip spaces at end of lines