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