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