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