ffb46aea970616bc5c1fa1739f0678f9a6a652e6
[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-2007 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 //set gecko options
1262 if (HTMLArea.is_gecko) {
1263 try { this._doc.execCommand("useCSS", false, !this.config.useCSS); } catch (e) {};
1264 try { this._doc.execCommand("styleWithCSS", false, this.config.useCSS); } catch (e) {};
1265 }
1266 break;
1267 default:
1268 return false;
1269 }
1270 if (!(mode == "docnotwellformedmode")) this.focusEditor();
1271 for (var i in this.plugins) {
1272 var plugin = this.plugins[i].instance;
1273 if (typeof(plugin.onMode) == "function") { plugin.onMode(mode); }
1274 }
1275 };
1276
1277 /*
1278 * Initialize iframe content when in full page mode
1279 */
1280 HTMLArea.prototype.setFullHTML = function(html) {
1281 var save_multiline = RegExp.multiline;
1282 RegExp.multiline = true;
1283 if(html.match(HTMLArea.RE_doctype)) {
1284 this.setDoctype(RegExp.$1);
1285 html = html.replace(HTMLArea.RE_doctype, "");
1286 };
1287 RegExp.multiline = save_multiline;
1288 if(!HTMLArea.is_ie) {
1289 if(html.match(HTMLArea.RE_head)) this._doc.getElementsByTagName("head")[0].innerHTML = RegExp.$1;
1290 if(html.match(HTMLArea.RE_body)) this._doc.getElementsByTagName("body")[0].innerHTML = RegExp.$1;
1291 } else {
1292 var html_re = /<html>((.|\n)*?)<\/html>/i;
1293 html = html.replace(html_re, "$1");
1294 this._doc.open();
1295 this._doc.write(html);
1296 this._doc.close();
1297 this._doc.body.contentEditable = true;
1298 return true;
1299 };
1300 };
1301
1302 /***************************************************
1303 * PLUGINS, STYLESHEETS, AND IMAGE AND POPUP URL'S
1304 ***************************************************/
1305
1306 /*
1307 * Create the specified plugin and register it with this HTMLArea
1308 */
1309 HTMLArea.prototype.registerPlugin = function() {
1310 var plugin = arguments[0];
1311 var args = [];
1312 for (var i=1; i < arguments.length; ++i) { args.push(arguments[i]); }
1313 this.registerPlugin2(plugin, args);
1314 };
1315
1316 /*
1317 * A variant of the function above where the plugin arguments are already packed in an array.
1318 * Externally, it should be only used in the full-screen editor code,
1319 * in order to initialize plugins with the same parameters as in the opener window.
1320 */
1321 HTMLArea.prototype.registerPlugin2 = function(plugin, args) {
1322 if (typeof(plugin) == "string") {
1323 var plugin = eval(plugin);
1324 };
1325 if (typeof(plugin) == "undefined") {
1326 HTMLArea._appendToLog("ERROR [HTMLArea::registerPlugin]: Can't register undefined plugin.");
1327 return false;
1328 };
1329 var obj = new plugin(this, args);
1330 if (obj) {
1331 var clone = {};
1332 var info = plugin._pluginInfo;
1333 for (var i in info) {
1334 clone[i] = info[i];
1335 }
1336 clone.instance = obj;
1337 clone.args = args;
1338 this.plugins[plugin._pluginInfo.name] = clone;
1339 } else {
1340 HTMLArea._appendToLog("ERROR [HTMLArea::registerPlugin]: Can't register plugin " + plugin.toString() + ".");
1341 };
1342 };
1343
1344 /*
1345 * Load the required plugin script and, unless not requested, the language file
1346 */
1347 HTMLArea.loadPlugin = function(pluginName,noLangFile,url) {
1348 if (typeof(url) == "undefined") {
1349 var dir = _editor_url + "plugins/" + pluginName;
1350 var plugin = pluginName.replace(/([a-z])([A-Z])([a-z])/g, "$1" + "-" + "$2" + "$3").toLowerCase() + ".js";
1351 var plugin_file = dir + "/" + plugin;
1352 HTMLArea.loadScript(plugin_file);
1353 if (typeof(noLangFile) == "undefined" || !noLangFile) {
1354 var plugin_lang = dir + "/lang/" + _editor_lang + ".js";
1355 HTMLArea._scripts.push(plugin_lang);
1356 }
1357 } else {
1358 HTMLArea.loadScript(url);
1359 }
1360 };
1361
1362 /*
1363 * Load a stylesheet file
1364 */
1365 HTMLArea.loadStyle = function(style, plugin, url) {
1366 if (typeof(url) == "undefined") {
1367 var url = _editor_url || '';
1368 if (typeof(plugin) != "undefined") { url += "plugins/" + plugin + "/"; }
1369 url += style;
1370 if (/^\//.test(style)) { url = style; }
1371 }
1372 var head = document.getElementsByTagName("head")[0];
1373 var link = document.createElement("link");
1374 link.rel = "stylesheet";
1375 link.href = url;
1376 head.appendChild(link);
1377 };
1378
1379 /*
1380 * Load the editor skin
1381 */
1382 HTMLArea.loadStyle('','',_editor_CSS);
1383
1384 /*
1385 * Get the url of some image
1386 */
1387 HTMLArea.prototype.imgURL = function(file, plugin) {
1388 if (typeof(plugin) == "undefined") return _editor_skin + this.config.imgURL + file;
1389 else return _editor_skin + this.config.imgURL + plugin + "/" + file;
1390 };
1391
1392 /*
1393 * Get the url of some popup
1394 */
1395 HTMLArea.prototype.popupURL = function(file) {
1396 var url = "";
1397 if(file.match(/^plugin:\/\/(.*?)\/(.*)/)) {
1398 var plugin = RegExp.$1;
1399 var popup = RegExp.$2;
1400 if(!/\.html$/.test(popup)) popup += ".html";
1401 url = _editor_url + "plugins/" + plugin + "/popups/" + popup;
1402 } else {
1403 url = _typo3_host_url + _editor_url + this.config.popupURL + file;
1404 }
1405 return url;
1406 };
1407
1408 /***************************************************
1409 * EDITOR UTILITIES
1410 ***************************************************/
1411 HTMLArea.getInnerText = function(el) {
1412 var txt = '', i;
1413 if(el.firstChild) {
1414 for(i=el.firstChild;i;i =i.nextSibling) {
1415 if(i.nodeType == 3) txt += i.data;
1416 else if(i.nodeType == 1) txt += HTMLArea.getInnerText(i);
1417 }
1418 } else {
1419 if(el.nodeType == 3) txt = el.data;
1420 }
1421 return txt;
1422 };
1423
1424 HTMLArea._wordClean = function(editor,html) {
1425 function clearClass(node) {
1426 var newc = node.className.replace(/(^|\s)mso.*?(\s|$)/ig,' ');
1427 if(newc != node.className) {
1428 node.className = newc;
1429 if(!/\S/.test(node.className)) node.removeAttribute("className");
1430 }
1431 }
1432 function clearStyle(node) {
1433 if (HTMLArea.is_ie) var style = node.style.cssText;
1434 else var style = node.getAttribute("style");
1435 if (style) {
1436 var declarations = style.split(/\s*;\s*/);
1437 for (var i = declarations.length; --i >= 0;) {
1438 if(/^mso|^tab-stops/i.test(declarations[i]) || /^margin\s*:\s*0..\s+0..\s+0../i.test(declarations[i])) declarations.splice(i,1);
1439 }
1440 node.setAttribute("style", declarations.join("; "));
1441 }
1442 }
1443 function stripTag(el) {
1444 if(HTMLArea.is_ie) {
1445 el.outerHTML = HTMLArea.htmlEncode(el.innerText);
1446 } else {
1447 var txt = document.createTextNode(HTMLArea.getInnerText(el));
1448 el.parentNode.insertBefore(txt,el);
1449 el.parentNode.removeChild(el);
1450 }
1451 }
1452 function checkEmpty(el) {
1453 if(/^(span|b|strong|i|em|font)$/i.test(el.tagName) && !el.firstChild) el.parentNode.removeChild(el);
1454 }
1455 function parseTree(root) {
1456 var tag = root.tagName.toLowerCase(), i, next;
1457 if((HTMLArea.is_ie && root.scopeName != 'HTML') || (!HTMLArea.is_ie && /:/.test(tag)) || /o:p/.test(tag)) {
1458 stripTag(root);
1459 return false;
1460 } else {
1461 clearClass(root);
1462 clearStyle(root);
1463 for (i=root.firstChild;i;i=next) {
1464 next = i.nextSibling;
1465 if(i.nodeType == 1 && parseTree(i)) { checkEmpty(i); }
1466 }
1467 }
1468 return true;
1469 }
1470 parseTree(html);
1471 };
1472
1473 HTMLArea.wordCleanLater = function(editorNumber,doUpdateToolbar) {
1474 var editor = RTEarea[editorNumber]["editor"];
1475 HTMLArea._wordClean(editor, editor._doc.body);
1476 if (doUpdateToolbar) editor.updateToolbar();
1477 };
1478
1479 /*
1480 * Handler for paste, dragdrop and drop events
1481 */
1482 HTMLArea.cleanWordOnPaste = function(ev) {
1483 if(!ev) var ev = window.event;
1484 var target = (ev.target) ? ev.target : ev.srcElement;
1485 var owner = (target.ownerDocument) ? target.ownerDocument : target;
1486 while (HTMLArea.is_ie && owner.parentElement ) { // IE5.5 does not report any ownerDocument
1487 owner = owner.parentElement;
1488 }
1489 // if we dropped an image dragged from the TYPO3 Browser, let's close the browser window
1490 if (typeof(browserWin) != "undefined") browserWin.close();
1491 window.setTimeout("HTMLArea.wordCleanLater(" + owner._editorNo + ", true);", 250);
1492 };
1493
1494 HTMLArea.prototype.forceRedraw = function() {
1495 this._doc.body.style.visibility = "hidden";
1496 this._doc.body.style.visibility = "visible";
1497 };
1498
1499 /*
1500 * Focus the editor iframe document or the textarea.
1501 */
1502 HTMLArea.prototype.focusEditor = function() {
1503 switch (this._editMode) {
1504 case "wysiwyg" :
1505 try {
1506 if (HTMLArea.is_safari || HTMLArea.is_opera) this._doc.focus();
1507 else this._iframe.contentWindow.focus();
1508 } catch(e) { };
1509 break;
1510 case "textmode":
1511 this._textArea.focus();
1512 break;
1513 }
1514 return this._doc;
1515 };
1516
1517 HTMLArea.undoTakeSnapshot = function(editorNumber) {
1518 var editor = RTEarea[editorNumber]["editor"];
1519 if (editor._doc) editor._undoTakeSnapshot();
1520 };
1521
1522 /*
1523 * Take a snapshot of the current contents for undo
1524 */
1525 HTMLArea.prototype._undoTakeSnapshot = function() {
1526 var curTime = (new Date()).getTime();
1527 var newOne = true;
1528 if(this._undoPos >= this.config.undoSteps) {
1529 // remove the first element
1530 this._undoQueue.shift();
1531 --this._undoPos;
1532 }
1533 // New undo slot should be used if this is first undoTakeSnapshot call or if undoTimeout is elapsed
1534 if (this._undoPos < 0 || this._undoQueue[this._undoPos].time < curTime - this.config.undoTimeout) {
1535 ++this._undoPos;
1536 } else {
1537 newOne = false;
1538 }
1539 // use the fasted method (getInnerHTML);
1540 var txt = this.getInnerHTML();
1541 if (newOne){
1542 // If previous slot contain same text new one should not be used
1543 if(this._undoPos == 0 || this._undoQueue[this._undoPos - 1].text != txt){
1544 this._undoQueue[this._undoPos] = { text: txt, time: curTime };
1545 this._undoQueue.length = this._undoPos + 1;
1546 } else {
1547 this._undoPos--;
1548 }
1549 } else {
1550 if(this._undoQueue[this._undoPos].text != txt){
1551 this._undoQueue[this._undoPos].text = txt;
1552 this._undoQueue.length = this._undoPos + 1;
1553 }
1554 }
1555 };
1556
1557 HTMLArea.setUndoQueueLater = function(editorNumber,op) {
1558 var editor = RTEarea[editorNumber]["editor"];
1559 if (op == "undo") {
1560 editor.setHTML(editor._undoQueue[--editor._undoPos].text);
1561 } else if (op == "redo") {
1562 if(editor._undoPos < editor._undoQueue.length - 1) editor.setHTML(editor._undoQueue[++editor._undoPos].text);
1563 }
1564 };
1565
1566 HTMLArea.prototype.undo = function() {
1567 if(this._undoPos > 0){
1568 // Make sure we would not loose any changes
1569 this._undoTakeSnapshot();
1570 if (!HTMLArea.is_opera) this.setHTML(this._undoQueue[--this._undoPos].text);
1571 else window.setTimeout("HTMLArea.setUndoQueueLater(" + this._editorNumber + ", 'undo');", 10);
1572 }
1573 };
1574
1575 HTMLArea.prototype.redo = function() {
1576 if(this._undoPos < this._undoQueue.length - 1) {
1577 // Make sure we would not loose any changes
1578 this._undoTakeSnapshot();
1579 // Previous call could make undo queue shorter
1580 if (!HTMLArea.is_opera) {
1581 if(this._undoPos < this._undoQueue.length - 1) this.setHTML(this._undoQueue[++this._undoPos].text);
1582 } else {
1583 window.setTimeout("HTMLArea.setUndoQueueLater(" + this._editorNumber + ", 'redo');", 10);
1584 }
1585 }
1586 };
1587
1588 /*
1589 * Update the enabled/disabled/active state of the toolbar elements
1590 */
1591 HTMLArea.updateToolbar = function(editorNumber) {
1592 var editor = RTEarea[editorNumber]["editor"];
1593 editor.updateToolbar();
1594 editor._timerToolbar = null;
1595 };
1596
1597 HTMLArea.prototype.updateToolbar = function(noStatus) {
1598 var doc = this._doc,
1599 text = (this._editMode == "textmode"),
1600 selection = this.hasSelectedText(),
1601 ancestors = null, cls = new Array(),
1602 txt, txtClass, i, cmd, inContext, match, matchAny, k, j, n, commandState;
1603 if(!text) {
1604 ancestors = this.getAllAncestors();
1605 if(this.config.statusBar && !noStatus) {
1606 // Unhook previous events handlers
1607 if(this._statusBarTree.hasChildNodes()) {
1608 for (i = this._statusBarTree.firstChild; i; i = i.nextSibling) {
1609 if(i.nodeName.toLowerCase() == "a") {
1610 HTMLArea._removeEvents(i,["click", "contextmenu, mousedown"], HTMLArea.statusBarHandler);
1611 i.el = null;
1612 i.editor = null;
1613 }
1614 }
1615 }
1616 this._statusBarTree.innerHTML = '';
1617 this._statusBarTree.appendChild(document.createTextNode(HTMLArea.I18N.msg["Path"] + ": ")); // clear
1618 for (i = ancestors.length; --i >= 0;) {
1619 var el = ancestors[i];
1620 if(!el) continue;
1621 var a = document.createElement("a");
1622 a.href = "#";
1623 a.el = el;
1624 a.editor = this;
1625 if (!HTMLArea.is_opera) {
1626 HTMLArea._addEvents(a, ["click", "contextmenu"], HTMLArea.statusBarHandler);
1627 } else {
1628 HTMLArea._addEvents(a, ["mousedown", "click"], HTMLArea.statusBarHandler);
1629 }
1630 txt = el.tagName.toLowerCase();
1631 a.title = el.style.cssText;
1632 if (el.id) { txt += "#" + el.id; }
1633 if (el.className) {
1634 txtClass = "";
1635 cls = el.className.trim().split(" ");
1636 for(j = cls.length; j > 0;) {
1637 if(!HTMLArea.reservedClassNames.test(cls[--j])) { txtClass = "." + cls[j]; }
1638 }
1639 txt += txtClass;
1640 }
1641 a.appendChild(document.createTextNode(txt));
1642 this._statusBarTree.appendChild(a);
1643 if (i != 0) this._statusBarTree.appendChild(document.createTextNode(String.fromCharCode(0xbb)));
1644 }
1645 }
1646 }
1647 for (i in this._toolbarObjects) {
1648 var btn = this._toolbarObjects[i];
1649 cmd = i;
1650
1651 // Determine if the button should be enabled
1652 inContext = true;
1653 if (btn.context && !text) {
1654 inContext = false;
1655 var attrs = [];
1656 var contexts = [];
1657 if (/(.*)\[(.*?)\]/.test(btn.context)) {
1658 contexts = RegExp.$1.split(",");
1659 attrs = RegExp.$2.split(",");
1660 } else {
1661 contexts = btn.context.split(",");
1662 }
1663 for (j = contexts.length; --j >= 0;) contexts[j] = contexts[j].toLowerCase();
1664 matchAny = (contexts[0] == "*");
1665 for (k = 0; k < ancestors.length; ++k) {
1666 if (!ancestors[k]) continue;
1667 match = false;
1668 for (j = contexts.length; --j >= 0;) match = match || (ancestors[k].tagName.toLowerCase() == contexts[j]);
1669 if (matchAny || match) {
1670 inContext = true;
1671 for (j = attrs.length; --j >= 0;) {
1672 if (!eval("ancestors[k]." + attrs[j])) {
1673 inContext = false;
1674 break;
1675 }
1676 }
1677 if (inContext) break;
1678 }
1679 }
1680 }
1681 if (cmd == "CreateLink") btn.state("enabled", (!text || btn.text) && (inContext || selection));
1682 else btn.state("enabled", (!text || btn.text) && inContext && (selection || !btn.selection));
1683
1684 if (typeof(cmd) == "function") { continue; };
1685 // look-it-up in the custom dropdown boxes
1686 var dropdown = this.config.customSelects[cmd];
1687 if((!text || btn.text) && (typeof(dropdown) != "undefined")) {
1688 dropdown.refresh(this);
1689 continue;
1690 }
1691 switch (cmd) {
1692 case "FontName":
1693 case "FontSize":
1694 if(!text) try {
1695 var value = ("" + doc.queryCommandValue(cmd)).toLowerCase();
1696 if(!value) {
1697 document.getElementById(btn.elementId).selectedIndex = 0;
1698 break;
1699 }
1700 // We rely on the fact that the variable in config has the same name as button name in the toolbar.
1701 var options = this.config[cmd];
1702 k = 0;
1703 for (j in options) {
1704 if((j.toLowerCase() == value) || (options[j].substr(0, value.length).toLowerCase() == value)) {
1705 document.getElementById(btn.elementId).selectedIndex = k;
1706 throw "ok";
1707 }
1708 ++k;
1709 }
1710 document.getElementById(btn.elementId).selectedIndex = 0;
1711 } catch(e) {}
1712 break;
1713 case "FormatBlock":
1714 var blocks = [ ];
1715 for(var i in this.config['FormatBlock']) {
1716 blocks[blocks.length] = this.config['FormatBlock'][i];
1717 }
1718 var deepestAncestor = this._getFirstAncestor(this._getSelection(), blocks);
1719 if(deepestAncestor) {
1720 for(var x= 0; x < blocks.length; x++) {
1721 if(blocks[x].toLowerCase() == deepestAncestor.tagName.toLowerCase()) document.getElementById(btn.elementId).selectedIndex = x;
1722 }
1723 } else {
1724 document.getElementById(btn.elementId).selectedIndex = 0;
1725 }
1726 break;
1727 case "TextIndicator":
1728 if(!text) {
1729 try {with (document.getElementById(btn.elementId).style) {
1730 backgroundColor = HTMLArea._makeColor(doc.queryCommandValue((HTMLArea.is_ie || HTMLArea.is_safari) ? "BackColor" : "HiliteColor"));
1731 // Mozilla
1732 if(/transparent/i.test(backgroundColor)) { backgroundColor = HTMLArea._makeColor(doc.queryCommandValue("BackColor")); }
1733 color = HTMLArea._makeColor(doc.queryCommandValue("ForeColor"));
1734 fontFamily = doc.queryCommandValue("FontName");
1735 // Check if queryCommandState is available
1736 fontWeight = "normal";
1737 fontStyle = "normal";
1738 try { fontWeight = doc.queryCommandState("Bold") ? "bold" : "normal"; } catch(ex) { fontWeight = "normal"; };
1739 try { fontStyle = doc.queryCommandState("Italic") ? "italic" : "normal"; } catch(ex) { fontStyle = "normal"; };
1740 }} catch (e) {
1741 // alert(e + "\n\n" + cmd);
1742 }
1743 }
1744 break;
1745 case "HtmlMode": btn.state("active", text); break;
1746 case "LeftToRight":
1747 case "RightToLeft":
1748 var el = this.getParentElement();
1749 while (el && !HTMLArea.isBlockElement(el)) { el = el.parentNode; }
1750 if (el) btn.state("active",(el.style.direction == ((cmd == "RightToLeft") ? "rtl" : "ltr")));
1751 break;
1752 case "Bold":
1753 case "Italic":
1754 case "StrikeThrough":
1755 case "Underline":
1756 case "Subscript":
1757 case "Superscript":
1758 case "JustifyLeft":
1759 case "JustifyCenter":
1760 case "JustifyRight":
1761 case "JustifyFull":
1762 case "Indent":
1763 case "Outdent":
1764 case "InsertOrderedList":
1765 case "InsertUnorderedList":
1766 commandState = false;
1767 if(!text) try { commandState = doc.queryCommandState(cmd); } catch(e) { commandState = false; }
1768 btn.state("active",commandState);
1769 break;
1770 default: break;
1771 }
1772 }
1773
1774 if (this._customUndo) this._undoTakeSnapshot();
1775 for (i in this.plugins) {
1776 var plugin = this.plugins[i].instance;
1777 if (typeof(plugin.onUpdateToolbar) == "function") plugin.onUpdateToolbar();
1778 }
1779 };
1780
1781 /***************************************************
1782 * DOM TREE MANIPULATION
1783 ***************************************************/
1784
1785 /*
1786 * Surround the currently selected HTML source code with the given tags.
1787 * Delete the selection, if any.
1788 */
1789 HTMLArea.prototype.surroundHTML = function(startTag,endTag) {
1790 this.insertHTML(startTag + this.getSelectedHTML().replace(HTMLArea.Reg_body, "") + endTag);
1791 };
1792
1793 /*
1794 * Change the tag name of a node.
1795 */
1796 HTMLArea.prototype.convertNode = function(el,newTagName) {
1797 var newel = this._doc.createElement(newTagName), p = el.parentNode;
1798 while (el.firstChild) newel.appendChild(el.firstChild);
1799 p.insertBefore(newel, el);
1800 p.removeChild(el);
1801 return newel;
1802 };
1803
1804 /*
1805 * Find a parent of an element with a specified tag
1806 */
1807 HTMLArea.getElementObject = function(el,tagName) {
1808 var oEl = el;
1809 while (oEl != null && oEl.nodeName.toLowerCase() != tagName) oEl = oEl.parentNode;
1810 return oEl;
1811 };
1812
1813 /*
1814 * Make XHTML-compliant nested list
1815 */
1816 HTMLArea.prototype.makeNestedList = function(el) {
1817 var previous, clone;
1818 for (var i = el.firstChild; i; i = i.nextSibling) {
1819 if (/^li$/i.test(i.tagName)) {
1820 for (var j = i.firstChild; j; j = j.nextSibling) {
1821 if (/^(ol|ul)$/i.test(j.tagName)) this.makeNestedList(j);
1822 }
1823 } else if (/^(ol|ul)$/i.test(i.tagName)) {
1824 previous = i.previousSibling;
1825 var clone = i.cloneNode(true);
1826 if (!previous) {
1827 previous = el.insertBefore(this._doc.createElement("li"),i);
1828 previous.appendChild(clone);
1829 } else {
1830 previous.appendChild(clone);
1831 }
1832 HTMLArea.removeFromParent(i);
1833 this.makeNestedList(el);
1834 break;
1835 }
1836 }
1837 };
1838
1839 /***************************************************
1840 * SELECTIONS AND RANGES
1841 ***************************************************/
1842
1843 /*
1844 * Return true if we have some selected content
1845 */
1846 HTMLArea.prototype.hasSelectedText = function() {
1847 return this.getSelectedHTML() != "";
1848 };
1849
1850 /*
1851 * Get an array with all the ancestor nodes of the selection.
1852 */
1853 HTMLArea.prototype.getAllAncestors = function() {
1854 var p = this.getParentElement();
1855 var a = [];
1856 while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
1857 a.push(p);
1858 p = p.parentNode;
1859 }
1860 a.push(this._doc.body);
1861 return a;
1862 };
1863
1864 /*
1865 * Get the deepest ancestor of the selection that is of the specified type
1866 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
1867 */
1868 HTMLArea.prototype._getFirstAncestor = function(sel,types) {
1869 var prnt = this._activeElement(sel);
1870 if (prnt == null) {
1871 try {
1872 prnt = (HTMLArea.is_ie ? this._createRange(sel).parentElement() : this._createRange(sel).commonAncestorContainer);
1873 } catch(e) {
1874 return null;
1875 }
1876 }
1877 if (typeof(types) == 'string') types = [types];
1878
1879 while (prnt) {
1880 if (prnt.nodeType == 1) {
1881 if (types == null) return prnt;
1882 for (var i = 0; i < types.length; i++) {
1883 if(prnt.tagName.toLowerCase() == types[i]) return prnt;
1884 }
1885 if(prnt.tagName.toLowerCase() == 'body') break;
1886 if(prnt.tagName.toLowerCase() == 'table') break;
1887 }
1888 prnt = prnt.parentNode;
1889 }
1890 return null;
1891 };
1892
1893 /***************************************************
1894 * LINKS, IMAGES AND TABLES
1895 ***************************************************/
1896
1897 /*
1898 * Get the create link action function
1899 */
1900 HTMLArea.createLinkDialog = function(editor,link) {
1901 return (function(param) {
1902 if (!param || typeof(param.f_href) == "undefined") return false;
1903 var a = link;
1904 if(!a) {
1905 try {
1906 editor._doc.execCommand("CreateLink",false,param.f_href);
1907 a = editor.getParentElement();
1908 var sel = editor._getSelection();
1909 var range = editor._createRange(sel);
1910 if (!HTMLArea.is_ie) {
1911 a = range.startContainer;
1912 if (!/^a$/i.test(a.tagName)) {
1913 a = a.nextSibling;
1914 if(a == null) a = range.startContainer.parentNode;
1915 }
1916 }
1917 } catch(e) {}
1918 } else {
1919 var href = param.f_href.trim();
1920 editor.selectNodeContents(a);
1921 if (href == "") {
1922 editor._doc.execCommand("Unlink", false, null);
1923 editor.updateToolbar();
1924 return false;
1925 }
1926 else {
1927 a.href = href;
1928 }
1929 }
1930 if (!(a && /^a$/i.test(a.tagName))) return false;
1931 if (typeof(param.f_target) != "undefined") a.target = param.f_target.trim();
1932 if (typeof(param.f_title) != "undefined") a.title = param.f_title.trim();
1933 editor.selectNodeContents(a);
1934 editor.updateToolbar();
1935 editor = null;
1936 link = null;
1937 });
1938 };
1939
1940 /*
1941 * Process the create link request
1942 */
1943 HTMLArea.prototype._createLink = function(link) {
1944 var outparam = null;
1945 this.focusEditor();
1946 if (typeof(link) == "undefined") {
1947 link = this.getParentElement();
1948 if(link) {
1949 if(/^img$/i.test(link.tagName)) link = link.parentNode;
1950 if(!/^a$/i.test(link.tagName)) link = null;
1951 }
1952 }
1953 if (!link) {
1954 var sel = this._getSelection();
1955 if (this._selectionEmpty(sel)) {
1956 alert("You need to select some text before creating a link");
1957 return;
1958 }
1959 outparam = {
1960 f_href : '',
1961 f_title : '',
1962 f_target : '',
1963 f_usetarget : this.config.makeLinkShowsTarget
1964 };
1965 } else {
1966 outparam = {
1967 f_href : HTMLArea.is_ie ? this.stripBaseURL(link.href) : link.getAttribute("href"),
1968 f_title : link.title,
1969 f_target : link.target,
1970 f_usetarget : this.config.makeLinkShowsTarget
1971 };
1972 }
1973 var createLinkDialogFunctRef = HTMLArea.createLinkDialog(this, link);
1974 this._popupDialog("link.html", createLinkDialogFunctRef, outparam, 450, 145);
1975 };
1976
1977 /*
1978 * Get the insert image action function
1979 */
1980 HTMLArea.insertImageDialog = function(editor,image) {
1981 return (function(param) {
1982 if (!param || typeof(param.f_url) == "undefined") return false;
1983 var img = image;
1984 if (!img) {
1985 var sel = editor._getSelection();
1986 var range = editor._createRange(sel);
1987 editor._doc.execCommand("InsertImage",false,param.f_url);
1988 if (HTMLArea.is_ie) {
1989 img = range.parentElement();
1990 if(img.tagName.toLowerCase() != "img") img = img.previousSibling;
1991 } else {
1992 var sel = editor._getSelection();
1993 var range = editor._createRange(sel);
1994 img = range.startContainer;
1995 if (HTMLArea.is_opera) img = img.parentNode;
1996 img = img.lastChild;
1997 while(img && img.nodeName.toLowerCase() != "img") img = img.previousSibling;
1998 }
1999 } else {
2000 img.src = param.f_url;
2001 }
2002
2003 for (var field in param) {
2004 var value = param[field];
2005 switch (field) {
2006 case "f_alt" : img.alt = value; break;
2007 case "f_border" :
2008 if (parseInt(value)) {
2009 img.style.borderWidth = parseInt(value)+"px";
2010 img.style.borderStyle = "solid";
2011 } else {
2012 img.style.borderWidth = "";
2013 img.style.borderStyle = "none";
2014 }
2015 break;
2016 case "f_align" :
2017 img.style.verticalAlign = value;
2018 break;
2019 case "f_vert" :
2020 if (parseInt(value)) {
2021 img.style.marginTop = parseInt(value)+"px";
2022 img.style.marginBottom = parseInt(value)+"px";
2023 } else {
2024 img.style.marginTop = "";
2025 img.style.marginBottom = "";
2026 }
2027 break;
2028 case "f_horiz" :
2029 if (parseInt(value)) {
2030 img.style.marginLeft = parseInt(value)+"px";
2031 img.style.marginRight = parseInt(value)+"px";
2032 } else {
2033 img.style.marginLeft = "";
2034 img.style.marginRight = "";
2035 }
2036 break;
2037 case "f_float" :
2038 if (HTMLArea.is_ie) img.style.styleFloat = value;
2039 else img.style.cssFloat = value;
2040 break;
2041 }
2042 }
2043 editor = null;
2044 image = null;
2045 });
2046 };
2047
2048 /*
2049 * Called when the "InsertImage" button is clicked.
2050 * If an image is already there, it will just modify it's properties.
2051 */
2052 HTMLArea.prototype._insertImage = function(image) {
2053 var outparam = null;
2054 this.focusEditor();
2055 if (typeof(image) == "undefined") {
2056 var image = this.getParentElement();
2057 if(image && !/^img$/i.test(image.tagName)) image = null;
2058 }
2059 if(image) outparam = {
2060 f_base : this.config.baseURL,
2061 f_url : image.getAttribute("src"),
2062 f_alt : image.alt,
2063 f_border : isNaN(parseInt(image.style.borderWidth))?"":parseInt(image.style.borderWidth),
2064 f_align : image.style.verticalAlign,
2065 f_vert : isNaN(parseInt(image.style.marginTop))?"":parseInt(image.style.marginTop),
2066 f_horiz : isNaN(parseInt(image.style.marginLeft))?"":parseInt(image.style.marginLeft),
2067 f_float : HTMLArea.is_ie ? image.style.styleFloat : image.style.cssFloat
2068 };
2069 var insertImageDialogFunctRef = HTMLArea.insertImageDialog(this, image);
2070 this._popupDialog("insert_image.html", insertImageDialogFunctRef, outparam, 580, 460);
2071 };
2072
2073 /*
2074 * Get the insert table action function
2075 */
2076 HTMLArea.insertTableDialog = function(editor, sel, range) {
2077 return (function(param) {
2078 if(!param) return false;
2079 var doc = editor._doc;
2080 var table = doc.createElement("table");
2081 for (var field in param) {
2082 var value = param[field];
2083 if(!value) continue;
2084 switch (field) {
2085 case "f_width" : if(value != "") table.style.width = parseInt(value) + param["f_unit"]; break;
2086 case "f_align" : table.style.textAlign = value; break;
2087 case "f_border" :
2088 if(value != "") {
2089 table.style.borderWidth = parseInt(value)+"px";
2090 table.style.borderStyle = "solid";
2091 }
2092 break;
2093 case "f_spacing" : if(value != "") table.cellSpacing = parseInt(value); break;
2094 case "f_padding" : if(value != "") table.cellPadding = parseInt(value); break;
2095 case "f_float" :
2096 if (HTMLArea.is_ie) table.style.styleFloat = ((value != "not set") ? value : "");
2097 else table.style.cssFloat = ((value != "not set") ? value : "");
2098 break;
2099 }
2100 }
2101 var cellwidth = 0;
2102 if(param.f_fixed) cellwidth = Math.floor(100 / parseInt(param.f_cols));
2103 var tbody = doc.createElement("tbody");
2104 table.appendChild(tbody);
2105 for (var i = param["f_rows"]; i > 0; i--) {
2106 var tr = doc.createElement("tr");
2107 tbody.appendChild(tr);
2108 for (var j = param["f_cols"]; j > 0; j--) {
2109 var td = doc.createElement("td");
2110 if (cellwidth) td.style.width = cellwidth + "%";
2111 if (HTMLArea.is_opera) { td.innerHTML = '&nbsp;'; }
2112 tr.appendChild(td);
2113 }
2114 }
2115 editor.focusEditor();
2116 if(HTMLArea.is_ie) range.pasteHTML(table.outerHTML);
2117 else editor.insertNodeAtSelection(table);
2118 if (editor.config.buttons["toggleborders"] && editor.config.buttons["toggleborders"]["setOnTableCreation"]) editor.plugins["TableOperations"].instance.buttonPress(editor,"TO-toggle-borders");
2119 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) editor.setMode("wysiwyg");
2120 editor.updateToolbar();
2121 editor = null;
2122 sel = null;
2123 range = null;
2124 return true;
2125 });
2126 };
2127
2128 /*
2129 * Process insert table request
2130 */
2131 HTMLArea.prototype._insertTable = function() {
2132 var sel = this._getSelection();
2133 var range = this._createRange(sel);
2134 this.focusEditor();
2135 var insertTableDialogFunctRef = HTMLArea.insertTableDialog(this, sel, range);
2136 this._popupDialog("insert_table.html", insertTableDialogFunctRef, this, 520, 230);
2137 };
2138
2139 /***************************************************
2140 * Category: EVENT HANDLERS
2141 ***************************************************/
2142 HTMLArea.selectColorDialog = function(editor,cmdID) {
2143 return (function(color) {
2144 if(color) editor._doc.execCommand(cmdID, false, "#" + color);
2145 });
2146 };
2147
2148 /*
2149 * Intercept some commands and replace them with our own implementation
2150 */
2151 HTMLArea.prototype.execCommand = function(cmdID, UI, param) {
2152 this.focusEditor();
2153 switch (cmdID) {
2154 case "HtmlMode" : this.setMode(); break;
2155 case "SplitBlock" : this._doc.execCommand('FormatBlock',false,((HTMLArea.is_ie || HTMLArea.is_safari) ? "<div>" : "div")); break;
2156 case "HiliteColor" : (HTMLArea.is_ie || HTMLArea.is_safari) && (cmdID = "BackColor");
2157 case "ForeColor" :
2158 var colorDialogFunctRef = HTMLArea.selectColorDialog(this, cmdID);
2159 this._popupDialog("select_color.html", colorDialogFunctRef, HTMLArea._colorToRgb(this._doc.queryCommandValue(cmdID)), 200, 182);
2160 break;
2161 case "CreateLink" : this._createLink(); break;
2162 case "Undo" :
2163 case "Redo" :
2164 if(this._customUndo) this[cmdID.toLowerCase()]();
2165 else this._doc.execCommand(cmdID,UI,param);
2166 break;
2167 case "InsertTable" : this._insertTable(); break;
2168 case "InsertImage" : this._insertImage(); break;
2169 case "About" : this._popupDialog("about.html", null, this, 475, 350); break;
2170 case "CleanWord" : HTMLArea._wordClean(this, this._doc.body); break;
2171 case "Cut" :
2172 case "Copy" :
2173 case "Paste" :
2174 try {
2175 this._doc.execCommand(cmdID,false,null);
2176 if (cmdID == "Paste" && this.config.cleanWordOnPaste) HTMLArea._wordClean(this, this._doc.body);
2177 } catch (e) {
2178 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) this._mozillaPasteException(cmdID, UI, param);
2179 }
2180 break;
2181 case "LeftToRight" :
2182 case "RightToLeft" :
2183 var dir = (cmdID == "RightToLeft") ? "rtl" : "ltr";
2184 var el = this.getParentElement();
2185 while (el && !HTMLArea.isBlockElement(el)) el = el.parentNode;
2186 if(el) {
2187 if(el.style.direction == dir) el.style.direction = "";
2188 else el.style.direction = dir;
2189 }
2190 break;
2191 case "Indent" :
2192 var el = this.getParentElement();
2193 while (el && (!HTMLArea.isBlockElement(el) || /^li$/i.test(el.nodeName))) el = el.parentNode;
2194 try { this._doc.execCommand(cmdID, UI, param); }
2195 catch(e) { if (this.config.debug) alert(e + "\n\nby execCommand(" + cmdID + ");"); }
2196 if (/^(ol|ul)$/i.test(el.nodeName)) {
2197 this.makeNestedList(el);
2198 this.selectNodeContents(el);
2199 }
2200 break;
2201 case "FontSize" :
2202 case "FontName" :
2203 if (param) {
2204 this._doc.execCommand(cmdID, UI, param);
2205 break;
2206 } else {
2207 var sel = this._getSelection();
2208 // Find font and select it
2209 if (HTMLArea.is_gecko && sel.isCollapsed) {
2210 var fontNode = this._getFirstAncestor(sel, "font");
2211 if (fontNode != null) this.selectNode(fontNode);
2212 }
2213 // Remove format
2214 this._doc.execCommand("RemoveFormat", UI, null);
2215 // Collapse range if font was found
2216 if (HTMLArea.is_gecko && fontNode != null) {
2217 sel = this._getSelection();
2218 var r = this._createRange(sel).cloneRange();
2219 r.collapse(false);
2220 if(HTMLArea.is_safari) {
2221 sel.empty();
2222 sel.setBaseAndExtent(r.startContainer,r.startOffset,r.endContainer,r.endOffset);
2223 } else {
2224 sel.removeAllRanges();
2225 sel.addRange(r);
2226 }
2227 }
2228 }
2229 break;
2230 default :
2231 try { this._doc.execCommand(cmdID, UI, param); }
2232 catch(e) { if (this.config.debug) alert(e + "\n\nby execCommand(" + cmdID + ");"); }
2233 }
2234 this.updateToolbar();
2235 return false;
2236 };
2237
2238 /*
2239 * A generic event handler for things that happen in the IFRAME's document.
2240 */
2241 HTMLArea._editorEvent = function(ev) {
2242 if(!ev) var ev = window.event;
2243 var target = (ev.target) ? ev.target : ev.srcElement;
2244 var owner = (target.ownerDocument) ? target.ownerDocument : target;
2245 if(HTMLArea.is_ie) { // IE5.5 does not report any ownerDocument
2246 while (owner.parentElement) { owner = owner.parentElement; }
2247 }
2248 var editor = RTEarea[owner._editorNo]["editor"];
2249 var keyEvent = ((HTMLArea.is_ie || HTMLArea.is_safari) && ev.type == "keydown") || (!HTMLArea.is_ie && ev.type == "keypress");
2250 editor.focusEditor();
2251
2252 if(keyEvent) {
2253 if(editor._hasPluginWithOnKeyPressHandler) {
2254 for (var i in editor.plugins) {
2255 var plugin = editor.plugins[i].instance;
2256 if (typeof(plugin.onKeyPress) == "function") {
2257 if (plugin.onKeyPress(ev)) return false;
2258 }
2259 }
2260 }
2261 if(ev.ctrlKey) {
2262 if(!ev.altKey) {
2263 // execute hotkey command
2264 var key = String.fromCharCode((HTMLArea.is_ie || HTMLArea.is_safari || HTMLArea.is_opera) ? ev.keyCode : ev.charCode).toLowerCase();
2265 if (HTMLArea.is_gecko && ev.keyCode == 32) key = String.fromCharCode(ev.keyCode).toLowerCase();
2266 var cmd = null;
2267 var value = null;
2268 switch (key) {
2269 // headings
2270 case '1':
2271 case '2':
2272 case '3':
2273 case '4':
2274 case '5':
2275 case '6':
2276 if (editor._toolbarObjects["FormatBlock"]) {
2277 cmd = "FormatBlock";
2278 value = "h" + key;
2279 if(HTMLArea.is_ie || HTMLArea.is_safari) value = "<" + value + ">";
2280 }
2281 break;
2282 case ' ':
2283 editor.insertHTML("&nbsp;");
2284 editor.updateToolbar();
2285 HTMLArea._stopEvent(ev);
2286 return false;
2287 // other hotkeys
2288 default:
2289 if (editor.config.hotKeyList[key]) {
2290 switch (editor.config.hotKeyList[key]) {
2291 case "SelectAll":
2292 case "CleanWord":
2293 cmd = editor.config.hotKeyList[key];
2294 break;
2295 case "Paste":
2296 if (HTMLArea.is_ie || HTMLArea.is_safari) {
2297 cmd = editor.config.hotKeyList[key];
2298 } else if (editor.config.cleanWordOnPaste) {
2299 window.setTimeout("HTMLArea.wordCleanLater(" + owner._editorNo + ", false);", 50);
2300 }
2301 break;
2302 default:
2303 if (editor._toolbarObjects[editor.config.hotKeyList[key]]) {
2304 cmd = editor.config.hotKeyList[key];
2305 if(cmd == "FormatBlock") value = (HTMLArea.is_ie || HTMLArea.is_safari) ? "<p>" : "p";
2306 }
2307 }
2308 }
2309 }
2310 if(cmd) {
2311 editor.execCommand(cmd, false, value);
2312 HTMLArea._stopEvent(ev);
2313 return false;
2314 } else {
2315 editor.updateToolbar();
2316 }
2317 }
2318 } else if (ev.altKey) {
2319 // check if context menu is already handling this event
2320 if(editor.plugins['ContextMenu'] && editor.plugins['ContextMenu'].instance) {
2321 var keys = editor.plugins['ContextMenu'].instance.keys;
2322 if (keys.length > 0) {
2323 var k;
2324 for (var i = keys.length; --i >= 0;) {
2325 k = keys[i];
2326 if (k[0].toLowerCase() == key) {
2327 HTMLArea._stopEvent(ev);
2328 return false;
2329 }
2330 }
2331 }
2332 }
2333 } else if (keyEvent) {
2334 if (HTMLArea.is_gecko) editor._detectURL(ev);
2335 switch (ev.keyCode) {
2336 case 13 : // KEY enter
2337 if (HTMLArea.is_gecko && !ev.shiftKey && !editor.config.disableEnterParagraphs) {
2338 if (editor._checkInsertP(ev)) HTMLArea._stopEvent(ev);
2339 // update the toolbar state after some time
2340 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
2341 editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 50);
2342 }
2343 break;
2344 case 8 : // KEY backspace
2345 case 46 : // KEY delete
2346 if ((HTMLArea.is_gecko && !ev.shiftKey) || HTMLArea.is_ie) {
2347 if (editor._checkBackspace()) HTMLArea._stopEvent(ev);
2348 }
2349 // update the toolbar state after some time
2350 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
2351 editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 50);
2352 break;
2353 case 9: // KEY horizontal tab
2354 if (HTMLArea.is_gecko) {
2355 editor.execCommand( (ev.shiftKey ? "Outdent" : "Indent"), false, null);
2356 HTMLArea._stopEvent(ev);
2357 return false;
2358 }
2359 break;
2360 case 37: // LEFT arrow key
2361 case 39: // RIGHT arrow key
2362 if (HTMLArea.is_ie) {
2363 editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 10);
2364 break;
2365 }
2366 }
2367 }
2368 } else {
2369 // mouse event
2370 if (editor._timerToolbar) window.clearTimeout(editor._timerToolbar);
2371 if (ev.type == "mouseup") editor.updateToolbar();
2372 else editor._timerToolbar = window.setTimeout("HTMLArea.updateToolbar(" + editor._editorNumber + ");", 50);
2373 }
2374 };
2375
2376 HTMLArea.prototype.scrollToCaret = function() {
2377 var e = this.getParentElement(),
2378 w = this._iframe.contentWindow ? this._iframe.contentWindow : window,
2379 h = w.innerHeight || w.height,
2380 d = this._doc,
2381 t = d.documentElement.scrollTop || d.body.scrollTop;
2382 if (typeof(h) == "undefined") return false;
2383 if(e.offsetTop > h + t) w.scrollTo(e.offsetLeft,e.offsetTop - h + e.offsetHeight);
2384 };
2385
2386 /*
2387 * Retrieve the HTML
2388 */
2389 HTMLArea.prototype.getHTML = function() {
2390 switch (this._editMode) {
2391 case "wysiwyg":
2392 if(!this.config.fullPage) { return HTMLArea.getHTML(this._doc.body,false,this); }
2393 else { return this.doctype + "\n" + HTMLArea.getHTML(this._doc.documentElement,true,this); }
2394 case "textmode": return this._textArea.value;
2395 }
2396 return false;
2397 };
2398
2399 /*
2400 * Retrieve the HTML using the fastest method
2401 */
2402 HTMLArea.prototype.getInnerHTML = function() {
2403 switch (this._editMode) {
2404 case "wysiwyg":
2405 if(!this.config.fullPage) return this._doc.body.innerHTML;
2406 else return this.doctype + "\n" + this._doc.documentElement.innerHTML;
2407 case "textmode": return this._textArea.value;
2408 }
2409 return false;
2410 };
2411
2412 /*
2413 * Replace the HTML inside
2414 */
2415 HTMLArea.prototype.setHTML = function(html) {
2416 switch (this._editMode) {
2417 case "wysiwyg":
2418 if(!this.config.fullPage) this._doc.body.innerHTML = html;
2419 else this._doc.body.innerHTML = html;
2420 break;
2421 case "textmode": this._textArea.value = html; break;
2422 }
2423 return false;
2424 };
2425
2426 /*
2427 * Set the given doctype when config.fullPage is true
2428 */
2429 HTMLArea.prototype.setDoctype = function(doctype) {
2430 this.doctype = doctype;
2431 };
2432
2433 /***************************************************
2434 * UTILITY FUNCTIONS
2435 ***************************************************/
2436
2437 // variable used to pass the object to the popup editor window.
2438 HTMLArea._object = null;
2439
2440 /*
2441 * Check if the client agent is supported
2442 */
2443 HTMLArea.checkSupportedBrowser = function() {
2444 if(HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
2445 if(navigator.productSub < 20030210) return false;
2446 }
2447 return HTMLArea.is_gecko || HTMLArea.is_ie;
2448 };
2449
2450 /* EventCache Version 1.0
2451 * Copyright 2005 Mark Wubben
2452 * Adaptation by Stanislas Rolland
2453 * Provides a way for automatically removing events from nodes and thus preventing memory leakage.
2454 * See <http://novemberborn.net/javascript/event-cache> for more information.
2455 * This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>
2456 * Event Cache uses an anonymous function to create a hidden scope chain. This is to prevent scoping issues.
2457 */
2458 HTMLArea._eventCacheConstructor = function() {
2459 var listEvents = [];
2460
2461 return ({
2462 listEvents : listEvents,
2463
2464 add : function(node, sEventName, fHandler) {
2465 listEvents.push(arguments);
2466 },
2467
2468 flush : function() {
2469 var item;
2470 for (var i = listEvents.length; --i >= 0;) {
2471 item = listEvents[i];
2472 try {
2473 HTMLArea._removeEvent(item[0], item[1], item[2]);
2474 item[0][item[1]] = null;
2475 item[0] = null;
2476 item[2] = null;
2477 } catch(e) { }
2478 }
2479 }
2480 });
2481 };
2482
2483 /*
2484 * Register an event
2485 */
2486 HTMLArea._addEvent = function(el,evname,func,useCapture) {
2487 if (typeof(useCapture) == "undefined") var useCapture = false;
2488 if (HTMLArea.is_gecko) {
2489 el.addEventListener(evname, func, !HTMLArea.is_opera || useCapture);
2490 } else {
2491 el.attachEvent("on" + evname, func);
2492 }
2493 HTMLArea._eventCache.add(el, evname, func);
2494 };
2495
2496 /*
2497 * Register a list of events
2498 */
2499 HTMLArea._addEvents = function(el,evs,func,useCapture) {
2500 if (typeof(useCapture) == "undefined") var useCapture = false;
2501 for (var i = evs.length; --i >= 0;) {
2502 HTMLArea._addEvent(el,evs[i], func, useCapture);
2503 }
2504 };
2505
2506 /*
2507 * Remove an event listener
2508 */
2509 HTMLArea._removeEvent = function(el,evname,func) {
2510 if(HTMLArea.is_gecko) {
2511 try { el.removeEventListener(evname, func, true); el.removeEventListener(evname, func, false); } catch(e) { }
2512 } else {
2513 try { el.detachEvent("on" + evname, func); } catch(e) { }
2514 }
2515 };
2516
2517 /*
2518 * Remove a list of events
2519 */
2520 HTMLArea._removeEvents = function(el,evs,func) {
2521 for (var i = evs.length; --i >= 0;) { HTMLArea._removeEvent(el, evs[i], func); }
2522 };
2523
2524 /*
2525 * Stop event propagation
2526 */
2527 HTMLArea._stopEvent = function(ev) {
2528 if(HTMLArea.is_gecko) {
2529 ev.stopPropagation();
2530 ev.preventDefault();
2531 } else {
2532 ev.cancelBubble = true;
2533 ev.returnValue = false;
2534 }
2535 };
2536
2537 /*
2538 * Remove a class name from the class attribute
2539 */
2540 HTMLArea._removeClass = function(el, removeClassName) {
2541 if(!(el && el.className)) return;
2542 var cls = el.className.trim().split(" ");
2543 var ar = new Array();
2544 for (var i = cls.length; i > 0;) {
2545 if (cls[--i] != removeClassName) ar[ar.length] = cls[i];
2546 }
2547 if (ar.length == 0) {
2548 if (!HTMLArea.is_opera) el.removeAttribute(HTMLArea.is_gecko ? "class" : "className");
2549 else el.className = '';
2550
2551 } else el.className = ar.join(" ");
2552 };
2553
2554 /*
2555 * Add a class name to the class attribute
2556 */
2557 HTMLArea._addClass = function(el, addClassName) {
2558 HTMLArea._removeClass(el, addClassName);
2559 if (el.className) el.className += " " + addClassName;
2560 else el.className = addClassName;
2561 };
2562
2563 /*
2564 * Check if a class name is in the class attribute
2565 */
2566 HTMLArea._hasClass = function(el, className) {
2567 if (!el || !el.className) return false;
2568 var cls = el.className.split(" ");
2569 for (var i = cls.length; i > 0;) {
2570 if(cls[--i] == className) return true;
2571 }
2572 return false;
2573 };
2574
2575 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)$/;
2576 HTMLArea.isBlockElement = function(el) { return el && el.nodeType == 1 && HTMLArea.RE_blockTags.test(el.nodeName.toLowerCase()); };
2577 HTMLArea.RE_closingTags = /^(p|span|a|li|ol|ul|dl|dt|td|th|tr|tbody|thead|tfoot|caption|colgroup|table|div|em|i|strong|b|code|cite|blockquote|q|dfn|abbr|acronym|font|center|object|embed|tt|style|script|title|head|clickenlarge)$/;
2578 HTMLArea.RE_noClosingTag = /^(img|br|hr|col|input|area|base|link|meta|param)$/;
2579 HTMLArea.needsClosingTag = function(el) { return el && el.nodeType == 1 && !HTMLArea.RE_noClosingTag.test(el.tagName.toLowerCase()); };
2580
2581 /*
2582 * Perform HTML encoding of some given string
2583 * Borrowed in part from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
2584 */
2585 HTMLArea.htmlDecode = function(str) {
2586 str = str.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
2587 str = str.replace(/&nbsp;/g, "\xA0"); // Decimal 160, non-breaking-space
2588 str = str.replace(/&quot;/g, "\x22");
2589 str = str.replace(/&#39;/g, "'") ;
2590 str = str.replace(/&amp;/g, "&");
2591 return str;
2592 };
2593 HTMLArea.htmlEncode = function(str) {
2594 if (typeof(str) != 'string') str = str.toString(); // we don't need regexp for that, but.. so be it for now.
2595 // Let's not do it twice
2596 str = HTMLArea.htmlDecode(str);
2597 str = str.replace(/&/g, "&amp;");
2598 str = str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
2599 str = str.replace(/\xA0/g, "&nbsp;"); // Decimal 160, non-breaking-space
2600 str = str.replace(/\x22/g, "&quot;"); // \x22 means '"'
2601 str = str.replace(HTMLArea.Reg_entities, "&$1;"); // keep numeric entities
2602 return str;
2603 };
2604
2605 /*
2606 * Retrieve the HTML code from the given node.
2607 * This is a replacement for getting innerHTML, using standard DOM calls.
2608 * Wrapper catches a Mozilla-Exception with non well-formed html source code.
2609 */
2610 HTMLArea.getHTML = function(root, outputRoot, editor){
2611 try {
2612 return HTMLArea.getHTMLWrapper(root,outputRoot,editor);
2613 } catch(e) {
2614 HTMLArea._appendToLog("The HTML document is not well-formed.");
2615 if(!HTMLArea._debugMode) alert(HTMLArea.I18N.msg["HTML-document-not-well-formed"]);
2616 else return HTMLArea.getHTMLWrapper(root,outputRoot,editor);
2617 return editor._doc.body.innerHTML;
2618 }
2619 };
2620
2621 HTMLArea.getHTMLWrapper = function(root, outputRoot, editor) {
2622 var html = "";
2623 if(!root) return html;
2624 switch (root.nodeType) {
2625 case 1: // ELEMENT_NODE
2626 case 11: // DOCUMENT_FRAGMENT_NODE
2627 case 9: // DOCUMENT_NODE
2628 var closed, i, config = editor.config;
2629 var root_tag = (root.nodeType == 1) ? root.tagName.toLowerCase() : '';
2630 if (root_tag == 'br' && config.removeTrailingBR && !root.nextSibling && HTMLArea.isBlockElement(root.parentNode) && (!root.previousSibling || root.previousSibling.nodeName.toLowerCase() != 'br')) break;
2631 if (config.htmlRemoveTagsAndContents && config.htmlRemoveTagsAndContents.test(root_tag)) break;
2632 var custom_tag = (config.customTags && config.customTags.test(root_tag));
2633 var empty_root = (root_tag == "clickenlarge" && !(root.firstChild && root.firstChild.nodeName.toLowerCase() == "img"));
2634 if (outputRoot) outputRoot = !(config.htmlRemoveTags && config.htmlRemoveTags.test(root_tag)) && !empty_root;
2635 if ((HTMLArea.is_ie || HTMLArea.is_safari) && root_tag == "head") {
2636 if(outputRoot) html += "<head>";
2637 var save_multiline = RegExp.multiline;
2638 RegExp.multiline = true;
2639 var txt = root.innerHTML.replace(HTMLArea.RE_tagName, function(str, p1, p2) {
2640 return p1 + p2.toLowerCase();
2641 });
2642 RegExp.multiline = save_multiline;
2643 html += txt;
2644 if(outputRoot) html += "</head>";
2645 break;
2646 } else if (outputRoot) {
2647 if (HTMLArea.is_gecko && root.hasAttribute('_moz_editor_bogus_node')) break;
2648 closed = (!(root.hasChildNodes() || HTMLArea.needsClosingTag(root) || custom_tag));
2649 html = "<" + root_tag;
2650 var a, name, value, attrs = root.attributes;
2651 var n = attrs.length;
2652 for (i = attrs.length; --i >= 0 ;) {
2653 a = attrs.item(i);
2654 name = a.nodeName.toLowerCase();
2655 if ((!a.specified && name != 'value') || /_moz|contenteditable|_msh/.test(name)) continue;
2656 if (!HTMLArea.is_ie || name != "style") {
2657 // IE5.5 reports wrong values. For this reason we extract the values directly from the root node.
2658 // Using Gecko the values of href and src are converted to absolute links unless we get them using nodeValue()
2659 if (typeof(root[a.nodeName]) != "undefined" && name != "href" && name != "src" && name != "style" && !/^on/.test(name)) {
2660 value = root[a.nodeName];
2661 } else {
2662 value = a.nodeValue;
2663 if (HTMLArea.is_ie && (name == "href" || name == "src")) value = editor.stripBaseURL(value);
2664 }
2665 } else { // IE fails to put style in attributes list.
2666 value = root.style.cssText;
2667 }
2668 // Mozilla reports some special values; we don't need them.
2669 if(/(_moz|^$)/.test(value)) continue;
2670 // Strip value="0" reported by IE on all li tags
2671 if(HTMLArea.is_ie && root_tag == "li" && name == "value" && a.nodeValue == 0) continue;
2672 html += " " + name + '="' + HTMLArea.htmlEncode(value) + '"';
2673 }
2674 if (html != "") html += closed ? " />" : ">";
2675 }
2676 for (i = root.firstChild; i; i = i.nextSibling) {
2677 if (/^li$/i.test(i.tagName) && !/^[ou]l$/i.test(root.tagName)) html += "<ul>" + HTMLArea.getHTMLWrapper(i, true, editor) + "</ul>";
2678 else html += HTMLArea.getHTMLWrapper(i, true, editor);
2679 }
2680 if (outputRoot && !closed) html += "</" + root_tag + ">";
2681 break;
2682 case 3: // TEXT_NODE
2683 html = /^(script|style)$/i.test(root.parentNode.tagName) ? root.data : HTMLArea.htmlEncode(root.data);
2684 break;
2685 case 8: // COMMENT_NODE
2686 if (!editor.config.htmlRemoveComments) html = "<!--" + root.data + "-->";
2687 break;
2688 case 4: // Node.CDATA_SECTION_NODE
2689 // Mozilla seems to convert CDATA into a comment when going into wysiwyg mode, don't know about IE
2690 html += '<![CDATA[' + root.data + ']]>';
2691 break;
2692 case 5: // Node.ENTITY_REFERENCE_NODE
2693 html += '&' + root.nodeValue + ';';
2694 break;
2695 case 7: // Node.PROCESSING_INSTRUCTION_NODE
2696 // PI's don't seem to survive going into the wysiwyg mode, (at least in moz) so this is purely academic
2697 html += '<?' + root.target + ' ' + root.data + ' ?>';
2698 break;
2699 default:
2700 break;
2701 }
2702 return html;
2703 };
2704
2705 HTMLArea.getPrevNode = function(node) {
2706 if(!node) return null;
2707 if(node.previousSibling) return node.previousSibling;
2708 if(node.parentNode) return node.parentNode;
2709 return null;
2710 };
2711
2712 HTMLArea.getNextNode = function(node) {
2713 if(!node) return null;
2714 if(node.nextSibling) return node.nextSibling;
2715 if(node.parentNode) return node.parentNode;
2716 return null;
2717 };
2718
2719 HTMLArea.removeFromParent = function(el) {
2720 if(!el.parentNode) return;
2721 var pN = el.parentNode;
2722 pN.removeChild(el);
2723 return el;
2724 };
2725
2726 HTMLArea.prototype.stripBaseURL = function(string) {
2727 var baseurl = this.config.baseURL;
2728
2729 // strip to last directory in case baseurl points to a file
2730 baseurl = baseurl.replace(/[^\/]+$/, '');
2731 var basere = new RegExp(baseurl);
2732 string = string.replace(basere, "");
2733
2734 // strip host-part of URL which is added by MSIE to links relative to server root
2735 baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, '$1');
2736 basere = new RegExp(baseurl);
2737 return string.replace(basere, "");
2738 };
2739
2740 String.prototype.trim = function() {
2741 return this.replace(/^\s+/, '').replace(/\s+$/, '');
2742 };
2743
2744 // creates a rgb-style color from a number
2745 HTMLArea._makeColor = function(v) {
2746 if (typeof(v) != "number") {
2747 // already in rgb (hopefully); IE doesn't get here.
2748 return v;
2749 }
2750 // IE sends number; convert to rgb.
2751 var r = v & 0xFF;
2752 var g = (v >> 8) & 0xFF;
2753 var b = (v >> 16) & 0xFF;
2754 return "rgb(" + r + "," + g + "," + b + ")";
2755 };
2756
2757 // returns hexadecimal color representation from a number or a rgb-style color.
2758 HTMLArea._colorToRgb = function(v) {
2759 if (!v)
2760 return '';
2761
2762 // returns the hex representation of one byte (2 digits)
2763 function hex(d) {
2764 return (d < 16) ? ("0" + d.toString(16)) : d.toString(16);
2765 };
2766
2767 if (typeof(v) == "number") {
2768 // we're talking to IE here
2769 var r = v & 0xFF;
2770 var g = (v >> 8) & 0xFF;
2771 var b = (v >> 16) & 0xFF;
2772 return "#" + hex(r) + hex(g) + hex(b);
2773 }
2774
2775 if (v.substr(0, 3) == "rgb") {
2776 // in rgb(...) form -- Mozilla
2777 var re = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2778 if (v.match(re)) {
2779 var r = parseInt(RegExp.$1);
2780 var g = parseInt(RegExp.$2);
2781 var b = parseInt(RegExp.$3);
2782 return "#" + hex(r) + hex(g) + hex(b);
2783 }
2784 // doesn't match RE?! maybe uses percentages or float numbers
2785 // -- FIXME: not yet implemented.
2786 return null;
2787 }
2788
2789 if (v.substr(0, 1) == "#") {
2790 // already hex rgb (hopefully :D )
2791 return v;
2792 }
2793
2794 // if everything else fails ;)
2795 return null;
2796 };
2797
2798 /** Use XML HTTPRequest to post some data back to the server and do something
2799 * with the response (asyncronously!), this is used by such things as the spellchecker update personal dict function
2800 */
2801 HTMLArea._postback = function(url, data, handler, addParams, charset) {
2802 if (typeof(charset) == "undefined") var charset = "utf-8";
2803 var req = null;
2804 if (window.XMLHttpRequest) req = new XMLHttpRequest();
2805 else if (window.ActiveXObject) {
2806 var success = false;
2807 for (var k = 0; k < HTMLArea.MSXML_XMLHTTP_PROGIDS.length && !success; k++) {
2808 try {
2809 req = new ActiveXObject(HTMLArea.MSXML_XMLHTTP_PROGIDS[k]);
2810 success = true;