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