a4484d57caec7af0e33ee448916f2e4c13e99f51
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / htmlarea.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2002-2004, interactivetools.com, inc.
5 * (c) 2003-2004 dynarch.com
6 * (c) 2004-2008 Stanislas Rolland <typo3(arobas)sjbr.ca>
7 * All rights reserved
8 *
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 * A copy is found in the textfile GPL.txt and important notices to the license
18 * from the author is found in LICENSE.txt distributed with these scripts.
19 *
20 *
21 * This script is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * This script is a modified version of a script published under the htmlArea License.
27 * A copy of the htmlArea License may be found in the textfile HTMLAREA_LICENSE.txt.
28 *
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
31 /*
32 * Main script of TYPO3 htmlArea RTE
33 *
34 * TYPO3 SVN ID: $Id$
35 */
36
37 /***************************************************
38 * EDITOR INITIALIZATION AND CONFIGURATION
39 ***************************************************/
40
41 /*
42 * Set some basic paths
43 */
44 if (typeof(_editor_url) == "string") {
45 // Leave exactly one backslash at the end of _editor_url
46 _editor_url = _editor_url.replace(/\x2f*$/, '/');
47 } else {
48 alert("WARNING: _editor_url is not set!");
49 var _editor_url = '';
50 }
51 if (typeof(_editor_skin) == "string") _editor_skin = _editor_skin.replace(/\x2f*$/, '/');
52 else var _editor_skin = _editor_url + "skins/default/";
53 if (typeof(_editor_CSS) != "string") var _editor_CSS = _editor_url + "skins/default/htmlarea.css";
54 if (typeof(_editor_edited_content_CSS) != "string") var _editor_edited_content_CSS = _editor_skin + "htmlarea-edited-content.css";
55 if (typeof(_editor_lang) == "string") _editor_lang = _editor_lang ? _editor_lang.toLowerCase() : "en";
56
57 /*
58 * HTMLArea object constructor.
59 */
60 var HTMLArea = function(textarea, config) {
61 if (HTMLArea.checkSupportedBrowser()) {
62 if (typeof(config) == "undefined") this.config = new HTMLArea.Config();
63 else this.config = config;
64 this._htmlArea = null;
65 this._textArea = textarea;
66 this._editMode = "wysiwyg";
67 this.plugins = {};
68 this._timerToolbar = null;
69 this.doctype = '';
70 this.eventHandlers = {};
71 }
72 };
73
74 HTMLArea.editorCSS = _editor_CSS;
75
76 /*
77 * Browser identification
78 */
79 HTMLArea.agt = navigator.userAgent.toLowerCase();
80 HTMLArea.is_opera = (HTMLArea.agt.indexOf("opera") != -1);
81 HTMLArea.is_ie = (HTMLArea.agt.indexOf("msie") != -1) && !HTMLArea.is_opera;
82 HTMLArea.is_safari = (HTMLArea.agt.indexOf("webkit") != -1);
83 HTMLArea.is_gecko = (navigator.product == "Gecko") || HTMLArea.is_opera;
84 HTMLArea.is_ff2 = (HTMLArea.agt.indexOf("firefox/2") != -1);
85 HTMLArea.is_chrome = HTMLArea.is_safari && (HTMLArea.agt.indexOf("chrome") != -1);
86 // Check on MacOS Wamcom version 1.3 but exclude Firefox rv 1.8.1.3
87 HTMLArea.is_wamcom = (HTMLArea.agt.indexOf("wamcom") != -1) || (HTMLArea.is_gecko && HTMLArea.agt.indexOf("1.3") != -1 && HTMLArea.agt.indexOf(".1.3") == -1);
88
89 /*
90 * A log for troubleshooting
91 */
92 HTMLArea._debugMode = false;
93 if (typeof(_editor_debug_mode) != "undefined") HTMLArea._debugMode = _editor_debug_mode;
94
95 HTMLArea._appendToLog = function(str){
96 if(HTMLArea._debugMode) {
97 var log = document.getElementById("HTMLAreaLog");
98 if(log) {
99 log.appendChild(document.createTextNode(str));
100 log.appendChild(document.createElement("br"));
101 }
102 }
103 };
104
105 /*
106 * Using compressed scripts
107 */
108 HTMLArea._compressedScripts = false;
109 if (typeof(_editor_compressed_scripts) != "undefined") HTMLArea._compressedScripts = _editor_compressed_scripts;
110
111 /*
112 * Localization of core script
113 */
114 HTMLArea.I18N = HTMLArea_langArray;
115
116 /*
117 * Build array of scripts to be loaded
118 */
119 HTMLArea.is_loaded = false;
120 HTMLArea.onload = function(){
121 HTMLArea.is_loaded = true;
122 HTMLArea._appendToLog("All scripts successfully loaded.");
123 };
124 HTMLArea.loadTimer;
125 HTMLArea._scripts = [];
126 HTMLArea._scriptLoaded = [];
127 HTMLArea._request = [];
128 HTMLArea.loadScript = function(url, pluginName, asynchronous) {
129 if (typeof(pluginName) == "undefined") {
130 var pluginName = "";
131 }
132 if (typeof(asynchronous) == "undefined") {
133 var asynchronous = true;
134 }
135 if (HTMLArea.is_opera) url = _typo3_host_url + url;
136 if (HTMLArea._compressedScripts && url.indexOf("compressed") == -1) url = url.replace(/\.js$/gi, "_compressed.js");
137 var scriptInfo = {
138 pluginName : pluginName,
139 url : url,
140 asynchronous : asynchronous
141 };
142 HTMLArea._scripts.push(scriptInfo);
143 };
144 if(HTMLArea.is_gecko) HTMLArea.loadScript(RTEarea[0]["htmlarea-gecko"] ? RTEarea[0]["htmlarea-gecko"] : _editor_url + "htmlarea-gecko.js");
145 if(HTMLArea.is_ie) HTMLArea.loadScript(RTEarea[0]["htmlarea-ie"] ? RTEarea[0]["htmlarea-ie"] : _editor_url + "htmlarea-ie.js");
146
147 /*
148 * Get a script using asynchronous XMLHttpRequest
149 */
150 HTMLArea.MSXML_XMLHTTP_PROGIDS = new Array("Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP");
151 HTMLArea.XMLHTTPResponseHandler = function (i) {
152 return (function() {
153 var url = HTMLArea._scripts[i].url;
154 if (HTMLArea._request[i].readyState != 4) return;
155 if (HTMLArea._request[i].status == 200) {
156 try {
157 eval(HTMLArea._request[i].responseText);
158 HTMLArea._scriptLoaded[i] = true;
159 i = null;
160 } catch (e) {
161 HTMLArea._appendToLog("ERROR [HTMLArea::getScript]: Unable to get script " + url + ": " + e);
162 }
163 } else {
164 HTMLArea._appendToLog("ERROR [HTMLArea::getScript]: Unable to get " + url + " . Server reported " + HTMLArea._request[i].status);
165 }
166 });
167 };
168 HTMLArea._getScript = function (i,asynchronous,url) {
169 if (typeof(url) == "undefined") var url = HTMLArea._scripts[i].url;
170 if (typeof(asynchronous) == "undefined") var asynchronous = HTMLArea._scripts[i].asynchronous;
171 if (window.XMLHttpRequest) HTMLArea._request[i] = new XMLHttpRequest();
172 else if (window.ActiveXObject) {
173 var success = false;
174 for (var k = 0; k < HTMLArea.MSXML_XMLHTTP_PROGIDS.length && !success; k++) {
175 try {
176 HTMLArea._request[i] = new ActiveXObject(HTMLArea.MSXML_XMLHTTP_PROGIDS[k]);
177 success = true;
178 } catch (e) { }
179 }
180 if (!success) return false;
181 }
182 var request = HTMLArea._request[i];
183 if (request) {
184 request.open("GET", url, asynchronous);
185 if (asynchronous) request.onreadystatechange = HTMLArea.XMLHTTPResponseHandler(i);
186 if (window.XMLHttpRequest) request.send(null);
187 else if (window.ActiveXObject) request.send();
188 if (!asynchronous) {
189 if (request.status == 200) return request.responseText;
190 else return '';
191 }
192 return true;
193 } else {
194 return false;
195 }
196 };
197
198 /*
199 * Wait for the loading process to complete
200 */
201 HTMLArea.checkInitialLoad = function() {
202 var scriptsLoaded = true;
203 for (var i = HTMLArea._scripts.length; --i >= 0;) {
204 scriptsLoaded = scriptsLoaded && HTMLArea._scriptLoaded[i];
205 }
206 if(HTMLArea.loadTimer) window.clearTimeout(HTMLArea.loadTimer);
207 if (scriptsLoaded) {
208 HTMLArea.is_loaded = true;
209 HTMLArea._appendToLog("[HTMLArea::init]: All scripts successfully loaded.");
210 HTMLArea._appendToLog("[HTMLArea::init]: Editor url set to: " + _editor_url);
211 HTMLArea._appendToLog("[HTMLArea::init]: Editor skin CSS set to: " + _editor_CSS);
212 HTMLArea._appendToLog("[HTMLArea::init]: Editor content skin CSS set to: " + _editor_edited_content_CSS);
213 if (window.ActiveXObject) {
214 for (var i = HTMLArea._scripts.length; --i >= 0;) {
215 HTMLArea._request[i].onreadystatechange = new Function();
216 HTMLArea._request[i] = null;
217 }
218 }
219 } else {
220 HTMLArea.loadTimer = window.setTimeout("HTMLArea.checkInitialLoad();", 200);
221 return false;
222 }
223 };
224
225 /*
226 * Get all the scripts
227 */
228 HTMLArea.init = function() {
229 HTMLArea._eventCache = HTMLArea._eventCacheConstructor();
230 if (window.XMLHttpRequest || window.ActiveXObject) {
231 try {
232 var success = true;
233 for (var i = 0, n = HTMLArea._scripts.length; i < n && success; i++) {
234 if (HTMLArea._scripts[i].asynchronous) {
235 success = success && HTMLArea._getScript(i);
236 } else {
237 try {
238 eval(HTMLArea._getScript(i));
239 HTMLArea._scriptLoaded[i] = true;
240 } catch (e) {
241 HTMLArea._appendToLog("ERROR [HTMLArea::getScript]: Unable to get script " + url + ": " + e);
242 }
243 }
244 }
245 } catch (e) {
246 HTMLArea._appendToLog("ERROR [HTMLArea::init]: Unable to use XMLHttpRequest: "+ e);
247 }
248 if (success) {
249 HTMLArea.checkInitialLoad();
250 } else {
251 if (HTMLArea.is_ie) window.setTimeout('if (window.document.getElementById("pleasewait1")) { window.document.getElementById("pleasewait1").innerHTML = HTMLArea.I18N.msg["ActiveX-required"]; } else { alert(HTMLArea.I18N.msg["ActiveX-required"]); };', 200);
252 }
253 } else {
254 if (HTMLArea.is_ie) alert(HTMLArea.I18N.msg["ActiveX-required"]);
255 }
256 };
257
258 /*
259 * Compile some regular expressions
260 */
261 HTMLArea.RE_tagName = /(<\/|<)\s*([^ \t\n>]+)/ig;
262 HTMLArea.RE_doctype = /(<!doctype((.|\n)*?)>)\n?/i;
263 HTMLArea.RE_head = /<head>((.|\n)*?)<\/head>/i;
264 HTMLArea.RE_body = /<body>((.|\n)*?)<\/body>/i;
265 HTMLArea.Reg_body = new RegExp("<\/?(body)[^>]*>", "gi");
266 HTMLArea.reservedClassNames = /htmlarea/;
267 HTMLArea.RE_email = /([0-9a-z]+([a-z0-9_-]*[0-9a-z])*){1}(\.[0-9a-z]+([a-z0-9_-]*[0-9a-z])*)*@([0-9a-z]+([a-z0-9_-]*[0-9a-z])*\.)+[a-z]{2,9}/i;
268 HTMLArea.RE_url = /(https?:\/\/)?(([a-z0-9_]+:[a-z0-9_]+@)?[a-z0-9_-]{2,}(\.[a-z0-9_-]{2,})+\.[a-z]{2,5}(:[0-9]+)?(\/\S+)*)/i;
269
270 /*
271 * Editor configuration object constructor
272 */
273
274 HTMLArea.Config = function () {
275 this.width = "auto";
276 this.height = "auto";
277 // enable creation of a status bar?
278 this.statusBar = true;
279 // whether the toolbar should be included in the size or not.
280 this.sizeIncludesToolbar = true;
281 // if true then HTMLArea will retrieve the full HTML, starting with the <HTML> tag.
282 this.fullPage = false;
283 // if the site is secure, create a secure iframe
284 this.useHTTPS = false;
285 // for Mozilla
286 this.useCSS = false;
287 this.enableMozillaExtension = true;
288 this.disableEnterParagraphs = false;
289 this.disableObjectResizing = false;
290 this.removeTrailingBR = false;
291 // style included in the iframe document
292 this.editedContentStyle = _editor_edited_content_CSS;
293 // content style
294 this.pageStyle = "";
295 // remove tags (these have to be a regexp, or null if this functionality is not desired)
296 this.htmlRemoveTags = null;
297 // remove tags and any contents (these have to be a regexp, or null if this functionality is not desired)
298 this.htmlRemoveTagsAndContents = null;
299 // remove comments
300 this.htmlRemoveComments = false;
301 // custom tags (these have to be a regexp, or null if this functionality is not desired)
302 this.customTags = null;
303 // BaseURL included in the iframe document
304 this.baseURL = document.baseURI || document.URL;
305 if(this.baseURL && this.baseURL.match(/(.*)\/([^\/]+)/)) this.baseURL = RegExp.$1 + "/";
306 // URL-s
307 this.imgURL = "images/";
308 this.popupURL = "popups/";
309 // DocumentType
310 this.documentType = '<!DOCTYPE html\r'
311 + ' PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r'
312 + ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r';
313
314 this.btnList = {
315 InsertHorizontalRule: ["Horizontal Rule", "ed_hr.gif",false, function(editor) {editor.execCommand("InsertHorizontalRule");}],
316 HtmlMode: ["Toggle HTML Source", "ed_html.gif", true, function(editor) {editor.execCommand("HtmlMode");}],
317 SelectAll: ["SelectAll", "", true, function(editor) {editor.execCommand("SelectAll");}, null, true, false]
318 };
319 // Default hotkeys
320 this.hotKeyList = {
321 a: { cmd: "SelectAll", action: null}
322 };
323
324 // Initialize tooltips from the I18N module, generate correct image path
325 for (var buttonId in this.btnList) {
326 if (this.btnList.hasOwnProperty(buttonId)) {
327 var btn = this.btnList[buttonId];
328 if (typeof(HTMLArea.I18N.tooltips[buttonId.toLowerCase()]) !== "undefined") {
329 btn[0] = HTMLArea.I18N.tooltips[buttonId.toLowerCase()];
330 }
331 if (typeof(btn[1]) === "string") {
332 btn[1] = _editor_skin + this.imgURL + btn[1];
333 } else {
334 btn[1][0] = _editor_skin + this.imgURL + btn[1][0];
335 }
336 }
337 }
338 this.customSelects = {};
339 };
340
341 /*
342 * Register a new button with the configuration.
343 * It can be called with all arguments, or with only one (first one). When called with
344 * only one argument it must be an object with the following properties:
345 * id, tooltip, image, textMode, action, context. Examples:
346 *
347 * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...}, context);
348 * 2. config.registerButton({
349 * id : "my-hilite", // Unique id for the button
350 * tooltip : "Hilite text", // the tooltip
351 * image : "my-hilite.gif", // image to be displayed in the toolbar
352 * textMode : false, // disabled in text mode
353 * action : function(editor) { // called when the button is clicked
354 * editor.surroundHTML('<span class="hilite">', '</span>');
355 * },
356 * context : "p" // will be disabled if not inside a <p> element
357 * hide : false // hide in menu and show only in context menu
358 * selection : false // will be disabled if there is no selection
359 * dialog : true // the button opens a dialogue
360 * dimensions : { width: nn, height: mm } // opening dimensions of the dialogue window
361 * });
362 */
363 HTMLArea.Config.prototype.registerButton = function(id,tooltip,image,textMode,action,context,hide,selection, dialog, dimensions) {
364 var buttonId;
365 switch (typeof(id)) {
366 case "string": buttonId = id; break;
367 case "object": buttonId = id.id; break;
368 default: HTMLArea._appendToLog("[HTMLArea.Config::registerButton]: invalid arguments");
369 return false;
370 }
371 if (typeof(this.customSelects[buttonId]) !== "undefined") {
372 HTMLArea._appendToLog("[HTMLArea.Config::registerButton]: A dropdown with the same Id: " + buttonId + " already exists.");
373 return false;
374 }
375 if (typeof(this.btnList[buttonId]) !== "undefined") {
376 HTMLArea._appendToLog("[HTMLArea.Config::registerButton]: A button with the same Id: " + buttonId + " already exists and will be overidden.");
377 }
378 switch (typeof(id)) {
379 case "string":
380 if (typeof(hide) === "undefined") var hide = false;
381 if (typeof(selection) === "undefined") var selection = false;
382 if (typeof(dialog) === "undefined") var dialog = true;
383 this.btnList[id] = [tooltip, image, textMode, action, context, hide, selection, dialog, dimensions];
384 break;
385 case "object":
386 if (typeof(id.hide) === "undefined") id.hide = false;
387 if (typeof(id.selection) === "undefined") id.selection = false;
388 if (typeof(id.dialog) === "undefined") id.dialog = true;
389 this.btnList[id.id] = [id.tooltip, id.image, id.textMode, id.action, id.context, id.hide, id.selection, id.dialog, id.dimensions];
390 break;
391 }
392 return true;
393 };
394
395 /*
396 * Register a dropdown box with the editor configuration.
397 */
398 HTMLArea.Config.prototype.registerDropdown = function(dropDownConfiguration) {
399 if (typeof(this.customSelects[dropDownConfiguration.id]) != "undefined") {
400 HTMLArea._appendToLog("[HTMLArea.Config::registerDropdown]: A dropdown with the same ID " + dropDownConfiguration.id + " already exists and will be overidden.");
401 }
402 if (typeof(this.btnList[dropDownConfiguration.id]) != "undefined") {
403 HTMLArea._appendToLog("ERROR [HTMLArea.Config::registerDropdown]: A button with the same ID " + dropDownConfiguration.id + " already exists.");
404 return false;
405 }
406 this.customSelects[dropDownConfiguration.id] = dropDownConfiguration;
407 return true;
408 };
409
410 /*
411 * Register a hotkey with the editor configuration.
412 */
413 HTMLArea.Config.prototype.registerHotKey = function(hotKeyConfiguration) {
414 if (typeof(this.hotKeyList[hotKeyConfiguration.id]) != "undefined") {
415 HTMLArea._appendToLog("[HTMLArea.Config::registerHotKey]: A hotkey with the same key " + hotKeyConfiguration.id + " already exists and will be overidden.");
416 }
417 this.hotKeyList[hotKeyConfiguration.id] = hotKeyConfiguration;
418 return true;
419 };
420
421 HTMLArea.Config.prototype.getDocumentType = function () {
422 return this.documentType;
423 };
424
425 /***************************************************
426 * EDITOR FRAMEWORK
427 ***************************************************/
428 /*
429 * Update the state of a toolbar element.
430 * This function is member of a toolbar element object, unnamed object created by createButton or createSelect functions.
431 */
432 HTMLArea.setButtonStatus = function(id,newval) {
433 var oldval = this[id];
434 var el = document.getElementById(this.elementId);
435 if (oldval != newval) {
436 switch (id) {
437 case "enabled":
438 if (newval) {
439 if (!HTMLArea.is_wamcom) {
440 HTMLArea._removeClass(el, "buttonDisabled");
441 HTMLArea._removeClass(el.parentNode, "buttonDisabled");
442 }
443 el.disabled = false;
444 } else {
445 if (!HTMLArea.is_wamcom) {
446 HTMLArea._addClass(el, "buttonDisabled");
447 HTMLArea._addClass(el.parentNode, "buttonDisabled");
448 }
449 el.disabled = true;
450 }
451 break;
452 case "active":
453 if (newval) {
454 HTMLArea._addClass(el, "buttonPressed");
455 HTMLArea._addClass(el.parentNode, "buttonPressed");
456 el.active = true;
457 } else {
458 HTMLArea._removeClass(el, "buttonPressed");
459 HTMLArea._removeClass(el.parentNode, "buttonPressed");
460 el.active = false;
461 }
462 break;
463 }
464 this[id] = newval;
465 }
466 };
467
468 /*
469 * Create a new line in the toolbar
470 */
471 HTMLArea.newLine = function(toolbar) {
472 tb_line = document.createElement("ul");
473 tb_line.className = "tb-line";
474 toolbar.appendChild(tb_line);
475 return tb_line;
476 };
477
478 /*
479 * Add a toolbar element to the current line or group
480 */
481 HTMLArea.addTbElement = function(element, tb_line, first_cell_on_line) {
482 var tb_cell = document.createElement("li");
483 if (first_cell_on_line) tb_cell.className = "tb-first-cell";
484 else tb_cell.className = "tb-cell";
485 HTMLArea._addClass(tb_cell, element.className);
486 tb_line.appendChild(tb_cell);
487 tb_cell.appendChild(element);
488 if(element.style.display == "none") {
489 tb_cell.style.display = "none";
490 if(HTMLArea._hasClass(tb_cell.previousSibling, "separator")) tb_cell.previousSibling.style.display = "none";
491 }
492 return false;
493 };
494
495 /*
496 * Create a new group on the current line
497 */
498 HTMLArea.addTbGroup = function(tb_line, first_cell_on_line) {
499 var tb_group = document.createElement("ul");
500 tb_group.className = "tb-group";
501 HTMLArea.addTbElement(tb_group, tb_line, first_cell_on_line);
502 return tb_group;
503 };
504
505 /*
506 * Create a combo box and add it to the toolbar
507 */
508 HTMLArea.prototype.createSelect = function(txt,tb_line,first_cell_on_line,labelObj) {
509 var options = null,
510 cmd = null,
511 context = null,
512 tooltip = "",
513 newObj = {
514 created : false,
515 el : null,
516 first : first_cell_on_line,
517 labelUsed : false
518 };
519
520 cmd = txt;
521 var dropdown = this.config.customSelects[cmd];
522 if (typeof(dropdown) != "undefined") {
523 options = dropdown.options;
524 context = dropdown.context;
525 if (typeof(dropdown.tooltip) != "undefined") tooltip = dropdown.tooltip;
526 }
527 if (options) {
528 newObj["el"] = document.createElement("select");
529 newObj["el"].className = "select";
530 newObj["el"].title = tooltip;
531 newObj["el"].id = this._editorNumber + "-" + txt;
532 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
533 var obj = {
534 name : txt, // field name
535 elementId : newObj["el"].id, // unique id for the UI element
536 enabled : true, // is it enabled?
537 text : false, // enabled in text mode?
538 cmd : cmd, // command ID
539 state : HTMLArea.setButtonStatus, // for changing state
540 context : context,
541 editorNumber : this._editorNumber
542 };
543 this._toolbarObjects[txt] = obj;
544 newObj["el"]._obj = obj;
545 if (labelObj["labelRef"]) {
546 labelObj["el"].htmlFor = newObj["el"].id;
547 newObj["labelUsed"] = true;
548 }
549 HTMLArea._addEvent(newObj["el"], "change", HTMLArea.toolBarButtonHandler);
550
551 for (var i in options) {
552 if (options.hasOwnProperty(i)) {
553 var op = document.createElement("option");
554 op.innerHTML = i;
555 op.value = options[i];
556 newObj["el"].appendChild(op);
557 }
558 }
559
560 newObj["created"] = true;
561 }
562
563 return newObj;
564 };
565
566 /*
567 * Create a button and add it to the toolbar
568 */
569 HTMLArea.prototype.createButton = function (txt,tb_line,first_cell_on_line,labelObj) {
570 var btn = null,
571 newObj = {
572 created : false,
573 el : null,
574 first : first_cell_on_line,
575 labelUsed : false
576 };
577
578 switch (txt) {
579 case "separator":
580 newObj["el"] = document.createElement("div");
581 newObj["el"].className = "separator";
582 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
583 newObj["created"] = true;
584 break;
585 case "space":
586 newObj["el"] = document.createElement("div");
587 newObj["el"].className = "space";
588 newObj["el"].innerHTML = "&nbsp;";
589 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
590 newObj["created"] = true;
591 break;
592 case "TextIndicator":
593 newObj["el"] = document.createElement("div");
594 newObj["el"].appendChild(document.createTextNode("A"));
595 newObj["el"].className = "indicator";
596 newObj["el"].title = HTMLArea.I18N.tooltips.textindicator;
597 newObj["el"].id = this._editorNumber + "-" + txt;
598 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
599 var obj = {
600 name : txt,
601 elementId : newObj["el"].id,
602 enabled : true,
603 active : false,
604 text : false,
605 cmd : "TextIndicator",
606 state : HTMLArea.setButtonStatus
607 };
608 this._toolbarObjects[txt] = obj;
609 newObj["created"] = true;
610 break;
611 default:
612 btn = this.config.btnList[txt];
613 }
614 if(!newObj["created"] && btn) {
615 newObj["el"] = document.createElement("button");
616 newObj["el"].title = btn[0];
617 newObj["el"].className = "button";
618 newObj["el"].id = this._editorNumber + "-" + txt;
619 if (btn[5]) newObj["el"].style.display = "none";
620 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
621 var obj = {
622 name : txt, // the button name
623 elementId : newObj["el"].id, // unique id for the UI element
624 enabled : true, // is it enabled?
625 active : false, // is it pressed?
626 text : btn[2], // enabled in text mode?
627 cmd : btn[3], // the function to be invoked
628 state : HTMLArea.setButtonStatus, // for changing state
629 context : btn[4] || null, // enabled in a certain context?
630 selection : btn[6], // disabled when no selection?
631 editorNumber : this._editorNumber
632 };
633 this._toolbarObjects[txt] = obj;
634 newObj["el"]._obj = obj;
635 if (labelObj["labelRef"]) {
636 labelObj["el"].htmlFor = newObj["el"].id;
637 newObj["labelUsed"] = true;
638 }
639 HTMLArea._addEvents(newObj["el"],["mouseover", "mouseout", "mousedown", "click"], HTMLArea.toolBarButtonHandler);
640 newObj["el"].className += " " + txt;
641 newObj["created"] = true;
642 }
643 return newObj;
644 };
645
646 /*
647 * Create a label and add it to the toolbar
648 */
649 HTMLArea.createLabel = function(txt,tb_line,first_cell_on_line) {
650 var newObj = {
651 created : false,
652 el : null,
653 labelRef : false,
654 first : first_cell_on_line
655 };
656 if (/^([IT])\[(.*?)\]/.test(txt)) {
657 var l7ed = RegExp.$1 == "I"; // localized?
658 var label = RegExp.$2;
659 if (l7ed) label = HTMLArea.I18N.dialogs[label];
660 newObj["el"] = document.createElement("label");
661 newObj["el"].className = "label";
662 newObj["el"].innerHTML = label;
663 newObj["labelRef"] = true;
664 newObj["created"] = true;
665 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
666 }
667 return newObj;
668 };
669
670 /*
671 * Create the toolbar and append it to the _htmlarea.
672 */
673 HTMLArea.prototype._createToolbar = function () {
674 var j, k, code, n = this.config.toolbar.length, m,
675 tb_line = null, tb_group = null,
676 first_cell_on_line = true,
677 labelObj = new Object(),
678 tbObj = new Object();
679
680 var toolbar = document.createElement("div");
681 this._toolbar = toolbar;
682 toolbar.className = "toolbar";
683 toolbar.unselectable = "1";
684 this._toolbarObjects = new Object();
685
686 for (j = 0; j < n; ++j) {
687 tb_line = HTMLArea.newLine(toolbar);
688 if(!this.config.keepButtonGroupTogether) HTMLArea._addClass(tb_line, "free-float");
689 first_cell_on_line = true;
690 tb_group = null;
691 var group = this.config.toolbar[j];
692 m = group.length;
693 for (k = 0; k < m; ++k) {
694 code = group[k];
695 if (code == "linebreak") {
696 tb_line = HTMLArea.newLine(toolbar);
697 if(!this.config.keepButtonGroupTogether) HTMLArea._addClass(tb_line, "free-float");
698 first_cell_on_line = true;
699 tb_group = null;
700 } else {
701 if ((code == "separator" || first_cell_on_line) && this.config.keepButtonGroupTogether) {
702 tb_group = HTMLArea.addTbGroup(tb_line, first_cell_on_line);
703 first_cell_on_line = false;
704 }
705 created = false;
706 if (/^([IT])\[(.*?)\]/.test(code)) {
707 labelObj = HTMLArea.createLabel(code, (tb_group?tb_group:tb_line), first_cell_on_line);
708 created = labelObj["created"] ;
709 first_cell_on_line = labelObj["first"];
710 }
711 if (!created) {
712 tbObj = this.createButton(code, (tb_group?tb_group:tb_line), first_cell_on_line, labelObj);
713 created = tbObj["created"];
714 first_cell_on_line = tbObj["first"];
715 if(tbObj["labelUsed"]) labelObj["labelRef"] = false;
716 }
717 if (!created) {
718 tbObj = this.createSelect(code, (tb_group?tb_group:tb_line), first_cell_on_line, labelObj);
719 created = tbObj["created"];
720 first_cell_on_line = tbObj["first"];
721 if(tbObj["labelUsed"]) labelObj["labelRef"] = false;
722 }
723 if (!created) HTMLArea._appendToLog("ERROR [HTMLArea::createToolbar]: Unknown toolbar item: " + code);
724 }
725 }
726 }
727
728 tb_line = HTMLArea.newLine(toolbar);
729 this._htmlArea.appendChild(toolbar);
730 };
731
732 /*
733 * Handle toolbar element events handler
734 */
735 HTMLArea.toolBarButtonHandler = function(ev) {
736 if(!ev) var ev = window.event;
737 var target = (ev.target) ? ev.target : ev.srcElement;
738 while (target.tagName.toLowerCase() == "img" || target.tagName.toLowerCase() == "div") target = target.parentNode;
739 var obj = target._obj;
740 var editorNumber = obj["editorNumber"];
741 var editor = RTEarea[editorNumber]["editor"];
742 if (obj.enabled) {
743 switch (ev.type) {
744 case "mouseover":
745 HTMLArea._addClass(target, "buttonHover");
746 HTMLArea._addClass(target.parentNode, "buttonHover");
747 break;
748 case "mouseout":
749 HTMLArea._removeClass(target, "buttonHover");
750 HTMLArea._removeClass(target.parentNode, "buttonHover");
751 HTMLArea._removeClass(target, "buttonActive");
752 HTMLArea._removeClass(target.parentNode, "buttonActive");
753 if (obj.active) {
754 HTMLArea._addClass(target, "buttonPressed");
755 HTMLArea._addClass(target.parentNode, "buttonPressed");
756 }
757 break;
758 case "mousedown":
759 HTMLArea._addClass(target, "buttonActive");
760 HTMLArea._addClass(target.parentNode, "buttonActive");
761 HTMLArea._removeClass(target, "buttonPressed");
762 HTMLArea._removeClass(target.parentNode, "buttonPressed");
763 HTMLArea._stopEvent(ev);
764 break;
765 case "click":
766 HTMLArea._removeClass(target, "buttonActive");
767 HTMLArea._removeClass(target.parentNode, "buttonActive");
768 HTMLArea._removeClass(target, "buttonHover");
769 HTMLArea._removeClass(target.parentNode, "buttonHover");
770 obj.cmd(editor, obj.name);
771 HTMLArea._stopEvent(ev);
772 if (HTMLArea.is_opera) {
773 editor._iframe.focus();
774 }
775 if (!editor.config.btnList[obj.name][7]) {
776 editor.updateToolbar();
777 }
778 break;
779 case "change":
780 editor.focusEditor();
781 var dropdown = editor.config.customSelects[obj.name];
782 if (typeof(dropdown) !== "undefined") {
783 dropdown.action(editor, obj.name);
784 HTMLArea._stopEvent(ev);
785 if (HTMLArea.is_opera) {
786 editor._iframe.focus();
787 }
788 editor.updateToolbar();
789 } else {
790 HTMLArea._appendToLog("ERROR [HTMLArea::toolBarButtonHandler]: Combo box " + obj.name + " not registered.");
791 }
792 }
793 }
794 };
795
796 /*
797 * Create the status bar
798 */
799 HTMLArea.prototype._createStatusBar = function() {
800 var statusBar = document.createElement("div");
801 this._statusBar = statusBar;
802 statusBar.className = "statusBar";
803 if (!this.config.statusBar) statusBar.style.display = "none";
804 var statusBarTree = document.createElement("span");
805 this._statusBarTree = statusBarTree;
806 statusBarTree.className = "statusBarTree";
807 statusBar.appendChild(statusBarTree);
808 statusBarTree.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
809 this._htmlArea.appendChild(statusBar);
810 };
811
812 /*
813 * Create the htmlArea iframe and replace the textarea with it.
814 */
815 HTMLArea.prototype.generate = function () {
816
817 // get the textarea and hide it
818 var textarea = this._textArea;
819 if (typeof(textarea) == "string") {
820 textarea = HTMLArea.getElementById("textarea", textarea);
821 this._textArea = textarea;
822 }
823 textarea.style.display = "none";
824
825 // create the editor framework and insert the editor before the textarea
826 var htmlarea = document.createElement("div");
827 htmlarea.className = "htmlarea";
828 htmlarea.style.width = textarea.style.width;
829 this._htmlArea = htmlarea;
830 textarea.parentNode.insertBefore(htmlarea, textarea);
831
832 if(textarea.form) {
833 // we have a form, on reset, re-initialize the HTMLArea content and update the toolbar
834 var f = textarea.form;
835 if (typeof(f.onreset) == "function") {
836 var funcref = f.onreset;
837 if (typeof(f.__msh_prevOnReset) == "undefined") f.__msh_prevOnReset = [];
838 f.__msh_prevOnReset.push(funcref);
839 }
840 f._editorNumber = this._editorNumber;
841 HTMLArea._addEvent(f, "reset", HTMLArea.resetHandler);
842 }
843
844 // create & append the toolbar
845 this._createToolbar();
846 HTMLArea._appendToLog("[HTMLArea::generate]: Toolbar successfully created.");
847
848 // create and append the IFRAME
849 var iframe = document.createElement("iframe");
850 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
851 iframe.setAttribute("src", "javascript:void(0);");
852 } else {
853 iframe.setAttribute("src", (HTMLArea.is_opera?_typo3_host_url:"") + _editor_url + "popups/blank.html");
854 }
855 iframe.className = "editorIframe";
856 if (!this.config.statusBar) iframe.className += " noStatusBar";
857 htmlarea.appendChild(iframe);
858 this._iframe = iframe;
859
860 // create & append the status bar
861 this._createStatusBar();
862
863 // size the iframe
864 this.sizeIframe(2);
865
866 HTMLArea._appendToLog("[HTMLArea::generate]: Editor iframe successfully created.");
867 this.initIframe();
868 return this;
869 };
870
871 /*
872 * Size the iframe according to user's prefs or initial textarea
873 */
874 HTMLArea.prototype.sizeIframe = function(diff) {
875 var height = (this.config.height == "auto" ? (this._textArea.style.height) : this.config.height);
876 var textareaHeight = height;
877 // All nested tabs and inline levels in the sorting order they were applied:
878 this.nested = {};
879 this.nested.all = RTEarea[this._editorNumber].tceformsNested;
880 this.nested.sorted = HTMLArea.simplifyNested(this.nested.all);
881 // Clone the array instead of using a reference (this.accessParentElements will change the array):
882 var parentElements = (this.nested.sorted && this.nested.sorted.length ? [].concat(this.nested.sorted) : []);
883 // Walk through all nested tabs and inline levels to make a correct positioning:
884 var dimensions = this.accessParentElements(parentElements, 'this.getDimensions()');
885
886 if(height.indexOf("%") == -1) {
887 height = parseInt(height) - diff;
888 if (this.config.sizeIncludesToolbar) {
889 this._initialToolbarOffsetHeight = dimensions.toolbar.height;
890 height -= dimensions.toolbar.height;
891 height -= dimensions.statusbar.height;
892 }
893 if (height < 0) height = 0;
894 textareaHeight = (height - 4);
895 if (textareaHeight < 0) textareaHeight = 0;
896 height += "px";
897 textareaHeight += "px";
898 }
899 this._iframe.style.height = height;
900 this._textArea.style.height = textareaHeight;
901 var textareaWidth = (this.config.width == "auto" ? this._textArea.style.width : this.config.width);
902 var iframeWidth = textareaWidth;
903 if(textareaWidth.indexOf("%") == -1) {
904 iframeWidth = parseInt(textareaWidth) + "px";
905 textareaWidth = parseInt(textareaWidth) - diff;
906 if (textareaWidth < 0) textareaWidth = 0;
907 textareaWidth += 'px';
908 }
909 this._iframe.style.width = "100%";
910 if (HTMLArea.is_opera) this._iframe.style.width = iframeWidth;
911 this._textArea.style.width = textareaWidth;
912 };
913
914 /**
915 * Get the dimensions of the toolbar and statusbar.
916 *
917 * @return object An object with width/height pairs for statusbar and toolbar.
918 * @author Oliver Hader <oh@inpublica.de>
919 */
920 HTMLArea.prototype.getDimensions = function() {
921 return {
922 toolbar: {width: this._toolbar.offsetWidth, height: this._toolbar.offsetHeight},
923 statusbar: {width: this._statusBar.offsetWidth, height: this._statusBar.offsetHeight}
924 };
925 };
926
927 /**
928 * Access an inline relational element or tab menu and make it "accesible".
929 * If a parent object has the style "display: none", offsetWidth & offsetHeight are '0'.
930 *
931 * @params object callbackFunc: A function to be called, when the embedded objects are "accessible".
932 * @return object An object returned by the callbackFunc.
933 * @author Oliver Hader <oh@inpublica.de>
934 */
935 HTMLArea.prototype.accessParentElements = function(parentElements, callbackFunc) {
936 var result = {};
937
938 if (parentElements.length) {
939 var currentElement = parentElements.pop();
940 var elementStyle = document.getElementById(currentElement).style;
941 var actionRequired = (elementStyle.display == 'none' ? true : false);
942
943 if (actionRequired) {
944 var originalVisibility = elementStyle.visibility;
945 var originalPosition = elementStyle.position;
946 elementStyle.visibility = 'hidden';
947 elementStyle.position = 'absolute';
948 elementStyle.display = '';
949 }
950
951 result = this.accessParentElements(parentElements, callbackFunc);
952
953 if (actionRequired) {
954 elementStyle.display = 'none';
955 elementStyle.position = originalPosition;
956 elementStyle.visibility = originalVisibility;
957 }
958
959 } else {
960 result = eval(callbackFunc);
961
962 }
963
964 return result;
965 };
966
967 /**
968 * Simplify the array of nested levels. Create an indexed array with the correct names of the elements.
969 *
970 * @param object nested: The array with the nested levels
971 * @return object The simplified array
972 * @author Oliver Hader <oh@inpublica.de>
973 */
974 HTMLArea.simplifyNested = function(nested) {
975 var i, type, level, max, simplifiedNested=[];
976 if (nested && nested.length) {
977 if (nested[0][0]=='inline') {
978 nested = inline.findContinuedNestedLevel(nested, nested[0][1]);
979 }
980 for (i=0, max=nested.length; i<max; i++) {
981 type = nested[i][0];
982 level = nested[i][1];
983 if (type=='tab') {
984 simplifiedNested.push(level+'-DIV');
985 } else if (type=='inline') {
986 simplifiedNested.push(level+'_fields');
987 }
988 }
989 }
990 return simplifiedNested;
991 };
992
993 /*
994 * Initialize the iframe
995 */
996 HTMLArea.initIframe = function(editorNumber) {
997 var editor = RTEarea[editorNumber]["editor"];
998 editor.initIframe();
999 };
1000
1001 HTMLArea.prototype.initIframe = function() {
1002 if (this._initIframeTimer) window.clearTimeout(this._initIframeTimer);
1003 if (!this._iframe || (!this._iframe.contentWindow && !this._iframe.contentDocument)) {
1004 this._initIframeTimer = window.setTimeout("HTMLArea.initIframe(" + this._editorNumber + ");", 50);
1005 return false;
1006 } else if (this._iframe.contentWindow && !HTMLArea.is_safari) {
1007 if (!this._iframe.contentWindow.document || !this._iframe.contentWindow.document.documentElement) {
1008 this._initIframeTimer = window.setTimeout("HTMLArea.initIframe(" + this._editorNumber + ");", 50);
1009 return false;
1010 }
1011 } else if (!this._iframe.contentDocument.documentElement || !this._iframe.contentDocument.body) {
1012 this._initIframeTimer = window.setTimeout("HTMLArea.initIframe(" + this._editorNumber + ");", 50);
1013 return false;
1014 }
1015 var doc = this._iframe.contentWindow ? this._iframe.contentWindow.document : this._iframe.contentDocument;
1016 this._doc = doc;
1017 // Set Doc Type in Firefox (doctype is readonly in DOM 2)
1018 // After adding doctype in Firefox, baseURL is set to the url of the parent document,
1019 // base element is ignored, and all created links, including external, are prepended with incorrect base
1020 /*if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
1021 this._doc.open();
1022 this._doc.write(this.config.getDocumentType());
1023 this._doc.close();
1024 }*/
1025 if (!this.config.fullPage) {
1026 var head = doc.getElementsByTagName("head")[0];
1027 if (!head) {
1028 head = doc.createElement("head");
1029 doc.documentElement.appendChild(head);
1030 }
1031 if (this.config.baseURL && !HTMLArea.is_opera) {
1032 var base = doc.getElementsByTagName("base")[0];
1033 if (!base) {
1034 base = doc.createElement("base");
1035 base.href = this.config.baseURL;
1036 head.appendChild(base);
1037 }
1038 HTMLArea._appendToLog("[HTMLArea::initIframe]: Iframe baseURL set to: " + this.config.baseURL);
1039 }
1040 var link0 = doc.getElementsByTagName("link")[0];
1041 if (!link0) {
1042 link0 = doc.createElement("link");
1043 link0.rel = "stylesheet";
1044 link0.href = this.config.editedContentStyle;
1045 head.appendChild(link0);
1046 HTMLArea._appendToLog("[HTMLArea::initIframe]: Skin CSS set to: " + this.config.editedContentStyle);
1047 }
1048 if (this.config.defaultPageStyle) {
1049 var link = doc.getElementsByTagName("link")[1];
1050 if (!link) {
1051 link = doc.createElement("link");
1052 link.rel = "stylesheet";
1053 link.href = this.config.defaultPageStyle;
1054 head.appendChild(link);
1055 }
1056 HTMLArea._appendToLog("[HTMLArea::initIframe]: Override CSS set to: " + this.config.defaultPageStyle);
1057 }
1058 if (this.config.pageStyle) {
1059 var link = doc.getElementsByTagName("link")[2];
1060 if (!link) {
1061 link = doc.createElement("link");
1062 link.rel = "stylesheet";
1063 link.href = this.config.pageStyle;
1064 head.appendChild(link);
1065 }
1066 HTMLArea._appendToLog("[HTMLArea::initIframe]: Content CSS set to: " + this.config.pageStyle);
1067 }
1068 } else {
1069 var html = this._textArea.value;
1070 this.setFullHTML(html);
1071 }
1072 HTMLArea._appendToLog("[HTMLArea::initIframe]: Editor iframe head successfully initialized.");
1073
1074 this.stylesLoaded();
1075 };
1076
1077 /*
1078 * Finalize editor Iframe initialization after loading the style sheets
1079 */
1080 HTMLArea.stylesLoaded = function(editorNumber) {
1081 var editor = RTEarea[editorNumber]["editor"];
1082 editor.stylesLoaded();
1083 };
1084
1085 HTMLArea.prototype.stylesLoaded = function() {
1086 var doc = this._doc;
1087 var docWellFormed = true;
1088
1089 // check if the stylesheets have been loaded
1090
1091 if (this._stylesLoadedTimer) window.clearTimeout(this._stylesLoadedTimer);
1092 var stylesAreLoaded = true;
1093 var errorText = '';
1094 var rules;
1095 for (var rule = 0; rule < doc.styleSheets.length; rule++) {
1096 if (HTMLArea.is_gecko) try { rules = doc.styleSheets[rule].cssRules; } catch(e) { stylesAreLoaded = false; errorText = e; }
1097 if (HTMLArea.is_ie) try { rules = doc.styleSheets[rule].rules; } catch(e) { stylesAreLoaded = false; errorText = e; }
1098 if (HTMLArea.is_ie) try { rules = doc.styleSheets[rule].imports; } catch(e) { stylesAreLoaded = false; errorText = e; }
1099 }
1100 if (!stylesAreLoaded && !HTMLArea.is_wamcom) {
1101 HTMLArea._appendToLog("[HTMLArea::initIframe]: Failed attempt at loading stylesheets: " + errorText + " Retrying...");
1102 this._stylesLoadedTimer = window.setTimeout("HTMLArea.stylesLoaded(" + this._editorNumber + ");", 100);
1103 return false;
1104 }
1105 HTMLArea._appendToLog("[HTMLArea::initIframe]: Stylesheets successfully loaded.");
1106
1107 if (!this.config.fullPage) {
1108 doc.body.style.borderWidth = "0px";
1109 doc.body.className = "htmlarea-content-body";
1110 try {
1111 doc.body.innerHTML = this._textArea.value;
1112 } catch(e) {
1113 HTMLArea._appendToLog("[HTMLArea::initIframe]: The HTML document is not well-formed.");
1114 alert(HTMLArea.I18N.msg["HTML-document-not-well-formed"]);
1115 docWellFormed = false;
1116 }
1117 }
1118
1119 // Set contents editable
1120 if (docWellFormed) {
1121 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera && !this._initEditMode()) {
1122 return false;
1123 }
1124 if (HTMLArea.is_ie || HTMLArea.is_safari) {
1125 doc.body.contentEditable = true;
1126 }
1127 if (HTMLArea.is_opera || HTMLArea.is_safari) {
1128 doc.designMode = "on";
1129 if (this._doc.queryCommandEnabled("insertbronreturn")) this._doc.execCommand("insertbronreturn", false, this.config.disableEnterParagraphs);
1130 if (this._doc.queryCommandEnabled("styleWithCSS")) this._doc.execCommand("styleWithCSS", false, this.config.useCSS);
1131 }
1132 if (HTMLArea.is_ie) doc.selection.empty();
1133 this._editMode = "wysiwyg";
1134 if (doc.body.contentEditable || doc.designMode == "on") HTMLArea._appendToLog("[HTMLArea::initIframe]: Design mode successfully set.");
1135 } else {
1136 this._editMode = "textmode";
1137 this.setMode("docnotwellformedmode");
1138 HTMLArea._appendToLog("[HTMLArea::initIframe]: Design mode could not be set.");
1139 }
1140
1141 // set editor number in iframe and document for retrieval in event handlers
1142 doc._editorNo = this._editorNumber;
1143 if (HTMLArea.is_ie) doc.documentElement._editorNo = this._editorNumber;
1144
1145 // intercept events for updating the toolbar & for keyboard handlers
1146 HTMLArea._addEvents((HTMLArea.is_ie ? doc.body : doc), ["keydown","keypress","mousedown","mouseup","drag"], HTMLArea._editorEvent, true);
1147
1148 // add unload handler
1149 if (!HTMLArea.hasUnloadHandler) {
1150 HTMLArea.hasUnloadHandler = true;
1151 HTMLArea._addEvent((this._iframe.contentWindow ? this._iframe.contentWindow : this._iframe.contentDocument), "unload", HTMLArea.removeEditorEvents);
1152 }
1153
1154 window.setTimeout("HTMLArea.generatePlugins(" + this._editorNumber + ");", 100);
1155 };
1156
1157 HTMLArea.generatePlugins = function(editorNumber) {
1158 var editor = RTEarea[editorNumber]["editor"];
1159 // check if any plugins have registered generate handlers
1160 // check also if any plugin has a onKeyPress handler
1161 editor._hasPluginWithOnKeyPressHandler = false;
1162 for (var pluginId in editor.plugins) {
1163 if (editor.plugins.hasOwnProperty(pluginId)) {
1164 var pluginInstance = editor.plugins[pluginId].instance;
1165 if (typeof(pluginInstance.onGenerate) === "function") {
1166 pluginInstance.onGenerate();
1167 }
1168 if (typeof(pluginInstance.onGenerateOnce) === "function") {
1169 pluginInstance.onGenerateOnce();
1170 pluginInstance.onGenerateOnce = null;
1171 }
1172 if (typeof(pluginInstance.onKeyPress) === "function") {
1173 editor._hasPluginWithOnKeyPressHandler = true;
1174 }
1175 }
1176 }
1177 if (typeof(editor.onGenerate) === "function") {
1178 editor.onGenerate();
1179 editor.onGenerate = null;
1180 }
1181 HTMLArea._appendToLog("[HTMLArea::initIframe]: All plugins successfully generated.");
1182 editor.focusEditor();
1183 editor.updateToolbar();
1184 };
1185
1186 /*
1187 * When we have a form, on reset, re-initialize the HTMLArea content and update the toolbar
1188 */
1189 HTMLArea.resetHandler = function(ev) {
1190 if(!ev) var ev = window.event;
1191 var form = (ev.target) ? ev.target : ev.srcElement;
1192 var editor = RTEarea[form._editorNumber]["editor"];
1193 editor.setHTML(editor._textArea.value);
1194 editor.updateToolbar();
1195 var a = form.__msh_prevOnReset;
1196 // call previous reset methods if they were there.
1197 if (typeof(a) != "undefined") {
1198 for (var i=a.length; --i >= 0; ) { a[i](); }
1199 }
1200 };
1201
1202 /*
1203 * Clean up event handlers and object references, undo/redo snapshots, update the textarea for submission
1204 */
1205 HTMLArea.removeEditorEvents = function(ev) {
1206 if (!ev) var ev = window.event;
1207 HTMLArea._stopEvent(ev);
1208 if (HTMLArea._eventCache) {
1209 HTMLArea._eventCache.flush();
1210 }
1211 for (var editorNumber = RTEarea.length; --editorNumber > 0 ;) {
1212 var editor = RTEarea[editorNumber].editor;
1213 if (editor) {
1214 RTEarea[editorNumber].editor = null;
1215 // save the HTML content into the original textarea for submit, back/forward, etc.
1216 editor._textArea.value = editor.getHTML();
1217 // do final cleanup
1218 HTMLArea.cleanup(editor);
1219 }
1220 }
1221 };
1222
1223 /*
1224 * Clean up a bunch of references in order to avoid memory leakages mainly in IE, but also in Firefox and Opera
1225 */
1226 HTMLArea.cleanup = function (editor) {
1227 // nullify envent handlers
1228 for (var handler in editor.eventHandlers) {
1229 if (editor.eventHandlers.hasOwnProperty(handler)) {
1230 editor.eventHandlers[handler] = null;
1231 }
1232 }
1233 for (var button in editor.btnList) {
1234 if (editor.btnList.hasOwnProperty(button)) {
1235 editor.btnList[button][3] = null;
1236 }
1237 }
1238 for (var dropdown in editor.config.customSelects) {
1239 if (editor.config.customSelects.hasOwnProperty(dropdown)) {
1240 editor.config.customSelects[dropdown].action = null;
1241 editor.config.customSelects[dropdown].refresh = null;
1242 }
1243 }
1244 for (var hotKey in editor.config.hotKeyList) {
1245 if (editor.config.customSelects.hasOwnProperty(hotKey)) {
1246 editor.config.hotKeyList[hotKey].action = null;
1247 }
1248 }
1249 editor.onGenerate = null;
1250 HTMLArea._editorEvent = null;
1251 if(editor._textArea.form) {
1252 editor._textArea.form.__msh_prevOnReset = null;
1253 editor._textArea.form._editorNumber = null;
1254 }
1255 HTMLArea.onload = null;
1256
1257 // cleaning plugins
1258 for (var plugin in editor.plugins) {
1259 if (editor.plugins.hasOwnProperty(plugin)) {
1260 var pluginInstance = editor.plugins[plugin].instance;
1261 if (typeof(pluginInstance.onClose) === "function") {
1262 pluginInstance.onClose();
1263 }
1264 pluginInstance.onClose = null;
1265 pluginInstance.onChange = null;
1266 pluginInstance.onButtonPress = null;
1267 pluginInstance.onGenerate = null;
1268 pluginInstance.onGenerateOnce = null;
1269 pluginInstance.onMode = null;
1270 pluginInstance.onHotKey = null;
1271 pluginInstance.onKeyPress = null;
1272 pluginInstance.onSelect = null;
1273 pluginInstance.onUpdateTolbar = null;
1274 }
1275 }
1276 // cleaning the toolbar elements
1277 for (var txt in editor._toolbarObjects) {
1278 if (editor._toolbarObjects.hasOwnProperty(txt)) {
1279 var obj = editor._toolbarObjects[txt];
1280 obj.state = null;
1281 obj.cmd = null;
1282 document.getElementById(obj.elementId)._obj = null;
1283 editor._toolbarObjects[txt] = null;
1284 }
1285 }
1286
1287 // cleaning the statusbar elements
1288 if (editor._statusBarTree.hasChildNodes()) {
1289 for (var i = editor._statusBarTree.firstChild; i; i = i.nextSibling) {
1290 if (i.nodeName.toLowerCase() == "a") {
1291 HTMLArea._removeEvents(i, ["click", "contextmenu"], HTMLArea.statusBarHandler);
1292 i.el = null;
1293 i.editor = null;
1294 }
1295 }
1296 }
1297 // final cleanup
1298 editor._toolbar = null;
1299 editor._statusBar = null;
1300 editor._statusBarTree = null;
1301 editor._htmlArea = null;
1302 editor._iframe = null;
1303 };
1304
1305 /*
1306 * Switch editor mode; parameter can be "textmode" or "wysiwyg".
1307 * If no parameter was passed, toggle between modes.
1308 */
1309 HTMLArea.prototype.setMode = function(mode) {
1310 if (typeof(mode) == "undefined") var mode = (this._editMode == "textmode") ? "wysiwyg" : "textmode";
1311 switch (mode) {
1312 case "textmode":
1313 case "docnotwellformedmode":
1314 this._textArea.value = this.getHTML();
1315 this._iframe.style.display = "none";
1316 this._textArea.style.display = "block";
1317 if(this.config.statusBar) {
1318 var statusBarTextMode = document.createElement("span");
1319 statusBarTextMode.className = "statusBarTextMode";
1320 statusBarTextMode.appendChild(document.createTextNode(HTMLArea.I18N.msg["TEXT_MODE"]));
1321 this._statusBar.innerHTML = '';
1322 this._statusBar.appendChild(statusBarTextMode);
1323 }
1324 this._editMode = "textmode";
1325 break;
1326 case "wysiwyg":
1327 if(HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) this._doc.designMode = "off";
1328 try {
1329 if(!this.config.fullPage) this._doc.body.innerHTML = this.getHTML();
1330 else this.setFullHTML(this.getHTML());
1331 } catch(e) {
1332 alert(HTMLArea.I18N.msg["HTML-document-not-well-formed"]);
1333 break;
1334 }
1335 this._textArea.style.display = "none";
1336 this._iframe.style.display = "block";
1337 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) this._doc.designMode = "on";
1338 if(this.config.statusBar) {
1339 this._statusBar.innerHTML = "";
1340 this._statusBar.appendChild(this._statusBarTree);
1341 }
1342 this._editMode = "wysiwyg";
1343 //set gecko options (if we can... raises exception in Firefox 3)
1344 if (HTMLArea.is_gecko) {
1345 try {
1346 if (this._doc.queryCommandEnabled("insertbronreturn")) this._doc.execCommand("insertbronreturn", false, this.config.disableEnterParagraphs);
1347 if (this._doc.queryCommandEnabled("enableObjectResizing")) this._doc.execCommand("enableObjectResizing", false, !this.config.disableObjectResizing);
1348 if (this._doc.queryCommandEnabled("enableInlineTableEditing")) this._doc.execCommand("enableInlineTableEditing", false, (this.config.buttons.table && this.config.buttons.table.enableHandles) ? true : false);
1349 if (this._doc.queryCommandEnabled("styleWithCSS")) this._doc.execCommand("styleWithCSS", false, this.config.useCSS);
1350 else if (this._doc.queryCommandEnabled("useCSS")) this._doc.execCommand("useCSS", false, !this.config.useCSS);
1351 } catch(e) {}
1352 }
1353 break;
1354 default:
1355 return false;
1356 }
1357 if (mode !== "docnotwellformedmode") this.focusEditor();
1358 for (var pluginId in this.plugins) {
1359 if (this.plugins.hasOwnProperty(pluginId)) {
1360 var pluginInstance = this.plugins[pluginId].instance;
1361 if (typeof(pluginInstance.onMode) === "function") {
1362 pluginInstance.onMode(mode);
1363 }
1364 }
1365 }
1366 };
1367
1368 /*
1369 * Get editor mode
1370 */
1371 HTMLArea.prototype.getMode = function() {
1372 return this._editMode;
1373 };
1374
1375 /*
1376 * Initialize iframe content when in full page mode
1377 */
1378 HTMLArea.prototype.setFullHTML = function(html) {
1379 var save_multiline = RegExp.multiline;
1380 RegExp.multiline = true;
1381 if(html.match(HTMLArea.RE_doctype)) {
1382 this.setDoctype(RegExp.$1);
1383 html = html.replace(HTMLArea.RE_doctype, "");
1384 };
1385 RegExp.multiline = save_multiline;
1386 if(!HTMLArea.is_ie) {
1387 if(html.match(HTMLArea.RE_head)) this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
1388 if(html.match(HTMLArea.RE_body)) this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
1389 } else {
1390 var html_re = /<html>((.|\n)*?)<\/html>/i;
1391 html = html.replace(html_re, "$1");
1392 this._doc.open();
1393 this._doc.write(html);
1394 this._doc.close();
1395 this._doc.body.contentEditable = true;
1396 return true;
1397 };
1398 };
1399
1400 /***************************************************
1401 * PLUGINS, STYLESHEETS, AND IMAGE AND POPUP URL'S
1402 ***************************************************/
1403
1404 /*
1405 * Instantiate the specified plugin and register it with the editor
1406 *
1407 * @param string plugin: the name of the plugin
1408 *
1409 * @return boolean true if the plugin was successfully registered
1410 */
1411 HTMLArea.prototype.registerPlugin = function(plugin) {
1412 var pluginName = plugin;
1413 if (typeof(plugin) === "string") {
1414 try {
1415 var plugin = eval(plugin);
1416 } catch(e) {
1417 HTMLArea._appendToLog("ERROR [HTMLArea::registerPlugin]: Cannot register invalid plugin: " + e);
1418 return false;
1419 }
1420 }
1421 if (typeof(plugin) !== "function") {
1422 HTMLArea._appendToLog("ERROR [HTMLArea::registerPlugin]: Cannot register undefined plugin.");
1423 return false;
1424 }
1425 var pluginInstance = new plugin(this, pluginName);
1426 if (pluginInstance) {
1427 var pluginInformation = plugin._pluginInfo;
1428 if(!pluginInformation) {
1429 pluginInformation = pluginInstance.getPluginInformation();
1430 }
1431 pluginInformation.instance = pluginInstance;
1432 this.plugins[pluginName] = pluginInformation;
1433 HTMLArea._appendToLog("[HTMLArea::registerPlugin]: Plugin " + pluginName + " was successfully registered.");
1434 return true;
1435 } else {
1436 HTMLArea._appendToLog("ERROR [HTMLArea::registerPlugin]: Can't register plugin " + pluginName + ".");
1437 return false;
1438 }
1439 };
1440
1441 /*
1442 * Load the required plugin script
1443 */
1444 HTMLArea.loadPlugin = function (pluginName, url, asynchronous) {
1445 if (typeof(asynchronous) == "undefined") {
1446 var asynchronous = true;
1447 }
1448 HTMLArea.loadScript(url, pluginName, asynchronous);
1449 };
1450
1451 /*
1452 * Load a stylesheet file
1453 */
1454 HTMLArea.loadStyle = function(style, plugin, url) {
1455 if (typeof(url) == "undefined") {
1456 var url = _editor_url || '';
1457 if (typeof(plugin) != "undefined") { url += "plugins/" + plugin + "/"; }
1458 url += style;
1459 if (/^\//.test(style)) { url = style; }
1460 }
1461 var head = document.getElementsByTagName("head")[0];
1462 var link = document.createElement("link");
1463 link.rel = "stylesheet";
1464 link.href = url;
1465 head.appendChild(link);
1466 };
1467
1468 /*
1469 * Get the url of some image
1470 */
1471 HTMLArea.prototype.imgURL = function(file, plugin) {
1472 if (typeof(plugin) == "undefined") return _editor_skin + this.config.imgURL + file;
1473 else return _editor_skin + this.config.imgURL + plugin + "/" + file;
1474 };
1475
1476 /*
1477 * Get the url of some popup
1478 */
1479 HTMLArea.prototype.popupURL = function(file) {
1480 var url = "";
1481 if(file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
1482 var pluginId = RegExp.$1;
1483 var popup = RegExp.$2;
1484 if(!/\.html$/.test(popup)) popup += ".html";
1485 if (this.config.pathToPluginDirectory[pluginId]) {
1486 url = this.config.pathToPluginDirectory[pluginId] + "popups/" + popup;
1487 } else {
1488 url = _typo3_host_url + _editor_url + "plugins/" + pluginId + "/popups/" + popup;
1489 }
1490 } else {
1491 url = _typo3_host_url + _editor_url + this.config.popupURL + file;
1492 }
1493 return url;
1494 };
1495
1496 /***************************************************
1497 * EDITOR UTILITIES
1498 ***************************************************/
1499 HTMLArea.getInnerText = function(el) {
1500 var txt = '', i;
1501 if(el.firstChild) {
1502 for(i=el.firstChild;i;i =i.nextSibling) {
1503 if(i.nodeType == 3) txt += i.data;
1504 else if(i.nodeType == 1) txt += HTMLArea.getInnerText(i);
1505 }
1506 } else {
1507 if(el.nodeType == 3) txt = el.data;
1508 }
1509 return txt;
1510 };
1511
1512 HTMLArea.prototype.forceRedraw = function() {
1513 this._doc.body.style.visibility = "hidden";
1514 this._doc.body.style.visibility = "visible";
1515 };
1516
1517 /*
1518 * Focus the editor iframe document or the textarea.
1519 */
1520 HTMLArea.prototype.focusEditor = function() {
1521 switch (this._editMode) {
1522 case "wysiwyg" :
1523 try {
1524 if (HTMLArea.is_safari) {
1525 this._iframe.focus();
1526 } else if (HTMLArea.is_opera) {
1527 this._doc.focus();
1528 } else {
1529 this._iframe.contentWindow.focus();
1530 }
1531 } catch(e) { }
1532 break;
1533 case "textmode":
1534 this._textArea.focus();
1535 break;
1536 }
1537 return this._doc;
1538 };
1539
1540 /*
1541 * Update the enabled/disabled/active state of the toolbar elements
1542 */
1543 HTMLArea.updateToolbar = function(editorNumber) {
1544 var editor = RTEarea[editorNumber]["editor"];
1545 editor.updateToolbar();
1546 editor._timerToolbar = null;
1547 };
1548
1549 HTMLArea.prototype.updateToolbar = function(noStatus) {
1550 var doc = this._doc,
1551 text = (this._editMode == "textmode"),
1552 selection = false,
1553 ancestors = null, cls = new Array(),
1554 txt, txtClass, i, inContext, match, matchAny, k, j, n, commandState;
1555 if(!text) {
1556 selection = !this._selectionEmpty(this._getSelection());
1557 ancestors = this.getAllAncestors();
1558 if(this.config.statusBar && !noStatus) {
1559 // Unhook previous events handlers
1560 if(this._statusBarTree.hasChildNodes()) {
1561 for (i = this._statusBarTree.firstChild; i; i = i.nextSibling) {
1562 if(i.nodeName.toLowerCase() == "a") {
1563 HTMLArea._removeEvents(i,["click", "contextmenu, mousedown"], HTMLArea.statusBarHandler);
1564 i.el = null;
1565 i.editor = null;
1566 }
1567 }
1568 }
1569 this._statusBarTree.selected = null;
1570 this._statusBarTree.innerHTML = '';
1571 this._statusBarTree.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); // clear
1572 for (i = ancestors.length; --i >= 0;) {
1573 var el = ancestors[i];
1574 if(!el) continue;
1575 var a = document.createElement("a");
1576 a.href = "#";
1577 a.el = el;
1578 a.editor = this;
1579 if (!HTMLArea.is_opera) {
1580 HTMLArea._addEvents(a, ["click", "contextmenu"], HTMLArea.statusBarHandler);
1581 } else {
1582 HTMLArea._addEvents(a, ["mousedown", "click"], HTMLArea.statusBarHandler);
1583 }
1584 txt = el.tagName.toLowerCase();
1585 a.title = el.style.cssText;
1586 if (el.id) { txt += "#" + el.id; }
1587 if (el.className) {
1588 txtClass = "";
1589 cls = el.className.trim().split(" ");
1590 for (j = 0; j < cls.length; ++j) {
1591 if (!HTMLArea.reservedClassNames.test(cls[j])) {
1592 txtClass += "." + cls[j];
1593 }
1594 }
1595 txt += txtClass;
1596 }
1597 a.appendChild(document.createTextNode(txt));
1598 this._statusBarTree.appendChild(a);
1599 if (i != 0) this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
1600 }
1601 }
1602 }
1603 for (var cmd in this._toolbarObjects) {
1604 if (this._toolbarObjects.hasOwnProperty(cmd)) {
1605 var btn = this._toolbarObjects[cmd];
1606
1607 // Determine if the button should be enabled
1608 inContext = true;
1609 if (btn.context && !text) {
1610 inContext = false;
1611 var attrs = [];
1612 var contexts = [];
1613 if (/(.*)\[(.*?)\]/.test(btn.context)) {
1614 contexts = RegExp.$1.split(",");
1615 attrs = RegExp.$2.split(",");
1616 } else {
1617 contexts = btn.context.split(",");
1618 }
1619 for (j = contexts.length; --j >= 0;) contexts[j] = contexts[j].toLowerCase();
1620 matchAny = (contexts[0] == "*");
1621 for (k = 0; k < ancestors.length; ++k) {
1622 if (!ancestors[k]) continue;
1623 match = false;
1624 for (j = contexts.length; --j >= 0;) match = match || (ancestors[k].tagName.toLowerCase() == contexts[j]);
1625 if (matchAny || match) {
1626 inContext = true;
1627 for (j = attrs.length; --j >= 0;) {
1628 if (!eval("ancestors[k]." + attrs[j])) {
1629 inContext = false;
1630 break;
1631 }
1632 }
1633 if (inContext) break;
1634 }
1635 }
1636 }
1637
1638 if (cmd == "CreateLink") {
1639 btn.state("enabled", (!text || btn.text) && (inContext || selection));
1640 } else {
1641 btn.state("enabled", (!text || btn.text) && inContext && (selection || !btn.selection));
1642 }
1643 if (typeof(cmd) == "function") { continue; };
1644 // look-it-up in the custom dropdown boxes
1645 var dropdown = this.config.customSelects[cmd];
1646 if ((!text || btn.text) && (typeof(dropdown) !== "undefined") && (typeof(dropdown.refresh) === "function")) {
1647 dropdown.refresh(this, cmd);
1648 continue;
1649 }
1650 switch (cmd) {
1651 case "TextIndicator":
1652 if(!text) {
1653 try {with (document.getElementById(btn.elementId).style) {
1654 backgroundColor = HTMLArea._makeColor(doc.queryCommandValue((HTMLArea.is_ie || HTMLArea.is_safari) ? "BackColor" : "HiliteColor"));
1655 // Mozilla
1656 if(/transparent/i.test(backgroundColor)) { backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("BackColor")); }
1657 color = HTMLArea._makeColor(doc.queryCommandValue("ForeColor"));
1658 fontFamily = doc.queryCommandValue("FontName");
1659 // Check if queryCommandState is available
1660 fontWeight = "normal";
1661 fontStyle = "normal";
1662 try { fontWeight = doc.queryCommandState("Bold") ? "bold" : "normal"; } catch(ex) { fontWeight = "normal"; };
1663 try { fontStyle = doc.queryCommandState("Italic") ? "italic" : "normal"; } catch(ex) { fontStyle = "normal"; };
1664 }} catch (e) {
1665 // alert(e + "\n\n" + cmd);
1666 }
1667 }
1668 break;
1669 case "HtmlMode":
1670 btn.state("active", text);
1671 break;
1672 default:
1673 break;
1674 }
1675 }
1676 }
1677
1678 for (var pluginId in this.plugins) {
1679 if (this.plugins.hasOwnProperty(pluginId)) {
1680 var pluginInstance = this.plugins[pluginId].instance;
1681 if (typeof(pluginInstance.onUpdateToolbar) === "function") {
1682 pluginInstance.onUpdateToolbar();
1683 }
1684 }
1685 }
1686 };
1687
1688 /***************************************************
1689 * DOM TREE MANIPULATION
1690 ***************************************************/
1691
1692 /*
1693 * Surround the currently selected HTML source code with the given tags.
1694 * Delete the selection, if any.
1695 */
1696 HTMLArea.prototype.surroundHTML = function(startTag,endTag) {
1697 this.insertHTML(startTag + this.getSelectedHTML().replace(HTMLArea.Reg_body, "") + endTag);
1698 };
1699
1700 /*
1701 * Change the tag name of a node.
1702 */
1703 HTMLArea.prototype.convertNode = function(el,newTagName) {
1704 var newel = this._doc.createElement(newTagName), p = el.parentNode;
1705 while (el.firstChild) newel.appendChild(el.firstChild);
1706 p.insertBefore(newel, el);
1707 p.removeChild(el);
1708 return newel;
1709 };
1710
1711 /*
1712 * Find a parent of an element with a specified tag
1713 */
1714 HTMLArea.getElementObject = function(el,tagName) {
1715 var oEl = el;
1716 while (oEl != null && oEl.nodeName.toLowerCase() != tagName) oEl = oEl.parentNode;
1717 return oEl;
1718 };
1719
1720 /*
1721 * This function removes the given markup element
1722 *
1723 * @param object element: the inline element to be removed, content being preserved
1724 *
1725 * @return void
1726 */
1727 HTMLArea.prototype.removeMarkup = function(element) {
1728 var bookmark = this.getBookmark(this._createRange(this._getSelection()));
1729 var parent = element.parentNode;
1730 while (element.firstChild) {
1731 parent.insertBefore(element.firstChild, element);
1732 }
1733 parent.removeChild(element);
1734 this.selectRange(this.moveToBookmark(bookmark));
1735 };
1736
1737 /*
1738 * This function verifies if the element has any allowed attributes
1739 *
1740 * @param object element: the DOM element
1741 * @param array allowedAttributes: array of allowed attribute names
1742 *
1743 * @return boolean true if the element has one of the allowed attributes
1744 */
1745 HTMLArea.hasAllowedAttributes = function(element,allowedAttributes) {
1746 var value;
1747 for (var i = allowedAttributes.length; --i >= 0;) {
1748 value = element.getAttribute(allowedAttributes[i]);
1749 if (value) {
1750 // IE returns an object on getAttribute("style");
1751 if (typeof(value) == "object") {
1752 if (allowedAttributes[i] == "style" && value.cssText) {
1753 return true;
1754 }
1755 } else {
1756 return true;
1757 }
1758 }
1759 }
1760 return false;
1761 };
1762
1763 /***************************************************
1764 * SELECTIONS AND RANGES
1765 ***************************************************/
1766
1767 /*
1768 * Return true if we have some selected content
1769 */
1770 HTMLArea.prototype.hasSelectedText = function() {
1771 return this.getSelectedHTML() != "";
1772 };
1773
1774 /*
1775 * Get an array with all the ancestor nodes of the selection.
1776 */
1777 HTMLArea.prototype.getAllAncestors = function() {
1778 var p = this.getParentElement();
1779 var a = [];
1780 while (p && (p.nodeType === 1) && (p.nodeName.toLowerCase() !== "body")) {
1781 a.push(p);
1782 p = p.parentNode;
1783 }
1784 a.push(this._doc.body);
1785 return a;
1786 };
1787
1788 /*
1789 * Get the block elements containing the start and the end points of the selection
1790 */
1791 HTMLArea.prototype.getEndBlocks = function(selection) {
1792 var range = this._createRange(selection);
1793 if (HTMLArea.is_gecko) {
1794 var parentStart = range.startContainer;
1795 var parentEnd = range.endContainer;
1796 } else {
1797 if (selection.type !== "Control" ) {
1798 var rangeEnd = range.duplicate();
1799 range.collapse(true);
1800 var parentStart = range.parentElement();
1801 rangeEnd.collapse(false);
1802 var parentEnd = rangeEnd.parentElement();
1803 } else {
1804 var parentStart = range.item(0);
1805 var parentEnd = parentStart;
1806 }
1807 }
1808 while (parentStart && !HTMLArea.isBlockElement(parentStart)) {
1809 parentStart = parentStart.parentNode;
1810 }
1811 while (parentEnd && !HTMLArea.isBlockElement(parentEnd)) {
1812 parentEnd = parentEnd.parentNode;
1813 }
1814 return { start : parentStart,
1815 end : parentEnd
1816 };
1817 };
1818
1819 /*
1820 * This function determines if the end poins of the current selection are within the same block
1821 *
1822 * @return boolean true if the end points of the current selection are inside the same block element
1823 */
1824 HTMLArea.prototype.endPointsInSameBlock = function() {
1825 var selection = this._getSelection();
1826 if (this._selectionEmpty(selection)) {
1827 return true;
1828 } else {
1829 var parent = this.getParentElement(selection);
1830 var endBlocks = this.getEndBlocks(selection);
1831 return (endBlocks.start === endBlocks.end && !/^(table|thead|tbody|tfoot|tr)$/i.test(parent.nodeName));
1832 }
1833 };
1834
1835 /*
1836 * Get the deepest ancestor of the selection that is of the specified type
1837 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
1838 */
1839 HTMLArea.prototype._getFirstAncestor = function(sel,types) {
1840 var prnt = this._activeElement(sel);
1841 if (prnt == null) {
1842 try {
1843 prnt = (HTMLArea.is_ie ? this._createRange(sel).parentElement() : this._createRange(sel).commonAncestorContainer);
1844 } catch(e) {
1845 return null;
1846 }
1847 }
1848 if (typeof(types) == 'string') types = [types];
1849
1850 while (prnt) {
1851 if (prnt.nodeType == 1) {
1852 if (types == null) return prnt;
1853 for (var i = 0; i < types.length; i++) {
1854 if(prnt.tagName.toLowerCase() == types[i]) return prnt;
1855 }
1856 if(prnt.tagName.toLowerCase() == 'body') break;
1857 if(prnt.tagName.toLowerCase() == 'table') break;
1858 }
1859 prnt = prnt.parentNode;
1860 }
1861 return null;
1862 };
1863
1864 /***************************************************
1865 * Category: EVENT HANDLERS
1866 ***************************************************/
1867
1868 /*
1869 * Intercept some commands and replace them with our own implementation
1870 */
1871 HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
1872 this.focusEditor();
1873 switch (cmdID) {
1874 case "HtmlMode" :
1875 this.setMode();
1876 break;
1877 default :
1878 try {
1879 this._doc.execCommand(cmdID, UI, param);
1880 } catch(e) {
1881 if (this.config.debug) alert(e + "\n\nby execCommand(" + cmdID + ");");
1882 }
1883 }
1884 this.updateToolbar();
1885 return false;
1886 };
1887
1888 /*
1889 * A generic event handler for things that happen in the IFRAME's document.
1890 */
1891 HTMLArea._editorEvent = function(ev) {
1892 if(!ev) var ev = window.event;
1893 var target = (ev.target) ? ev.target : ev.srcElement;
1894 var owner = (target.ownerDocument) ? target.ownerDocument : target;
1895 if(HTMLArea.is_ie) { // IE5.5 does not report any ownerDocument
1896 while (owner.parentElement) { owner = owner.parentElement; }
1897 }
1898 var editor = RTEarea[owner._editorNo]["editor"];
1899 var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (HTMLArea.is_gecko && ev.type == "keypress");
1900 editor.focusEditor();
1901
1902 if(keyEvent) {
1903 if(editor._hasPluginWithOnKeyPressHandler) {
1904 for (var pluginId in editor.plugins) {
1905 if (editor.plugins.hasOwnProperty(pluginId)) {
1906 var pluginInstance = editor.plugins[pluginId].instance;
1907 if (typeof(pluginInstance.onKeyPress) === "function") {
1908 if (!pluginInstance.onKeyPress(ev)) {
1909 HTMLArea._stopEvent(ev);
1910 return false;
1911 }
1912 }
1913 }
1914 }
1915 }
1916 if(ev.ctrlKey && !ev.shiftKey) {
1917 if(!ev.altKey) {
1918 // execute hotkey command
1919 var key = String.fromCharCode((HTMLArea.is_ie || HTMLArea.is_safari || HTMLArea.is_opera) ? ev.keyCode : ev.charCode).toLowerCase();
1920 if (HTMLArea.is_gecko && ev.keyCode == 32) key = String.fromCharCode(ev.keyCode).toLowerCase();
1921 if (key == " ") {
1922 editor.insertHTML("&nbsp;");
1923 editor.updateToolbar();
1924 HTMLArea._stopEvent(ev);
1925 return false;
1926 }
1927 if (!editor.config.hotKeyList[key]) return true;
1928 var cmd = editor.config.hotKeyList[key].cmd;
1929 if (!cmd) return true;
1930 switch (cmd) {
1931 case "SelectAll":
1932 cmd = editor.config.hotKeyList[key].cmd;
1933 editor.execCommand(cmd, false, null);
1934 HTMLArea._stopEvent(ev);
1935 return false;
1936 break;
1937 default:
1938 if (editor.config.hotKeyList[key] && editor.config.hotKeyList[key].action) {
1939 if (!editor.config.hotKeyList[key].action(editor, key)) {
1940 HTMLArea._stopEvent(ev);
1941 return false;
1942 }
1943 }
1944 }
1945 }
1946 } else if (ev.altKey) {
1947 // check if context menu is already handling this event
1948 if(editor.plugins["ContextMenu"] && editor.plugins["ContextMenu"].instance) {
1949 var keys = editor.plugins["ContextMenu"].instance.keys;
1950 if (keys.length > 0) {
1951 var k;
1952 for (var i = keys.length; --i >= 0;) {
1953 k = keys[i];
1954 if (k[0].toLowerCase() == key) {
1955 HTMLArea._stopEvent(ev);
1956 return false;
1957 }
1958 }
1959 }
1960 }
1961 } else if (keyEvent) {
1962 if (HTMLArea.is_gecko) editor._detectURL(ev);
1963 switch (ev.keyCode) {
1964 case 13 : // KEY enter
1965 if (HTMLArea.is_gecko) {
1966 if (!ev.shiftKey && !editor.config.disableEnterParagraphs) {
1967 if (editor._checkInsertP()) {
1968 HTMLArea._stopEvent(ev);
1969 }
1970 } else if (HTMLArea.is_safari) {
1971 var brNode = document.createElement("br");
1972 editor.insertNodeAtSelection(brNode);
1973 if (!brNode.nextSibling || !HTMLArea.getInnerText(brNode.nextSibling)) {
1974 var secondBrNode = document.createElement("br");
1975 secondBrNode = brNode.parentNode.appendChild(secondBrNode);
1976 editor.selectNode(secondBrNode, false);
1977 }
1978 HTMLArea._stopEvent(ev);
1979 }
1980 // update the toolbar state after some time
1981 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
1982 editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 100);
1983 return false;
1984 }
1985 break;
1986 case 8 : // KEY backspace
1987 case 46 : // KEY delete
1988 if ((HTMLArea.is_gecko && !ev.shiftKey) || HTMLArea.is_ie) {
1989 if (editor._checkBackspace()) HTMLArea._stopEvent(ev);
1990 }
1991 // update the toolbar state after some time
1992 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
1993 editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 50);
1994 break;
1995 case 9: // KEY horizontal tab
1996 var newkey = (ev.shiftKey ? "SHIFT-" : "") + "TAB";
1997 if (editor.config.hotKeyList[newkey] && editor.config.hotKeyList[newkey].action) {
1998 if (!editor.config.hotKeyList[newkey].action(editor, newkey)) {
1999 HTMLArea._stopEvent(ev);
2000 return false;
2001 }
2002 }
2003 break;
2004 case 37: // LEFT arrow key
2005 case 38: // UP arrow key
2006 case 39: // RIGHT arrow key
2007 case 40: // DOWN arrow key
2008 if (HTMLArea.is_ie) {
2009 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
2010 editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 10);
2011 return true;
2012 }
2013 }
2014 }
2015 } else {
2016 // mouse event
2017 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
2018 if (ev.type == "mouseup") editor.updateToolbar();
2019 else editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 50);
2020 }
2021 };
2022
2023 HTMLArea.prototype.scrollToCaret = function() {
2024 if (HTMLArea.is_gecko) {
2025 var e = this.getParentElement(),
2026 w = this._iframe.contentWindow ? this._iframe.contentWindow : window,
2027 h = w.innerHeight || w.height,
2028 d = this._doc,
2029 t = d.documentElement.scrollTop || d.body.scrollTop;
2030 if (e.offsetTop > h+t || e.offsetTop < t) {
2031 this.getParentElement().scrollIntoView();
2032 }
2033 }
2034 };
2035
2036 /*
2037 * Retrieve the HTML
2038 */
2039 HTMLArea.prototype.getHTML = function() {
2040 switch (this._editMode) {
2041 case "wysiwyg":
2042 return HTMLArea.getHTML(this._doc.body, false, this);
2043 case "textmode":
2044 return this._textArea.value;
2045 }
2046 return false;
2047 };
2048
2049 /*
2050 * Retrieve raw HTML
2051 */
2052 HTMLArea.prototype.getInnerHTML = function() {
2053 switch (this._editMode) {
2054 case "wysiwyg":
2055 return this._doc.body.innerHTML;
2056 case "textmode":
2057 return this._textArea.value;
2058 }
2059 return false;
2060 };
2061
2062 /*
2063 * Replace the HTML inside
2064 */
2065 HTMLArea.prototype.setHTML = function(html) {
2066 switch (this._editMode) {
2067 case "wysiwyg":
2068 this._doc.body.innerHTML = html;
2069 break;
2070 case "textmode":
2071 this._textArea.value = html;
2072 break;
2073 }
2074 return false;
2075 };
2076
2077 /*
2078 * Set the given doctype when config.fullPage is true
2079 */
2080 HTMLArea.prototype.setDoctype = function(doctype) {
2081 this.doctype = doctype;
2082 };
2083
2084 /***************************************************
2085 * UTILITY FUNCTIONS
2086 ***************************************************/
2087
2088 // variable used to pass the object to the popup editor window.
2089 HTMLArea._object = null;
2090
2091 /*
2092 * Check if the client agent is supported
2093 */
2094 HTMLArea.checkSupportedBrowser = function() {
2095 if(HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
2096 if(navigator.productSub < 20030210) return false;
2097 }
2098 return HTMLArea.is_gecko || HTMLArea.is_ie;
2099 };
2100
2101 /* EventCache Version 1.0
2102 * Copyright 2005 Mark Wubben
2103 * Adaptation by Stanislas Rolland
2104 * Provides a way for automatically removing events from nodes and thus preventing memory leakage.
2105 * See <http://novemberborn.net/javascript/event-cache> for more information.
2106 * This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>
2107 * Event Cache uses an anonymous function to create a hidden scope chain. This is to prevent scoping issues.
2108 */
2109 HTMLArea._eventCacheConstructor = function() {
2110 var listEvents = [];
2111
2112 return ({
2113 listEvents : listEvents,
2114
2115 add : function(node, sEventName, fHandler) {
2116 listEvents.push(arguments);
2117 },
2118
2119 flush : function() {
2120 var item;
2121 for (var i = listEvents.length; --i >= 0;) {
2122 item = listEvents[i];
2123 try {
2124 HTMLArea._removeEvent(item[0], item[1], item[2]);
2125 item[0][item[1]] = null;
2126 item[0] = null;
2127 item[2] = null;
2128 } catch(e) { }
2129 }
2130 listEvents.length = 0;
2131 }
2132 });
2133 };
2134
2135 /*
2136 * Register an event
2137 */
2138 HTMLArea._addEvent = function(el,evname,func,useCapture) {
2139 if (typeof(useCapture) == "undefined") {
2140 var useCapture = false;
2141 }
2142 if (HTMLArea.is_gecko) {
2143 el.addEventListener(evname, func, !HTMLArea.is_opera || useCapture);
2144 } else {
2145 el.attachEvent("on" + evname, func);
2146 }
2147 HTMLArea._eventCache.add(el, evname, func);
2148 };
2149
2150 /*
2151 * Register a list of events
2152 */
2153 HTMLArea._addEvents = function(el,evs,func,useCapture) {
2154 if (typeof(useCapture) == "undefined") {
2155 var useCapture = false;
2156 }
2157 for (var i = evs.length; --i >= 0;) {
2158 HTMLArea._addEvent(el,evs[i], func, useCapture);
2159 }
2160 };
2161
2162 /*
2163 * Remove an event listener
2164 */
2165 HTMLArea._removeEvent = function(el,evname,func) {
2166 if (HTMLArea.is_gecko) {
2167 // Avoid Safari crash when removing events on some orphan documents
2168 if (!HTMLArea.is_safari || HTMLArea.is_chrome) {
2169 try {
2170 el.removeEventListener(evname, func, true);
2171 el.removeEventListener(evname, func, false);
2172 } catch(e) { }
2173 } else if (el.nodeType != 9 || el.defaultView) {
2174 try {
2175 el.removeEventListener(evname, func, true);
2176 el.removeEventListener(evname, func, false);
2177 } catch(e) { }
2178 }
2179 } else {
2180 try {
2181 el.detachEvent("on" + evname, func);
2182 } catch(e) { }
2183 }
2184 };
2185
2186 /*
2187 * Remove a list of events
2188 */
2189 HTMLArea._removeEvents = function(el,evs,func) {
2190 for (var i = evs.length; --i >= 0;) { HTMLArea._removeEvent(el, evs[i], func); }
2191 };
2192
2193 /*
2194 * Stop event propagation
2195 */
2196 HTMLArea._stopEvent = function(ev) {
2197 if(HTMLArea.is_gecko) {
2198 ev.stopPropagation();
2199 ev.preventDefault();
2200 } else {
2201 ev.cancelBubble = true;
2202 ev.returnValue = false;
2203 }
2204 };
2205
2206 /*
2207 * Remove a class name from the class attribute of an element
2208 *
2209 * @param object el: the element
2210 * @param string className: the class name to remove
2211 * @param boolean substring: if true, remove the first class name starting with the given string
2212 * @return void
2213 */
2214 HTMLArea._removeClass = function(el, className, substring) {
2215 if (!el || !el.className) return;
2216 var classes = el.className.trim().split(" ");
2217 var newClasses = new Array();
2218 for (var i = classes.length; --i >= 0;) {
2219 if (!substring) {
2220 if (classes[i] != className) {
2221 newClasses[newClasses.length] = classes[i];
2222 }
2223 } else if (classes[i].indexOf(className) != 0) {
2224 newClasses[newClasses.length] = classes[i];
2225 }
2226 }
2227 if (newClasses.length == 0) {
2228 if (!HTMLArea.is_opera) el.removeAttribute(HTMLArea.is_gecko ? "class" : "className");
2229 else el.className = '';
2230 } else {
2231 el.className = newClasses.join(" ");
2232 }
2233 };
2234
2235 /*
2236 * Add a class name to the class attribute
2237 */
2238 HTMLArea._addClass = function(el, addClassName) {
2239 HTMLArea._removeClass(el, addClassName);
2240 if (el.className && HTMLArea.classesXOR) {
2241 var classNames = el.className.trim().split(" ");
2242 for (var i = classNames.length; --i >= 0;) {
2243 if (HTMLArea.classesXOR[addClassName] && HTMLArea.classesXOR[addClassName].test(classNames[i])) {
2244 HTMLArea._removeClass(el, classNames[i]);
2245 }
2246 }
2247 }
2248 if (el.className) el.className += " " + addClassName;
2249 else el.className = addClassName;
2250 };
2251
2252 /*
2253 * Check if a class name is in the class attribute of an element
2254 *
2255 * @param object el: the element
2256 * @param string className: the class name to look for
2257 * @param boolean substring: if true, look for a class name starting with the given string
2258 * @return boolean true if the class name was found
2259 */
2260 HTMLArea._hasClass = function(el, className, substring) {
2261 if (!el || !el.className) return false;
2262 var classes = el.className.trim().split(" ");
2263 for (var i = classes.length; --i >= 0;) {
2264 if (classes[i] == className || (substring && classes[i].indexOf(className) == 0)) return true;
2265 }
2266 return false;
2267 };
2268
2269 /*
2270 * Select a value in a select element
2271 *
2272 * @param object select: the select object
2273 * @param string value: the value
2274 * @return void
2275 */
2276 HTMLArea.selectValue = function(select, value) {
2277 var options = select.getElementsByTagName("option");
2278 for (var i = options.length; --i >= 0;) {
2279 var option = options[i];
2280 option.selected = (option.value == value);
2281 select.selectedIndex = i;
2282 }
2283 };
2284
2285 HTMLArea.RE_blockTags = /^(body|p|h1|h2|h3|h4|h5|h6|ul|ol|pre|dl|dt|dd|div|noscript|blockquote|form|hr|table|caption|fieldset|address|td|tr|th|li|tbody|thead|tfoot|iframe)$/;
2286 HTMLArea.isBlockElement = function(el) { return el && el.nodeType == 1 && HTMLArea.RE_blockTags.test(el.nodeName.toLowerCase()); };
2287 HTMLArea.RE_closingTags = /^(p|blockquote|a|li|ol|ul|dl|dt|td|th|tr|tbody|thead|tfoot|caption|colgroup|table|div|b|bdo|big|cite|code|del|dfn|em|i|ins|kbd|label|q|samp|small|span|strike|strong|sub|sup|tt|u|var|abbr|acronym|font|center|object|embed|style|script|title|head)$/;
2288 HTMLArea.RE_noClosingTag = /^(img|br|hr|col|input|area|base|link|meta|param)$/;
2289 HTMLArea.needsClosingTag = function(el) { return el && el.nodeType == 1 && !HTMLArea.RE_noClosingTag.test(el.tagName.toLowerCase()); };
2290
2291 /*
2292 * Perform HTML encoding of some given string
2293 * Borrowed in part from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
2294 */
2295 HTMLArea.htmlDecode = function(str) {
2296 str = str.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
2297 str = str.replace(/&nbsp;/g, "\xA0"); // Decimal 160, non-breaking-space
2298 str = str.replace(/&quot;/g, "\x22");
2299 str = str.replace(/&#39;/g, "'") ;
2300 str = str.replace(/&amp;/g, "&");
2301 return str;
2302 };
2303 HTMLArea.htmlEncode = function(str) {
2304 if (typeof(str) != 'string') str = str.toString(); // we don't need regexp for that, but.. so be it for now.
2305 str = str.replace(/&/g, "&amp;");
2306 str = str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
2307 str = str.replace(/\xA0/g, "&nbsp;"); // Decimal 160, non-breaking-space
2308 str = str.replace(/\x22/g, "&quot;"); // \x22 means '"'
2309 return str;
2310 };
2311
2312 /*
2313 * Retrieve the HTML code from the given node.
2314 * This is a replacement for getting innerHTML, using standard DOM calls.
2315 * Wrapper catches a Mozilla-Exception with non well-formed html source code.
2316 */
2317 HTMLArea.getHTML = function(root, outputRoot, editor){
2318 try {
2319 return HTMLArea.getHTMLWrapper(root,outputRoot,editor);
2320 } catch(e) {
2321 HTMLArea._appendToLog("The HTML document is not well-formed.");
2322 if(!HTMLArea._debugMode) alert(HTMLArea.I18N.msg["HTML-document-not-well-formed"]);
2323 else return HTMLArea.getHTMLWrapper(root,outputRoot,editor);
2324 return editor._doc.body.innerHTML;
2325 }
2326 };
2327
2328 HTMLArea.getHTMLWrapper = function(root, outputRoot, editor) {
2329 var html = "";
2330 if(!root) return html;
2331 switch (root.nodeType) {
2332 case 1: // ELEMENT_NODE
2333 case 11: // DOCUMENT_FRAGMENT_NODE
2334 case 9: // DOCUMENT_NODE
2335 var closed, i, config = editor.config;
2336 var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
2337 if (root_tag == "br" && config.removeTrailingBR && !root.nextSibling && HTMLArea.isBlockElement(root.parentNode) && (!root.previousSibling || root.previousSibling.nodeName.toLowerCase() != "br")) {
2338 if (!root.previousSibling && root.parentNode && root.parentNode.nodeName.toLowerCase() == "p" && root.parentNode.className) html += "&nbsp;";
2339 break;
2340 }
2341 if (config.htmlRemoveTagsAndContents && config.htmlRemoveTagsAndContents.test(root_tag)) break;
2342 var custom_tag = (config.customTags && config.customTags.test(root_tag));
2343 if (outputRoot) outputRoot = !(config.htmlRemoveTags && config.htmlRemoveTags.test(root_tag));
2344 if ((HTMLArea.is_ie || HTMLArea.is_safari) && root_tag == "head") {
2345 if(outputRoot) html += "<head>";
2346 var save_multiline = RegExp.multiline;
2347 RegExp.multiline = true;
2348 var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
2349 return p1 + p2.toLowerCase();
2350 });
2351 RegExp.multiline = save_multiline;
2352 html += txt;
2353 if(outputRoot) html += "</head>";
2354 break;
2355 } else if (outputRoot) {
2356 if (HTMLArea.is_gecko && root.hasAttribute('_moz_editor_bogus_node')) break;
2357 closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root) || custom_tag));
2358 html = "<" + root_tag;
2359 var a, name, value, attrs = root.attributes;
2360 var n = attrs.length;
2361 for (i = attrs.length; --i >= 0 ;) {
2362 a = attrs.item(i);
2363 name = a.nodeName.toLowerCase();
2364 if ((!a.specified && name != 'value') || /_moz|contenteditable|_msh/.test(name)) continue;
2365 if (!HTMLArea.is_ie || name != "style") {
2366 // IE5.5 reports wrong values. For this reason we extract the values directly from the root node.
2367 // Using Gecko the values of href and src are converted to absolute links unless we get them using nodeValue()
2368 if (typeof(root[a.nodeName]) != "undefined" && name != "href" && name != "src" && name != "style" && !/^on/.test(name)) {
2369 value = root[a.nodeName];
2370 } else {
2371 value = a.nodeValue;
2372 if (HTMLArea.is_ie && (name == "href" || name == "src") && editor.plugins.link && editor.plugins.link.instance && editor.plugins.link.instance.stripBaseURL) {
2373 value = editor.plugins.link.instance.stripBaseURL(value);
2374 }
2375 }
2376 } else { // IE fails to put style in attributes list.
2377 value = root.style.cssText;
2378 }
2379 // Mozilla reports some special values; we don't need them.
2380 if(/(_moz|^$)/.test(value)) continue;
2381 // Strip value="0" reported by IE on all li tags
2382 if(HTMLArea.is_ie && root_tag == "li" && name == "value" && a.nodeValue == 0) continue;
2383 html += " " + name + '="' + HTMLArea.htmlEncode(value) + '"';
2384 }
2385 if (html != "") html += closed ? " />" : ">";
2386 }
2387 for (i = root.firstChild; i; i = i.nextSibling) {
2388 if (/^li$/i.test(i.tagName) && !/^[ou]l$/i.test(root.tagName)) html += "<ul>" + HTMLArea.getHTMLWrapper(i, true, editor) + "</ul>";
2389 else html += HTMLArea.getHTMLWrapper(i, true, editor);
2390 }
2391 if (outputRoot && !closed) html += "</" + root_tag + ">";
2392 break;
2393 case 3: // TEXT_NODE
2394 html = /^(script|style)$/i.test(root.parentNode.tagName) ? root.data : HTMLArea.htmlEncode(root.data);
2395 break;
2396 case 8: // COMMENT_NODE
2397 if (!editor.config.htmlRemoveComments) html = "<!--" + root.data + "-->";
2398 break;
2399 case 4: // Node.CDATA_SECTION_NODE
2400 // Mozilla seems to convert CDATA into a comment when going into wysiwyg mode, don't know about IE
2401 html += '<![CDATA[' + root.data + ']]>';
2402 break;
2403 case 5: // Node.ENTITY_REFERENCE_NODE
2404 html += '&' + root.nodeValue + ';';
2405 break;
2406 case 7: // Node.PROCESSING_INSTRUCTION_NODE
2407 // PI's don't seem to survive going into the wysiwyg mode, (at least in moz) so this is purely academic
2408 html += '<?' + root.target + ' ' + root.data + ' ?>';
2409 break;
2410 default:
2411 break;
2412 }
2413 return html;
2414 };
2415
2416 HTMLArea.getPrevNode = function(node) {
2417 if(!node) return null;
2418 if(node.previousSibling) return node.previousSibling;
2419 if(node.parentNode) return node.parentNode;
2420 return null;
2421 };
2422
2423 HTMLArea.getNextNode = function(node) {
2424 if(!node) return null;
2425 if(node.nextSibling) return node.nextSibling;
2426 if(node.parentNode) return node.parentNode;
2427 return null;
2428 };
2429
2430 HTMLArea.removeFromParent = function(el) {
2431 if(!el.parentNode) return;
2432 var pN = el.parentNode;
2433 pN.removeChild(el);
2434 return el;
2435 };
2436
2437 String.prototype.trim = function() {
2438 return this.replace(/^\s+/, '').replace(/\s+$/, '');
2439 };
2440
2441 // creates a rgb-style color from a number
2442 HTMLArea._makeColor = function(v) {
2443 if (typeof(v) != "number") {
2444 // already in rgb (hopefully); IE doesn't get here.
2445 return v;
2446 }
2447 // IE sends number; convert to rgb.
2448 var r = v & 0xFF;
2449 var g = (v >> 8) & 0xFF;
2450 var b = (v >> 16) & 0xFF;
2451 return "rgb(" + r + "," + g + "," + b + ")";
2452 };
2453
2454 // returns hexadecimal color representation from a number or a rgb-style color.
2455 HTMLArea._colorToRgb = function(v) {
2456 if (!v)
2457 return '';
2458
2459 // returns the hex representation of one byte (2 digits)
2460 function hex(d) {
2461 return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
2462 };
2463
2464 if (typeof(v) == "number") {
2465 // we're talking to IE here
2466 var r = v & 0xFF;
2467 var g = (v >> 8) & 0xFF;
2468 var b = (v >> 16) & 0xFF;
2469 return "#" + hex(r) + hex(g) + hex(b);
2470 }
2471
2472 if (v.substr(0, 3) == "rgb") {
2473 // in rgb(...) form -- Mozilla
2474 var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2475 if (v.match(re)) {
2476 var r = parseInt(RegExp.$1);
2477 var g = parseInt(RegExp.$2);
2478 var b = parseInt(RegExp.$3);
2479 return "#" + hex(r) + hex(g) + hex(b);
2480 }
2481 // doesn't match RE?! maybe uses percentages or float numbers
2482 // -- FIXME: not yet implemented.
2483 return null;
2484 }
2485
2486 if (v.substr(0, 1) == "#") {
2487 // already hex rgb (hopefully :D )
2488 return v;
2489 }
2490
2491 // if everything else fails ;)
2492 return null;
2493 };
2494
2495 /** Use XML HTTPRequest to post some data back to the server and do something
2496 * with the response (asyncronously!), this is used by such things as the spellchecker update personal dict function
2497 */
2498 HTMLArea._postback = function(url, data, handler, addParams, charset) {
2499 if (typeof(charset) == "undefined") var charset = "utf-8";
2500 var req = null;
2501 if (window.XMLHttpRequest) req = new XMLHttpRequest();
2502 else if (window.ActiveXObject) {
2503 var success = false;
2504 for (var k = 0; k < HTMLArea.MSXML_XMLHTTP_PROGIDS.length && !success; k++) {
2505 try {
2506 req = new ActiveXObject(HTMLArea.MSXML_XMLHTTP_PROGIDS[k]);
2507 success = true;
2508 } catch (e) { }
2509 }
2510 }
2511
2512 if(req) {
2513 var content = '';
2514 for (var i in data) {
2515 content += (content.length ? '&' : '') + i + '=' + encodeURIComponent(data[i]);
2516 }
2517 content += (content.length ? '&' : '') + 'charset=' + charset;
2518 if (typeof(addParams) != "undefined") content += addParams;
2519 if (url.substring(0,1) == '/') {
2520 var postUrl = _typo3_host_url + url;
2521 } else {
2522 var postUrl = _typo3_host_url + _editor_url + url;
2523 }
2524
2525 function callBack() {
2526 if(req.readyState == 4) {
2527 if (req.status == 200) {
2528 if (typeof(handler) == 'function') handler(req.responseText, req);
2529 HTMLArea._appendToLog("[HTMLArea::_postback]: Server response: " + req.responseText);
2530 } else {
2531 HTMLArea._appendToLog("ERROR [HTMLArea::_postback]: Unable to post " + postUrl + " . Server reported " + req.statusText);
2532 }
2533 }
2534 }
2535 req.onreadystatechange = callBack;
2536 function sendRequest() {
2537 HTMLArea._appendToLog("[HTMLArea::_postback]: Request: " + content);
2538 req.send(content);
2539 }
2540
2541 req.open('POST', postUrl, true);
2542 req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
2543 window.setTimeout(sendRequest, 500);
2544 }
2545 };
2546
2547 /**
2548 * Internet Explorer returns an item having the _name_ equal to the given id, even if it's not having any id.
2549 * This way it can return a different form field even if it's not a textarea. This works around the problem by
2550 * specifically looking to search only elements having a certain tag name.
2551 */
2552 HTMLArea.getElementById = function(tag, id) {
2553 var el, i, objs = document.getElementsByTagName(tag);
2554 for (i = objs.length; --i >= 0 && (el = objs[i]);) {
2555 if (el.id == id) return el;
2556 }
2557 return null;
2558 };
2559
2560 /***************************************************
2561 * TYPO3-SPECIFIC FUNCTIONS
2562 ***************************************************/
2563 /*
2564 * Set the size of textarea with the RTE. It's called, if we are in fullscreen-mode.
2565 */
2566 var setRTEsizeByJS = function(divId, height, width) {
2567 if (HTMLArea.is_gecko) height = height - 25;
2568 else height = height - 60;
2569 if (height > 0) document.getElementById(divId).style.height = height + "px";
2570 if (HTMLArea.is_gecko) width = "99%";
2571 else width = "97%";
2572 document.getElementById(divId).style.width = width;
2573 };
2574
2575 /*
2576 * Extending the TYPO3 Lorem Ipsum extension
2577 */
2578 var lorem_ipsum = function(element,text) {
2579 if (element.tagName.toLowerCase() == "textarea" && element.id && element.id.substr(0,7) == "RTEarea") {
2580 var editor = RTEarea[element.id.substr(7,8)]["editor"];
2581 editor.insertHTML(text);
2582 editor.updateToolbar();
2583 }
2584 };
2585
2586 /*
2587 * Initialize the editor, configure the toolbar, setup the plugins, etc.
2588 */
2589 HTMLArea.initTimer = [];
2590
2591 HTMLArea.onGenerateHandler = function(editorNumber) {
2592 return (function() {
2593 document.getElementById('pleasewait' + editorNumber).style.display = 'none';
2594 document.getElementById('editorWrap' + editorNumber).style.visibility = 'visible';
2595 editorNumber = null;
2596 });
2597 };
2598
2599 HTMLArea.initEditor = function(editorNumber) {
2600 if(HTMLArea.checkSupportedBrowser()) {
2601 document.getElementById('pleasewait' + editorNumber).style.display = 'block';
2602 document.getElementById('editorWrap' + editorNumber).style.visibility = 'hidden';
2603 if(HTMLArea.initTimer[editorNumber]) window.clearTimeout(HTMLArea.initTimer[editorNumber]);
2604 if(!HTMLArea.is_loaded) {
2605 HTMLArea.initTimer[editorNumber] = window.setTimeout( "HTMLArea.initEditor(" + editorNumber + ");", 150);
2606 } else {
2607 var RTE = RTEarea[editorNumber];
2608
2609 // Get the configuration properties
2610 var config = new HTMLArea.Config();
2611 for (var property in RTE) {
2612 if (RTE.hasOwnProperty(property)) {
2613 config[property] = RTE[property] ? RTE[property] : false;
2614 }
2615 }
2616 // Create an editor for the textarea
2617 var editor = new HTMLArea(RTE.id, config);
2618 RTE.editor = editor;
2619
2620 // Save the editornumber in the object
2621 editor._typo3EditerNumber = editorNumber;
2622 editor._editorNumber = editorNumber;
2623
2624 // Override these settings if they were ever modified
2625 editor.config.width = "auto";
2626 editor.config.height = "auto";
2627 editor.config.sizeIncludesToolbar = true;
2628 editor.config.fullPage = false;
2629
2630 // Register the plugins included in the configuration
2631 for (var plugin in editor.config.plugin) {
2632 if (editor.config.plugin.hasOwnProperty(plugin) && editor.config.plugin[plugin]) {
2633 editor.registerPlugin(plugin);
2634 }
2635 }
2636
2637 editor.onGenerate = HTMLArea.onGenerateHandler(editorNumber);
2638
2639 editor.generate();
2640 return false;
2641 }
2642 } else {
2643 document.getElementById('pleasewait' + editorNumber).style.display = 'none';
2644 document.getElementById('editorWrap' + editorNumber).style.visibility = 'visible';
2645 }
2646 };
2647
2648 HTMLArea.allElementsAreDisplayed = function(elements) {
2649 for (var i=0, length=elements.length; i < length; i++) {
2650 if (document.getElementById(elements[i]).style.display == "none") {
2651 return false;
2652 }
2653 }
2654 return true;
2655 };
2656
2657 /**
2658 * Base, version 1.0.2
2659 * Copyright 2006, Dean Edwards
2660 * License: http://creativecommons.org/licenses/LGPL/2.1/
2661 */
2662
2663 HTMLArea.Base = function() {
2664 if (arguments.length) {
2665 if (this == window) { // cast an object to this class
2666 HTMLArea.Base.prototype.extend.call(arguments[0], arguments.callee.prototype);
2667 } else {
2668 this.extend(arguments[0]);
2669 }
2670 }
2671 };
2672
2673 HTMLArea.Base.version = "1.0.2";
2674
2675 HTMLArea.Base.prototype = {
2676 extend: function(source, value) {
2677 var extend = HTMLArea.Base.prototype.extend;
2678 if (arguments.length == 2) {
2679 var ancestor = this[source];
2680 // overriding?
2681 if ((ancestor instanceof Function) && (value instanceof Function) &&
2682 ancestor.valueOf() != value.valueOf() && /\bbase\b/.test(value)) {
2683 var method = value;
2684 // var _prototype = this.constructor.prototype;
2685 // var fromPrototype = !Base._prototyping && _prototype[source] == ancestor;
2686 value = function() {
2687 var previous = this.base;
2688 // this.base = fromPrototype ? _prototype[source] : ancestor;
2689 this.base = ancestor;
2690 var returnValue = method.apply(this, arguments);
2691 this.base = previous;
2692 return returnValue;
2693 };
2694 // point to the underlying method
2695 value.valueOf = function() {
2696 return method;
2697 };
2698 value.toString = function() {
2699 return String(method);
2700 };
2701 }
2702 return this[source] = value;
2703 } else if (source) {
2704 var _prototype = {toSource: null};
2705 // do the "toString" and other methods manually
2706 var _protected = ["toString", "valueOf"];
2707 // if we are prototyping then include the constructor
2708 if (HTMLArea.Base._prototyping) _protected[2] = "constructor";
2709 for (var i = 0; (name = _protected[i]); i++) {
2710 if (source[name] != _prototype[name]) {
2711 extend.call(this, name, source[name]);
2712 }
2713 }
2714 // copy each of the source object's properties to this object
2715 for (var name in source) {
2716 if (!_prototype[name]) {
2717 extend.call(this, name, source[name]);
2718 }
2719 }
2720 }
2721 return this;
2722 },
2723
2724 base: function() {
2725 // call this method from any other method to invoke that method's ancestor
2726 }
2727 };
2728
2729 HTMLArea.Base.extend = function(_instance, _static) {
2730 var extend = HTMLArea.Base.prototype.extend;
2731 if (!_instance) _instance = {};
2732 // build the prototype
2733 HTMLArea.Base._prototyping = true;
2734 var _prototype = new this;
2735 extend.call(_prototype, _instance);
2736 var constructor = _prototype.constructor;
2737 _prototype.constructor = this;
2738 delete HTMLArea.Base._prototyping;
2739 // create the wrapper for the constructor function
2740 var klass = function() {
2741 if (!HTMLArea.Base._prototyping) constructor.apply(this, arguments);
2742 this.constructor = klass;
2743 };
2744 klass.prototype = _prototype;
2745 // build the class interface
2746 klass.extend = this.extend;
2747 klass.implement = this.implement;
2748 klass.toString = function() {
2749 return String(constructor);
2750 };
2751 extend.call(klass, _static);
2752 // single instance
2753 var object = constructor ? klass : _prototype;
2754 // class initialisation
2755 if (object.init instanceof Function) object.init();
2756 return object;
2757 };
2758
2759 HTMLArea.Base.implement = function(_interface) {
2760 if (_interface instanceof Function) _interface = _interface.prototype;
2761 this.prototype.extend(_interface);
2762 };
2763
2764 /**
2765 * HTMLArea.plugin class
2766 *
2767 * Every plugin should be a subclass of this class
2768 *
2769 */
2770 HTMLArea.Plugin = HTMLArea.Base.extend({
2771
2772 /**
2773 * HTMLArea.plugin constructor
2774 *
2775 * @param object editor: instance of RTE
2776 * @param string pluginName: name of the plugin
2777 *
2778 * @return boolean true if the plugin was configured
2779 */
2780 constructor : function(editor, pluginName) {
2781 this.editor = editor;
2782 this.editorNumber = editor._editorNumber;
2783 this.editorConfiguration = editor.config;
2784 this.name = pluginName;
2785 try {
2786 HTMLArea.I18N[this.name] = eval(this.name + "_langArray");
2787 this.I18N = HTMLArea.I18N[this.name];
2788 } catch(e) {
2789 this.appendToLog("initialize", "The localization array for plugin " + this.name + " could not be assigned.");
2790 }
2791 return this.configurePlugin(editor);
2792 },
2793
2794 /**
2795 * Configures the plugin
2796 * This function is invoked by the class constructor.
2797 * This function should be redefined by the plugin subclass. Normal steps would be:
2798 * - registering plugin ingormation with method registerPluginInformation;
2799 * - registering any buttons with method registerButton;
2800 * - registering any drop-down lists with method registerDropDown.
2801 *
2802 * @param object editor: instance of RTE
2803 *
2804 * @return boolean true if the plugin was configured
2805 */
2806 configurePlugin : function(editor) {
2807 return false;
2808 },
2809
2810 /**
2811 * Registers the plugin "About" information
2812 *
2813 * @param object pluginInformation:
2814 * version : the version,
2815 * developer : the name of the developer,
2816 * developerUrl : the url of the developer,
2817 * copyrightOwner : the name of the copyright owner,
2818 * sponsor : the name of the sponsor,
2819 * sponsorUrl : the url of the sponsor,
2820 * license : the type of license (should be "GPL")
2821 *
2822 * @return boolean true if the information was registered
2823 */
2824 registerPluginInformation : function(pluginInformation) {
2825 if (typeof(pluginInformation) !== "object") {
2826 this.appendToLog("registerPluginInformation", "Plugin information was not provided");
2827 return false;
2828 } else {
2829 this.pluginInformation = pluginInformation;
2830 this.pluginInformation.name = this.name;
2831 /* Ensure backwards compatibility */
2832 this.pluginInformation.developer_url = this.pluginInformation.developerUrl;
2833 this.pluginInformation.c_owner = this.pluginInformation.copyrightOwner;
2834 this.pluginInformation.sponsor_url = this.pluginInformation.sponsorUrl;
2835 return true;
2836 }
2837 },
2838
2839 /**
2840 * Returns the plugin information
2841 *
2842 * @return object the plugin information object
2843 */
2844 getPluginInformation : function() {
2845 return this.pluginInformation;
2846 },
2847
2848 /**
2849 * Returns true if the button is enabled in the toolbar configuration
2850 *
2851 * @param string buttonId: identification of the button
2852 *
2853 * @return boolean true if the button is enabled in the toolbar configuration
2854 */
2855 isButtonInToolbar : function(buttonId) {
2856 var toolbar = this.editorConfiguration.toolbar;
2857 var n = toolbar.length;
2858 for ( var i = 0; i < n; ++i ) {
2859 var buttonInToolbar = new RegExp( "^(" + toolbar[i].join("|") + ")$", "i");
2860 if (buttonInToolbar.test(buttonId)) {
2861 return true;
2862 }
2863 }
2864 return false;
2865 },
2866
2867 /**
2868 * Registors a button for inclusion in the toolbar
2869 *
2870 * @param object buttonConfiguration: the configuration object of the button:
2871 * id : unique id for the button
2872 * tooltip : tooltip for the button
2873 * textMode : enable in text mode
2874 * action : name of the function invoked when the button is pressed
2875 * context : will be disabled if not inside one of listed elements
2876 * hide : hide in menu and show only in context menu?
2877 * selection : will be disabled if there is no selection?
2878 * hotkey : hotkey character
2879 * dialog : if true, the button opens a dialogue
2880 * dimensions : the opening dimensions object of the dialogue window
2881 *
2882 * @return boolean true if the button was successfully registered
2883 */
2884 registerButton : function (buttonConfiguration) {
2885 if (this.isButtonInToolbar(buttonConfiguration.id)) {
2886 if ((typeof(buttonConfiguration.action) === "string") && (typeof(this[buttonConfiguration.action]) === "function")) {
2887 var hotKeyAction = buttonConfiguration.action;
2888 var actionFunctionReference = this.makeFunctionReference(buttonConfiguration.action);
2889 buttonConfiguration.action = actionFunctionReference;
2890 if (!buttonConfiguration.textMode) {
2891 buttonConfiguration.textMode = false;
2892 }
2893 if (buttonConfiguration.dialog) {
2894 if (!buttonConfiguration.dimensions) {
2895 buttonConfiguration.dimensions = { width: 250, height: 250};
2896 }
2897 buttonConfiguration.dimensions.top = buttonConfiguration.dimensions.top ? buttonConfiguration.dimensions.top : this.editorConfiguration.dialogueWindows.defaultPositionFromTop;
2898 buttonConfiguration.dimensions.left = buttonConfiguration.dimensions.left ? buttonConfiguration.dimensions.left : this.editorConfiguration.dialogueWindows.defaultPositionFromLeft;
2899 } else {
2900 buttonConfiguration.dialog = false;
2901 }
2902 if (this.editorConfiguration.registerButton(buttonConfiguration)) {
2903 var hotKey = buttonConfiguration.hotKey ? buttonConfiguration.hotKey :
2904 ((this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonConfiguration.id]] && this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonConfiguration.id]].hotKey) ? this.editorConfiguration.buttons[this.editorConfiguration.convertButtonId[buttonConfiguration.id]].hotKey : null);
2905 if (hotKey) {
2906 var hotKeyConfiguration = {
2907 id : hotKey,
2908 cmd : buttonConfiguration.id,
2909 action : hotKeyAction
2910 };
2911 return this.registerHotKey(hotKeyConfiguration);
2912 }
2913 return true;
2914 }
2915 } else {
2916 this.appendToLog("registerButton", "Function " + buttonConfiguration.action + " was not defined when registering button " + buttonConfiguration.id);
2917 }
2918 }
2919 return false;
2920 },
2921
2922 /**
2923 * Registors a drop-down list for inclusion in the toolbar
2924 *
2925 * @param object dropDownConfiguration: the configuration object of the drop-down:
2926 * id : unique id for the drop-down
2927 * tooltip : tooltip for the drop-down
2928 * textMode : enable in text mode
2929 * action : name of the function invoked when a new option is selected
2930 * refresh : name of the function invoked in order to refresh the drop-down when the toolbar is updated
2931 * context : will be disabled if not inside one of listed elements
2932 *
2933 * @return boolean true if the drop-down list was successfully registered
2934 */
2935 registerDropDown : function (dropDownConfiguration) {
2936 if (this.isButtonInToolbar(dropDownConfiguration.id