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