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