6ddc8c18c0dd1588e84761e9176e398c08fe610e
[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 CVS 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("ERROR [HTMLArea.Config::registerHotKey]: A hotkey with the same key " + hotKeyConfiguration.id + " already exists.");
405 return false;
406 }
407 this.hotKeyList[hotKeyConfiguration.id] = hotKeyConfiguration;
408 return true;
409 };
410
411 /***************************************************
412 * EDITOR FRAMEWORK
413 ***************************************************/
414 /*
415 * Update the state of a toolbar element.
416 * This function is member of a toolbar element object, unnamed object created by createButton or createSelect functions.
417 */
418 HTMLArea.setButtonStatus = function(id,newval) {
419 var oldval = this[id];
420 var el = document.getElementById(this.elementId);
421 if (oldval != newval) {
422 switch (id) {
423 case "enabled":
424 if (newval) {
425 if (!HTMLArea.is_wamcom) {
426 HTMLArea._removeClass(el, "buttonDisabled");
427 HTMLArea._removeClass(el.parentNode, "buttonDisabled");
428 }
429 el.disabled = false;
430 } else {
431 if (!HTMLArea.is_wamcom) {
432 HTMLArea._addClass(el, "buttonDisabled");
433 HTMLArea._addClass(el.parentNode, "buttonDisabled");
434 }
435 el.disabled = true;
436 }
437 break;
438 case "active":
439 if (newval) {
440 HTMLArea._addClass(el, "buttonPressed");
441 HTMLArea._addClass(el.parentNode, "buttonPressed");
442 } else {
443 HTMLArea._removeClass(el, "buttonPressed");
444 HTMLArea._removeClass(el.parentNode, "buttonPressed");
445 }
446 break;
447 }
448 this[id] = newval;
449 }
450 };
451
452 /*
453 * Create a new line in the toolbar
454 */
455 HTMLArea.newLine = function(toolbar) {
456 tb_line = document.createElement("ul");
457 tb_line.className = "tb-line";
458 toolbar.appendChild(tb_line);
459 return tb_line;
460 };
461
462 /*
463 * Add a toolbar element to the current line or group
464 */
465 HTMLArea.addTbElement = function(element, tb_line, first_cell_on_line) {
466 var tb_cell = document.createElement("li");
467 if (first_cell_on_line) tb_cell.className = "tb-first-cell";
468 else tb_cell.className = "tb-cell";
469 HTMLArea._addClass(tb_cell, element.className);
470 tb_line.appendChild(tb_cell);
471 tb_cell.appendChild(element);
472 if(element.style.display == "none") {
473 tb_cell.style.display = "none";
474 if(HTMLArea._hasClass(tb_cell.previousSibling, "separator")) tb_cell.previousSibling.style.display = "none";
475 }
476 return false;
477 };
478
479 /*
480 * Create a new group on the current line
481 */
482 HTMLArea.addTbGroup = function(tb_line, first_cell_on_line) {
483 var tb_group = document.createElement("ul");
484 tb_group.className = "tb-group";
485 HTMLArea.addTbElement(tb_group, tb_line, first_cell_on_line);
486 return tb_group;
487 };
488
489 /*
490 * Create a combo box and add it to the toolbar
491 */
492 HTMLArea.prototype.createSelect = function(txt,tb_line,first_cell_on_line,labelObj) {
493 var options = null,
494 cmd = null,
495 context = null,
496 tooltip = "",
497 newObj = {
498 created : false,
499 el : null,
500 first : first_cell_on_line,
501 labelUsed : false
502 };
503
504 cmd = txt;
505 var dropdown = this.config.customSelects[cmd];
506 if (typeof(dropdown) != "undefined") {
507 options = dropdown.options;
508 context = dropdown.context;
509 if (typeof(dropdown.tooltip) != "undefined") tooltip = dropdown.tooltip;
510 }
511 if (options) {
512 newObj["el"] = document.createElement("select");
513 newObj["el"].className = "select";
514 newObj["el"].title = tooltip;
515 newObj["el"].id = this._editorNumber + "-" + txt;
516 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
517 var obj = {
518 name : txt, // field name
519 elementId : newObj["el"].id, // unique id for the UI element
520 enabled : true, // is it enabled?
521 text : false, // enabled in text mode?
522 cmd : cmd, // command ID
523 state : HTMLArea.setButtonStatus, // for changing state
524 context : context,
525 editorNumber : this._editorNumber
526 };
527 this._toolbarObjects[txt] = obj;
528 newObj["el"]._obj = obj;
529 if (labelObj["labelRef"]) {
530 labelObj["el"].htmlFor = newObj["el"].id;
531 newObj["labelUsed"] = true;
532 }
533 HTMLArea._addEvent(newObj["el"], "change", HTMLArea.toolBarButtonHandler);
534
535 for (var i in options) {
536 if (options.hasOwnProperty(i)) {
537 var op = document.createElement("option");
538 op.innerHTML = i;
539 op.value = options[i];
540 newObj["el"].appendChild(op);
541 }
542 }
543
544 newObj["created"] = true;
545 }
546
547 return newObj;
548 };
549
550 /*
551 * Create a button and add it to the toolbar
552 */
553 HTMLArea.prototype.createButton = function (txt,tb_line,first_cell_on_line,labelObj) {
554 var btn = null,
555 newObj = {
556 created : false,
557 el : null,
558 first : first_cell_on_line,
559 labelUsed : false
560 };
561
562 switch (txt) {
563 case "separator":
564 newObj["el"] = document.createElement("div");
565 newObj["el"].className = "separator";
566 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
567 newObj["created"] = true;
568 break;
569 case "space":
570 newObj["el"] = document.createElement("div");
571 newObj["el"].className = "space";
572 newObj["el"].innerHTML = "&nbsp;";
573 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
574 newObj["created"] = true;
575 break;
576 case "TextIndicator":
577 newObj["el"] = document.createElement("div");
578 newObj["el"].appendChild(document.createTextNode("A"));
579 newObj["el"].className = "indicator";
580 newObj["el"].title = HTMLArea.I18N.tooltips.textindicator;
581 newObj["el"].id = this._editorNumber + "-" + txt;
582 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
583 var obj = {
584 name : txt,
585 elementId : newObj["el"].id,
586 enabled : true,
587 active : false,
588 text : false,
589 cmd : "TextIndicator",
590 state : HTMLArea.setButtonStatus
591 };
592 this._toolbarObjects[txt] = obj;
593 newObj["created"] = true;
594 break;
595 default:
596 btn = this.config.btnList[txt];
597 }
598 if(!newObj["created"] && btn) {
599 newObj["el"] = document.createElement("button");
600 newObj["el"].title = btn[0];
601 newObj["el"].className = "button";
602 newObj["el"].id = this._editorNumber + "-" + txt;
603 if (btn[5]) newObj["el"].style.display = "none";
604 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
605 var obj = {
606 name : txt, // the button name
607 elementId : newObj["el"].id, // unique id for the UI element
608 enabled : true, // is it enabled?
609 active : false, // is it pressed?
610 text : btn[2], // enabled in text mode?
611 cmd : btn[3], // the function to be invoked
612 state : HTMLArea.setButtonStatus, // for changing state
613 context : btn[4] || null, // enabled in a certain context?
614 selection : btn[6], // disabled when no selection?
615 editorNumber : this._editorNumber
616 };
617 this._toolbarObjects[txt] = obj;
618 newObj["el"]._obj = obj;
619 if (labelObj["labelRef"]) {
620 labelObj["el"].htmlFor = newObj["el"].id;
621 newObj["labelUsed"] = true;
622 }
623 HTMLArea._addEvents(newObj["el"],["mouseover", "mouseout", "mousedown", "click"], HTMLArea.toolBarButtonHandler);
624 newObj["el"].className += " " + txt;
625 newObj["created"] = true;
626 }
627 return newObj;
628 };
629
630 /*
631 * Create a label and add it to the toolbar
632 */
633 HTMLArea.createLabel = function(txt,tb_line,first_cell_on_line) {
634 var newObj = {
635 created : false,
636 el : null,
637 labelRef : false,
638 first : first_cell_on_line
639 };
640 if (/^([IT])\[(.*?)\]/.test(txt)) {
641 var l7ed = RegExp.$1 == "I"; // localized?
642 var label = RegExp.$2;
643 if (l7ed) label = HTMLArea.I18N.dialogs[label];
644 newObj["el"] = document.createElement("label");
645 newObj["el"].className = "label";
646 newObj["el"].innerHTML = label;
647 newObj["labelRef"] = true;
648 newObj["created"] = true;
649 newObj["first"] = HTMLArea.addTbElement(newObj["el"], tb_line, first_cell_on_line);
650 }
651 return newObj;
652 };
653
654 /*
655 * Create the toolbar and append it to the _htmlarea.
656 */
657 HTMLArea.prototype._createToolbar = function () {
658 var j, k, code, n = this.config.toolbar.length, m,
659 tb_line = null, tb_group = null,
660 first_cell_on_line = true,
661 labelObj = new Object(),
662 tbObj = new Object();
663
664 var toolbar = document.createElement("div");
665 this._toolbar = toolbar;
666 toolbar.className = "toolbar";
667 toolbar.unselectable = "1";
668 this._toolbarObjects = new Object();
669
670 for (j = 0; j < n; ++j) {
671 tb_line = HTMLArea.newLine(toolbar);
672 if(!this.config.keepButtonGroupTogether) HTMLArea._addClass(tb_line, "free-float");
673 first_cell_on_line = true;
674 tb_group = null;
675 var group = this.config.toolbar[j];
676 m = group.length;
677 for (k = 0; k < m; ++k) {
678 code = group[k];
679 if (code == "linebreak") {
680 tb_line = HTMLArea.newLine(toolbar);
681 if(!this.config.keepButtonGroupTogether) HTMLArea._addClass(tb_line, "free-float");
682 first_cell_on_line = true;
683 tb_group = null;
684 } else {
685 if ((code == "separator" || first_cell_on_line) && this.config.keepButtonGroupTogether) {
686 tb_group = HTMLArea.addTbGroup(tb_line, first_cell_on_line);
687 first_cell_on_line = false;
688 }
689 created = false;
690 if (/^([IT])\[(.*?)\]/.test(code)) {
691 labelObj = HTMLArea.createLabel(code, (tb_group?tb_group:tb_line), first_cell_on_line);
692 created = labelObj["created"] ;
693 first_cell_on_line = labelObj["first"];
694 }
695 if (!created) {
696 tbObj = this.createButton(code, (tb_group?tb_group:tb_line), first_cell_on_line, labelObj);
697 created = tbObj["created"];
698 first_cell_on_line = tbObj["first"];
699 if(tbObj["labelUsed"]) labelObj["labelRef"] = false;
700 }
701 if (!created) {
702 tbObj = this.createSelect(code, (tb_group?tb_group:tb_line), first_cell_on_line, labelObj);
703 created = tbObj["created"];
704 first_cell_on_line = tbObj["first"];
705 if(tbObj["labelUsed"]) labelObj["labelRef"] = false;
706 }
707 if (!created) HTMLArea._appendToLog("ERROR [HTMLArea::createToolbar]: Unknown toolbar item: " + code);
708 }
709 }
710 }
711
712 tb_line = HTMLArea.newLine(toolbar);
713 this._htmlArea.appendChild(toolbar);
714 };
715
716 /*
717 * Handle toolbar element events handler
718 */
719 HTMLArea.toolBarButtonHandler = function(ev) {
720 if(!ev) var ev = window.event;
721 var target = (ev.target) ? ev.target : ev.srcElement;
722 while (target.tagName.toLowerCase() == "img" || target.tagName.toLowerCase() == "div") target = target.parentNode;
723 var obj = target._obj;
724 var editorNumber = obj["editorNumber"];
725 var editor = RTEarea[editorNumber]["editor"];
726 if (obj.enabled) {
727 switch (ev.type) {
728 case "mouseover":
729 HTMLArea._addClass(target, "buttonHover");
730 HTMLArea._addClass(target.parentNode, "buttonHover");
731 break;
732 case "mouseout":
733 HTMLArea._removeClass(target, "buttonHover");
734 HTMLArea._removeClass(target.parentNode, "buttonHover");
735 HTMLArea._removeClass(target, "buttonActive");
736 HTMLArea._removeClass(target.parentNode, "buttonActive");
737 if (obj.active) {
738 HTMLArea._addClass(target, "buttonPressed");
739 HTMLArea._addClass(target.parentNode, "buttonPressed");
740 }
741 break;
742 case "mousedown":
743 HTMLArea._addClass(target, "buttonActive");
744 HTMLArea._addClass(target.parentNode, "buttonActive");
745 HTMLArea._removeClass(target, "buttonPressed");
746 HTMLArea._removeClass(target.parentNode, "buttonPressed");
747 HTMLArea._stopEvent(ev);
748 break;
749 case "click":
750 HTMLArea._removeClass(target, "buttonActive");
751 HTMLArea._removeClass(target.parentNode, "buttonActive");
752 HTMLArea._removeClass(target, "buttonHover");
753 HTMLArea._removeClass(target.parentNode, "buttonHover");
754 obj.cmd(editor, obj.name);
755 HTMLArea._stopEvent(ev);
756 if (HTMLArea.is_opera) {
757 editor._iframe.focus();
758 }
759 if (!editor.config.btnList[obj.name][7]) {
760 editor.updateToolbar();
761 }
762 break;
763 case "change":
764 editor.focusEditor();
765 var dropdown = editor.config.customSelects[obj.name];
766 if (typeof(dropdown) !== "undefined") {
767 dropdown.action(editor, obj.name);
768 HTMLArea._stopEvent(ev);
769 if (HTMLArea.is_opera) {
770 editor._iframe.focus();
771 }
772 editor.updateToolbar();
773 } else {
774 HTMLArea._appendToLog("ERROR [HTMLArea::toolBarButtonHandler]: Combo box " + obj.name + " not registered.");
775 }
776 }
777 }
778 };
779
780 /*
781 * Create the status bar
782 */
783 HTMLArea.prototype._createStatusBar = function() {
784 var statusBar = document.createElement("div");
785 this._statusBar = statusBar;
786 statusBar.className = "statusBar";
787 if (!this.config.statusBar) statusBar.style.display = "none";
788 var statusBarTree = document.createElement("span");
789 this._statusBarTree = statusBarTree;
790 statusBarTree.className = "statusBarTree";
791 statusBar.appendChild(statusBarTree);
792 statusBarTree.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": "));
793 this._htmlArea.appendChild(statusBar);
794 };
795
796 /*
797 * Create the htmlArea iframe and replace the textarea with it.
798 */
799 HTMLArea.prototype.generate = function () {
800
801 // get the textarea and hide it
802 var textarea = this._textArea;
803 if (typeof(textarea) == "string") {
804 textarea = HTMLArea.getElementById("textarea", textarea);
805 this._textArea = textarea;
806 }
807 textarea.style.display = "none";
808
809 // create the editor framework and insert the editor before the textarea
810 var htmlarea = document.createElement("div");
811 htmlarea.className = "htmlarea";
812 htmlarea.style.width = textarea.style.width;
813 this._htmlArea = htmlarea;
814 textarea.parentNode.insertBefore(htmlarea, textarea);
815
816 if(textarea.form) {
817 // we have a form, on reset, re-initialize the HTMLArea content and update the toolbar
818 var f = textarea.form;
819 if (typeof(f.onreset) == "function") {
820 var funcref = f.onreset;
821 if (typeof(f.__msh_prevOnReset) == "undefined") f.__msh_prevOnReset = [];
822 f.__msh_prevOnReset.push(funcref);
823 }
824 f._editorNumber = this._editorNumber;
825 HTMLArea._addEvent(f, "reset", HTMLArea.resetHandler);
826 }
827
828 // create & append the toolbar
829 this._createToolbar();
830 HTMLArea._appendToLog("[HTMLArea::generate]: Toolbar successfully created.");
831
832 // create and append the IFRAME
833 var iframe = document.createElement("iframe");
834 if (HTMLArea.is_ie || HTMLArea.is_safari || HTMLArea.is_wamcom) {
835 iframe.setAttribute("src",_editor_url + "popups/blank.html");
836 } else if (HTMLArea.is_opera) {
837 iframe.setAttribute("src",_typo3_host_url + _editor_url + "popups/blank.html");
838 } else {
839 iframe.setAttribute("src","javascript:void(0);");
840 }
841 iframe.className = "editorIframe";
842 if (!this.config.statusBar) iframe.className += " noStatusBar";
843 htmlarea.appendChild(iframe);
844 this._iframe = iframe;
845
846 // create & append the status bar
847 this._createStatusBar();
848
849 // size the iframe
850 this.sizeIframe(2);
851
852 HTMLArea._appendToLog("[HTMLArea::generate]: Editor iframe successfully created.");
853 this.initIframe();
854 return this;
855 };
856
857 /*
858 * Size the iframe according to user's prefs or initial textarea
859 */
860 HTMLArea.prototype.sizeIframe = function(diff) {
861 var height = (this.config.height == "auto" ? (this._textArea.style.height) : this.config.height);
862 var textareaHeight = height;
863 // All nested tabs and inline levels in the sorting order they were applied:
864 this.nested = {};
865 this.nested.all = RTEarea[this._editorNumber].tceformsNested;
866 this.nested.sorted = HTMLArea.simplifyNested(this.nested.all);
867 // Clone the array instead of using a reference (this.accessParentElements will change the array):
868 var parentElements = (this.nested.sorted && this.nested.sorted.length ? [].concat(this.nested.sorted) : []);
869 // Walk through all nested tabs and inline levels to make a correct positioning:
870 var dimensions = this.accessParentElements(parentElements, 'this.getDimensions()');
871
872 if(height.indexOf("%") == -1) {
873 height = parseInt(height) - diff;
874 if (this.config.sizeIncludesToolbar) {
875 this._initialToolbarOffsetHeight = dimensions.toolbar.height;
876 height -= dimensions.toolbar.height;
877 height -= dimensions.statusbar.height;
878 }
879 if (height < 0) height = 0;
880 textareaHeight = (height - 4);
881 if (textareaHeight < 0) textareaHeight = 0;
882 height += "px";
883 textareaHeight += "px";
884 }
885 this._iframe.style.height = height;
886 this._textArea.style.height = textareaHeight;
887 var textareaWidth = (this.config.width == "auto" ? this._textArea.style.width : this.config.width);
888 var iframeWidth = textareaWidth;
889 if(textareaWidth.indexOf("%") == -1) {
890 iframeWidth = parseInt(textareaWidth) + "px";
891 textareaWidth = parseInt(textareaWidth) - diff;
892 if (textareaWidth < 0) textareaWidth = 0;
893 textareaWidth += 'px';
894 }
895 this._iframe.style.width = "100%";
896 if (HTMLArea.is_opera) this._iframe.style.width = iframeWidth;
897 this._textArea.style.width = textareaWidth;
898 };
899
900 /**
901 * Get the dimensions of the toolbar and statusbar.
902 *
903 * @return object An object with width/height pairs for statusbar and toolbar.
904 * @author Oliver Hader <oh@inpublica.de>
905 */
906 HTMLArea.prototype.getDimensions = function() {
907 return {
908 toolbar: {width: this._toolbar.offsetWidth, height: this._toolbar.offsetHeight},
909 statusbar: {width: this._statusBar.offsetWidth, height: this._statusBar.offsetHeight}
910 };
911 };
912
913 /**
914 * Access an inline relational element or tab menu and make it "accesible".
915 * If a parent object has the style "display: none", offsetWidth & offsetHeight are '0'.
916 *
917 * @params object callbackFunc: A function to be called, when the embedded objects are "accessible".
918 * @return object An object returned by the callbackFunc.
919 * @author Oliver Hader <oh@inpublica.de>
920 */
921 HTMLArea.prototype.accessParentElements = function(parentElements, callbackFunc) {
922 var result = {};
923
924 if (parentElements.length) {
925 var currentElement = parentElements.pop();
926 var elementStyle = document.getElementById(currentElement).style;
927 var actionRequired = (elementStyle.display == 'none' ? true : false);
928
929 if (actionRequired) {
930 var originalVisibility = elementStyle.visibility;
931 var originalPosition = elementStyle.position;
932 elementStyle.visibility = 'hidden';
933 elementStyle.position = 'absolute';
934 elementStyle.display = '';
935 }
936
937 result = this.accessParentElements(parentElements, callbackFunc);
938
939 if (actionRequired) {
940 elementStyle.display = 'none';
941 elementStyle.position = originalPosition;
942 elementStyle.visibility = originalVisibility;
943 }
944
945 } else {
946 result = eval(callbackFunc);
947
948 }
949
950 return result;
951 };
952
953 /**
954 * Simplify the array of nested levels. Create an indexed array with the correct names of the elements.
955 *
956 * @param object nested: The array with the nested levels
957 * @return object The simplified array
958 * @author Oliver Hader <oh@inpublica.de>
959 */
960 HTMLArea.simplifyNested = function(nested) {
961 var i, type, level, max, simplifiedNested=[];
962 if (nested && nested.length) {
963 if (nested[0][0]=='inline') {
964 nested = inline.findContinuedNestedLevel(nested, nested[0][1]);
965 }
966 for (i=0, max=nested.length; i<max; i++) {
967 type = nested[i][0];
968 level = nested[i][1];
969 if (type=='tab') {
970 simplifiedNested.push(level+'-DIV');
971 } else if (type=='inline') {
972 simplifiedNested.push(level+'_fields');
973 }
974 }
975 }
976 return simplifiedNested;
977 };
978
979 /*
980 * Initialize the iframe
981 */
982 HTMLArea.initIframe = function(editorNumber) {
983 var editor = RTEarea[editorNumber]["editor"];
984 editor.initIframe();
985 };
986
987 HTMLArea.prototype.initIframe = function() {
988 if (this._initIframeTimer) window.clearTimeout(this._initIframeTimer);
989 if (!this._iframe || (!this._iframe.contentWindow && !this._iframe.contentDocument)) {
990 this._initIframeTimer = window.setTimeout("HTMLArea.initIframe(" + this._editorNumber + ");", 50);
991 return false;
992 } else if (this._iframe.contentWindow && !HTMLArea.is_safari) {
993 if (!this._iframe.contentWindow.document || !this._iframe.contentWindow.document.documentElement) {
994 this._initIframeTimer = window.setTimeout("HTMLArea.initIframe(" + this._editorNumber + ");", 50);
995 return false;
996 }
997 } else if (!this._iframe.contentDocument.documentElement || !this._iframe.contentDocument.body) {
998 this._initIframeTimer = window.setTimeout("HTMLArea.initIframe(" + this._editorNumber + ");", 50);
999 return false;
1000 }
1001 var doc = this._iframe.contentWindow ? this._iframe.contentWindow.document : this._iframe.contentDocument;
1002 this._doc = doc;
1003
1004 if (!this.config.fullPage) {
1005 var head = doc.getElementsByTagName("head")[0];
1006 if (!head) {
1007 head = doc.createElement("head");
1008 doc.documentElement.appendChild(head);
1009 }
1010 if (this.config.baseURL && !HTMLArea.is_opera) {
1011 var base = doc.getElementsByTagName("base")[0];
1012 if (!base) {
1013 base = doc.createElement("base");
1014 base.href = this.config.baseURL;
1015 head.appendChild(base);
1016 }
1017 HTMLArea._appendToLog("[HTMLArea::initIframe]: Iframe baseURL set to: " + this.config.baseURL);
1018 }
1019 var link0 = doc.getElementsByTagName("link")[0];
1020 if (!link0) {
1021 link0 = doc.createElement("link");
1022 link0.rel = "stylesheet";
1023 link0.href = this.config.editedContentStyle;
1024 head.appendChild(link0);
1025 HTMLArea._appendToLog("[HTMLArea::initIframe]: Skin CSS set to: " + this.config.editedContentStyle);
1026 }
1027 if (this.config.defaultPageStyle) {
1028 var link = doc.getElementsByTagName("link")[1];
1029 if (!link) {
1030 link = doc.createElement("link");
1031 link.rel = "stylesheet";
1032 link.href = this.config.defaultPageStyle;
1033 head.appendChild(link);
1034 }
1035 HTMLArea._appendToLog("[HTMLArea::initIframe]: Override CSS set to: " + this.config.defaultPageStyle);
1036 }
1037 if (this.config.pageStyle) {
1038 var link = doc.getElementsByTagName("link")[2];
1039 if (!link) {
1040 link = doc.createElement("link");
1041 link.rel = "stylesheet";
1042 link.href = this.config.pageStyle;
1043 head.appendChild(link);
1044 }
1045 HTMLArea._appendToLog("[HTMLArea::initIframe]: Content CSS set to: " + this.config.pageStyle);
1046 }
1047 } else {
1048 var html = this._textArea.value;
1049 this.setFullHTML(html);
1050 }
1051 HTMLArea._appendToLog("[HTMLArea::initIframe]: Editor iframe head successfully initialized.");
1052
1053 this.stylesLoaded();
1054 };
1055
1056 /*
1057 * Finalize editor Iframe initialization after loading the style sheets
1058 */
1059 HTMLArea.stylesLoaded = function(editorNumber) {
1060 var editor = RTEarea[editorNumber]["editor"];
1061 editor.stylesLoaded();
1062 };
1063
1064 HTMLArea.prototype.stylesLoaded = function() {
1065 var doc = this._doc;
1066 var docWellFormed = true;
1067
1068 // check if the stylesheets have been loaded
1069
1070 if (this._stylesLoadedTimer) window.clearTimeout(this._stylesLoadedTimer);
1071 var stylesAreLoaded = true;
1072 var errorText = '';
1073 var rules;
1074 for (var rule = 0; rule < doc.styleSheets.length; rule++) {
1075 if (HTMLArea.is_gecko) try { rules = doc.styleSheets[rule].cssRules; } catch(e) { stylesAreLoaded = false; errorText = e; }
1076 if (HTMLArea.is_ie) try { rules = doc.styleSheets[rule].rules; } catch(e) { stylesAreLoaded = false; errorText = e; }
1077 if (HTMLArea.is_ie) try { rules = doc.styleSheets[rule].imports; } catch(e) { stylesAreLoaded = false; errorText = e; }
1078 }
1079 if (!stylesAreLoaded && !HTMLArea.is_wamcom) {
1080 HTMLArea._appendToLog("[HTMLArea::initIframe]: Failed attempt at loading stylesheets: " + errorText + " Retrying...");
1081 this._stylesLoadedTimer = window.setTimeout("HTMLArea.stylesLoaded(" + this._editorNumber + ");", 100);
1082 return false;
1083 }
1084 HTMLArea._appendToLog("[HTMLArea::initIframe]: Stylesheets successfully loaded.");
1085
1086 if (!this.config.fullPage) {
1087 doc.body.style.borderWidth = "0px";
1088 doc.body.className = "htmlarea-content-body";
1089 try {
1090 doc.body.innerHTML = this._textArea.value;
1091 } catch(e) {
1092 HTMLArea._appendToLog("[HTMLArea::initIframe]: The HTML document is not well-formed.");
1093 alert(HTMLArea.I18N.msg["HTML-document-not-well-formed"]);
1094 docWellFormed = false;
1095 }
1096 }
1097 // Start undo snapshots
1098 if (this._customUndo) this._timerUndo = window.setInterval("HTMLArea.undoTakeSnapshot(" + this._editorNumber + ");", this.config.undoTimeout);
1099
1100 // Set contents editable
1101 if (docWellFormed) {
1102 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera && !this._initEditMode()) {
1103 return false;
1104 }
1105 if (HTMLArea.is_ie || HTMLArea.is_safari) {
1106 doc.body.contentEditable = true;
1107 }
1108 if (HTMLArea.is_opera || HTMLArea.is_safari) {
1109 doc.designMode = "on";
1110 if (this._doc.queryCommandEnabled("insertbronreturn")) this._doc.execCommand("insertbronreturn", false, this.config.disableEnterParagraphs);
1111 if (this._doc.queryCommandEnabled("styleWithCSS")) this._doc.execCommand("styleWithCSS", false, this.config.useCSS);
1112 }
1113 if (HTMLArea.is_ie) doc.selection.empty();
1114 this._editMode = "wysiwyg";
1115 if (doc.body.contentEditable || doc.designMode == "on") HTMLArea._appendToLog("[HTMLArea::initIframe]: Design mode successfully set.");
1116 } else {
1117 this._editMode = "textmode";
1118 this.setMode("docnotwellformedmode");
1119 HTMLArea._appendToLog("[HTMLArea::initIframe]: Design mode could not be set.");
1120 }
1121
1122 // set editor number in iframe and document for retrieval in event handlers
1123 doc._editorNo = this._editorNumber;
1124 if (HTMLArea.is_ie) doc.documentElement._editorNo = this._editorNumber;
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 plugin = RegExp.$1;
1474 var popup = RegExp.$2;
1475 if(!/\.html$/.test(popup)) popup += ".html";
1476 url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
1477 } else {
1478 url = _typo3_host_url + _editor_url + this.config.popupURL + file;
1479 }
1480 return url;
1481 };
1482
1483 /***************************************************
1484 * EDITOR UTILITIES
1485 ***************************************************/
1486 HTMLArea.getInnerText = function(el) {
1487 var txt = '', i;
1488 if(el.firstChild) {
1489 for(i=el.firstChild;i;i =i.nextSibling) {
1490 if(i.nodeType == 3) txt += i.data;
1491 else if(i.nodeType == 1) txt += HTMLArea.getInnerText(i);
1492 }
1493 } else {
1494 if(el.nodeType == 3) txt = el.data;
1495 }
1496 return txt;
1497 };
1498
1499 HTMLArea.prototype.forceRedraw = function() {
1500 this._doc.body.style.visibility = "hidden";
1501 this._doc.body.style.visibility = "visible";
1502 };
1503
1504 /*
1505 * Focus the editor iframe document or the textarea.
1506 */
1507 HTMLArea.prototype.focusEditor = function() {
1508 switch (this._editMode) {
1509 case "wysiwyg" :
1510 try {
1511 if (HTMLArea.is_safari) {
1512 this._iframe.focus();
1513 } else if (HTMLArea.is_opera) {
1514 this._doc.focus();
1515 } else {
1516 this._iframe.contentWindow.focus();
1517 }
1518 } catch(e) { }
1519 break;
1520 case "textmode":
1521 this._textArea.focus();
1522 break;
1523 }
1524 return this._doc;
1525 };
1526
1527 HTMLArea.undoTakeSnapshot = function(editorNumber) {
1528 var editor = RTEarea[editorNumber].editor;
1529 if (editor._doc) {
1530 editor._undoTakeSnapshot();
1531 }
1532 };
1533
1534 /*
1535 * Take a snapshot of the current contents for undo
1536 */
1537 HTMLArea.prototype._undoTakeSnapshot = function () {
1538 var currentTime = (new Date()).getTime();
1539 var newSnapshot = false, bookmark = null, bookmarkedText = null;
1540 if (this._undoPos >= this.config.undoSteps) {
1541 // Remove the first element
1542 this._undoQueue.shift();
1543 --this._undoPos;
1544 }
1545 // New undo slot should be used if this is first undoTakeSnapshot call or if undoTimeout is elapsed
1546 if (this._undoPos < 0 || this._undoQueue[this._undoPos].time < currentTime - this.config.undoTimeout) {
1547 ++this._undoPos;
1548 newSnapshot = true;
1549 }
1550 // Insert a bookmark
1551 if (this.getMode() === "wysiwyg") {
1552 var selection = this._getSelection();
1553 var bookmark = (!(HTMLArea.is_ie && selection.type.toLowerCase() == "control") && !HTMLArea.is_opera) ? this.getBookmark(this._createRange(selection)) : null;
1554 }
1555 // Get the bookmarked html text and remove the bookmark
1556 if (bookmark) {
1557 bookmarkedText = this.getInnerHTML();
1558 var range = this.moveToBookmark(bookmark);
1559 }
1560 // Get the html text
1561 var txt = this.getInnerHTML();
1562
1563 if (newSnapshot) {
1564 // If previous slot contains the same text, a new one should not be used
1565 if (this._undoPos == 0 || this._undoQueue[this._undoPos - 1].text != txt) {
1566 this._undoQueue[this._undoPos] = {
1567 text: txt,
1568 time: currentTime,
1569 bookmark: bookmark,
1570 bookmarkedText : bookmarkedText
1571 };
1572 this._undoQueue.length = this._undoPos + 1;
1573 if (this._undoPos == 1) {
1574 this.updateToolbar();
1575 }
1576 } else {
1577 this._undoPos--;
1578 }
1579 } else {
1580 if (this._undoQueue[this._undoPos].text != txt){
1581 this._undoQueue[this._undoPos].text = txt;
1582 this._undoQueue[this._undoPos].bookmark = bookmark;
1583 this._undoQueue[this._undoPos].bookmarkedText = bookmarkedText;
1584 this._undoQueue.length = this._undoPos + 1;
1585 }
1586 }
1587 };
1588
1589 HTMLArea.prototype.undo = function () {
1590 if (this._undoPos > 0) {
1591 // Make sure we would not loose any changes
1592 this._undoTakeSnapshot();
1593 var bookmark = this._undoQueue[--this._undoPos].bookmark;
1594 if (bookmark && this._undoPos) {
1595 this.setHTML(this._undoQueue[this._undoPos].bookmarkedText);
1596 this.focusEditor();
1597 this.selectRange(this.moveToBookmark(bookmark));
1598 this.scrollToCaret();
1599 } else {
1600 this.setHTML(this._undoQueue[this._undoPos].text);
1601 }
1602 }
1603 };
1604
1605 HTMLArea.prototype.redo = function () {
1606 if (this._undoPos < this._undoQueue.length - 1) {
1607 // Make sure we would not loose any changes
1608 this._undoTakeSnapshot();
1609 // Previous call could make undo queue shorter
1610 if (this._undoPos < this._undoQueue.length - 1) {
1611 var bookmark = this._undoQueue[++this._undoPos].bookmark;
1612 if (bookmark) {
1613 this.setHTML(this._undoQueue[this._undoPos].bookmarkedText);
1614 this.focusEditor();
1615 this.selectRange(this.moveToBookmark(bookmark));
1616 this.scrollToCaret();
1617 } else {
1618 this.setHTML(this._undoQueue[this._undoPos].text);
1619 }
1620 }
1621 }
1622 };
1623
1624 /*
1625 * Update the enabled/disabled/active state of the toolbar elements
1626 */
1627 HTMLArea.updateToolbar = function(editorNumber) {
1628 var editor = RTEarea[editorNumber]["editor"];
1629 editor.updateToolbar();
1630 editor._timerToolbar = null;
1631 };
1632
1633 HTMLArea.prototype.updateToolbar = function(noStatus) {
1634 var doc = this._doc,
1635 text = (this._editMode == "textmode"),
1636 selection = false,
1637 ancestors = null, cls = new Array(),
1638 txt, txtClass, i, inContext, match, matchAny, k, j, n, commandState;
1639 if(!text) {
1640 selection = !this._selectionEmpty(this._getSelection());
1641 ancestors = this.getAllAncestors();
1642 if(this.config.statusBar && !noStatus) {
1643 // Unhook previous events handlers
1644 if(this._statusBarTree.hasChildNodes()) {
1645 for (i = this._statusBarTree.firstChild; i; i = i.nextSibling) {
1646 if(i.nodeName.toLowerCase() == "a") {
1647 HTMLArea._removeEvents(i,["click", "contextmenu, mousedown"], HTMLArea.statusBarHandler);
1648 i.el = null;
1649 i.editor = null;
1650 }
1651 }
1652 }
1653 this._statusBarTree.selected = null;
1654 this._statusBarTree.innerHTML = '';
1655 this._statusBarTree.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); // clear
1656 for (i = ancestors.length; --i >= 0;) {
1657 var el = ancestors[i];
1658 if(!el) continue;
1659 var a = document.createElement("a");
1660 a.href = "#";
1661 a.el = el;
1662 a.editor = this;
1663 if (!HTMLArea.is_opera) {
1664 HTMLArea._addEvents(a, ["click", "contextmenu"], HTMLArea.statusBarHandler);
1665 } else {
1666 HTMLArea._addEvents(a, ["mousedown", "click"], HTMLArea.statusBarHandler);
1667 }
1668 txt = el.tagName.toLowerCase();
1669 a.title = el.style.cssText;
1670 if (el.id) { txt += "#" + el.id; }
1671 if (el.className) {
1672 txtClass = "";
1673 cls = el.className.trim().split(" ");
1674 for (j = 0; j < cls.length; ++j) {
1675 if (!HTMLArea.reservedClassNames.test(cls[j])) {
1676 txtClass += "." + cls[j];
1677 }
1678 }
1679 txt += txtClass;
1680 }
1681 a.appendChild(document.createTextNode(txt));
1682 this._statusBarTree.appendChild(a);
1683 if (i != 0) this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
1684 }
1685 }
1686 }
1687 for (var cmd in this._toolbarObjects) {
1688 if (this._toolbarObjects.hasOwnProperty(cmd)) {
1689 var btn = this._toolbarObjects[cmd];
1690
1691 // Determine if the button should be enabled
1692 inContext = true;
1693 if (btn.context && !text) {
1694 inContext = false;
1695 var attrs = [];
1696 var contexts = [];
1697 if (/(.*)\[(.*?)\]/.test(btn.context)) {
1698 contexts = RegExp.$1.split(",");
1699 attrs = RegExp.$2.split(",");
1700 } else {
1701 contexts = btn.context.split(",");
1702 }
1703 for (j = contexts.length; --j >= 0;) contexts[j] = contexts[j].toLowerCase();
1704 matchAny = (contexts[0] == "*");
1705 for (k = 0; k < ancestors.length; ++k) {
1706 if (!ancestors[k]) continue;
1707 match = false;
1708 for (j = contexts.length; --j >= 0;) match = match || (ancestors[k].tagName.toLowerCase() == contexts[j]);
1709 if (matchAny || match) {
1710 inContext = true;
1711 for (j = attrs.length; --j >= 0;) {
1712 if (!eval("ancestors[k]." + attrs[j])) {
1713 inContext = false;
1714 break;
1715 }
1716 }
1717 if (inContext) break;
1718 }
1719 }
1720 }
1721
1722 if (cmd == "CreateLink") {
1723 btn.state("enabled", (!text || btn.text) && (inContext || selection));
1724 } else {
1725 btn.state("enabled", (!text || btn.text) && inContext && (selection || !btn.selection));
1726 }
1727 if (typeof(cmd) == "function") { continue; };
1728 // look-it-up in the custom dropdown boxes
1729 var dropdown = this.config.customSelects[cmd];
1730 if ((!text || btn.text) && (typeof(dropdown) !== "undefined") && (typeof(dropdown.refresh) === "function")) {
1731 dropdown.refresh(this, cmd);
1732 continue;
1733 }
1734 switch (cmd) {
1735 case "TextIndicator":
1736 if(!text) {
1737 try {with (document.getElementById(btn.elementId).style) {
1738 backgroundColor = HTMLArea._makeColor(doc.queryCommandValue((HTMLArea.is_ie || HTMLArea.is_safari) ? "BackColor" : "HiliteColor"));
1739 // Mozilla
1740 if(/transparent/i.test(backgroundColor)) { backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("BackColor")); }
1741 color = HTMLArea._makeColor(doc.queryCommandValue("ForeColor"));
1742 fontFamily = doc.queryCommandValue("FontName");
1743 // Check if queryCommandState is available
1744 fontWeight = "normal";
1745 fontStyle = "normal";
1746 try { fontWeight = doc.queryCommandState("Bold") ? "bold" : "normal"; } catch(ex) { fontWeight = "normal"; };
1747 try { fontStyle = doc.queryCommandState("Italic") ? "italic" : "normal"; } catch(ex) { fontStyle = "normal"; };
1748 }} catch (e) {
1749 // alert(e + "\n\n" + cmd);
1750 }
1751 }
1752 break;
1753 case "HtmlMode":
1754 btn.state("active", text);
1755 break;
1756 case "Paste":
1757 if (!text) {
1758 try {
1759 btn.state("enabled", doc.queryCommandEnabled('Paste'));
1760 } catch(e) {
1761 btn.state("enabled", false);
1762 }
1763 }
1764 break;
1765 case "Undo":
1766 btn.state("enabled", !text && (!this._customUndo || this._undoPos > 0));
1767 break;
1768 case "Redo":
1769 btn.state("enabled", !text && (!this._customUndo || this._undoPos < this._undoQueue.length-1));
1770 break;
1771 default:
1772 break;
1773 }
1774 }
1775 }
1776
1777 if (this._customUndo) {
1778 this._undoTakeSnapshot();
1779 }
1780 for (var pluginId in this.plugins) {
1781 if (this.plugins.hasOwnProperty(pluginId)) {
1782 var pluginInstance = this.plugins[pluginId].instance;
1783 if (typeof(pluginInstance.onUpdateToolbar) === "function") {
1784 pluginInstance.onUpdateToolbar();
1785 }
1786 }
1787 }
1788 };
1789
1790 /***************************************************
1791 * DOM TREE MANIPULATION
1792 ***************************************************/
1793
1794 /*
1795 * Surround the currently selected HTML source code with the given tags.
1796 * Delete the selection, if any.
1797 */
1798 HTMLArea.prototype.surroundHTML = function(startTag,endTag) {
1799 this.insertHTML(startTag + this.getSelectedHTML().replace(HTMLArea.Reg_body, "") + endTag);
1800 };
1801
1802 /*
1803 * Change the tag name of a node.
1804 */
1805 HTMLArea.prototype.convertNode = function(el,newTagName) {
1806 var newel = this._doc.createElement(newTagName), p = el.parentNode;
1807 while (el.firstChild) newel.appendChild(el.firstChild);
1808 p.insertBefore(newel, el);
1809 p.removeChild(el);
1810 return newel;
1811 };
1812
1813 /*
1814 * Find a parent of an element with a specified tag
1815 */
1816 HTMLArea.getElementObject = function(el,tagName) {
1817 var oEl = el;
1818 while (oEl != null && oEl.nodeName.toLowerCase() != tagName) oEl = oEl.parentNode;
1819 return oEl;
1820 };
1821
1822 /***************************************************
1823 * SELECTIONS AND RANGES
1824 ***************************************************/
1825
1826 /*
1827 * Return true if we have some selected content
1828 */
1829 HTMLArea.prototype.hasSelectedText = function() {
1830 return this.getSelectedHTML() != "";
1831 };
1832
1833 /*
1834 * Get an array with all the ancestor nodes of the selection.
1835 */
1836 HTMLArea.prototype.getAllAncestors = function() {
1837 var p = this.getParentElement();
1838 var a = [];
1839 while (p && (p.nodeType === 1) && (p.nodeName.toLowerCase() !== "body")) {
1840 a.push(p);
1841 p = p.parentNode;
1842 }
1843 a.push(this._doc.body);
1844 return a;
1845 };
1846
1847 /*
1848 * Get the block elements containing the start and the end points of the selection
1849 */
1850 HTMLArea.prototype.getEndBlocks = function(selection) {
1851 var range = this._createRange(selection);
1852 if (HTMLArea.is_gecko) {
1853 var parentStart = range.startContainer;
1854 var parentEnd = range.endContainer;
1855 } else {
1856 if (selection.type !== "Control" ) {
1857 var rangeEnd = range.duplicate();
1858 range.collapse(true);
1859 var parentStart = range.parentElement();
1860 rangeEnd.collapse(false);
1861 var parentEnd = rangeEnd.parentElement();
1862 } else {
1863 var parentStart = range.item(0);
1864 var parentEnd = parentStart;
1865 }
1866 }
1867 while (parentStart && !HTMLArea.isBlockElement(parentStart)) {
1868 parentStart = parentStart.parentNode;
1869 }
1870 while (parentEnd && !HTMLArea.isBlockElement(parentEnd)) {
1871 parentEnd = parentEnd.parentNode;
1872 }
1873 return { start : parentStart,
1874 end : parentEnd
1875 };
1876 };
1877
1878 /*
1879 * Get the deepest ancestor of the selection that is of the specified type
1880 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
1881 */
1882 HTMLArea.prototype._getFirstAncestor = function(sel,types) {
1883 var prnt = this._activeElement(sel);
1884 if (prnt == null) {
1885 try {
1886 prnt = (HTMLArea.is_ie ? this._createRange(sel).parentElement() : this._createRange(sel).commonAncestorContainer);
1887 } catch(e) {
1888 return null;
1889 }
1890 }
1891 if (typeof(types) == 'string') types = [types];
1892
1893 while (prnt) {
1894 if (prnt.nodeType == 1) {
1895 if (types == null) return prnt;
1896 for (var i = 0; i < types.length; i++) {
1897 if(prnt.tagName.toLowerCase() == types[i]) return prnt;
1898 }
1899 if(prnt.tagName.toLowerCase() == 'body') break;
1900 if(prnt.tagName.toLowerCase() == 'table') break;
1901 }
1902 prnt = prnt.parentNode;
1903 }
1904 return null;
1905 };
1906
1907 /***************************************************
1908 * Category: EVENT HANDLERS
1909 ***************************************************/
1910
1911 /*
1912 * Intercept some commands and replace them with our own implementation
1913 */
1914 HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
1915 this.focusEditor();
1916 switch (cmdID) {
1917 case "HtmlMode" :
1918 this.setMode();
1919 break;
1920 case "Undo" :
1921 case "Redo" :
1922 if(this._customUndo) this[cmdID.toLowerCase()]();
1923 else this._doc.execCommand(cmdID,UI,param);
1924 break;
1925 case "Cut" :
1926 case "Copy" :
1927 case "Paste" :
1928 try {
1929 this._doc.execCommand(cmdID, false, null);
1930 // In FF3, the paste operation will indeed trigger the paste event
1931 if (HTMLArea.is_gecko && cmdID == "Paste" && this._toolbarObjects.CleanWord && navigator.productSub < 2008020514) {
1932 this._toolbarObjects.CleanWord.cmd(this, "CleanWord");
1933 }
1934 } catch (e) {
1935 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
1936 this._mozillaPasteException(cmdID, UI, param);
1937 }
1938 }
1939 break;
1940 default :
1941 try {
1942 this._doc.execCommand(cmdID, UI, param);
1943 } catch(e) {
1944 if (this.config.debug) alert(e + "\n\nby execCommand(" + cmdID + ");");
1945 }
1946 }
1947 this.updateToolbar();
1948 return false;
1949 };
1950
1951 /*
1952 * A generic event handler for things that happen in the IFRAME's document.
1953 */
1954 HTMLArea._editorEvent = function(ev) {
1955 if(!ev) var ev = window.event;
1956 var target = (ev.target) ? ev.target : ev.srcElement;
1957 var owner = (target.ownerDocument) ? target.ownerDocument : target;
1958 if(HTMLArea.is_ie) { // IE5.5 does not report any ownerDocument
1959 while (owner.parentElement) { owner = owner.parentElement; }
1960 }
1961 var editor = RTEarea[owner._editorNo]["editor"];
1962 var keyEvent = (HTMLArea.is_ie && ev.type == "keydown") || (HTMLArea.is_gecko && ev.type == "keypress");
1963 editor.focusEditor();
1964
1965 if(keyEvent) {
1966 if(editor._hasPluginWithOnKeyPressHandler) {
1967 for (var pluginId in editor.plugins) {
1968 if (editor.plugins.hasOwnProperty(pluginId)) {
1969 var pluginInstance = editor.plugins[pluginId].instance;
1970 if (typeof(pluginInstance.onKeyPress) === "function") {
1971 if (!pluginInstance.onKeyPress(ev)) {
1972 HTMLArea._stopEvent(ev);
1973 return false;
1974 }
1975 }
1976 }
1977 }
1978 }
1979 if(ev.ctrlKey && !ev.shiftKey) {
1980 if(!ev.altKey) {
1981 // execute hotkey command
1982 var key = String.fromCharCode((HTMLArea.is_ie || HTMLArea.is_safari || HTMLArea.is_opera) ? ev.keyCode : ev.charCode).toLowerCase();
1983 if (HTMLArea.is_gecko && ev.keyCode == 32) key = String.fromCharCode(ev.keyCode).toLowerCase();
1984 if (key == " ") {
1985 editor.insertHTML("&nbsp;");
1986 editor.updateToolbar();
1987 HTMLArea._stopEvent(ev);
1988 return false;
1989 }
1990 if (!editor.config.hotKeyList[key]) return false;
1991 var cmd = editor.config.hotKeyList[key].cmd;
1992 if (!cmd) return false;
1993 switch (cmd) {
1994 case "SelectAll":
1995 case "Undo" :
1996 case "Redo" :
1997 cmd = editor.config.hotKeyList[key].cmd;
1998 editor.execCommand(cmd, false, null);
1999 HTMLArea._stopEvent(ev);
2000 return false;
2001 break;
2002 case "Paste" :
2003 if (HTMLArea.is_ie || HTMLArea.is_safari) {
2004 cmd = editor.config.hotKeyList[key].cmd;
2005 editor.execCommand(cmd, false, null);
2006 HTMLArea._stopEvent(ev);
2007 return false;
2008 // In FF3, the paste operation will indeed trigger the paste event
2009 } else if (HTMLArea.is_opera || (HTMLArea.is_gecko && navigator.productSub < 2008020514)) {
2010 if (editor._toolbarObjects.CleanWord) {
2011 var cleanLaterFunctRef = editor.plugins.DefaultClean ? editor.plugins.DefaultClean.instance.cleanLaterFunctRef : (editor.plugins.TYPO3HtmlParser ? editor.plugins.TYPO3HtmlParser.instance.cleanLaterFunctRef : null);
2012 if (cleanLaterFunctRef) {
2013 window.setTimeout(cleanLaterFunctRef, 50);
2014 }
2015 }
2016 }
2017 break;
2018 default:
2019 if (editor.config.hotKeyList[key] && editor.config.hotKeyList[key].action) {
2020 if (!editor.config.hotKeyList[key].action(editor, key)) {
2021 HTMLArea._stopEvent(ev);
2022 return false;
2023 }
2024 }
2025 }
2026 }
2027 } else if (ev.altKey) {
2028 // check if context menu is already handling this event
2029 if(editor.plugins["ContextMenu"] && editor.plugins["ContextMenu"].instance) {
2030 var keys = editor.plugins["ContextMenu"].instance.keys;
2031 if (keys.length > 0) {
2032 var k;
2033 for (var i = keys.length; --i >= 0;) {
2034 k = keys[i];
2035 if (k[0].toLowerCase() == key) {
2036 HTMLArea._stopEvent(ev);
2037 return false;
2038 }
2039 }
2040 }
2041 }
2042 } else if (keyEvent) {
2043 if (HTMLArea.is_gecko) editor._detectURL(ev);
2044 switch (ev.keyCode) {
2045 case 13 : // KEY enter
2046 if (HTMLArea.is_gecko) {
2047 if (!ev.shiftKey && !editor.config.disableEnterParagraphs) {
2048 if (editor._checkInsertP()) {
2049 HTMLArea._stopEvent(ev);
2050 }
2051 } else if (HTMLArea.is_safari) {
2052 var brNode = document.createElement("br");
2053 editor.insertNodeAtSelection(brNode);
2054 if (!brNode.nextSibling || !HTMLArea.getInnerText(brNode.nextSibling)) {
2055 var secondBrNode = document.createElement("br");
2056 secondBrNode = brNode.parentNode.appendChild(secondBrNode);
2057 editor.selectNode(secondBrNode, false);
2058 }
2059 HTMLArea._stopEvent(ev);
2060 }
2061 // update the toolbar state after some time
2062 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
2063 editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 100);
2064 return false;
2065 }
2066 break;
2067 case 8 : // KEY backspace
2068 case 46 : // KEY delete
2069 if ((HTMLArea.is_gecko && !ev.shiftKey) || HTMLArea.is_ie) {
2070 if (editor._checkBackspace()) HTMLArea._stopEvent(ev);
2071 }
2072 // update the toolbar state after some time
2073 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
2074 editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 50);
2075 break;
2076 case 9: // KEY horizontal tab
2077 var newkey = (ev.shiftKey ? "SHIFT-" : "") + "TAB";
2078 if (editor.config.hotKeyList[newkey] && editor.config.hotKeyList[newkey].action) {
2079 if (!editor.config.hotKeyList[newkey].action(editor, newkey)) {
2080 HTMLArea._stopEvent(ev);
2081 return false;
2082 }
2083 }
2084 break;
2085 case 37: // LEFT arrow key
2086 case 38: // UP arrow key
2087 case 39: // RIGHT arrow key
2088 case 40: // DOWN arrow key
2089 if (HTMLArea.is_ie) {
2090 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
2091 editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 10);
2092 return true;
2093 }
2094 }
2095 }
2096 } else {
2097 // mouse event
2098 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
2099 if (ev.type == "mouseup") editor.updateToolbar();
2100 else editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 50);
2101 }
2102 };
2103
2104 HTMLArea.prototype.scrollToCaret = function() {
2105 if (HTMLArea.is_gecko) {
2106 var e = this.getParentElement(),
2107 w = this._iframe.contentWindow ? this._iframe.contentWindow : window,
2108 h = w.innerHeight || w.height,
2109 d = this._doc,
2110 t = d.documentElement.scrollTop || d.body.scrollTop;
2111 if (e.offsetTop > h+t || e.offsetTop < t) {
2112 this.getParentElement().scrollIntoView();
2113 }
2114 }
2115 };
2116
2117 /*
2118 * Retrieve the HTML
2119 */
2120 HTMLArea.prototype.getHTML = function() {
2121 switch (this._editMode) {
2122 case "wysiwyg":
2123 return HTMLArea.getHTML(this._doc.body, false, this);
2124 case "textmode":
2125 return this._textArea.value;
2126 }
2127 return false;
2128 };
2129
2130 /*
2131 * Retrieve raw HTML
2132 */
2133 HTMLArea.prototype.getInnerHTML = function() {
2134 switch (this._editMode) {
2135 case "wysiwyg":
2136 return this._doc.body.innerHTML;
2137 case "textmode":
2138 return this._textArea.value;
2139 }
2140 return false;
2141 };
2142
2143 /*
2144 * Replace the HTML inside
2145 */
2146 HTMLArea.prototype.setHTML = function(html) {
2147 switch (this._editMode) {
2148 case "wysiwyg":
2149 this._doc.body.innerHTML = html;
2150 break;
2151 case "textmode":
2152 this._textArea.value = html;
2153 break;
2154 }
2155 return false;
2156 };
2157
2158 /*
2159 * Set the given doctype when config.fullPage is true
2160 */
2161 HTMLArea.prototype.setDoctype = function(doctype) {
2162 this.doctype = doctype;
2163 };
2164
2165 /***************************************************
2166 * UTILITY FUNCTIONS
2167 ***************************************************/
2168
2169 // variable used to pass the object to the popup editor window.
2170 HTMLArea._object = null;
2171
2172 /*
2173 * Check if the client agent is supported
2174 */
2175 HTMLArea.checkSupportedBrowser = function() {
2176 if(HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
2177 if(navigator.productSub < 20030210) return false;
2178 }
2179 return HTMLArea.is_gecko || HTMLArea.is_ie;
2180 };
2181
2182 /* EventCache Version 1.0
2183 * Copyright 2005 Mark Wubben
2184 * Adaptation by Stanislas Rolland
2185 * Provides a way for automatically removing events from nodes and thus preventing memory leakage.
2186 * See <http://novemberborn.net/javascript/event-cache> for more information.
2187 * This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>
2188 * Event Cache uses an anonymous function to create a hidden scope chain. This is to prevent scoping issues.
2189 */
2190 HTMLArea._eventCacheConstructor = function() {
2191 var listEvents = [];
2192
2193 return ({
2194 listEvents : listEvents,
2195
2196 add : function(node, sEventName, fHandler) {
2197 listEvents.push(arguments);
2198 },
2199
2200 flush : function() {
2201 var item;
2202 for (var i = listEvents.length; --i >= 0;) {
2203 item = listEvents[i];
2204 try {
2205 HTMLArea._removeEvent(item[0], item[1], item[2]);
2206 item[0][item[1]] = null;
2207 item[0] = null;
2208 item[2] = null;
2209 } catch(e) { }
2210 }
2211 listEvents.length = 0;
2212 }
2213 });
2214 };
2215
2216 /*
2217 * Register an event
2218 */
2219 HTMLArea._addEvent = function(el,evname,func,useCapture) {
2220 if (typeof(useCapture) == "undefined") var useCapture = false;
2221 if (HTMLArea.is_gecko) {
2222 el.addEventListener(evname, func, !HTMLArea.is_opera || useCapture);
2223 } else {
2224 el.attachEvent("on" + evname, func);
2225 }
2226 HTMLArea._eventCache.add(el, evname, func);
2227 };
2228
2229 /*
2230 * Register a list of events
2231 */
2232 HTMLArea._addEvents = function(el,evs,func,useCapture) {
2233 if (typeof(useCapture) == "undefined") var useCapture = false;
2234 for (var i = evs.length; --i >= 0;) {
2235 HTMLArea._addEvent(el,evs[i], func, useCapture);
2236 }
2237 };
2238
2239 /*
2240 * Remove an event listener
2241 */
2242 HTMLArea._removeEvent = function(el,evname,func) {
2243 if(HTMLArea.is_gecko) {
2244 try { el.removeEventListener(evname, func, true); el.removeEventListener(evname, func, false); } catch(e) { }
2245 } else {
2246 try { el.detachEvent("on" + evname, func); } catch(e) { }
2247 }
2248 };
2249
2250 /*
2251 * Remove a list of events
2252 */
2253 HTMLArea._removeEvents = function(el,evs,func) {
2254 for (var i = evs.length; --i >= 0;) { HTMLArea._removeEvent(el, evs[i], func); }
2255 };
2256
2257 /*
2258 * Stop event propagation
2259 */
2260 HTMLArea._stopEvent = function(ev) {
2261 if(HTMLArea.is_gecko) {
2262 ev.stopPropagation();
2263 ev.preventDefault();
2264 } else {
2265 ev.cancelBubble = true;
2266 ev.returnValue = false;
2267 }
2268 };
2269
2270 /*
2271 * Remove a class name from the class attribute
2272 */
2273 HTMLArea._removeClass = function(el, removeClassName) {
2274 if(!(el && el.className)) return;
2275 var cls = el.className.trim().split(" ");
2276 var ar = new Array();
2277 for (var i = cls.length; i > 0;) {
2278 if (cls[--i] != removeClassName) ar[ar.length] = cls[i];
2279 }
2280 if (ar.length == 0) {
2281 if (!HTMLArea.is_opera) el.removeAttribute(HTMLArea.is_gecko ? "class" : "className");
2282 else el.className = '';
2283
2284 } else el.className = ar.join(" ");
2285 };
2286
2287 /*
2288 * Add a class name to the class attribute
2289 */
2290 HTMLArea._addClass = function(el, addClassName) {
2291 HTMLArea._removeClass(el, addClassName);
2292 if (el.className && HTMLArea.classesXOR) {
2293 var classNames = el.className.trim().split(" ");
2294 for (var i = classNames.length; --i >= 0;) {
2295 if (HTMLArea.classesXOR[addClassName] && HTMLArea.classesXOR[addClassName].test(classNames[i])) {
2296 HTMLArea._removeClass(el, classNames[i]);
2297 }
2298 }
2299 }
2300 if (el.className) el.className += " " + addClassName;
2301 else el.className = addClassName;
2302 };
2303
2304 /*
2305 * Check if a class name is in the class attribute
2306 */
2307 HTMLArea._hasClass = function(el, className) {
2308 if (!el || !el.className) return false;
2309 var cls = el.className.split(" ");
2310 for (var i = cls.length; i > 0;) {
2311 if(cls[--i] == className) return true;
2312 }
2313 return false;
2314 };
2315
2316 /*
2317 * Select a value in a select element
2318 *
2319 * @param object select: the select object
2320 * @param string value: the value
2321 * @return void
2322 */
2323 HTMLArea.selectValue = function(select, value) {
2324 var options = select.getElementsByTagName("option");
2325 for (var i = options.length; --i >= 0;) {
2326 var option = options[i];
2327 option.selected = (option.value == value);
2328 select.selectedIndex = i;
2329 }
2330 };
2331
2332 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)$/;
2333 HTMLArea.isBlockElement = function(el) { return el && el.nodeType == 1 && HTMLArea.RE_blockTags.test(el.nodeName.toLowerCase()); };
2334 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)$/;
2335 HTMLArea.RE_noClosingTag = /^(img|br|hr|col|input|area|base|link|meta|param)$/;
2336 HTMLArea.needsClosingTag = function(el) { return el && el.nodeType == 1 && !HTMLArea.RE_noClosingTag.test(el.tagName.toLowerCase()); };
2337
2338 /*
2339 * Perform HTML encoding of some given string
2340 * Borrowed in part from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
2341 */
2342 HTMLArea.htmlDecode = function(str) {
2343 str = str.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
2344 str = str.replace(/&nbsp;/g, "\xA0"); // Decimal 160, non-breaking-space
2345 str = str.replace(/&quot;/g, "\x22");
2346 str = str.replace(/&#39;/g, "'") ;
2347 str = str.replace(/&amp;/g, "&");
2348 return str;
2349 };
2350 HTMLArea.htmlEncode = function(str) {
2351 if (typeof(str) != 'string') str = str.toString(); // we don't need regexp for that, but.. so be it for now.
2352 str = str.replace(/&/g, "&amp;");
2353 str = str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
2354 str = str.replace(/\xA0/g, "&nbsp;"); // Decimal 160, non-breaking-space
2355 str = str.replace(/\x22/g, "&quot;"); // \x22 means '"'
2356 return str;
2357 };
2358
2359 /*
2360 * Retrieve the HTML code from the given node.
2361 * This is a replacement for getting innerHTML, using standard DOM calls.
2362 * Wrapper catches a Mozilla-Exception with non well-formed html source code.
2363 */
2364 HTMLArea.getHTML = function(root, outputRoot, editor){
2365 try {
2366 return HTMLArea.getHTMLWrapper(root,outputRoot,editor);
2367 } catch(e) {
2368 HTMLArea._appendToLog("The HTML document is not well-formed.");
2369 if(!HTMLArea._debugMode) alert(HTMLArea.I18N.msg["HTML-document-not-well-formed"]);
2370 else return HTMLArea.getHTMLWrapper(root,outputRoot,editor);
2371 return editor._doc.body.innerHTML;
2372 }
2373 };
2374
2375 HTMLArea.getHTMLWrapper = function(root, outputRoot, editor) {
2376 var html = "";
2377 if(!root) return html;
2378 switch (root.nodeType) {
2379 case 1: // ELEMENT_NODE
2380 case 11: // DOCUMENT_FRAGMENT_NODE
2381 case 9: // DOCUMENT_NODE
2382 var closed, i, config = editor.config;
2383 var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
2384 if (root_tag == "br" && config.removeTrailingBR && !root.nextSibling && HTMLArea.isBlockElement(root.parentNode) && (!root.previousSibling || root.previousSibling.nodeName.toLowerCase() != "br")) {
2385 if (!root.previousSibling && root.parentNode && root.parentNode.nodeName.toLowerCase() == "p" && root.parentNode.className) html += "&nbsp;";
2386 break;
2387 }
2388 if (config.htmlRemoveTagsAndContents && config.htmlRemoveTagsAndContents.test(root_tag)) break;
2389 var custom_tag = (config.customTags && config.customTags.test(root_tag));
2390 if (outputRoot) outputRoot = !(config.htmlRemoveTags && config.htmlRemoveTags.test(root_tag));
2391 if ((HTMLArea.is_ie || HTMLArea.is_safari) && root_tag == "head") {
2392 if(outputRoot) html += "<head>";
2393 var save_multiline = RegExp.multiline;
2394 RegExp.multiline = true;
2395 var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
2396 return p1 + p2.toLowerCase();
2397 });
2398 RegExp.multiline = save_multiline;
2399 html += txt;
2400 if(outputRoot) html += "</head>";
2401 break;
2402 } else if (outputRoot) {
2403 if (HTMLArea.is_gecko && root.hasAttribute('_moz_editor_bogus_node')) break;
2404 closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root) || custom_tag));
2405 html = "<" + root_tag;
2406 var a, name, value, attrs = root.attributes;
2407 var n = attrs.length;
2408 for (i = attrs.length; --i >= 0 ;) {
2409 a = attrs.item(i);
2410 name = a.nodeName.toLowerCase();
2411 if ((!a.specified && name != 'value') || /_moz|contenteditable|_msh/.test(name)) continue;
2412 if (!HTMLArea.is_ie || name != "style") {
2413 // IE5.5 reports wrong values. For this reason we extract the values directly from the root node.
2414 // Using Gecko the values of href and src are converted to absolute links unless we get them using nodeValue()
2415 if (typeof(root[a.nodeName]) != "undefined" && name != "href" && name != "src" && name != "style" && !/^on/.test(name)) {
2416 value = root[a.nodeName];
2417 } else {
2418 value = a.nodeValue;
2419 if (HTMLArea.is_ie && (name == "href" || name == "src") && editor.plugins.link && editor.plugins.link.instance && editor.plugins.link.instance.stripBaseURL) {
2420 value = editor.plugins.link.instance.stripBaseURL(value);
2421 }
2422 }
2423 } else { // IE fails to put style in attributes list.
2424 value = root.style.cssText;
2425 }
2426 // Mozilla reports some special values; we don't need them.
2427 if(/(_moz|^$)/.test(value)) continue;
2428 // Strip value="0" reported by IE on all li tags
2429 if(HTMLArea.is_ie && root_tag == "li" && name == "value" && a.nodeValue == 0) continue;
2430 html += " " + name + '="' + HTMLArea.htmlEncode(value) + '"';
2431 }
2432 if (html != "") html += closed ? " />" : ">";
2433 }
2434 for (i = root.firstChild; i; i = i.nextSibling) {
2435 if (/^li$/i.test(i.tagName) && !/^[ou]l$/i.test(root.tagName)) html += "<ul>" + HTMLArea.getHTMLWrapper(i, true, editor) + "</ul>";
2436 else html += HTMLArea.getHTMLWrapper(i, true, editor);
2437 }
2438 if (outputRoot && !closed) html += "</" + root_tag + ">";
2439 break;
2440 case 3: // TEXT_NODE
2441 html = /^(script|style)$/i.test(root.parentNode.tagName) ? root.data : HTMLArea.htmlEncode(root.data);
2442 break;
2443 case 8: // COMMENT_NODE
2444 if (!editor.config.htmlRemoveComments) html = "<!--" + root.data + "-->";
2445 break;
2446 case 4: // Node.CDATA_SECTION_NODE
2447 // Mozilla seems to convert CDATA into a comment when going into wysiwyg mode, don't know about IE
2448 html += '<![CDATA[' + root.data + ']]>';
2449 break;
2450 case 5: // Node.ENTITY_REFERENCE_NODE
2451 html += '&' + root.nodeValue + ';';
2452 break;
2453 case 7: // Node.PROCESSING_INSTRUCTION_NODE
2454 // PI's don't seem to survive going into the wysiwyg mode, (at least in moz) so this is purely academic
2455 html += '<?' + root.target + ' ' + root.data + ' ?>';
2456 break;
2457 default:
2458 break;
2459 }
2460 return html;
2461 };
2462
2463 HTMLArea.getPrevNode = function(node) {
2464 if(!node) return null;
2465 if(node.previousSibling) return node.previousSibling;
2466 if(node.parentNode) return node.parentNode;
2467 return null;
2468 };
2469
2470 HTMLArea.getNextNode = function(node) {
2471 if(!node) return null;
2472 if(node.nextSibling) return node.nextSibling;
2473 if(node.parentNode) return node.parentNode;
2474 return null;
2475 };
2476
2477 HTMLArea.removeFromParent = function(el) {
2478 if(!el.parentNode) return;
2479 var pN = el.parentNode;
2480 pN.removeChild(el);
2481 return el;
2482 };
2483
2484 String.prototype.trim = function() {
2485 return this.replace(/^\s+/, '').replace(/\s+$/, '');
2486 };
2487
2488 // creates a rgb-style color from a number
2489 HTMLArea._makeColor = function(v) {
2490 if (typeof(v) != "number") {
2491 // already in rgb (hopefully); IE doesn't get here.
2492 return v;
2493 }
2494 // IE sends number; convert to rgb.
2495 var r = v & 0xFF;
2496 var g = (v >> 8) & 0xFF;
2497 var b = (v >> 16) & 0xFF;
2498 return "rgb(" + r + "," + g + "," + b + ")";
2499 };
2500
2501 // returns hexadecimal color representation from a number or a rgb-style color.
2502 HTMLArea._colorToRgb = function(v) {
2503 if (!v)
2504 return '';
2505
2506 // returns the hex representation of one byte (2 digits)
2507 function hex(d) {
2508 return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
2509 };
2510
2511 if (typeof(v) == "number") {
2512 // we're talking to IE here
2513 var r = v & 0xFF;
2514 var g = (v >> 8) & 0xFF;
2515 var b = (v >> 16) & 0xFF;
2516 return "#" + hex(r) + hex(g) + hex(b);
2517 }
2518
2519 if (v.substr(0, 3) == "rgb") {
2520 // in rgb(...) form -- Mozilla
2521 var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2522 if (v.match(re)) {
2523 var r = parseInt(RegExp.$1);
2524 var g = parseInt(RegExp.$2);
2525 var b = parseInt(RegExp.$3);
2526 return "#" + hex(r) + hex(g) + hex(b);
2527 }
2528 // doesn't match RE?! maybe uses percentages or float numbers
2529 // -- FIXME: not yet implemented.
2530 return null;
2531 }
2532
2533 if (v.substr(0, 1) == "#") {
2534 // already hex rgb (hopefully :D )
2535 return v;
2536 }
2537
2538 // if everything else fails ;)
2539 return null;
2540 };
2541
2542 /** Use XML HTTPRequest to post some data back to the server and do something
2543 * with the response (asyncronously!), this is used by such things as the spellchecker update personal dict function
2544 */
2545 HTMLArea._postback = function(url, data, handler, addParams, charset) {
2546 if (typeof(charset) == "undefined") var charset = "utf-8";
2547 var req = null;
2548 if (window.XMLHttpRequest) req = new XMLHttpRequest();
2549 else if (window.ActiveXObject) {
2550 var success = false;
2551 for (var k = 0; k < HTMLArea.MSXML_XMLHTTP_PROGIDS.length && !success; k++) {
2552 try {
2553 req = new ActiveXObject(HTMLArea.MSXML_XMLHTTP_PROGIDS[k]);
2554 success = true;
2555 } catch (e) { }
2556 }
2557 }
2558
2559 if(req) {
2560 var content = '';
2561 for (var i in data) {
2562 content += (content.length ? '&' : '') + i + '=' + encodeURIComponent(data[i]);
2563 }
2564 content += (content.length ? '&' : '') + 'charset=' + charset;
2565 if (typeof(addParams) != "undefined") content += addParams;
2566 if (url.substring(0,1) == '/') {
2567 var postUrl = _typo3_host_url + url;
2568 } else {
2569 var postUrl = _typo3_host_url + _editor_url + url;
2570 }
2571
2572 function callBack() {
2573 if(req.readyState == 4) {
2574 if (req.status == 200) {
2575 if (typeof(handler) == 'function') handler(req.responseText, req);
2576 HTMLArea._appendToLog("[HTMLArea::_postback]: Server response: " + req.responseText);
2577 } else {
2578 HTMLArea._appendToLog("ERROR [HTMLArea::_postback]: Unable to post " + postUrl + " . Server reported " + req.statusText);
2579 }
2580 }
2581 }
2582 req.onreadystatechange = callBack;
2583 function sendRequest() {
2584 HTMLArea._appendToLog("[HTMLArea::_postback]: Request: " + content);
2585 req.send(content);
2586 }
2587
2588 req.open('POST', postUrl, true);
2589 req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
2590 window.setTimeout(sendRequest, 500);
2591 }
2592 };
2593
2594 /**
2595 * Internet Explorer returns an item having the _name_ equal to the given id, even if it's not having any id.
2596 * This way it can return a different form field even if it's not a textarea. This works around the problem by
2597 * specifically looking to search only elements having a certain tag name.
2598 */
2599 HTMLArea.getElementById = function(tag, id) {
2600 var el, i, objs = document.getElementsByTagName(tag);
2601 for (i = objs.length; --i >= 0 && (el = objs[i]);) {
2602 if (el.id == id) return el;
2603 }
2604 return null;
2605 };
2606
2607 /***************************************************
2608 * TYPO3-SPECIFIC FUNCTIONS
2609 ***************************************************/
2610 /*
2611 * Set the size of textarea with the RTE. It's called, if we are in fullscreen-mode.
2612 */
2613 var setRTEsizeByJS = function(divId, height, width) {
2614 if (HTMLArea.is_gecko) height = height - 25;
2615 else height = height - 60;
2616 if (height > 0) document.getElementById(divId).style.height = height + "px";
2617 if (HTMLArea.is_gecko) width = "99%";
2618 else width = "97%";
2619 document.getElementById(divId).style.width = width;
2620 };
2621
2622 /*
2623 * Extending the TYPO3 Lorem Ipsum extension
2624 */
2625 var lorem_ipsum = function(element,text) {
2626 if (element.tagName.toLowerCase() == "textarea" && element.id && element.id.substr(0,7) == "RTEarea") {
2627 var editor = RTEarea[element.id.substr(7,8)]["editor"];
2628 editor.insertHTML(text);
2629 editor.updateToolbar();
2630 }
2631 };
2632
2633 /*
2634 * Initialize the editor, configure the toolbar, setup the plugins, etc.
2635 */
2636 HTMLArea.initTimer = [];
2637
2638 HTMLArea.onGenerateHandler = function(editorNumber) {
2639 return (function() {
2640 document.getElementById('pleasewait' + editorNumber).style.display = 'none';
2641 document.getElementById('editorWrap' + editorNumber).style.visibility = 'visible';
2642 editorNumber = null;
2643 });
2644 };
2645
2646 HTMLArea.initEditor = function(editorNumber) {
2647 if(HTMLArea.checkSupportedBrowser()) {
2648 document.getElementById('pleasewait' + editorNumber).style.display = 'block';
2649 document.getElementById('editorWrap' + editorNumber).style.visibility = 'hidden';
2650 if(HTMLArea.initTimer[editorNumber]) window.clearTimeout(HTMLArea.initTimer[editorNumber]);
2651 if(!HTMLArea.is_loaded) {
2652 HTMLArea.initTimer[editorNumber] = window.setTimeout( "HTMLArea.initEditor(" + editorNumber + ");", 150);
2653 } else {
2654 var RTE = RTEarea[editorNumber];
2655
2656 // Get the configuration properties
2657 var config = new HTMLArea.Config();
2658 for (var property in RTE) {
2659 if (RTE.hasOwnProperty(property)) {
2660 config[property] = RTE[property] ? RTE[property] : false;
2661 }
2662 }
2663 // Create an editor for the textarea
2664 var editor = new HTMLArea(RTE.id, config);
2665 RTE.editor = editor;
2666
2667 // Save the editornumber in the object
2668 editor._typo3EditerNumber = editorNumber;
2669 editor._editorNumber = editorNumber;
2670
2671 // Override these settings if they were ever modified
2672 editor.config.width = "auto";
2673 editor.config.height = "auto";
2674 editor.config.sizeIncludesToolbar = true;
2675 editor.config.fullPage = false;
2676
2677 // Register the plugins included in the configuration
2678 for (var plugin in editor.config.plugin) {
2679 if (editor.config.plugin.hasOwnProperty(plugin) && editor.config.plugin[plugin]) {
2680 editor.registerPlugin(plugin);
2681 }
2682 }
2683
2684 editor.onGenerate = HTMLArea.onGenerateHandler(editorNumber);
2685
2686 editor.generate();
2687 return false;
2688 }
2689 } else {
2690 document.getElementById('pleasewait' + editorNumber).style.display = 'none';
2691 document.getElementById('editorWrap' + editorNumber).style.visibility = 'visible';
2692 }
2693 };
2694
2695 HTMLArea.allElementsAreDisplayed = function(elements) {
2696 for (var i=0, length=elements.length; i < length; i++) {
2697 if (document.getElementById(elements[i]).style.display == 'none') {
2698 return false;
2699 }
2700 }
2701 return true;
2702 };
2703
2704 /**
2705 * Base, version 1.0.2
2706 * Copyright 2006, Dean Edwards
2707 * License: http://creativecommons.org/licenses/LGPL/2.1/
2708 */
2709
2710 HTMLArea.Base = function() {
2711 if (arguments.length) {
2712 if (this == window) { // cast an object to this class
2713 HTMLArea.Base.prototype.extend.call(arguments[0], arguments.callee.prototype);
2714 } else {
2715 this.extend(arguments[0]);
2716 }
2717 }
2718 };
2719
2720 HTMLArea.Base.version = "1.0.2";
2721
2722 HTMLArea.Base.prototype = {
2723 extend: function(source, value) {
2724 var extend = HTMLArea.Base.prototype.extend;
2725 if (arguments.length == 2) {
2726 var ancestor = this[source];
2727 // overriding?
2728 if ((ancestor instanceof Function) && (value instanceof Function) &&
2729 ancestor.valueOf() != value.valueOf() && /\bbase\b/.test(value)) {
2730 var method = value;
2731 // var _prototype = this.constructor.prototype;
2732 // var fromPrototype = !Base._prototyping && _prototype[source] == ancestor;
2733 value = function() {
2734 var previous = this.base;
2735 // this.base = fromPrototype ? _prototype[source] : ancestor;
2736 this.base = ancestor;
2737 var returnValue = method.apply(this, arguments);
2738 this.base = previous;
2739 return returnValue;
2740 };
2741 // point to the underlying method
2742 value.valueOf = function() {
2743 return method;
2744 };
2745 value.toString = function() {
2746 return String(method);
2747 };
2748 }
2749 return this[source] = value;
2750 } else if (source) {
2751 var _prototype = {toSource: null};
2752 // do the "toString" and other methods manually
2753 var _protected = ["toString", "valueOf"];
2754 // if we are prototyping then include the constructor
2755 if (HTMLArea.Base._prototyping) _protected[2] = "constructor";
2756 for (var i = 0; (name = _protected[i]); i++) {
2757 if (source[name] != _prototype[name]) {
2758 extend.call(this, name, source[name]);
2759 }
2760 }
2761 // copy each of the source object's properties to this object
2762 for (var name in source) {
2763 if (!_prototype[name]) {
2764 extend.call(this, name, source[name]);
2765 }
2766 }
2767 }
2768 return this;
2769 },
2770
2771 base: function() {
2772 // call this method from any other method to invoke that method's ancestor
2773 }
2774 };
2775
2776 HTMLArea.Base.extend = function(_instance, _static) {
2777 var extend = HTMLArea.Base.prototype.extend;
2778 if (!_instance) _instance = {};
2779 // build the prototype
2780 HTMLArea.Base._prototyping = true;
2781 var _prototype = new this;
2782 extend.call(_prototype, _instance);
2783 var constructor = _prototype.constructor;
2784 _prototype.constructor = this;
2785 delete HTMLArea.Base._prototyping;
2786 // create the wrapper for the constructor function
2787 var klass = function() {
2788 if (!HTMLArea.Base._prototyping) constructor.apply(this, arguments);
2789 this.constructor = klass;
2790 };
2791 klass.prototype = _prototype;
2792 // build the class interface
2793 klass.extend = this.extend;
2794 klass.implement = this.implement;
2795 klass.toString = function() {
2796 return String(constructor);
2797 };
2798 extend.call(klass, _static);
2799 // single instance
2800 var object = constructor ? klass : _prototype;
2801 // class initialisation
2802 if (object.init instanceof Function) object.init();
2803 return object;
2804 };
2805
2806 HTMLArea.Base.implement = function(_interface) {
2807 if (_interface instanceof Function) _interface = _interface.prototype;
2808 this.prototype.extend(_interface);
2809 };
2810
2811 /**
2812 * HTMLArea.plugin class
2813 *
2814 * Every plugin should be a subclass of this class
2815 *
2816 */
2817 HTMLArea.Plugin = HTMLArea.Base.extend({
2818
2819 /**
2820 * HTMLArea.plugin constructor
2821 *
2822 * @param object editor: instance of RTE
2823 * @param string pluginName: name of the plugin
2824 *
2825 * @return boolean true if the plugin was configured
2826 */
2827 constructor : function(editor, pluginName) {
2828 this.editor = editor;
2829 this.editorNumber = editor._editorNumber;
2830 this.editorConfiguration = editor.config;
2831 this.name = pluginName;
2832 try {
2833 HTMLArea.I18N[this.name] = eval(this.name + "_langArray");
2834 this.I18N = HTMLArea.I18N[this.name];
2835 } catch(e) {
2836 this.appendToLog("initialize", "The localization array for plugin " + this.name + " could not be assigned.");
2837 }
2838 return this.configurePlugin(editor);
2839 },
2840
2841 /**
2842 * Configures the plugin
2843 * This function is invoked by the class constructor.
2844 * This function should be redefined by the plugin subclass. Normal steps would be:
2845 * - registering plugin ingormation with method registerPluginInformation;
2846 * - registering any buttons with method registerButton;
2847 * - registering any drop-down lists with method registerDropDown.
2848 *
2849 * @param object editor: instance of RTE
2850 *
2851 * @return boolean true if the plugin was configured
2852 */
2853 configurePlugin : function(editor) {
2854 return false;
2855 },
2856
2857 /**
2858 * Registers the plugin "About" information
2859 *
2860 * @param object pluginInformation:
2861 * version : the version,
2862 * developer : the name of the developer,
2863 * developerUrl : the url of the developer,
2864 * copyrightOwner : the name of the copyright owner,
2865 * sponsor : the name of the sponsor,
2866 * sponsorUrl : the url of the sponsor,
2867 * license : the type of license (should be "GPL")
2868 *
2869 * @return boolean true if the information was registered
2870 */
2871 registerPluginInformation : function(pluginInformation) {
2872 if (typeof(pluginInformation) !== "object") {
2873 this.appendToLog("registerPluginInformation", "Plugin information was not provided");
2874 return false;
2875 } else {
2876 this.pluginInformation = pluginInformation;
2877 this.pluginInformation.name = this.name;
2878 /* Ensure backwards compatibility */
2879 this.pluginInformation.developer_url = this.pluginInformation.developerUrl;
2880 this.pluginInformation.c_owner = this.pluginInformation.copyrightOwner;
2881 this.pluginInformation.sponsor_url = this.pluginInformation.sponsorUrl;
2882 return true;
2883 }
2884 },
2885
2886 /**
2887 * Returns the plugin information
2888 *
2889 * @return object the plugin information object
2890 */
2891 getPluginInformation : function() {
2892 return this.pluginInformation;
2893 },
2894
2895 /**
2896 * Returns true if the button is enabled in the toolbar configuration
2897 *
2898 * @param string buttonId: identification of the button
2899 *
2900 * @return boolean true if the button is enabled in the toolbar configuration
2901 */
2902 isButtonInToolbar : function(buttonId) {
2903 var toolbar = this.editorConfiguration.toolbar;
2904 var n = toolbar.length;
2905 for ( var i = 0; i < n; ++i ) {
2906 var buttonInToolbar = new RegExp( "^(" + toolbar[i].join("|") + ")$", "i"