2 include("../../config.php");
4 $lastmodified = filemtime("htmlarea.php");
7 header("Content-type: application/x-javascript"); // Correct MIME type
8 header("Last-Modified: " . gmdate("D, d M Y H:i:s", $lastmodified) . " GMT");
9 header("Expires: " . gmdate("D, d M Y H:i:s", time() +
$lifetime) . " GMT");
10 header("Cache-control: max_age = $lifetime");
13 $lang = current_language();
19 $strheading = get_string("heading", "editor");
20 $strnormal = get_string("normal", "editor");
21 $straddress = get_string("address", "editor");
22 $strpreformatted = get_string("preformatted", "editor");
25 // htmlArea v3.0 - Copyright (c) 2002, 2003 interactivetools.com, inc.
26 // This copyright notice MUST stay intact for use (see license.txt).
28 // Portions (c) dynarch.com, 2003-2004
30 // A free WYSIWYG editor replacement for <textarea> fields.
31 // For full source code and docs, visit http://www.interactivetools.com/
33 // Version 3.0 developed by Mihai Bazon.
34 // http://dynarch.com/mishoo
38 if (typeof _editor_url
== "string") {
39 // Leave exactly one backslash at the end of _editor_url
40 _editor_url
= _editor_url
.replace(/\x2f*$
/, '/');
42 //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.");
43 _editor_url
= '<?php print ($CFG->wwwroot); ?>/lib/editor/';
46 // make sure we have a language
47 if (typeof _editor_lang
== "string") {
48 _editor_lang
= "en"; // should always be english in moodle.
53 // Creates a new HTMLArea object. Tries to replace the textarea with the given
55 function HTMLArea(textarea
, config
) {
56 if (HTMLArea
.checkSupportedBrowser()) {
57 if (typeof config
== "undefined") {
58 this
.config
= new HTMLArea
.Config();
62 this
._htmlArea
= null;
63 this
._textArea
= textarea
;
64 this
._editMode
= "wysiwyg";
66 this
._timerToolbar
= null;
67 this
._timerUndo
= null;
68 this
._undoQueue
= new Array(this
.config
.undoSteps
);
70 this
._customUndo
= true;
71 this
._mdoc
= document
; // cache the document, we need it in plugins
78 var scripts
= HTMLArea
._scripts
= [ _editor_url +
"htmlarea.js",
79 _editor_url +
"dialog.js",
80 _editor_url +
"popupwin.js",
81 _editor_url +
"lang/" + _editor_lang +
".js" ];
82 var head
= document
.getElementsByTagName("head")[0];
83 // start from 1, htmlarea.js is already loaded
84 for (var i
= 1; i
< scripts
.length
; ++i
) {
85 var script
= document
.createElement("script");
86 script
.src
= scripts
[i
];
87 head
.appendChild(script
);
92 HTMLArea
.RE_tagName
= /(<\
/|
<)\s
*([^ \t\n
>]+
)/ig
;
93 HTMLArea
.RE_doctype
= /(<!doctype((.|\n
)*?
)>)\n?
/i
;
94 HTMLArea
.RE_head
= /<head
>((.|\n
)*?
)<\
/head
>/i
;
95 HTMLArea
.RE_body
= /<body
>((.|\n
)*?
)<\
/body
>/i
;
97 HTMLArea
.Config
= function () {
101 this
.height
= "auto";
103 // enable creation of a status bar?
104 this
.statusBar
= true;
106 // maximum size of the undo queue
109 // the time interval at which undo samples are taken
110 this
.undoTimeout
= 500; // 1/2 sec.
112 // the next parameter specifies whether the toolbar should be included
113 // in the size or not.
114 this
.sizeIncludesToolbar
= true;
116 // if true then HTMLArea will retrieve the full HTML, starting with the
118 this
.fullPage
= false;
120 // style included in the iframe document
121 this
.pageStyle
= "body { background-color: #fff; font-family: 'Times New Roman', Times; }";
123 // set to true if you want Word code to be cleaned upon Paste
124 this
.killWordOnPaste
= true;
126 // BaseURL included in the iframe document
127 this
.baseURL
= document
.baseURI || document
.URL
;
128 if (this
.baseURL
&& this
.baseURL
.match(/(.*)\
/([^\
/]+
)/))
129 this
.baseURL
= RegExp
.$1 +
"/";
132 this
.imgURL
= "images/";
133 this
.popupURL
= "popups/";
135 /** CUSTOMIZING THE TOOLBAR
136 * -------------------------
138 * It is recommended that you customize the toolbar contents in an
139 * external file (i.e. the one calling HTMLArea) and leave this one
140 * unchanged. That's because when we (InteractiveTools.com) release a
141 * new official version, it's less likely that you will have problems
142 * upgrading HTMLArea.
145 [ "fontname", "space",
147 "formatblock", "space",
148 "bold", "italic", "underline", "strikethrough", "separator",
149 "subscript", "superscript", "separator",
150 "copy", "cut", "paste","clean", "separator", "undo", "redo" ],
152 [ "justifyleft", "justifycenter", "justifyright", "justifyfull", "separator",
153 "lefttoright", "righttoleft", "separator",
154 "insertorderedlist", "insertunorderedlist", "outdent", "indent", "separator",
155 "forecolor", "hilitecolor", "separator",
156 "inserthorizontalrule", "createlink", "unlink", "insertimage", "inserttable",
157 "insertsmile", "insertchar", "separator", "htmlmode", "separator", "popupeditor" ]
161 "Arial": 'arial,helvetica,sans-serif',
162 "Courier New": 'courier new,courier,monospace',
163 "Georgia": 'georgia,times new roman,times,serif',
164 "Tahoma": 'tahoma,arial,helvetica,sans-serif',
165 "Times New Roman": 'times new roman,times,serif',
166 "Verdana": 'verdana,arial,helvetica,sans-serif',
168 "WingDings": 'wingdings'
182 "<?php echo $strheading ?> 1": "h1",
183 "<?php echo $strheading ?> 2": "h2",
184 "<?php echo $strheading ?> 3": "h3",
185 "<?php echo $strheading ?> 4": "h4",
186 "<?php echo $strheading ?> 5": "h5",
187 "<?php echo $strheading ?> 6": "h6",
188 "<?php echo $strnormal ?>": "p",
189 "<?php echo $straddress ?>": "address",
190 "<?php echo $strpreformatted ?>": "pre"
193 this
.customSelects
= {};
195 function cut_copy_paste(e
, cmd
, obj
) {
199 // ADDING CUSTOM BUTTONS: please read below!
200 // format of the btnList elements is "ID: [ ToolTip, Icon, Enabled in text mode?, ACTION ]"
201 // - ID: unique ID for the button. If the button calls document.execCommand
202 // it's wise to give it the same name as the called command.
203 // - ACTION: function that gets called when the button is clicked.
204 // it has the following prototype:
205 // function(editor, buttonName)
206 // - editor is the HTMLArea object that triggered the call
207 // - buttonName is the ID of the clicked button
208 // These 2 parameters makes it possible for you to use the same
209 // handler for more HTMLArea objects or for more different buttons.
210 // - ToolTip: default tooltip, for cases when it is not defined in the -lang- file (HTMLArea.I18N)
211 // - Icon: path to an icon image file for the button (TODO: use one image for all buttons!)
212 // - Enabled in text mode: if false the button gets disabled for text-only mode; otherwise enabled all the time.
214 bold
: [ "Bold", "ed_format_bold.gif", false, function(e
) {e
.execCommand("bold");} ],
215 italic
: [ "Italic", "ed_format_italic.gif", false, function(e
) {e
.execCommand("italic");} ],
216 underline
: [ "Underline", "ed_format_underline.gif", false, function(e
) {e
.execCommand("underline");} ],
217 strikethrough
: [ "Strikethrough", "ed_format_strike.gif", false, function(e
) {e
.execCommand("strikethrough");} ],
218 subscript
: [ "Subscript", "ed_format_sub.gif", false, function(e
) {e
.execCommand("subscript");} ],
219 superscript
: [ "Superscript", "ed_format_sup.gif", false, function(e
) {e
.execCommand("superscript");} ],
220 justifyleft
: [ "Justify Left", "ed_align_left.gif", false, function(e
) {e
.execCommand("justifyleft");} ],
221 justifycenter
: [ "Justify Center", "ed_align_center.gif", false, function(e
) {e
.execCommand("justifycenter");} ],
222 justifyright
: [ "Justify Right", "ed_align_right.gif", false, function(e
) {e
.execCommand("justifyright");} ],
223 justifyfull
: [ "Justify Full", "ed_align_justify.gif", false, function(e
) {e
.execCommand("justifyfull");} ],
224 insertorderedlist
: [ "Ordered List", "ed_list_num.gif", false, function(e
) {e
.execCommand("insertorderedlist");} ],
225 insertunorderedlist
: [ "Bulleted List", "ed_list_bullet.gif", false, function(e
) {e
.execCommand("insertunorderedlist");} ],
226 outdent
: [ "Decrease Indent", "ed_indent_less.gif", false, function(e
) {e
.execCommand("outdent");} ],
227 indent
: [ "Increase Indent", "ed_indent_more.gif", false, function(e
) {e
.execCommand("indent");} ],
228 forecolor
: [ "Font Color", "ed_color_fg.gif", false, function(e
) {e
.execCommand("forecolor");} ],
229 hilitecolor
: [ "Background Color", "ed_color_bg.gif", false, function(e
) {e
.execCommand("hilitecolor");} ],
230 inserthorizontalrule
: [ "Horizontal Rule", "ed_hr.gif", false, function(e
) {e
.execCommand("inserthorizontalrule");} ],
231 createlink
: [ "Insert Web Link", "ed_link.gif", false, function(e
) {e
.execCommand("createlink", true);} ],
232 unlink
: [ "Remove Link", "ed_unlink.gif", false, function(e
) {e
.execCommand("unlink");} ],
233 insertimage
: [ "Insert/Modify Image", "ed_image.gif", false, function(e
) {e
.execCommand("insertimage");} ],
234 inserttable
: [ "Insert Table", "insert_table.gif", false, function(e
) {e
.execCommand("inserttable");} ],
235 htmlmode
: [ "Toggle HTML Source", "ed_html.gif", true, function(e
) {e
.execCommand("htmlmode");} ],
236 popupeditor
: [ "Enlarge Editor", "fullscreen_maximize.gif", true, function(e
) {e
.execCommand("popupeditor");} ],
237 about
: [ "About this editor", "ed_about.gif", true, function(e
) {e
.execCommand("about");} ],
238 showhelp
: [ "Help using editor", "ed_help.gif", true, function(e
) {e
.execCommand("showhelp");} ],
239 undo
: [ "Undoes your last action", "ed_undo.gif", false, function(e
) {e
.execCommand("undo");} ],
240 redo
: [ "Redoes your last action", "ed_redo.gif", false, function(e
) {e
.execCommand("redo");} ],
241 cut
: [ "Cut selection", "ed_cut.gif", false, cut_copy_paste
],
242 copy
: [ "Copy selection", "ed_copy.gif", false, cut_copy_paste
],
243 paste
: [ "Paste from clipboard", "ed_paste.gif", false, cut_copy_paste
],
244 clean
: [ "Clean Word HTML", "ed_wordclean.gif", false, function(e
) {e
.execCommand("killword"); }],
245 lefttoright
: [ "Direction left to right", "ed_left_to_right.gif", false, function(e
) {e
.execCommand("lefttoright");} ],
246 righttoleft
: [ "Direction right to left", "ed_right_to_left.gif", false, function(e
) {e
.execCommand("righttoleft");} ],
247 insertsmile
: ["Insert Smiley", "em.icon.smile.gif", false, function(e
) {e
.execCommand("insertsmile");} ],
248 insertchar
: [ "Insert Char", "icon_ins_char.gif", false, function(e
) {e
.execCommand("insertchar");} ]
250 /* ADDING CUSTOM BUTTONS
251 * ---------------------
253 * It is recommended that you add the custom buttons in an external
254 * file and leave this one unchanged. That's because when we
255 * (InteractiveTools.com) release a new official version, it's less
256 * likely that you will have problems upgrading HTMLArea.
258 * Example on how to add a custom button when you construct the HTMLArea:
260 * var editor = new HTMLArea("your_text_area_id");
261 * var cfg = editor.config; // this is the default configuration
262 * cfg.btnList["my-hilite"] =
263 * [ function(editor) { editor.surroundHTML('<span style="background:yellow">', '</span>'); }, // action
264 * "Highlight selection", // tooltip
265 * "my_hilite.gif", // image
266 * false // disabled in text mode
268 * cfg.toolbar.push(["linebreak", "my-hilite"]); // add the new button to the toolbar
270 * An alternate (also more convenient and recommended) way to
271 * accomplish this is to use the registerButton function below.
273 // initialize tooltips from the I18N module and generate correct image path
274 for (var i in this
.btnList
) {
275 var btn
= this
.btnList
[i
];
276 btn
[1] = _editor_url + this
.imgURL + btn
[1];
277 if (typeof HTMLArea
.I18N
.tooltips
[i
] != "undefined") {
278 btn
[0] = HTMLArea
.I18N
.tooltips
[i
];
283 /** Helper function: register a new button with the configuration. It can be
284 * called with all 5 arguments, or with only one (first one). When called with
285 * only one argument it must be an object with the following properties: id,
286 * tooltip, image, textMode, action. Examples:
288 * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...});
289 * 2. config.registerButton({
290 * id : "my-hilite", // the ID of your button
291 * tooltip : "Hilite text", // the tooltip
292 * image : "my-hilite.gif", // image to be displayed in the toolbar
293 * textMode : false, // disabled in text mode
294 * action : function(editor) { // called when the button is clicked
295 * editor.surroundHTML('<span class="hilite">', '</span>');
297 * context : "p" // will be disabled if outside a <p> element
300 HTMLArea
.Config
.prototype
.registerButton
= function(id
, tooltip
, image
, textMode
, action
, context
) {
302 if (typeof id
== "string") {
304 } else if (typeof id
== "object") {
307 alert("ERROR [HTMLArea.Config::registerButton]:\ninvalid arguments");
310 // check for existing id
311 if (typeof this
.customSelects
[the_id
] != "undefined") {
312 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
314 if (typeof this
.btnList
[the_id
] != "undefined") {
315 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
318 case "string": this
.btnList
[id
] = [ tooltip
, image
, textMode
, action
, context
]; break;
319 case "object": this
.btnList
[id
.id
] = [ id
.tooltip
, id
.image
, id
.textMode
, id
.action
, id
.context
]; break;
323 /** The following helper function registers a dropdown box with the editor
324 * configuration. You still have to add it to the toolbar, same as with the
325 * buttons. Call it like this:
329 HTMLArea
.Config
.prototype
.registerDropdown
= function(object) {
330 // check for existing id
331 if (typeof this
.customSelects
[object.id
] != "undefined") {
332 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA dropdown with the same ID already exists.");
334 if (typeof this
.btnList
[object.id
] != "undefined") {
335 // alert("WARNING [HTMLArea.Config::registerDropdown]:\nA button with the same ID already exists.");
337 this
.customSelects
[object.id
] = object;
340 /** Call this function to remove some buttons/drop-down boxes from the toolbar.
341 * Pass as the only parameter a string containing button/drop-down names
342 * delimited by spaces. Note that the string should also begin with a space
343 * and end with a space. Example:
345 * config.hideSomeButtons(" fontname fontsize textindicator ");
347 * It's useful because it's easier to remove stuff from the defaul toolbar than
348 * create a brand new toolbar ;-)
350 HTMLArea
.Config
.prototype
.hideSomeButtons
= function(remove
) {
351 var toolbar
= this
.toolbar
;
352 for (var i in toolbar
) {
353 var line
= toolbar
[i
];
354 for (var j
= line
.length
; --j
>= 0; ) {
355 if (remove
.indexOf(" " + line
[j
] +
" ") >= 0) {
357 if (/separator|space
/.test(line
[j +
1])) {
366 /** Helper function: replace all TEXTAREA-s in the document with HTMLArea-s. */
367 HTMLArea
.replaceAll
= function(config
) {
368 var tas
= document
.getElementsByTagName("textarea");
369 for (var i
= tas
.length
; i
> 0; (new HTMLArea(tas
[--i
], config
)).generate());
372 /** Helper function: replaces the TEXTAREA with the given ID with HTMLArea. */
373 HTMLArea
.replace
= function(id
, config
) {
374 var ta
= HTMLArea
.getElementById("textarea", id
);
375 return ta ?
(new HTMLArea(ta
, config
)).generate() : null;
378 // Creates the toolbar and appends it to the _htmlarea
379 HTMLArea
.prototype
._createToolbar
= function () {
380 var editor
= this
; // to access this in nested functions
382 var toolbar
= document
.createElement("div");
383 this
._toolbar
= toolbar
;
384 toolbar
.className
= "toolbar";
385 toolbar
.unselectable
= "1";
387 var tb_objects
= new Object();
388 this
._toolbarObjects
= tb_objects
;
390 // creates a new line in the toolbar
392 var table
= document
.createElement("table");
393 table
.border
= "0px";
394 table
.cellSpacing
= "0px";
395 table
.cellPadding
= "0px";
396 toolbar
.appendChild(table
);
397 // TBODY is required for IE, otherwise you don't see anything
399 var tb_body
= document
.createElement("tbody");
400 table
.appendChild(tb_body
);
401 tb_row
= document
.createElement("tr");
402 tb_body
.appendChild(tb_row
);
403 }; // END of function: newLine
407 // updates the state of a toolbar element. This function is member of
408 // a toolbar element object (unnamed objects created by createButton or
409 // createSelect functions below).
410 function setButtonStatus(id
, newval
) {
411 var oldval
= this
[id
];
412 var el
= this
.element
;
413 if (oldval
!= newval
) {
417 HTMLArea
._removeClass(el
, "buttonDisabled");
420 HTMLArea
._addClass(el
, "buttonDisabled");
426 HTMLArea
._addClass(el
, "buttonPressed");
428 HTMLArea
._removeClass(el
, "buttonPressed");
434 }; // END of function: setButtonStatus
436 // this function will handle creation of combo boxes. Receives as
437 // parameter the name of a button as defined in the toolBar config.
438 // This function is called from createButton, above, if the given "txt"
439 // doesn't match a button.
440 function createSelect(txt
) {
444 var customSelects
= editor
.config
.customSelects
;
450 // the following line retrieves the correct
451 // configuration option because the variable name
452 // inside the Config object is named the same as the
453 // button/select in the toolbar. For instance, if txt
454 // == "formatblock" we retrieve config.formatblock (or
455 // a different way to write it in JS is
456 // config["formatblock"].
457 options
= editor
.config
[txt
];
461 // try to fetch it from the list of registered selects
463 var dropdown
= customSelects
[cmd
];
464 if (typeof dropdown
!= "undefined") {
465 options
= dropdown
.options
;
466 context
= dropdown
.context
;
468 alert("ERROR [createSelect]:\nCan't find the requested dropdown definition");
473 el
= document
.createElement("select");
475 name
: txt
, // field name
476 element
: el
, // the UI element (SELECT)
477 enabled
: true, // is it enabled?
478 text
: false, // enabled in text mode?
479 cmd
: cmd
, // command ID
480 state
: setButtonStatus
, // for changing state
483 tb_objects
[txt
] = obj
;
484 for (var i in options
) {
485 var op
= document
.createElement("option");
486 op
.appendChild(document
.createTextNode(i
));
487 op
.value
= options
[i
];
490 HTMLArea
._addEvent(el
, "change", function () {
491 editor
._comboSelected(el
, txt
);
495 }; // END of function: createSelect
497 // appends a new button to toolbar
498 function createButton(txt
) {
499 // the element that will be created
504 el
= document
.createElement("div");
505 el
.className
= "separator";
508 el
= document
.createElement("div");
509 el
.className
= "space";
514 case "textindicator":
515 el
= document
.createElement("div");
516 el
.appendChild(document
.createTextNode("A"));
517 el
.className
= "indicator";
518 el
.title
= HTMLArea
.I18N
.tooltips
.textindicator
;
520 name
: txt
, // the button name (i.e. 'bold')
521 element
: el
, // the UI element (DIV)
522 enabled
: true, // is it enabled?
523 active
: false, // is it pressed?
524 text
: false, // enabled in text mode?
525 cmd
: "textindicator", // the command ID
526 state
: setButtonStatus
// for changing state
528 tb_objects
[txt
] = obj
;
531 btn
= editor
.config
.btnList
[txt
];
534 el
= document
.createElement("div");
536 el
.className
= "button";
537 // let's just pretend we have a button object, and
538 // assign all the needed information to it.
540 name
: txt
, // the button name (i.e. 'bold')
541 element
: el
, // the UI element (DIV)
542 enabled
: true, // is it enabled?
543 active
: false, // is it pressed?
544 text
: btn
[2], // enabled in text mode?
545 cmd
: btn
[3], // the command ID
546 state
: setButtonStatus
, // for changing state
547 context
: btn
[4] ||
null // enabled in a certain context?
549 tb_objects
[txt
] = obj
;
550 // handlers to emulate nice flat toolbar buttons
551 HTMLArea
._addEvent(el
, "mouseover", function () {
553 HTMLArea
._addClass(el
, "buttonHover");
556 HTMLArea
._addEvent(el
, "mouseout", function () {
557 if (obj
.enabled
) with (HTMLArea
) {
558 _removeClass(el
, "buttonHover");
559 _removeClass(el
, "buttonActive");
560 (obj
.active
) && _addClass(el
, "buttonPressed");
563 HTMLArea
._addEvent(el
, "mousedown", function (ev
) {
564 if (obj
.enabled
) with (HTMLArea
) {
565 _addClass(el
, "buttonActive");
566 _removeClass(el
, "buttonPressed");
567 _stopEvent(is_ie ? window
.event
: ev
);
570 // when clicked, do the following:
571 HTMLArea
._addEvent(el
, "click", function (ev
) {
572 if (obj
.enabled
) with (HTMLArea
) {
573 _removeClass(el
, "buttonActive");
574 _removeClass(el
, "buttonHover");
575 obj
.cmd(editor
, obj
.name
, obj
);
576 _stopEvent(is_ie ? window
.event
: ev
);
579 var img
= document
.createElement("img");
581 img
.style
.width
= "18px";
582 img
.style
.height
= "18px";
585 el
= createSelect(txt
);
588 var tb_cell
= document
.createElement("td");
589 tb_row
.appendChild(tb_cell
);
590 tb_cell
.appendChild(el
);
592 alert("FIXME: Unknown toolbar item: " + txt
);
598 for (var i in this
.config
.toolbar
) {
600 createButton("linebreak");
604 var group
= this
.config
.toolbar
[i
];
605 for (var j in group
) {
607 if (/^
([IT
])\
[(.*?
)\
]/.test(code
)) {
608 // special case, create text label
609 var l7ed
= RegExp
.$1 == "I"; // localized?
610 var label
= RegExp
.$2;
612 label
= HTMLArea
.I18N
.custom
[label
];
614 var tb_cell
= document
.createElement("td");
615 tb_row
.appendChild(tb_cell
);
616 tb_cell
.className
= "label";
617 tb_cell
.innerHTML
= label
;
624 this
._htmlArea
.appendChild(toolbar
);
627 HTMLArea
.prototype
._createStatusBar
= function() {
628 var statusbar
= document
.createElement("div");
629 statusbar
.className
= "statusBar";
630 this
._htmlArea
.appendChild(statusbar
);
631 this
._statusBar
= statusbar
;
632 // statusbar.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
633 // creates a holder for the path view
634 div
= document
.createElement("span");
635 div
.className
= "statusBarTree";
636 div
.innerHTML
= HTMLArea
.I18N
.msg
["Path"] +
": ";
637 this
._statusBarTree
= div
;
638 this
._statusBar
.appendChild(div
);
639 if (!this
.config
.statusBar
) {
641 statusbar
.style
.display
= "none";
645 // Creates the HTMLArea object and replaces the textarea with it.
646 HTMLArea
.prototype
.generate
= function () {
647 var editor
= this
; // we'll need "this" in some nested functions
649 var textarea
= this
._textArea
;
650 if (typeof textarea
== "string") {
651 // it's not element but ID
652 this
._textArea
= textarea
= HTMLArea
.getElementById("textarea", textarea
);
655 w
: textarea
.offsetWidth
,
656 h
: textarea
.offsetHeight
658 textarea
.style
.display
= "none";
660 // create the editor framework
661 var htmlarea
= document
.createElement("div");
662 htmlarea
.className
= "htmlarea";
663 this
._htmlArea
= htmlarea
;
665 // insert the editor before the textarea.
666 textarea
.parentNode
.insertBefore(htmlarea
, textarea
);
669 // we have a form, on submit get the HTMLArea content and
670 // update original textarea.
671 var f
= textarea
.form
;
672 if (typeof f
.onsubmit
== "function") {
673 var funcref
= f
.onsubmit
;
674 if (typeof f
.__msh_prevOnSubmit
== "undefined") {
675 f
.__msh_prevOnSubmit
= [];
677 f
.__msh_prevOnSubmit
.push(funcref
);
679 f
.onsubmit
= function() {
680 editor
._textArea
.value
= editor
.getHTML();
681 var a
= this
.__msh_prevOnSubmit
;
682 // call previous submit methods if they were there.
683 if (typeof a
!= "undefined") {
691 // add a handler for the "back/forward" case -- on body.unload we save
692 // the HTML content into the original textarea.
693 window
.onunload
= function() {
694 editor
._textArea
.value
= editor
.getHTML();
697 // creates & appends the toolbar
698 this
._createToolbar();
701 var iframe
= document
.createElement("iframe");
703 if (HTMLArea
.is_ie
) { // http://moodle.org/mod/forum/discuss.php?d=8555
704 // tricky! set src to local url to turn off SSL security alert
705 iframe
.src
= _editor_url + this
.config
.popupURL+
"blank.html";
708 htmlarea
.appendChild(iframe
);
710 this
._iframe
= iframe
;
712 // creates & appends the status bar, if the case
713 this
._createStatusBar();
715 // remove the default border as it keeps us from computing correctly
716 // the sizes. (somebody tell me why doesn't this work in IE)
718 if (!HTMLArea
.is_ie
) {
719 iframe
.style
.borderWidth
= "1px";
720 // iframe.frameBorder = "1";
721 // iframe.marginHeight = "0";
722 // iframe.marginWidth = "0";
725 // size the IFRAME according to user's prefs or initial textarea
726 var height
= (this
.config
.height
== "auto" ?
(this
._ta_size
.h +
"px") : this
.config
.height
);
727 height
= parseInt(height
);
728 var width
= (this
.config
.width
== "auto" ?
(this
._ta_size
.w +
50 +
"px") : this
.config
.width
);
729 width
= parseInt(width
);
731 if (!HTMLArea
.is_ie
) {
736 iframe
.style
.width
= width +
"px";
737 if (this
.config
.sizeIncludesToolbar
) {
738 // substract toolbar height
739 height
-= this
._toolbar
.offsetHeight
;
740 height
-= this
._statusBar
.offsetHeight
;
745 iframe
.style
.height
= height +
"px";
747 // the editor including the toolbar now have the same size as the
748 // original textarea.. which means that we need to reduce that a bit.
749 textarea
.style
.width
= iframe
.style
.width
;
750 textarea
.style
.height
= iframe
.style
.height
;
752 // IMPORTANT: we have to allow Mozilla a short time to recognize the
753 // new frame. Otherwise we get a stupid exception.
754 function initIframe() {
755 var doc
= editor
._iframe
.contentWindow
.document
;
758 // FIXME: don't know what else to do here. Normally
759 // we'll never reach this point.
760 if (HTMLArea
.is_gecko
) {
761 setTimeout(initIframe
, 100);
764 alert("ERROR: IFRAME can't be initialized.");
767 if (HTMLArea
.is_gecko
) {
768 // enable editable mode for Mozilla
769 doc
.designMode
= "on";
772 if (!editor
.config
.fullPage
) {
774 var html
= "<html>\n";
776 if (editor
.config
.baseURL
)
777 html +
= '<base href="' + editor
.config
.baseURL +
'" />';
778 html +
= "<style>" + editor
.config
.pageStyle +
" td { border: 1px dotted gray; }</style>\n";
781 html +
= editor
._textArea
.value
;
787 var html
= editor
._textArea
.value
;
788 if (html
.match(HTMLArea
.RE_doctype
)) {
789 editor
.setDoctype(RegExp
.$1);
790 html
= html
.replace(HTMLArea
.RE_doctype
, "");
797 if (HTMLArea
.is_ie
) {
798 // enable editable mode for IE. For some reason this
799 // doesn't work if done in the same place as for Gecko
801 doc
.body
.contentEditable
= true;
804 editor
.focusEditor();
805 // intercept some events; for updating the toolbar & keyboard handlers
807 (doc
, ["keydown", "keypress", "mousedown", "mouseup", "drag"],
809 return editor
._editorEvent(HTMLArea
.is_ie ? editor
._iframe
.contentWindow
.event
: event
);
812 // check if any plugins have registered refresh handlers
813 for (var i in editor
.plugins
) {
814 var plugin
= editor
.plugins
[i
].instance
;
815 if (typeof plugin
.onGenerate
== "function")
819 setTimeout(function() {
820 editor
.updateToolbar();
823 if (typeof editor
.onGenerate
== "function")
826 setTimeout(initIframe
, 100);
829 // Switches editor mode; parameter can be "textmode" or "wysiwyg". If no
830 // parameter was passed this function toggles between modes.
831 HTMLArea
.prototype
.setMode
= function(mode
) {
832 if (typeof mode
== "undefined") {
833 mode
= ((this
._editMode
== "textmode") ?
"wysiwyg" : "textmode");
837 this
._textArea
.value
= this
.getHTML();
838 this
._iframe
.style
.display
= "none";
839 this
._textArea
.style
.display
= "block";
840 if (this
.config
.statusBar
) {
841 this
._statusBar
.innerHTML
= HTMLArea
.I18N
.msg
["TEXT_MODE"];
845 if (HTMLArea
.is_gecko
) {
846 // disable design mode before changing innerHTML
848 this
._doc
.designMode
= "off";
851 if (!this
.config
.fullPage
)
852 this
._doc
.body
.innerHTML
= this
.getHTML();
854 this
.setFullHTML(this
.getHTML());
855 this
._iframe
.style
.display
= "block";
856 this
._textArea
.style
.display
= "none";
857 if (HTMLArea
.is_gecko
) {
858 // we need to refresh that info for Moz-1.3a
860 this
._doc
.designMode
= "on";
864 if (this
.config
.statusBar
) {
865 this
._statusBar
.innerHTML
= '';
866 this
._statusBar
.appendChild(document
.createTextNode(HTMLArea
.I18N
.msg
["Path"] +
": "));
867 this
._statusBar
.appendChild(this
._statusBarTree
);
871 alert("Mode <" + mode +
"> not defined!");
874 this
._editMode
= mode
;
878 HTMLArea
.prototype
.setFullHTML
= function(html
) {
879 var save_multiline
= RegExp
.multiline
;
880 RegExp
.multiline
= true;
881 if (html
.match(HTMLArea
.RE_doctype
)) {
882 this
.setDoctype(RegExp
.$1);
883 html
= html
.replace(HTMLArea
.RE_doctype
, "");
885 RegExp
.multiline
= save_multiline
;
886 if (!HTMLArea
.is_ie
) {
887 if (html
.match(HTMLArea
.RE_head
))
888 this
._doc
.getElementsByTagName("head")[0].innerHTML
= RegExp
.$1;
889 if (html
.match(HTMLArea
.RE_body
))
890 this
._doc
.getElementsByTagName("body")[0].innerHTML
= RegExp
.$1;
892 var html_re
= /<html
>((.|\n
)*?
)<\
/html
>/i
;
893 html
= html
.replace(html_re
, "$1");
895 this
._doc
.write(html
);
897 this
._doc
.body
.contentEditable
= true;
902 /***************************************************
904 ***************************************************/
906 // this is the variant of the function above where the plugin arguments are
907 // already packed in an array. Externally, it should be only used in the
908 // full-screen editor code, in order to initialize plugins with the same
909 // parameters as in the opener window.
910 HTMLArea
.prototype
.registerPlugin2
= function(plugin
, args
) {
911 if (typeof plugin
== "string")
912 plugin
= eval(plugin
);
913 var obj
= new plugin(this
, args
);
916 var info
= plugin
._pluginInfo
;
919 clone.instance
= obj
;
921 this
.plugins
[plugin
._pluginInfo
.name
] = clone;
923 alert("Can't register plugin " + plugin
.toString() +
".");
926 // Create the specified plugin and register it with this HTMLArea
927 HTMLArea
.prototype
.registerPlugin
= function() {
928 var plugin
= arguments
[0];
930 for (var i
= 1; i
< arguments
.length
; ++i
)
931 args
.push(arguments
[i
]);
932 this
.registerPlugin2(plugin
, args
);
935 // static function that loads the required plugin and lang file, based on the
936 // language loaded already for HTMLArea. You better make sure that the plugin
937 // _has_ that language, otherwise shit might happen ;-)
938 HTMLArea
.loadPlugin
= function(pluginName
) {
939 var dir
= _editor_url +
"plugins/" + pluginName
;
940 var plugin
= pluginName
.replace(/([a
-z
])([A
-Z
])([a
-z
])/g
,
941 function (str
, l1
, l2
, l3
) {
942 return l1 +
"-" + l2
.toLowerCase() + l3
;
943 }).toLowerCase() +
".js";
944 var plugin_file
= dir +
"/" + plugin
;
945 var plugin_lang
= dir +
"/lang/" + HTMLArea
.I18N
.lang +
".js";
946 HTMLArea
._scripts
.push(plugin_file
, plugin_lang
);
947 document
.write("<script type='text/javascript' src='" + plugin_file +
"'></script>");
948 document
.write("<script type='text/javascript' src='" + plugin_lang +
"'></script>");
951 HTMLArea
.loadStyle
= function(style
, plugin
) {
952 var url
= _editor_url ||
'';
953 if (typeof plugin
!= "undefined") {
954 url +
= "plugins/" + plugin +
"/";
957 document
.write("<style type='text/css'>@import url(" + url +
");</style>");
959 HTMLArea
.loadStyle("htmlarea.css");
961 /***************************************************
962 * Category: EDITOR UTILITIES
963 ***************************************************/
965 // The following function is a slight variation of the word cleaner code posted
966 // by Weeezl (user @ InteractiveTools forums).
967 HTMLArea
.prototype
._wordClean
= function() {
968 var D
= this
.getInnerHTML();
969 if (D
.indexOf("class=Mso") >= 0 || D
.indexOf("mso") >= 0 || D
.indexOf("Mso") >= 0) {
972 D
= D
.replace(/\r\n/g
, ' ').
975 replace(/\
 \
;/g
,' ');
977 // keep tags, strip attributes
978 D
= D
.replace(/ class=[^\s|
>]*/gi
,'').
979 //replace(/<p [^>]*TEXT-ALIGN: justify[^>]*>/gi,'<p align="justify">').
980 replace(/ style
=\"[^
>]*\"/gi
,'').
981 replace(/ align
=[^\s|
>]*/gi
,'');
984 D
= D
.replace(/<b
[^
>]*>/gi
,'<b>').
985 replace(/<i
[^
>]*>/gi
,'<i>').
986 replace(/<li
[^
>]*>/gi
,'<li>').
987 replace(/<ul
[^
>]*>/gi
,'<ul>');
989 // replace outdated tags
990 D
= D
.replace(/<b
>/gi
,'<strong>').
991 replace(/<\
/b
>/gi
,'</strong>');
993 // mozilla doesn't like <em> tags
994 D
= D
.replace(/<em
>/gi
,'<i>').
995 replace(/<\
/em
>/gi
,'</i>');
997 // kill unwanted tags
998 D
= D
.replace(/<\?xml
:[^
>]*>/g
, ''). // Word xml
999 replace(/<\
/?st1
:[^
>]*>/g
,''). // Word SmartTags
1000 replace(/<\
/?
[a
-z
]\
:[^
>]*>/g
,''). // All other funny Word non-HTML stuff
1001 replace(/<\
/?font
[^
>]*>/gi
,''). // Disable if you want to keep font formatting
1002 replace(/<\
/?span
[^
>]*>/gi
,' ').
1003 replace(/<\
/?div
[^
>]*>/gi
,' ').
1004 replace(/<\
/?pre
[^
>]*>/gi
,' ').
1005 replace(/<(\
/?
)(h
[1-6]+
)[^
>]*>/gi
,'<$1$2>');
1008 //D = D.replace(/<strong><\/strong>/gi,'').
1009 //replace(/<i><\/i>/gi,'').
1010 //replace(/<P[^>]*><\/P>/gi,'');
1013 oldlen
= D
.length +
1;
1014 while(oldlen
> D
.length
) {
1016 // join us now and free the tags, we'll be free hackers, we'll be free... ;-)
1017 D
= D
.replace(/<([a
-z
][a
-z
]*)> *<\
/\
1>/gi
,' ').
1018 replace(/<([a
-z
][a
-z
]*)> *<([a
-z
][^
>]*)> *<\
/\
1>/gi
,'<$2>');
1020 D
= D
.replace(/<([a
-z
][a
-z
]*)><\
1>/gi
,'<$1>').
1021 replace(/<\
/([a
-z
][a
-z
]*)><\
/\
1>/gi
,'<\/$1>');
1023 // nuke double spaces
1024 D
= D
.replace(/ */gi
,' ');
1027 this
.updateToolbar();
1031 HTMLArea
.prototype
.forceRedraw
= function() {
1032 this
._doc
.body
.style
.visibility
= "hidden";
1033 this
._doc
.body
.style
.visibility
= "visible";
1034 // this._doc.body.innerHTML = this.getInnerHTML();
1037 // focuses the iframe window. returns a reference to the editor document.
1038 HTMLArea
.prototype
.focusEditor
= function() {
1039 switch (this
._editMode
) {
1040 case "wysiwyg" : this
._iframe
.contentWindow
.focus(); break;
1041 case "textmode": this
._textArea
.focus(); break;
1042 default : alert("ERROR: mode " + this
._editMode +
" is not defined");
1047 // takes a snapshot of the current text (for undo)
1048 HTMLArea
.prototype
._undoTakeSnapshot
= function() {
1050 if (this
._undoPos
>= this
.config
.undoSteps
) {
1051 // remove the first element
1052 this
._undoQueue
.shift();
1055 // use the fasted method (getInnerHTML);
1057 var txt
= this
.getInnerHTML();
1058 if (this
._undoPos
> 0)
1059 take
= (this
._undoQueue
[this
._undoPos
- 1] != txt
);
1061 this
._undoQueue
[this
._undoPos
] = txt
;
1067 HTMLArea
.prototype
.undo
= function() {
1068 if (this
._undoPos
> 0) {
1069 var txt
= this
._undoQueue
[--this
._undoPos
];
1070 if (txt
) this
.setHTML(txt
);
1071 else ++this
._undoPos
;
1075 HTMLArea
.prototype
.redo
= function() {
1076 if (this
._undoPos
< this
._undoQueue
.length
- 1) {
1077 var txt
= this
._undoQueue
[++this
._undoPos
];
1078 if (txt
) this
.setHTML(txt
);
1079 else --this
._undoPos
;
1083 // updates enabled/disable/active state of the toolbar elements
1084 HTMLArea
.prototype
.updateToolbar
= function(noStatus
) {
1085 var doc
= this
._doc
;
1086 var text
= (this
._editMode
== "textmode");
1087 var ancestors
= null;
1089 ancestors
= this
.getAllAncestors();
1090 if (this
.config
.statusBar
&& !noStatus
) {
1091 this
._statusBarTree
.innerHTML
= HTMLArea
.I18N
.msg
["Path"] +
": "; // clear
1092 for (var i
= ancestors
.length
; --i
>= 0;) {
1093 var el
= ancestors
[i
];
1095 // hell knows why we get here; this
1096 // could be a classic example of why
1097 // it's good to check for conditions
1098 // that are impossible to happen ;-)
1101 var a
= document
.createElement("a");
1105 a
.onclick
= function() {
1107 this
.editor
.selectNodeContents(this
.el
);
1108 this
.editor
.updateToolbar(true);
1111 a
.oncontextmenu
= function() {
1112 // TODO: add context menu here
1114 var info
= "Inline style:\n\n";
1115 info +
= this
.el
.style
.cssText
.split(/;\s*/
).join(";\n");
1119 var txt
= el
.tagName
.toLowerCase();
1120 a
.title
= el
.style
.cssText
;
1125 txt +
= "." + el
.className
;
1127 a
.appendChild(document
.createTextNode(txt
));
1128 this
._statusBarTree
.appendChild(a
);
1130 this
._statusBarTree
.appendChild(document
.createTextNode(String.fromCharCode(0xbb)));
1135 for (var i in this
._toolbarObjects
) {
1136 var btn
= this
._toolbarObjects
[i
];
1138 var inContext
= true;
1139 if (btn
.context
&& !text
) {
1141 var context
= btn
.context
;
1143 if (/(.*)\
[(.*?
)\
]/.test(context
)) {
1144 context
= RegExp
.$1;
1145 attrs
= RegExp
.$2.split(",");
1147 context
= context
.toLowerCase();
1148 var match
= (context
== "*");
1149 for (var k in ancestors
) {
1150 if (!ancestors
[k
]) {
1151 // the impossible really happens.
1154 if (match ||
(ancestors
[k
].tagName
.toLowerCase() == context
)) {
1156 for (var ka in attrs
) {
1157 if (!eval("ancestors[k]." + attrs
[ka
])) {
1168 btn
.state("enabled", (!text || btn
.text
) && inContext
);
1169 if (typeof cmd
== "function") {
1172 // look-it-up in the custom dropdown boxes
1173 var dropdown
= this
.config
.customSelects
[cmd
];
1174 if ((!text || btn
.text
) && (typeof dropdown
!= "undefined")) {
1175 dropdown
.refresh(this
);
1183 var value
= ("" + doc
.queryCommandValue(cmd
)).toLowerCase();
1185 // FIXME: what do we do here?
1188 // HACK -- retrieve the config option for this
1189 // combo box. We rely on the fact that the
1190 // variable in config has the same name as
1191 // button name in the toolbar.
1192 var options
= this
.config
[cmd
];
1194 // btn.element.selectedIndex = 0;
1195 for (var j in options
) {
1196 // FIXME: the following line is scary.
1197 if ((j
.toLowerCase() == value
) ||
1198 (options
[j
].substr(0, value
.length
).toLowerCase() == value
)) {
1199 btn
.element
.selectedIndex
= k
;
1206 case "textindicator":
1208 try {with (btn
.element
.style
) {
1209 backgroundColor
= HTMLArea
._makeColor(
1210 doc
.queryCommandValue(HTMLArea
.is_ie ?
"backcolor" : "hilitecolor"));
1211 if (/transparent
/i
.test(backgroundColor
)) {
1213 backgroundColor
= HTMLArea
._makeColor(doc
.queryCommandValue("backcolor"));
1215 color
= HTMLArea
._makeColor(doc
.queryCommandValue("forecolor"));
1216 fontFamily
= doc
.queryCommandValue("fontname");
1217 fontWeight
= doc
.queryCommandState("bold") ?
"bold" : "normal";
1218 fontStyle
= doc
.queryCommandState("italic") ?
"italic" : "normal";
1220 // alert(e + "\n\n" + cmd);
1224 case "htmlmode": btn
.state("active", text
); break;
1227 var el
= this
.getParentElement();
1228 while (el
&& !HTMLArea
.isBlockElement(el
))
1231 btn
.state("active", (el
.style
.direction
== ((cmd
== "righttoleft") ?
"rtl" : "ltr")));
1235 btn
.state("active", (!text
&& doc
.queryCommandState(cmd
)));
1239 // take undo snapshots
1240 if (this
._customUndo
&& !this
._timerUndo
) {
1241 this
._undoTakeSnapshot();
1243 this
._timerUndo
= setTimeout(function() {
1244 editor
._timerUndo
= null;
1245 }, this
.config
.undoTimeout
);
1247 // check if any plugins have registered refresh handlers
1248 for (var i in this
.plugins
) {
1249 var plugin
= this
.plugins
[i
].instance
;
1250 if (typeof plugin
.onUpdateToolbar
== "function")
1251 plugin
.onUpdateToolbar();
1255 /** Returns a node after which we can insert other nodes, in the current
1256 * selection. The selection is removed. It splits a text node, if needed.
1258 HTMLArea
.prototype
.insertNodeAtSelection
= function(toBeInserted
) {
1259 if (!HTMLArea
.is_ie
) {
1260 var sel
= this
._getSelection();
1261 var range
= this
._createRange(sel
);
1262 // remove the current selection
1263 sel
.removeAllRanges();
1264 range
.deleteContents();
1265 var node
= range
.startContainer
;
1266 var pos
= range
.startOffset
;
1267 switch (node
.nodeType
) {
1268 case 3: // Node.TEXT_NODE
1269 // we have to split it at the caret position.
1270 if (toBeInserted
.nodeType
== 3) {
1271 // do optimized insertion
1272 node
.insertData(pos
, toBeInserted
.data
);
1273 range
= this
._createRange();
1274 range
.setEnd(node
, pos + toBeInserted
.length
);
1275 range
.setStart(node
, pos + toBeInserted
.length
);
1276 sel
.addRange(range
);
1278 node
= node
.splitText(pos
);
1279 var selnode
= toBeInserted
;
1280 if (toBeInserted
.nodeType
== 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1281 selnode
= selnode
.firstChild
;
1283 node
.parentNode
.insertBefore(toBeInserted
, node
);
1284 this
.selectNodeContents(selnode
);
1285 this
.updateToolbar();
1288 case 1: // Node.ELEMENT_NODE
1289 var selnode
= toBeInserted
;
1290 if (toBeInserted
.nodeType
== 11 /* Node.DOCUMENT_FRAGMENT_NODE */) {
1291 selnode
= selnode
.firstChild
;
1293 node
.insertBefore(toBeInserted
, node
.childNodes
[pos
]);
1294 this
.selectNodeContents(selnode
);
1295 this
.updateToolbar();
1299 return null; // this function not yet used for IE <FIXME>
1303 // Returns the deepest node that contains both endpoints of the selection.
1304 HTMLArea
.prototype
.getParentElement
= function() {
1305 var sel
= this
._getSelection();
1306 var range
= this
._createRange(sel
);
1307 if (HTMLArea
.is_ie
) {
1311 // It seems that even for selection of type "None",
1312 // there _is_ a parent element and it's value is not
1313 // only correct, but very important to us. MSIE is
1314 // certainly the buggiest browser in the world and I
1315 // wonder, God, how can Earth stand it?
1316 return range
.parentElement();
1318 return range
.item(0);
1320 return this
._doc
.body
;
1323 var p
= range
.commonAncestorContainer
;
1324 if (!range
.collapsed
&& range
.startContainer
== range
.endContainer
&&
1325 range
.startOffset
- range
.endOffset
<= 1 && range
.startContainer
.hasChildNodes())
1326 p
= range
.startContainer
.childNodes
[range
.startOffset
];
1328 alert(range.startContainer + ":" + range.startOffset + "\n" +
1329 range.endContainer + ":" + range.endOffset);
1331 while (p
.nodeType
== 3) {
1340 // Returns an array with all the ancestor nodes of the selection.
1341 HTMLArea
.prototype
.getAllAncestors
= function() {
1342 var p
= this
.getParentElement();
1344 while (p
&& (p
.nodeType
== 1) && (p
.tagName
.toLowerCase() != 'body')) {
1348 a
.push(this
._doc
.body
);
1352 // Selects the contents inside the given node
1353 HTMLArea
.prototype
.selectNodeContents
= function(node
, pos
) {
1357 var collapsed
= (typeof pos
!= "undefined");
1358 if (HTMLArea
.is_ie
) {
1359 range
= this
._doc
.body
.createTextRange();
1360 range
.moveToElementText(node
);
1361 (collapsed
) && range
.collapse(pos
);
1364 var sel
= this
._getSelection();
1365 range
= this
._doc
.createRange();
1366 range
.selectNodeContents(node
);
1367 (collapsed
) && range
.collapse(pos
);
1368 sel
.removeAllRanges();
1369 sel
.addRange(range
);
1373 /** Call this function to insert HTML code at the current position. It deletes
1374 * the selection, if any.
1376 HTMLArea
.prototype
.insertHTML
= function(html
) {
1377 var sel
= this
._getSelection();
1378 var range
= this
._createRange(sel
);
1379 if (HTMLArea
.is_ie
) {
1380 range
.pasteHTML(html
);
1382 // construct a new document fragment with the given HTML
1383 var fragment
= this
._doc
.createDocumentFragment();
1384 var div
= this
._doc
.createElement("div");
1385 div
.innerHTML
= html
;
1386 while (div
.firstChild
) {
1387 // the following call also removes the node from div
1388 fragment
.appendChild(div
.firstChild
);
1390 // this also removes the selection
1391 var node
= this
.insertNodeAtSelection(fragment
);
1396 * Call this function to surround the existing HTML code in the selection with
1397 * your tags. FIXME: buggy! This function will be deprecated "soon".
1399 HTMLArea
.prototype
.surroundHTML
= function(startTag
, endTag
) {
1400 var html
= this
.getSelectedHTML();
1401 // the following also deletes the selection
1402 this
.insertHTML(startTag + html + endTag
);
1405 /// Retrieve the selected block
1406 HTMLArea
.prototype
.getSelectedHTML
= function() {
1407 var sel
= this
._getSelection();
1408 var range
= this
._createRange(sel
);
1409 var existing
= null;
1410 if (HTMLArea
.is_ie
) {
1411 existing
= range
.htmlText
;
1413 existing
= HTMLArea
.getHTML(range
.cloneContents(), false, this
);
1418 /// Return true if we have some selection
1419 HTMLArea
.prototype
.hasSelectedText
= function() {
1420 // FIXME: come _on_ mishoo, you can do better than this ;-)
1421 return this
.getSelectedHTML() != '';
1424 HTMLArea
.prototype
._createLink
= function(link
) {
1426 var outparam
= null;
1427 if (typeof link
== "undefined") {
1428 link
= this
.getParentElement();
1429 if (link
&& !/^a$
/i
.test(link
.tagName
))
1432 if (link
) outparam
= {
1433 f_href
: HTMLArea
.is_ie ? editor
.stripBaseURL(link
.href
) : link
.getAttribute("href"),
1434 f_title
: link
.title
,
1435 f_target
: link
.target
1437 this
._popupDialog("link_std.php?id=<?php echo $id; ?>", function(param
) {
1442 editor
._doc
.execCommand("createlink", false, param
.f_href
);
1443 a
= editor
.getParentElement();
1444 var sel
= editor
._getSelection();
1445 var range
= editor
._createRange(sel
);
1446 // if (!HTMLArea.is_ie) { /// Removed by PJ and Martin, Moodle bug #1455
1447 // a = range.startContainer;
1448 // if (!/^a$/i.test(a.tagName))
1449 // a = a.nextSibling;
1451 } else a
.href
= param
.f_href
.trim();
1452 if (!/^a$
/i
.test(a
.tagName
))
1454 a
.target
= param
.f_target
.trim();
1455 a
.title
= param
.f_title
.trim();
1456 editor
.selectNodeContents(a
);
1457 editor
.updateToolbar();
1461 // Called when the user clicks on "InsertImage" button. If an image is already
1462 // there, it will just modify it's properties.
1463 HTMLArea
.prototype
._insertImage
= function(image
) {
1464 var editor
= this
; // for nested functions
1465 var outparam
= null;
1466 if (typeof image
== "undefined") {
1467 image
= this
.getParentElement();
1468 if (image
&& !/^img$
/i
.test(image
.tagName
))
1471 if (image
) outparam
= {
1472 f_url
: HTMLArea
.is_ie ? editor
.stripBaseURL(image
.src
) : image
.getAttribute("src"),
1474 f_border
: image
.border
,
1475 f_align
: image
.align
,
1476 f_vert
: image
.vspace
,
1477 f_horiz
: image
.hspace
,
1478 f_width
: image
.width
,
1479 f_height
: image
.height
1481 this
._popupDialog("<?php
1482 if (has_capability('moodle/course:managefiles', get_context_instance(CONTEXT_COURSE, $id))) {
1483 echo "insert_image
.php?id
=$id";
1485 echo "insert_image_std
.php?id
=$id";
1486 }?>", function(param
) {
1487 if (!param
) { // user must have pressed Cancel
1492 var sel
= editor
._getSelection();
1493 var range
= editor
._createRange(sel
);
1494 editor
._doc
.execCommand("insertimage", false, param
.f_url
);
1495 if (HTMLArea
.is_ie
) {
1496 img
= range
.parentElement();
1497 // wonder if this works...
1498 if (img
.tagName
.toLowerCase() != "img") {
1499 img
= img
.previousSibling
;
1502 img
= range
.startContainer
.previousSibling
;
1505 img
.src
= param
.f_url
;
1507 for (field in param
) {
1508 var value
= param
[field
];
1510 case "f_alt" : img
.alt
= value
; break;
1511 case "f_border" : img
.border
= parseInt(value ||
"0"); break;
1512 case "f_align" : img
.align
= value
; break;
1513 case "f_vert" : img
.vspace
= parseInt(value ||
"0"); break;
1514 case "f_horiz" : img
.hspace
= parseInt(value ||
"0"); break;
1517 img
.width
= parseInt(value
);
1524 img
.height
= parseInt(value
);
1534 // Called when the user clicks the Insert Table button
1535 HTMLArea
.prototype
._insertTable
= function() {
1536 var sel
= this
._getSelection();
1537 var range
= this
._createRange(sel
);
1538 var editor
= this
; // for nested functions
1539 this
._popupDialog("insert_table.php", function(param
) {
1540 if (!param
) { // user must have pressed Cancel
1543 var doc
= editor
._doc
;
1544 // create the table element
1545 var table
= doc
.createElement("table");
1546 // assign the given arguments
1547 for (var field in param
) {
1548 var value
= param
[field
];
1553 case "f_width" : table
.width
= value + param
["f_unit"]; break;
1554 case "f_align" : table
.align
= value
; break;
1555 case "f_border" : table
.border
= parseInt(value
); break;
1556 case "f_spacing" : table
.cellspacing
= parseInt(value
); break;
1557 case "f_padding" : table
.cellpadding
= parseInt(value
); break;
1560 var tbody
= doc
.createElement("tbody");
1561 table
.appendChild(tbody
);
1562 for (var i
= 0; i
< param
["f_rows"]; ++i
) {
1563 var tr
= doc
.createElement("tr");
1564 tbody
.appendChild(tr
);
1565 for (var j
= 0; j
< param
["f_cols"]; ++j
) {
1566 var td
= doc
.createElement("td");
1568 if(param
["f_unit"] == "px") {
1569 tdwidth
= Math
.round(table
.width
/ param
["f_cols"]);
1571 tdwidth
= Math
.round(100 / param
["f_cols"]);
1573 td
.setAttribute("width",tdwidth + param
["f_unit"]);
1574 td
.setAttribute("valign","top");
1575 /// Moodle hack -ends
1577 // Mozilla likes to see something inside the cell.
1578 (HTMLArea
.is_gecko
) && td
.appendChild(doc
.createElement("br"));
1581 if (HTMLArea
.is_ie
) {
1582 range
.pasteHTML(table
.outerHTML
);
1585 editor
.insertNodeAtSelection(table
);
1590 /******************************************************************
1591 * Moodle hack - insertSmile
1592 ******************************************************************/
1593 /// since method insertimage doesn't work the same way in mozilla
1594 /// as it does in IE, let's go around this for both browsers.
1595 HTMLArea
.prototype
._insertSmile
= function() {
1596 var sel
= this
._getSelection();
1597 var range
= this
._createRange(sel
);
1598 var editor
= this
; // for nested functions
1599 this
._popupDialog("dlg_ins_smile.php", function(imgString
) {
1603 if (HTMLArea
.is_ie
) {
1604 range
.pasteHTML(imgString
);
1607 editor
.insertHTML(imgString
);
1613 HTMLArea
.prototype
._insertChar
= function() {
1614 var sel
= this
._getSelection();
1615 var range
= this
._createRange(sel
);
1616 var editor
= this
; // for nested functions
1617 this
._popupDialog("dlg_ins_char.php", function(sChar
) {
1621 if (HTMLArea
.is_ie
) {
1622 range
.pasteHTML(sChar
);
1625 editor
.insertHTML(sChar
);
1631 HTMLArea
.prototype
._removelink
= function() {
1633 link
= this
.getParentElement();
1634 editor
.selectNodeContents(link
);
1636 this
._doc
.execCommand("unlink", false, null);
1639 /************************************************************************
1640 * Moodle hack's ends
1641 ************************************************************************/
1642 /***************************************************
1643 * Category: EVENT HANDLERS
1644 ***************************************************/
1646 // el is reference to the SELECT object
1647 // txt is the name of the select field, as in config.toolbar
1648 HTMLArea
.prototype
._comboSelected
= function(el
, txt
) {
1650 var value
= el
.options
[el
.selectedIndex
].value
;
1653 case "fontsize": this
.execCommand(txt
, false, value
); break;
1655 (HTMLArea
.is_ie
) && (value
= "<" + value +
">");
1656 this
.execCommand(txt
, false, value
);
1659 // try to look it up in the registered dropdowns
1660 var dropdown
= this
.config
.customSelects
[txt
];
1661 if (typeof dropdown
!= "undefined") {
1662 dropdown
.action(this
);
1664 alert("FIXME: combo box " + txt +
" not implemented");
1669 // the execCommand function (intercepts some commands and replaces them with
1670 // our own implementation)
1671 HTMLArea
.prototype
.execCommand
= function(cmdID
, UI
, param
) {
1672 var editor
= this
; // for nested functions
1674 cmdID
= cmdID
.toLowerCase();
1676 case "htmlmode" : this
.setMode(); break;
1678 (HTMLArea
.is_ie
) && (cmdID
= "backcolor");
1680 this
._popupDialog("select_color.php", function(color
) {
1681 if (color
) { // selection not canceled
1682 editor
._doc
.execCommand(cmdID
, false, "#" + color
);
1684 }, HTMLArea
._colorToRgb(this
._doc
.queryCommandValue(cmdID
)));
1689 case "unlink": this
._removelink(); break;
1691 // this object will be passed to the newly opened window
1692 HTMLArea
._object
= this
;
1693 if (HTMLArea
.is_ie
) {
1694 //if (confirm(HTMLArea.I18N.msg["IE-sucks-full-screen"]))
1696 window
.open(this
.popupURL("fullscreen.php?id=<?php print($id);?>"), "ha_fullscreen",
1697 "toolbar=no,location=no,directories=no,status=no,menubar=no," +
1698 "scrollbars=no,resizable=yes,width=800,height=600");
1701 window
.open(this
.popupURL("fullscreen.php?id=<?php print($id);?>"), "ha_fullscreen",
1702 "toolbar=no,menubar=no,personalbar=no,width=800,height=600," +
1703 "scrollbars=no,resizable=yes");
1708 if (this
._customUndo
)
1711 this
._doc
.execCommand(cmdID
, UI
, param
);
1713 case "inserttable": this
._insertTable(); break;
1714 case "insertimage": this
._insertImage(); break;
1715 case "insertsmile": this
._insertSmile(); break;
1716 case "insertchar": this
._insertChar(); break;
1717 case "about" : this
._popupDialog("about.html", null, this
); break;
1718 case "showhelp" : window
.open(_editor_url +
"reference.html", "ha_help"); break;
1720 case "killword": this
._wordClean(); break;
1726 if (this
.config
.killWordOnPaste
)
1728 this
._doc
.execCommand(cmdID
, UI
, param
);
1730 if (HTMLArea
.is_gecko
) {
1731 if (confirm("Unprivileged scripts cannot access Cut/Copy/Paste programatically " +
1732 "for security reasons. Click OK to see a technical note at mozilla.org " +
1733 "which shows you how to allow a script to access the clipboard." +
1734 "\n\nFor more information HOW TO enable Cut/Copy/Paste see moodle -discussion: " +
1735 "\nhttp://moodle.org/mod/forum/discuss.php?d=5880"))
1736 window
.open("http://mozilla.org/editor/midasdemo/securityprefs.html");
1742 var dir
= (cmdID
== "righttoleft") ?
"rtl" : "ltr";
1743 var el
= this
.getParentElement();
1744 while (el
&& !HTMLArea
.isBlockElement(el
))
1747 if (el
.style
.direction
== dir
)
1748 el
.style
.direction
= "";
1750 el
.style
.direction
= dir
;
1753 default: this
._doc
.execCommand(cmdID
, UI
, param
);
1755 this
.updateToolbar();
1759 /** A generic event handler for things that happen in the IFRAME's document.
1760 * This function also handles key bindings. */
1761 HTMLArea
.prototype
._editorEvent
= function(ev
) {
1763 var keyEvent
= (HTMLArea
.is_ie
&& ev
.type
== "keydown") ||
(ev
.type
== "keypress");
1765 for (var i in editor
.plugins
) {
1766 var plugin
= editor
.plugins
[i
].instance
;
1767 if (typeof plugin
.onKeyPress
== "function") plugin
.onKeyPress(ev
);
1770 if (keyEvent
&& ev
.ctrlKey
&& ! ev
.altKey
) {
1773 var key
= String.fromCharCode(HTMLArea
.is_ie ? ev
.keyCode
: ev
.charCode
).toLowerCase();
1778 if (!HTMLArea
.is_ie
) {
1780 sel
= this
._getSelection();
1781 sel
.removeAllRanges();
1782 range
= this
._createRange();
1783 range
.selectNodeContents(this
._doc
.body
);
1784 sel
.addRange(range
);
1785 HTMLArea
._stopEvent(ev
);
1789 // simple key commands follow
1791 case 'b': cmd
= "bold"; break;
1792 case 'i': cmd
= "italic"; break;
1793 case 'u': cmd
= "underline"; break;
1794 case 's': cmd
= "strikethrough"; break;
1795 case 'l': cmd
= "justifyleft"; break;
1796 case 'e': cmd
= "justifycenter"; break;
1797 case 'r': cmd
= "justifyright"; break;
1798 case 'j': cmd
= "justifyfull"; break;
1799 case 'z': cmd
= "undo"; break;
1800 case 'y': cmd
= "redo"; break;
1801 case 'v': cmd
= "paste"; break;
1803 case '0': cmd
= "killword"; break;
1812 cmd
= "formatblock";
1814 if (HTMLArea
.is_ie
) {
1815 value
= "<" + value +
">";
1820 // execute simple command
1821 this
.execCommand(cmd
, false, value
);
1822 HTMLArea
._stopEvent(ev
);
1826 else if (keyEvent) {
1828 switch (ev.keyCode) {
1829 case 13: // KEY enter
1830 // if (HTMLArea.is_ie) {
1831 this.insertHTML("<br />");
1832 HTMLArea._stopEvent(ev);
1838 // update the toolbar state after some time
1839 if (editor
._timerToolbar
) {
1840 clearTimeout(editor
._timerToolbar
);
1842 editor
._timerToolbar
= setTimeout(function() {
1843 editor
.updateToolbar();
1844 editor
._timerToolbar
= null;
1848 // retrieve the HTML
1849 HTMLArea
.prototype
.getHTML
= function() {
1850 switch (this
._editMode
) {
1852 if (!this
.config
.fullPage
) {
1853 return HTMLArea
.getHTML(this
._doc
.body
, false, this
);
1855 return this
.doctype +
"\n" + HTMLArea
.getHTML(this
._doc
.documentElement
, true, this
);
1856 case "textmode" : return this
._textArea
.value
;
1857 default : alert("Mode <" + mode +
"> not defined!");
1862 // retrieve the HTML (fastest version, but uses innerHTML)
1863 HTMLArea
.prototype
.getInnerHTML
= function() {
1864 switch (this
._editMode
) {
1866 if (!this
.config
.fullPage
)
1867 return this
._doc
.body
.innerHTML
;
1869 return this
.doctype +
"\n" + this
._doc
.documentElement
.innerHTML
;
1870 case "textmode" : return this
._textArea
.value
;
1871 default : alert("Mode <" + mode +
"> not defined!");
1876 // completely change the HTML inside
1877 HTMLArea
.prototype
.setHTML
= function(html
) {
1878 switch (this
._editMode
) {
1880 if (!this
.config
.fullPage
)
1881 this
._doc
.body
.innerHTML
= html
;
1883 // this._doc.documentElement.innerHTML = html;
1884 this
._doc
.body
.innerHTML
= html
;
1886 case "textmode" : this
._textArea
.value
= html
; break;
1887 default : alert("Mode <" + mode +
"> not defined!");
1892 // sets the given doctype (useful when config.fullPage is true)
1893 HTMLArea
.prototype
.setDoctype
= function(doctype
) {
1894 this
.doctype
= doctype
;
1897 /***************************************************
1898 * Category: UTILITY FUNCTIONS
1899 ***************************************************/
1901 // browser identification
1903 HTMLArea
.agt
= navigator
.userAgent
.toLowerCase();
1904 HTMLArea
.is_ie
= ((HTMLArea
.agt
.indexOf("msie") != -1) && (HTMLArea
.agt
.indexOf("opera") == -1));
1905 HTMLArea
.is_opera
= (HTMLArea
.agt
.indexOf("opera") != -1);
1906 HTMLArea
.is_mac
= (HTMLArea
.agt
.indexOf("mac") != -1);
1907 HTMLArea
.is_mac_ie
= (HTMLArea
.is_ie
&& HTMLArea
.is_mac
);
1908 HTMLArea
.is_win_ie
= (HTMLArea
.is_ie
&& !HTMLArea
.is_mac
);
1909 HTMLArea
.is_gecko
= (navigator
.product
== "Gecko");
1911 // variable used to pass the object to the popup editor window.
1912 HTMLArea
._object
= null;
1914 // function that returns a clone of the given object
1915 HTMLArea
.cloneObject
= function(obj
) {
1916 var newObj
= new Object;
1918 // check for array objects
1919 if (obj
.constructor
.toString().indexOf("function Array(") == 1) {
1920 newObj
= obj
.constructor();
1923 // check for function objects (as usual, IE is phucked up)
1924 if (obj
.constructor
.toString().indexOf("function Function(") == 1) {
1925 newObj
= obj
; // just copy reference to it
1926 } else for (var n in obj
) {
1928 if (typeof node
== 'object') { newObj
[n
] = HTMLArea
.cloneObject(node
); }
1929 else { newObj
[n
] = node
; }
1935 // FIXME!!! this should return false for IE < 5.5
1936 HTMLArea
.checkSupportedBrowser
= function() {
1937 if (HTMLArea
.is_gecko
) {
1938 if (navigator
.productSub
< 20021201) {
1939 alert("You need at least Mozilla-1.3 Alpha.\n" +
1940 "Sorry, your Gecko is not supported.");
1943 if (navigator
.productSub
< 20030210) {
1944 alert("Mozilla < 1.3 Beta is not supported!\n" +
1945 "I'll try, though, but it might not work.");
1948 return HTMLArea
.is_gecko || HTMLArea
.is_ie
;
1951 // selection & ranges
1953 // returns the current selection object
1954 HTMLArea
.prototype
._getSelection
= function() {
1955 if (HTMLArea
.is_ie
) {
1956 return this
._doc
.selection
;
1958 return this
._iframe
.contentWindow
.getSelection();
1962 // returns a range for the current selection
1963 HTMLArea
.prototype
._createRange
= function(sel
) {
1964 if (HTMLArea
.is_ie
) {
1965 return sel
.createRange();
1968 if (typeof sel
!= "undefined") {
1970 return sel
.getRangeAt(0);
1972 return this
._doc
.createRange();
1975 return this
._doc
.createRange();
1982 HTMLArea
._addEvent
= function(el
, evname
, func
) {
1983 if (HTMLArea
.is_ie
) {
1984 el
.attachEvent("on" + evname
, func
);
1986 el
.addEventListener(evname
, func
, true);
1990 HTMLArea
._addEvents
= function(el
, evs
, func
) {
1991 for (var i in evs
) {
1992 HTMLArea
._addEvent(el
, evs
[i
], func
);
1996 HTMLArea
._removeEvent
= function(el
, evname
, func
) {
1997 if (HTMLArea
.is_ie
) {
1998 el
.detachEvent("on" + evname
, func
);
2000 el
.removeEventListener(evname
, func
, true);
2004 HTMLArea
._removeEvents
= function(el
, evs
, func
) {
2005 for (var i in evs
) {
2006 HTMLArea
._removeEvent(el
, evs
[i
], func
);
2010 HTMLArea
._stopEvent
= function(ev
) {
2011 if (HTMLArea
.is_ie
) {
2012 ev
.cancelBubble
= true;
2013 ev
.returnValue
= false;
2015 ev
.preventDefault();
2016 ev
.stopPropagation();
2020 HTMLArea
._removeClass
= function(el
, className
) {
2021 if (!(el
&& el
.className
)) {
2024 var cls
= el
.className
.split(" ");
2025 var ar
= new Array();
2026 for (var i
= cls
.length
; i
> 0;) {
2027 if (cls
[--i
] != className
) {
2028 ar
[ar
.length
] = cls
[i
];
2031 el
.className
= ar
.join(" ");
2034 HTMLArea
._addClass
= function(el
, className
) {
2035 // remove the class first, if already there
2036 HTMLArea
._removeClass(el
, className
);
2037 el
.className +
= " " + className
;
2040 HTMLArea
._hasClass
= function(el
, className
) {
2041 if (!(el
&& el
.className
)) {
2044 var cls
= el
.className
.split(" ");
2045 for (var i
= cls
.length
; i
> 0;) {
2046 if (cls
[--i
] == className
) {
2053 HTMLArea
.isBlockElement
= function(el
) {
2054 var blockTags
= " body form textarea fieldset ul ol dl li div " +
2055 "p h1 h2 h3 h4 h5 h6 quote pre table thead " +
2056 "tbody tfoot tr td iframe address ";
2057 return (blockTags
.indexOf(" " + el
.tagName
.toLowerCase() +
" ") != -1);
2060 HTMLArea
.needsClosingTag
= function(el
) {
2061 var closingTags
= " head script style div span tr td tbody table em strong font a title ";
2062 return (closingTags
.indexOf(" " + el
.tagName
.toLowerCase() +
" ") != -1);
2065 // performs HTML encoding of some given string
2066 HTMLArea
.htmlEncode
= function(str
) {
2067 // we don't need regexp for that, but.. so be it for now.
2068 str
= str
.replace(/&/ig
, "&");
2069 str
= str
.replace(/</ig
, "<");
2070 str
= str
.replace(/>/ig
, ">");
2071 str
= str
.replace(/\x22/ig
, """);
2072 // \x22 means '"' -- we use hex reprezentation so that we don't disturb
2073 // JS compressors (well, at least mine fails.. ;)
2077 // Retrieves the HTML code from the given node. This is a replacement for
2078 // getting innerHTML, using standard DOM calls.
2079 HTMLArea
.getHTML
= function(root
, outputRoot
, editor
) {
2081 switch (root
.nodeType
) {
2082 case 1: // Node.ELEMENT_NODE
2083 case 11: // Node.DOCUMENT_FRAGMENT_NODE
2086 var root_tag
= (root
.nodeType
== 1) ? root
.tagName
.toLowerCase() : '';
2087 if (HTMLArea
.is_ie
&& root_tag
== "head") {
2091 var save_multiline
= RegExp
.multiline
;
2092 RegExp
.multiline
= true;
2093 var txt
= root
.innerHTML
.replace(HTMLArea
.RE_tagName
, function(str
, p1
, p2
) {
2094 return p1 + p2
.toLowerCase();
2096 RegExp
.multiline
= save_multiline
;
2101 } else if (outputRoot
) {
2102 closed
= (!(root
.hasChildNodes() || HTMLArea
.needsClosingTag(root
)));
2103 html
= "<" + root
.tagName
.toLowerCase();
2104 var attrs
= root
.attributes
;
2105 for (i
= 0; i
< attrs
.length
; ++i
) {
2106 var a
= attrs
.item(i
);
2110 var name
= a
.nodeName
.toLowerCase();
2111 if (/_moz|contenteditable|_msh
/.test(name
)) {
2112 // avoid certain attributes
2116 if (name
!= "style") {
2117 // IE5.5 reports 25 when cellSpacing is
2118 // 1; other values might be doomed too.
2119 // For this reason we extract the
2120 // values directly from the root node.
2121 // I'm starting to HATE JavaScript
2122 // development. Browser differences
2125 // Using Gecko the values of href and src are converted to absolute links
2126 // unless we get them using nodeValue()
2127 if (typeof root
[a
.nodeName
] != "undefined" && name
!= "href" && name
!= "src") {
2128 value
= root
[a
.nodeName
];
2130 value
= a
.nodeValue
;
2131 // IE seems not willing to return the original values - it converts to absolute
2132 // links using a.nodeValue, a.value, a.stringValue, root.getAttribute("href")
2133 // So we have to strip the baseurl manually -/
2134 if (HTMLArea
.is_ie
&& (name
== "href" || name
== "src")) {
2135 value
= editor
.stripBaseURL(value
);
2138 } else { // IE fails to put style in attributes list
2139 // FIXME: cssText reported by IE is UPPERCASE
2140 value
= root
.style
.cssText
;
2142 if (/(_moz|^$
)/.test(value
)) {
2143 // Mozilla reports some special tags
2144 // here; we don't need them.
2147 html +
= " " + name +
'="' + value +
'"';
2149 html +
= closed ?
" />" : ">";
2151 for (i
= root
.firstChild
; i
; i
= i
.nextSibling
) {
2152 html +
= HTMLArea
.getHTML(i
, true, editor
);
2154 if (outputRoot
&& !closed
) {
2155 html +
= "</" + root
.tagName
.toLowerCase() +
">";
2158 case 3: // Node.TEXT_NODE
2159 // If a text node is alone in an element and all spaces, replace it with an non breaking one
2160 // This partially undoes the damage done by moz, which translates ' 's into spaces in the data element
2161 if ( !root
.previousSibling
&& !root
.nextSibling
&& root
.data
.match(/^\s
*$
/i
) ) html
= ' ';
2162 else html
= HTMLArea
.htmlEncode(root
.data
);
2164 case 8: // Node.COMMENT_NODE
2165 html
= "<!--" + root
.data +
"-->";
2166 break; // skip comments, for now.
2171 HTMLArea
.prototype
.stripBaseURL
= function(string) {
2172 var baseurl
= this
.config
.baseURL
;
2174 // IE adds the path to an anchor, converting #anchor
2175 // to path/#anchor which of course needs to be fixed
2176 var index
= string.indexOf("/#")+
1;
2177 if ((index
> 0) && (string.indexOf(baseurl
) > -1)) {
2178 return string.substr(index
);
2180 return string; // Moodle doesn't use the code below because
2181 // Moodle likes to keep absolute links
2183 // strip to last directory in case baseurl points to a file
2184 baseurl
= baseurl
.replace(/[^\
/]+$
/, '');
2185 var basere
= new RegExp(baseurl
);
2186 string = string.replace(basere
, "");
2188 // strip host-part of URL which is added by MSIE to links relative to server root
2189 baseurl
= baseurl
.replace(/^
(https?
:\
/\
/[^\
/]+
)(.*)$
/, '$1');
2190 basere
= new RegExp(baseurl
);
2191 return string.replace(basere
, "");
2194 String.prototype
.trim
= function() {
2195 a
= this
.replace(/^\s+
/, '');
2196 return a
.replace(/\s+$
/, '');
2199 // creates a rgb-style color from a number
2200 HTMLArea
._makeColor
= function(v
) {
2201 if (typeof v
!= "number") {
2202 // already in rgb (hopefully); IE doesn't get here.
2205 // IE sends number; convert to rgb.
2207 var g
= (v
>> 8) & 0xFF;
2208 var b
= (v
>> 16) & 0xFF;
2209 return "rgb(" + r +
"," + g +
"," + b +
")";
2212 // returns hexadecimal color representation from a number or a rgb-style color.
2213 HTMLArea
._colorToRgb
= function(v
) {
2217 // returns the hex representation of one byte (2 digits)
2219 return (d
< 16) ?
("0" + d
.toString(16)) : d
.toString(16);
2222 if (typeof v
== "number") {
2223 // we're talking to IE here
2225 var g
= (v
>> 8) & 0xFF;
2226 var b
= (v
>> 16) & 0xFF;
2227 return "#" +
hex(r
) +
hex(g
) +
hex(b
);
2230 if (v
.substr(0, 3) == "rgb") {
2231 // in rgb(...) form -- Mozilla
2232 var re
= /rgb\s
*\
(\s
*([0-9]+
)\s
*,\s
*([0-9]+
)\s
*,\s
*([0-9]+
)\s
*\
)/;
2234 var r
= parseInt(RegExp
.$1);
2235 var g
= parseInt(RegExp
.$2);
2236 var b
= parseInt(RegExp
.$3);
2237 return "#" +
hex(r
) +
hex(g
) +
hex(b
);
2239 // doesn't match RE?! maybe uses percentages or float numbers
2240 // -- FIXME: not yet implemented.
2244 if (v
.substr(0, 1) == "#") {
2245 // already hex rgb (hopefully :D )
2249 // if everything else fails ;)
2253 // modal dialogs for Mozilla (for IE we're using the showModalDialog() call).
2255 // receives an URL to the popup dialog and a function that receives one value;
2256 // this function will get called after the dialog is closed, with the return
2257 // value of the dialog.
2258 HTMLArea
.prototype
._popupDialog
= function(url
, action
, init
) {
2259 Dialog(this
.popupURL(url
), action
, init
);
2264 HTMLArea
.prototype
.imgURL
= function(file
, plugin
) {
2265 if (typeof plugin
== "undefined")
2266 return _editor_url + file
;
2268 return _editor_url +
"plugins/" + plugin +
"/img/" + file
;
2271 HTMLArea
.prototype
.popupURL
= function(file
) {
2273 if (file
.match(/^plugin
:\
/\
/(.*?
)\
/(.*)/)) {
2274 var plugin
= RegExp
.$1;
2275 var popup
= RegExp
.$2;
2276 if (!/\
.html$
/.test(popup
))
2278 url
= _editor_url +
"plugins/" + plugin +
"/popups/" + popup
;
2280 url
= _editor_url + this
.config
.popupURL + file
;
2285 * FIX: Internet Explorer returns an item having the _name_ equal to the given
2286 * id, even if it's not having any id. This way it can return a different form
2287 * field even if it's not a textarea. This workarounds the problem by
2288 * specifically looking to search only elements having a certain tag name.
2290 HTMLArea
.getElementById
= function(tag
, id
) {
2291 var el
, i
, objs
= document
.getElementsByTagName(tag
);
2292 for (i
= objs
.length
; --i
>= 0 && (el
= objs
[i
]);)
2301 // Local variables: //
2302 // c-basic-offset:8 //
2303 // indent-tabs-mode:t //