1 /***************************************************************
4 * (c) 2002-2004, interactivetools.com, inc.
5 * (c) 2003-2004 dynarch.com
6 * (c) 2004-2008 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 ***************************************************/
42 * Set some basic paths
44 if (typeof(_editor_url
) == "string") {
45 // Leave exactly one backslash at the end of _editor_url
46 _editor_url
= _editor_url
.replace(/\x2f*$/, '/');
48 alert("WARNING: _editor_url is not set!");
51 if (typeof(_editor_skin
) == "string") _editor_skin
= _editor_skin
.replace(/\x2f*$/, '/');
52 else var _editor_skin
= _editor_url
+ "skins/default/";
53 if (typeof(_editor_CSS
) != "string") var _editor_CSS
= _editor_url
+ "skins/default/htmlarea.css";
54 if (typeof(_editor_edited_content_CSS
) != "string") var _editor_edited_content_CSS
= _editor_skin
+ "htmlarea-edited-content.css";
55 if (typeof(_editor_lang
) == "string") _editor_lang
= _editor_lang
? _editor_lang
.toLowerCase() : "en";
58 * HTMLArea object constructor.
60 var HTMLArea = function(textarea
, config
) {
61 if (HTMLArea
.checkSupportedBrowser()) {
62 if (typeof(config
) == "undefined") this.config
= new HTMLArea
.Config();
63 else this.config
= config
;
64 this._htmlArea
= null;
65 this._textArea
= textarea
;
66 this._editMode
= "wysiwyg";
68 this._timerToolbar
= null;
69 this._undoQueue
= new Array();
71 this._customUndo
= true;
73 this.eventHandlers
= {};
77 HTMLArea
.editorCSS
= _editor_CSS
;
80 * Browser identification
82 HTMLArea
.agt
= navigator
.userAgent
.toLowerCase();
83 HTMLArea
.is_opera
= (HTMLArea
.agt
.indexOf("opera") != -1);
84 HTMLArea
.is_ie
= (HTMLArea
.agt
.indexOf("msie") != -1) && !HTMLArea
.is_opera
;
85 HTMLArea
.is_safari
= (HTMLArea
.agt
.indexOf("webkit") != -1);
86 HTMLArea
.is_gecko
= (navigator
.product
== "Gecko") || HTMLArea
.is_opera
;
87 // Check on MacOS Wamcom version 1.3 but exclude Firefox rv 1.8.1.3
88 HTMLArea
.is_wamcom
= (HTMLArea
.agt
.indexOf("wamcom") != -1) || (HTMLArea
.is_gecko
&& HTMLArea
.agt
.indexOf("1.3") != -1 && HTMLArea
.agt
.indexOf(".1.3") == -1);
91 * A log for troubleshooting
93 HTMLArea
._debugMode
= false;
94 if (typeof(_editor_debug_mode
) != "undefined") HTMLArea
._debugMode
= _editor_debug_mode
;
96 HTMLArea
._appendToLog = function(str
){
97 if(HTMLArea
._debugMode
) {
98 var log
= document
.getElementById("HTMLAreaLog");
100 log
.appendChild(document
.createTextNode(str
));
101 log
.appendChild(document
.createElement("br"));
107 * Using compressed scripts
109 HTMLArea
._compressedScripts
= false;
110 if (typeof(_editor_compressed_scripts
) != "undefined") HTMLArea
._compressedScripts
= _editor_compressed_scripts
;
113 * Localization of core script
115 HTMLArea
.I18N
= HTMLArea_langArray
;
118 * Build array of scripts to be loaded
120 HTMLArea
.is_loaded
= false;
121 HTMLArea
.onload = function(){
122 HTMLArea
.is_loaded
= true;
123 HTMLArea
._appendToLog("All scripts successfully loaded.");
126 HTMLArea
._scripts
= [];
127 HTMLArea
._scriptLoaded
= [];
128 HTMLArea
._request
= [];
129 HTMLArea
.loadScript = function(url
, plugin
) {
130 if (plugin
) url
= _editor_url
+ "/plugins/" + plugin
+ '/' + url
;
131 if (HTMLArea
.is_opera
) url
= _typo3_host_url
+ url
;
132 if (HTMLArea
._compressedScripts
&& url
.indexOf("compressed") == -1) url
= url
.replace(/\.js$/gi, "_compressed.js");
133 HTMLArea
._scripts
.push(url
);
135 if(HTMLArea
.is_gecko
) HTMLArea
.loadScript(RTEarea
[0]["htmlarea-gecko"] ? RTEarea
[0]["htmlarea-gecko"] : _editor_url
+ "htmlarea-gecko.js");
136 if(HTMLArea
.is_ie
) HTMLArea
.loadScript(RTEarea
[0]["htmlarea-ie"] ? RTEarea
[0]["htmlarea-ie"] : _editor_url
+ "htmlarea-ie.js");
139 * Get a script using asynchronous XMLHttpRequest
141 HTMLArea
.MSXML_XMLHTTP_PROGIDS
= new Array("Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP");
142 HTMLArea
.XMLHTTPResponseHandler = function (i
) {
144 var url
= HTMLArea
._scripts
[i
];
145 if (HTMLArea
._request
[i
].readyState
!= 4) return;
146 if (HTMLArea
._request
[i
].status
== 200) {
148 eval(HTMLArea
._request
[i
].responseText
);
149 HTMLArea
._scriptLoaded
[i
] = true;
152 HTMLArea
._appendToLog("ERROR [HTMLArea::getScript]: Unable to get script " + url
+ ": " + e
);
155 HTMLArea
._appendToLog("ERROR [HTMLArea::getScript]: Unable to get " + url
+ " . Server reported " + HTMLArea
._request
[i
].status
);
159 HTMLArea
._getScript = function (i
,asynchronous
,url
) {
160 if (typeof(url
) == "undefined") var url
= HTMLArea
._scripts
[i
];
161 if (typeof(asynchronous
) == "undefined") var asynchronous
= true;
162 if (window
.XMLHttpRequest
) HTMLArea
._request
[i
] = new XMLHttpRequest();
163 else if (window
.ActiveXObject
) {
165 for (var k
= 0; k
< HTMLArea
.MSXML_XMLHTTP_PROGIDS
.length
&& !success
; k
++) {
167 HTMLArea
._request
[i
] = new ActiveXObject(HTMLArea
.MSXML_XMLHTTP_PROGIDS
[k
]);
171 if (!success
) return false;
173 var request
= HTMLArea
._request
[i
];
175 request
.open("GET", url
, asynchronous
);
176 if (asynchronous
) request
.onreadystatechange
= HTMLArea
.XMLHTTPResponseHandler(i
);
177 if (window
.XMLHttpRequest
) request
.send(null);
178 else if (window
.ActiveXObject
) request
.send();
180 if (request
.status
== 200) return request
.responseText
;
190 * Wait for the loading process to complete
192 HTMLArea
.checkInitialLoad = function() {
193 var scriptsLoaded
= true;
194 for (var i
= HTMLArea
._scripts
.length
; --i
>= 0;) {
195 scriptsLoaded
= scriptsLoaded
&& HTMLArea
._scriptLoaded
[i
];
197 if(HTMLArea
.loadTimer
) window
.clearTimeout(HTMLArea
.loadTimer
);
199 HTMLArea
.is_loaded
= true;
200 HTMLArea
._appendToLog("[HTMLArea::init]: All scripts successfully loaded.");
201 HTMLArea
._appendToLog("[HTMLArea::init]: Editor url set to: " + _editor_url
);
202 HTMLArea
._appendToLog("[HTMLArea::init]: Editor skin CSS set to: " + _editor_CSS
);
203 HTMLArea
._appendToLog("[HTMLArea::init]: Editor content skin CSS set to: " + _editor_edited_content_CSS
);
204 if (window
.ActiveXObject
) {
205 for (var i
= HTMLArea
._scripts
.length
; --i
>= 0;) {
206 HTMLArea
._request
[i
].onreadystatechange
= new Function();
207 HTMLArea
._request
[i
] = null;
211 HTMLArea
.loadTimer
= window
.setTimeout("HTMLArea.checkInitialLoad();", 200);
217 * Get all the scripts
219 HTMLArea
.init = function() {
220 HTMLArea
._eventCache
= HTMLArea
._eventCacheConstructor();
221 if (window
.XMLHttpRequest
|| window
.ActiveXObject
) {
224 for (var i
= 0, n
= HTMLArea
._scripts
.length
; i
< n
&& success
; i
++) {
225 success
= success
&& HTMLArea
._getScript(i
);
228 HTMLArea
._appendToLog("ERROR [HTMLArea::init]: Unable to use XMLHttpRequest: "+ e
);
231 HTMLArea
.checkInitialLoad();
233 if (HTMLArea
.is_ie
) window
.setTimeout('if (window.document.getElementById("pleasewait1")) { window.document.getElementById("pleasewait1").innerHTML = HTMLArea.I18N.msg["ActiveX-required"]; } else { alert(HTMLArea.I18N.msg["ActiveX-required"]); };', 200);
236 if (HTMLArea
.is_ie
) alert(HTMLArea
.I18N
.msg
["ActiveX-required"]);
241 * Compile some regular expressions
243 HTMLArea
.RE_tagName
= /(<\/|<)\s*([^ \t\n>]+)/ig;
244 HTMLArea
.RE_doctype
= /(<!doctype((.|\n)*?)>)\n?/i;
245 HTMLArea
.RE_head
= /<head>((.|\n)*?)<\/head>/i;
246 HTMLArea
.RE_body
= /<body>((.|\n)*?)<\/body>/i;
247 HTMLArea
.Reg_body
= new RegExp("<\/?(body)[^>]*>", "gi");
248 HTMLArea
.reservedClassNames
= /htmlarea/;
249 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;
250 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;
253 * Editor configuration object constructor
256 HTMLArea
.Config = function () {
258 this.height
= "auto";
259 // enable creation of a status bar?
260 this.statusBar
= true;
261 // maximum size of the undo queue
263 // the time interval at which undo samples are taken: 1/2 sec.
264 this.undoTimeout
= 500;
265 // whether the toolbar should be included in the size or not.
266 this.sizeIncludesToolbar
= true;
267 // if true then HTMLArea will retrieve the full HTML, starting with the <HTML> tag.
268 this.fullPage
= false;
269 // if the site is secure, create a secure iframe
270 this.useHTTPS
= false;
273 this.enableMozillaExtension
= true;
274 this.disableEnterParagraphs
= false;
275 this.disableObjectResizing
= false;
276 this.removeTrailingBR
= false;
277 // style included in the iframe document
278 this.editedContentStyle
= _editor_edited_content_CSS
;
281 // remove tags (these have to be a regexp, or null if this functionality is not desired)
282 this.htmlRemoveTags
= null;
283 // remove tags and any contents (these have to be a regexp, or null if this functionality is not desired)
284 this.htmlRemoveTagsAndContents
= null;
286 this.htmlRemoveComments
= false;
287 // custom tags (these have to be a regexp, or null if this functionality is not desired)
288 this.customTags
= null;
289 // BaseURL included in the iframe document
290 this.baseURL
= document
.baseURI
|| document
.URL
;
291 if(this.baseURL
&& this.baseURL
.match(/(.*)\/([^\/]+)/)) this.baseURL
= RegExp
.$1 + "/";
293 this.imgURL
= "images/";
294 this.popupURL
= "popups/";
297 InsertHorizontalRule
: ["Horizontal Rule", "ed_hr.gif",false, function(editor
) {editor
.execCommand("InsertHorizontalRule");}],
298 HtmlMode
: ["Toggle HTML Source", "ed_html.gif", true, function(editor
) {editor
.execCommand("HtmlMode");}],
299 SelectAll
: ["SelectAll", "", true, function(editor
) {editor
.execCommand("SelectAll");}, null, true, false],
300 Undo
: ["Undo the last action", "ed_undo.gif", false, function(editor
) {editor
.execCommand("Undo");}],
301 Redo
: ["Redo the last action", "ed_redo.gif", false, function(editor
) {editor
.execCommand("Redo");}],
302 Cut
: ["Cut selection", "ed_cut.gif", false, function(editor
) {editor
.execCommand("Cut");}],
303 Copy
: ["Copy selection", "ed_copy.gif", false, function(editor
) {editor
.execCommand("Copy");}],
304 Paste
: ["Paste from clipboard", "ed_paste.gif", false, function(editor
) {editor
.execCommand("Paste");}]
308 a
: { cmd
: "SelectAll", action
: null},
309 v
: { cmd
: "Paste", action
: null},
310 z
: { cmd
: "Undo", action
: null},
311 y
: { cmd
: "Redo", action
: null}
314 // Initialize tooltips from the I18N module, generate correct image path
315 for (var buttonId
in this.btnList
) {
316 if (this.btnList
.hasOwnProperty(buttonId
)) {
317 var btn
= this.btnList
[buttonId
];
318 if (typeof(HTMLArea
.I18N
.tooltips
[buttonId
.toLowerCase()]) !== "undefined") {
319 btn
[0] = HTMLArea
.I18N
.tooltips
[buttonId
.toLowerCase()];
321 if (typeof(btn
[1]) === "string") {
322 btn
[1] = _editor_skin
+ this.imgURL
+ btn
[1];
324 btn
[1][0] = _editor_skin
+ this.imgURL
+ btn
[1][0];
328 this.customSelects
= {};
332 * Register a new button with the configuration.
333 * It can be called with all arguments, or with only one (first one). When called with
334 * only one argument it must be an object with the following properties:
335 * id, tooltip, image, textMode, action, context. Examples:
337 * 1. config.registerButton("my-hilite", "Hilite text", "my-hilite.gif", false, function(editor) {...}, context);
338 * 2. config.registerButton({
339 * id : "my-hilite", // Unique id for the button
340 * tooltip : "Hilite text", // the tooltip
341 * image : "my-hilite.gif", // image to be displayed in the toolbar
342 * textMode : false, // disabled in text mode
343 * action : function(editor) { // called when the button is clicked
344 * editor.surroundHTML('<span class="hilite">', '</span>');
346 * context : "p" // will be disabled if not inside a <p> element
347 * hide : false // hide in menu and show only in context menu
348 * selection : false // will be disabled if there is no selection
349 * dialog : true // the button opens a dialog
352 HTMLArea
.Config
.prototype.registerButton = function(id
,tooltip
,image
,textMode
,action
,context
,hide
,selection
, dialog
) {
354 switch (typeof(id
)) {
355 case "string": buttonId
= id
; break;
356 case "object": buttonId
= id
.id
; break;
357 default: HTMLArea
._appendToLog("[HTMLArea.Config::registerButton]: invalid arguments");
360 if (typeof(this.customSelects
[buttonId
]) !== "undefined") {
361 HTMLArea
._appendToLog("[HTMLArea.Config::registerButton]: A dropdown with the same Id: " + buttonId
+ " already exists.");
364 if (typeof(this.btnList
[buttonId
]) !== "undefined") {
365 HTMLArea
._appendToLog("[HTMLArea.Config::registerButton]: A button with the same Id: " + buttonId
+ " already exists and will be overidden.");
367 switch (typeof(id
)) {
369 if (typeof(hide
) === "undefined") var hide
= false;
370 if (typeof(selection
) === "undefined") var selection
= false;
371 if (typeof(dialog
) === "undefined") var dialog
= true;
372 this.btnList
[id
] = [tooltip
, image
, textMode
, action
, context
, hide
, selection
, dialog
];
375 if (typeof(id
.hide
) === "undefined") id
.hide
= false;
376 if (typeof(id
.selection
) === "undefined") id
.selection
= false;
377 if (typeof(id
.dialog
) === "undefined") id
.dialog
= true;
378 this.btnList
[id
.id
] = [id
.tooltip
, id
.image
, id
.textMode
, id
.action
, id
.context
, id
.hide
, id
.selection
, id
.dialog
];
385 * Register a dropdown box with the editor configuration.
387 HTMLArea
.Config
.prototype.registerDropdown = function(dropDownConfiguration
) {
388 if (typeof(this.customSelects
[dropDownConfiguration
.id
]) != "undefined") {
389 HTMLArea
._appendToLog("[HTMLArea.Config::registerDropdown]: A dropdown with the same ID " + dropDownConfiguration
.id
+ " already exists and will be overidden.");
391 if (typeof(this.btnList
[dropDownConfiguration
.id
]) != "undefined") {
392 HTMLArea
._appendToLog("ERROR [HTMLArea.Config::registerDropdown]: A button with the same ID " + dropDownConfiguration
.id
+ " already exists.");
395 this.customSelects
[dropDownConfiguration
.id
] = dropDownConfiguration
;
400 * Register a hotkey with the editor configuration.
402 HTMLArea
.Config
.prototype.registerHotKey = function(hotKeyConfiguration
) {
403 if (typeof(this.hotKeyList
[hotKeyConfiguration
.id
]) != "undefined") {
404 HTMLArea
._appendToLog("ERROR [HTMLArea.Config::registerHotKey]: A hotkey with the same key " + hotKeyConfiguration
.id
+ " already exists.");
407 this.hotKeyList
[hotKeyConfiguration
.id
] = hotKeyConfiguration
;
411 /***************************************************
413 ***************************************************/
415 * Update the state of a toolbar element.
416 * This function is member of a toolbar element object, unnamed object created by createButton or createSelect functions.
418 HTMLArea
.setButtonStatus = function(id
,newval
) {
419 var oldval
= this[id
];
420 var el
= document
.getElementById(this.elementId
);
421 if (oldval
!= newval
) {
425 if (!HTMLArea
.is_wamcom
) {
426 HTMLArea
._removeClass(el
, "buttonDisabled");
427 HTMLArea
._removeClass(el
.parentNode
, "buttonDisabled");
431 if (!HTMLArea
.is_wamcom
) {
432 HTMLArea
._addClass(el
, "buttonDisabled");
433 HTMLArea
._addClass(el
.parentNode
, "buttonDisabled");
440 HTMLArea
._addClass(el
, "buttonPressed");
441 HTMLArea
._addClass(el
.parentNode
, "buttonPressed");
443 HTMLArea
._removeClass(el
, "buttonPressed");
444 HTMLArea
._removeClass(el
.parentNode
, "buttonPressed");
453 * Create a new line in the toolbar
455 HTMLArea
.newLine = function(toolbar
) {
456 tb_line
= document
.createElement("ul");
457 tb_line
.className
= "tb-line";
458 toolbar
.appendChild(tb_line
);
463 * Add a toolbar element to the current line or group
465 HTMLArea
.addTbElement = function(element
, tb_line
, first_cell_on_line
) {
466 var tb_cell
= document
.createElement("li");
467 if (first_cell_on_line
) tb_cell
.className
= "tb-first-cell";
468 else tb_cell
.className
= "tb-cell";
469 HTMLArea
._addClass(tb_cell
, element
.className
);
470 tb_line
.appendChild(tb_cell
);
471 tb_cell
.appendChild(element
);
472 if(element
.style
.display
== "none") {
473 tb_cell
.style
.display
= "none";
474 if(HTMLArea
._hasClass(tb_cell
.previousSibling
, "separator")) tb_cell
.previousSibling
.style
.display
= "none";
480 * Create a new group on the current line
482 HTMLArea
.addTbGroup = function(tb_line
, first_cell_on_line
) {
483 var tb_group
= document
.createElement("ul");
484 tb_group
.className
= "tb-group";
485 HTMLArea
.addTbElement(tb_group
, tb_line
, first_cell_on_line
);
490 * Create a combo box and add it to the toolbar
492 HTMLArea
.prototype.createSelect = function(txt
,tb_line
,first_cell_on_line
,labelObj
) {
500 first
: first_cell_on_line
,
505 var dropdown
= this.config
.customSelects
[cmd
];
506 if (typeof(dropdown
) != "undefined") {
507 options
= dropdown
.options
;
508 context
= dropdown
.context
;
509 if (typeof(dropdown
.tooltip
) != "undefined") tooltip
= dropdown
.tooltip
;
512 newObj
["el"] = document
.createElement("select");
513 newObj
["el"].className
= "select";
514 newObj
["el"].title
= tooltip
;
515 newObj
["el"].id
= this._editorNumber
+ "-" + txt
;
516 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
518 name
: txt
, // field name
519 elementId
: newObj
["el"].id
, // unique id for the UI element
520 enabled
: true, // is it enabled?
521 text
: false, // enabled in text mode?
522 cmd
: cmd
, // command ID
523 state
: HTMLArea
.setButtonStatus
, // for changing state
525 editorNumber
: this._editorNumber
527 this._toolbarObjects
[txt
] = obj
;
528 newObj
["el"]._obj
= obj
;
529 if (labelObj
["labelRef"]) {
530 labelObj
["el"].htmlFor
= newObj
["el"].id
;
531 newObj
["labelUsed"] = true;
533 HTMLArea
._addEvent(newObj
["el"], "change", HTMLArea
.toolBarButtonHandler
);
535 for (var i
in options
) {
536 if (options
.hasOwnProperty(i
)) {
537 var op
= document
.createElement("option");
539 op
.value
= options
[i
];
540 newObj
["el"].appendChild(op
);
544 newObj
["created"] = true;
551 * Create a button and add it to the toolbar
553 HTMLArea
.prototype.createButton = function (txt
,tb_line
,first_cell_on_line
,labelObj
) {
558 first
: first_cell_on_line
,
564 newObj
["el"] = document
.createElement("div");
565 newObj
["el"].className
= "separator";
566 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
567 newObj
["created"] = true;
570 newObj
["el"] = document
.createElement("div");
571 newObj
["el"].className
= "space";
572 newObj
["el"].innerHTML
= " ";
573 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
574 newObj
["created"] = true;
576 case "TextIndicator":
577 newObj
["el"] = document
.createElement("div");
578 newObj
["el"].appendChild(document
.createTextNode("A"));
579 newObj
["el"].className
= "indicator";
580 newObj
["el"].title
= HTMLArea
.I18N
.tooltips
.textindicator
;
581 newObj
["el"].id
= this._editorNumber
+ "-" + txt
;
582 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
585 elementId
: newObj
["el"].id
,
589 cmd
: "TextIndicator",
590 state
: HTMLArea
.setButtonStatus
592 this._toolbarObjects
[txt
] = obj
;
593 newObj
["created"] = true;
596 btn
= this.config
.btnList
[txt
];
598 if(!newObj
["created"] && btn
) {
599 newObj
["el"] = document
.createElement("button");
600 newObj
["el"].title
= btn
[0];
601 newObj
["el"].className
= "button";
602 newObj
["el"].id
= this._editorNumber
+ "-" + txt
;
603 if (btn
[5]) newObj
["el"].style
.display
= "none";
604 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
606 name
: txt
, // the button name
607 elementId
: newObj
["el"].id
, // unique id for the UI element
608 enabled
: true, // is it enabled?
609 active
: false, // is it pressed?
610 text
: btn
[2], // enabled in text mode?
611 cmd
: btn
[3], // the function to be invoked
612 state
: HTMLArea
.setButtonStatus
, // for changing state
613 context
: btn
[4] || null, // enabled in a certain context?
614 selection
: btn
[6], // disabled when no selection?
615 editorNumber
: this._editorNumber
617 this._toolbarObjects
[txt
] = obj
;
618 newObj
["el"]._obj
= obj
;
619 if (labelObj
["labelRef"]) {
620 labelObj
["el"].htmlFor
= newObj
["el"].id
;
621 newObj
["labelUsed"] = true;
623 HTMLArea
._addEvents(newObj
["el"],["mouseover", "mouseout", "mousedown", "click"], HTMLArea
.toolBarButtonHandler
);
624 newObj
["el"].className
+= " " + txt
;
625 newObj
["created"] = true;
631 * Create a label and add it to the toolbar
633 HTMLArea
.createLabel = function(txt
,tb_line
,first_cell_on_line
) {
638 first
: first_cell_on_line
640 if (/^([IT])\[(.*?)\]/.test(txt
)) {
641 var l7ed
= RegExp
.$1 == "I"; // localized?
642 var label
= RegExp
.$2;
643 if (l7ed
) label
= HTMLArea
.I18N
.dialogs
[label
];
644 newObj
["el"] = document
.createElement("label");
645 newObj
["el"].className
= "label";
646 newObj
["el"].innerHTML
= label
;
647 newObj
["labelRef"] = true;
648 newObj
["created"] = true;
649 newObj
["first"] = HTMLArea
.addTbElement(newObj
["el"], tb_line
, first_cell_on_line
);
655 * Create the toolbar and append it to the _htmlarea.
657 HTMLArea
.prototype._createToolbar = function () {
658 var j
, k
, code
, n
= this.config
.toolbar
.length
, m
,
659 tb_line
= null, tb_group
= null,
660 first_cell_on_line
= true,
661 labelObj
= new Object(),
662 tbObj
= new Object();
664 var toolbar
= document
.createElement("div");
665 this._toolbar
= toolbar
;
666 toolbar
.className
= "toolbar";
667 toolbar
.unselectable
= "1";
668 this._toolbarObjects
= new Object();
670 for (j
= 0; j
< n
; ++j
) {
671 tb_line
= HTMLArea
.newLine(toolbar
);
672 if(!this.config
.keepButtonGroupTogether
) HTMLArea
._addClass(tb_line
, "free-float");
673 first_cell_on_line
= true;
675 var group
= this.config
.toolbar
[j
];
677 for (k
= 0; k
< m
; ++k
) {
679 if (code
== "linebreak") {
680 tb_line
= HTMLArea
.newLine(toolbar
);
681 if(!this.config
.keepButtonGroupTogether
) HTMLArea
._addClass(tb_line
, "free-float");
682 first_cell_on_line
= true;
685 if ((code
== "separator" || first_cell_on_line
) && this.config
.keepButtonGroupTogether
) {
686 tb_group
= HTMLArea
.addTbGroup(tb_line
, first_cell_on_line
);
687 first_cell_on_line
= false;
690 if (/^([IT])\[(.*?)\]/.test(code
)) {
691 labelObj
= HTMLArea
.createLabel(code
, (tb_group
?tb_group
:tb_line
), first_cell_on_line
);
692 created
= labelObj
["created"] ;
693 first_cell_on_line
= labelObj
["first"];
696 tbObj
= this.createButton(code
, (tb_group
?tb_group
:tb_line
), first_cell_on_line
, labelObj
);
697 created
= tbObj
["created"];
698 first_cell_on_line
= tbObj
["first"];
699 if(tbObj
["labelUsed"]) labelObj
["labelRef"] = false;
702 tbObj
= this.createSelect(code
, (tb_group
?tb_group
:tb_line
), first_cell_on_line
, labelObj
);
703 created
= tbObj
["created"];
704 first_cell_on_line
= tbObj
["first"];
705 if(tbObj
["labelUsed"]) labelObj
["labelRef"] = false;
707 if (!created
) HTMLArea
._appendToLog("ERROR [HTMLArea::createToolbar]: Unknown toolbar item: " + code
);
712 tb_line
= HTMLArea
.newLine(toolbar
);
713 this._htmlArea
.appendChild(toolbar
);
717 * Handle toolbar element events handler
719 HTMLArea
.toolBarButtonHandler = function(ev
) {
720 if(!ev
) var ev
= window
.event
;
721 var target
= (ev
.target
) ? ev
.target
: ev
.srcElement
;
722 while (target
.tagName
.toLowerCase() == "img" || target
.tagName
.toLowerCase() == "div") target
= target
.parentNode
;
723 var obj
= target
._obj
;
724 var editorNumber
= obj
["editorNumber"];
725 var editor
= RTEarea
[editorNumber
]["editor"];
729 HTMLArea
._addClass(target
, "buttonHover");
730 HTMLArea
._addClass(target
.parentNode
, "buttonHover");
733 HTMLArea
._removeClass(target
, "buttonHover");
734 HTMLArea
._removeClass(target
.parentNode
, "buttonHover");
735 HTMLArea
._removeClass(target
, "buttonActive");
736 HTMLArea
._removeClass(target
.parentNode
, "buttonActive");
738 HTMLArea
._addClass(target
, "buttonPressed");
739 HTMLArea
._addClass(target
.parentNode
, "buttonPressed");
743 HTMLArea
._addClass(target
, "buttonActive");
744 HTMLArea
._addClass(target
.parentNode
, "buttonActive");
745 HTMLArea
._removeClass(target
, "buttonPressed");
746 HTMLArea
._removeClass(target
.parentNode
, "buttonPressed");
747 HTMLArea
._stopEvent(ev
);
750 HTMLArea
._removeClass(target
, "buttonActive");
751 HTMLArea
._removeClass(target
.parentNode
, "buttonActive");
752 HTMLArea
._removeClass(target
, "buttonHover");
753 HTMLArea
._removeClass(target
.parentNode
, "buttonHover");
754 obj
.cmd(editor
, obj
.name
);
755 HTMLArea
._stopEvent(ev
);
756 if (HTMLArea
.is_opera
) {
757 editor
._iframe
.focus();
759 if (!editor
.config
.btnList
[obj
.name
][7]) {
760 editor
.updateToolbar();
764 editor
.focusEditor();
765 var dropdown
= editor
.config
.customSelects
[obj
.name
];
766 if (typeof(dropdown
) !== "undefined") {
767 dropdown
.action(editor
, obj
.name
);
768 HTMLArea
._stopEvent(ev
);
769 if (HTMLArea
.is_opera
) {
770 editor
._iframe
.focus();
772 editor
.updateToolbar();
774 HTMLArea
._appendToLog("ERROR [HTMLArea::toolBarButtonHandler]: Combo box " + obj
.name
+ " not registered.");
781 * Create the status bar
783 HTMLArea
.prototype._createStatusBar = function() {
784 var statusBar
= document
.createElement("div");
785 this._statusBar
= statusBar
;
786 statusBar
.className
= "statusBar";
787 if (!this.config
.statusBar
) statusBar
.style
.display
= "none";
788 var statusBarTree
= document
.createElement("span");
789 this._statusBarTree
= statusBarTree
;
790 statusBarTree
.className
= "statusBarTree";
791 statusBar
.appendChild(statusBarTree
);
792 statusBarTree
.appendChild(document
.createTextNode(HTMLArea
.I18N
.msg
["Path"] + ": "));
793 this._htmlArea
.appendChild(statusBar
);
797 * Create the htmlArea iframe and replace the textarea with it.
799 HTMLArea
.prototype.generate = function () {
801 // get the textarea and hide it
802 var textarea
= this._textArea
;
803 if (typeof(textarea
) == "string") {
804 textarea
= HTMLArea
.getElementById("textarea", textarea
);
805 this._textArea
= textarea
;
807 textarea
.style
.display
= "none";
809 // create the editor framework and insert the editor before the textarea
810 var htmlarea
= document
.createElement("div");
811 htmlarea
.className
= "htmlarea";
812 htmlarea
.style
.width
= textarea
.style
.width
;
813 this._htmlArea
= htmlarea
;
814 textarea
.parentNode
.insertBefore(htmlarea
, textarea
);
817 // we have a form, on reset, re-initialize the HTMLArea content and update the toolbar
818 var f
= textarea
.form
;
819 if (typeof(f
.onreset
) == "function") {
820 var funcref
= f
.onreset
;
821 if (typeof(f
.__msh_prevOnReset
) == "undefined") f
.__msh_prevOnReset
= [];
822 f
.__msh_prevOnReset
.push(funcref
);
824 f
._editorNumber
= this._editorNumber
;
825 HTMLArea
._addEvent(f
, "reset", HTMLArea
.resetHandler
);
828 // create & append the toolbar
829 this._createToolbar();
830 HTMLArea
._appendToLog("[HTMLArea::generate]: Toolbar successfully created.");
832 // create and append the IFRAME
833 var iframe
= document
.createElement("iframe");
834 if (HTMLArea
.is_ie
|| HTMLArea
.is_safari
|| HTMLArea
.is_wamcom
) {
835 iframe
.setAttribute("src",_editor_url
+ "popups/blank.html");
836 } else if (HTMLArea
.is_opera
) {
837 iframe
.setAttribute("src",_typo3_host_url
+ _editor_url
+ "popups/blank.html");
839 iframe
.setAttribute("src","javascript:void(0);");
841 iframe
.className
= "editorIframe";
842 if (!this.config
.statusBar
) iframe
.className
+= " noStatusBar";
843 htmlarea
.appendChild(iframe
);
844 this._iframe
= iframe
;
846 // create & append the status bar
847 this._createStatusBar();
852 HTMLArea
._appendToLog("[HTMLArea::generate]: Editor iframe successfully created.");
858 * Size the iframe according to user's prefs or initial textarea
860 HTMLArea
.prototype.sizeIframe = function(diff
) {
861 var height
= (this.config
.height
== "auto" ? (this._textArea
.style
.height
) : this.config
.height
);
862 var textareaHeight
= height
;
863 // All nested tabs and inline levels in the sorting order they were applied:
865 this.nested
.all
= RTEarea
[this._editorNumber
].tceformsNested
;
866 this.nested
.sorted
= HTMLArea
.simplifyNested(this.nested
.all
);
867 // Clone the array instead of using a reference (this.accessParentElements will change the array):
868 var parentElements
= (this.nested
.sorted
&& this.nested
.sorted
.length
? [].concat(this.nested
.sorted
) : []);
869 // Walk through all nested tabs and inline levels to make a correct positioning:
870 var dimensions
= this.accessParentElements(parentElements
, 'this.getDimensions()');
872 if(height
.indexOf("%") == -1) {
873 height
= parseInt(height
) - diff
;
874 if (this.config
.sizeIncludesToolbar
) {
875 this._initialToolbarOffsetHeight
= dimensions
.toolbar
.height
;
876 height
-= dimensions
.toolbar
.height
;
877 height
-= dimensions
.statusbar
.height
;
879 if (height
< 0) height
= 0;
880 textareaHeight
= (height
- 4);
881 if (textareaHeight
< 0) textareaHeight
= 0;
883 textareaHeight
+= "px";
885 this._iframe
.style
.height
= height
;
886 this._textArea
.style
.height
= textareaHeight
;
887 var textareaWidth
= (this.config
.width
== "auto" ? this._textArea
.style
.width
: this.config
.width
);
888 var iframeWidth
= textareaWidth
;
889 if(textareaWidth
.indexOf("%") == -1) {
890 iframeWidth
= parseInt(textareaWidth
) + "px";
891 textareaWidth
= parseInt(textareaWidth
) - diff
;
892 if (textareaWidth
< 0) textareaWidth
= 0;
893 textareaWidth
+= 'px';
895 this._iframe
.style
.width
= "100%";
896 if (HTMLArea
.is_opera
) this._iframe
.style
.width
= iframeWidth
;
897 this._textArea
.style
.width
= textareaWidth
;
901 * Get the dimensions of the toolbar and statusbar.
903 * @return object An object with width/height pairs for statusbar and toolbar.
904 * @author Oliver Hader <oh@inpublica.de>
906 HTMLArea
.prototype.getDimensions = function() {
908 toolbar
: {width
: this._toolbar
.offsetWidth
, height
: this._toolbar
.offsetHeight
},
909 statusbar
: {width
: this._statusBar
.offsetWidth
, height
: this._statusBar
.offsetHeight
}
914 * Access an inline relational element or tab menu and make it "accesible".
915 * If a parent object has the style "display: none", offsetWidth & offsetHeight are '0'.
917 * @params object callbackFunc: A function to be called, when the embedded objects are "accessible".
918 * @return object An object returned by the callbackFunc.
919 * @author Oliver Hader <oh@inpublica.de>
921 HTMLArea
.prototype.accessParentElements = function(parentElements
, callbackFunc
) {
924 if (parentElements
.length
) {
925 var currentElement
= parentElements
.pop();
926 var elementStyle
= document
.getElementById(currentElement
).style
;
927 var actionRequired
= (elementStyle
.display
== 'none' ? true : false);
929 if (actionRequired
) {
930 var originalVisibility
= elementStyle
.visibility
;
931 var originalPosition
= elementStyle
.position
;
932 elementStyle
.visibility
= 'hidden';
933 elementStyle
.position
= 'absolute';
934 elementStyle
.display
= '';
937 result
= this.accessParentElements(parentElements
, callbackFunc
);
939 if (actionRequired
) {
940 elementStyle
.display
= 'none';
941 elementStyle
.position
= originalPosition
;
942 elementStyle
.visibility
= originalVisibility
;
946 result
= eval(callbackFunc
);
954 * Simplify the array of nested levels. Create an indexed array with the correct names of the elements.
956 * @param object nested: The array with the nested levels
957 * @return object The simplified array
958 * @author Oliver Hader <oh@inpublica.de>
960 HTMLArea
.simplifyNested = function(nested
) {
961 var i
, type
, level
, max
, simplifiedNested
=[];
962 if (nested
&& nested
.length
) {
963 if (nested
[0][0]=='inline') {
964 nested
= inline
.findContinuedNestedLevel(nested
, nested
[0][1]);
966 for (i
=0, max
=nested
.length
; i
<max
; i
++) {
968 level
= nested
[i
][1];
970 simplifiedNested
.push(level
+'-DIV');
971 } else if (type
=='inline') {
972 simplifiedNested
.push(level
+'_fields');
976 return simplifiedNested
;
980 * Initialize the iframe
982 HTMLArea
.initIframe = function(editorNumber
) {
983 var editor
= RTEarea
[editorNumber
]["editor"];
987 HTMLArea
.prototype.initIframe = function() {
988 if (this._initIframeTimer
) window
.clearTimeout(this._initIframeTimer
);
989 if (!this._iframe
|| (!this._iframe
.contentWindow
&& !this._iframe
.contentDocument
)) {
990 this._initIframeTimer
= window
.setTimeout("HTMLArea.initIframe(" + this._editorNumber
+ ");", 50);
992 } else if (this._iframe
.contentWindow
&& !HTMLArea
.is_safari
) {
993 if (!this._iframe
.contentWindow
.document
|| !this._iframe
.contentWindow
.document
.documentElement
) {
994 this._initIframeTimer
= window
.setTimeout("HTMLArea.initIframe(" + this._editorNumber
+ ");", 50);
997 } else if (!this._iframe
.contentDocument
.documentElement
|| !this._iframe
.contentDocument
.body
) {
998 this._initIframeTimer
= window
.setTimeout("HTMLArea.initIframe(" + this._editorNumber
+ ");", 50);
1001 var doc
= this._iframe
.contentWindow
? this._iframe
.contentWindow
.document
: this._iframe
.contentDocument
;
1004 if (!this.config
.fullPage
) {
1005 var head
= doc
.getElementsByTagName("head")[0];
1007 head
= doc
.createElement("head");
1008 doc
.documentElement
.appendChild(head
);
1010 if (this.config
.baseURL
&& !HTMLArea
.is_opera
) {
1011 var base
= doc
.getElementsByTagName("base")[0];
1013 base
= doc
.createElement("base");
1014 base
.href
= this.config
.baseURL
;
1015 head
.appendChild(base
);
1017 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Iframe baseURL set to: " + this.config
.baseURL
);
1019 var link0
= doc
.getElementsByTagName("link")[0];
1021 link0
= doc
.createElement("link");
1022 link0
.rel
= "stylesheet";
1023 link0
.href
= this.config
.editedContentStyle
;
1024 head
.appendChild(link0
);
1025 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Skin CSS set to: " + this.config
.editedContentStyle
);
1027 if (this.config
.defaultPageStyle
) {
1028 var link
= doc
.getElementsByTagName("link")[1];
1030 link
= doc
.createElement("link");
1031 link
.rel
= "stylesheet";
1032 link
.href
= this.config
.defaultPageStyle
;
1033 head
.appendChild(link
);
1035 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Override CSS set to: " + this.config
.defaultPageStyle
);
1037 if (this.config
.pageStyle
) {
1038 var link
= doc
.getElementsByTagName("link")[2];
1040 link
= doc
.createElement("link");
1041 link
.rel
= "stylesheet";
1042 link
.href
= this.config
.pageStyle
;
1043 head
.appendChild(link
);
1045 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Content CSS set to: " + this.config
.pageStyle
);
1048 var html
= this._textArea
.value
;
1049 this.setFullHTML(html
);
1051 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Editor iframe head successfully initialized.");
1053 this.stylesLoaded();
1057 * Finalize editor Iframe initialization after loading the style sheets
1059 HTMLArea
.stylesLoaded = function(editorNumber
) {
1060 var editor
= RTEarea
[editorNumber
]["editor"];
1061 editor
.stylesLoaded();
1064 HTMLArea
.prototype.stylesLoaded = function() {
1065 var doc
= this._doc
;
1066 var docWellFormed
= true;
1068 // check if the stylesheets have been loaded
1070 if (this._stylesLoadedTimer
) window
.clearTimeout(this._stylesLoadedTimer
);
1071 var stylesAreLoaded
= true;
1074 for (var rule
= 0; rule
< doc
.styleSheets
.length
; rule
++) {
1075 if (HTMLArea
.is_gecko
) try { rules
= doc
.styleSheets
[rule
].cssRules
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; }
1076 if (HTMLArea
.is_ie
) try { rules
= doc
.styleSheets
[rule
].rules
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; }
1077 if (HTMLArea
.is_ie
) try { rules
= doc
.styleSheets
[rule
].imports
; } catch(e
) { stylesAreLoaded
= false; errorText
= e
; }
1079 if (!stylesAreLoaded
&& !HTMLArea
.is_wamcom
) {
1080 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Failed attempt at loading stylesheets: " + errorText
+ " Retrying...");
1081 this._stylesLoadedTimer
= window
.setTimeout("HTMLArea.stylesLoaded(" + this._editorNumber
+ ");", 100);
1084 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Stylesheets successfully loaded.");
1086 if (!this.config
.fullPage
) {
1087 doc
.body
.style
.borderWidth
= "0px";
1088 doc
.body
.className
= "htmlarea-content-body";
1090 doc
.body
.innerHTML
= this._textArea
.value
;
1092 HTMLArea
._appendToLog("[HTMLArea::initIframe]: The HTML document is not well-formed.");
1093 alert(HTMLArea
.I18N
.msg
["HTML-document-not-well-formed"]);
1094 docWellFormed
= false;
1097 // Start undo snapshots
1098 if (this._customUndo
) this._timerUndo
= window
.setInterval("HTMLArea.undoTakeSnapshot(" + this._editorNumber
+ ");", this.config
.undoTimeout
);
1100 // Set contents editable
1101 if (docWellFormed
) {
1102 if (HTMLArea
.is_gecko
&& !HTMLArea
.is_safari
&& !HTMLArea
.is_opera
&& !this._initEditMode()) {
1105 if (HTMLArea
.is_ie
|| HTMLArea
.is_safari
) {
1106 doc
.body
.contentEditable
= true;
1108 if (HTMLArea
.is_opera
|| HTMLArea
.is_safari
) {
1109 doc
.designMode
= "on";
1110 if (this._doc
.queryCommandEnabled("insertbronreturn")) this._doc
.execCommand("insertbronreturn", false, this.config
.disableEnterParagraphs
);
1111 if (this._doc
.queryCommandEnabled("styleWithCSS")) this._doc
.execCommand("styleWithCSS", false, this.config
.useCSS
);
1113 if (HTMLArea
.is_ie
) doc
.selection
.empty();
1114 this._editMode
= "wysiwyg";
1115 if (doc
.body
.contentEditable
|| doc
.designMode
== "on") HTMLArea
._appendToLog("[HTMLArea::initIframe]: Design mode successfully set.");
1117 this._editMode
= "textmode";
1118 this.setMode("docnotwellformedmode");
1119 HTMLArea
._appendToLog("[HTMLArea::initIframe]: Design mode could not be set.");
1122 // set editor number in iframe and document for retrieval in event handlers
1123 doc
._editorNo
= this._editorNumber
;
1124 if (HTMLArea
.is_ie
) doc
.documentElement
._editorNo
= this._editorNumber
;
1126 // intercept events for updating the toolbar & for keyboard handlers
1127 HTMLArea
._addEvents((HTMLArea
.is_ie
? doc
.body
: doc
), ["keydown","keypress","mousedown","mouseup","drag"], HTMLArea
._editorEvent
, true);
1129 // add unload handler
1130 if (!HTMLArea
.hasUnloadHandler
) {
1131 HTMLArea
.hasUnloadHandler
= true;
1132 HTMLArea
._addEvent((this._iframe
.contentWindow
? this._iframe
.contentWindow
: this._iframe
.contentDocument
), "unload", HTMLArea
.removeEditorEvents
);
1135 // set enableWordClean and intercept paste, dragdrop and drop events for wordClean
1136 //if (this.config.enableWordClean) HTMLArea._addEvents((HTMLArea.is_ie ? doc.body : doc), ["paste","dragdrop","drop"], HTMLArea.wordClean, true);
1138 window
.setTimeout("HTMLArea.generatePlugins(" + this._editorNumber
+ ");", 100);
1141 HTMLArea
.generatePlugins = function(editorNumber
) {
1142 var editor
= RTEarea
[editorNumber
]["editor"];
1143 // check if any plugins have registered generate handlers
1144 // check also if any plugin has a onKeyPress handler
1145 editor
._hasPluginWithOnKeyPressHandler
= false;
1146 for (var pluginId
in editor
.plugins
) {
1147 if (editor
.plugins
.hasOwnProperty(pluginId
)) {
1148 var pluginInstance
= editor
.plugins
[pluginId
].instance
;
1149 if (typeof(pluginInstance
.onGenerate
) === "function") {
1150 pluginInstance
.onGenerate();
1152 if (typeof(pluginInstance
.onGenerateOnce
) === "function") {
1153 pluginInstance
.onGenerateOnce();
1154 pluginInstance
.onGenerateOnce
= null;
1156 if (typeof(pluginInstance
.onKeyPress
) === "function") {
1157 editor
._hasPluginWithOnKeyPressHandler
= true;
1161 if (typeof(editor
.onGenerate
) === "function") {
1162 editor
.onGenerate();
1163 editor
.onGenerate
= null;
1165 HTMLArea
._appendToLog("[HTMLArea::initIframe]: All plugins successfully generated.");
1166 editor
.focusEditor();
1167 editor
.updateToolbar();
1171 * When we have a form, on reset, re-initialize the HTMLArea content and update the toolbar
1173 HTMLArea
.resetHandler = function(ev
) {
1174 if(!ev
) var ev
= window
.event
;
1175 var form
= (ev
.target
) ? ev
.target
: ev
.srcElement
;
1176 var editor
= RTEarea
[form
._editorNumber
]["editor"];
1177 editor
.setHTML(editor
._textArea
.value
);
1178 editor
.updateToolbar();
1179 var a
= form
.__msh_prevOnReset
;
1180 // call previous reset methods if they were there.
1181 if (typeof(a
) != "undefined") {
1182 for (var i
=a
.length
; --i
>= 0; ) { a
[i
](); }
1187 * Clean up event handlers and object references, undo/redo snapshots, update the textarea for submission
1189 HTMLArea
.removeEditorEvents = function(ev
) {
1190 if (!ev
) var ev
= window
.event
;
1191 HTMLArea
._stopEvent(ev
);
1192 if (HTMLArea
._eventCache
) {
1193 HTMLArea
._eventCache
.flush();
1195 for (var editorNumber
= RTEarea
.length
; --editorNumber
> 0 ;) {
1196 var editor
= RTEarea
[editorNumber
].editor
;
1198 RTEarea
[editorNumber
].editor
= null;
1199 // save the HTML content into the original textarea for submit, back/forward, etc.
1200 editor
._textArea
.value
= editor
.getHTML();
1201 // release undo/redo snapshots
1202 window
.clearInterval(editor
._timerUndo
);
1203 editor
._undoQueue
= null;
1205 HTMLArea
.cleanup(editor
);
1211 * Clean up a bunch of references in order to avoid memory leakages mainly in IE, but also in Firefox and Opera
1213 HTMLArea
.cleanup = function (editor
) {
1214 // nullify envent handlers
1215 for (var handler
in editor
.eventHandlers
) {
1216 if (editor
.eventHandlers
.hasOwnProperty(handler
)) {
1217 editor
.eventHandlers
[handler
] = null;
1220 for (var button
in editor
.btnList
) {
1221 if (editor
.btnList
.hasOwnProperty(button
)) {
1222 editor
.btnList
[button
][3] = null;
1225 for (var dropdown
in editor
.config
.customSelects
) {
1226 if (editor
.config
.customSelects
.hasOwnProperty(dropdown
)) {
1227 editor
.config
.customSelects
[dropdown
].action
= null;
1228 editor
.config
.customSelects
[dropdown
].refresh
= null;
1231 for (var hotKey
in editor
.config
.hotKeyList
) {
1232 if (editor
.config
.customSelects
.hasOwnProperty(hotKey
)) {
1233 editor
.config
.hotKeyList
[hotKey
].action
= null;
1236 editor
.onGenerate
= null;
1237 HTMLArea
._editorEvent
= null;
1238 if(editor
._textArea
.form
) {
1239 editor
._textArea
.form
.__msh_prevOnReset
= null;
1240 editor
._textArea
.form
._editorNumber
= null;
1242 HTMLArea
.onload
= null;
1244 // cleaning plugin handlers
1245 for (var plugin
in editor
.plugins
) {
1246 if (editor
.plugins
.hasOwnProperty(plugin
)) {
1247 var pluginInstance
= editor
.plugins
[plugin
].instance
;
1248 pluginInstance
.onChange
= null;
1249 pluginInstance
.onButtonPress
= null;
1250 pluginInstance
.onGenerate
= null;
1251 pluginInstance
.onGenerateOnce
= null;
1252 pluginInstance
.onMode
= null;
1253 pluginInstance
.onHotKey
= null;
1254 pluginInstance
.onKeyPress
= null;
1255 pluginInstance
.onSelect
= null;
1256 pluginInstance
.onUpdateTolbar
= null;
1259 // cleaning the toolbar elements
1260 for (var txt
in editor
._toolbarObjects
) {
1261 if (editor
._toolbarObjects
.hasOwnProperty(txt
)) {
1262 var obj
= editor
._toolbarObjects
[txt
];
1265 document
.getElementById(obj
.elementId
)._obj
= null;
1266 editor
._toolbarObjects
[txt
] = null;
1270 // cleaning the statusbar elements
1271 if (editor
._statusBarTree
.hasChildNodes()) {
1272 for (var i
= editor
._statusBarTree
.firstChild
; i
; i
= i
.nextSibling
) {
1273 if (i
.nodeName
.toLowerCase() == "a") {
1274 HTMLArea
._removeEvents(i
, ["click", "contextmenu"], HTMLArea
.statusBarHandler
);
1281 editor
._toolbar
= null;
1282 editor
._statusBar
= null;
1283 editor
._statusBarTree
= null;
1284 editor
._htmlArea
= null;
1285 editor
._iframe
= null;
1289 * Switch editor mode; parameter can be "textmode" or "wysiwyg".
1290 * If no parameter was passed, toggle between modes.
1292 HTMLArea
.prototype.setMode = function(mode
) {
1293 if (typeof(mode
) == "undefined") var mode
= (this._editMode
== "textmode") ? "wysiwyg" : "textmode";
1296 case "docnotwellformedmode":
1297 this._textArea
.value
= this.getHTML();
1298 this._iframe
.style
.display
= "none";
1299 this._textArea
.style
.display
= "block";
1300 if(this.config
.statusBar
) {
1301 var statusBarTextMode
= document
.createElement("span");
1302 statusBarTextMode
.className
= "statusBarTextMode";
1303 statusBarTextMode
.appendChild(document
.createTextNode(HTMLArea
.I18N
.msg
["TEXT_MODE"]));
1304 this._statusBar
.innerHTML
= '';
1305 this._statusBar
.appendChild(statusBarTextMode
);
1307 this._editMode
= "textmode";
1310 if(HTMLArea
.is_gecko
&& !HTMLArea
.is_safari
&& !HTMLArea
.is_opera
) this._doc
.designMode
= "off";
1312 if(!this.config
.fullPage
) this._doc
.body
.innerHTML
= this.getHTML();
1313 else this.setFullHTML(this.getHTML());
1315 alert(HTMLArea
.I18N
.msg
["HTML-document-not-well-formed"]);
1318 this._textArea
.style
.display
= "none";
1319 this._iframe
.style
.display
= "block";
1320 if (HTMLArea
.is_gecko
&& !HTMLArea
.is_safari
&& !HTMLArea
.is_opera
) this._doc
.designMode
= "on";
1321 if(this.config
.statusBar
) {
1322 this._statusBar
.innerHTML
= "";
1323 this._statusBar
.appendChild(this._statusBarTree
);
1325 this._editMode
= "wysiwyg";
1326 //set gecko options (if we can... raises exception in Firefox 3)
1327 if (HTMLArea
.is_gecko
) {
1329 if (this._doc
.queryCommandEnabled("insertbronreturn")) this._doc
.execCommand("insertbronreturn", false, this.config
.disableEnterParagraphs
);
1330 if (this._doc
.queryCommandEnabled("enableObjectResizing")) this._doc
.execCommand("enableObjectResizing", false, !this.config
.disableObjectResizing
);
1331 if (this._doc
.queryCommandEnabled("enableInlineTableEditing")) this._doc
.execCommand("enableInlineTableEditing", false, (this.config
.buttons
.table
&& this.config
.buttons
.table
.enableHandles
) ? true : false);
1332 if (this._doc
.queryCommandEnabled("styleWithCSS")) this._doc
.execCommand("styleWithCSS", false, this.config
.useCSS
);
1333 else if (this._doc
.queryCommandEnabled("useCSS")) this._doc
.execCommand("useCSS", false, !this.config
.useCSS
);
1340 if (mode
!== "docnotwellformedmode") this.focusEditor();
1341 for (var pluginId
in this.plugins
) {
1342 if (this.plugins
.hasOwnProperty(pluginId
)) {
1343 var pluginInstance
= this.plugins
[pluginId
].instance
;
1344 if (typeof(pluginInstance
.onMode
) === "function") {
1345 pluginInstance
.onMode(mode
);
1354 HTMLArea
.prototype.getMode = function() {
1355 return this._editMode
;
1359 * Initialize iframe content when in full page mode
1361 HTMLArea
.prototype.setFullHTML = function(html
) {
1362 var save_multiline
= RegExp
.multiline
;
1363 RegExp
.multiline
= true;
1364 if(html
.match(HTMLArea
.RE_doctype
)) {
1365 this.setDoctype(RegExp
.$1);
1366 html
= html
.replace(HTMLArea
.RE_doctype
, "");
1368 RegExp
.multiline
= save_multiline
;
1369 if(!HTMLArea
.is_ie
) {
1370 if(html
.match(HTMLArea
.RE_head
)) this._doc
.getElementsByTagName("head")[0].innerHTML
= RegExp
.$1;
1371 if(html
.match(HTMLArea
.RE_body
)) this._doc
.getElementsByTagName("body")[0].innerHTML
= RegExp
.$1;
1373 var html_re
= /<html>((.|\n)*?)<\/html>/i;
1374 html
= html
.replace(html_re
, "$1");
1376 this._doc
.write(html
);
1378 this._doc
.body
.contentEditable
= true;
1383 /***************************************************
1384 * PLUGINS, STYLESHEETS, AND IMAGE AND POPUP URL'S
1385 ***************************************************/
1388 * Instantiate the specified plugin and register it with the editor
1390 * @param string plugin: the name of the plugin
1392 * @return boolean true if the plugin was successfully registered
1394 HTMLArea
.prototype.registerPlugin = function(plugin
) {
1395 var pluginName
= plugin
;
1396 if (typeof(plugin
) === "string") {
1398 var plugin
= eval(plugin
);
1400 HTMLArea
._appendToLog("ERROR [HTMLArea::registerPlugin]: Cannot register invalid plugin: " + e
);
1404 if (typeof(plugin
) !== "function") {
1405 HTMLArea
._appendToLog("ERROR [HTMLArea::registerPlugin]: Cannot register undefined plugin.");
1408 var pluginInstance
= new plugin(this, pluginName
);
1409 if (pluginInstance
) {
1410 var pluginInformation
= plugin
._pluginInfo
;
1411 if(!pluginInformation
) {
1412 pluginInformation
= pluginInstance
.getPluginInformation();
1414 pluginInformation
.instance
= pluginInstance
;
1415 this.plugins
[pluginName
] = pluginInformation
;
1416 HTMLArea
._appendToLog("[HTMLArea::registerPlugin]: Plugin " + pluginName
+ " was successfully registered.");
1419 HTMLArea
._appendToLog("ERROR [HTMLArea::registerPlugin]: Can't register plugin " + pluginName
+ ".");
1425 * Load the required plugin script and, unless not requested, the language file
1427 HTMLArea
.loadPlugin = function(pluginName
,noLangFile
,url
) {
1428 if (typeof(url
) == "undefined") {
1429 var dir
= _editor_url
+ "plugins/" + pluginName
;
1430 var plugin
= pluginName
.replace(/([a-z])([A-Z])([a-z])/g, "$1" + "-" + "$2" + "$3").toLowerCase() + ".js";
1431 var plugin_file
= dir
+ "/" + plugin
;
1432 HTMLArea
.loadScript(plugin_file
);
1433 if (typeof(noLangFile
) == "undefined" || !noLangFile
) {
1434 var plugin_lang
= dir
+ "/lang/" + _editor_lang
+ ".js";
1435 HTMLArea
._scripts
.push(plugin_lang
);
1438 HTMLArea
.loadScript(url
);
1443 * Load a stylesheet file
1445 HTMLArea
.loadStyle = function(style
, plugin
, url
) {
1446 if (typeof(url
) == "undefined") {
1447 var url
= _editor_url
|| '';
1448 if (typeof(plugin
) != "undefined") { url
+= "plugins/" + plugin
+ "/"; }
1450 if (/^\//.test(style
)) { url
= style
; }
1452 var head
= document
.getElementsByTagName("head")[0];
1453 var link
= document
.createElement("link");
1454 link
.rel
= "stylesheet";
1456 head
.appendChild(link
);
1460 * Get the url of some image
1462 HTMLArea
.prototype.imgURL = function(file
, plugin
) {
1463 if (typeof(plugin
) == "undefined") return _editor_skin
+ this.config
.imgURL
+ file
;
1464 else return _editor_skin
+ this.config
.imgURL
+ plugin
+ "/" + file
;
1468 * Get the url of some popup
1470 HTMLArea
.prototype.popupURL = function(file
) {
1472 if(file
.match(/^plugin:\/\/(.*?)\/(.*)/)) {
1473 var plugin
= RegExp
.$1;
1474 var popup
= RegExp
.$2;
1475 if(!/\.html$/.test(popup
)) popup
+= ".html";
1476 url
= _editor_url
+ "plugins/" + plugin
+ "/popups/" + popup
;
1478 url
= _typo3_host_url
+ _editor_url
+ this.config
.popupURL
+ file
;
1483 /***************************************************
1485 ***************************************************/
1486 HTMLArea
.getInnerText = function(el
) {
1489 for(i
=el
.firstChild
;i
;i
=i
.nextSibling
) {
1490 if(i
.nodeType
== 3) txt
+= i
.data
;
1491 else if(i
.nodeType
== 1) txt
+= HTMLArea
.getInnerText(i
);
1494 if(el
.nodeType
== 3) txt
= el
.data
;
1499 HTMLArea
.prototype.forceRedraw = function() {
1500 this._doc
.body
.style
.visibility
= "hidden";
1501 this._doc
.body
.style
.visibility
= "visible";
1505 * Focus the editor iframe document or the textarea.
1507 HTMLArea
.prototype.focusEditor = function() {
1508 switch (this._editMode
) {
1511 if (HTMLArea
.is_safari
) {
1512 this._iframe
.focus();
1513 } else if (HTMLArea
.is_opera
) {
1516 this._iframe
.contentWindow
.focus();
1521 this._textArea
.focus();
1527 HTMLArea
.undoTakeSnapshot = function(editorNumber
) {
1528 var editor
= RTEarea
[editorNumber
].editor
;
1530 editor
._undoTakeSnapshot();
1535 * Take a snapshot of the current contents for undo
1537 HTMLArea
.prototype._undoTakeSnapshot = function () {
1538 var currentTime
= (new Date()).getTime();
1539 var newSnapshot
= false, bookmark
= null, bookmarkedText
= null;
1540 if (this._undoPos
>= this.config
.undoSteps
) {
1541 // Remove the first element
1542 this._undoQueue
.shift();
1545 // New undo slot should be used if this is first undoTakeSnapshot call or if undoTimeout is elapsed
1546 if (this._undoPos
< 0 || this._undoQueue
[this._undoPos
].time
< currentTime
- this.config
.undoTimeout
) {
1550 // Insert a bookmark
1551 if (this.getMode() === "wysiwyg") {
1552 var selection
= this._getSelection();
1553 var bookmark
= (!(HTMLArea
.is_ie
&& selection
.type
.toLowerCase() == "control") && !HTMLArea
.is_opera
) ? this.getBookmark(this._createRange(selection
)) : null;
1555 // Get the bookmarked html text and remove the bookmark
1557 bookmarkedText
= this.getInnerHTML();
1558 var range
= this.moveToBookmark(bookmark
);
1560 // Get the html text
1561 var txt
= this.getInnerHTML();
1564 // If previous slot contains the same text, a new one should not be used
1565 if (this._undoPos
== 0 || this._undoQueue
[this._undoPos
- 1].text
!= txt
) {
1566 this._undoQueue
[this._undoPos
] = {
1570 bookmarkedText
: bookmarkedText
1572 this._undoQueue
.length
= this._undoPos
+ 1;
1573 if (this._undoPos
== 1) {
1574 this.updateToolbar();
1580 if (this._undoQueue
[this._undoPos
].text
!= txt
){
1581 this._undoQueue
[this._undoPos
].text
= txt
;
1582 this._undoQueue
[this._undoPos
].bookmark
= bookmark
;
1583 this._undoQueue
[this._undoPos
].bookmarkedText
= bookmarkedText
;
1584 this._undoQueue
.length
= this._undoPos
+ 1;
1589 HTMLArea
.prototype.undo = function () {
1590 if (this._undoPos
> 0) {
1591 // Make sure we would not loose any changes
1592 this._undoTakeSnapshot();
1593 var bookmark
= this._undoQueue
[--this._undoPos
].bookmark
;
1594 if (bookmark
&& this._undoPos
) {
1595 this.setHTML(this._undoQueue
[this._undoPos
].bookmarkedText
);
1597 this.selectRange(this.moveToBookmark(bookmark
));
1598 this.scrollToCaret();
1600 this.setHTML(this._undoQueue
[this._undoPos
].text
);
1605 HTMLArea
.prototype.redo = function () {
1606 if (this._undoPos
< this._undoQueue
.length
- 1) {
1607 // Make sure we would not loose any changes
1608 this._undoTakeSnapshot();
1609 // Previous call could make undo queue shorter
1610 if (this._undoPos
< this._undoQueue
.length
- 1) {
1611 var bookmark
= this._undoQueue
[++this._undoPos
].bookmark
;
1613 this.setHTML(this._undoQueue
[this._undoPos
].bookmarkedText
);
1615 this.selectRange(this.moveToBookmark(bookmark
));
1616 this.scrollToCaret();
1618 this.setHTML(this._undoQueue
[this._undoPos
].text
);
1625 * Update the enabled/disabled/active state of the toolbar elements
1627 HTMLArea
.updateToolbar = function(editorNumber
) {
1628 var editor
= RTEarea
[editorNumber
]["editor"];
1629 editor
.updateToolbar();
1630 editor
._timerToolbar
= null;
1633 HTMLArea
.prototype.updateToolbar = function(noStatus
) {
1634 var doc
= this._doc
,
1635 text
= (this._editMode
== "textmode"),
1637 ancestors
= null, cls
= new Array(),
1638 txt
, txtClass
, i
, inContext
, match
, matchAny
, k
, j
, n
, commandState
;
1640 selection
= !this._selectionEmpty(this._getSelection());
1641 ancestors
= this.getAllAncestors();
1642 if(this.config
.statusBar
&& !noStatus
) {
1643 // Unhook previous events handlers
1644 if(this._statusBarTree
.hasChildNodes()) {
1645 for (i
= this._statusBarTree
.firstChild
; i
; i
= i
.nextSibling
) {
1646 if(i
.nodeName
.toLowerCase() == "a") {
1647 HTMLArea
._removeEvents(i
,["click", "contextmenu, mousedown"], HTMLArea
.statusBarHandler
);
1653 this._statusBarTree
.selected
= null;
1654 this._statusBarTree
.innerHTML
= '';
1655 this._statusBarTree
.appendChild(document
.createTextNode(HTMLArea
.I18N
.msg
["Path"] + ": ")); // clear
1656 for (i
= ancestors
.length
; --i
>= 0;) {
1657 var el
= ancestors
[i
];
1659 var a
= document
.createElement("a");
1663 if (!HTMLArea
.is_opera
) {
1664 HTMLArea
._addEvents(a
, ["click", "contextmenu"], HTMLArea
.statusBarHandler
);
1666 HTMLArea
._addEvents(a
, ["mousedown", "click"], HTMLArea
.statusBarHandler
);
1668 txt
= el
.tagName
.toLowerCase();
1669 a
.title
= el
.style
.cssText
;
1670 if (el
.id
) { txt
+= "#" + el
.id
; }
1673 cls
= el
.className
.trim().split(" ");
1674 for (j
= 0; j
< cls
.length
; ++j
) {
1675 if (!HTMLArea
.reservedClassNames
.test(cls
[j
])) {
1676 txtClass
+= "." + cls
[j
];
1681 a
.appendChild(document
.createTextNode(txt
));
1682 this._statusBarTree
.appendChild(a
);
1683 if (i
!= 0) this._statusBarTree
.appendChild(document
.createTextNode(String
.fromCharCode(0xbb)));
1687 for (var cmd
in this._toolbarObjects
) {
1688 if (this._toolbarObjects
.hasOwnProperty(cmd
)) {
1689 var btn
= this._toolbarObjects
[cmd
];
1691 // Determine if the button should be enabled
1693 if (btn
.context
&& !text
) {
1697 if (/(.*)\[(.*?)\]/.test(btn
.context
)) {
1698 contexts
= RegExp
.$1.split(",");
1699 attrs
= RegExp
.$2.split(",");
1701 contexts
= btn
.context
.split(",");
1703 for (j
= contexts
.length
; --j
>= 0;) contexts
[j
] = contexts
[j
].toLowerCase();
1704 matchAny
= (contexts
[0] == "*");
1705 for (k
= 0; k
< ancestors
.length
; ++k
) {
1706 if (!ancestors
[k
]) continue;
1708 for (j
= contexts
.length
; --j
>= 0;) match
= match
|| (ancestors
[k
].tagName
.toLowerCase() == contexts
[j
]);
1709 if (matchAny
|| match
) {
1711 for (j
= attrs
.length
; --j
>= 0;) {
1712 if (!eval("ancestors[k]." + attrs
[j
])) {
1717 if (inContext
) break;
1722 if (cmd
== "CreateLink") {
1723 btn
.state("enabled", (!text
|| btn
.text
) && (inContext
|| selection
));
1725 btn
.state("enabled", (!text
|| btn
.text
) && inContext
&& (selection
|| !btn
.selection
));
1727 if (typeof(cmd
) == "function") { continue; };
1728 // look-it-up in the custom dropdown boxes
1729 var dropdown
= this.config
.customSelects
[cmd
];
1730 if ((!text
|| btn
.text
) && (typeof(dropdown
) !== "undefined") && (typeof(dropdown
.refresh
) === "function")) {
1731 dropdown
.refresh(this, cmd
);
1735 case "TextIndicator":
1737 try {with (document
.getElementById(btn
.elementId
).style
) {
1738 backgroundColor
= HTMLArea
._makeColor(doc
.queryCommandValue((HTMLArea
.is_ie
|| HTMLArea
.is_safari
) ? "BackColor" : "HiliteColor"));
1740 if(/transparent/i.test(backgroundColor
)) { backgroundColor
= HTMLArea
._makeColor(doc
.queryCommandValue("BackColor")); }
1741 color
= HTMLArea
._makeColor(doc
.queryCommandValue("ForeColor"));
1742 fontFamily
= doc
.queryCommandValue("FontName");
1743 // Check if queryCommandState is available
1744 fontWeight
= "normal";
1745 fontStyle
= "normal";
1746 try { fontWeight
= doc
.queryCommandState("Bold") ? "bold" : "normal"; } catch(ex
) { fontWeight
= "normal"; };
1747 try { fontStyle
= doc
.queryCommandState("Italic") ? "italic" : "normal"; } catch(ex
) { fontStyle
= "normal"; };
1749 // alert(e + "\n\n" + cmd);
1754 btn
.state("active", text
);
1759 btn
.state("enabled", doc
.queryCommandEnabled('Paste'));
1761 btn
.state("enabled", false);
1766 btn
.state("enabled", !text
&& (!this._customUndo
|| this._undoPos
> 0));
1769 btn
.state("enabled", !text
&& (!this._customUndo
|| this._undoPos
< this._undoQueue
.length
-1));
1777 if (this._customUndo
) {
1778 this._undoTakeSnapshot();
1780 for (var pluginId
in this.plugins
) {
1781 if (this.plugins
.hasOwnProperty(pluginId
)) {
1782 var pluginInstance
= this.plugins
[pluginId
].instance
;
1783 if (typeof(pluginInstance
.onUpdateToolbar
) === "function") {
1784 pluginInstance
.onUpdateToolbar();
1790 /***************************************************
1791 * DOM TREE MANIPULATION
1792 ***************************************************/
1795 * Surround the currently selected HTML source code with the given tags.
1796 * Delete the selection, if any.
1798 HTMLArea
.prototype.surroundHTML = function(startTag
,endTag
) {
1799 this.insertHTML(startTag
+ this.getSelectedHTML().replace(HTMLArea
.Reg_body
, "") + endTag
);
1803 * Change the tag name of a node.
1805 HTMLArea
.prototype.convertNode = function(el
,newTagName
) {
1806 var newel
= this._doc
.createElement(newTagName
), p
= el
.parentNode
;
1807 while (el
.firstChild
) newel
.appendChild(el
.firstChild
);
1808 p
.insertBefore(newel
, el
);
1814 * Find a parent of an element with a specified tag
1816 HTMLArea
.getElementObject = function(el
,tagName
) {
1818 while (oEl
!= null && oEl
.nodeName
.toLowerCase() != tagName
) oEl
= oEl
.parentNode
;
1822 /***************************************************
1823 * SELECTIONS AND RANGES
1824 ***************************************************/
1827 * Return true if we have some selected content
1829 HTMLArea
.prototype.hasSelectedText = function() {
1830 return this.getSelectedHTML() != "";
1834 * Get an array with all the ancestor nodes of the selection.
1836 HTMLArea
.prototype.getAllAncestors = function() {
1837 var p
= this.getParentElement();
1839 while (p
&& (p
.nodeType
=== 1) && (p
.nodeName
.toLowerCase() !== "body")) {
1843 a
.push(this._doc
.body
);
1848 * Get the block elements containing the start and the end points of the selection
1850 HTMLArea
.prototype.getEndBlocks = function(selection
) {
1851 var range
= this._createRange(selection
);
1852 if (HTMLArea
.is_gecko
) {
1853 var parentStart
= range
.startContainer
;
1854 var parentEnd
= range
.endContainer
;
1856 if (selection
.type
!== "Control" ) {
1857 var rangeEnd
= range
.duplicate();
1858 range
.collapse(true);
1859 var parentStart
= range
.parentElement();
1860 rangeEnd
.collapse(false);
1861 var parentEnd
= rangeEnd
.parentElement();
1863 var parentStart
= range
.item(0);
1864 var parentEnd
= parentStart
;
1867 while (parentStart
&& !HTMLArea
.isBlockElement(parentStart
)) {
1868 parentStart
= parentStart
.parentNode
;
1870 while (parentEnd
&& !HTMLArea
.isBlockElement(parentEnd
)) {
1871 parentEnd
= parentEnd
.parentNode
;
1873 return { start
: parentStart
,
1879 * Get the deepest ancestor of the selection that is of the specified type
1880 * Borrowed from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
1882 HTMLArea
.prototype._getFirstAncestor = function(sel
,types
) {
1883 var prnt
= this._activeElement(sel
);
1886 prnt
= (HTMLArea
.is_ie
? this._createRange(sel
).parentElement() : this._createRange(sel
).commonAncestorContainer
);
1891 if (typeof(types
) == 'string') types
= [types
];
1894 if (prnt
.nodeType
== 1) {
1895 if (types
== null) return prnt
;
1896 for (var i
= 0; i
< types
.length
; i
++) {
1897 if(prnt
.tagName
.toLowerCase() == types
[i
]) return prnt
;
1899 if(prnt
.tagName
.toLowerCase() == 'body') break;
1900 if(prnt
.tagName
.toLowerCase() == 'table') break;
1902 prnt
= prnt
.parentNode
;
1907 /***************************************************
1908 * Category: EVENT HANDLERS
1909 ***************************************************/
1912 * Intercept some commands and replace them with our own implementation
1914 HTMLArea
.prototype.execCommand = function(cmdID
, UI
, param
) {
1922 if(this._customUndo
) this[cmdID
.toLowerCase()]();
1923 else this._doc
.execCommand(cmdID
,UI
,param
);
1929 this._doc
.execCommand(cmdID
, false, null);
1930 // In FF3, the paste operation will indeed trigger the paste event
1931 if (HTMLArea
.is_gecko
&& cmdID
== "Paste" && this._toolbarObjects
.CleanWord
&& navigator
.productSub
< 2008020514) {
1932 this._toolbarObjects
.CleanWord
.cmd(this, "CleanWord");
1935 if (HTMLArea
.is_gecko
&& !HTMLArea
.is_safari
&& !HTMLArea
.is_opera
) {
1936 this._mozillaPasteException(cmdID
, UI
, param
);
1942 this._doc
.execCommand(cmdID
, UI
, param
);
1944 if (this.config
.debug
) alert(e
+ "\n\nby execCommand(" + cmdID
+ ");");
1947 this.updateToolbar();
1952 * A generic event handler for things that happen in the IFRAME's document.
1954 HTMLArea
._editorEvent = function(ev
) {
1955 if(!ev
) var ev
= window
.event
;
1956 var target
= (ev
.target
) ? ev
.target
: ev
.srcElement
;
1957 var owner
= (target
.ownerDocument
) ? target
.ownerDocument
: target
;
1958 if(HTMLArea
.is_ie
) { // IE5.5 does not report any ownerDocument
1959 while (owner
.parentElement
) { owner
= owner
.parentElement
; }
1961 var editor
= RTEarea
[owner
._editorNo
]["editor"];
1962 var keyEvent
= (HTMLArea
.is_ie
&& ev
.type
== "keydown") || (HTMLArea
.is_gecko
&& ev
.type
== "keypress");
1963 editor
.focusEditor();
1966 if(editor
._hasPluginWithOnKeyPressHandler
) {
1967 for (var pluginId
in editor
.plugins
) {
1968 if (editor
.plugins
.hasOwnProperty(pluginId
)) {
1969 var pluginInstance
= editor
.plugins
[pluginId
].instance
;
1970 if (typeof(pluginInstance
.onKeyPress
) === "function") {
1971 if (!pluginInstance
.onKeyPress(ev
)) {
1972 HTMLArea
._stopEvent(ev
);
1979 if(ev
.ctrlKey
&& !ev
.shiftKey
) {
1981 // execute hotkey command
1982 var key
= String
.fromCharCode((HTMLArea
.is_ie
|| HTMLArea
.is_safari
|| HTMLArea
.is_opera
) ? ev
.keyCode
: ev
.charCode
).toLowerCase();
1983 if (HTMLArea
.is_gecko
&& ev
.keyCode
== 32) key
= String
.fromCharCode(ev
.keyCode
).toLowerCase();
1985 editor
.insertHTML(" ");
1986 editor
.updateToolbar();
1987 HTMLArea
._stopEvent(ev
);
1990 if (!editor
.config
.hotKeyList
[key
]) return false;
1991 var cmd
= editor
.config
.hotKeyList
[key
].cmd
;
1992 if (!cmd
) return false;
1997 cmd
= editor
.config
.hotKeyList
[key
].cmd
;
1998 editor
.execCommand(cmd
, false, null);
1999 HTMLArea
._stopEvent(ev
);
2003 if (HTMLArea
.is_ie
|| HTMLArea
.is_safari
) {
2004 cmd
= editor
.config
.hotKeyList
[key
].cmd
;
2005 editor
.execCommand(cmd
, false, null);
2006 HTMLArea
._stopEvent(ev
);
2008 // In FF3, the paste operation will indeed trigger the paste event
2009 } else if (HTMLArea
.is_opera
|| (HTMLArea
.is_gecko
&& navigator
.productSub
< 2008020514)) {
2010 if (editor
._toolbarObjects
.CleanWord
) {
2011 var cleanLaterFunctRef
= editor
.plugins
.DefaultClean
? editor
.plugins
.DefaultClean
.instance
.cleanLaterFunctRef
: (editor
.plugins
.TYPO3HtmlParser
? editor
.plugins
.TYPO3HtmlParser
.instance
.cleanLaterFunctRef
: null);
2012 if (cleanLaterFunctRef
) {
2013 window
.setTimeout(cleanLaterFunctRef
, 50);
2019 if (editor
.config
.hotKeyList
[key
] && editor
.config
.hotKeyList
[key
].action
) {
2020 if (!editor
.config
.hotKeyList
[key
].action(editor
, key
)) {
2021 HTMLArea
._stopEvent(ev
);
2027 } else if (ev
.altKey
) {
2028 // check if context menu is already handling this event
2029 if(editor
.plugins
["ContextMenu"] && editor
.plugins
["ContextMenu"].instance
) {
2030 var keys
= editor
.plugins
["ContextMenu"].instance
.keys
;
2031 if (keys
.length
> 0) {
2033 for (var i
= keys
.length
; --i
>= 0;) {
2035 if (k
[0].toLowerCase() == key
) {
2036 HTMLArea
._stopEvent(ev
);
2042 } else if (keyEvent
) {
2043 if (HTMLArea
.is_gecko
) editor
._detectURL(ev
);
2044 switch (ev
.keyCode
) {
2045 case 13 : // KEY enter
2046 if (HTMLArea
.is_gecko
) {
2047 if (!ev
.shiftKey
&& !editor
.config
.disableEnterParagraphs
) {
2048 if (editor
._checkInsertP()) {
2049 HTMLArea
._stopEvent(ev
);
2051 } else if (HTMLArea
.is_safari
) {
2052 var brNode
= document
.createElement("br");
2053 editor
.insertNodeAtSelection(brNode
);
2054 if (!brNode
.nextSibling
|| !HTMLArea
.getInnerText(brNode
.nextSibling
)) {
2055 var secondBrNode
= document
.createElement("br");
2056 secondBrNode
= brNode
.parentNode
.appendChild(secondBrNode
);
2057 editor
.selectNode(secondBrNode
, false);
2059 HTMLArea
._stopEvent(ev
);
2061 // update the toolbar state after some time
2062 if (editor
._timerToolbar
) window
.clearTimeout(editor
._timerToolbar
);
2063 editor
._timerToolbar
= window
.setTimeout("HTMLArea.updateToolbar(" + editor
._editorNumber
+ ");", 100);
2067 case 8 : // KEY backspace
2068 case 46 : // KEY delete
2069 if ((HTMLArea
.is_gecko
&& !ev
.shiftKey
) || HTMLArea
.is_ie
) {
2070 if (editor
._checkBackspace()) HTMLArea
._stopEvent(ev
);
2072 // update the toolbar state after some time
2073 if (editor
._timerToolbar
) window
.clearTimeout(editor
._timerToolbar
);
2074 editor
._timerToolbar
= window
.setTimeout("HTMLArea.updateToolbar(" + editor
._editorNumber
+ ");", 50);
2076 case 9: // KEY horizontal tab
2077 var newkey
= (ev
.shiftKey
? "SHIFT-" : "") + "TAB";
2078 if (editor
.config
.hotKeyList
[newkey
] && editor
.config
.hotKeyList
[newkey
].action
) {
2079 if (!editor
.config
.hotKeyList
[newkey
].action(editor
, newkey
)) {
2080 HTMLArea
._stopEvent(ev
);
2085 case 37: // LEFT arrow key
2086 case 38: // UP arrow key
2087 case 39: // RIGHT arrow key
2088 case 40: // DOWN arrow key
2089 if (HTMLArea
.is_ie
) {
2090 if (editor
._timerToolbar
) window
.clearTimeout(editor
._timerToolbar
);
2091 editor
._timerToolbar
= window
.setTimeout("HTMLArea.updateToolbar(" + editor
._editorNumber
+ ");", 10);
2098 if (editor
._timerToolbar
) window
.clearTimeout(editor
._timerToolbar
);
2099 if (ev
.type
== "mouseup") editor
.updateToolbar();
2100 else editor
._timerToolbar
= window
.setTimeout("HTMLArea.updateToolbar(" + editor
._editorNumber
+ ");", 50);
2104 HTMLArea
.prototype.scrollToCaret = function() {
2105 if (HTMLArea
.is_gecko
) {
2106 var e
= this.getParentElement(),
2107 w
= this._iframe
.contentWindow
? this._iframe
.contentWindow
: window
,
2108 h
= w
.innerHeight
|| w
.height
,
2110 t
= d
.documentElement
.scrollTop
|| d
.body
.scrollTop
;
2111 if (e
.offsetTop
> h
+t
|| e
.offsetTop
< t
) {
2112 this.getParentElement().scrollIntoView();
2120 HTMLArea
.prototype.getHTML = function() {
2121 switch (this._editMode
) {
2123 return HTMLArea
.getHTML(this._doc
.body
, false, this);
2125 return this._textArea
.value
;
2133 HTMLArea
.prototype.getInnerHTML = function() {
2134 switch (this._editMode
) {
2136 return this._doc
.body
.innerHTML
;
2138 return this._textArea
.value
;
2144 * Replace the HTML inside
2146 HTMLArea
.prototype.setHTML = function(html
) {
2147 switch (this._editMode
) {
2149 this._doc
.body
.innerHTML
= html
;
2152 this._textArea
.value
= html
;
2159 * Set the given doctype when config.fullPage is true
2161 HTMLArea
.prototype.setDoctype = function(doctype
) {
2162 this.doctype
= doctype
;
2165 /***************************************************
2167 ***************************************************/
2169 // variable used to pass the object to the popup editor window.
2170 HTMLArea
._object
= null;
2173 * Check if the client agent is supported
2175 HTMLArea
.checkSupportedBrowser = function() {
2176 if(HTMLArea
.is_gecko
&& !HTMLArea
.is_safari
&& !HTMLArea
.is_opera
) {
2177 if(navigator
.productSub
< 20030210) return false;
2179 return HTMLArea
.is_gecko
|| HTMLArea
.is_ie
;
2182 /* EventCache Version 1.0
2183 * Copyright 2005 Mark Wubben
2184 * Adaptation by Stanislas Rolland
2185 * Provides a way for automatically removing events from nodes and thus preventing memory leakage.
2186 * See <http://novemberborn.net/javascript/event-cache> for more information.
2187 * This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>
2188 * Event Cache uses an anonymous function to create a hidden scope chain. This is to prevent scoping issues.
2190 HTMLArea
._eventCacheConstructor = function() {
2191 var listEvents
= [];
2194 listEvents
: listEvents
,
2196 add : function(node
, sEventName
, fHandler
) {
2197 listEvents
.push(arguments
);
2200 flush : function() {
2202 for (var i
= listEvents
.length
; --i
>= 0;) {
2203 item
= listEvents
[i
];
2205 HTMLArea
._removeEvent(item
[0], item
[1], item
[2]);
2206 item
[0][item
[1]] = null;
2211 listEvents
.length
= 0;
2219 HTMLArea
._addEvent = function(el
,evname
,func
,useCapture
) {
2220 if (typeof(useCapture
) == "undefined") var useCapture
= false;
2221 if (HTMLArea
.is_gecko
) {
2222 el
.addEventListener(evname
, func
, !HTMLArea
.is_opera
|| useCapture
);
2224 el
.attachEvent("on" + evname
, func
);
2226 HTMLArea
._eventCache
.add(el
, evname
, func
);
2230 * Register a list of events
2232 HTMLArea
._addEvents = function(el
,evs
,func
,useCapture
) {
2233 if (typeof(useCapture
) == "undefined") var useCapture
= false;
2234 for (var i
= evs
.length
; --i
>= 0;) {
2235 HTMLArea
._addEvent(el
,evs
[i
], func
, useCapture
);
2240 * Remove an event listener
2242 HTMLArea
._removeEvent = function(el
,evname
,func
) {
2243 if(HTMLArea
.is_gecko
) {
2244 try { el
.removeEventListener(evname
, func
, true); el
.removeEventListener(evname
, func
, false); } catch(e
) { }
2246 try { el
.detachEvent("on" + evname
, func
); } catch(e
) { }
2251 * Remove a list of events
2253 HTMLArea
._removeEvents = function(el
,evs
,func
) {
2254 for (var i
= evs
.length
; --i
>= 0;) { HTMLArea
._removeEvent(el
, evs
[i
], func
); }
2258 * Stop event propagation
2260 HTMLArea
._stopEvent = function(ev
) {
2261 if(HTMLArea
.is_gecko
) {
2262 ev
.stopPropagation();
2263 ev
.preventDefault();
2265 ev
.cancelBubble
= true;
2266 ev
.returnValue
= false;
2271 * Remove a class name from the class attribute
2273 HTMLArea
._removeClass = function(el
, removeClassName
) {
2274 if(!(el
&& el
.className
)) return;
2275 var cls
= el
.className
.trim().split(" ");
2276 var ar
= new Array();
2277 for (var i
= cls
.length
; i
> 0;) {
2278 if (cls
[--i
] != removeClassName
) ar
[ar
.length
] = cls
[i
];
2280 if (ar
.length
== 0) {
2281 if (!HTMLArea
.is_opera
) el
.removeAttribute(HTMLArea
.is_gecko
? "class" : "className");
2282 else el
.className
= '';
2284 } else el
.className
= ar
.join(" ");
2288 * Add a class name to the class attribute
2290 HTMLArea
._addClass = function(el
, addClassName
) {
2291 HTMLArea
._removeClass(el
, addClassName
);
2292 if (el
.className
&& HTMLArea
.classesXOR
) {
2293 var classNames
= el
.className
.trim().split(" ");
2294 for (var i
= classNames
.length
; --i
>= 0;) {
2295 if (HTMLArea
.classesXOR
[addClassName
] && HTMLArea
.classesXOR
[addClassName
].test(classNames
[i
])) {
2296 HTMLArea
._removeClass(el
, classNames
[i
]);
2300 if (el
.className
) el
.className
+= " " + addClassName
;
2301 else el
.className
= addClassName
;
2305 * Check if a class name is in the class attribute
2307 HTMLArea
._hasClass = function(el
, className
) {
2308 if (!el
|| !el
.className
) return false;
2309 var cls
= el
.className
.split(" ");
2310 for (var i
= cls
.length
; i
> 0;) {
2311 if(cls
[--i
] == className
) return true;
2317 * Select a value in a select element
2319 * @param object select: the select object
2320 * @param string value: the value
2323 HTMLArea
.selectValue = function(select
, value
) {
2324 var options
= select
.getElementsByTagName("option");
2325 for (var i
= options
.length
; --i
>= 0;) {
2326 var option
= options
[i
];
2327 option
.selected
= (option
.value
== value
);
2328 select
.selectedIndex
= i
;
2332 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)$/;
2333 HTMLArea
.isBlockElement = function(el
) { return el
&& el
.nodeType
== 1 && HTMLArea
.RE_blockTags
.test(el
.nodeName
.toLowerCase()); };
2334 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)$/;
2335 HTMLArea
.RE_noClosingTag
= /^(img|br|hr|col|input|area|base|link|meta|param)$/;
2336 HTMLArea
.needsClosingTag = function(el
) { return el
&& el
.nodeType
== 1 && !HTMLArea
.RE_noClosingTag
.test(el
.tagName
.toLowerCase()); };
2339 * Perform HTML encoding of some given string
2340 * Borrowed in part from Xinha (is not htmlArea) - http://xinha.gogo.co.nz/
2342 HTMLArea
.htmlDecode = function(str
) {
2343 str
= str
.replace(/</g, "<").replace(/>/g, ">");
2344 str
= str
.replace(/ /g, "\xA0"); // Decimal 160, non-breaking-space
2345 str
= str
.replace(/"/g, "\x22");
2346 str
= str
.replace(/'/g, "'") ;
2347 str
= str
.replace(/&/g, "&");
2350 HTMLArea
.htmlEncode = function(str
) {
2351 if (typeof(str
) != 'string') str
= str
.toString(); // we don't need regexp for that, but.. so be it for now.
2352 str
= str
.replace(/&/g
, "&");
2353 str
= str
.replace(/</g, "<").replace(/>/g
, ">");
2354 str
= str
.replace(/\xA0/g, " "); // Decimal 160, non-breaking-space
2355 str
= str
.replace(/\x22/g, """); // \x22 means '"'
2360 * Retrieve the HTML code from the given node.
2361 * This is a replacement for getting innerHTML, using standard DOM calls.
2362 * Wrapper catches a Mozilla-Exception with non well-formed html source code.
2364 HTMLArea
.getHTML = function(root
, outputRoot
, editor
){
2366 return HTMLArea
.getHTMLWrapper(root
,outputRoot
,editor
);
2368 HTMLArea
._appendToLog("The HTML document is not well-formed.");
2369 if(!HTMLArea
._debugMode
) alert(HTMLArea
.I18N
.msg
["HTML-document-not-well-formed"]);
2370 else return HTMLArea
.getHTMLWrapper(root
,outputRoot
,editor
);
2371 return editor
._doc
.body
.innerHTML
;
2375 HTMLArea
.getHTMLWrapper = function(root
, outputRoot
, editor
) {
2377 if(!root
) return html
;
2378 switch (root
.nodeType
) {
2379 case 1: // ELEMENT_NODE
2380 case 11: // DOCUMENT_FRAGMENT_NODE
2381 case 9: // DOCUMENT_NODE
2382 var closed
, i
, config
= editor
.config
;
2383 var root_tag
= (root
.nodeType
== 1) ? root
.tagName
.toLowerCase() : '';
2384 if (root_tag
== "br" && config
.removeTrailingBR
&& !root
.nextSibling
&& HTMLArea
.isBlockElement(root
.parentNode
) && (!root
.previousSibling
|| root
.previousSibling
.nodeName
.toLowerCase() != "br")) {
2385 if (!root
.previousSibling
&& root
.parentNode
&& root
.parentNode
.nodeName
.toLowerCase() == "p" && root
.parentNode
.className
) html
+= " ";
2388 if (config
.htmlRemoveTagsAndContents
&& config
.htmlRemoveTagsAndContents
.test(root_tag
)) break;
2389 var custom_tag
= (config
.customTags
&& config
.customTags
.test(root_tag
));
2390 if (outputRoot
) outputRoot
= !(config
.htmlRemoveTags
&& config
.htmlRemoveTags
.test(root_tag
));
2391 if ((HTMLArea
.is_ie
|| HTMLArea
.is_safari
) && root_tag
== "head") {
2392 if(outputRoot
) html
+= "<head>";
2393 var save_multiline
= RegExp
.multiline
;
2394 RegExp
.multiline
= true;
2395 var txt
= root
.innerHTML
.replace(HTMLArea
.RE_tagName
, function(str
, p1
, p2
) {
2396 return p1
+ p2
.toLowerCase();
2398 RegExp
.multiline
= save_multiline
;
2400 if(outputRoot
) html
+= "</head>";
2402 } else if (outputRoot
) {
2403 if (HTMLArea
.is_gecko
&& root
.hasAttribute('_moz_editor_bogus_node')) break;
2404 closed
= (!(root
.hasChildNodes() || HTMLArea
.needsClosingTag(root
) || custom_tag
));
2405 html
= "<" + root_tag
;
2406 var a
, name
, value
, attrs
= root
.attributes
;
2407 var n
= attrs
.length
;
2408 for (i
= attrs
.length
; --i
>= 0 ;) {
2410 name
= a
.nodeName
.toLowerCase();
2411 if ((!a
.specified
&& name
!= 'value') || /_moz|contenteditable|_msh/.test(name
)) continue;
2412 if (!HTMLArea
.is_ie
|| name
!= "style") {
2413 // IE5.5 reports wrong values. For this reason we extract the values directly from the root node.
2414 // Using Gecko the values of href and src are converted to absolute links unless we get them using nodeValue()
2415 if (typeof(root
[a
.nodeName
]) != "undefined" && name
!= "href" && name
!= "src" && name
!= "style" && !/^on/.test(name
)) {
2416 value
= root
[a
.nodeName
];
2418 value
= a
.nodeValue
;
2419 if (HTMLArea
.is_ie
&& (name
== "href" || name
== "src") && editor
.plugins
.link
&& editor
.plugins
.link
.instance
&& editor
.plugins
.link
.instance
.stripBaseURL
) {
2420 value
= editor
.plugins
.link
.instance
.stripBaseURL(value
);
2423 } else { // IE fails to put style in attributes list.
2424 value
= root
.style
.cssText
;
2426 // Mozilla reports some special values; we don't need them.
2427 if(/(_moz|^$)/.test(value
)) continue;
2428 // Strip value="0" reported by IE on all li tags
2429 if(HTMLArea
.is_ie
&& root_tag
== "li" && name
== "value" && a
.nodeValue
== 0) continue;
2430 html
+= " " + name
+ '="' + HTMLArea
.htmlEncode(value
) + '"';
2432 if (html
!= "") html
+= closed
? " />" : ">";
2434 for (i
= root
.firstChild
; i
; i
= i
.nextSibling
) {
2435 if (/^li$/i.test(i
.tagName
) && !/^[ou]l$/i.test(root
.tagName
)) html
+= "<ul>" + HTMLArea
.getHTMLWrapper(i
, true, editor
) + "</ul>";
2436 else html
+= HTMLArea
.getHTMLWrapper(i
, true, editor
);
2438 if (outputRoot
&& !closed
) html
+= "</" + root_tag
+ ">";
2440 case 3: // TEXT_NODE
2441 html
= /^(script|style)$/i.test(root
.parentNode
.tagName
) ? root
.data
: HTMLArea
.htmlEncode(root
.data
);
2443 case 8: // COMMENT_NODE
2444 if (!editor
.config
.htmlRemoveComments
) html
= "<!--" + root
.data
+ "-->";
2446 case 4: // Node.CDATA_SECTION_NODE
2447 // Mozilla seems to convert CDATA into a comment when going into wysiwyg mode, don't know about IE
2448 html
+= '<![CDATA[' + root
.data
+ ']]>';
2450 case 5: // Node.ENTITY_REFERENCE_NODE
2451 html
+= '&' + root
.nodeValue
+ ';';
2453 case 7: // Node.PROCESSING_INSTRUCTION_NODE
2454 // PI's don't seem to survive going into the wysiwyg mode, (at least in moz) so this is purely academic
2455 html
+= '<?' + root
.target
+ ' ' + root
.data
+ ' ?>';
2463 HTMLArea
.getPrevNode = function(node
) {
2464 if(!node
) return null;
2465 if(node
.previousSibling
) return node
.previousSibling
;
2466 if(node
.parentNode
) return node
.parentNode
;
2470 HTMLArea
.getNextNode = function(node
) {
2471 if(!node
) return null;
2472 if(node
.nextSibling
) return node
.nextSibling
;
2473 if(node
.parentNode
) return node
.parentNode
;
2477 HTMLArea
.removeFromParent = function(el
) {
2478 if(!el
.parentNode
) return;
2479 var pN
= el
.parentNode
;
2484 String
.prototype.trim = function() {
2485 return this.replace(/^\s+/, '').replace(/\s+$/, '');
2488 // creates a rgb-style color from a number
2489 HTMLArea
._makeColor = function(v
) {
2490 if (typeof(v
) != "number") {
2491 // already in rgb (hopefully); IE doesn't get here.
2494 // IE sends number; convert to rgb.
2496 var g
= (v
>> 8) & 0xFF;
2497 var b
= (v
>> 16) & 0xFF;
2498 return "rgb(" + r
+ "," + g
+ "," + b
+ ")";
2501 // returns hexadecimal color representation from a number or a rgb-style color.
2502 HTMLArea
._colorToRgb = function(v
) {
2506 // returns the hex representation of one byte (2 digits)
2508 return (d
< 16) ? ("0" + d
.toString(16)) : d
.toString(16);
2511 if (typeof(v
) == "number") {
2512 // we're talking to IE here
2514 var g
= (v
>> 8) & 0xFF;
2515 var b
= (v
>> 16) & 0xFF;
2516 return "#" + hex(r
) + hex(g
) + hex(b
);
2519 if (v
.substr(0, 3) == "rgb") {
2520 // in rgb(...) form -- Mozilla
2521 var re
= /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/;
2523 var r
= parseInt(RegExp
.$1);
2524 var g
= parseInt(RegExp
.$2);
2525 var b
= parseInt(RegExp
.$3);
2526 return "#" + hex(r
) + hex(g
) + hex(b
);
2528 // doesn't match RE?! maybe uses percentages or float numbers
2529 // -- FIXME: not yet implemented.
2533 if (v
.substr(0, 1) == "#") {
2534 // already hex rgb (hopefully :D )
2538 // if everything else fails ;)
2542 /** Use XML HTTPRequest to post some data back to the server and do something
2543 * with the response (asyncronously!), this is used by such things as the spellchecker update personal dict function
2545 HTMLArea
._postback = function(url
, data
, handler
, addParams
, charset
) {
2546 if (typeof(charset
) == "undefined") var charset
= "utf-8";
2548 if (window
.XMLHttpRequest
) req
= new XMLHttpRequest();
2549 else if (window
.ActiveXObject
) {
2550 var success
= false;
2551 for (var k
= 0; k
< HTMLArea
.MSXML_XMLHTTP_PROGIDS
.length
&& !success
; k
++) {
2553 req
= new ActiveXObject(HTMLArea
.MSXML_XMLHTTP_PROGIDS
[k
]);
2561 for (var i
in data
) {
2562 content
+= (content
.length
? '&' : '') + i
+ '=' + encodeURIComponent(data
[i
]);
2564 content
+= (content
.length
? '&' : '') + 'charset=' + charset
;
2565 if (typeof(addParams
) != "undefined") content
+= addParams
;
2566 if (url
.substring(0,1) == '/') {
2567 var postUrl
= _typo3_host_url
+ url
;
2569 var postUrl
= _typo3_host_url
+ _editor_url
+ url
;
2572 function callBack() {
2573 if(req
.readyState
== 4) {
2574 if (req
.status
== 200) {
2575 if (typeof(handler
) == 'function') handler(req
.responseText
, req
);
2576 HTMLArea
._appendToLog("[HTMLArea::_postback]: Server response: " + req
.responseText
);
2578 HTMLArea
._appendToLog("ERROR [HTMLArea::_postback]: Unable to post " + postUrl
+ " . Server reported " + req
.statusText
);
2582 req
.onreadystatechange
= callBack
;
2583 function sendRequest() {
2584 HTMLArea
._appendToLog("[HTMLArea::_postback]: Request: " + content
);
2588 req
.open('POST', postUrl
, true);
2589 req
.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
2590 window
.setTimeout(sendRequest
, 500);
2595 * Internet Explorer returns an item having the _name_ equal to the given id, even if it's not having any id.
2596 * This way it can return a different form field even if it's not a textarea. This works around the problem by
2597 * specifically looking to search only elements having a certain tag name.
2599 HTMLArea
.getElementById = function(tag
, id
) {
2600 var el
, i
, objs
= document
.getElementsByTagName(tag
);
2601 for (i
= objs
.length
; --i
>= 0 && (el
= objs
[i
]);) {
2602 if (el
.id
== id
) return el
;
2607 /***************************************************
2608 * TYPO3-SPECIFIC FUNCTIONS
2609 ***************************************************/
2611 * Set the size of textarea with the RTE. It's called, if we are in fullscreen-mode.
2613 var setRTEsizeByJS = function(divId
, height
, width
) {
2614 if (HTMLArea
.is_gecko
) height
= height
- 25;
2615 else height
= height
- 60;
2616 if (height
> 0) document
.getElementById(divId
).style
.height
= height
+ "px";
2617 if (HTMLArea
.is_gecko
) width
= "99%";
2619 document
.getElementById(divId
).style
.width
= width
;
2623 * Extending the TYPO3 Lorem Ipsum extension
2625 var lorem_ipsum = function(element
,text
) {
2626 if (element
.tagName
.toLowerCase() == "textarea" && element
.id
&& element
.id
.substr(0,7) == "RTEarea") {
2627 var editor
= RTEarea
[element
.id
.substr(7,8)]["editor"];
2628 editor
.insertHTML(text
);
2629 editor
.updateToolbar();
2634 * Initialize the editor, configure the toolbar, setup the plugins, etc.
2636 HTMLArea
.initTimer
= [];
2638 HTMLArea
.onGenerateHandler = function(editorNumber
) {
2639 return (function() {
2640 document
.getElementById('pleasewait' + editorNumber
).style
.display
= 'none';
2641 document
.getElementById('editorWrap' + editorNumber
).style
.visibility
= 'visible';
2642 editorNumber
= null;
2646 HTMLArea
.initEditor = function(editorNumber
) {
2647 if(HTMLArea
.checkSupportedBrowser()) {
2648 document
.getElementById('pleasewait' + editorNumber
).style
.display
= 'block';
2649 document
.getElementById('editorWrap' + editorNumber
).style
.visibility
= 'hidden';
2650 if(HTMLArea
.initTimer
[editorNumber
]) window
.clearTimeout(HTMLArea
.initTimer
[editorNumber
]);
2651 if(!HTMLArea
.is_loaded
) {
2652 HTMLArea
.initTimer
[editorNumber
] = window
.setTimeout( "HTMLArea.initEditor(" + editorNumber
+ ");", 150);
2654 var RTE
= RTEarea
[editorNumber
];
2656 // Get the configuration properties
2657 var config
= new HTMLArea
.Config();
2658 for (var property
in RTE
) {
2659 if (RTE
.hasOwnProperty(property
)) {
2660 config
[property
] = RTE
[property
] ? RTE
[property
] : false;
2663 // Create an editor for the textarea
2664 var editor
= new HTMLArea(RTE
.id
, config
);
2665 RTE
.editor
= editor
;
2667 // Save the editornumber in the object
2668 editor
._typo3EditerNumber
= editorNumber
;
2669 editor
._editorNumber
= editorNumber
;
2671 // Override these settings if they were ever modified
2672 editor
.config
.width
= "auto";
2673 editor
.config
.height
= "auto";
2674 editor
.config
.sizeIncludesToolbar
= true;
2675 editor
.config
.fullPage
= false;
2677 // Register the plugins included in the configuration
2678 for (var plugin
in editor
.config
.plugin
) {
2679 if (editor
.config
.plugin
.hasOwnProperty(plugin
) && editor
.config
.plugin
[plugin
]) {
2680 editor
.registerPlugin(plugin
);
2684 editor
.onGenerate
= HTMLArea
.onGenerateHandler(editorNumber
);
2690 document
.getElementById('pleasewait' + editorNumber
).style
.display
= 'none';
2691 document
.getElementById('editorWrap' + editorNumber
).style
.visibility
= 'visible';
2695 HTMLArea
.allElementsAreDisplayed = function(elements
) {
2696 for (var i
=0, length
=elements
.length
; i
< length
; i
++) {
2697 if (document
.getElementById(elements
[i
]).style
.display
== 'none') {
2705 * Base, version 1.0.2
2706 * Copyright 2006, Dean Edwards
2707 * License: http://creativecommons.org/licenses/LGPL/2.1/
2710 HTMLArea
.Base = function() {
2711 if (arguments
.length
) {
2712 if (this == window
) { // cast an object to this class
2713 HTMLArea
.Base
.prototype.extend
.call(arguments
[0], arguments
.callee
.prototype);
2715 this.extend(arguments
[0]);
2720 HTMLArea
.Base
.version
= "1.0.2";
2722 HTMLArea
.Base
.prototype = {
2723 extend: function(source
, value
) {
2724 var extend
= HTMLArea
.Base
.prototype.extend
;
2725 if (arguments
.length
== 2) {
2726 var ancestor
= this[source
];
2728 if ((ancestor
instanceof Function
) && (value
instanceof Function
) &&
2729 ancestor
.valueOf() != value
.valueOf() && /\bbase\b/.test(value
)) {
2731 // var _prototype = this.constructor.prototype;
2732 // var fromPrototype = !Base._prototyping && _prototype[source] == ancestor;
2733 value = function() {
2734 var previous
= this.base
;
2735 // this.base = fromPrototype ? _prototype[source] : ancestor;
2736 this.base
= ancestor
;
2737 var returnValue
= method
.apply(this, arguments
);
2738 this.base
= previous
;
2741 // point to the underlying method
2742 value
.valueOf = function() {
2745 value
.toString = function() {
2746 return String(method
);
2749 return this[source
] = value
;
2750 } else if (source
) {
2751 var _prototype
= {toSource
: null};
2752 // do the "toString" and other methods manually
2753 var _protected
= ["toString", "valueOf"];
2754 // if we are prototyping then include the constructor
2755 if (HTMLArea
.Base
._prototyping
) _protected
[2] = "constructor";
2756 for (var i
= 0; (name
= _protected
[i
]); i
++) {
2757 if (source
[name
] != _prototype
[name
]) {
2758 extend
.call(this, name
, source
[name
]);
2761 // copy each of the source object's properties to this object
2762 for (var name
in source
) {
2763 if (!_prototype
[name
]) {
2764 extend
.call(this, name
, source
[name
]);
2772 // call this method from any other method to invoke that method's ancestor
2776 HTMLArea
.Base
.extend = function(_instance
, _static
) {
2777 var extend
= HTMLArea
.Base
.prototype.extend
;
2778 if (!_instance
) _instance
= {};
2779 // build the prototype
2780 HTMLArea
.Base
._prototyping
= true;
2781 var _prototype
= new this;
2782 extend
.call(_prototype
, _instance
);
2783 var constructor = _prototype
.constructor;
2784 _prototype
.constructor = this;
2785 delete HTMLArea
.Base
._prototyping
;
2786 // create the wrapper for the constructor function
2787 var klass = function() {
2788 if (!HTMLArea
.Base
._prototyping
) constructor.apply(this, arguments
);
2789 this.constructor = klass
;
2791 klass
.prototype = _prototype
;
2792 // build the class interface
2793 klass
.extend
= this.extend
;
2794 klass
.implement
= this.implement
;
2795 klass
.toString = function() {
2796 return String(constructor);
2798 extend
.call(klass
, _static
);
2800 var object
= constructor ? klass
: _prototype
;
2801 // class initialisation
2802 if (object
.init
instanceof Function
) object
.init();
2806 HTMLArea
.Base
.implement = function(_interface
) {
2807 if (_interface
instanceof Function
) _interface
= _interface
.prototype;
2808 this.prototype.extend(_interface
);
2812 * HTMLArea.plugin class
2814 * Every plugin should be a subclass of this class
2817 HTMLArea
.Plugin
= HTMLArea
.Base
.extend({
2820 * HTMLArea.plugin constructor
2822 * @param object editor: instance of RTE
2823 * @param string pluginName: name of the plugin
2825 * @return boolean true if the plugin was configured
2827 constructor : function(editor
, pluginName
) {
2828 this.editor
= editor
;
2829 this.editorNumber
= editor
._editorNumber
;
2830 this.editorConfiguration
= editor
.config
;
2831 this.name
= pluginName
;
2833 HTMLArea
.I18N
[this.name
] = eval(this.name
+ "_langArray");
2834 this.I18N
= HTMLArea
.I18N
[this.name
];
2836 this.appendToLog("initialize", "The localization array for plugin " + this.name
+ " could not be assigned.");
2838 return this.configurePlugin(editor
);
2842 * Configures the plugin
2843 * This function is invoked by the class constructor.
2844 * This function should be redefined by the plugin subclass. Normal steps would be:
2845 * - registering plugin ingormation with method registerPluginInformation;
2846 * - registering any buttons with method registerButton;
2847 * - registering any drop-down lists with method registerDropDown.
2849 * @param object editor: instance of RTE
2851 * @return boolean true if the plugin was configured
2853 configurePlugin : function(editor
) {
2858 * Registers the plugin "About" information
2860 * @param object pluginInformation:
2861 * version : the version,
2862 * developer : the name of the developer,
2863 * developerUrl : the url of the developer,
2864 * copyrightOwner : the name of the copyright owner,
2865 * sponsor : the name of the sponsor,
2866 * sponsorUrl : the url of the sponsor,
2867 * license : the type of license (should be "GPL")
2869 * @return boolean true if the information was registered
2871 registerPluginInformation : function(pluginInformation
) {
2872 if (typeof(pluginInformation
) !== "object") {
2873 this.appendToLog("registerPluginInformation", "Plugin information was not provided");
2876 this.pluginInformation
= pluginInformation
;
2877 this.pluginInformation
.name
= this.name
;
2878 /* Ensure backwards compatibility */
2879 this.pluginInformation
.developer_url
= this.pluginInformation
.developerUrl
;
2880 this.pluginInformation
.c_owner
= this.pluginInformation
.copyrightOwner
;
2881 this.pluginInformation
.sponsor_url
= this.pluginInformation
.sponsorUrl
;
2887 * Returns the plugin information
2889 * @return object the plugin information object
2891 getPluginInformation : function() {
2892 return this.pluginInformation
;
2896 * Returns true if the button is enabled in the toolbar configuration
2898 * @param string buttonId: identification of the button
2900 * @return boolean true if the button is enabled in the toolbar configuration
2902 isButtonInToolbar : function(buttonId
) {
2903 var toolbar
= this.editorConfiguration
.toolbar
;
2904 var n
= toolbar
.length
;
2905 for ( var i
= 0; i
< n
; ++i
) {
2906 var buttonInToolbar
= new RegExp( "^(" + toolbar
[i
].join("|") + ")$", "i"