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