1 /***************************************************************
4 * (c) 2002-2004, interactivetools.com, inc.
5 * (c) 2003-2004 dynarch.com
6 * (c) 2004-2009 Stanislas Rolland <typo3(arobas)sjbr.ca>
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.
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.
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.
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.
29 * This copyright notice MUST APPEAR in all copies of the script!
30 ***************************************************************/
32 * Main script of TYPO3 htmlArea RTE
37 /***************************************************
38 * EDITOR INITIALIZATION AND CONFIGURATION
39 ***************************************************/
40 // Avoid re-starting on Ajax request
41 if (typeof(HTMLArea
) != "function") {
44 * HTMLArea object constructor.
46 HTMLArea
= function(textarea
, config
) {
47 if (HTMLArea
.checkSupportedBrowser()) {
48 if (typeof(config
) == "undefined") this.config
= new HTMLArea
.Config();
49 else this.config
= config
;
50 this._htmlArea
= null;
51 this._textArea
= textarea
;
52 if (typeof(this._textArea
) == "string") {
53 this._textArea
= HTMLArea
.getElementById("textarea", this._textArea
);
56 this._timerToolbar
= null;
58 this.eventHandlers
= {};
63 * Browser identification
65 HTMLArea
.agt
= navigator
.userAgent
.toLowerCase();
66 HTMLArea
.is_opera
= (HTMLArea
.agt
.indexOf("opera") != -1);
67 HTMLArea
.is_ie
= (HTMLArea
.agt
.indexOf("msie") != -1) && !HTMLArea
.is_opera
;
68 HTMLArea
.is_safari
= (HTMLArea
.agt
.indexOf("webkit") != -1);
69 HTMLArea
.is_gecko
= (navigator
.product
== "Gecko") || HTMLArea
.is_opera
;
70 HTMLArea
.is_ff2
= (HTMLArea
.agt
.indexOf("firefox/2") != -1);
71 HTMLArea
.is_chrome
= HTMLArea
.is_safari
&& (HTMLArea
.agt
.indexOf("chrome") != -1);
72 // Check on MacOS Wamcom version 1.3 but exclude Firefox rv 1.8.1.3
73 HTMLArea
.is_wamcom
= (HTMLArea
.agt
.indexOf("wamcom") != -1) || (HTMLArea
.is_gecko
&& HTMLArea
.agt
.indexOf("1.3") != -1 && HTMLArea
.agt
.indexOf(".1.3") == -1);
76 * A log for troubleshooting
78 HTMLArea
._appendToLog
= function(str
){
79 if(HTMLArea
._debugMode
) {
80 var log
= document
.getElementById("HTMLAreaLog");
82 log
.appendChild(document
.createTextNode(str
));
83 log
.appendChild(document
.createElement("br"));
89 * Build stack of scripts to be loaded
91 HTMLArea
.loadScript
= function(url
, pluginName
, asynchronous
) {
92 if (typeof(pluginName
) == "undefined") {
95 if (typeof(asynchronous
) == "undefined") {
96 var asynchronous
= true;
98 if (HTMLArea
.is_opera
) url
= _typo3_host_url
+ url
;
99 if (HTMLArea
._compressedScripts
&& url
.indexOf("compressed") == -1) url
= url
.replace(/\.js$/gi, "_compressed.js");
101 pluginName
: pluginName
,
103 asynchronous
: asynchronous
105 HTMLArea
._scripts
.push(scriptInfo
);
109 * Get a script using asynchronous XMLHttpRequest
111 HTMLArea
.MSXML_XMLHTTP_PROGIDS
= new Array("Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP");
112 HTMLArea
.XMLHTTPResponseHandler
= function (i
) {
114 var url
= HTMLArea
._scripts
[i
].url
;
115 if (HTMLArea
._request
[i
].readyState
!= 4) return;
116 if (HTMLArea
._request
[i
].status
== 200) {
118 eval(HTMLArea
._request
[i
].responseText
);
119 HTMLArea
._scriptLoaded
[i
] = true;
122 HTMLArea
._appendToLog("ERROR [HTMLArea::getScript]: Unable to get script " + url
+ ": " + e
);
125 HTMLArea
._appendToLog("ERROR [HTMLArea::getScript]: Unable to get " + url
+ " . Server reported " + HTMLArea
._request
[i
].status
);
129 HTMLArea
._getScript
= function (i
,asynchronous
,url
) {
130 if (typeof(url
) == "undefined") var url
= HTMLArea
._scripts
[i
].url
;
131 if (typeof(asynchronous
) == "undefined") var asynchronous
= HTMLArea
._scripts
[i
].asynchronous
;
132 if (window
.XMLHttpRequest
) HTMLArea
._request
[i
] = new XMLHttpRequest();
133 else if (window
.ActiveXObject
) {
135 for (var k
= 0; k
< HTMLArea
.MSXML_XMLHTTP_PROGIDS
.length
&& !success
; k
++) {
137 HTMLArea
._request
[i
] = new ActiveXObject(HTMLArea
.MSXML_XMLHTTP_PROGIDS
[k
]);
141 if (!success
) return false;
143 var request
= HTMLArea
._request
[i
];
145 HTMLArea
._appendToLog("[HTMLArea::getScript]: Requesting script " + url
);
146 request
.open("GET", url
, asynchronous
);
147 if (asynchronous
) request
.onreadystatechange
= HTMLArea
.XMLHTTPResponseHandler(i
);
148 if (window
.XMLHttpRequest
) request
.send(null);
149 else if (window
.ActiveXObject
) request
.send();
151 if (request
.status
== 200) return request
.responseText
;
161 * Wait for the loading process to complete
163 HTMLArea
.checkInitialLoad
= function() {
164 var scriptsLoaded
= true;
165 for (var i
= HTMLArea
._scripts
.length
; --i
>= 0;) {
166 scriptsLoaded
= scriptsLoaded
&& HTMLArea
._scriptLoaded
[i
];
168 if(HTMLArea
.loadTimer
) window
.clearTimeout(HTMLArea
.loadTimer
);
170 HTMLArea
.is_loaded
= true;
171 HTMLArea
._appendToLog("[HTMLArea::init]: All scripts successfully loaded.");
172 HTMLArea
._appendToLog("[HTMLArea::init]: Editor url set to: " + _editor_url
);
173 HTMLArea
._appendToLog("[HTMLArea::init]: Editor skin CSS set to: " + _editor_CSS
);
174 HTMLArea
._appendToLog("[HTMLArea::init]: Editor content skin CSS set to: " + _editor_edited_content_CSS
);
175 if (window
.ActiveXObject
) {
176 for (var i
= HTMLArea
._scripts
.length
; --i
>= 0;) {
177 HTMLArea
._request
[i
].onreadystatechange
= new Function();
178 HTMLArea
._request
[i
] = null;
182 HTMLArea
.loadTimer
= window
.setTimeout("HTMLArea.checkInitialLoad();", 200);
190 HTMLArea
.init
= function() {
191 if (typeof(_editor_url
) != "string") {
192 window
.setTimeout("HTMLArea.init();", 50);
194 // Set some basic paths
195 // Leave exactly one backslash at the end of _editor_url
196 _editor_url
= _editor_url
.replace(/\x2f*$/, '/');
197 if (typeof(_editor_skin
) == "string") _editor_skin
= _editor_skin
.replace(/\x2f*$/, '/');
198 else _editor_skin
= _editor_url
+ "skins/default/";
199 if (typeof(_editor_CSS
) != "string") _editor_CSS
= _editor_url
+ "skins/default/htmlarea.css";
200 if (typeof(_editor_edited_content_CSS
) != "string") _editor_edited_content_CSS
= _editor_skin
+ "htmlarea-edited-content.css";
201 if (typeof(_editor_lang
) == "string") _editor_lang
= _editor_lang
? _editor_lang
.toLowerCase() : "en";
202 HTMLArea
.editorCSS
= _editor_CSS
;
203 // Initialize event cache
204 HTMLArea
._eventCache
= HTMLArea
._eventCacheConstructor();
205 // Set troubleshooting mode
206 HTMLArea
._debugMode
= false;
207 if (typeof(_editor_debug_mode
) != "undefined") HTMLArea
._debugMode
= _editor_debug_mode
;
208 // Using compressed scripts
209 HTMLArea
._compressedScripts
= false;
210 if (typeof(_editor_compressed_scripts
) != "undefined") HTMLArea
._compressedScripts
= _editor_compressed_scripts
;
211 // Localization of core script
212 HTMLArea
.I18N
= HTMLArea_langArray
;
213 // Build array of scripts to be loaded
214 HTMLArea
.is_loaded
= false;
216 HTMLArea
._scripts
= [];
217 HTMLArea
._scriptLoaded
= [];
218 HTMLArea
._request
= [];
219 if (HTMLArea
.is_gecko
) HTMLArea
.loadScript(RTEarea
[0]["htmlarea-gecko"] ? RTEarea
[0]["htmlarea-gecko"] : _editor_url
+ "htmlarea-gecko.js");
220 if (HTMLArea
.is_ie
) HTMLArea
.loadScript(RTEarea
[0]["htmlarea-ie"] ? RTEarea
[0]["htmlarea-ie"] : _editor_url
+ "htmlarea-ie.js");
221 for (var i
= 0, n
= HTMLArea_plugins
.length
; i
< n
; i
++) {
222 HTMLArea
.loadScript(HTMLArea_plugins
[i
].url
, "", HTMLArea_plugins
[i
].asynchronous
);
224 // Get all the scripts
225 if (window
.XMLHttpRequest
|| window
.ActiveXObject
) {
228 for (var i
= 0, n
= HTMLArea
._scripts
.length
; i
< n
&& success
; i
++) {
229 if (HTMLArea
._scripts
[i
].asynchronous
) {
230 success
= success
&& HTMLArea
._getScript(i
);
233 eval(HTMLArea
._getScript(i
));
234 HTMLArea
._scriptLoaded
[i
] = true;
236 HTMLArea
._appendToLog("ERROR [HTMLArea::getScript]: Unable to get script " + url
+ ": " + e
);
241 HTMLArea
._appendToLog("ERROR [HTMLArea::init]: Unable to use XMLHttpRequest: "+ e
);
244 HTMLArea
.checkInitialLoad();
246 if (HTMLArea
.is_ie
) window
.setTimeout('alert(HTMLArea.I18N.msg["ActiveX-required"]);', 200);
249 if (HTMLArea
.is_ie
) alert(HTMLArea
.I18N
.msg
["ActiveX-required"]);
255 * Compile some regular expressions
257 HTMLArea
.RE_tagName
= /(<\/|<)\s
*([^ \t\n>]+)/ig
;
258 HTMLArea
.RE_doctype
= /(<!doctype((.|\n)*?)>)\n?/i;
259 HTMLArea
.RE_head
= /<head>((.|\n)*?)<\/head
>/i
;
260 HTMLArea
.RE_body
= /<body>((.|\n)*?)<\/body
>/i
;
261 HTMLArea
.Reg_body
= new RegExp("<\/?(body)[^>]*>", "gi");
262 HTMLArea
.reservedClassNames
= /htmlarea/;
263 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;
264 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
;
267 * Editor configuration object constructor
270 HTMLArea
.Config
= function () {
272 this.height
= "auto";
273 // whether the toolbar should be included in the size or not.
274 this.sizeIncludesToolbar
= true;
275 // if true then HTMLArea will retrieve the full HTML, starting with the <HTML> tag.
276 this.fullPage
= false;
277 // if the site is secure, create a secure iframe
278 this.useHTTPS
= false;
281 this.enableMozillaExtension
= true;
282 this.disableEnterParagraphs
= false;
283 this.disableObjectResizing
= false;
284 this.removeTrailingBR
= false;
285 // style included in the iframe document
286 this.editedContentStyle
= _editor_edited_content_CSS
;
289 // remove tags (these have to be a regexp, or null if this functionality is not desired)
290 this.htmlRemoveTags
= null;
291 // remove tags and any contents (these have to be a regexp, or null if this functionality is not desired)
292 this.htmlRemoveTagsAndContents
= null;
294 this.htmlRemoveComments
= false;
295 // custom tags (these have to be a regexp, or null if this functionality is not desired)
296 this.customTags
= null;
297 // BaseURL included in the iframe document
298 this.baseURL
= document
.baseURI
|| document
.URL
;
299 if(this.baseURL
&& this.baseURL
.match(/(.*)\/([^\/]+)/)) this.baseURL
= RegExp
.$1 + "/";
301 this.imgURL
= "images/";
302 this.popupURL
= "popups/";
304 this.documentType
= '<!DOCTYPE html\r'
305 + ' PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\r'
306 + ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r';
309 InsertHorizontalRule
: ["Horizontal Rule", "ed_hr.gif",false, function(editor
) {editor
.execCommand("InsertHorizontalRule");}],
310 SelectAll
: ["SelectAll", "", true, function(editor
) {editor
.execCommand("SelectAll");}, null, true, false]
314 a
: { cmd
: "SelectAll", action
: null}
317 // Initialize tooltips from the I18N module, generate correct image path
318 for (var buttonId
in this.btnList
) {
319 if (this.btnList
.hasOwnProperty(buttonId
)) {
320 var btn
= this.btnList
[buttonId
];
321 if (typeof(HTMLArea
.I18N
.tooltips
[buttonId
.toLowerCase()]) !== "undefined") {
322 btn
[0] = HTMLArea
.I18N
.tooltips
[buttonId
.toLowerCase()];
324 if (typeof(btn
[1]) === "string") {
325 btn
[1] = _editor_skin
+ this.imgURL
+ btn
[1];
327 btn
[1][0] = _editor_skin
+ this.imgURL
+ btn
[1][0];
331 this.customSelects
= {};
335 * Register a new button with the configuration.
336 * It can be called with all arguments, or with only one (first one). When called with
337 * only one argument it must be an object with the following properties:
338 * id, tooltip, image, textMode, action, context. Examples:
340 * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...}, context);
341 * 2. config.registerButton({
342 * id : "my-hilite", // Unique id for the button
343 * tooltip : "Hilite text", // the tooltip
344 * image : "my-hilite.gif", // image to be displayed in the toolbar
345 * textMode : false, // disabled in text mode
346 * action : function(editor) { // called when the button is clicked
347 * editor.surroundHTML('<span class="hilite">', '</span>');
349 * context : "p" // will be disabled if not inside a <p> element
350 * hide : false // hide in menu and show only in context menu
351 * selection : false // will be disabled if there is no selection
352 * dialog : true // the button opens a dialogue
353 * dimensions : { width: nn, height: mm } // opening dimensions of the dialogue window
356 HTMLArea
.Config
.prototype.registerButton
= function(id
,tooltip
,image
,textMode
,action
,context
,hide
,selection
, dialog
, dimensions
) {
358 switch (typeof(id
)) {
359 case "string": buttonId
= id
; break;
360 case "object": buttonId
= id
.id
; break;
361 default: HTMLArea
._appendToLog("[HTMLArea.Config::registerButton]: invalid arguments");
364 if (typeof(this.customSelects
[buttonId
]) !== "undefined") {
365 HTMLArea
._appendToLog("[HTMLArea.Config::registerButton]: A dropdown with the same Id: " + buttonId
+ " already exists.");
368 if (typeof(this.btnList
[buttonId
]) !== "undefined") {
369 HTMLArea
._appendToLog("[HTMLArea.Config::registerButton]: A button with the same Id: " + buttonId
+ " already exists and will be overidden.");
371 switch (typeof(id
)) {
373 if (typeof(hide
) === "undefined") var hide
= false;
374 if (typeof(selection
) === "undefined") var selection
= false;
375 if (typeof(dialog
) === "undefined") var dialog
= true;
376 this.btnList
[id
] = [tooltip
, image
, textMode
, action
, context
, hide
, selection
, dialog
, dimensions
];
379 if (typeof(id
.hide
) === "undefined") id
.hide
= false;
380 if (typeof(id
.selection
) === "undefined") id
.selection
= false;
381 if (typeof(id
.dialog
) === "undefined") id
.dialog
= true;
382 this.btnList
[id
.id
] = [id
.tooltip
, id
.image
, id
.textMode
, id
.action
, id
.context
, id
.hide
, id
.selection
, id
.dialog
, id
.dimensions
];
389 * Register a dropdown box with the editor configuration.
391 HTMLArea
.Config
.prototype.registerDropdown
= function(dropDownConfiguration
) {
392 if (typeof(this.customSelects
[dropDownConfiguration
.id
]) != "undefined") {
393 HTMLArea
._appendToLog("[HTMLArea.Config::registerDropdown]: A dropdown with the same ID " + dropDownConfiguration
.id
+ " already exists and will be overidden.");
395 if (typeof(this.btnList
[dropDownConfiguration
.id
]) != "undefined") {
396 HTMLArea
._appendToLog("ERROR [HTMLArea.Config::registerDropdown]: A button with the same ID " + dropDownConfiguration
.id
+ " already exists.");
399 this.customSelects
[dropDownConfiguration
.id
] = dropDownConfiguration
;
404 * Register a hotkey with the editor configuration.
406 HTMLArea
.Config
.prototype.registerHotKey
= function(hotKeyConfiguration
) {
407 if (typeof(this.hotKeyList
[hotKeyConfiguration
.id
]) != "undefined") {
408 HTMLArea
._appendToLog("[HTMLArea.Config::registerHotKey]: A hotkey with the same key " + hotKeyConfiguration
.id
+ " already exists and will be overidden.");
410 this.hotKeyList
[hotKeyConfiguration
.id
] = hotKeyConfiguration
;
414 HTMLArea
.Config
.prototype.getDocumentType
= function () {
415 return this.documentType
;
418 /***************************************************
420 ***************************************************/
422 * Update the state of a toolbar element.
423 * This function is member of a toolbar element object, unnamed object created by createButton or createSelect functions.
425 HTMLArea
.setButtonStatus
= function(id
,newval
) {
426 var oldval
= this[id
];
427 var el
= document
.getElementById(this.elementId
);
428 if (oldval
!= newval
) {
432 if (!HTMLArea
.is_wamcom
) {
433 HTMLArea
._removeClass(el
, "buttonDisabled");
434 HTMLArea
._removeClass(el
.parentNode
, "buttonDisabled");
438 if (!HTMLArea
.is_wamcom
) {
439 HTMLArea
._addClass(el
, "buttonDisabled");
440 HTMLArea
._addClass(el
.parentNode
, "buttonDisabled");
447 HTMLArea
._addClass(el
, "buttonPressed");
448 HTMLArea
._addClass(el
.parentNode
, "buttonPressed");
451 HTMLArea
._removeClass(el
, "buttonPressed");
452 HTMLArea
._removeClass(el
.parentNode
, "buttonPressed");
462 * Create a new line in the toolbar
464 HTMLArea
.newLine
= function(toolbar
) {
465 tb_line
= document
.createElement("ul");
466 tb_line
.className
= "tb-line";
467 toolbar
.appendChild(tb_line
);
472 * Add a toolbar element to the current line or group
474 HTMLArea
.addTbElement
= function(element
, tb_line
, first_cell_on_line
) {
475 var tb_cell
= document
.createElement("li");
476 if (first_cell_on_line
) tb_cell
.className
= "tb-first-cell";
477 else tb_cell
.className
= "tb-cell";
478 HTMLArea
._addClass(tb_cell
, element
.className
);
479 tb_line
.appendChild(tb_cell
);
480 tb_cell
.appendChild(element
);
481 if(element
.style
.display
== "none") {
482 tb_cell
.style
.display
= "none";
483 if(HTMLArea
._hasClass(tb_cell
.previousSibling
, "separator")) tb_cell
.previousSibling
.style
.display
= "none";
489 * Create a new group on the current line
491 HTMLArea
.addTbGroup
= function(tb_line
, first_cell_on_line
) {
492 var tb_group
= document
.createElement("ul");
493 tb_group
.className
= "tb-group";
494 HTMLArea
.addTbElement(tb_group
, tb_line
, first_cell_on_line
);
499 * Create a combo box and add it to the toolbar
501 HTMLArea
.prototype.createSelect
= function(txt
,tb_line
,first_cell_on_line
,labelObj
) {
509 first
: first_cell_on_line
,
514 var dropdown
= this.config
.customSelects
[cmd
];
515 if (typeof(dropdown
) != "undefined") {
516 options
= dropdown
.options
;
517 context
= dropdown
.context
;
518 if (typeof(dropdown
.tooltip
) != "undefined") tooltip
= dropdown
.tooltip
;
521 newObj
["el"] = document
.createElement("select");
522 newObj
["el"].className
= "select";
523 newObj
["el"].title
= tooltip
;
524 newObj
["el"].id
= this._editorNumber
+ "-" + txt
;
525 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
527 name
: txt
, // field name
528 elementId
: newObj
["el"].id
, // unique id for the UI element
529 enabled
: true, // is it enabled?
530 text
: false, // enabled in text mode?
531 cmd
: cmd
, // command ID
532 state
: HTMLArea
.setButtonStatus
, // for changing state
534 editorNumber
: this._editorNumber
536 this._toolbarObjects
[txt
] = obj
;
537 newObj
["el"]._obj
= obj
;
538 if (labelObj
["labelRef"]) {
539 labelObj
["el"].htmlFor
= newObj
["el"].id
;
540 newObj
["labelUsed"] = true;
542 HTMLArea
._addEvent(newObj
["el"], "change", HTMLArea
.toolBarButtonHandler
);
544 for (var i
in options
) {
545 if (options
.hasOwnProperty(i
)) {
546 var op
= document
.createElement("option");
548 op
.value
= options
[i
];
549 newObj
["el"].appendChild(op
);
553 newObj
["created"] = true;
560 * Create a button and add it to the toolbar
562 HTMLArea
.prototype.createButton
= function (txt
,tb_line
,first_cell_on_line
,labelObj
) {
567 first
: first_cell_on_line
,
573 newObj
["el"] = document
.createElement("div");
574 newObj
["el"].className
= "separator";
575 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
576 newObj
["created"] = true;
579 newObj
["el"] = document
.createElement("div");
580 newObj
["el"].className
= "space";
581 newObj
["el"].innerHTML
= " ";
582 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
583 newObj
["created"] = true;
585 case "TextIndicator":
586 newObj
["el"] = document
.createElement("div");
587 newObj
["el"].appendChild(document
.createTextNode("A"));
588 newObj
["el"].className
= "indicator";
589 newObj
["el"].title
= HTMLArea
.I18N
.tooltips
.textindicator
;
590 newObj
["el"].id
= this._editorNumber
+ "-" + txt
;
591 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
594 elementId
: newObj
["el"].id
,
598 cmd
: "TextIndicator",
599 state
: HTMLArea
.setButtonStatus
601 this._toolbarObjects
[txt
] = obj
;
602 newObj
["created"] = true;
605 btn
= this.config
.btnList
[txt
];
607 if(!newObj
["created"] && btn
) {
608 newObj
["el"] = document
.createElement("button");
609 newObj
["el"].title
= btn
[0];
610 newObj
["el"].className
= "button";
611 newObj
["el"].id
= this._editorNumber
+ "-" + txt
;
612 if (btn
[5]) newObj
["el"].style
.display
= "none";
613 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
615 name
: txt
, // the button name
616 elementId
: newObj
["el"].id
, // unique id for the UI element
617 enabled
: true, // is it enabled?
618 active
: false, // is it pressed?
619 text
: btn
[2], // enabled in text mode?
620 cmd
: btn
[3], // the function to be invoked
621 state
: HTMLArea
.setButtonStatus
, // for changing state
622 context
: btn
[4] || null, // enabled in a certain context?
623 selection
: btn
[6], // disabled when no selection?
624 editorNumber
: this._editorNumber
626 this._toolbarObjects
[txt
] = obj
;
627 newObj
["el"]._obj
= obj
;
628 if (labelObj
["labelRef"]) {
629 labelObj
["el"].htmlFor
= newObj
["el"].id
;
630 newObj
["labelUsed"] = true;
632 HTMLArea
._addEvents(newObj
["el"],["mouseover", "mouseout", "mousedown", "click"], HTMLArea
.toolBarButtonHandler
);
633 newObj
["el"].className
+= " " + txt
;
634 newObj
["created"] = true;
640 * Create a label and add it to the toolbar
642 HTMLArea
.createLabel
= function(txt
,tb_line
,first_cell_on_line
) {
647 first
: first_cell_on_line
649 if (/^([IT])\[(.*?)\]/.test(txt
)) {
650 var l7ed
= RegExp
.$1 == "I"; // localized?
651 var label
= RegExp
.$2;
652 if (l7ed
) label
= HTMLArea
.I18N
.dialogs
[label
];
653 newObj
["el"] = document
.createElement("label");
654 newObj
["el"].className
= "label";
655 newObj
["el"].innerHTML
= label
;
656 newObj
["labelRef"] = true;
657 newObj
["created"] = true;
658 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
664 * Create the toolbar and append it to the _htmlarea.
666 HTMLArea
.prototype._createToolbar
= function () {
667 var j
, k
, code
, n
= this.config
.toolbar
.length
, m
,
668 tb_line
= null, tb_group
= null,
669 first_cell_on_line
= true,
670 labelObj
= new Object(),
671 tbObj
= new Object();
673 var toolbar
= document
.createElement("div");
674 this._toolbar
= toolbar
;
675 toolbar
.className
= "toolbar";
676 toolbar
.unselectable
= "1";
677 this._toolbarObjects
= new Object();
679 for (j
= 0; j
< n
; ++j
) {
680 tb_line
= HTMLArea
.newLine(toolbar
);
681 if(!this.config
.keepButtonGroupTogether
) HTMLArea
._addClass(tb_line
, "free-float");
682 first_cell_on_line
= true;
684 var group
= this.config
.toolbar
[j
];
686 for (k
= 0; k
< m
; ++k
) {
688 if (code
== "linebreak") {
689 tb_line
= HTMLArea
.newLine(toolbar
);
690 if(!this.config
.keepButtonGroupTogether
) HTMLArea
._addClass(tb_line
, "free-float");
691 first_cell_on_line
= true;
694 if ((code
== "separator" || first_cell_on_line
) && this.config
.keepButtonGroupTogether
) {
695 tb_group
= HTMLArea
.addTbGroup(tb_line
, first_cell_on_line
);
696 first_cell_on_line
= false;
699 if (/^([IT])\[(.*?)\]/.test(code
)) {
700 labelObj
= HTMLArea
.createLabel(code
, (tb_group
?tb_group
:tb_line
), first_cell_on_line
);
701 created
= labelObj
["created"] ;
702 first_cell_on_line
= labelObj
["first"];
705 tbObj
= this.createButton(code
, (tb_group
?tb_group
:tb_line
), first_cell_on_line
, labelObj
);
706 created
= tbObj
["created"];
707 first_cell_on_line
= tbObj
["first"];
708 if(tbObj
["labelUsed"]) labelObj
["labelRef"] = false;
711 tbObj
= this.createSelect(code
, (tb_group
?tb_group
:tb_line
), first_cell_on_line
, labelObj
);
712 created
= tbObj
["created"];
713 first_cell_on_line
= tbObj
["first"];
714 if(tbObj
["labelUsed"]) labelObj
["labelRef"] = false;
716 if (!created
) HTMLArea
._appendToLog("ERROR [HTMLArea::createToolbar]: Unknown toolbar item: " + code
);
721 tb_line
= HTMLArea
.newLine(toolbar
);
722 this._htmlArea
.appendChild(toolbar
);
726 * Handle toolbar element events handler
728 HTMLArea
.toolBarButtonHandler
= function(ev
) {
729 if(!ev
) var ev
= window
.event
;
730 var target
= (ev
.target
) ? ev
.target
: ev
.srcElement
;
731 while (target
.tagName
.toLowerCase() == "img" || target
.tagName
.toLowerCase() == "div") target
= target
.parentNode
;
732 var obj
= target
._obj
;
733 var editorNumber
= obj
["editorNumber"];
734 var editor
= RTEarea
[editorNumber
]["editor"];
738 HTMLArea
._addClass(target
, "buttonHover");
739 HTMLArea
._addClass(target
.parentNode
, "buttonHover");
742 HTMLArea
._removeClass(target
, "buttonHover");
743 HTMLArea
._removeClass(target
.parentNode
, "buttonHover");
744 HTMLArea
._removeClass(target
, "buttonActive");
745 HTMLArea
._removeClass(target
.parentNode
, "buttonActive");
747 HTMLArea
._addClass(target
, "buttonPressed");
748 HTMLArea
._addClass(target
.parentNode
, "buttonPressed");
752 HTMLArea
._addClass(target
, "buttonActive");
753 HTMLArea
._addClass(target
.parentNode
, "buttonActive");
754 HTMLArea
._removeClass(target
, "buttonPressed");
755 HTMLArea
._removeClass(target
.parentNode
, "buttonPressed");
756 HTMLArea
._stopEvent(ev
);
759 HTMLArea
._removeClass(target
, "buttonActive");
760 HTMLArea
._removeClass(target
.parentNode
, "buttonActive");
761 HTMLArea
._removeClass(target
, "buttonHover");
762 HTMLArea
._removeClass(target
.parentNode
, "buttonHover");
763 obj
.cmd(editor
, obj
.name
);
764 HTMLArea
._stopEvent(ev
);
765 if (HTMLArea
.is_opera
) {
766 editor
._iframe
.focus();
768 if (!editor
.config
.btnList
[obj
.name
][7]) {
769 editor
.updateToolbar();
773 editor
.focusEditor();
774 var dropdown
= editor
.config
.customSelects
[obj
.name
];
775 if (typeof(dropdown
) !== "undefined") {
776 dropdown
.action(editor
, obj
.name
);
777 HTMLArea
._stopEvent(ev
);
778 if (HTMLArea
.is_opera
) {
779 editor
._iframe
.focus();
781 editor
.updateToolbar();
783 HTMLArea
._appendToLog("ERROR [HTMLArea::toolBarButtonHandler]: Combo box " + obj
.name
+ " not registered.");
790 * Create the htmlArea iframe and replace the textarea with it.
792 HTMLArea
.prototype.generate
= function () {
794 // get the textarea and hide it
795 var textarea
= this._textArea
;
796 textarea
.style
.display
= "none";
798 // create the editor framework and insert the editor before the textarea
799 var htmlarea
= document
.createElement("div");
800 htmlarea
.className
= "htmlarea";
801 htmlarea
.style
.width
= textarea
.style
.width
;
802 this._htmlArea
= htmlarea
;
803 textarea
.parentNode
.insertBefore(htmlarea
, textarea
);
806 // we have a form, on reset, re-initialize the HTMLArea content and update the toolbar
807 var f
= textarea
.form
;
808 if (typeof(f
.onreset
) == "function") {
809 var funcref
= f
.onreset
;
810 if (typeof(f
.__msh_prevOnReset
) == "undefined") f
.__msh_prevOnReset
= [];
811 f
.__msh_prevOnReset
.push(funcref
);
813 f
._editorNumber
= this._editorNumber
;
814 HTMLArea
._addEvent(f
, "reset", HTMLArea
.resetHandler
);
817 // create & append the toolbar
818 this._createToolbar();
819 HTMLArea
._appendToLog("[HTMLArea::generate]: Toolbar successfully created.");
821 // create and append the IFRAME
822 var iframe
= document
.createElement("iframe");
823 if (HTMLArea
.is_gecko
&& !HTMLArea
.is_safari
&& !HTMLArea
.is_opera
) {
824 iframe
.setAttribute("src", "javascript:void(0);");
826 iframe
.setAttribute("src", (HTMLArea
.is_opera
?_typo3_host_url
:"") + _editor_url
+ "popups/blank.html");
828 iframe
.className
= "editorIframe";
829 if (!this.getPluginInstance("StatusBar")) {
830 iframe
.className
+= " noStatusBar";
832 htmlarea
.appendChild(iframe
);
833 this._iframe
= iframe
;
834 HTMLArea
._appendToLog("[HTMLArea::generate]: Editor iframe successfully created.");
840 * Size the iframe according to user's prefs or initial textarea
842 HTMLArea
.prototype.sizeIframe
= function(diff
) {
843 var height
= (this.config
.height
== "auto" ? (this._textArea
.style
.height
) : this.config
.height
);
844 var textareaHeight
= height
;
845 // Clone the array of nested tabs and inline levels instead of using a reference (this.accessParentElements will change the array):
846 var parentElements
= (this.nested
.sorted
&& this.nested
.sorted
.length
? [].concat(this.nested
.sorted
) : []);
847 // Walk through all nested tabs and inline levels to make a correct positioning:
848 var dimensions
= this.accessParentElements(parentElements
, 'this.getDimensions()');
850 if(height
.indexOf("%") == -1) {
851 height
= parseInt(height
) - diff
;
852 if (this.config
.sizeIncludesToolbar
) {
853 this._initialToolbarOffsetHeight
= dimensions
.toolbar
.height
;
854 height
-= dimensions
.toolbar
.height
;
855 height
-= dimensions
.statusbar
.height
;
857 if (height
< 0) height
= 0;
858 textareaHeight
= (height
- 4);
859 if (textareaHeight
< 0) textareaHeight
= 0;
861 textareaHeight
+= "px";
863 this._iframe
.style
.height
= height
;
864 this._textArea
.style
.height
= textareaHeight
;
865 var textareaWidth
= (this.config
.width
== "auto" ? this._textArea
.style
.width
: this.config
.width
);
866 var iframeWidth
= textareaWidth
;
867 if(textareaWidth
.indexOf("%") == -1) {
868 iframeWidth
= parseInt(textareaWidth
) + "px";
869 textareaWidth
= parseInt(textareaWidth
) - diff
;
870 if (textareaWidth
< 0) textareaWidth
= 0;
871 textareaWidth
+= 'px';
873 this._iframe
.style
.width
= "100%";
874 if (HTMLArea
.is_opera
) this._iframe
.style
.width
= iframeWidth
;
875 this._textArea
.style
.width
= textareaWidth
;
879 * Get the dimensions of the toolbar and statusbar.
881 * @return object An object with width/height pairs for statusbar and toolbar.
882 * @author Oliver Hader <oh@inpublica.de>
884 HTMLArea
.prototype.getDimensions
= function() {
886 toolbar
: {width
: this._toolbar
.offsetWidth
, height
: this._toolbar
.offsetHeight
},
887 statusbar
: {width
: (this.getPluginInstance("StatusBar") ? this.getPluginInstance("StatusBar").statusBar
.offsetWidth
: 0), height
: (this.getPluginInstance("StatusBar") ? this.getPluginInstance("StatusBar").statusBar
.offsetHeight
: 0)}
892 * Access an inline relational element or tab menu and make it "accesible".
893 * If a parent object has the style "display: none", offsetWidth & offsetHeight are '0'.
895 * @params object callbackFunc: A function to be called, when the embedded objects are "accessible".
896 * @return object An object returned by the callbackFunc.
897 * @author Oliver Hader <oh@inpublica.de>
899 HTMLArea
.prototype.accessParentElements
= function(parentElements
, callbackFunc
) {
902 if (parentElements
.length
) {
903 var currentElement
= parentElements
.pop();
904 var elementStyle
= document
.getElementById(currentElement
).style
;
905 var actionRequired
= (elementStyle
.display
== 'none' ? true : false);
907 if (actionRequired
) {
908 var originalVisibility
= elementStyle
.visibility
;
909 var originalPosition
= elementStyle
.position
;
910 elementStyle
.visibility
= 'hidden';
911 elementStyle
.position
= 'absolute';
912 elementStyle
.display
= '';
915 result
= this.accessParentElements(parentElements
, callbackFunc
);
917 if (actionRequired
) {
918 elementStyle
.display
= 'none';
919 elementStyle
.position
= originalPosition
;
920 elementStyle
.visibility
= originalVisibility
;
924 result
= eval(callbackFunc
);
932 * Simplify the array of nested levels. Create an indexed array with the correct names of the elements.
934 * @param object nested: The array with the nested levels
935 * @return object The simplified array
936 * @author Oliver Hader <oh@inpublica.de>
938 HTMLArea
.simplifyNested
= function(nested
) {
939 var i
, type
, level
, max
, simplifiedNested
=[];
940 if (nested
&& nested
.length
) {
941 if (nested
[0][0]=='inline') {
942 nested
= inline
.findContinuedNestedLevel(nested
, nested
[0][1]);
944 for (i
=0, max
=nested
.length
; i
<max
; i
++) {
946 level
= nested
[i
][1];
948 simplifiedNested
.push(level
+'-DIV');
949 } else if (type
=='inline') {
950 simplifiedNested
.push(level
+'_fields');
954 return simplifiedNested
;
958 * Initialize the iframe
960 HTMLArea
.initIframe
= function(editorNumber
) {
961 var editor
= RTEarea
[editorNumber
]["editor"];
965 HTMLArea
.prototype.initIframe
= function() {
966 if (this._initIframeTimer
) window
.clearTimeout(this._initIframeTimer
);
967 if (!this._iframe
|| (!this._iframe
.contentWindow
&& !this._iframe
.contentDocument
)) {
968 this._initIframeTimer
= window
.setTimeout("HTMLArea.initIframe(\'" + this._editorNumber
+ "\');", 50);
970 } else if (this._iframe
.contentWindow
&& !HTMLArea
.is_safari
) {
971 if (!this._iframe
.contentWindow
.document
|| !this._iframe
.contentWindow
.document
.documentElement
) {
972 this._initIframeTimer
= window
.setTimeout("HTMLArea.initIframe(\'" + this._editorNumber
+ "\');", 50);
975 } else if (!this._iframe
.contentDocument
.documentElement
|| !this._iframe
.contentDocument
.body
) {
976 this._initIframeTimer
= window
.setTimeout("HTMLArea.initIframe(\'" + this._editorNumber
+ "\');", 50);
979 var doc
= this._iframe
.contentWindow
? this._iframe
.contentWindow
.document
: this._iframe
.contentDocument
;
981 // Set Doc Type in Firefox (doctype is readonly in DOM 2)
982 // After adding doctype in Firefox, baseURL is set to the url of the parent document,
983 // base element is ignored, and all created links, including external, are prepended with incorrect base
984 /*if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
986 this._doc.write(this.config.getDocumentType());
989 if (!this.config
.fullPage
) {
990 var head
= doc
.getElementsByTagName("head")[0];
992 head
= doc
.createElement("head");
993 doc
.documentElement
.appendChild(head
);
995 if (this.config
.baseURL
&& !HTMLArea
.is_opera
) {
996 var base
= doc
.getElementsByTagName("base")[0];
998 base
= doc
.createElement("base");
999 base
.href
= this.config
.baseURL
;
1000 head
.appendChild(base
);
1002 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Iframe baseURL set to: " + this.config
.baseURL
);
1004 var link0
= doc
.getElementsByTagName("link")[0];
1006 link0
= doc
.createElement("link");
1007 link0
.rel
= "stylesheet";
1008 link0
.href
= this.config
.editedContentStyle
;
1009 head
.appendChild(link0
);
1010 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Skin CSS set to: " + this.config
.editedContentStyle
);
1012 if (this.config
.defaultPageStyle
) {
1013 var link
= doc
.getElementsByTagName("link")[1];
1015 link
= doc
.createElement("link");
1016 link
.rel
= "stylesheet";
1017 link
.href
= this.config
.defaultPageStyle
;
1018 head
.appendChild(link
);
1020 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Override CSS set to: " + this.config
.defaultPageStyle
);
1022 if (this.config
.pageStyle
) {
1023 var link
= doc
.getElementsByTagName("link")[2];
1025 link
= doc
.createElement("link");
1026 link
.rel
= "stylesheet";
1027 link
.href
= this.config
.pageStyle
;
1028 head
.appendChild(link
);
1030 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Content CSS set to: " + this.config
.pageStyle
);
1033 var html
= this._textArea
.value
;
1034 this.setFullHTML(html
);
1036 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Editor iframe head successfully initialized.");
1038 this.stylesLoaded();
1042 * Finalize editor Iframe initialization after loading the style sheets
1044 HTMLArea
.stylesLoaded
= function(editorNumber
) {
1045 var editor
= RTEarea
[editorNumber
]["editor"];
1046 editor
.stylesLoaded();
1049 HTMLArea
.prototype.stylesLoaded
= function() {
1050 var doc
= this._doc
;
1052 // check if the stylesheets have been loaded
1053 if (this._stylesLoadedTimer
) window
.clearTimeout(this._stylesLoadedTimer
);
1054 var stylesAreLoaded
= true;
1057 for (var rule
= 0; rule
< doc
.styleSheets
.length
; rule
++) {
1058 if (HTMLArea
.is_gecko
) try { rules
= doc
.styleSheets
[rule
].cssRules
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; }
1059 if (HTMLArea
.is_ie
) try { rules
= doc
.styleSheets
[rule
].rules
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; }
1060 if (HTMLArea
.is_ie
) try { rules
= doc
.styleSheets
[rule
].imports
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; }
1062 if (!stylesAreLoaded
&& !HTMLArea
.is_wamcom
) {
1063 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Failed attempt at loading stylesheets: " + errorText
+ " Retrying...");
1064 this._stylesLoadedTimer
= window
.setTimeout("HTMLArea.stylesLoaded(\'" + this._editorNumber
+ "\');", 100);
1067 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Stylesheets successfully loaded.");
1069 doc
.body
.style
.borderWidth
= "0px";
1070 doc
.body
.className
= "htmlarea-content-body";
1072 // Initialize editor mode
1073 this.getPluginInstance("EditorMode").init();
1075 // set editor number in iframe and document for retrieval in event handlers
1076 doc
._editorNo
= this._editorNumber
;
1077 if (HTMLArea
.is_ie
) doc
.documentElement
._editorNo
= this._editorNumber
;
1079 // intercept events for updating the toolbar & for keyboard handlers
1080 HTMLArea
._addEvents((HTMLArea
.is_ie
? doc
.body
: doc
), ["keydown","keypress","mousedown","mouseup","drag"], HTMLArea
._editorEvent
, true);
1082 // add unload handler
1083 if (!HTMLArea
.hasUnloadHandler
) {
1084 HTMLArea
.hasUnloadHandler
= true;
1085 HTMLArea
._addEvent((this._iframe
.contentWindow
? this._iframe
.contentWindow
: this._iframe
.contentDocument
), "unload", HTMLArea
.removeEditorEvents
);
1088 window
.setTimeout("HTMLArea.generatePlugins(\'" + this._editorNumber
+ "\');", 100);
1091 HTMLArea
.generatePlugins
= function(editorNumber
) {
1092 var editor
= RTEarea
[editorNumber
]["editor"];
1093 // check if any plugins have registered generate handlers
1094 // check also if any plugin has a onKeyPress handler
1095 editor
._hasPluginWithOnKeyPressHandler
= false;
1096 for (var pluginId
in editor
.plugins
) {
1097 if (editor
.plugins
.hasOwnProperty(pluginId
)) {
1098 var pluginInstance
= editor
.plugins
[pluginId
].instance
;
1099 if (typeof(pluginInstance
.onGenerate
) === "function") {
1100 pluginInstance
.onGenerate();
1102 if (typeof(pluginInstance
.onGenerateOnce
) === "function") {
1103 pluginInstance
.onGenerateOnce();
1104 pluginInstance
.onGenerateOnce
= null;
1106 if (typeof(pluginInstance
.onKeyPress
) === "function") {
1107 editor
._hasPluginWithOnKeyPressHandler
= true;
1111 if (typeof(editor
.onGenerate
) === "function") {
1112 editor
.onGenerate();
1113 editor
.onGenerate
= null;
1115 HTMLArea
._appendToLog("[HTMLArea::initIframe]: All plugins successfully generated.");
1117 editor
.sizeIframe(2);
1118 // Focus on the first editor instance
1119 for (var editorId
in RTEarea
) {
1120 if (RTEarea
.hasOwnProperty(editorId
)) {
1121 if (RTEarea
[editorId
].editor
) {
1122 if (editorNumber
== editorId
) {
1123 editor
.focusEditor();
1129 editor
.updateToolbar();
1133 * When we have a form, on reset, re-initialize the HTMLArea content and update the toolbar
1135 HTMLArea
.resetHandler
= function(ev
) {
1136 if(!ev
) var ev
= window
.event
;
1137 var form
= (ev
.target
) ? ev
.target
: ev
.srcElement
;
1138 var editor
= RTEarea
[form
._editorNumber
]["editor"];
1139 editor
.getPluginInstance("EditorMode").setHTML(editor
._textArea
.value
);
1140 editor
.updateToolbar();
1141 var a
= form
.__msh_prevOnReset
;
1142 // call previous reset methods if they were there.
1143 if (typeof(a
) != "undefined") {
1144 for (var i
=a
.length
; --i
>= 0; ) { a
[i
](); }
1149 * Clean up event handlers and object references, undo/redo snapshots, update the textarea for submission
1151 HTMLArea
.removeEditorEvents
= function(ev
) {
1152 if (!ev
) var ev
= window
.event
;
1153 HTMLArea
._stopEvent(ev
);
1154 if (HTMLArea
._eventCache
) {
1155 HTMLArea
._eventCache
.flush();
1157 for (var editorNumber
in RTEarea
) {
1158 if (RTEarea
.hasOwnProperty(editorNumber
)) {
1159 var editor
= RTEarea
[editorNumber
].editor
;
1161 RTEarea
[editorNumber
].editor
= null;
1162 // save the HTML content into the original textarea for submit, back/forward
, etc
.
1163 editor
._textArea
.value
= editor
.getPluginInstance("EditorMode").getHTML();
1165 HTMLArea
.cleanup(editor
);
1172 * Clean up a bunch of references in order to avoid memory leakages mainly in IE, but also in Firefox and Opera
1174 HTMLArea
.cleanup
= function (editor
) {
1175 // nullify envent handlers
1176 for (var handler
in editor
.eventHandlers
) {
1177 if (editor
.eventHandlers
.hasOwnProperty(handler
)) {
1178 editor
.eventHandlers
[handler
] = null;
1181 for (var button
in editor
.btnList
) {
1182 if (editor
.btnList
.hasOwnProperty(button
)) {
1183 editor
.btnList
[button
][3] = null;
1186 for (var dropdown
in editor
.config
.customSelects
) {
1187 if (editor
.config
.customSelects
.hasOwnProperty(dropdown
)) {
1188 editor
.config
.customSelects
[dropdown
].action
= null;
1189 editor
.config
.customSelects
[dropdown
].refresh
= null;
1192 for (var hotKey
in editor
.config
.hotKeyList
) {
1193 if (editor
.config
.customSelects
.hasOwnProperty(hotKey
)) {
1194 editor
.config
.hotKeyList
[hotKey
].action
= null;
1197 editor
.onGenerate
= null;
1198 if(editor
._textArea
.form
) {
1199 editor
._textArea
.form
.__msh_prevOnReset
= null;
1200 editor
._textArea
.form
._editorNumber
= null;
1204 for (var plugin
in editor
.plugins
) {
1205 if (editor
.plugins
.hasOwnProperty(plugin
)) {
1206 var pluginInstance
= editor
.plugins
[plugin
].instance
;
1207 if (typeof(pluginInstance
.onClose
) === "function") {
1208 pluginInstance
.onClose();
1210 pluginInstance
.onClose
= null;
1211 pluginInstance
.onChange
= null;
1212 pluginInstance
.onButtonPress
= null;
1213 pluginInstance
.onGenerate
= null;
1214 pluginInstance
.onGenerateOnce
= null;
1215 pluginInstance
.onMode
= null;
1216 pluginInstance
.onHotKey
= null;
1217 pluginInstance
.onKeyPress
= null;
1218 pluginInstance
.onSelect
= null;
1219 pluginInstance
.onUpdateTolbar
= null;
1222 // cleaning the toolbar elements
1223 for (var txt
in editor
._toolbarObjects
) {
1224 if (editor
._toolbarObjects
.hasOwnProperty(txt
)) {
1225 var obj
= editor
._toolbarObjects
[txt
];
1228 var element
= document
.getElementById(obj
.elementId
);
1230 element
._obj
= null;
1232 editor
._toolbarObjects
[txt
] = null;
1236 editor
._toolbar
= null;
1237 editor
._htmlArea
= null;
1238 editor
._iframe
= null;
1244 HTMLArea
.prototype.getMode
= function() {
1245 return this.getPluginInstance("EditorMode").getEditorMode();
1249 * Initialize iframe content when in full page mode
1251 HTMLArea
.prototype.setFullHTML
= function(html
) {
1252 var save_multiline
= RegExp
.multiline
;
1253 RegExp
.multiline
= true;
1254 if(html
.match(HTMLArea
.RE_doctype
)) {
1255 this.setDoctype(RegExp
.$1);
1256 html
= html
.replace(HTMLArea
.RE_doctype
, "");
1258 RegExp
.multiline
= save_multiline
;
1259 if(!HTMLArea
.is_ie
) {
1260 if(html
.match(HTMLArea
.RE_head
)) this._doc
.getElementsByTagName("head")[0].innerHTML
= RegExp
.$1;
1261 if(html
.match(HTMLArea
.RE_body
)) this._doc
.getElementsByTagName("body")[0].innerHTML
= RegExp
.$1;
1263 var html_re
= /<html>((.|\n)*?)<\/html
>/i
;
1264 html
= html
.replace(html_re
, "$1");
1266 this._doc
.write(html
);
1268 this._doc
.body
.contentEditable
= true;
1273 /***************************************************
1274 * PLUGINS, STYLESHEETS, AND IMAGE AND POPUP URL'S
1275 ***************************************************/
1278 * Instantiate the specified plugin and register it with the editor
1280 * @param string plugin: the name of the plugin
1282 * @return boolean true if the plugin was successfully registered
1284 HTMLArea
.prototype.registerPlugin
= function(plugin
) {
1285 var pluginName
= plugin
;
1286 if (typeof(plugin
) === "string") {
1288 var plugin
= eval(plugin
);
1290 HTMLArea
._appendToLog("ERROR [HTMLArea::registerPlugin]: Cannot register invalid plugin: " + e
);
1294 if (typeof(plugin
) !== "function") {
1295 HTMLArea
._appendToLog("ERROR [HTMLArea::registerPlugin]: Cannot register undefined plugin.");
1298 var pluginInstance
= new plugin(this, pluginName
);
1299 if (pluginInstance
) {
1300 var pluginInformation
= plugin
._pluginInfo
;
1301 if(!pluginInformation
) {
1302 pluginInformation
= pluginInstance
.getPluginInformation();
1304 pluginInformation
.instance
= pluginInstance
;
1305 this.plugins
[pluginName
] = pluginInformation
;
1306 HTMLArea
._appendToLog("[HTMLArea::registerPlugin]: Plugin " + pluginName
+ " was successfully registered.");
1309 HTMLArea
._appendToLog("ERROR [HTMLArea::registerPlugin]: Can't register plugin " + pluginName
+ ".");
1315 * Get the instance of the specified plugin, if it exists
1317 * @param string pluginName: the name of the plugin
1318 * @return object the plugin instance or null
1320 HTMLArea
.prototype.getPluginInstance
= function(pluginName
) {
1321 return (this.plugins
[pluginName
] ? this.plugins
[pluginName
].instance
: null);
1325 * Load the required plugin script
1327 HTMLArea
.loadPlugin
= function (pluginName
, url
, asynchronous
) {
1328 if (typeof(asynchronous
) == "undefined") {
1329 var asynchronous
= true;
1331 HTMLArea
.loadScript(url
, pluginName
, asynchronous
);
1335 * Load a stylesheet file
1337 HTMLArea
.loadStyle
= function(style
, plugin
, url
) {
1338 if (typeof(url
) == "undefined") {
1339 var url
= _editor_url
|| '';
1340 if (typeof(plugin
) != "undefined") { url
+= "plugins/" + plugin + "/"; }
1342 if (/^\//.test(style
)) { url
= style
; }
1344 var head
= document
.getElementsByTagName("head")[0];
1345 var link
= document
.createElement("link");
1346 link
.rel
= "stylesheet";
1348 head
.appendChild(link
);
1352 * Get the url of some image
1354 HTMLArea
.prototype.imgURL
= function(file
, plugin
) {
1355 if (typeof(plugin
) == "undefined") return _editor_skin
+ this.config
.imgURL
+ file
;
1356 else return _editor_skin
+ this.config
.imgURL
+ plugin
+ "/" + file
;
1360 * Get the url of some popup
1362 HTMLArea
.prototype.popupURL
= function(file
) {
1364 if(file
.match(/^plugin:\/\/(.*?)\/(.*)/)) {
1365 var pluginId
= RegExp
.$1;
1366 var popup
= RegExp
.$2;
1367 if(!/\.html$/.test(popup
)) popup
+= ".html";
1368 if (this.config
.pathToPluginDirectory
[pluginId
]) {
1369 url
= this.config
.pathToPluginDirectory
[pluginId
] + "popups/" + popup
;
1371 url
= _typo3_host_url
+ _editor_url
+ "plugins/" + pluginId + "/popups/" + popup
;
1374 url
= _typo3_host_url
+ _editor_url
+ this.config
.popupURL
+ file
;
1379 /***************************************************
1381 ***************************************************/
1382 HTMLArea
.getInnerText
= function(el
) {
1385 for(i
=el
.firstChild
;i
;i
=i
.nextSibling
) {
1386 if(i
.nodeType
== 3) txt
+= i
.data
;
1387 else if(i
.nodeType
== 1) txt
+= HTMLArea
.getInnerText(i
);
1390 if(el
.nodeType
== 3) txt
= el
.data
;
1395 HTMLArea
.prototype.forceRedraw
= function() {
1396 this._doc
.body
.style
.visibility
= "hidden";
1397 this._doc
.body
.style
.visibility
= "visible";
1401 * Focus the editor iframe document or the textarea.
1403 HTMLArea
.prototype.focusEditor
= function() {
1404 switch (this.getMode()) {
1407 if (HTMLArea
.is_safari
) {
1408 this._iframe
.focus();
1410 this._iframe
.contentWindow
.focus();
1415 this._textArea
.focus();
1422 * Update the enabled/disabled/active state of the toolbar elements
1424 HTMLArea
.updateToolbar
= function(editorNumber
) {
1425 var editor
= RTEarea
[editorNumber
]["editor"];
1426 editor
.updateToolbar();
1427 editor
._timerToolbar
= null;
1430 HTMLArea
.prototype.updateToolbar
= function(noStatus
) {
1431 var doc
= this._doc
,
1432 text
= (this.getMode() == "textmode"),
1435 inContext
, match
, matchAny
, k
, j
, n
, commandState
;
1437 selection
= !this._selectionEmpty(this._getSelection());
1438 ancestors
= this.getAllAncestors();
1440 for (var cmd
in this._toolbarObjects
) {
1441 if (this._toolbarObjects
.hasOwnProperty(cmd
)) {
1442 var btn
= this._toolbarObjects
[cmd
];
1443 // Determine if the button should be enabled
1445 if (btn
.context
&& !text
) {
1449 if (/(.*)\[(.*?)\]/.test(btn
.context
)) {
1450 contexts
= RegExp
.$1.split(",");
1451 attrs
= RegExp
.$2.split(",");
1453 contexts
= btn
.context
.split(",");
1455 for (j
= contexts
.length
; --j
>= 0;) contexts
[j
] = contexts
[j
].toLowerCase();
1456 matchAny
= (contexts
[0] == "*");
1457 for (k
= 0; k
< ancestors
.length
; ++k
) {
1458 if (!ancestors
[k
]) continue;
1460 for (j
= contexts
.length
; --j
>= 0;) match
= match
|| (ancestors
[k
].tagName
.toLowerCase() == contexts
[j
]);
1461 if (matchAny
|| match
) {
1463 for (j
= attrs
.length
; --j
>= 0;) {
1464 if (!eval("ancestors[k]." + attrs
[j
])) {
1469 if (inContext
) break;
1474 if (cmd
== "CreateLink") {
1475 btn
.state("enabled", (!text
|| btn
.text
) && (inContext
|| selection
));
1477 btn
.state("enabled", (!text
|| btn
.text
) && inContext
&& (selection
|| !btn
.selection
));
1479 if (typeof(cmd
) == "function") { continue; };
1480 // look-it-up in the custom dropdown boxes
1481 var dropdown
= this.config
.customSelects
[cmd
];
1482 if ((!text
|| btn
.text
) && (typeof(dropdown
) !== "undefined") && (typeof(dropdown
.refresh
) === "function")) {
1483 dropdown
.refresh(this, cmd
);
1487 case "TextIndicator":
1489 try {with (document
.getElementById(btn
.elementId
).style
) {
1490 backgroundColor
= HTMLArea
._makeColor(doc
.queryCommandValue((HTMLArea
.is_ie
|| HTMLArea
.is_safari
) ? "BackColor" : "HiliteColor"));
1492 if(/transparent/i.test(backgroundColor
)) { backgroundColor
= HTMLArea
._makeColor(doc
.queryCommandValue("BackColor")); }
1493 color
= HTMLArea
._makeColor(doc
.queryCommandValue("ForeColor"));
1494 fontFamily
= doc
.queryCommandValue("FontName");
1495 // Check if queryCommandState is available
1496 fontWeight
= "normal";
1497 fontStyle
= "normal";
1498 try { fontWeight
= doc
.queryCommandState("Bold") ? "bold" : "normal"; } catch(ex
) { fontWeight
= "normal"; };
1499 try { fontStyle
= doc
.queryCommandState("Italic") ? "italic" : "normal"; } catch(ex
) { fontStyle
= "normal"; };
1501 // alert(e + "\n\n" + cmd);
1511 for (var pluginId
in this.plugins
) {
1512 if (this.plugins
.hasOwnProperty(pluginId
)) {
1513 var pluginInstance
= this.plugins
[pluginId
].instance
;
1514 if (typeof(pluginInstance
.onUpdateToolbar
) === "function") {
1515 pluginInstance
.onUpdateToolbar();
1521 /***************************************************
1522 * DOM TREE MANIPULATION
1523 ***************************************************/
1526 * Surround the currently selected HTML source code with the given tags.
1527 * Delete the selection, if any.
1529 HTMLArea
.prototype.surroundHTML
= function(startTag
,endTag
) {
1530 this.insertHTML(startTag
+ this.getSelectedHTML().replace(HTMLArea
.Reg_body
, "") + endTag
);
1534 * Change the tag name of a node.
1536 HTMLArea
.prototype.convertNode
= function(el
,newTagName
) {
1537 var newel
= this._doc
.createElement(newTagName
), p
= el
.parentNode
;
1538 while (el
.firstChild
) newel
.appendChild(el
.firstChild
);
1539 p
.insertBefore(newel
, el
);
1545 * Find a parent of an element with a specified tag
1547 HTMLArea
.getElementObject
= function(el
,tagName
) {
1549 while (oEl
!= null && oEl
.nodeName
.toLowerCase() != tagName
) oEl
= oEl
.parentNode
;
1554 * This function removes the given markup element
1556 * @param object element: the inline element to be removed, content being preserved
1560 HTMLArea
.prototype.removeMarkup
= function(element
) {
1561 var bookmark
= this.getBookmark(this._createRange(this._getSelection()));
1562 var parent
= element
.parentNode
;
1563 while (element
.firstChild
) {
1564 parent
.insertBefore(element
.firstChild
, element
);
1566 parent
.removeChild(element
);
1567 this.selectRange(this.moveToBookmark(bookmark
));
1571 * This function verifies if the element has any allowed attributes
1573 * @param object element: the DOM element
1574 * @param array allowedAttributes: array of allowed attribute names
1576 * @return boolean true if the element has one of the allowed attributes
1578 HTMLArea
.hasAllowedAttributes
= function(element
,allowedAttributes
) {
1580 for (var i
= allowedAttributes
.length
; --i
>= 0;) {
1581 value
= element
.getAttribute(allowedAttributes
[i
]);
1583 if (allowedAttributes
[i
] == "style" && element
.style
.cssText
) {
1593 /***************************************************
1594 * SELECTIONS AND RANGES
1595 ***************************************************/
1598 * Return true if we have some selected content
1600 HTMLArea
.prototype.hasSelectedText
= function() {
1601 return this.getSelectedHTML() != "";
1605 * Get an array with all the ancestor nodes of the selection.
1607 HTMLArea
.prototype.getAllAncestors
= function() {
1608 var p
= this.getParentElement();
1610 while (p
&& (p
.nodeType
=== 1) && (p
.nodeName
.toLowerCase() !== "body")) {
1614 a
.push(this._doc
.body
);
1619 * Get the block ancestors of an element within a given block
1621 HTMLArea
.prototype.getBlockAncestors
= function(element
, withinBlock
) {
1622 var ancestors
= new Array();
1623 var ancestor
= element
;
1624 while (ancestor
&& (ancestor
.nodeType
=== 1) && !/^(body)$/i.test(ancestor
.nodeName
) && ancestor
!= withinBlock
) {
1625 if (HTMLArea
.isBlockElement(ancestor
)) {
1626 ancestors
.unshift(ancestor
);
1628 ancestor
= ancestor
.parentNode
;
1630 ancestors
.unshift(ancestor
);
1635 * Get the block elements containing the start and the end points of the selection
1637 HTMLArea
.prototype.getEndBlocks
= function(selection
) {
1638 var range
= this._createRange(selection
);
1639 if (HTMLArea
.is_gecko
) {
1640 var parentStart
= range
.startContainer
;
1641 var parentEnd
= range
.endContainer
;
1643 if (selection
.type
!== "Control" ) {
1644 var rangeEnd
= range
.duplicate();
1645 range
.collapse(true);
1646 var parentStart
= range
.parentElement();
1647 rangeEnd
.collapse(false);
1648 var parentEnd
= rangeEnd
.parentElement();
1650 var parentStart
= range
.item(0);
1651 var parentEnd
= parentStart
;
1654 while (parentStart
&& !HTMLArea
.isBlockElement(parentStart
)) {
1655 parentStart
= parentStart
.parentNode
;
1657 while (parentEnd
&& !HTMLArea
.isBlockElement(parentEnd
)) {
1658 parentEnd
= parentEnd
.parentNode
;
1660 return { start
: parentStart
,
1666 * This function determines if the end poins of the current selection are within the same block
1668 * @return boolean true if the end points of the current selection are inside the same block element
1670 HTMLArea
.prototype.endPointsInSameBlock
= function() {
1671 var selection
= this._getSelection();
1672 if (this._selectionEmpty(selection
)) {
1675 var parent
= this.getParentElement(selection
);
1676 var endBlocks
= this.getEndBlocks(selection
);
1677 return (endBlocks
.start
=== endBlocks
.end
&& !/^(table|thead|tbody|tfoot|tr)$/i.test(parent
.nodeName
));
1682 * Get the deepest ancestor of the selection that is of the specified type
1683 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
1685 HTMLArea
.prototype._getFirstAncestor
= function(sel
,types
) {
1686 var prnt
= this._activeElement(sel
);
1689 prnt
= (HTMLArea
.is_ie
? this._createRange(sel
).parentElement() : this._createRange(sel
).commonAncestorContainer
);
1694 if (typeof(types
) == 'string') types
= [types
];
1697 if (prnt
.nodeType
== 1) {
1698 if (types
== null) return prnt
;
1699 for (var i
= 0; i
< types
.length
; i
++) {
1700 if(prnt
.tagName
.toLowerCase() == types
[i
]) return prnt
;
1702 if(prnt
.tagName
.toLowerCase() == 'body') break;
1703 if(prnt
.tagName
.toLowerCase() == 'table') break;
1705 prnt
= prnt
.parentNode
;
1710 /***************************************************
1711 * Category: EVENT HANDLERS
1712 ***************************************************/
1715 * Intercept some commands and replace them with our own implementation
1717 HTMLArea
.prototype.execCommand
= function(cmdID
, UI
, param
) {
1722 this._doc
.execCommand(cmdID
, UI
, param
);
1724 if (this.config
.debug
) alert(e
+ "\n\nby execCommand(" + cmdID
+ ");");
1727 this.updateToolbar();
1732 * A generic event handler for things that happen in the IFRAME's document.
1734 HTMLArea
._editorEvent
= function(ev
) {
1735 if(!ev
) var ev
= window
.event
;
1736 var target
= (ev
.target
) ? ev
.target
: ev
.srcElement
;
1737 var owner
= (target
.ownerDocument
) ? target
.ownerDocument
: target
;
1738 if(HTMLArea
.is_ie
) { // IE5.5 does not report any ownerDocument
1739 while (owner
.parentElement
) { owner
= owner
.parentElement
; }
1741 var editor
= RTEarea
[owner
._editorNo
]["editor"];
1742 var keyEvent
= ((HTMLArea
.is_ie
|| HTMLArea
.is_safari
) && ev
.type
== "keydown") || (HTMLArea
.is_gecko
&& ev
.type
== "keypress");
1743 editor
.focusEditor();
1746 if(editor
._hasPluginWithOnKeyPressHandler
) {
1747 for (var pluginId
in editor
.plugins
) {
1748 if (editor
.plugins
.hasOwnProperty(pluginId
)) {
1749 var pluginInstance
= editor
.plugins
[pluginId
].instance
;
1750 if (typeof(pluginInstance
.onKeyPress
) === "function") {
1751 if (!pluginInstance
.onKeyPress(ev
)) {
1752 HTMLArea
._stopEvent(ev
);
1759 if(ev
.ctrlKey
&& !ev
.shiftKey
) {
1761 // execute hotkey command
1762 var key
= String
.fromCharCode((HTMLArea
.is_ie
|| HTMLArea
.is_safari
|| HTMLArea
.is_opera
) ? ev
.keyCode
: ev
.charCode
).toLowerCase();
1763 if (HTMLArea
.is_gecko
&& ev
.keyCode
== 32) key
= String
.fromCharCode(ev
.keyCode
).toLowerCase();
1765 editor
.insertHTML(" ");
1766 editor
.updateToolbar();
1767 HTMLArea
._stopEvent(ev
);
1770 if (!editor
.config
.hotKeyList
[key
]) return true;
1771 var cmd
= editor
.config
.hotKeyList
[key
].cmd
;
1772 if (!cmd
) return true;
1775 cmd
= editor
.config
.hotKeyList
[key
].cmd
;
1776 editor
.execCommand(cmd
, false, null);
1777 HTMLArea
._stopEvent(ev
);
1781 if (editor
.config
.hotKeyList
[key
] && editor
.config
.hotKeyList
[key
].action
) {
1782 if (!editor
.config
.hotKeyList
[key
].action(editor
, key
)) {
1783 HTMLArea
._stopEvent(ev
);
1790 } else if (ev
.altKey
) {
1791 // check if context menu is already handling this event
1792 if(editor
.plugins
["ContextMenu"] && editor
.plugins
["ContextMenu"].instance
) {
1793 var keys
= editor
.plugins
["ContextMenu"].instance
.keys
;
1794 if (keys
.length
> 0) {
1796 for (var i
= keys
.length
; --i
>= 0;) {
1798 if (k
[0].toLowerCase() == key
) {
1799 HTMLArea
._stopEvent(ev
);
1806 } else if (keyEvent
) {
1807 if (HTMLArea
.is_gecko
) editor
._detectURL(ev
);
1808 switch (ev
.keyCode
) {
1809 case 13 : // KEY enter
1810 if (HTMLArea
.is_gecko
) {
1811 if (!ev
.shiftKey
&& !editor
.config
.disableEnterParagraphs
) {
1812 if (editor
._checkInsertP()) {
1813 HTMLArea
._stopEvent(ev
);
1815 } else if (HTMLArea
.is_safari
) {
1816 var brNode
= editor
._doc
.createElement("br");
1817 editor
.insertNodeAtSelection(brNode
);
1818 if (!brNode
.nextSibling
|| !HTMLArea
.getInnerText(brNode
.nextSibling
)) {
1819 var secondBrNode
= editor
._doc
.createElement("br");
1820 secondBrNode
= brNode
.parentNode
.appendChild(secondBrNode
);
1821 editor
.selectNode(secondBrNode
, false);
1823 HTMLArea
._stopEvent(ev
);
1825 // update the toolbar state after some time
1826 if (editor
._timerToolbar
) window
.clearTimeout(editor
._timerToolbar
);
1827 editor
._timerToolbar
= window
.setTimeout("HTMLArea.updateToolbar(\'" + editor
._editorNumber
+ "\');", 100);
1831 case 8 : // KEY backspace
1832 case 46 : // KEY delete
1833 if ((HTMLArea
.is_gecko
&& !ev
.shiftKey
) || HTMLArea
.is_ie
) {
1834 if (editor
._checkBackspace()) HTMLArea
._stopEvent(ev
);
1836 // update the toolbar state after some time
1837 if (editor
._timerToolbar
) window
.clearTimeout(editor
._timerToolbar
);
1838 editor
._timerToolbar
= window
.setTimeout("HTMLArea.updateToolbar(\'" + editor
._editorNumber
+ "\');", 50);
1840 case 9: // KEY horizontal tab
1841 var newkey
= (ev
.shiftKey
? "SHIFT-" : "") + "TAB";
1842 if (editor
.config
.hotKeyList
[newkey
] && editor
.config
.hotKeyList
[newkey
].action
) {
1843 if (!editor
.config
.hotKeyList
[newkey
].action(editor
, newkey
)) {
1844 HTMLArea
._stopEvent(ev
);
1849 case 37: // LEFT arrow key
1850 case 38: // UP arrow key
1851 case 39: // RIGHT arrow key
1852 case 40: // DOWN arrow key
1853 if (HTMLArea
.is_ie
) {
1854 if (editor
._timerToolbar
) window
.clearTimeout(editor
._timerToolbar
);
1855 editor
._timerToolbar
= window
.setTimeout("HTMLArea.updateToolbar(\'" + editor
._editorNumber
+ "\');", 10);
1862 if (editor
._timerToolbar
) window
.clearTimeout(editor
._timerToolbar
);
1863 if (ev
.type
== "mouseup") editor
.updateToolbar();
1864 else editor
._timerToolbar
= window
.setTimeout("HTMLArea.updateToolbar(\'" + editor
._editorNumber
+ "\');", 50);
1868 HTMLArea
.prototype.scrollToCaret
= function() {
1869 if (HTMLArea
.is_gecko
) {
1870 var e
= this.getParentElement(),
1871 w
= this._iframe
.contentWindow
? this._iframe
.contentWindow
: window
,
1872 h
= w
.innerHeight
|| w
.height
,
1874 t
= d
.documentElement
.scrollTop
|| d
.body
.scrollTop
;
1875 if (e
.offsetTop
> h
+t
|| e
.offsetTop
< t
) {
1876 this.getParentElement().scrollIntoView();
1882 * Get the html content of the current editing mode
1884 HTMLArea
.prototype.getHTML
= function() {
1885 return this.getPluginInstance("EditorMode").getHTML();
1889 * Set the given doctype when config.fullPage is true
1891 HTMLArea
.prototype.setDoctype
= function(doctype
) {
1892 this.doctype
= doctype
;
1895 /***************************************************
1897 ***************************************************/
1899 // variable used to pass the object to the popup editor window.
1900 HTMLArea
._object
= null;
1903 * Check if the client agent is supported
1905 HTMLArea
.checkSupportedBrowser
= function() {
1906 if(HTMLArea
.is_gecko
&& !HTMLArea
.is_safari
&& !HTMLArea
.is_opera
) {
1907 if(navigator
.productSub
< 20030210) return false;
1909 return HTMLArea
.is_gecko
|| HTMLArea
.is_ie
;
1912 /* EventCache Version 1.0
1913 * Copyright 2005 Mark Wubben
1914 * Adaptation by Stanislas Rolland
1915 * Provides a way for automatically removing events from nodes and thus preventing memory leakage.
1916 * See <http://novemberborn.net/javascript/event-cache> for more information.
1917 * This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>
1918 * Event Cache uses an anonymous function to create a hidden scope chain. This is to prevent scoping issues.
1920 HTMLArea
._eventCacheConstructor
= function() {
1921 var listEvents
= [];
1924 listEvents
: listEvents
,
1926 add
: function(node
, sEventName
, fHandler
) {
1927 listEvents
.push(arguments
);
1930 flush
: function() {
1932 for (var i
= listEvents
.length
; --i
>= 0;) {
1933 item
= listEvents
[i
];
1935 HTMLArea
._removeEvent(item
[0], item
[1], item
[2]);
1936 item
[0][item
[1]] = null;
1941 listEvents
.length
= 0;
1949 HTMLArea
._addEvent
= function(el
,evname
,func
,useCapture
) {
1950 if (typeof(useCapture
) == "undefined") {
1951 var useCapture
= false;
1953 if (HTMLArea
.is_gecko
) {
1954 el
.addEventListener(evname
, func
, !HTMLArea
.is_opera
|| useCapture
);
1956 el
.attachEvent("on" + evname
, func
);
1958 HTMLArea
._eventCache
.add(el
, evname
, func
);
1962 * Register a list of events
1964 HTMLArea
._addEvents
= function(el
,evs
,func
,useCapture
) {
1965 if (typeof(useCapture
) == "undefined") {
1966 var useCapture
= false;
1968 for (var i
= evs
.length
; --i
>= 0;) {
1969 HTMLArea
._addEvent(el
,evs
[i
], func
, useCapture
);
1974 * Remove an event listener
1976 HTMLArea
._removeEvent
= function(el
,evname
,func
) {
1977 if (HTMLArea
.is_gecko
) {
1978 // Avoid Safari crash when removing events on some orphan documents
1979 if (!HTMLArea
.is_safari
|| HTMLArea
.is_chrome
) {
1981 el
.removeEventListener(evname
, func
, true);
1982 el
.removeEventListener(evname
, func
, false);
1984 } else if (el
.nodeType
!= 9 || el
.defaultView
) {
1986 el
.removeEventListener(evname
, func
, true);
1987 el
.removeEventListener(evname
, func
, false);
1992 el
.detachEvent("on" + evname
, func
);
1998 * Remove a list of events
2000 HTMLArea
._removeEvents
= function(el
,evs
,func
) {
2001 for (var i
= evs
.length
; --i
>= 0;) { HTMLArea
._removeEvent(el
, evs
[i
], func
); }
2005 * Stop event propagation
2007 HTMLArea
._stopEvent
= function(ev
) {
2008 if(HTMLArea
.is_gecko
) {
2009 ev
.stopPropagation();
2010 ev
.preventDefault();
2012 ev
.cancelBubble
= true;
2013 ev
.returnValue
= false;
2018 * Remove a class name from the class attribute of an element
2020 * @param object el: the element
2021 * @param string className: the class name to remove
2022 * @param boolean substring: if true, remove the first class name starting with the given string
2025 HTMLArea
._removeClass
= function(el
, className
, substring
) {
2026 if (!el
|| !el
.className
) return;
2027 var classes
= el
.className
.trim().split(" ");
2028 var newClasses
= new Array();
2029 for (var i
= classes
.length
; --i
>= 0;) {
2031 if (classes
[i
] != className
) {
2032 newClasses
[newClasses
.length
] = classes
[i
];
2034 } else if (classes
[i
].indexOf(className
) != 0) {
2035 newClasses
[newClasses
.length
] = classes
[i
];
2038 if (newClasses
.length
== 0) {
2039 if (!HTMLArea
.is_opera
) el
.removeAttribute(HTMLArea
.is_gecko
? "class" : "className");
2040 else el
.className
= '';
2042 el
.className
= newClasses
.join(" ");
2047 * Add a class name to the class attribute
2049 HTMLArea
._addClass
= function(el
, addClassName
) {
2050 HTMLArea
._removeClass(el
, addClassName
);
2051 if (el
.className
&& HTMLArea
.classesXOR
) {
2052 var classNames
= el
.className
.trim().split(" ");
2053 for (var i
= classNames
.length
; --i
>= 0;) {
2054 if (HTMLArea
.classesXOR
[addClassName
] && HTMLArea
.classesXOR
[addClassName
].test(classNames
[i
])) {
2055 HTMLArea
._removeClass(el
, classNames
[i
]);
2059 if (el
.className
) el
.className
+= " " + addClassName
;
2060 else el
.className
= addClassName
;
2064 * Check if a class name is in the class attribute of an element
2066 * @param object el: the element
2067 * @param string className: the class name to look for
2068 * @param boolean substring: if true, look for a class name starting with the given string
2069 * @return boolean true if the class name was found
2071 HTMLArea
._hasClass
= function(el
, className
, substring
) {
2072 if (!el
|| !el
.className
) return false;
2073 var classes
= el
.className
.trim().split(" ");
2074 for (var i
= classes
.length
; --i
>= 0;) {
2075 if (classes
[i
] == className
|| (substring
&& classes
[i
].indexOf(className
) == 0)) return true;
2081 * Select a value in a select element
2083 * @param object select: the select object
2084 * @param string value: the value
2087 HTMLArea
.selectValue
= function(select
, value
) {
2088 var options
= select
.getElementsByTagName("option");
2089 for (var i
= options
.length
; --i
>= 0;) {
2090 var option
= options
[i
];
2091 option
.selected
= (option
.value
== value
);
2092 select
.selectedIndex
= i
;
2096 HTMLArea
.RE_blockTags
= /^(body|p|h1|h2|h3|h4|h5|h6|ul|ol|pre|dl|dt|dd|div|noscript|blockquote|form|hr|table|caption|fieldset|address|td|tr|th|li|tbody|thead|tfoot|iframe)$/;
2097 HTMLArea
.isBlockElement
= function(el
) { return el
&& el
.nodeType
== 1 && HTMLArea
.RE_blockTags
.test(el
.nodeName
.toLowerCase()); };
2098 HTMLArea
.RE_closingTags
= /^(p|blockquote|a|li|ol|ul|dl|dt|td|th|tr|tbody|thead|tfoot|caption|colgroup|table|div|b|bdo|big|cite|code|del|dfn|em|i|ins|kbd|label|q|samp|small|span|strike|strong|sub|sup|tt|u|var|abbr|acronym|font|center|object|embed|style|script|title|head)$/;
2099 HTMLArea
.RE_noClosingTag
= /^(img|br|hr|col|input|area|base|link|meta|param)$/;
2100 HTMLArea
.needsClosingTag
= function(el
) { return el
&& el
.nodeType
== 1 && !HTMLArea
.RE_noClosingTag
.test(el
.tagName
.toLowerCase()); };
2103 * Perform HTML encoding of some given string
2104 * Borrowed in part from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
2106 HTMLArea
.htmlDecode
= function(str
) {
2107 str
= str
.replace(/</g, "<").replace(/>/g, ">");
2108 str
= str
.replace(/ /g, "\xA0"); // Decimal 160, non-breaking-space
2109 str
= str
.replace(/"/g, "\x22");
2110 str
= str
.replace(/'/g, "'") ;
2111 str
= str
.replace(/&/g, "&");
2114 HTMLArea
.htmlEncode
= function(str
) {
2115 if (typeof(str
) != 'string') str
= str
.toString(); // we don't need regexp for that, but.. so be it for now.
2116 str
= str
.replace(/&/g, "&");
2117 str
= str
.replace(/</g, "<").replace(/>/g, ">");
2118 str
= str
.replace(/\xA0/g, " "); // Decimal 160, non-breaking-space
2119 str
= str
.replace(/\x22/g, """); // \x22 means '"'
2124 * Retrieve the HTML code from the given node.
2125 * This is a replacement for getting innerHTML, using standard DOM calls.
2126 * Wrapper catches a Mozilla-Exception with non well-formed html source code.
2128 HTMLArea
.getHTML
= function(root
, outputRoot
, editor
){
2130 return HTMLArea
.getHTMLWrapper(root
,outputRoot
,editor
);
2132 HTMLArea
._appendToLog("The HTML document is not well-formed.");
2133 if(!HTMLArea
._debugMode
) alert(HTMLArea
.I18N
.msg
["HTML-document-not-well-formed"]);
2134 else return HTMLArea
.getHTMLWrapper(root
,outputRoot
,editor
);
2135 return editor
._doc
.body
.innerHTML
;
2139 HTMLArea
.getHTMLWrapper
= function(root
, outputRoot
, editor
) {
2141 if(!root
) return html
;
2142 switch (root
.nodeType
) {
2143 case 1: // ELEMENT_NODE
2144 case 11: // DOCUMENT_FRAGMENT_NODE
2145 case 9: // DOCUMENT_NODE
2146 var closed
, i
, config
= editor
.config
;
2147 var root_tag
= (root
.nodeType
== 1) ? root
.tagName
.toLowerCase() : '';
2148 if (root_tag
== "br" && config
.removeTrailingBR
&& !root
.nextSibling
&& HTMLArea
.isBlockElement(root
.parentNode
) && (!root
.previousSibling
|| root
.previousSibling
.nodeName
.toLowerCase() != "br")) {
2149 if (!root
.previousSibling
&& root
.parentNode
&& root
.parentNode
.nodeName
.toLowerCase() == "p" && root
.parentNode
.className
) html
+= " ";
2152 if (config
.htmlRemoveTagsAndContents
&& config
.htmlRemoveTagsAndContents
.test(root_tag
)) break;
2153 var custom_tag
= (config
.customTags
&& config
.customTags
.test(root_tag
));
2154 if (outputRoot
) outputRoot
= !(config
.htmlRemoveTags
&& config
.htmlRemoveTags
.test(root_tag
));
2155 if ((HTMLArea
.is_ie
|| HTMLArea
.is_safari
) && root_tag
== "head") {
2156 if(outputRoot
) html
+= "<head>";
2157 var save_multiline
= RegExp
.multiline
;
2158 RegExp
.multiline
= true;
2159 var txt
= root
.innerHTML
.replace(HTMLArea
.RE_tagName
, function(str
, p1
, p2
) {
2160 return p1
+ p2
.toLowerCase();
2162 RegExp
.multiline
= save_multiline
;
2164 if(outputRoot
) html
+= "</head>";
2166 } else if (outputRoot
) {
2167 if (HTMLArea
.is_gecko
&& root
.hasAttribute('_moz_editor_bogus_node')) break;
2168 closed
= (!(root
.hasChildNodes() || HTMLArea
.needsClosingTag(root
) || custom_tag
));
2169 html
= "<" + root_tag
;
2170 var a
, name
, value
, attrs
= root
.attributes
;
2171 var n
= attrs
.length
;
2172 for (i
= attrs
.length
; --i
>= 0 ;) {
2174 name
= a
.nodeName
.toLowerCase();
2175 if ((!a
.specified
&& name
!= 'value') || /_moz|contenteditable|_msh/.test(name
)) continue;
2176 if (!HTMLArea
.is_ie
|| name
!= "style") {
2177 // IE5.5 reports wrong values. For this reason we extract the values directly from the root node.
2178 // Using Gecko the values of href and src are converted to absolute links unless we get them using nodeValue()
2179 if (typeof(root
[a
.nodeName
]) != "undefined" && name
!= "href" && name
!= "src" && name
!= "style" && !/^on/.test(name
)) {
2180 value
= root
[a
.nodeName
];
2182 value
= a
.nodeValue
;
2183 if (HTMLArea
.is_ie
&& (name
== "href" || name
== "src") && editor
.plugins
.link
&& editor
.plugins
.link
.instance
&& editor
.plugins
.link
.instance
.stripBaseURL
) {
2184 value
= editor
.plugins
.link
.instance
.stripBaseURL(value
);
2187 } else { // IE fails to put style in attributes list.
2188 value
= root
.style
.cssText
;
2190 // Mozilla reports some special values; we don't need them.
2191 if(/(_moz|^$)/.test(value
)) continue;
2192 // Strip value="0" reported by IE on all li tags
2193 if(HTMLArea
.is_ie
&& root_tag
== "li" && name
== "value" && a
.nodeValue
== 0) continue;
2194 html
+= " " + name
+ '="' + HTMLArea
.htmlEncode(value
) + '"';
2196 if (html
!= "") html
+= closed
? " />" : ">";
2198 for (i
= root
.firstChild
; i
; i
= i
.nextSibling
) {
2199 if (/^li$/i.test(i
.tagName
) && !/^[ou]l$/i.test(root
.tagName
)) html
+= "<ul>" + HTMLArea
.getHTMLWrapper(i
, true, editor
) + "</ul>";
2200 else html
+= HTMLArea
.getHTMLWrapper(i
, true, editor
);
2202 if (outputRoot
&& !closed
) html
+= "</" + root_tag
+ ">";
2204 case 3: // TEXT_NODE
2205 html
= /^(script|style)$/i.test(root
.parentNode
.tagName
) ? root
.data
: HTMLArea
.htmlEncode(root
.data
);
2207 case 8: // COMMENT_NODE
2208 if (!editor
.config
.htmlRemoveComments
) html
= "<!--" + root
.data
+ "-->";
2210 case 4: // Node.CDATA_SECTION_NODE
2211 // Mozilla seems to convert CDATA into a comment when going into wysiwyg mode, don't know about IE
2212 html
+= '<![CDATA[' + root
.data
+ ']]>';
2214 case 5: // Node.ENTITY_REFERENCE_NODE
2215 html
+= '&' + root
.nodeValue
+ ';';
2217 case 7: // Node.PROCESSING_INSTRUCTION_NODE
2218 // PI's don't seem to survive going into the wysiwyg mode, (at least in moz) so this is purely academic
2219 html
+= '<?' + root
.target
+ ' ' + root
.data
+ ' ?>';
2227 HTMLArea
.getPrevNode
= function(node
) {
2228 if(!node
) return null;
2229 if(node
.previousSibling
) return node
.previousSibling
;
2230 if(node
.parentNode
) return node
.parentNode
;
2234 HTMLArea
.getNextNode
= function(node
) {
2235 if(!node
) return null;
2236 if(node
.nextSibling
) return node
.nextSibling
;
2237 if(node
.parentNode
) return node
.parentNode
;
2241 HTMLArea
.removeFromParent
= function(el
) {
2242 if(!el
.parentNode
) return;
2243 var pN
= el
.parentNode
;
2248 String
.prototype.trim
= function() {
2249 return this.replace(/^\s+/, '').replace(/\s+$/, '');
2252 // creates a rgb-style color from a number
2253 HTMLArea
._makeColor
= function(v
) {
2254 if (typeof(v
) != "number") {
2255 // already in rgb (hopefully); IE doesn't get here.
2258 // IE sends number; convert to rgb.
2260 var g
= (v
>> 8) & 0xFF;
2261 var b
= (v
>> 16) & 0xFF;
2262 return "rgb(" + r
+ "," + g
+ "," + b
+ ")";
2265 // returns hexadecimal color representation from a number or a rgb-style color.
2266 HTMLArea
._colorToRgb
= function(v
) {
2270 // returns the hex representation of one byte (2 digits)
2272 return (d
< 16) ? ("0" + d
.toString(16)) : d
.toString(16);
2275 if (typeof(v
) == "number") {
2276 // we're talking to IE here
2278 var g
= (v
>> 8) & 0xFF;
2279 var b
= (v
>> 16) & 0xFF;
2280 return "#" + hex(r
) + hex(g
) + hex(b
);
2283 if (v
.substr(0, 3) == "rgb") {
2284 // in rgb(...) form -- Mozilla
2285 var re
= /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2287 var r
= parseInt(RegExp
.$1);
2288 var g
= parseInt(RegExp
.$2);
2289 var b
= parseInt(RegExp
.$3);
2290 return "#" + hex(r
) + hex(g
) + hex(b
);
2292 // doesn't match RE?! maybe uses percentages or float numbers
2293 // -- FIXME: not yet implemented.
2297 if (v
.substr(0, 1) == "#") {
2298 // already hex rgb (hopefully :D )
2302 // if everything else fails ;)
2306 /** Use XML HTTPRequest to post some data back to the server and do something
2307 * with the response (asyncronously!), this is used by such things as the spellchecker update personal dict function
2309 HTMLArea
._postback
= function(url
, data
, handler
, addParams
, charset
) {
2310 if (typeof(charset
) == "undefined") var charset
= "utf-8";
2312 if (window
.XMLHttpRequest
) req
= new XMLHttpRequest();
2313 else if (window
.ActiveXObject
) {
2314 var success
= false;
2315 for (var k
= 0; k
< HTMLArea
.MSXML_XMLHTTP_PROGIDS
.length
&& !success
; k
++) {
2317 req
= new ActiveXObject(HTMLArea
.MSXML_XMLHTTP_PROGIDS
[k
]);
2325 for (var i
in data
) {
2326 content
+= (content
.length
? '&' : '') + i
+ '=' + encodeURIComponent(data
[i
]);
2328 content
+= (content
.length
? '&' : '') + 'charset=' + charset
;
2329 if (typeof(addParams
) != "undefined") content
+= addParams
;
2330 if (url
.substring(0,1) == '/') {
2331 var postUrl
= _typo3_host_url
+ url
;
2333 var postUrl
= _typo3_host_url
+ _editor_url
+ url
;
2336 function callBack() {
2337 if(req
.readyState
== 4) {
2338 if (req
.status
== 200) {
2339 if (typeof(handler
) == 'function') handler(req
.responseText
, req
);
2340 HTMLArea
._appendToLog("[HTMLArea::_postback]: Server response: " + req
.responseText
);
2342 HTMLArea
._appendToLog("ERROR [HTMLArea::_postback]: Unable to post " + postUrl
+ " . Server reported " + req
.statusText
);
2346 req
.onreadystatechange
= callBack
;
2347 function sendRequest() {
2348 HTMLArea
._appendToLog("[HTMLArea::_postback]: Request: " + content
);
2352 req
.open('POST', postUrl
, true);
2353 req
.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
2354 window
.setTimeout(sendRequest
, 500);
2359 * Internet Explorer returns an item having the _name_ equal to the given id, even if it's not having any id.
2360 * This way it can return a different form field even if it's not a textarea. This works around the problem by
2361 * specifically looking to search only elements having a certain tag name.
2363 HTMLArea
.getElementById
= function(tag
, id
) {
2364 var el
, i
, objs
= document
.getElementsByTagName(tag
);
2365 for (i
= objs
.length
; --i
>= 0 && (el
= objs
[i
]);) {
2366 if (el
.id
== id
) return el
;
2371 /***************************************************
2372 * TYPO3-SPECIFIC FUNCTIONS
2373 ***************************************************/
2375 * Set the size of textarea with the RTE. It's called, if we are in fullscreen-mode.
2377 var setRTEsizeByJS
= function(divId
, height
, width
) {
2378 if (HTMLArea
.is_gecko
) height
= height
- 25;
2379 else height
= height
- 60;
2380 if (height
> 0) document
.getElementById(divId
).style
.height
= height
+ "px";
2381 if (HTMLArea
.is_gecko
) width
= "99%";
2383 document
.getElementById(divId
).style
.width
= width
;
2387 * Extending the TYPO3 Lorem Ipsum extension
2389 var lorem_ipsum
= function(element
,text
) {
2390 if (element
.tagName
.toLowerCase() == "textarea" && element
.id
&& element
.id
.substr(0,7) == "RTEarea") {
2391 var editor
= RTEarea
[element
.id
.substr(7, element
.id
.length
)]["editor"];
2392 editor
.insertHTML(text
);
2393 editor
.updateToolbar();
2398 * Initialize the editor, configure the toolbar, setup the plugins, etc.
2400 HTMLArea
.initTimer
= new Object();
2402 HTMLArea
.onGenerateHandler
= function(editorNumber
) {
2403 return (function() {
2404 document
.getElementById('pleasewait' + editorNumber
).style
.display
= 'none';
2405 document
.getElementById('editorWrap' + editorNumber
).style
.visibility
= 'visible';
2406 editorNumber
= null;
2410 HTMLArea
.initEditor
= function(editorNumber
) {
2411 if (document
.getElementById('pleasewait' + editorNumber
)) {
2412 if(HTMLArea
.checkSupportedBrowser()) {
2413 document
.getElementById('pleasewait' + editorNumber
).style
.display
= 'block';
2414 document
.getElementById('editorWrap' + editorNumber
).style
.visibility
= 'hidden';
2415 if(HTMLArea
.initTimer
[editorNumber
]) window
.clearTimeout(HTMLArea
.initTimer
[editorNumber
]);
2416 if(!HTMLArea
.is_loaded
) {
2417 HTMLArea
.initTimer
[editorNumber
] = window
.setTimeout("HTMLArea.initEditor(\'" + editorNumber
+ "\');", 150);
2419 var RTE
= RTEarea
[editorNumber
];
2420 HTMLArea
._appendToLog("[HTMLArea::initEditor]: Initializing editor with editor Id: " + editorNumber
+ ".");
2422 // Get the configuration properties
2423 var config
= new HTMLArea
.Config();
2424 for (var property
in RTE
) {
2425 if (RTE
.hasOwnProperty(property
)) {
2426 config
[property
] = RTE
[property
] ? RTE
[property
] : false;
2429 // Create an editor for the textarea
2430 var editor
= new HTMLArea(RTE
.id
, config
);
2431 RTE
.editor
= editor
;
2433 // Save the editornumber in the object
2434 editor
._typo3EditerNumber
= editorNumber
;
2435 editor
._editorNumber
= editorNumber
;
2437 // Override these settings if they were ever modified
2438 editor
.config
.width
= "auto";
2439 editor
.config
.height
= "auto";
2440 editor
.config
.sizeIncludesToolbar
= true;
2441 editor
.config
.fullPage
= false;
2443 // All nested tabs and inline levels in the sorting order they were applied
2445 editor
.nested
.all
= RTEarea
[editorNumber
].tceformsNested
;
2446 editor
.nested
.sorted
= HTMLArea
.simplifyNested(editor
.nested
.all
);
2448 // Register the plugins included in the configuration
2449 for (var plugin
in editor
.config
.plugin
) {
2450 if (editor
.config
.plugin
.hasOwnProperty(plugin
) && editor
.config
.plugin
[plugin
]) {
2451 editor
.registerPlugin(plugin
);
2455 editor
.onGenerate
= HTMLArea
.onGenerateHandler(editorNumber
);
2461 document
.getElementById('pleasewait' + editorNumber
).style
.display
= 'none';
2462 document
.getElementById('editorWrap' + editorNumber
).style
.visibility
= 'visible';
2467 HTMLArea
.allElementsAreDisplayed
= function(elements
) {
2468 for (var i
=0, length
=elements
.length
; i
< length
; i
++) {
2469 if (document
.getElementById(elements
[i
]).style
.display
== "none") {
2477 * Base, version 1.0.2
2478 * Copyright 2006, Dean Edwards
2479 * License: http://creativecommons.org/licenses/LGPL/2.1/
2482 HTMLArea
.Base
= function() {
2483 if (arguments
.length
) {
2484 if (this == window
) { // cast an object to this class
2485 HTMLArea
.Base
.prototype.extend
.call(arguments
[0], arguments
.callee
.prototype);
2487 this.extend(arguments
[0]);
2492 HTMLArea
.Base
.version
= "1.0.2";
2494 HTMLArea
.Base
.prototype = {
2495 extend
: function(source
, value
) {
2496 var extend
= HTMLArea
.Base
.prototype.extend
;
2497 if (arguments
.length
== 2) {
2498 var ancestor
= this[source
];
2500 if ((ancestor
instanceof Function
) && (value
instanceof Function
) &&
2501 ancestor
.valueOf() != value
.valueOf() && /\bbase\b/.test(value
)) {
2503 // var _prototype = this.constructor.prototype;
2504 // var fromPrototype = !Base._prototyping && _prototype[source] == ancestor;
2505 value
= function() {
2506 var previous
= this.base
;
2507 // this.base = fromPrototype ? _prototype[source] : ancestor;
2508 this.base
= ancestor
;
2509 var returnValue
= method
.apply(this, arguments
);
2510 this.base
= previous
;
2513 // point to the underlying method
2514 value
.valueOf
= function() {
2517 value
.toString
= function() {
2518 return String(method
);
2521 return this[source
] = value
;
2522 } else if (source
) {
2523 var _prototype
= {toSource
: null};
2524 // do the "toString" and other methods manually
2525 var _protected
= ["toString", "valueOf"];
2526 // if we are prototyping then include the constructor
2527 if (HTMLArea
.Base
._prototyping
) _protected
[2] = "constructor";
2528 for (var i
= 0; (name
= _protected
[i
]); i
++) {
2529 if (source
[name
] != _prototype
[name
]) {
2530 extend
.call(this, name
, source
[name
]);
2533 // copy each of the source object's properties to this object
2534 for (var name
in source
) {
2535 if (!_prototype
[name
]) {
2536 extend
.call(this, name
, source
[name
]);
2544 // call this method from any other method to invoke that method's ancestor
2548 HTMLArea
.Base
.extend
= function(_instance
, _static
) {
2549 var extend
= HTMLArea
.Base
.prototype.extend
;
2550 if (!_instance
) _instance
= {};
2551 // build the prototype
2552 HTMLArea
.Base
._prototyping
= true;
2553 var _prototype
= new this;
2554 extend
.call(_prototype
, _instance
);
2555 var constructor
= _prototype
.constructor
;
2556 _prototype
.constructor
= this;
2557 delete HTMLArea
.Base
._prototyping
;
2558 // create the wrapper for the constructor function
2559 var klass
= function() {
2560 if (!HTMLArea
.Base
._prototyping
) constructor
.apply(this, arguments
);
2561 this.constructor
= klass
;
2563 klass
.prototype = _prototype
;
2564 // build the class interface
2565 klass
.extend
= this.extend
;
2566 klass
.implement
= this.implement
;
2567 klass
.toString
= function() {
2568 return String(constructor
);
2570 extend
.call(klass
, _static
);
2572 var object
= constructor
? klass
: _prototype
;
2573 // class initialisation
2574 if (object
.init
instanceof Function
) object
.init();
2578 HTMLArea
.Base
.implement
= function(_interface
) {
2579 if (_interface
instanceof Function
) _interface
= _interface
.prototype;
2580 this.prototype.extend(_interface
);
2584 * HTMLArea.plugin class
2586 * Every plugin should be a subclass of this class
2589 HTMLArea
.Plugin
= HTMLArea
.Base
.extend({
2592 * HTMLArea.plugin constructor
2594 * @param object editor: instance of RTE
2595 * @param string pluginName: name of the plugin
2597 * @return boolean true if the plugin was configured
2599 constructor
: function(editor
, pluginName
) {
2600 this.editor
= editor
;
2601 this.editorNumber
= editor
._editorNumber
;
2602 this.editorConfiguration
= editor
.config
;
2603 this.name
= pluginName
;
2605 HTMLArea
.I18N
[this.name
] = eval(this.name
+ "_langArray");
2606 this.I18N
= HTMLArea
.I18N
[this.name
];
2608 this.I18N
= new Array();
2610 return this.configurePlugin(editor
);
2614 * Configures the plugin
2615 * This function is invoked by the class constructor.
2616 * This function should be redefined by the plugin subclass. Normal steps would be:
2617 * - registering plugin ingormation with method registerPluginInformation;
2618 * - registering any buttons with method registerButton;
2619 * - registering any drop-down lists with method registerDropDown.
2621 * @param object editor: instance of RTE
2623 * @return boolean true if the plugin was configured
2625 configurePlugin
: function(editor
) {
2630 * Registers the plugin "About" information
2632 * @param object pluginInformation:
2633 * version : the version,
2634 * developer : the name of the developer,
2635 * developerUrl : the url of the developer,
2636 * copyrightOwner : the name of the copyright owner,
2637 * sponsor : the name of the sponsor,
2638 * sponsorUrl : the url of the sponsor,
2639 * license : the type of license (should be "GPL")
2641 * @return boolean true if the information was registered
2643 registerPluginInformation
: function(pluginInformation
) {
2644 if (typeof(pluginInformation
) !== "object") {
2645 this.appendToLog("registerPluginInformation", "Plugin information was not provided");
2648 this.pluginInformation
= pluginInformation
;
2649 this.pluginInformation
.name
= this.name
;
2650 /* Ensure backwards compatibility */
2651 this.pluginInformation
.developer_url
= this.pluginInformation
.developerUrl
;
2652 this.pluginInformation
.c_owner
= this.pluginInformation
.copyrightOwner
;
2653 this.pluginInformation
.sponsor_url
= this.pluginInformation
.sponsorUrl
;
2659 * Returns the plugin information
2661 * @return object the plugin information object
2663 getPluginInformation
: function() {
2664 return this.pluginInformation
;
2668 * Returns a plugin object
2670 * @param string pluinName: the name of some plugin
2671 * @return object the plugin object or null
2673 getPluginInstance
: function(pluginName
) {
2674 return this.editor
.getPluginInstance(pluginName
);
2678 * Returns a current editor mode
2680 * @return string editor mode
2682 getEditorMode
: function() {
2683 return this.getPluginInstance("EditorMode").getEditorMode();
2687 * Returns true if the button is enabled in the toolbar configuration
2689 * @param string buttonId: identification of the button
2691 * @return boolean true if the button is enabled in the toolbar configuration
2693 isButtonInToolbar
: function(buttonId
) {
2694 var toolbar
= this.editorConfiguration
.toolbar
;
2695 var n
= toolbar
.length
;
2696 for ( var i
= 0; i
< n
; ++i
) {
2697 var buttonInToolbar
= new RegExp( "^(" + toolbar
[i
].join("|") + ")$", "i");
2698 if (buttonInToolbar
.test(buttonId
)) {
2706 * Registors a button for inclusion in the toolbar
2708 * @param object buttonConfiguration: the configuration object of the button:
2709 * id : unique id for the button
2710 * tooltip : tooltip for the button
2711 * textMode : enable in text mode
2712 * action : name of the function invoked when the button is pressed
2713 * context : will be disabled if not inside one of listed elements
2714 * hide : hide in menu and show only in context menu?
2715 * selection : will be disabled if there is no selection?
2716 * hotkey : hotkey character
2717 * dialog : if true, the button opens a dialogue
2718 * dimensions : the opening dimensions object of the dialogue window
2720 * @return boolean true if the button was successfully registered
2722 registerButton
: function (buttonConfiguration
) {
2723 if (this.isButtonInToolbar(buttonConfiguration
.id
)) {
2724 if ((typeof(buttonConfiguration
.action
) === "string") && (typeof(this[buttonConfiguration
.action
]) === "function")) {
2725 var hotKeyAction
= buttonConfiguration
.action
;
2726 var actionFunctionReference
= this.makeFunctionReference(buttonConfiguration
.action
);
2727 buttonConfiguration
.action
= actionFunctionReference
;
2728 if (!buttonConfiguration
.textMode
) {
2729 buttonConfiguration
.textMode
= false;
2731 if (buttonConfiguration
.dialog
) {
2732 if (!buttonConfiguration
.dimensions
) {
2733 buttonConfiguration
.dimensions
= { width
: 250, height
: 250};
2735 buttonConfiguration
.dimensions
.top
= buttonConfiguration
.dimensions
.top
? buttonConfiguration
.dimensions
.top
: this.editorConfiguration
.dialogueWindows
.defaultPositionFromTop
;
2736 buttonConfiguration
.dimensions
.left
= buttonConfiguration
.dimensions
.left
? buttonConfiguration
.dimensions
.left
: this.editorConfiguration
.dialogueWindows
.defaultPositionFromLeft
;
2738 buttonConfiguration
.dialog
= false;
2740 if (this.editorConfiguration
.registerButton(buttonConfiguration
)) {
2741 var hotKey
= buttonConfiguration
.hotKey
? buttonConfiguration
.hotKey
:
2742 ((this.editorConfiguration
.buttons
[this.editorConfiguration
.convertButtonId
[buttonConfiguration
.id
]] && this.editorConfiguration
.buttons
[this.editorConfiguration
.convertButtonId
[buttonConfiguration
.id
]].hotKey
) ? this.editorConfiguration
.buttons
[this.editorConfiguration
.convertButtonId
[buttonConfiguration
.id
]].hotKey
: null);
2743 if (!hotKey
&& buttonConfiguration
.hotKey
== "0") {
2746 if (!hotKey
&& this.editorConfiguration
.buttons
[this.editorConfiguration
.convertButtonId
[buttonConfiguration
.id
]] && this.editorConfiguration
.buttons
[this.editorConfiguration
.convertButtonId
[buttonConfiguration
.id
]].hotKey
== "0") {
2749 if (hotKey
|| hotKey
== "0") {
2750 var hotKeyConfiguration
= {
2752 cmd
: buttonConfiguration
.id
,
2753 action
: hotKeyAction
2755 return this.registerHotKey(hotKeyConfiguration
);
2760 this.appendToLog("registerButton", "Function " + buttonConfiguration
.action
+ " was not defined when registering button " + buttonConfiguration
.id
);
2767 * Registors a drop-down list for inclusion in the toolbar
2769 * @param object dropDownConfiguration: the configuration object of the drop-down:
2770 * id : unique id for the drop-down
2771 * tooltip : tooltip for the drop-down
2772 * textMode : enable in text mode
2773 * action : name of the function invoked when a new option is selected
2774 * refresh : name of the function invoked in order to refresh the drop-down when the toolbar is updated
2775 * context : will be disabled if not inside one of listed elements
2777 * @return boolean true if the drop-down list was successfully registered
2779 registerDropDown
: function (dropDownConfiguration
) {
2780 if (this.isButtonInToolbar(dropDownConfiguration
.id
)) {
2781 if (typeof((dropDownConfiguration
.action
) === "string") && (typeof(this[dropDownConfiguration
.action
]) === "function")) {
2782 var actionFunctionReference
= this.makeFunctionReference(dropDownConfiguration
.action
);
2783 dropDownConfiguration
.action
= actionFunctionReference
;
2784 if (!dropDownConfiguration
.textMode
) {
2785 dropDownConfiguration
.textMode
= false;
2787 if (typeof(dropDownConfiguration
.refresh
) === "string") {
2788 if (typeof(this[dropDownConfiguration
.refresh
]) === "function") {
2789 var refreshFunctionReference
= this.makeFunctionReference(dropDownConfiguration
.refresh
);
2790 dropDownConfiguration
.refresh
= refreshFunctionReference
;
2792 this.appendToLog("registerDropDown", "Function " + dropDownConfiguration
.refresh
+ " was not defined when registering drop-down " + dropDownConfiguration
.id
);
2796 return this.editorConfiguration
.registerDropdown(dropDownConfiguration
);
2798 this.appendToLog("registerDropDown", "Function " + dropDownConfiguration
.action
+ " was not defined when registering drop-down " + dropDownConfiguration
.id
);
2805 * Returns the drop-down configuration
2807 * @param string dropDownId: the unique id of the drop-down
2809 * @return object the drop-down configuration object
2811 getDropDownConfiguration
: function(dropDownId
) {
2812 return this.editorConfiguration
.customSelects
[dropDownId
];
2816 * Registors a hotkey
2818 * @param object hotKeyConfiguration: the configuration object of the hotkey:
2820 * action : name of the function invoked when a hotkey is pressed
2822 * @return boolean true if the hotkey was successfully registered
2824 registerHotKey
: function (hotKeyConfiguration
) {
2825 if (typeof((hotKeyConfiguration
.action
) === "string") && (typeof(this[hotKeyConfiguration
.action
]) === "function")) {
2826 var actionFunctionReference
= this.makeFunctionReference(hotKeyConfiguration
.action
);
2827 hotKeyConfiguration
.action
= actionFunctionReference
;
2828 return this.editorConfiguration
.registerHotKey(hotKeyConfiguration
);
2830 this.appendToLog("registerHotKey", "Function " + hotKeyConfiguration
.action
+ " was not defined when registering hotkey " + hotKeyConfiguration
.id
);
2836 * Returns the buttonId corresponding to the hotkey, if any
2838 * @param string key: the hotkey
2840 * @return string the buttonId or ""
2842 translateHotKey
: function(key
) {
2843 if (typeof(this.editorConfiguration
.hotKeyList
[key
]) !== "undefined") {
2844 var buttonId
= this.editorConfiguration
.hotKeyList
[key
].cmd
;
2845 if (typeof(buttonId
) !== "undefined") {
2855 * Returns the hotkey configuration
2857 * @param string key: the hotkey
2859 * @return object the hotkey configuration object
2861 getHotKeyConfiguration
: function(key
) {
2862 if (typeof(this.editorConfiguration
.hotKeyList
[key
]) !== "undefined") {
2863 return this.editorConfiguration
.hotKeyList
[key
];
2870 * The toolbar refresh handler of the plugin
2871 * This function may be defined by the plugin subclass.
2872 * If defined, the function will be invoked whenever the toolbar state is refreshed.
2876 onUpdateToolbar
: null,
2879 * The keyPress event handler
2880 * This function may be defined by the plugin subclass.
2881 * If defined, the function is invoked whenever a key is pressed.
2883 * @param event keyEvent: the event that was triggered when a key was pressed
2890 * The hotKey event handler
2891 * This function may be defined by the plugin subclass.
2892 * If defined, the function is invoked whenever a hot key is pressed.
2894 * @param event key: the hot key that was pressed
2901 * The onMode event handler
2902 * This function may be redefined by the plugin subclass.
2903 * The function is invoked whenever the editor changes mode.
2905 * @param string mode: "wysiwyg" or "textmode"
2909 onMode
: function(mode
) {
2910 if (mode
=== "textmode" && this.dialog
&& HTMLArea
.Dialog
[this.name
] == this.dialog
&& !(this.dialog
.buttonId
&& this.editorConfiguration
.btnList
[this.dialog
.buttonId
] && this.editorConfiguration
.btnList
[this.dialog
.buttonId
].textMode
)) {
2911 this.dialog
.close();
2916 * The onGenerate event handler
2917 * This function may be defined by the plugin subclass.
2918 * If defined, the function is invoked when the editor is initialized
2925 * Make function reference in order to avoid memory leakage in IE
2927 * @param string functionName: the name of the plugin function to be invoked
2929 * @return function function definition invoking the specified function of the plugin
2931 makeFunctionReference
: function (functionName
) {
2933 return (function(arg1
, arg2
) {
2934 return (self
[functionName
](arg1
, arg2
));});
2940 * @param string label: the name of the label to localize
2942 * @return string the localization of the label
2944 localize
: function (label
) {
2945 return this.I18N
[label
] || HTMLArea
.I18N
.dialogs
[label
] || HTMLArea
.I18N
.tooltips
[label
] || HTMLArea
.I18N
.msg
[label
];
2949 * Load a Javascript file synchronously
2951 * @param string url: url of the file to load
2953 * @return boolean true on success
2955 getJavascriptFile
: function (url
, noEval
) {
2956 var script
= HTMLArea
._getScript(0, false, url
);
2965 this.appendToLog("getJavascriptFile", "Error evaluating contents of Javascript file: " + url
);
2975 * Post data to the server
2977 * @param string url: url to post data to
2978 * @param object data: data to be posted
2979 * @param function handler: function that will handle the response returned by the server
2981 * @return boolean true on success
2983 postData
: function (url
, data
, handler
) {
2984 HTMLArea
._postback(url
, data
, handler
, this.editorConfiguration
.RTEtsConfigParams
, (this.editorConfiguration
.typo3ContentCharset
? this.editorConfiguration
.typo3ContentCharset
: "utf-8"));