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