* Upgraded TinyMCE to version 2.0RC3. This fixes a conflict with
[citadel.git] / webcit / tiny_mce / tiny_mce_src.js
1 /**
2  * $RCSfile$
3  * $Revision$
4  * $Date$
5  *
6  * @author Moxiecode
7  * @copyright Copyright © 2004, Moxiecode Systems AB, All rights reserved.
8  */
9
10 function TinyMCE() {
11         this.majorVersion = "2";
12         this.minorVersion = "0RC3";
13         this.releaseDate = "2005-09-26";
14
15         this.instances = new Array();
16         this.stickyClassesLookup = new Array();
17         this.windowArgs = new Array();
18         this.loadedFiles = new Array();
19         this.configs = new Array();
20         this.currentConfig = 0;
21         this.eventHandlers = new Array();
22
23         // Browser check
24         this.isMSIE = (navigator.appName == "Microsoft Internet Explorer");
25         this.isMSIE5 = this.isMSIE && (navigator.userAgent.indexOf('MSIE 5') != -1);
26         this.isMSIE5_0 = this.isMSIE && (navigator.userAgent.indexOf('MSIE 5.0') != -1);
27         this.isGecko = navigator.userAgent.indexOf('Gecko') != -1;
28         this.isSafari = navigator.userAgent.indexOf('Safari') != -1;
29         this.isMac = navigator.userAgent.indexOf('Mac') != -1;
30         this.dialogCounter = 0;
31
32         // TinyMCE editor id instance counter
33         this.idCounter = 0;
34 };
35
36 TinyMCE.prototype.defParam = function(key, def_val) {
37         this.settings[key] = tinyMCE.getParam(key, def_val);
38 };
39
40 TinyMCE.prototype.init = function(settings) {
41         var theme;
42
43         this.settings = settings;
44
45         // Check if valid browser has execcommand support
46         if (typeof(document.execCommand) == 'undefined')
47                 return;
48
49         // Get script base path
50         if (!tinyMCE.baseURL) {
51                 var elements = document.getElementsByTagName('script');
52
53                 for (var i=0; i<elements.length; i++) {
54                         if (elements[i].src && (elements[i].src.indexOf("tiny_mce.js") != -1 || elements[i].src.indexOf("tiny_mce_src.js") != -1 || elements[i].src.indexOf("tiny_mce_gzip.php") != -1)) {
55                                 var src = elements[i].src;
56
57                                 tinyMCE.srcMode = (src.indexOf('_src') != -1) ? '_src' : '';
58                                 src = src.substring(0, src.lastIndexOf('/'));
59
60                                 tinyMCE.baseURL = src;
61                                 break;
62                         }
63                 }
64         }
65
66         // Get document base path
67         this.documentBasePath = document.location.href;
68         if (this.documentBasePath.indexOf('?') != -1)
69                 this.documentBasePath = this.documentBasePath.substring(0, this.documentBasePath.indexOf('?'));
70         this.documentURL = this.documentBasePath;
71         this.documentBasePath = this.documentBasePath.substring(0, this.documentBasePath.lastIndexOf('/'));
72
73         // If not HTTP absolute
74         if (tinyMCE.baseURL.indexOf('://') == -1 && tinyMCE.baseURL.charAt(0) != '/') {
75                 // If site absolute
76                 tinyMCE.baseURL = this.documentBasePath + "/" + tinyMCE.baseURL;
77         }
78
79         // Set default values on settings
80         this.defParam("mode", "none");
81         this.defParam("theme", "advanced");
82         this.defParam("plugins", "", true);
83         this.defParam("language", "en");
84         this.defParam("docs_language", this.settings['language']);
85         this.defParam("elements", "");
86         this.defParam("textarea_trigger", "mce_editable");
87         this.defParam("editor_selector", "");
88         this.defParam("editor_deselector", "mceNoEditor");
89         this.defParam("valid_elements", "+a[id|style|rel|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/b[class|style],-em/i[class|style],-strike[class|style],-u[class|style],+p[style|dir|class|align],-ol[class|style],-ul[class|style],-li[class|style],br,img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border=0|alt|title|hspace|vspace|width|height|align],-sub[style|class],-sup[style|class],-blockquote[dir|style],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],-td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[style|class|align],-pre[class|align|style],address[class|align|style],-h1[style|dir|class|align],-h2[style|dir|class|align],-h3[style|dir|class|align],-h4[style|dir|class|align],-h5[style|dir|class|align],-h6[style|dir|class|align],hr[class|style]");
90         this.defParam("extended_valid_elements", "");
91         this.defParam("invalid_elements", "");
92         this.defParam("encoding", "");
93         this.defParam("urlconverter_callback", tinyMCE.getParam("urlconvertor_callback", "TinyMCE.prototype.convertURL"));
94         this.defParam("save_callback", "");
95         this.defParam("debug", false);
96         this.defParam("force_br_newlines", false);
97         this.defParam("force_p_newlines", true);
98         this.defParam("add_form_submit_trigger", true);
99         this.defParam("relative_urls", true);
100         this.defParam("remove_script_host", true);
101         this.defParam("focus_alert", true);
102         this.defParam("document_base_url", this.documentURL);
103         this.defParam("visual", true);
104         this.defParam("visual_table_class", "mceVisualAid");
105         this.defParam("setupcontent_callback", "");
106         this.defParam("fix_content_duplication", true);
107         this.defParam("custom_undo_redo", true);
108         this.defParam("custom_undo_redo_levels", -1);
109         this.defParam("custom_undo_redo_keyboard_shortcuts", true);
110         this.defParam("verify_css_classes", false);
111         this.defParam("verify_html", true);
112         this.defParam("apply_source_formatting", false);
113         this.defParam("directionality", "ltr");
114         this.defParam("cleanup_on_startup", false);
115         this.defParam("inline_styles", false);
116         this.defParam("convert_newlines_to_brs", false);
117         this.defParam("auto_reset_designmode", true);
118         this.defParam("entities", "160,nbsp,38,amp,34,quot,162,cent,8364,euro,163,pound,165,yen,169,copy,174,reg,8482,trade,8240,permil,181,micro,183,middot,8226,bull,8230,hellip,8242,prime,8243,Prime,167,sect,182,para,223,szlig,8249,lsaquo,8250,rsaquo,171,laquo,187,raquo,8216,lsquo,8217,rsquo,8220,ldquo,8221,rdquo,8218,sbquo,8222,bdquo,60,lt,62,gt,8804,le,8805,ge,8211,ndash,8212,mdash,175,macr,8254,oline,164,curren,166,brvbar,168,uml,161,iexcl,191,iquest,710,circ,732,tilde,176,deg,8722,minus,177,plusmn,247,divide,8260,frasl,215,times,185,sup1,178,sup2,179,sup3,188,frac14,189,frac12,190,frac34,402,fnof,8747,int,8721,sum,8734,infin,8730,radic,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8712,isin,8713,notin,8715,ni,8719,prod,8743,and,8744,or,172,not,8745,cap,8746,cup,8706,part,8704,forall,8707,exist,8709,empty,8711,nabla,8727,lowast,8733,prop,8736,ang,180,acute,184,cedil,170,ordf,186,ordm,8224,dagger,8225,Dagger,192,Agrave,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,202,Ecirc,203,Euml,204,Igrave,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,212,Ocirc,213,Otilde,214,Ouml,216,Oslash,338,OElig,217,Ugrave,219,Ucirc,220,Uuml,376,Yuml,222,THORN,224,agrave,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,234,ecirc,235,euml,236,igrave,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,244,ocirc,245,otilde,246,ouml,248,oslash,339,oelig,249,ugrave,251,ucirc,252,uuml,254,thorn,255,yuml,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,8501,alefsym,982,piv,8476,real,977,thetasym,978,upsih,8472,weierp,8465,image,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8756,there4,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,173,shy,233,eacute,237,iacute,243,oacute,250,uacute,193,Aacute,225,aacute,201,Eacute,205,Iacute,211,Oacute,218,Uacute,221,Yacute,253,yacute");
119         this.defParam("entity_encoding", "named");
120         this.defParam("cleanup_callback", "");
121         this.defParam("add_unload_trigger", true);
122         this.defParam("ask", false);
123         this.defParam("nowrap", false);
124         this.defParam("auto_resize", false);
125         this.defParam("auto_focus", false);
126         this.defParam("cleanup", true);
127         this.defParam("remove_linebreaks", true);
128         this.defParam("button_tile_map", false);
129         this.defParam("submit_patch", true);
130         this.defParam("browsers", "msie,safari,gecko");
131         this.defParam("dialog_type", "window");
132         this.defParam("convert_fonts_to_styles", true);
133         this.defParam("accessibility_warnings", true);
134
135         // Browser check IE
136         if (this.isMSIE && this.settings['browsers'].indexOf('msie') == -1)
137                 return;
138
139         // Browser check Gecko
140         if (this.isGecko && this.settings['browsers'].indexOf('gecko') == -1)
141                 return;
142
143         // Browser check Safari
144         if (this.isSafari && this.settings['browsers'].indexOf('safari') == -1)
145                 return;
146
147         // Setup baseHREF
148         var baseHREF = tinyMCE.settings['document_base_url'];
149         if (baseHREF.indexOf('?') != -1)
150                 baseHREF = baseHREF.substring(0, baseHREF.indexOf('?'));
151         this.settings['base_href'] = baseHREF.substring(0, baseHREF.lastIndexOf('/')) + "/";
152
153         theme = this.settings['theme'];
154
155         this.blockRegExp = new RegExp("^(h[1-6]|p|div|address|pre|form|table|li|ol|ul|td)$", "i");
156         this.posKeyCodes = new Array(13,45,36,35,33,34,37,38,39,40);
157
158         // Theme url
159         this.settings['theme_href'] = tinyMCE.baseURL + "/themes/" + theme;
160
161         if (!tinyMCE.isMSIE)
162                 this.settings['force_br_newlines'] = false;
163
164         if (tinyMCE.getParam("content_css", false)) {
165                 var cssPath = tinyMCE.getParam("content_css", "");
166
167                 // Is relative
168                 if (cssPath.indexOf('://') == -1 && cssPath.charAt(0) != '/')
169                         this.settings['content_css'] = this.documentBasePath + "/" + cssPath;
170                 else
171                         this.settings['content_css'] = cssPath;
172         } else
173                 this.settings['content_css'] = '';
174
175         if (tinyMCE.getParam("popups_css", false)) {
176                 var cssPath = tinyMCE.getParam("popups_css", "");
177
178                 // Is relative
179                 if (cssPath.indexOf('://') == -1 && cssPath.charAt(0) != '/')
180                         this.settings['popups_css'] = this.documentBasePath + "/" + cssPath;
181                 else
182                         this.settings['popups_css'] = cssPath;
183         } else
184                 this.settings['popups_css'] = tinyMCE.baseURL + "/themes/" + theme + "/css/editor_popup.css";
185
186         if (tinyMCE.getParam("editor_css", false)) {
187                 var cssPath = tinyMCE.getParam("editor_css", "");
188
189                 // Is relative
190                 if (cssPath.indexOf('://') == -1 && cssPath.charAt(0) != '/')
191                         this.settings['editor_css'] = this.documentBasePath + "/" + cssPath;
192                 else
193                         this.settings['editor_css'] = cssPath;
194         } else
195                 this.settings['editor_css'] = tinyMCE.baseURL + "/themes/" + theme + "/css/editor_ui.css";
196
197         if (tinyMCE.settings['debug']) {
198                 var msg = "Debug: \n";
199
200                 msg += "baseURL: " + this.baseURL + "\n";
201                 msg += "documentBasePath: " + this.documentBasePath + "\n";
202                 msg += "content_css: " + this.settings['content_css'] + "\n";
203                 msg += "popups_css: " + this.settings['popups_css'] + "\n";
204                 msg += "editor_css: " + this.settings['editor_css'] + "\n";
205
206                 alert(msg);
207         }
208
209         // Init HTML cleanup
210         this._initCleanup();
211
212         // Only do this once
213         if (this.configs.length == 0) {
214                 // Is Safari enabled
215                 if (this.isSafari && this.getParam('safari_warning', true))
216                         alert("Safari support is very limited and should be considered experimental.\nSo there is no need to even submit bugreports on this early version.");
217
218                 tinyMCE.addEvent(window, "load", TinyMCE.prototype.onLoad);
219
220                 if (tinyMCE.isMSIE) {
221                         if (tinyMCE.settings['add_unload_trigger']) {
222                                 tinyMCE.addEvent(window, "unload", TinyMCE.prototype.unloadHandler);
223                                 tinyMCE.addEvent(window.document, "beforeunload", TinyMCE.prototype.unloadHandler);
224                         }
225                 } else {
226                         if (tinyMCE.settings['add_unload_trigger'])
227                                 tinyMCE.addEvent(window, "unload", function () {tinyMCE.triggerSave(true, true);});
228                 }
229         }
230
231         this.loadScript(tinyMCE.baseURL + '/themes/' + this.settings['theme'] + '/editor_template' + tinyMCE.srcMode + '.js');
232         this.loadScript(tinyMCE.baseURL + '/langs/' + this.settings['language'] +  '.js');
233         this.loadCSS(this.settings['editor_css']);
234
235         // Add theme plugins
236         var themePlugins = tinyMCE.getParam('plugins', '', true, ',');
237         if (this.settings['plugins'] != '') {
238                 for (var i=0; i<themePlugins.length; i++)
239                         this.loadScript(tinyMCE.baseURL + '/plugins/' + themePlugins[i] + '/editor_plugin' + tinyMCE.srcMode + '.js');
240         }
241
242         // Save away this config
243         settings['index'] = this.configs.length;
244         this.configs[this.configs.length] = settings;
245 };
246
247 TinyMCE.prototype.loadScript = function(url) {
248         for (var i=0; i<this.loadedFiles.length; i++) {
249                 if (this.loadedFiles[i] == url)
250                         return;
251         }
252
253         document.write('<sc'+'ript language="javascript" type="text/javascript" src="' + url + '"></script>');
254
255         this.loadedFiles[this.loadedFiles.length] = url;
256 };
257
258 TinyMCE.prototype.loadCSS = function(url) {
259         for (var i=0; i<this.loadedFiles.length; i++) {
260                 if (this.loadedFiles[i] == url)
261                         return;
262         }
263
264         document.write('<link href="' + url + '" rel="stylesheet" type="text/css" />');
265
266         this.loadedFiles[this.loadedFiles.length] = url;
267 };
268
269 TinyMCE.prototype.importCSS = function(doc, css_file) {
270         if (css_file == '')
271                 return;
272
273         if (tinyMCE.isMSIE)
274                 var styleSheet = doc.createStyleSheet(css_file);
275         else {
276                 var elm = doc.createElement("link");
277
278                 elm.rel = "stylesheet";
279                 elm.href = css_file;
280
281                 if (headArr = doc.getElementsByTagName("head"))
282                         headArr[0].appendChild(elm);
283         }
284 };
285
286 TinyMCE.prototype.confirmAdd = function(e, settings) {
287         var elm = tinyMCE.isMSIE ? event.srcElement : e.target;
288         var elementId = elm.name ? elm.name : elm.id;
289
290         tinyMCE.settings = settings;
291
292         if (!elm.getAttribute('mce_noask') && confirm(tinyMCELang['lang_edit_confirm']))
293                 tinyMCE.addMCEControl(elm, elementId);
294
295         elm.setAttribute('mce_noask', 'true');
296 };
297
298 TinyMCE.prototype.updateContent = function(form_element_name) {
299         // Find MCE instance linked to given form element and copy it's value
300         var formElement = document.getElementById(form_element_name);
301         for (var n in tinyMCE.instances) {
302                 var inst = tinyMCE.instances[n];
303                 if (typeof(inst) == 'function')
304                         continue;
305
306                 inst.switchSettings();
307
308                 if (inst.formElement == formElement) {
309                         var doc = inst.getDoc();
310         
311                         tinyMCE._setHTML(doc, inst.formElement.value);
312
313                         if (!tinyMCE.isMSIE)
314                                 doc.body.innerHTML = tinyMCE._cleanupHTML(inst, doc, this.settings, doc.body, inst.visualAid);
315                 }
316         }
317 };
318
319 TinyMCE.prototype.addMCEControl = function(replace_element, form_element_name, target_document) {
320         var id = "mce_editor_" + tinyMCE.idCounter++;
321         var inst = new TinyMCEControl(tinyMCE.settings);
322
323         inst.editorId = id;
324         this.instances[id] = inst;
325
326         inst.onAdd(replace_element, form_element_name, target_document);
327 };
328
329 TinyMCE.prototype.triggerSave = function(skip_cleanup, skip_callback) {
330         // Cleanup and set all form fields
331         for (var n in tinyMCE.instances) {
332                 var inst = tinyMCE.instances[n];
333                 if (typeof(inst) == 'function')
334                         continue;
335
336                 inst.switchSettings();
337
338                 tinyMCE.settings['preformatted'] = false;
339
340                 // Default to false
341                 if (typeof(skip_cleanup) == "undefined")
342                         skip_cleanup = false;
343
344                 // Default to false
345                 if (typeof(skip_callback) == "undefined")
346                         skip_callback = false;
347
348                 tinyMCE._setHTML(inst.getDoc(), inst.getBody().innerHTML);
349
350                 // Remove visual aids when cleanup is disabled
351                 if (inst.settings['cleanup'] == false) {
352                         tinyMCE.handleVisualAid(inst.getBody(), true, false, inst);
353                         tinyMCE._setEventsEnabled(inst.getBody(), false);
354                 }
355
356                 tinyMCE._customCleanup(inst, "submit_content_dom", inst.contentWindow.document.body);
357                 var htm = skip_cleanup ? inst.getBody().innerHTML : tinyMCE._cleanupHTML(inst, inst.getDoc(), this.settings, inst.getBody(), this.visualAid, true);
358                 htm = tinyMCE._customCleanup(inst, "submit_content", htm);
359
360                 if (tinyMCE.settings["encoding"] == "xml" || tinyMCE.settings["encoding"] == "html")
361                         htm = tinyMCE.convertStringToXML(htm);
362
363                 if (!skip_callback && tinyMCE.settings['save_callback'] != "")
364                         var content = eval(tinyMCE.settings['save_callback'] + "(inst.formTargetElementId,htm,inst.getBody());");
365
366                 // Use callback content if available
367                 if ((typeof(content) != "undefined") && content != null)
368                         htm = content;
369
370                 // Replace some weird entities (Bug: #1056343)
371                 htm = tinyMCE.regexpReplace(htm, "&#40;", "(", "gi");
372                 htm = tinyMCE.regexpReplace(htm, "&#41;", ")", "gi");
373                 htm = tinyMCE.regexpReplace(htm, "&#59;", ";", "gi");
374                 htm = tinyMCE.regexpReplace(htm, "&#34;", "&quot;", "gi");
375                 htm = tinyMCE.regexpReplace(htm, "&#94;", "^", "gi");
376
377                 if (inst.formElement)
378                         inst.formElement.value = htm;
379         }
380 };
381
382 TinyMCE.prototype._setEventsEnabled = function(node, state) {
383         var events = new Array('onfocus','onblur','onclick','ondblclick',
384                                 'onmousedown','onmouseup','onmouseover','onmousemove',
385                                 'onmouseout','onkeypress','onkeydown','onkeydown','onkeyup');
386
387         var elms = node.getElementsByTagName("a");
388         for (var i=0; i<elms.length; i++) {
389                 var event = "";
390
391                 for (var x=0; x<events.length; x++) {
392                         if ((event = tinyMCE.getAttrib(elms[i], events[x])) != '') {
393                                 event = tinyMCE.cleanupEventStr("" + event);
394
395                                 if (state)
396                                         event = "return true;" + event;
397                                 else
398                                         event = event.replace(/^return true;/gi, '');
399
400                                 elms[i].removeAttribute(events[x]);
401                                 elms[i].setAttribute(events[x], event);
402                         }
403                 }
404         }
405 };
406
407 TinyMCE.prototype.resetForm = function(form_index) {
408         var formObj = document.forms[form_index];
409
410         for (var n in tinyMCE.instances) {
411                 var inst = tinyMCE.instances[n];
412                 if (typeof(inst) == 'function')
413                         continue;
414
415                 inst.switchSettings();
416
417                 for (var i=0; i<formObj.elements.length; i++) {
418                         if (inst.formTargetElementId == formObj.elements[i].name) {
419                                 inst.getBody().innerHTML = formObj.elements[i].value;
420                                 return;
421                         }
422                 }
423         }
424 };
425
426 TinyMCE.prototype.execInstanceCommand = function(editor_id, command, user_interface, value, focus) {
427         var inst = tinyMCE.getInstanceById(editor_id);
428         if (inst) {
429                 if (typeof(focus) == "undefined")
430                         focus = true;
431
432                 if (focus)
433                         inst.contentWindow.focus();
434
435                 // Reset design mode if lost
436                 inst.autoResetDesignMode();
437
438                 this.selectedElement = inst.getFocusElement();
439                 this.selectedInstance = inst;
440                 tinyMCE.execCommand(command, user_interface, value);
441         }
442 };
443
444 TinyMCE.prototype.execCommand = function(command, user_interface, value) {
445         // Default input
446         user_interface = user_interface ? user_interface : false;
447         value = value ? value : null;
448
449         if (tinyMCE.selectedInstance)
450                 tinyMCE.selectedInstance.switchSettings();
451
452         switch (command) {
453                 case 'mceHelp':
454                         var template = new Array();
455
456                         template['file']   = 'about.htm';
457                         template['width']  = 480;
458                         template['height'] = 380;
459
460                         tinyMCE.openWindow(template, {
461                                 tinymce_version : tinyMCE.majorVersion + "." + tinyMCE.minorVersion,
462                                 tinymce_releasedate : tinyMCE.releaseDate,
463                                 inline : "yes"
464                         });
465                 return;
466
467                 case 'mceFocus':
468                         var inst = tinyMCE.getInstanceById(value);
469                         if (inst)
470                                 inst.contentWindow.focus();
471                 return;
472
473                 case "mceAddControl":
474                 case "mceAddEditor":
475                         tinyMCE.addMCEControl(tinyMCE._getElementById(value), value);
476                         return;
477
478                 case "mceAddFrameControl":
479                         tinyMCE.addMCEControl(tinyMCE._getElementById(value), value['element'], value['document']);
480                         return;
481
482                 case "mceRemoveControl":
483                 case "mceRemoveEditor":
484                         tinyMCE.removeMCEControl(value);
485                         return;
486
487                 case "mceResetDesignMode":
488                         // Resets the designmode state of the editors in Gecko
489                         if (!tinyMCE.isMSIE) {
490                                 for (var n in tinyMCE.instances) {
491                                         if (typeof(tinyMCE.instances[n]) == 'function')
492                                                 continue;
493
494                                         try {
495                                                 tinyMCE.instances[n].getDoc().designMode = "on";
496                                         } catch (e) {
497                                                 // Ignore any errors
498                                         }
499                                 }
500                         }
501
502                         return;
503         }
504
505         if (this.selectedInstance)
506                 this.selectedInstance.execCommand(command, user_interface, value);
507         else if (tinyMCE.settings['focus_alert'])
508                 alert(tinyMCELang['lang_focus_alert']);
509 };
510
511 TinyMCE.prototype.eventPatch = function(editor_id) {
512         // Remove odd, error
513         if (typeof(tinyMCE) == "undefined")
514                 return true;
515
516         for (var i=0; i<document.frames.length; i++) {
517                 try {
518                         if (document.frames[i].event) {
519                                 var event = document.frames[i].event;
520
521                                 event.target = event.srcElement;
522
523                                 TinyMCE.prototype.handleEvent(event);
524                                 return;
525                         }
526                 } catch (ex) {
527                         // Ignore error if iframe is pointing to external URL
528                 }
529         }
530 };
531
532 TinyMCE.prototype.unloadHandler = function() {
533         tinyMCE.triggerSave(true, true);
534 };
535
536 TinyMCE.prototype.addEventHandlers = function(editor_id) {
537         if (tinyMCE.isMSIE) {
538                 var doc = document.frames[editor_id].document;
539
540                 // Event patch
541                 tinyMCE.addEvent(doc, "keypress", TinyMCE.prototype.eventPatch);
542                 tinyMCE.addEvent(doc, "keyup", TinyMCE.prototype.eventPatch);
543                 tinyMCE.addEvent(doc, "keydown", TinyMCE.prototype.eventPatch);
544                 tinyMCE.addEvent(doc, "mouseup", TinyMCE.prototype.eventPatch);
545                 tinyMCE.addEvent(doc, "click", TinyMCE.prototype.eventPatch);
546         } else {
547                 var inst = tinyMCE.instances[editor_id];
548                 var doc = inst.getDoc();
549
550                 inst.switchSettings();
551
552                 tinyMCE.addEvent(doc, "keypress", tinyMCE.handleEvent);
553                 tinyMCE.addEvent(doc, "keydown", tinyMCE.handleEvent);
554                 tinyMCE.addEvent(doc, "keyup", tinyMCE.handleEvent);
555                 tinyMCE.addEvent(doc, "click", tinyMCE.handleEvent);
556                 tinyMCE.addEvent(doc, "mouseup", tinyMCE.handleEvent);
557                 tinyMCE.addEvent(doc, "mousedown", tinyMCE.handleEvent);
558                 tinyMCE.addEvent(doc, "focus", tinyMCE.handleEvent);
559                 tinyMCE.addEvent(doc, "blur", tinyMCE.handleEvent);
560
561                 eval('try { doc.designMode = "On"; } catch(e) {}');
562         }
563 };
564
565 TinyMCE.prototype._createIFrame = function(replace_element) {
566         var iframe = document.createElement("iframe");
567         var id = replace_element.getAttribute("id");
568
569         iframe.setAttribute("id", id);
570         //iframe.setAttribute("className", "mceEditorArea");
571         iframe.setAttribute("border", "0");
572         iframe.setAttribute("frameBorder", "0");
573         iframe.setAttribute("marginWidth", "0");
574         iframe.setAttribute("marginHeight", "0");
575         iframe.setAttribute("leftMargin", "0");
576         iframe.setAttribute("topMargin", "0");
577         iframe.setAttribute("width", tinyMCE.settings['area_width']);
578         iframe.setAttribute("height", tinyMCE.settings['area_height']);
579         iframe.setAttribute("allowtransparency", "true");
580
581         if (tinyMCE.settings["auto_resize"])
582                 iframe.setAttribute("scrolling", "no");
583
584         // Must have a src element in MSIE HTTPs breaks aswell as absoute URLs
585         if (tinyMCE.isMSIE)
586                 iframe.setAttribute("src", this.settings['default_document']);
587
588         iframe.style.width = tinyMCE.settings['area_width'];
589         iframe.style.height = tinyMCE.settings['area_height'];
590
591         // MSIE 5.0 issue
592         if (tinyMCE.isMSIE)
593                 replace_element.outerHTML = iframe.outerHTML;
594         else
595                 replace_element.parentNode.replaceChild(iframe, replace_element);
596
597         if (tinyMCE.isMSIE)
598                 return window.frames[id];
599         else
600                 return iframe;
601 };
602
603 TinyMCE.prototype.setupContent = function(editor_id) {
604         var inst = tinyMCE.instances[editor_id];
605         var doc = inst.getDoc();
606         var head = doc.getElementsByTagName('head').item(0);
607         var content = inst.startContent;
608
609         inst.switchSettings();
610
611         // Not loaded correctly hit it again, Mozilla bug #997860
612         if (!tinyMCE.isMSIE && doc.title != "blank_page") {
613                 // This part will remove the designMode status
614                 doc.location.href = tinyMCE.baseURL + "/blank.htm";
615                 window.setTimeout("tinyMCE.setupContent('" + editor_id + "');", 1000);
616                 return;
617         }
618
619         if (!head) {
620                 window.setTimeout("tinyMCE.setupContent('" + editor_id + "');", 10);
621                 return;
622         }
623
624         // Import theme specific content CSS the user specific
625         tinyMCE.importCSS(inst.getDoc(), tinyMCE.baseURL + "/themes/" + inst.settings['theme'] + "/css/editor_content.css");
626         tinyMCE.importCSS(inst.getDoc(), inst.settings['content_css']);
627         tinyMCE.executeCallback('init_instance_callback', '_initInstance', 0, inst);
628
629         if (tinyMCE.settings['nowrap'])
630                 doc.body.style.whiteSpace = "nowrap";
631
632         doc.body.dir = this.settings['directionality'];
633         doc.editorId = editor_id;
634
635         // Add on document element in Mozilla
636         if (!tinyMCE.isMSIE)
637                 doc.documentElement.editorId = editor_id;
638
639         // Setup base element
640         var base = doc.createElement("base");
641         base.setAttribute('href', tinyMCE.settings['base_href']);
642         head.appendChild(base);
643
644         // Replace new line characters to BRs
645         if (tinyMCE.settings['convert_newlines_to_brs']) {
646                 content = tinyMCE.regexpReplace(content, "\r\n", "<br />", "gi");
647                 content = tinyMCE.regexpReplace(content, "\r", "<br />", "gi");
648                 content = tinyMCE.regexpReplace(content, "\n", "<br />", "gi");
649         }
650
651         // Open closed anchors
652         content = content.replace(new RegExp('<a(.*?)/>', 'gi'), '<a$1></a>');
653         content = content.replace(new RegExp('<a(.*?)name="(.*?)"(.*?)>(.*?)</a>', 'gi'), '<a$1name="$2"$3></a>$4');
654
655         // Call custom cleanup code
656         content = tinyMCE._customCleanup(inst, "insert_to_editor", content);
657
658         if (tinyMCE.isMSIE) {
659                 // Ugly!!!
660                 window.setInterval('try{tinyMCE.getCSSClasses(document.frames["' + editor_id + '"].document, "' + editor_id + '");}catch(e){}', 500);
661
662                 if (tinyMCE.settings["force_br_newlines"])
663                         document.frames[editor_id].document.styleSheets[0].addRule("p", "margin: 0px;");
664
665                 var body = document.frames[editor_id].document.body;
666
667                 tinyMCE.addEvent(body, "beforepaste", TinyMCE.prototype.eventPatch);
668                 tinyMCE.addEvent(body, "beforecut", TinyMCE.prototype.eventPatch);
669
670                 body.editorId = editor_id;
671         }
672
673         // Fix for bug #958637
674         if (!tinyMCE.isMSIE) {
675                 var contentElement = inst.getDoc().createElement("body");
676                 var doc = inst.getDoc();
677
678                 contentElement.innerHTML = content;
679
680                 // Remove weridness!
681                 if (tinyMCE.settings['force_p_newlines'])
682                         content = content.replace(new RegExp('&lt;&gt;', 'g'), "");
683
684                 if (tinyMCE.settings['cleanup_on_startup'])
685                         inst.getBody().innerHTML = tinyMCE._cleanupHTML(inst, doc, this.settings, contentElement);
686                 else {
687                         // Convert all strong/em to b/i
688                         content = tinyMCE.regexpReplace(content, "<strong", "<b", "gi");
689                         content = tinyMCE.regexpReplace(content, "<em(/?)>", "<i$1>", "gi");
690                         content = tinyMCE.regexpReplace(content, "<em ", "<i ", "gi");
691                         content = tinyMCE.regexpReplace(content, "</strong>", "</b>", "gi");
692                         content = tinyMCE.regexpReplace(content, "</em>", "</i>", "gi");
693                         inst.getBody().innerHTML = content;
694                 }
695
696                 inst.convertAllRelativeURLs();
697         } else {
698                 if (tinyMCE.settings['cleanup_on_startup']) {
699                         tinyMCE._setHTML(inst.getDoc(), content);
700
701                         // Produces permission denied error in MSIE 5.5
702                         eval('try {inst.getBody().innerHTML = tinyMCE._cleanupHTML(inst, inst.contentDocument, this.settings, inst.getBody());} catch(e) {}');
703                 } else
704                         tinyMCE._setHTML(inst.getDoc(), content);
705         }
706
707         // Fix for bug #957681
708         //inst.getDoc().designMode = inst.getDoc().designMode;
709
710         // Setup element references
711         var parentElm = document.getElementById(inst.editorId + '_parent');
712         if (parentElm.lastChild.nodeName.toLowerCase() == "input")
713                 inst.formElement = parentElm.lastChild;
714         else
715                 inst.formElement = parentElm.nextSibling;
716
717         tinyMCE.handleVisualAid(inst.getBody(), true, tinyMCE.settings['visual'], inst);
718         tinyMCE.executeCallback('setupcontent_callback', '_setupContent', 0, editor_id, inst.getBody(), inst.getDoc());
719
720         // Re-add design mode on mozilla
721         if (!tinyMCE.isMSIE)
722                 TinyMCE.prototype.addEventHandlers(editor_id);
723
724         // Add blur handler
725         if (tinyMCE.isMSIE)
726                 tinyMCE.addEvent(inst.getBody(), "blur", TinyMCE.prototype.eventPatch);
727
728         // Trigger node change, this call locks buttons for tables and so forth
729         tinyMCE.selectedInstance = inst;
730         tinyMCE.selectedElement = inst.contentWindow.document.body;
731         tinyMCE.triggerNodeChange(false, true);
732
733         // Call custom DOM cleanup
734         tinyMCE._customCleanup(inst, "insert_to_editor_dom", inst.getBody());
735         tinyMCE._customCleanup(inst, "setup_content_dom", inst.getBody());
736         tinyMCE._setEventsEnabled(inst.getBody(), true);
737
738         inst.startContent = tinyMCE.trim(inst.getBody().innerHTML);
739         inst.undoLevels[inst.undoLevels.length] = inst.startContent;
740 };
741
742 TinyMCE.prototype.cancelEvent = function(e) {
743         if (tinyMCE.isMSIE) {
744                 e.returnValue = false;
745                 e.cancelBubble = true;
746         } else
747                 e.preventDefault();
748 };
749
750 TinyMCE.prototype.removeTinyMCEFormElements = function(form_obj) {
751         // Disable all UI form elements that TinyMCE created
752         for (var i=0; i<form_obj.elements.length; i++) {
753                 var elementId = form_obj.elements[i].name ? form_obj.elements[i].name : form_obj.elements[i].id;
754
755                 if (elementId.indexOf('mce_editor_') == 0)
756                         form_obj.elements[i].disabled = true;
757         }
758 };
759
760 TinyMCE.prototype.accessibleEventHandler = function(e) {
761         var win = this._win;
762         e = tinyMCE.isMSIE ? win.event : e;
763         var elm = tinyMCE.isMSIE ? e.srcElement : e.target;
764
765         // Piggyback onchange
766         if (elm.nodeName == "SELECT" && !elm.oldonchange) {
767                 elm.oldonchange = elm.onchange;
768                 elm.onchange = null;
769         }
770
771         // Execute onchange and remove piggyback
772         if (e.keyCode == 13 || e.keyCode == 32) {
773                 elm.onchange = elm.oldonchange;
774                 elm.onchange();
775                 elm.oldonchange = null;
776                 tinyMCE.cancelEvent(e);
777         }
778 };
779
780 TinyMCE.prototype.addSelectAccessibility = function(e, select, win) {
781         // Add event handlers 
782         if (!select._isAccessible) {
783                 select.onkeydown = tinyMCE.accessibleEventHandler;
784                 select._isAccessible = true;
785                 select._win = win;
786         }
787 };
788
789 TinyMCE.prototype.handleEvent = function(e) {
790         // Remove odd, error
791         if (typeof(tinyMCE) == "undefined")
792                 return true;
793
794         //tinyMCE.debug(e.type + " " + e.target.nodeName + " " + (e.relatedTarget ? e.relatedTarget.nodeName : ""));
795
796         switch (e.type) {
797                 case "blur":
798                         if (tinyMCE.selectedInstance)
799                                 tinyMCE.selectedInstance.execCommand('mceEndTyping');
800
801                         return;
802
803                 case "submit":
804                         tinyMCE.removeTinyMCEFormElements(tinyMCE.isMSIE ? window.event.srcElement : e.target);
805                         tinyMCE.triggerSave();
806                         tinyMCE.isNotDirty = true;
807                         return;
808
809                 case "reset":
810                         var formObj = tinyMCE.isMSIE ? window.event.srcElement : e.target;
811
812                         for (var i=0; i<document.forms.length; i++) {
813                                 if (document.forms[i] == formObj)
814                                         window.setTimeout('tinyMCE.resetForm(' + i + ');', 10);
815                         }
816
817                         return;
818
819                 case "keypress":
820                         if (e.target.editorId) {
821                                 tinyMCE.selectedInstance = tinyMCE.instances[e.target.editorId];
822                         } else {
823                                 if (e.target.ownerDocument.editorId)
824                                         tinyMCE.selectedInstance = tinyMCE.instances[e.target.ownerDocument.editorId];
825                         }
826
827                         if (tinyMCE.selectedInstance)
828                                 tinyMCE.selectedInstance.switchSettings();
829
830                         // Insert space instead of &nbsp;
831 /*                      if (tinyMCE.isGecko && e.charCode == 32) {
832                                 if (tinyMCE.selectedInstance._insertSpace()) {
833                                         // Cancel event
834                                         e.preventDefault();
835                                         return false;
836                                 }
837                         }*/
838
839                         // Insert P element
840                         if (tinyMCE.isGecko && tinyMCE.settings['force_p_newlines'] && e.keyCode == 13 && !e.shiftKey) {
841                                 // Insert P element instead of BR
842                                 if (tinyMCE.selectedInstance._insertPara(e)) {
843                                         // Cancel event
844                                         tinyMCE.execCommand("mceAddUndoLevel");
845                                         tinyMCE.cancelEvent(e);
846                                         return false;
847                                 }
848                         }
849
850                         // Handle backspace
851                         if (tinyMCE.isGecko && tinyMCE.settings['force_p_newlines'] && (e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) {
852                                 // Insert P element instead of BR
853                                 if (tinyMCE.selectedInstance._handleBackSpace(e.type)) {
854                                         // Cancel event
855                                         tinyMCE.execCommand("mceAddUndoLevel");
856                                         e.preventDefault();
857                                         return false;
858                                 }
859                         }
860
861                         // Mozilla custom key handling
862                         if (tinyMCE.isGecko && e.ctrlKey && tinyMCE.settings['custom_undo_redo']) {
863                                 if (tinyMCE.settings['custom_undo_redo_keyboard_shortcuts']) {
864                                         if (e.charCode == 122) { // Ctrl+Z
865                                                 tinyMCE.selectedInstance.execCommand("Undo");
866
867                                                 // Cancel event
868                                                 e.preventDefault();
869                                                 return false;
870                                         }
871
872                                         if (e.charCode == 121) { // Ctrl+Y
873                                                 tinyMCE.selectedInstance.execCommand("Redo");
874
875                                                 // Cancel event
876                                                 e.preventDefault();
877                                                 return false;
878                                         }
879                                 }
880
881                                 if (e.charCode == 98) { // Ctrl+B
882                                         tinyMCE.selectedInstance.execCommand("Bold");
883
884                                         // Cancel event
885                                         e.preventDefault();
886                                         return false;
887                                 }
888
889                                 if (e.charCode == 105) { // Ctrl+I
890                                         tinyMCE.selectedInstance.execCommand("Italic");
891
892                                         // Cancel event
893                                         e.preventDefault();
894                                         return false;
895                                 }
896
897                                 if (e.charCode == 117) { // Ctrl+U
898                                         tinyMCE.selectedInstance.execCommand("Underline");
899
900                                         // Cancel event
901                                         e.preventDefault();
902                                         return false;
903                                 }
904                         }
905
906                         // Return key pressed
907                         if (tinyMCE.isMSIE && tinyMCE.settings['force_br_newlines'] && e.keyCode == 13) {
908                                 if (e.target.editorId)
909                                         tinyMCE.selectedInstance = tinyMCE.instances[e.target.editorId];
910
911                                 if (tinyMCE.selectedInstance) {
912                                         var sel = tinyMCE.selectedInstance.getDoc().selection;
913                                         var rng = sel.createRange();
914
915                                         if (tinyMCE.getParentElement(rng.parentElement(), "li") != null)
916                                                 return false;
917
918                                         // Cancel event
919                                         e.returnValue = false;
920                                         e.cancelBubble = true;
921
922                                         // Insert BR element
923                                         rng.pasteHTML("<br />");
924                                         rng.collapse(false);
925                                         rng.select();
926
927                                         tinyMCE.execCommand("mceAddUndoLevel");
928                                         tinyMCE.triggerNodeChange(false);
929                                         return false;
930                                 }
931                         }
932
933                         // Backspace or delete
934                         if (e.keyCode == 8 || e.keyCode == 46) {
935                                 tinyMCE.selectedElement = e.target;
936                                 tinyMCE.linkElement = tinyMCE.getParentElement(e.target, "a");
937                                 tinyMCE.imgElement = tinyMCE.getParentElement(e.target, "img");
938                                 tinyMCE.triggerNodeChange(false);
939                         }
940
941                         return false;
942                 break;
943
944                 case "keyup":
945                 case "keydown":
946                         if (e.target.editorId)
947                                 tinyMCE.selectedInstance = tinyMCE.instances[e.target.editorId];
948                         else
949                                 return;
950
951                         if (tinyMCE.selectedInstance)
952                                 tinyMCE.selectedInstance.switchSettings();
953
954                         var inst = tinyMCE.selectedInstance;
955
956                         // Handle backspace
957                         if (tinyMCE.isGecko && tinyMCE.settings['force_p_newlines'] && (e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey) {
958                                 // Insert P element instead of BR
959                                 if (tinyMCE.selectedInstance._handleBackSpace(e.type)) {
960                                         // Cancel event
961                                         tinyMCE.execCommand("mceAddUndoLevel");
962                                         e.preventDefault();
963                                         return false;
964                                 }
965                         }
966
967                         tinyMCE.selectedElement = null;
968                         tinyMCE.selectedNode = null;
969                         var elm = tinyMCE.selectedInstance.getFocusElement();
970                         tinyMCE.linkElement = tinyMCE.getParentElement(elm, "a");
971                         tinyMCE.imgElement = tinyMCE.getParentElement(elm, "img");
972                         tinyMCE.selectedElement = elm;
973
974                         // Update visualaids on tabs
975                         if (tinyMCE.isGecko && e.type == "keyup" && e.keyCode == 9)
976                                 tinyMCE.handleVisualAid(tinyMCE.selectedInstance.getBody(), true, tinyMCE.settings['visual'], tinyMCE.selectedInstance);
977
978                         // Run image/link fix on Gecko if diffrent document base on paste
979                         if (tinyMCE.isGecko && tinyMCE.settings['document_base_url'] != "" + document.location.href && e.type == "keyup" && e.ctrlKey && e.keyCode == 86)
980                                 tinyMCE.selectedInstance.fixBrokenURLs();
981
982                         // Fix empty elements on return/enter, check where enter occured
983                         if (tinyMCE.isMSIE && e.type == "keydown" && e.keyCode == 13)
984                                 tinyMCE.enterKeyElement = tinyMCE.selectedInstance.getFocusElement();
985
986                         // Fix empty elements on return/enter
987                         if (tinyMCE.isMSIE && e.type == "keyup" && e.keyCode == 13) {
988                                 var elm = tinyMCE.enterKeyElement;
989                                 if (elm) {
990                                         var re = new RegExp('^HR|IMG|BR$','g'); // Skip these
991                                         var dre = new RegExp('^H[1-6]$','g'); // Add double on these
992
993                                         if (!elm.hasChildNodes() && !re.test(elm.nodeName)) {
994                                                 if (dre.test(elm.nodeName))
995                                                         elm.innerHTML = "&nbsp;&nbsp;";
996                                                 else
997                                                         elm.innerHTML = "&nbsp;";
998                                         }
999                                 }
1000                         }
1001
1002                         // Check if it's a position key
1003                         var keys = tinyMCE.posKeyCodes;
1004                         var posKey = false;
1005                         for (var i=0; i<keys.length; i++) {
1006                                 if (keys[i] == e.keyCode) {
1007                                         posKey = true;
1008                                         break;
1009                                 }
1010                         }
1011
1012                         //tinyMCE.debug(e.keyCode);
1013
1014                         // MSIE custom key handling
1015                         if (tinyMCE.isMSIE && tinyMCE.settings['custom_undo_redo']) {
1016                                 var keys = new Array(8,46); // Backspace,Delete
1017                                 for (var i=0; i<keys.length; i++) {
1018                                         if (keys[i] == e.keyCode) {
1019                                                 if (e.type == "keyup")
1020                                                         tinyMCE.triggerNodeChange(false);
1021                                         }
1022                                 }
1023
1024                                 if (tinyMCE.settings['custom_undo_redo_keyboard_shortcuts']) {
1025                                         if (e.keyCode == 90 && e.ctrlKey && e.type == "keydown") { // Ctrl+Z
1026                                                 tinyMCE.selectedInstance.execCommand("Undo");
1027                                                 tinyMCE.triggerNodeChange(false);
1028                                         }
1029
1030                                         if (e.keyCode == 89 && e.ctrlKey && e.type == "keydown") { // Ctrl+Y
1031                                                 tinyMCE.selectedInstance.execCommand("Redo");
1032                                                 tinyMCE.triggerNodeChange(false);
1033                                         }
1034
1035                                         if ((e.keyCode == 90 || e.keyCode == 89) && e.ctrlKey) {
1036                                                 // Cancel event
1037                                                 e.returnValue = false;
1038                                                 e.cancelBubble = true;
1039                                                 return false;
1040                                         }
1041                                 }
1042                         }
1043
1044                         // Handle Undo/Redo when typing content
1045
1046                         // Start typing (non position key)
1047                         if (!posKey && e.type == "keyup")
1048                                 tinyMCE.execCommand("mceStartTyping");
1049
1050                         // End typing (position key) or some Ctrl event
1051                         if (e.type == "keyup" && (posKey || e.ctrlKey))
1052                                 tinyMCE.execCommand("mceEndTyping");
1053
1054                         if (posKey && e.type == "keyup")
1055                                 tinyMCE.triggerNodeChange(false);
1056                 break;
1057
1058                 case "mousedown":
1059                 case "mouseup":
1060                 case "click":
1061                 case "focus":
1062                         if (tinyMCE.selectedInstance)
1063                                 tinyMCE.selectedInstance.switchSettings();
1064
1065                         // Check instance event trigged on
1066                         var targetBody = tinyMCE.getParentElement(e.target, "body");
1067                         for (var instanceName in tinyMCE.instances) {
1068                                 if (typeof(tinyMCE.instances[instanceName]) == 'function')
1069                                         continue;
1070
1071                                 var inst = tinyMCE.instances[instanceName];
1072
1073                                 // Reset design mode if lost (on everything just in case)
1074                                 inst.autoResetDesignMode();
1075
1076                                 if (inst.getBody() == targetBody) {
1077                                         tinyMCE.selectedInstance = inst;
1078                                         tinyMCE.selectedElement = e.target;
1079                                         tinyMCE.linkElement = tinyMCE.getParentElement(tinyMCE.selectedElement, "a");
1080                                         tinyMCE.imgElement = tinyMCE.getParentElement(tinyMCE.selectedElement, "img");
1081                                         break;
1082                                 }
1083                         }
1084
1085                         if (tinyMCE.isSafari) {
1086                                 tinyMCE.selectedInstance.lastSafariSelection = tinyMCE.selectedInstance.getBookmark();
1087                                 tinyMCE.selectedInstance.lastSafariSelectedElement = tinyMCE.selectedElement;
1088
1089                                 var lnk = tinyMCE.getParentElement(tinyMCE.selectedElement, "a");
1090
1091                                 // Patch the darned link
1092                                 if (lnk && e.type == "mousedown") {
1093                                         lnk.setAttribute("mce_real_href", lnk.getAttribute("href"));
1094                                         lnk.setAttribute("href", "javascript:void(0);");
1095                                 }
1096
1097                                 // Patch back
1098                                 if (lnk && e.type == "click") {
1099                                         window.setTimeout(function() {
1100                                                 lnk.setAttribute("href", lnk.getAttribute("mce_real_href"));
1101                                                 lnk.removeAttribute("mce_real_href");
1102                                         }, 10);
1103                                 }
1104                         }
1105
1106                         // Reset selected node
1107                         if (e.type != "focus")
1108                                 tinyMCE.selectedNode = null;
1109
1110                         tinyMCE.triggerNodeChange(false);
1111                         tinyMCE.execCommand("mceEndTyping");
1112
1113                         if (e.type == "mouseup")
1114                                 tinyMCE.execCommand("mceAddUndoLevel");
1115
1116                         // Just in case
1117                         if (!tinyMCE.selectedInstance && e.target.editorId)
1118                                 tinyMCE.selectedInstance = tinyMCE.instances[e.target.editorId];
1119
1120                         // Run image/link fix on Gecko if diffrent document base
1121                         if (tinyMCE.isGecko && tinyMCE.settings['document_base_url'] != "" + document.location.href)
1122                                 window.setTimeout('tinyMCE.getInstanceById("' + inst.editorId + '").fixBrokenURLs();', 10);
1123
1124                         return false;
1125                 break;
1126     } // end switch
1127 }; // end function
1128
1129 TinyMCE.prototype.switchClass = function(element, class_name, lock_state) {
1130         var lockChanged = false;
1131
1132         if (typeof(lock_state) != "undefined" && element != null) {
1133                 element.classLock = lock_state;
1134                 lockChanged = true;
1135         }
1136
1137         if (element != null && (lockChanged || !element.classLock)) {
1138                 element.oldClassName = element.className;
1139                 element.className = class_name;
1140         }
1141 };
1142
1143 TinyMCE.prototype.restoreAndSwitchClass = function(element, class_name) {
1144         if (element != null && !element.classLock) {
1145                 this.restoreClass(element);
1146                 this.switchClass(element, class_name);
1147         }
1148 };
1149
1150 TinyMCE.prototype.switchClassSticky = function(element_name, class_name, lock_state) {
1151         var element, lockChanged = false;
1152
1153         // Performance issue
1154         if (!this.stickyClassesLookup[element_name])
1155                 this.stickyClassesLookup[element_name] = document.getElementById(element_name);
1156
1157 //      element = document.getElementById(element_name);
1158         element = this.stickyClassesLookup[element_name];
1159
1160         if (typeof(lock_state) != "undefined" && element != null) {
1161                 element.classLock = lock_state;
1162                 lockChanged = true;
1163         }
1164
1165         if (element != null && (lockChanged || !element.classLock)) {
1166                 element.className = class_name;
1167                 element.oldClassName = class_name;
1168         }
1169 };
1170
1171 TinyMCE.prototype.restoreClass = function(element) {
1172         if (element != null && element.oldClassName && !element.classLock) {
1173                 element.className = element.oldClassName;
1174                 element.oldClassName = null;
1175         }
1176 };
1177
1178 TinyMCE.prototype.setClassLock = function(element, lock_state) {
1179         if (element != null)
1180                 element.classLock = lock_state;
1181 };
1182
1183 TinyMCE.prototype.addEvent = function(obj, name, handler) {
1184         if (tinyMCE.isMSIE) {
1185                 obj.attachEvent("on" + name, handler);
1186         } else
1187                 obj.addEventListener(name, handler, false);
1188 };
1189
1190 TinyMCE.prototype.submitPatch = function() {
1191         tinyMCE.removeTinyMCEFormElements(this);
1192         tinyMCE.triggerSave();
1193         this.mceOldSubmit();
1194         tinyMCE.isNotDirty = true;
1195 };
1196
1197 TinyMCE.prototype.onLoad = function() {
1198         for (var c=0; c<tinyMCE.configs.length; c++) {
1199                 tinyMCE.settings = tinyMCE.configs[c];
1200
1201                 var selector = tinyMCE.getParam("editor_selector");
1202                 var deselector = tinyMCE.getParam("editor_deselector");
1203                 var elementRefAr = new Array();
1204
1205                 // Add submit triggers
1206                 if (document.forms && tinyMCE.settings['add_form_submit_trigger'] && !tinyMCE.submitTriggers) {
1207                         for (var i=0; i<document.forms.length; i++) {
1208                                 var form = document.forms[i];
1209
1210                                 tinyMCE.addEvent(form, "submit", TinyMCE.prototype.handleEvent);
1211                                 tinyMCE.addEvent(form, "reset", TinyMCE.prototype.handleEvent);
1212                                 tinyMCE.submitTriggers = true; // Do it only once
1213
1214                                 // Patch the form.submit function
1215                                 if (tinyMCE.settings['submit_patch']) {
1216                                         try {
1217                                                 form.mceOldSubmit = form.submit;
1218                                                 form.submit = TinyMCE.prototype.submitPatch;
1219                                         } catch (e) {
1220                                                 // Do nothing
1221                                         }
1222                                 }
1223                         }
1224                 }
1225
1226                 // Add editor instances based on mode
1227                 var mode = tinyMCE.settings['mode'];
1228                 switch (mode) {
1229                         case "exact":
1230                                 var elements = tinyMCE.getParam('elements', '', true, ',');
1231
1232                                 for (var i=0; i<elements.length; i++) {
1233                                         var element = tinyMCE._getElementById(elements[i]);
1234                                         var trigger = element ? element.getAttribute(tinyMCE.settings['textarea_trigger']) : "";
1235
1236                                         if (tinyMCE.getAttrib(element, "class").indexOf(deselector) != -1)
1237                                                 continue;
1238
1239                                         if (trigger == "false")
1240                                                 continue;
1241
1242                                         if (tinyMCE.settings['ask'] && element) {
1243                                                 elementRefAr[elementRefAr.length] = element;
1244                                                 continue;
1245                                         }
1246
1247                                         if (element)
1248                                                 tinyMCE.addMCEControl(element, elements[i]);
1249                                         else if (tinyMCE.settings['debug'])
1250                                                 alert("Error: Could not find element by id or name: " + elements[i]);
1251                                 }
1252                         break;
1253
1254                         case "specific_textareas":
1255                         case "textareas":
1256                                 var nodeList = document.getElementsByTagName("textarea");
1257
1258                                 for (var i=0; i<nodeList.length; i++) {
1259                                         var elm = nodeList.item(i);
1260                                         var trigger = elm.getAttribute(tinyMCE.settings['textarea_trigger']);
1261
1262                                         if (selector != '' && tinyMCE.getAttrib(elm, "class").indexOf(selector) == -1)
1263                                                 continue;
1264
1265                                         if (tinyMCE.getAttrib(elm, "class").indexOf(deselector) != -1)
1266                                                 continue;
1267
1268                                         if ((mode == "specific_textareas" && trigger == "true") || (mode == "textareas" && trigger != "false"))
1269                                                 elementRefAr[elementRefAr.length] = elm;
1270                                 }
1271                         break;
1272                 }
1273
1274                 for (var i=0; i<elementRefAr.length; i++) {
1275                         var element = elementRefAr[i];
1276                         var elementId = element.name ? element.name : element.id;
1277
1278                         if (tinyMCE.settings['ask']) {
1279                                 // Focus breaks in Mozilla
1280                                 if (tinyMCE.isGecko) {
1281                                         var settings = tinyMCE.settings;
1282
1283                                         tinyMCE.addEvent(element, "focus", function (e) {window.setTimeout(function() {TinyMCE.prototype.confirmAdd(e, settings);}, 10);});
1284                                 } else {
1285                                         var settings = tinyMCE.settings;
1286
1287                                         tinyMCE.addEvent(element, "focus", function () { TinyMCE.prototype.confirmAdd(null, settings); });
1288                                 }
1289                         } else
1290                                 tinyMCE.addMCEControl(element, elementId);
1291                 }
1292
1293                 // Handle auto focus
1294                 if (tinyMCE.settings['auto_focus']) {
1295                         window.setTimeout(function () {
1296                                 var inst = tinyMCE.getInstanceById(tinyMCE.settings['auto_focus']);
1297                                 inst.selectNode(inst.getBody(), true, true);
1298                                 inst.contentWindow.focus();
1299                         }, 10);
1300                 }
1301
1302                 tinyMCE.executeCallback('oninit', '_oninit', 0);
1303         }
1304 };
1305
1306 TinyMCE.prototype.removeMCEControl = function(editor_id) {
1307         var inst = tinyMCE.getInstanceById(editor_id);
1308
1309         if (inst) {
1310                 inst.switchSettings();
1311
1312                 editor_id = inst.editorId;
1313                 var html = tinyMCE.getContent(editor_id);
1314
1315                 // Remove editor instance from instances array
1316                 var tmpInstances = new Array();
1317                 for (var instanceName in tinyMCE.instances) {
1318                         var instance = tinyMCE.instances[instanceName];
1319                         if (typeof(instance) == 'function')
1320                                 continue;
1321
1322                         if (instanceName != editor_id)
1323                                         tmpInstances[instanceName] = instance;
1324                 }
1325                 tinyMCE.instances = tmpInstances;
1326
1327                 tinyMCE.selectedElement = null;
1328                 tinyMCE.selectedInstance = null;
1329
1330                 // Remove element
1331                 var replaceElement = document.getElementById(editor_id + "_parent");
1332                 var oldTargetElement = inst.oldTargetElement;
1333                 var targetName = oldTargetElement.nodeName.toLowerCase();
1334
1335                 if (targetName == "textarea" || targetName == "input") {
1336                         // Just show the old text area
1337                         replaceElement.parentNode.removeChild(replaceElement);
1338                         oldTargetElement.style.display = "inline";
1339                         oldTargetElement.value = html;
1340                 } else {
1341                         oldTargetElement.innerHTML = html;
1342
1343                         replaceElement.parentNode.insertBefore(oldTargetElement, replaceElement);
1344                         replaceElement.parentNode.removeChild(replaceElement);
1345                 }
1346         }
1347 };
1348
1349 TinyMCE.prototype._cleanupElementName = function(element_name, element) {
1350         var name = "";
1351
1352         element_name = element_name.toLowerCase();
1353
1354         // Never include body
1355         if (element_name == "body")
1356                 return null;
1357
1358         // If verification mode
1359         if (tinyMCE.cleanup_verify_html) {
1360                 // Check if invalid element
1361                 for (var i=0; i<tinyMCE.cleanup_invalidElements.length; i++) {
1362                         if (tinyMCE.cleanup_invalidElements[i] == element_name)
1363                                 return null;
1364                 }
1365
1366                 // Check if valid element
1367                 var validElement = false;
1368                 var elementAttribs = null;
1369                 for (var i=0; i<tinyMCE.cleanup_validElements.length && !elementAttribs; i++) {
1370                         for (var x=0, n=tinyMCE.cleanup_validElements[i][0].length; x<n; x++) {
1371                                 var elmMatch = tinyMCE.cleanup_validElements[i][0][x];
1372
1373                                 if (elmMatch.charAt(0) == '+' || elmMatch.charAt(0) == '-')
1374                                         elmMatch = elmMatch.substring(1);
1375
1376                                 // Handle wildcard/regexp
1377                                 if (elmMatch.match(new RegExp('\\*|\\?|\\+', 'g')) != null) {
1378                                         elmMatch = elmMatch.replace(new RegExp('\\?', 'g'), '(\\S?)');
1379                                         elmMatch = elmMatch.replace(new RegExp('\\+', 'g'), '(\\S+)');
1380                                         elmMatch = elmMatch.replace(new RegExp('\\*', 'g'), '(\\S*)');
1381                                         elmMatch = "^" + elmMatch + "$";
1382                                         if (element_name.match(new RegExp(elmMatch, 'g'))) {
1383                                                 elementAttribs = tinyMCE.cleanup_validElements[i];
1384                                                 validElement = true;
1385                                                 break;
1386                                         }
1387                                 }
1388
1389                                 // Handle non regexp
1390                                 if (element_name == elmMatch) {
1391                                         elementAttribs = tinyMCE.cleanup_validElements[i];
1392                                         validElement = true;
1393                                         element_name = elementAttribs[0][0];
1394                                         break;
1395                                 }
1396                         }
1397                 }
1398
1399                 if (!validElement)
1400                         return null;
1401         }
1402
1403         if (element_name.charAt(0) == '+' || element_name.charAt(0) == '-')
1404                 name = element_name.substring(1);
1405
1406         // Special Mozilla stuff
1407         if (!tinyMCE.isMSIE) {
1408                 // Fix for bug #958498
1409                 if (name == "strong" && !tinyMCE.cleanup_on_save)
1410                         element_name = "b";
1411                 else if (name == "em" && !tinyMCE.cleanup_on_save)
1412                         element_name = "i";
1413         }
1414
1415         var elmData = new Object();
1416
1417         elmData.element_name = element_name;
1418         elmData.valid_attribs = elementAttribs;
1419
1420         return elmData;
1421 };
1422
1423 /**
1424  * This function moves CSS styles to/from attributes.
1425  */
1426 TinyMCE.prototype._moveStyle = function(elm, style, attrib) {
1427         if (tinyMCE.cleanup_inline_styles) {
1428                 var val = tinyMCE.getAttrib(elm, attrib);
1429
1430                 if (val != '') {
1431                         val = '' + val;
1432
1433                         switch (attrib) {
1434                                 case "background":
1435                                         val = "url('" + val + "');";
1436                                         break;
1437
1438                                 case "bordercolor":
1439                                         if (elm.style.borderStyle == '' || elm.style.borderStyle == 'none')
1440                                                 elm.style.borderStyle = 'solid';
1441                                         break;
1442
1443                                 case "border":
1444                                 case "width":
1445                                 case "height":
1446                                         if (attrib == "border" && elm.style.borderWidth > 0)
1447                                                 return;
1448
1449                                         if (val.indexOf('%') == -1)
1450                                                 val += 'px';
1451                                         break;
1452
1453                                 case "vspace":
1454                                 case "hspace":
1455                                         elm.style.marginTop = val + "px";
1456                                         elm.style.marginBottom = val + "px";
1457                                         elm.removeAttribute(attrib);
1458                                         return;
1459
1460                                 case "align":
1461                                         if (elm.nodeName == "IMG") {
1462                                                 if (tinyMCE.isMSIE)
1463                                                         elm.style.styleFloat = val;
1464                                                 else
1465                                                         elm.style.cssFloat = val;
1466                                         } else
1467                                                 elm.style.textAlign = val;
1468
1469                                         elm.removeAttribute(attrib);
1470                                         return;
1471                         }
1472
1473                         if (val != '') {
1474                                 eval('elm.style.' + style + ' = val;');
1475                                 elm.removeAttribute(attrib);
1476                         }
1477                 }
1478         } else {
1479                 if (style == '')
1480                         return;
1481
1482                 var val = eval('elm.style.' + style) == '' ? tinyMCE.getAttrib(elm, attrib) : eval('elm.style.' + style);
1483                 val = val == null ? '' : '' + val;
1484
1485                 switch (attrib) {
1486                         // Always move background to style
1487                         case "background":
1488                                 if (val.indexOf('url') == -1 && val != '')
1489                                         val = "url('" + val + "');";
1490
1491                                 if (val != '') {
1492                                         elm.style.backgroundImage = val;
1493                                         elm.removeAttribute(attrib);
1494                                 }
1495                                 return;
1496
1497                         case "border":
1498                         case "width":
1499                         case "height":
1500                                 val = val.replace('px', '');
1501                                 break;
1502
1503                         case "align":
1504                                 if (tinyMCE.getAttrib(elm, 'align') == '') {
1505                                         if (elm.nodeName == "IMG") {
1506                                                 if (tinyMCE.isMSIE && elm.style.styleFloat != '') {
1507                                                         val = elm.style.styleFloat;
1508                                                         style = 'styleFloat';
1509                                                 } else if (tinyMCE.isGecko && elm.style.cssFloat != '') {
1510                                                         val = elm.style.cssFloat;
1511                                                         style = 'cssFloat';
1512                                                 }
1513                                         }
1514                                 }
1515                                 break;
1516                 }
1517
1518                 if (val != '') {
1519                         elm.removeAttribute(attrib);
1520                         elm.setAttribute(attrib, val);
1521                         eval('elm.style.' + style + ' = "";');
1522                 }
1523         }
1524 };
1525
1526 TinyMCE.prototype._cleanupAttribute = function(valid_attributes, element_name, attribute_node, element_node) {
1527         var attribName = attribute_node.nodeName.toLowerCase();
1528         var attribValue = attribute_node.nodeValue;
1529         var attribMustBeValue = null;
1530         var verified = false;
1531
1532         // Mozilla attibute, remove them
1533         if (attribName.indexOf('moz_') != -1)
1534                 return null;
1535
1536         // Mozilla fix for drag-drop/copy/paste images
1537         if (!tinyMCE.isMSIE && (attribName == "mce_real_href" || attribName == "mce_real_src")) {
1538                 if (!tinyMCE.cleanup_on_save) {
1539                         var attrib = new Object();
1540
1541                         attrib.name = attribName;
1542                         attrib.value = attribValue;
1543
1544                         return attrib;
1545                 } else
1546                         return null;
1547         }
1548
1549         // Auto verify 
1550         if (attribName == "mce_onclick")
1551                 verified = true;
1552
1553         // Verify attrib
1554         if (tinyMCE.cleanup_verify_html && !verified) {
1555                 for (var i=1; i<valid_attributes.length; i++) {
1556                         var attribMatch = valid_attributes[i][0];
1557                         var re = null;
1558
1559                         // Build regexp from wildcard
1560                         if (attribMatch.match(new RegExp('\\*|\\?|\\+', 'g')) != null) {
1561                                 attribMatch = attribMatch.replace(new RegExp('\\?', 'g'), '(\\S?)');
1562                                 attribMatch = attribMatch.replace(new RegExp('\\+', 'g'), '(\\S+)');
1563                                 attribMatch = attribMatch.replace(new RegExp('\\*', 'g'), '(\\S*)');
1564                                 attribMatch = "^" + attribMatch + "$";
1565                                 re = new RegExp(attribMatch, 'g');
1566                         }
1567
1568                         if ((re && attribName.match(re) != null) || attribName == attribMatch) {
1569                                 verified = true;
1570                                 attribMustBeValue = valid_attributes[i][3];
1571                                 break;
1572                         }
1573                 }
1574
1575                 if (!verified)
1576                         return false;
1577         } else
1578                 verified = true;
1579
1580         // Treat some attribs diffrent
1581         switch (attribName) {
1582                 case "size":
1583                         if (tinyMCE.isMSIE5 && element_name == "font")
1584                                 attribValue = element_node.size;
1585                         break;
1586
1587                 case "width":
1588                 case "height":
1589                 case "border":
1590                         // Old MSIE needs this
1591                         if (tinyMCE.isMSIE5)
1592                                 attribValue = eval("element_node." + attribName);
1593                         break;
1594
1595                 case "shape":
1596                         attribValue = attribValue.toLowerCase();
1597                         break;
1598
1599                 case "color":
1600                         if (tinyMCE.isMSIE5 && element_name == "font")
1601                                 attribValue = element_node.color;
1602                         break;
1603
1604                 case "class":
1605                         if (element_name == "table" || element_name == "td") {
1606                                 // Handle visual aid
1607                                 if (tinyMCE.cleanup_visual_table_class != "")
1608                                         attribValue = tinyMCE.getVisualAidClass(attribValue, !tinyMCE.cleanup_on_save);
1609                         }
1610
1611                         if (!tinyMCE._verifyClass(element_node) || attribValue == "")
1612                                 return null;
1613
1614                         break;
1615
1616                 case "onfocus":
1617                 case "onblur":
1618                 case "onclick":
1619                 case "ondblclick":
1620                 case "onmousedown":
1621                 case "onmouseup":
1622                 case "onmouseover":
1623                 case "onmousemove":
1624                 case "onmouseout":
1625                 case "onkeypress":
1626                 case "onkeydown":
1627                 case "onkeydown":
1628                 case "onkeyup":
1629                         attribValue = tinyMCE.cleanupEventStr("" + attribValue);
1630
1631                         if (attribValue.indexOf('return false;') == 0)
1632                                 attribValue = attribValue.substring(14);
1633
1634                         break;
1635
1636                 case "style":
1637                         attribValue = tinyMCE.serializeStyle(tinyMCE.parseStyle(element_node.style.cssText));
1638                         break;
1639
1640                 // Convert the URLs of these
1641                 case "href":
1642                 case "src":
1643                         // Fix for dragdrop/copy paste Mozilla issue
1644                         if (!tinyMCE.isMSIE && attribName == "href" && element_node.getAttribute("mce_real_href"))
1645                                 attribValue = element_node.getAttribute("mce_real_href");
1646
1647                         // Fix for dragdrop/copy paste Mozilla issue
1648                         if (!tinyMCE.isMSIE && attribName == "src" && element_node.getAttribute("mce_real_src"))
1649                                 attribValue = element_node.getAttribute("mce_real_src");
1650
1651                         // Force absolute URLs in Firefox
1652                         if (tinyMCE.isGecko && !tinyMCE.settings['relative_urls'])
1653                                 attribValue = tinyMCE.convertRelativeToAbsoluteURL(tinyMCE.settings['base_href'], attribValue);
1654
1655                         attribValue = eval(tinyMCE.cleanup_urlconverter_callback + "(attribValue, element_node, tinyMCE.cleanup_on_save);");
1656                         break;
1657
1658                 case "colspan":
1659                 case "rowspan":
1660                         // Not needed
1661                         if (attribValue == "1")
1662                                 return null;
1663                         break;
1664
1665                 // Skip these
1666                 case "_moz-userdefined":
1667                 case "editorid":
1668                 case "mce_real_href":
1669                 case "mce_real_src":
1670                         return null;
1671         }
1672
1673         // Not the must be value
1674         if (attribMustBeValue != null) {
1675                 var isCorrect = false;
1676                 for (var i=0; i<attribMustBeValue.length; i++) {
1677                         if (attribValue == attribMustBeValue[i]) {
1678                                 isCorrect = true;
1679                                 break;
1680                         }
1681                 }
1682
1683                 if (!isCorrect)
1684                         return null;
1685         }
1686
1687         var attrib = new Object();
1688
1689         attrib.name = attribName;
1690         attrib.value = attribValue;
1691
1692         return attrib;
1693 };
1694
1695 TinyMCE.prototype.parseStyle = function(str) {
1696         var ar = new Array();
1697         var st = str.split(';');
1698
1699         for (var i=0; i<st.length; i++) {
1700                 if (st[i] == '')
1701                         continue;
1702
1703                 var re = new RegExp('^\\s*([^:]*):\\s*(.*)\\s*$');
1704                 var pa = st[i].replace(re, '$1||$2').split('||');
1705 //tinyMCE.debug(str, pa[0] + "=" + pa[1], st[i].replace(re, '$1||$2'));
1706                 if (pa.length == 2)
1707                         ar[pa[0].toLowerCase()] = pa[1];
1708         }
1709
1710         return ar;
1711 };
1712
1713 TinyMCE.prototype.compressStyle = function(ar, pr, sf, res) {
1714         var box = new Array();
1715
1716         box[0] = ar[pr + '-top' + sf];
1717         box[1] = ar[pr + '-left' + sf];
1718         box[2] = ar[pr + '-right' + sf];
1719         box[3] = ar[pr + '-bottom' + sf];
1720
1721         for (var i=0; i<box.length; i++) {
1722                 if (box[i] == null)
1723                         return;
1724
1725                 for (var a=0; a<box.length; a++) {
1726                         if (box[a] != box[i])
1727                                 return;
1728                 }
1729         }
1730
1731         // They are all the same
1732         ar[res] = box[0];
1733         ar[pr + '-top' + sf] = null;
1734         ar[pr + '-left' + sf] = null;
1735         ar[pr + '-right' + sf] = null;
1736         ar[pr + '-bottom' + sf] = null;
1737 };
1738
1739 TinyMCE.prototype.serializeStyle = function(ar) {
1740         var str = "";
1741
1742         // Compress box
1743         tinyMCE.compressStyle(ar, "border", "", "border");
1744         tinyMCE.compressStyle(ar, "border", "-width", "border-width");
1745         tinyMCE.compressStyle(ar, "border", "-color", "border-color");
1746
1747         for (var key in ar) {
1748                 var val = ar[key];
1749                 if (typeof(val) == 'function')
1750                         continue;
1751
1752                 if (val != null && val != '') {
1753                         val = '' + val; // Force string
1754
1755                         // Fix style URL
1756                         val = val.replace(new RegExp("url\\(\\'?([^\\']*)\\'?\\)", 'gi'), "url('$1')");
1757
1758                         if (val != "url('')")
1759                                 str += key.toLowerCase() + ": " + val + "; ";
1760                 }
1761         }
1762
1763         if (new RegExp('; $').test(str))
1764                 str = str.substring(0, str.length - 2);
1765
1766         return str;
1767 };
1768
1769 TinyMCE.prototype._verifyClass = function(node) {
1770         // Sometimes the class gets set to null, weird Gecko bug?
1771         if (tinyMCE.isGecko) {
1772                 var className = node.getAttribute('class');
1773                 if (!className)
1774                         return false;
1775         }
1776
1777         // Trim CSS class
1778         if (tinyMCE.isMSIE)
1779                 var className = node.getAttribute('className');
1780
1781         if (tinyMCE.cleanup_verify_css_classes && tinyMCE.cleanup_on_save) {
1782                 var csses = tinyMCE.getCSSClasses();
1783                 nonDefinedCSS = true;
1784                 for (var c=0; c<csses.length; c++) {
1785                         if (csses[c] == className) {
1786                                 nonDefinedCSS = false;
1787                                 break;
1788                         }
1789                 }
1790
1791                 if (nonDefinedCSS && className.indexOf('mce_') != 0) {
1792                         node.removeAttribute('className');
1793                         node.removeAttribute('class');
1794                         return false;
1795                 }
1796         }
1797
1798         return true;
1799 };
1800
1801 TinyMCE.prototype.cleanupNode = function(node) {
1802         var output = "";
1803
1804         switch (node.nodeType) {
1805                 case 1: // Element
1806                         var elementData = tinyMCE._cleanupElementName(node.nodeName, node);
1807                         var elementName = elementData ? elementData.element_name : null;
1808                         var elementValidAttribs = elementData ? elementData.valid_attribs : null;
1809                         var elementAttribs = "";
1810                         var openTag = false, nonEmptyTag = false;
1811
1812                         if (elementName != null && elementName.charAt(0) == '+') {
1813                                 elementName = elementName.substring(1);
1814                                 openTag = true;
1815                         }
1816
1817                         if (elementName != null && elementName.charAt(0) == '-') {
1818                                 elementName = elementName.substring(1);
1819                                 nonEmptyTag = true;
1820                         }
1821
1822                         // Checking DOM tree for MSIE weirdness!!
1823                         if (tinyMCE.isMSIE && tinyMCE.settings['fix_content_duplication']) {
1824                                 var lookup = tinyMCE.cleanup_elementLookupTable;
1825
1826                                 for (var i=0; i<lookup.length; i++) {
1827                                         // Found element reference else were, hmm?
1828                                         if (lookup[i] == node)
1829                                                 return output;
1830                                 }
1831
1832                                 // Add element to lookup table
1833                                 lookup[lookup.length] = node;
1834                         }
1835
1836                         // Element not valid (only render children)
1837                         if (!elementName) {
1838                                 if (node.hasChildNodes()) {
1839                                         for (var i=0; i<node.childNodes.length; i++)
1840                                                 output += this.cleanupNode(node.childNodes[i]);
1841                                 }
1842
1843                                 return output;
1844                         }
1845
1846                         if (tinyMCE.cleanup_on_save) {
1847                                 if (node.nodeName == "A" && node.className == "mceItemAnchor") {
1848                                         if (node.hasChildNodes()) {
1849                                                 for (var i=0; i<node.childNodes.length; i++)
1850                                                         output += this.cleanupNode(node.childNodes[i]);
1851                                         }
1852
1853                                         return '<a name="' + this.convertStringToXML(node.getAttribute("name")) + '"></a>' + output;
1854                                 }
1855                         }
1856
1857                         // Remove deprecated attributes
1858                         var re = new RegExp("^(TABLE|TD|TR)$");
1859                         if (re.test(node.nodeName)) {
1860                                 // Move attrib to style
1861                                 if ((node.nodeName != "TABLE" || tinyMCE.cleanup_inline_styles) && (width = tinyMCE.getAttrib(node, "width")) != '') {
1862                                         node.style.width = width.indexOf('%') == -1 ? width : width + "px";
1863                                         node.removeAttribute("width");
1864                                 }
1865
1866                                 // Is table and not inline
1867                                 if ((node.nodeName == "TABLE" && !tinyMCE.cleanup_inline_styles) && node.style.width != '') {
1868                                         tinyMCE.setAttrib(node, "width", node.style.width.replace('px',''));
1869                                         node.style.width = '';
1870                                 }
1871
1872                                 // Move attrib to style
1873                                 if ((height = tinyMCE.getAttrib(node, "height")) != '') {
1874                                         node.style.height = height.indexOf('%') == -1 ? height : height + "px";
1875                                         node.removeAttribute("height");
1876                                 }
1877                         }
1878
1879                         // Handle inline/outline styles
1880                         if (tinyMCE.cleanup_inline_styles) {
1881                                 var re = new RegExp("^(TABLE|TD|TR|IMG|HR)$");
1882                                 if (re.test(node.nodeName)) {
1883                                         tinyMCE._moveStyle(node, 'width', 'width');
1884                                         tinyMCE._moveStyle(node, 'height', 'height');
1885                                         tinyMCE._moveStyle(node, 'borderWidth', 'border');
1886                                         tinyMCE._moveStyle(node, '', 'vspace');
1887                                         tinyMCE._moveStyle(node, '', 'hspace');
1888                                         tinyMCE._moveStyle(node, 'textAlign', 'align');
1889                                         tinyMCE._moveStyle(node, 'backgroundColor', 'bgColor');
1890                                         tinyMCE._moveStyle(node, 'borderColor', 'borderColor');
1891                                         tinyMCE._moveStyle(node, 'backgroundImage', 'background');
1892
1893                                         // Refresh element in old MSIE
1894                                         if (tinyMCE.isMSIE5)
1895                                                 node.outerHTML = node.outerHTML;
1896                                 } else if (tinyMCE.isBlockElement(node))
1897                                         tinyMCE._moveStyle(node, 'textAlign', 'align');
1898
1899                                 if (node.nodeName == "FONT")
1900                                         tinyMCE._moveStyle(node, 'color', 'color');
1901                         }
1902
1903                         // Set attrib data
1904                         if (elementValidAttribs) {
1905                                 for (var a=1; a<elementValidAttribs.length; a++) {
1906                                         var attribName, attribDefaultValue, attribForceValue, attribValue;
1907
1908                                         attribName = elementValidAttribs[a][0];
1909                                         attribDefaultValue = elementValidAttribs[a][1];
1910                                         attribForceValue = elementValidAttribs[a][2];
1911
1912                                         if (attribDefaultValue != null || attribForceValue != null) {
1913                                                 var attribValue = node.getAttribute(attribName);
1914
1915                                                 if (node.getAttribute(attribName) == null || node.getAttribute(attribName) == "")
1916                                                         attribValue = attribDefaultValue;
1917
1918                                                 attribValue = attribForceValue ? attribForceValue : attribValue;
1919
1920                                                 // Is to generate id
1921                                                 if (attribValue == "{$uid}")
1922                                                         attribValue = "uid_" + (tinyMCE.cleanup_idCount++);
1923
1924                                                 // Add visual aid class
1925                                                 if (attribName == "class")
1926                                                         attribValue = tinyMCE.getVisualAidClass(attribValue, tinyMCE.cleanup_on_save);
1927
1928                                                 node.setAttribute(attribName, attribValue);
1929                                                 //alert(attribName + "=" + attribValue);
1930                                         }
1931                                 }
1932                         }
1933
1934                         // Remove empty tables
1935                         if (elementName == "table" && !node.hasChildNodes())
1936                                 return "";
1937
1938                         // Handle element attributes
1939                         if (node.attributes.length > 0) {
1940                                 for (var i=0; i<node.attributes.length; i++) {
1941                                         if (node.attributes[i].specified || (node.attributes[i].nodeValue != null && node.attributes[i].nodeValue.length > 0)) {
1942                                                 // tinyMCE.debug(node.attributes[i].nodeName + "=" + node.attributes[i].nodeValue);
1943                                                 var attrib = tinyMCE._cleanupAttribute(elementValidAttribs, elementName, node.attributes[i], node);
1944                                                 if (attrib && attrib.value != "")
1945                                                         elementAttribs += " " + attrib.name + "=" + '"' + this.convertStringToXML("" + attrib.value) + '"';
1946                                         }
1947                                 }
1948                         }
1949
1950                         // MSIE table summary fix (MSIE 5.5)
1951                         if (tinyMCE.isMSIE && elementName == "table" && node.getAttribute("summary") != null && elementAttribs.indexOf('summary') == -1) {
1952                                 var summary = tinyMCE.getAttrib(node, 'summary');
1953                                 if (summary != '')
1954                                         elementAttribs += " summary=" + '"' + this.convertStringToXML(summary) + '"';
1955                         }
1956
1957                         // MSIE form element issue
1958                         if (tinyMCE.isMSIE && elementName == "input") {
1959                                 if (node.type) {
1960                                         if (!elementAttribs.match(/ type=/g))
1961                                                 elementAttribs += " type=" + '"' + node.type + '"';
1962                                 }
1963
1964                                 if (node.value) {
1965                                         if (!elementAttribs.match(/ value=/g))
1966                                                 elementAttribs += " value=" + '"' + node.value + '"';
1967                                 }
1968                         }
1969
1970                         // Add nbsp to some elements
1971                         if ((elementName == "p" || elementName == "td") && (node.innerHTML == "" || node.innerHTML == "&nbsp;"))
1972                                 return "<" + elementName + elementAttribs + ">" + this.convertStringToXML(String.fromCharCode(160)) + "</" + elementName + ">";
1973
1974                         // Is MSIE script element
1975                         if (tinyMCE.isMSIE && elementName == "script")
1976                                 return "<" + elementName + elementAttribs + ">" + node.text + "</" + elementName + ">";
1977
1978                         // Clean up children
1979                         if (node.hasChildNodes()) {
1980                                 // Force BR
1981                                 if (elementName == "p" && tinyMCE.cleanup_force_br_newlines)
1982                                         output += "<div" + elementAttribs + ">";
1983                                 else
1984                                         output += "<" + elementName + elementAttribs + ">";
1985
1986                                 for (var i=0; i<node.childNodes.length; i++)
1987                                         output += this.cleanupNode(node.childNodes[i]);
1988
1989                                 // Force BR
1990                                 if (elementName == "p" && tinyMCE.cleanup_force_br_newlines)
1991                                         output += "</div><br />";
1992                                 else
1993                                         output += "</" + elementName + ">";
1994                         } else {
1995                                 if (!nonEmptyTag) {
1996                                         if (openTag)
1997                                                 output += "<" + elementName + elementAttribs + "></" + elementName + ">";
1998                                         else
1999                                                 output += "<" + elementName + elementAttribs + " />";
2000                                 }
2001                         }
2002
2003                         return output;
2004
2005                 case 3: // Text
2006                         // Do not convert script elements
2007                         if (node.parentNode.nodeName.toLowerCase() == "script")
2008                                 return node.nodeValue;
2009
2010                         return this.convertStringToXML(node.nodeValue);
2011
2012                 case 8: // Comment
2013                         return "<!--" + node.nodeValue + "-->";
2014
2015                 default: // Unknown
2016                         return "[UNKNOWN NODETYPE " + node.nodeType + "]";
2017         }
2018 };
2019
2020 TinyMCE.prototype.convertStringToXML = function(html_data) {
2021     var output = "";
2022
2023         for (var i=0; i<html_data.length; i++) {
2024                 var chr = html_data.charCodeAt(i);
2025
2026                 // Numeric entities
2027                 if (tinyMCE.settings['entity_encoding'] == "numeric") {
2028                         if (chr > 127)
2029                                 output += '&#' + chr + ";";
2030                         else
2031                                 output += String.fromCharCode(chr);
2032
2033                         continue;
2034                 }
2035
2036                 // Raw entities
2037                 if (tinyMCE.settings['entity_encoding'] == "raw") {
2038                         output += String.fromCharCode(chr);
2039                         continue;
2040                 }
2041
2042                 // Named entities
2043                 if (typeof(tinyMCE.cleanup_entities["c" + chr]) != 'undefined' && tinyMCE.cleanup_entities["c" + chr] != '')
2044                         output += '&' + tinyMCE.cleanup_entities["c" + chr] + ';';
2045                 else
2046                         output += '' + String.fromCharCode(chr);
2047     }
2048
2049     return output;
2050 };
2051
2052 TinyMCE.prototype._getCleanupElementName = function(chunk) {
2053         var pos;
2054
2055         if (chunk.charAt(0) == '+')
2056                 chunk = chunk.substring(1);
2057
2058         if (chunk.charAt(0) == '-')
2059                 chunk = chunk.substring(1);
2060
2061         if ((pos = chunk.indexOf('/')) != -1)
2062                 chunk = chunk.substring(0, pos);
2063
2064         if ((pos = chunk.indexOf('[')) != -1)
2065                 chunk = chunk.substring(0, pos);
2066
2067         return chunk;
2068 };
2069
2070 TinyMCE.prototype._initCleanup = function() {
2071         // Parse valid elements and attributes
2072         var validElements = tinyMCE.settings["valid_elements"];
2073         validElements = validElements.split(',');
2074
2075         // Handle extended valid elements
2076         var extendedValidElements = tinyMCE.settings["extended_valid_elements"];
2077         extendedValidElements = extendedValidElements.split(',');
2078         for (var i=0; i<extendedValidElements.length; i++) {
2079                 var elementName = this._getCleanupElementName(extendedValidElements[i]);
2080                 var skipAdd = false;
2081
2082                 // Check if it's defined before, if so override that one
2083                 for (var x=0; x<validElements.length; x++) {
2084                         if (this._getCleanupElementName(validElements[x]) == elementName) {
2085                                 validElements[x] = extendedValidElements[i];
2086                                 skipAdd = true;
2087                                 break;
2088                         }
2089                 }
2090
2091                 if (!skipAdd)
2092                         validElements[validElements.length] = extendedValidElements[i];
2093         }
2094
2095         for (var i=0; i<validElements.length; i++) {
2096                 var item = validElements[i];
2097
2098                 item = item.replace('[','|');
2099                 item = item.replace(']','');
2100
2101                 // Split and convert
2102                 var attribs = item.split('|');
2103                 for (var x=0; x<attribs.length; x++)
2104                         attribs[x] = attribs[x].toLowerCase();
2105
2106                 // Handle change elements
2107                 attribs[0] = attribs[0].split('/');
2108
2109                 // Handle default attribute values
2110                 for (var x=1; x<attribs.length; x++) {
2111                         var attribName = attribs[x];
2112                         var attribDefault = null;
2113                         var attribForce = null;
2114                         var attribMustBe = null;
2115
2116                         // Default value
2117                         if ((pos = attribName.indexOf('=')) != -1) {
2118                                 attribDefault = attribName.substring(pos+1);
2119                                 attribName = attribName.substring(0, pos);
2120                         }
2121
2122                         // Force check
2123                         if ((pos = attribName.indexOf(':')) != -1) {
2124                                 attribForce = attribName.substring(pos+1);
2125                                 attribName = attribName.substring(0, pos);
2126                         }
2127
2128                         // Force check
2129                         if ((pos = attribName.indexOf('<')) != -1) {
2130                                 attribMustBe = attribName.substring(pos+1).split('?');
2131                                 attribName = attribName.substring(0, pos);
2132                         }
2133
2134                         attribs[x] = new Array(attribName, attribDefault, attribForce, attribMustBe);
2135                 }
2136
2137                 validElements[i] = attribs;
2138         }
2139
2140         var invalidElements = tinyMCE.settings['invalid_elements'].split(',');
2141         for (var i=0; i<invalidElements.length; i++)
2142                 invalidElements[i] = invalidElements[i].toLowerCase();
2143
2144         // Set these for performance
2145         tinyMCE.settings['cleanup_validElements'] = validElements;
2146         tinyMCE.settings['cleanup_invalidElements'] = invalidElements;
2147
2148         // Setup entities
2149         tinyMCE.settings['cleanup_entities'] = new Array();
2150         var entities = tinyMCE.getParam('entities', '', true, ',');
2151         for (var i=0; i<entities.length; i+=2)
2152                 tinyMCE.settings['cleanup_entities']['c' + entities[i]] = entities[i+1];
2153 };
2154
2155 TinyMCE.prototype._cleanupHTML = function(inst, doc, config, element, visual, on_save) {
2156         if (!tinyMCE.settings['cleanup'])
2157                 return element.innerHTML;
2158
2159         // Call custom cleanup code
2160         tinyMCE._customCleanup(inst, on_save ? "get_from_editor_dom" : "insert_to_editor_dom", doc.body);
2161
2162         // Set these for performance
2163         tinyMCE.cleanup_validElements = tinyMCE.settings['cleanup_validElements'];
2164         tinyMCE.cleanup_entities = tinyMCE.settings['cleanup_entities'];
2165         tinyMCE.cleanup_invalidElements = tinyMCE.settings['cleanup_invalidElements'];
2166         tinyMCE.cleanup_verify_html = tinyMCE.settings['verify_html'];
2167         tinyMCE.cleanup_force_br_newlines = tinyMCE.settings['force_br_newlines'];
2168         tinyMCE.cleanup_urlconverter_callback = tinyMCE.settings['urlconverter_callback'];
2169         tinyMCE.cleanup_verify_css_classes = tinyMCE.settings['verify_css_classes'];
2170         tinyMCE.cleanup_visual_table_class = tinyMCE.settings['visual_table_class'];
2171         tinyMCE.cleanup_apply_source_formatting = tinyMCE.settings['apply_source_formatting'];
2172         tinyMCE.cleanup_inline_styles = tinyMCE.settings['inline_styles'];
2173         tinyMCE.cleanup_visual_aid = visual;
2174         tinyMCE.cleanup_on_save = on_save;
2175         tinyMCE.cleanup_idCount = 0;
2176         tinyMCE.cleanup_elementLookupTable = new Array();
2177
2178         var startTime = new Date().getTime();
2179
2180         // Cleanup madness that breaks the editor in MSIE
2181         if (tinyMCE.isMSIE) {
2182                 // Remove null ids from HR elements, results in runtime error
2183                 var nodes = element.getElementsByTagName("hr");
2184                 for (var i=0; i<nodes.length; i++) {
2185                         if (nodes[i].id == "null")
2186                                 nodes[i].removeAttribute("id");
2187                 }
2188
2189                 element.innerHTML = tinyMCE.regexpReplace(element.innerHTML, '<p>[ \n\r]*<hr.*>[ \n\r]*</p>', '<hr />', 'gi');
2190                 element.innerHTML = tinyMCE.regexpReplace(element.innerHTML, '<!([^-(DOCTYPE)]* )|<!/[^-]*>', '', 'gi');
2191         }
2192
2193         var html = this.cleanupNode(element);
2194
2195         if (tinyMCE.settings['debug'])
2196                 tinyMCE.debug("Cleanup process executed in: " + (new Date().getTime()-startTime) + " ms.");
2197
2198         // Remove pesky HR paragraphs
2199         html = tinyMCE.regexpReplace(html, '<p><hr /></p>', '<hr />');
2200         html = tinyMCE.regexpReplace(html, '<p>&nbsp;</p><hr /><p>&nbsp;</p>', '<hr />');
2201         html = tinyMCE.regexpReplace(html, '<td>\\s*<br />\\s*</td>', '<td>&nbsp;</td>');
2202
2203         // Remove empty anchors
2204         html = html.replace(new RegExp('<a>(.*?)</a>', 'gi'), '$1');
2205
2206         // Move contents behind anchors
2207         html = html.replace(new RegExp('<a(.*?)name="(.*?)"(.*?)>(.*?)</a>', 'gi'), '<a$1name="$2"$3></a>$4');
2208
2209         // Remove some mozilla crap
2210         if (!tinyMCE.isMSIE)
2211                 html = html.replace(new RegExp('<o:p _moz-userdefined="" />', 'g'), "");
2212
2213         if (tinyMCE.settings['apply_source_formatting']) {
2214                 html = html.replace(new RegExp('<(p|div)([^>]*)>', 'g'), "\n<$1$2>\n");
2215                 html = html.replace(new RegExp('<\/(p|div)([^>]*)>', 'g'), "\n</$1$2>\n");
2216                 html = html.replace(new RegExp('<br />', 'g'), "<br />\n");
2217         }
2218
2219         if (tinyMCE.settings['force_br_newlines']) {
2220                 var re = new RegExp('<p>&nbsp;</p>', 'g');
2221                 html = html.replace(re, "<br />");
2222         }
2223
2224         if (tinyMCE.settings['force_p_newlines']) {
2225                 // Remove weridness!
2226                 var re = new RegExp('&lt;&gt;', 'g');
2227                 html = html.replace(re, "");
2228         }
2229
2230         if (tinyMCE.settings['remove_linebreaks'])
2231                 html = html.replace(new RegExp('\r|\n', 'g'), ' ');
2232
2233         // Call custom cleanup code
2234         html = tinyMCE._customCleanup(inst, on_save ? "get_from_editor" : "insert_to_editor", html);
2235
2236         // Emtpy node, return empty
2237         var chk = tinyMCE.regexpReplace(html, "[ \t\r\n]", "").toLowerCase();
2238         if (chk == "<br/>" || chk == "<br>" || chk == "<p>&nbsp;</p>" || chk == "<p>&#160;</p>" || chk == "<p></p>")
2239                 html = "";
2240
2241         if (tinyMCE.settings["preformatted"])
2242                 return "<pre>" + html + "</pre>";
2243
2244         return html;
2245 };
2246
2247 TinyMCE.prototype.insertLink = function(href, target, title, onclick, style_class) {
2248         tinyMCE.execCommand('mceBeginUndoLevel');
2249
2250         if (this.selectedInstance && this.selectedElement && this.selectedElement.nodeName.toLowerCase() == "img") {
2251                 var doc = this.selectedInstance.getDoc();
2252                 var linkElement = tinyMCE.getParentElement(this.selectedElement, "a");
2253                 var newLink = false;
2254
2255                 if (!linkElement) {
2256                         linkElement = doc.createElement("a");
2257                         newLink = true;
2258                 }
2259
2260                 href = eval(tinyMCE.settings['urlconverter_callback'] + "(href, linkElement);");
2261                 tinyMCE.setAttrib(linkElement, 'href', href);
2262                 tinyMCE.setAttrib(linkElement, 'target', target);
2263                 tinyMCE.setAttrib(linkElement, 'title', title);
2264         tinyMCE.setAttrib(linkElement, 'onclick', onclick);
2265                 tinyMCE.setAttrib(linkElement, 'class', style_class);
2266
2267                 if (newLink) {
2268                         linkElement.appendChild(this.selectedElement.cloneNode(true));
2269                         this.selectedElement.parentNode.replaceChild(linkElement, this.selectedElement);
2270                 }
2271
2272                 return;
2273         }
2274
2275         if (!this.linkElement && this.selectedInstance) {
2276                 if (tinyMCE.isSafari) {
2277                         tinyMCE.execCommand("mceInsertContent", false, '<a href="#mce_temp_url#">' + this.selectedInstance.getSelectedHTML() + '</a>');
2278                 } else
2279                         this.selectedInstance.contentDocument.execCommand("createlink", false, "#mce_temp_url#");
2280
2281                 tinyMCE.linkElement = this.getElementByAttributeValue(this.selectedInstance.contentDocument.body, "a", "href", "#mce_temp_url#");
2282
2283                 var elementArray = this.getElementsByAttributeValue(this.selectedInstance.contentDocument.body, "a", "href", "#mce_temp_url#");
2284
2285                 for (var i=0; i<elementArray.length; i++) {
2286                         href = eval(tinyMCE.settings['urlconverter_callback'] + "(href, elementArray[i]);");
2287                         tinyMCE.setAttrib(elementArray[i], 'href', href);
2288                         tinyMCE.setAttrib(elementArray[i], 'mce_real_href', href);
2289                         tinyMCE.setAttrib(elementArray[i], 'target', target);
2290                         tinyMCE.setAttrib(elementArray[i], 'title', title);
2291             tinyMCE.setAttrib(elementArray[i], 'onclick', onclick);
2292                         tinyMCE.setAttrib(elementArray[i], 'class', style_class);
2293                 }
2294
2295                 tinyMCE.linkElement = elementArray[0];
2296         }
2297
2298         if (this.linkElement) {
2299                 href = eval(tinyMCE.settings['urlconverter_callback'] + "(href, this.linkElement);");
2300                 tinyMCE.setAttrib(this.linkElement, 'href', href);
2301                 tinyMCE.setAttrib(this.linkElement, 'mce_real_href', href);
2302                 tinyMCE.setAttrib(this.linkElement, 'target', target);
2303                 tinyMCE.setAttrib(this.linkElement, 'title', title);
2304         tinyMCE.setAttrib(this.linkElement, 'onclick', onclick);
2305                 tinyMCE.setAttrib(this.linkElement, 'class', style_class);
2306         }
2307
2308         tinyMCE.execCommand('mceEndUndoLevel');
2309 };
2310
2311 TinyMCE.prototype.insertImage = function(src, alt, border, hspace, vspace, width, height, align, title, onmouseover, onmouseout) {
2312         tinyMCE.execCommand('mceBeginUndoLevel');
2313
2314         if (src == "")
2315                 return;
2316
2317         if (!this.imgElement && tinyMCE.isSafari) {
2318                 var html = "";
2319
2320                 html += '<img src="' + src + '" alt="' + alt + '"';
2321                 html += ' border="' + border + '" hspace="' + hspace + '"';
2322                 html += ' vspace="' + vspace + '" width="' + width + '"';
2323                 html += ' height="' + height + '" align="' + align + '" title="' + title + '" onmouseover="' + onmouseover + '" onmouseout="' + onmouseout + '" />';
2324
2325                 tinyMCE.execCommand("mceInsertContent", false, html);
2326         } else {
2327                 if (!this.imgElement && this.selectedInstance) {
2328                         if (tinyMCE.isSafari)
2329                                 tinyMCE.execCommand("mceInsertContent", false, '<img src="#mce_temp_url#" />');
2330                         else
2331                                 this.selectedInstance.contentDocument.execCommand("insertimage", false, "#mce_temp_url#");
2332
2333                         tinyMCE.imgElement = this.getElementByAttributeValue(this.selectedInstance.contentDocument.body, "img", "src", "#mce_temp_url#");
2334                 }
2335         }
2336
2337         if (this.imgElement) {
2338                 var needsRepaint = false;
2339
2340                 src = eval(tinyMCE.settings['urlconverter_callback'] + "(src, tinyMCE.imgElement);");
2341
2342                 if (onmouseover && onmouseover != "")
2343                         onmouseover = "this.src='" + eval(tinyMCE.settings['urlconverter_callback'] + "(onmouseover, tinyMCE.imgElement);") + "';";
2344
2345                 if (onmouseout && onmouseout != "")
2346                         onmouseout = "this.src='" + eval(tinyMCE.settings['urlconverter_callback'] + "(onmouseout, tinyMCE.imgElement);") + "';";
2347
2348                 // Use alt as title if it's undefined
2349                 if (typeof(title) == "undefined")
2350                         title = alt;
2351
2352                 if (width != this.imgElement.getAttribute("width") || height != this.imgElement.getAttribute("height") || align != this.imgElement.getAttribute("align"))
2353                         needsRepaint = true;
2354
2355                 tinyMCE.setAttrib(this.imgElement, 'src', src);
2356                 tinyMCE.setAttrib(this.imgElement, 'mce_real_src', src);
2357                 tinyMCE.setAttrib(this.imgElement, 'alt', alt);
2358                 tinyMCE.setAttrib(this.imgElement, 'title', title);
2359                 tinyMCE.setAttrib(this.imgElement, 'align', align);
2360                 tinyMCE.setAttrib(this.imgElement, 'border', border, true);
2361                 tinyMCE.setAttrib(this.imgElement, 'hspace', hspace, true);
2362                 tinyMCE.setAttrib(this.imgElement, 'vspace', vspace, true);
2363                 tinyMCE.setAttrib(this.imgElement, 'width', width, true);
2364                 tinyMCE.setAttrib(this.imgElement, 'height', height, true);
2365                 tinyMCE.setAttrib(this.imgElement, 'onmouseover', onmouseover);
2366                 tinyMCE.setAttrib(this.imgElement, 'onmouseout', onmouseout);
2367
2368                 // Fix for bug #989846 - Image resize bug
2369                 if (width && width != "")
2370                         this.imgElement.style.pixelWidth = width;
2371
2372                 if (height && height != "")
2373                         this.imgElement.style.pixelHeight = height;
2374
2375                 if (needsRepaint)
2376                         tinyMCE.selectedInstance.repaint();
2377         }
2378
2379         tinyMCE.execCommand('mceEndUndoLevel');
2380 };
2381
2382 TinyMCE.prototype.getElementByAttributeValue = function(node, element_name, attrib, value) {
2383         var elements = this.getElementsByAttributeValue(node, element_name, attrib, value);
2384         if (elements.length == 0)
2385                 return null;
2386
2387         return elements[0];
2388 };
2389
2390 TinyMCE.prototype.getElementsByAttributeValue = function(node, element_name, attrib, value) {
2391         var elements = new Array();
2392
2393         if (node && node.nodeName.toLowerCase() == element_name) {
2394                 if (node.getAttribute(attrib) && node.getAttribute(attrib).indexOf(value) != -1)
2395                         elements[elements.length] = node;
2396         }
2397
2398         if (node.hasChildNodes) {
2399                 for (var x=0, n=node.childNodes.length; x<n; x++) {
2400                         var childElements = this.getElementsByAttributeValue(node.childNodes[x], element_name, attrib, value);
2401                         for (var i=0, m=childElements.length; i<m; i++)
2402                                 elements[elements.length] = childElements[i];
2403                 }
2404         }
2405
2406         return elements;
2407 };
2408
2409 TinyMCE.prototype.isBlockElement = function(node) {
2410         return node != null && node.nodeType == 1 && this.blockRegExp.test(node.nodeName);
2411 };
2412
2413 TinyMCE.prototype.getParentBlockElement = function(node) {
2414         // Search up the tree for block element
2415         while (node) {
2416                 if (this.blockRegExp.test(node.nodeName))
2417                         return node;
2418
2419                 node = node.parentNode;
2420         }
2421
2422         return null;
2423 };
2424
2425 TinyMCE.prototype.getNodeTree = function(node, node_array, type, node_name) {
2426         if (typeof(type) == "undefined" || node.nodeType == type && (typeof(node_name) == "undefined" || node.nodeName == node_name))
2427                 node_array[node_array.length] = node;
2428
2429         if (node.hasChildNodes()) {
2430                 for (var i=0; i<node.childNodes.length; i++)
2431                         tinyMCE.getNodeTree(node.childNodes[i], node_array, type, node_name);
2432         }
2433
2434         return node_array;
2435 };
2436
2437 TinyMCE.prototype.getParentElement = function(node, names, attrib_name, attrib_value) {
2438         if (typeof(names) == "undefined") {
2439                 if (node.nodeType == 1)
2440                         return node;
2441
2442                 // Find parent node that is a element
2443                 while ((node = node.parentNode) != null && node.nodeType != 1) ;
2444
2445                 return node;
2446         }
2447
2448         var namesAr = names.split(',');
2449
2450         if (node == null)
2451                 return null;
2452
2453         do {
2454                 for (var i=0; i<namesAr.length; i++) {
2455                         if (node.nodeName.toLowerCase() == namesAr[i].toLowerCase() || names == "*") {
2456                                 if (typeof(attrib_name) == "undefined")
2457                                         return node;
2458                                 else if (node.getAttribute(attrib_name)) {
2459                                         if (typeof(attrib_value) == "undefined") {
2460                                                 if (node.getAttribute(attrib_name) != "")
2461                                                         return node;
2462                                         } else if (node.getAttribute(attrib_name) == attrib_value)
2463                                                 return node;
2464                                 }
2465                         }
2466                 }
2467         } while (node = node.parentNode);
2468
2469         return null;
2470 };
2471
2472 TinyMCE.prototype.convertURL = function(url, node, on_save) {
2473         var prot = document.location.protocol;
2474         var host = document.location.hostname;
2475         var port = document.location.port;
2476
2477         var fileProto = (prot == "file:");
2478
2479         // Something is wrong, remove weirdness
2480         url = tinyMCE.regexpReplace(url, '(http|https):///', '/');
2481
2482         // Mailto link or anchor (Pass through)
2483         if (url.indexOf('mailto:') != -1 || url.indexOf('javascript:') != -1 || tinyMCE.regexpReplace(url,'[ \t\r\n\+]|%20','').charAt(0) == "#")
2484                 return url;
2485
2486         // Fix relative/Mozilla
2487         if (!tinyMCE.isMSIE && !on_save && url.indexOf("://") == -1 && url.charAt(0) != '/')
2488                 return tinyMCE.settings['base_href'] + url;
2489
2490         // Handle absolute url anchors
2491         if (!tinyMCE.settings['relative_urls']) {
2492                 var urlParts = tinyMCE.parseURL(url);
2493                 var baseUrlParts = tinyMCE.parseURL(tinyMCE.settings['base_href']);
2494
2495                 // If anchor and path is the same page
2496                 if (urlParts['anchor'] && urlParts['path'] == baseUrlParts['path'])
2497                         return "#" + urlParts['anchor'];
2498         }
2499
2500         // Convert to relative urls
2501         if (on_save && tinyMCE.settings['relative_urls']) {
2502                 var urlParts = tinyMCE.parseURL(url);
2503
2504                 // If not absolute url, do nothing (Mozilla)
2505                 // WEIRD STUFF?!
2506 /*              if (!urlParts['protocol'] && !tinyMCE.isMSIE) {
2507                         var urlPrefix = "http://";
2508                         urlPrefix += host;
2509                         if (port != "")
2510                                 urlPrefix += ":" + port;
2511
2512                         url = urlPrefix + url;
2513                         urlParts = tinyMCE.parseURL(url);
2514                 }*/
2515
2516                 var tmpUrlParts = tinyMCE.parseURL(tinyMCE.settings['document_base_url']);
2517
2518                 // Link is within this site
2519                 if (urlParts['host'] == tmpUrlParts['host'] && (!urlParts['port'] || urlParts['port'] == tmpUrlParts['port']))
2520                         return tinyMCE.convertAbsoluteURLToRelativeURL(tinyMCE.settings['document_base_url'], url);
2521         }
2522
2523         // Remove current domain
2524         if (!fileProto && tinyMCE.settings['remove_script_host']) {
2525                 var start = "", portPart = "";
2526
2527                 if (port != "")
2528                         portPart = ":" + port;
2529
2530                 start = prot + "//" + host + portPart + "/";
2531
2532                 if (url.indexOf(start) == 0)
2533                         url = url.substring(start.length-1);
2534
2535                 // Add first slash if missing on a absolute URL
2536                 if (!tinyMCE.settings['relative_urls'] && url.indexOf('://') == -1 && url.charAt(0) != '/')
2537                         url = '/' + url;
2538         }
2539
2540         return url;
2541 };
2542
2543 /**
2544  * Parses a URL in to its diffrent components.
2545  */
2546 TinyMCE.prototype.parseURL = function(url_str) {
2547         var urlParts = new Array();
2548
2549         if (url_str) {
2550                 var pos, lastPos;
2551
2552                 // Parse protocol part
2553                 pos = url_str.indexOf('://');
2554                 if (pos != -1) {
2555                         urlParts['protocol'] = url_str.substring(0, pos);
2556                         lastPos = pos + 3;
2557                 }
2558
2559                 // Find port or path start
2560                 for (var i=lastPos; i<url_str.length; i++) {
2561                         var chr = url_str.charAt(i);
2562
2563                         if (chr == ':')
2564                                 break;
2565
2566                         if (chr == '/')
2567                                 break;
2568                 }
2569                 pos = i;
2570
2571                 // Get host
2572                 urlParts['host'] = url_str.substring(lastPos, pos);
2573
2574                 // Get port
2575                 lastPos = pos;
2576                 if (url_str.charAt(pos) == ':') {
2577                         pos = url_str.indexOf('/', lastPos);
2578                         urlParts['port'] = url_str.substring(lastPos+1, pos);
2579                 }
2580
2581                 // Get path
2582                 lastPos = pos;
2583                 pos = url_str.indexOf('?', lastPos);
2584
2585                 if (pos == -1)
2586                         pos = url_str.indexOf('#', lastPos);
2587
2588                 if (pos == -1)
2589                         pos = url_str.length;
2590
2591                 urlParts['path'] = url_str.substring(lastPos, pos);
2592
2593                 // Get query
2594                 lastPos = pos;
2595                 if (url_str.charAt(pos) == '?') {
2596                         pos = url_str.indexOf('#');
2597                         pos = (pos == -1) ? url_str.length : pos;
2598                         urlParts['query'] = url_str.substring(lastPos+1, pos);
2599                 }
2600
2601                 // Get anchor
2602                 lastPos = pos;
2603                 if (url_str.charAt(pos) == '#') {
2604                         pos = url_str.length;
2605                         urlParts['anchor'] = url_str.substring(lastPos+1, pos);
2606                 }
2607         }
2608
2609         return urlParts;
2610 };
2611
2612 /**
2613  * Converts an absolute path to relative path.
2614  */
2615 TinyMCE.prototype.convertAbsoluteURLToRelativeURL = function(base_url, url_to_relative) {
2616         var strTok1;
2617         var strTok2;
2618         var breakPoint = 0;
2619         var outputString = "";
2620
2621         // Crop away last path part
2622         base_url = base_url.substring(0, base_url.lastIndexOf('/'));
2623         strTok1 = base_url.split('/');
2624         strTok2 = url_to_relative.split('/');
2625
2626         if (strTok1.length >= strTok2.length) {
2627                 for (var i=0; i<strTok1.length; i++) {
2628                         if (i >= strTok2.length || strTok1[i] != strTok2[i]) {
2629                                 breakPoint = i + 1;
2630                                 break;
2631                         }
2632                 }
2633         }
2634
2635         if (strTok1.length < strTok2.length) {
2636                 for (var i=0; i<strTok2.length; i++) {
2637                         if (i >= strTok1.length || strTok1[i] != strTok2[i]) {
2638                                 breakPoint = i + 1;
2639                                 break;
2640                         }
2641                 }
2642         }
2643
2644         if (breakPoint == 1)
2645                 return url_to_relative;
2646
2647         for (var i=0; i<(strTok1.length-(breakPoint-1)); i++)
2648                 outputString += "../";
2649
2650         for (var i=breakPoint-1; i<strTok2.length; i++) {
2651                 if (i != (breakPoint-1))
2652                         outputString += "/" + strTok2[i];
2653                 else
2654                         outputString += strTok2[i];
2655         }
2656
2657         return outputString;
2658 };
2659
2660 TinyMCE.prototype.convertRelativeToAbsoluteURL = function(base_url, relative_url) {
2661         var baseURL = TinyMCE.prototype.parseURL(base_url);
2662         var relURL = TinyMCE.prototype.parseURL(relative_url);
2663
2664         if (relative_url == "" || relative_url.charAt(0) == '/' || relative_url.indexOf('://') != -1 || relative_url.indexOf('mailto:') != -1 || relative_url.indexOf('javascript:') != -1 || tinyMCE.regexpReplace(relative_url,'[ \t\r\n\+]|%20','').charAt(0) == "#")
2665                 return relative_url;
2666
2667         // Split parts
2668         baseURLParts = baseURL['path'].split('/');
2669         relURLParts = relURL['path'].split('/');
2670
2671         // Remove empty chunks
2672         var newBaseURLParts = new Array();
2673         for (var i=baseURLParts.length-1; i>=0; i--) {
2674                 if (baseURLParts[i].length == 0)
2675                         continue;
2676
2677                 newBaseURLParts[newBaseURLParts.length] = baseURLParts[i];
2678         }
2679         baseURLParts = newBaseURLParts.reverse();
2680
2681         // Merge relURLParts chunks
2682         var newRelURLParts = new Array();
2683         var numBack = 0;
2684         for (var i=relURLParts.length-1; i>=0; i--) {
2685                 if (relURLParts[i].length == 0 || relURLParts[i] == ".")
2686                         continue;
2687
2688                 if (relURLParts[i] == '..') {
2689                         numBack++;
2690                         continue;
2691                 }
2692
2693                 if (numBack > 0) {
2694                         numBack--;
2695                         continue;
2696                 }
2697
2698                 newRelURLParts[newRelURLParts.length] = relURLParts[i];
2699         }
2700
2701         relURLParts = newRelURLParts.reverse();
2702
2703         // Remove end from absolute path
2704         var len = baseURLParts.length-numBack;
2705         var absPath = (len <= 0 ? "" : "/") + baseURLParts.slice(0, len).join('/') + "/" + relURLParts.join('/');
2706         var start = "", end = "";
2707
2708         // Build start part
2709         if (baseURL['protocol'])
2710                 start += baseURL['protocol'] + "://";
2711
2712         if (baseURL['host'])
2713                 start += baseURL['host'];
2714
2715         if (baseURL['port'])
2716                 start += ":" + baseURL['port'];
2717
2718         // Build end part
2719         if (relURL['query'])
2720                 end += "?" + relURL['query'];
2721
2722         if (relURL['anchor'])
2723                 end += "#" + relURL['anchor'];
2724
2725         // Re-add trailing slash if it's removed
2726         if (relative_url.charAt(relative_url.length-1) == "/")
2727                 end += "/";
2728
2729         return start + absPath + end;
2730 };
2731
2732 TinyMCE.prototype.getParam = function(name, default_value, strip_whitespace, split_chr) {
2733         var value = (typeof(this.settings[name]) == "undefined") ? default_value : this.settings[name];
2734
2735         // Fix bool values
2736         if (value == "true" || value == "false")
2737                 return (value == "true");
2738
2739         if (strip_whitespace)
2740                 value = tinyMCE.regexpReplace(value, "[ \t\r\n]", "");
2741
2742         if (typeof(split_chr) != "undefined" && split_chr != null) {
2743                 value = value.split(split_chr);
2744                 var outArray = new Array();
2745
2746                 for (var i=0; i<value.length; i++) {
2747                         if (value[i] && value[i] != "")
2748                                 outArray[outArray.length] = value[i];
2749                 }
2750
2751                 value = outArray;
2752         }
2753
2754         return value;
2755 };
2756
2757 TinyMCE.prototype.getLang = function(name, default_value, parse_entities) {
2758         var value = (typeof(tinyMCELang[name]) == "undefined") ? default_value : tinyMCELang[name];
2759
2760         if (parse_entities) {
2761                 var el = document.createElement("div");
2762                 el.innerHTML = value;
2763                 value = el.innerHTML;
2764         }
2765
2766         return value;
2767 };
2768
2769 TinyMCE.prototype.addToLang = function(prefix, ar) {
2770         for (var key in ar) {
2771                 if (typeof(ar[key]) == 'function')
2772                         continue;
2773
2774                 tinyMCELang[(key.indexOf('lang_') == -1 ? 'lang_' : '') + (prefix != '' ? (prefix + "_") : '') + key] = ar[key];
2775         }
2776
2777 //      for (var key in ar)
2778 //              tinyMCELang[(key.indexOf('lang_') == -1 ? 'lang_' : '') + (prefix != '' ? (prefix + "_") : '') + key] = "|" + ar[key] + "|";
2779 };
2780
2781 TinyMCE.prototype.replaceVar = function(replace_haystack, replace_var, replace_str) {
2782         var re = new RegExp('{\\\$' + replace_var + '}', 'g');
2783         return replace_haystack.replace(re, replace_str);
2784 };
2785
2786 TinyMCE.prototype.replaceVars = function(replace_haystack, replace_vars) {
2787         for (var key in replace_vars) {
2788                 var value = replace_vars[key];
2789                 if (typeof(value) == 'function')
2790                         continue;
2791
2792                 replace_haystack = tinyMCE.replaceVar(replace_haystack, key, value);
2793         }
2794
2795         return replace_haystack;
2796 };
2797
2798 TinyMCE.prototype.triggerNodeChange = function(focus, setup_content) {
2799         if (tinyMCE.settings['handleNodeChangeCallback']) {
2800                 if (tinyMCE.selectedInstance) {
2801                         var inst = tinyMCE.selectedInstance;
2802                         var editorId = inst.editorId;
2803                         var elm = (typeof(setup_content) != "undefined" && setup_content) ? tinyMCE.selectedElement : inst.getFocusElement();
2804                         var undoIndex = -1;
2805                         var undoLevels = -1;
2806                         var anySelection = false;
2807                         var selectedText = inst.getSelectedText();
2808
2809                         if (tinyMCE.settings["auto_resize"]) {
2810                                 var doc = inst.getDoc();
2811
2812                                 inst.iframeElement.style.width = doc.body.offsetWidth + "px";
2813                                 inst.iframeElement.style.height = doc.body.offsetHeight + "px";
2814                         }
2815
2816                         if (tinyMCE.selectedElement)
2817                                 anySelection = (tinyMCE.selectedElement.nodeName.toLowerCase() == "img") || (selectedText && selectedText.length > 0);
2818
2819                         if (tinyMCE.settings['custom_undo_redo']) {
2820                                 undoIndex = inst.undoIndex;
2821                                 undoLevels = inst.undoLevels.length;
2822                         }
2823
2824                         tinyMCE.executeCallback('handleNodeChangeCallback', '_handleNodeChange', 0, editorId, elm, undoIndex, undoLevels, inst.visualAid, anySelection);
2825                 }
2826         }
2827
2828         if (this.selectedInstance && (typeof(focus) == "undefined" || focus))
2829                 this.selectedInstance.contentWindow.focus();
2830 };
2831
2832 TinyMCE.prototype._customCleanup = function(inst, type, content) {
2833         // Call custom cleanup
2834         var customCleanup = tinyMCE.settings['cleanup_callback'];
2835         if (customCleanup != "" && eval("typeof(" + customCleanup + ")") != "undefined")
2836                 content = eval(customCleanup + "(type, content, inst);");
2837
2838         // Trigger plugin cleanups
2839         var plugins = tinyMCE.getParam('plugins', '', true, ',');
2840         for (var i=0; i<plugins.length; i++) {
2841                 if (eval("typeof(TinyMCE_" + plugins[i] +  "_cleanup)") != "undefined")
2842                         content = eval("TinyMCE_" + plugins[i] +  "_cleanup(type, content, inst);");
2843         }
2844
2845         return content;
2846 };
2847
2848 TinyMCE.prototype.getContent = function(editor_id) {
2849         if (typeof(editor_id) != "undefined")
2850                 tinyMCE.selectedInstance = tinyMCE.getInstanceById(editor_id);
2851
2852         if (tinyMCE.selectedInstance)
2853                 return tinyMCE._cleanupHTML(this.selectedInstance, this.selectedInstance.getDoc(), tinyMCE.settings, this.selectedInstance.getBody(), false, true);
2854
2855         return null;
2856 };
2857
2858 TinyMCE.prototype.setContent = function(html_content) {
2859         if (tinyMCE.selectedInstance) {
2860                 tinyMCE.selectedInstance.execCommand('mceSetContent', false, html_content);
2861                 tinyMCE.selectedInstance.repaint();
2862         }
2863 };
2864
2865 TinyMCE.prototype.importThemeLanguagePack = function(name) {
2866         if (typeof(name) == "undefined")
2867                 name = tinyMCE.settings['theme'];
2868
2869         tinyMCE.loadScript(tinyMCE.baseURL + '/themes/' + name + '/langs/' + tinyMCE.settings['language'] + '.js');
2870 };
2871
2872 TinyMCE.prototype.importPluginLanguagePack = function(name, valid_languages) {
2873         var lang = "en";
2874
2875         valid_languages = valid_languages.split(',');
2876         for (var i=0; i<valid_languages.length; i++) {
2877                 if (tinyMCE.settings['language'] == valid_languages[i])
2878                         lang = tinyMCE.settings['language'];
2879         }
2880
2881         tinyMCE.loadScript(tinyMCE.baseURL + '/plugins/' + name + '/langs/' + lang +  '.js');
2882 };
2883
2884 /**
2885  * Adds themeurl, settings and lang to HTML code.
2886  */
2887 TinyMCE.prototype.applyTemplate = function(html, args) {
2888         html = tinyMCE.replaceVar(html, "themeurl", tinyMCE.themeURL);
2889
2890         if (typeof(args) != "undefined")
2891                 html = tinyMCE.replaceVars(html, args);
2892
2893         html = tinyMCE.replaceVars(html, tinyMCE.settings);
2894         html = tinyMCE.replaceVars(html, tinyMCELang);
2895
2896         return html;
2897 };
2898
2899 TinyMCE.prototype.openWindow = function(template, args) {
2900         var html, width, height, x, y, resizable, scrollbars, url;
2901
2902         args['mce_template_file'] = template['file'];
2903         args['mce_width'] = template['width'];
2904         args['mce_height'] = template['height'];
2905         tinyMCE.windowArgs = args;
2906
2907         html = template['html'];
2908         if (!(width = parseInt(template['width'])))
2909                 width = 320;
2910
2911         if (!(height = parseInt(template['height'])))
2912                 height = 200;
2913
2914         // Add to height in M$ due to SP2 WHY DON'T YOU GUYS IMPLEMENT innerWidth of windows!!
2915         if (tinyMCE.isMSIE)
2916                 height += 40;
2917         else
2918                 height += 20;
2919
2920         x = parseInt(screen.width / 2.0) - (width / 2.0);
2921         y = parseInt(screen.height / 2.0) - (height / 2.0);
2922
2923         resizable = (args && args['resizable']) ? args['resizable'] : "no";
2924         scrollbars = (args && args['scrollbars']) ? args['scrollbars'] : "no";
2925
2926         if (template['file'].charAt(0) != '/' && template['file'].indexOf('://') == -1)
2927                 url = tinyMCE.baseURL + "/themes/" + tinyMCE.getParam("theme") + "/" + template['file'];
2928         else
2929                 url = template['file'];
2930
2931         // Replace all args as variables in URL
2932         for (var name in args) {
2933                 if (typeof(args[name]) == 'function')
2934                         continue;
2935
2936                 url = tinyMCE.replaceVar(url, name, escape(args[name]));
2937         }
2938
2939         if (html) {
2940                 html = tinyMCE.replaceVar(html, "css", this.settings['popups_css']);
2941                 html = tinyMCE.applyTemplate(html, args);
2942
2943                 var win = window.open("", "mcePopup" + new Date().getTime(), "top=" + y + ",left=" + x + ",scrollbars=" + scrollbars + ",dialog=yes,minimizable=" + resizable + ",modal=yes,width=" + width + ",height=" + height + ",resizable=" + resizable);
2944                 if (win == null) {
2945                         alert(tinyMCELang['lang_popup_blocked']);
2946                         return;
2947                 }
2948
2949                 win.document.write(html);
2950                 win.document.close();
2951                 win.resizeTo(width, height);
2952                 win.focus();
2953         } else {
2954                 if (tinyMCE.isMSIE && resizable != 'yes' && tinyMCE.settings["dialog_type"] == "modal") {
2955             var features = "resizable:" + resizable 
2956                 + ";scroll:"
2957                 + scrollbars + ";status:yes;center:yes;help:no;dialogWidth:"
2958                 + width + "px;dialogHeight:" + height + "px;";
2959
2960                         window.showModalDialog(url, window, features);
2961                 } else {
2962                         var modal = (resizable == "yes") ? "no" : "yes";
2963
2964                         if (tinyMCE.isGecko && tinyMCE.isMac)
2965                                 modal = "no";
2966
2967                         if (template['close_previous'] != "no")
2968                                 try {tinyMCE.lastWindow.close();} catch (ex) {}
2969
2970                         var win = window.open(url, "mcePopup" + new Date().getTime(), "top=" + y + ",left=" + x + ",scrollbars=" + scrollbars + ",dialog=" + modal + ",minimizable=" + resizable + ",modal=" + modal + ",width=" + width + ",height=" + height + ",resizable=" + resizable);
2971                         if (win == null) {
2972                                 alert(tinyMCELang['lang_popup_blocked']);
2973                                 return;
2974                         }
2975
2976                         if (template['close_previous'] != "no")
2977                                 tinyMCE.lastWindow = win;
2978
2979                         eval('try { win.resizeTo(width, height); } catch(e) { }');
2980
2981                         // Make it bigger if statusbar is forced
2982                         if (tinyMCE.isGecko) {
2983                                 if (win.document.defaultView.statusbar.visible)
2984                                         win.resizeBy(0, tinyMCE.isMac ? 10 : 24);
2985                         }
2986
2987                         win.focus();
2988                 }
2989         }
2990 };
2991
2992 TinyMCE.prototype.closeWindow = function(win) {
2993         win.close();
2994 };
2995
2996 TinyMCE.prototype.getVisualAidClass = function(class_name, state) {
2997         var aidClass = tinyMCE.settings['visual_table_class'];
2998
2999         if (typeof(state) == "undefined")
3000                 state = tinyMCE.settings['visual'];
3001
3002         // Split
3003         var classNames = new Array();
3004         var ar = class_name.split(' ');
3005         for (var i=0; i<ar.length; i++) {
3006                 if (ar[i] == aidClass)
3007                         ar[i] = "";
3008
3009                 if (ar[i] != "")
3010                         classNames[classNames.length] = ar[i];
3011         }
3012
3013         if (state)
3014                 classNames[classNames.length] = aidClass;
3015
3016         // Glue
3017         var className = "";
3018         for (var i=0; i<classNames.length; i++) {
3019                 if (i > 0)
3020                         className += " ";
3021
3022                 className += classNames[i];
3023         }
3024
3025         return className;
3026 };
3027
3028 TinyMCE.prototype.handleVisualAid = function(element, deep, state, inst) {
3029         if (!element)
3030                 return;
3031
3032         var tableElement = null;
3033
3034         switch (element.nodeName) {
3035                 case "TABLE":
3036                         var oldW = element.style.width;
3037                         var oldH = element.style.height;
3038
3039                         element.className = tinyMCE.getVisualAidClass(element.className, state && element.getAttribute("border") == 0);
3040
3041                         element.style.width = oldW;
3042                         element.style.height = oldH;
3043
3044                         for (var y=0; y<element.rows.length; y++) {
3045                                 for (var x=0; x<element.rows[y].cells.length; x++) {
3046                                         var className = tinyMCE.getVisualAidClass(element.rows[y].cells[x].className, state && element.getAttribute("border") == 0);
3047                                         element.rows[y].cells[x].className = className;
3048                                 }
3049                         }
3050
3051                         break;
3052
3053                 case "A":
3054                         var anchorName = tinyMCE.getAttrib(element, "name");
3055
3056                         if (anchorName != '' && state) {
3057                                 element.title = anchorName;
3058                                 element.className = 'mceItemAnchor';
3059                         } else if (anchorName != '' && !state)
3060                                 element.className = '';
3061
3062                         break;
3063         }
3064
3065         if (deep && element.hasChildNodes()) {
3066                 for (var i=0; i<element.childNodes.length; i++)
3067                         tinyMCE.handleVisualAid(element.childNodes[i], deep, state, inst);
3068         }
3069 };
3070
3071 TinyMCE.prototype.getAttrib = function(elm, name, default_value) {
3072         if (typeof(default_value) == "undefined")
3073                 default_value = "";
3074
3075         // Not a element
3076         if (!elm || elm.nodeType != 1)
3077                 return default_value;
3078
3079         var v = elm.getAttribute(name);
3080
3081         // Try className for class attrib
3082         if (name == "class" && !v)
3083                 v = elm.className;
3084
3085         if (name == "style")
3086                 v = elm.style.cssText;
3087
3088         return (v && v != "") ? v : default_value;
3089 };
3090
3091 TinyMCE.prototype.setAttrib = function(element, name, value, fix_value) {
3092         if (typeof(value) == "number" && value != null)
3093                 value = "" + value;
3094
3095         if (fix_value) {
3096                 if (value == null)
3097                         value = "";
3098
3099                 var re = new RegExp('[^0-9%]', 'g');
3100                 value = value.replace(re, '');
3101         }
3102
3103         if (name == "style")
3104                 element.style.cssText = value;
3105
3106         if (name == "class")
3107                 element.className = value;
3108
3109         if (value != null && value != "" && value != -1)
3110                 element.setAttribute(name, value);
3111         else
3112                 element.removeAttribute(name);
3113 };
3114
3115 TinyMCE.prototype._setHTML = function(doc, html_content) {
3116         // Force closed anchors open
3117         html_content = html_content.replace(new RegExp('<a(.*?)/>', 'gi'), '<a$1></a>');
3118         html_content = html_content.replace(new RegExp('<a(.*?)name="(.*?)"(.*?)>(.*?)</a>', 'gi'), '<a$1name="$2"$3></a>$4');
3119
3120         // Weird MSIE bug, <p><hr /></p> breaks runtime?
3121         if (tinyMCE.isMSIE) {
3122                 var re = new RegExp('<p><hr /></p>', 'g');
3123                 html_content = html_content.replace(re, "<hr>");
3124         }
3125
3126         // Try innerHTML if it fails use pasteHTML in MSIE
3127         try {
3128                 doc.body.innerHTML = html_content;
3129         } catch (e) {
3130                 if (this.isMSIE)
3131                         doc.body.createTextRange().pasteHTML(html_content);
3132         }
3133
3134         // Content duplication bug fix
3135         if (tinyMCE.isMSIE && tinyMCE.settings['fix_content_duplication']) {
3136                 // Remove P elements in P elements
3137                 var paras = doc.getElementsByTagName("P");
3138                 for (var i=0; i<paras.length; i++) {
3139                         var node = paras[i];
3140                         while ((node = node.parentNode) != null) {
3141                                 if (node.nodeName.toLowerCase() == "p")
3142                                         node.outerHTML = node.innerHTML;
3143                         }
3144                 }
3145
3146                 // Content duplication bug fix (Seems to be word crap)
3147                 var html = doc.body.innerHTML;
3148
3149                 if (html.indexOf('="mso') != -1) {
3150                         for (var i=0; i<doc.body.all.length; i++) {
3151                                 var el = doc.body.all[i];
3152                                 el.removeAttribute("className","",0);
3153                                 el.removeAttribute("style","",0);
3154                         }
3155
3156                         html = doc.body.innerHTML;
3157                         html = tinyMCE.regexpReplace(html, "<o:p><\/o:p>", "<br />");
3158                         html = tinyMCE.regexpReplace(html, "<o:p>&nbsp;<\/o:p>", "");
3159                         html = tinyMCE.regexpReplace(html, "<st1:.*?>", "");
3160                         html = tinyMCE.regexpReplace(html, "<p><\/p>", "");
3161                         html = tinyMCE.regexpReplace(html, "<p><\/p>\r\n<p><\/p>", "");
3162                         html = tinyMCE.regexpReplace(html, "<p>&nbsp;<\/p>", "<br />");
3163                         html = tinyMCE.regexpReplace(html, "<p>\s*(<p>\s*)?", "<p>");
3164                         html = tinyMCE.regexpReplace(html, "<\/p>\s*(<\/p>\s*)?", "</p>");
3165                 }
3166
3167                 // Always set the htmlText output
3168                 doc.body.innerHTML = html;
3169         }
3170 };
3171
3172 TinyMCE.prototype.getImageSrc = function(str) {
3173         var pos = -1;
3174
3175         if (!str)
3176                 return "";
3177
3178         if ((pos = str.indexOf('this.src=')) != -1) {
3179                 var src = str.substring(pos + 10);
3180
3181                 src = src.substring(0, src.indexOf('\''));
3182
3183                 return src;
3184         }
3185
3186         return "";
3187 };
3188
3189 TinyMCE.prototype._getElementById = function(element_id) {
3190         var elm = document.getElementById(element_id);
3191         if (!elm) {
3192                 // Check for element in forms
3193                 for (var j=0; j<document.forms.length; j++) {
3194                         for (var k=0; k<document.forms[j].elements.length; k++) {
3195                                 if (document.forms[j].elements[k].name == element_id) {
3196                                         elm = document.forms[j].elements[k];
3197                                         break;
3198                                 }
3199                         }
3200                 }
3201         }
3202
3203         return elm;
3204 };
3205
3206 TinyMCE.prototype.getEditorId = function(form_element) {
3207         var inst = this.getInstanceById(form_element);
3208         if (!inst)
3209                 return null;
3210
3211         return inst.editorId;
3212 };
3213
3214 TinyMCE.prototype.getInstanceById = function(editor_id) {
3215         var inst = this.instances[editor_id];
3216         if (!inst) {
3217                 for (var n in tinyMCE.instances) {
3218                         var instance = tinyMCE.instances[n];
3219                         if (typeof(instance) == 'function')
3220                                 continue;
3221
3222                         if (instance.formTargetElementId == editor_id) {
3223                                 inst = instance;
3224                                 break;
3225                         }
3226                 }
3227         }
3228
3229         return inst;
3230 };
3231
3232 TinyMCE.prototype.queryInstanceCommandValue = function(editor_id, command) {
3233         var inst = tinyMCE.getInstanceById(editor_id);
3234         if (inst)
3235                 return inst.queryCommandValue(command);
3236
3237         return false;
3238 };
3239
3240 TinyMCE.prototype.queryInstanceCommandState = function(editor_id, command) {
3241         var inst = tinyMCE.getInstanceById(editor_id);
3242         if (inst)
3243                 return inst.queryCommandState(command);
3244
3245         return null;
3246 };
3247
3248 TinyMCE.prototype.setWindowArg = function(name, value) {
3249         this.windowArgs[name] = value;
3250 };
3251
3252 TinyMCE.prototype.getWindowArg = function(name, default_value) {
3253         return (typeof(this.windowArgs[name]) == "undefined") ? default_value : this.windowArgs[name];
3254 };
3255
3256 TinyMCE.prototype.getCSSClasses = function(editor_id, doc) {
3257         var output = new Array();
3258
3259         // Is cached, use that
3260         if (typeof(tinyMCE.cssClasses) != "undefined")
3261                 return tinyMCE.cssClasses;
3262
3263         if (typeof(editor_id) == "undefined" && typeof(doc) == "undefined") {
3264                 var instance;
3265
3266                 for (var instanceName in tinyMCE.instances) {
3267                         instance = tinyMCE.instances[instanceName];
3268                         if (typeof(instance) == 'function')
3269                                 continue;
3270
3271                         break;
3272                 }
3273
3274                 doc = instance.getDoc();
3275         }
3276
3277         if (typeof(doc) == "undefined") {
3278                 var instance = tinyMCE.getInstanceById(editor_id);
3279                 doc = instance.getDoc();
3280         }
3281
3282         if (doc) {
3283                 var styles = tinyMCE.isMSIE ? doc.styleSheets : doc.styleSheets;
3284
3285                 if (styles && styles.length > 0) {
3286                         for (var x=0; x<styles.length; x++) {
3287                                 var csses = null;
3288
3289                                 // Just ignore any errors
3290                                 eval("try {var csses = tinyMCE.isMSIE ? doc.styleSheets(" + x + ").rules : doc.styleSheets[" + x + "].cssRules;} catch(e) {}");
3291                                 if (!csses)
3292                                         return new Array();
3293
3294                                 for (var i=0; i<csses.length; i++) {
3295                                         var selectorText = csses[i].selectorText;
3296
3297                                         // Can be multiple rules per selector
3298                                         if (selectorText) {
3299                                                 var rules = selectorText.split(',');
3300                                                 for (var c=0; c<rules.length; c++) {
3301                                                         // Invalid rule
3302                                                         if (rules[c].indexOf(' ') != -1 || rules[c].indexOf(':') != -1 || rules[c].indexOf('mceItem') != -1)
3303                                                                 continue;
3304
3305                                                         if (rules[c] == "." + tinyMCE.settings['visual_table_class'])
3306                                                                 continue;
3307
3308                                                         // Is class rule
3309                                                         if (rules[c].indexOf('.') != -1) {
3310                                                                 //alert(rules[c].substring(rules[c].indexOf('.')));
3311                                                                 output[output.length] = rules[c].substring(rules[c].indexOf('.')+1);
3312                                                         }
3313                                                 }
3314                                         }
3315                                 }
3316                         }
3317                 }
3318         }
3319
3320         // Cache em
3321         if (output.length > 0)
3322                 tinyMCE.cssClasses = output;
3323
3324         return output;
3325 };
3326
3327 TinyMCE.prototype.regexpReplace = function(in_str, reg_exp, replace_str, opts) {
3328         if (typeof(opts) == "undefined")
3329                 opts = 'g';
3330
3331         var re = new RegExp(reg_exp, opts);
3332         return in_str.replace(re, replace_str);
3333 };
3334
3335 TinyMCE.prototype.trim = function(str) {
3336         return str.replace(/^\s*|\s*$/g, "");
3337 };
3338
3339 TinyMCE.prototype.cleanupEventStr = function(str) {
3340         str = "" + str;
3341         str = str.replace('function anonymous()\n{\n', '');
3342         str = str.replace('\n}', '');
3343         str = str.replace(/^return true;/gi, '');
3344
3345         return str;
3346 };
3347
3348 TinyMCE.prototype.getAbsPosition = function(node) {
3349         var pos = new Object();
3350
3351         pos.absLeft = pos.absTop = 0;
3352
3353         var parentNode = node;
3354         while (parentNode) {
3355                 pos.absLeft += parentNode.offsetLeft;
3356                 pos.absTop += parentNode.offsetTop;
3357
3358                 parentNode = parentNode.offsetParent;
3359         }
3360
3361         return pos;
3362 };
3363
3364 TinyMCE.prototype.getControlHTML = function(control_name) {
3365         var themePlugins = tinyMCE.getParam('plugins', '', true, ',');
3366         var templateFunction;
3367
3368         // Is it defined in any plugins
3369         for (var i=themePlugins.length; i>=0; i--) {
3370                 templateFunction = 'TinyMCE_' + themePlugins[i] + "_getControlHTML";
3371                 if (eval("typeof(" + templateFunction + ")") != 'undefined') {
3372                         var html = eval(templateFunction + "('" + control_name + "');");
3373                         if (html != "")
3374                                 return tinyMCE.replaceVar(html, "pluginurl", tinyMCE.baseURL + "/plugins/" + themePlugins[i]);
3375                 }
3376         }
3377
3378         return eval('TinyMCE_' + tinyMCE.settings['theme'] + "_getControlHTML" + "('" + control_name + "');");
3379 };
3380
3381 TinyMCE.prototype._themeExecCommand = function(editor_id, element, command, user_interface, value) {
3382         var themePlugins = tinyMCE.getParam('plugins', '', true, ',');
3383         var templateFunction;
3384
3385         // Is it defined in any plugins
3386         for (var i=themePlugins.length; i>=0; i--) {
3387                 templateFunction = 'TinyMCE_' + themePlugins[i] + "_execCommand";
3388                 if (eval("typeof(" + templateFunction + ")") != 'undefined') {
3389                         if (eval(templateFunction + "(editor_id, element, command, user_interface, value);"))
3390                                 return true;
3391                 }
3392         }
3393
3394         // Theme funtion
3395         templateFunction = 'TinyMCE_' + tinyMCE.settings['theme'] + "_execCommand";
3396         if (eval("typeof(" + templateFunction + ")") != 'undefined')
3397                 return eval(templateFunction + "(editor_id, element, command, user_interface, value);");
3398
3399         // Pass to normal
3400         return false;
3401 };
3402
3403 TinyMCE.prototype._getThemeFunction = function(suffix, skip_plugins) {
3404         if (skip_plugins)
3405                 return 'TinyMCE_' + tinyMCE.settings['theme'] + suffix;
3406
3407         var themePlugins = tinyMCE.getParam('plugins', '', true, ',');
3408         var templateFunction;
3409
3410         // Is it defined in any plugins
3411         for (var i=themePlugins.length; i>=0; i--) {
3412                 templateFunction = 'TinyMCE_' + themePlugins[i] + suffix;
3413                 if (eval("typeof(" + templateFunction + ")") != 'undefined')
3414                         return templateFunction;
3415         }
3416
3417         return 'TinyMCE_' + tinyMCE.settings['theme'] + suffix;
3418 };
3419
3420
3421 TinyMCE.prototype.isFunc = function(func_name) {
3422         if (func_name == null || func_name == "")
3423                 return false;
3424
3425         return eval("typeof(" + func_name + ")") != "undefined";
3426 };
3427
3428 TinyMCE.prototype.exec = function(func_name, args) {
3429         var str = func_name + '(';
3430
3431         // Add all arguments
3432         for (var i=3; i<args.length; i++) {
3433                 str += 'args[' + i + ']';
3434
3435                 if (i < args.length-1)
3436                         str += ',';
3437         }
3438
3439         str += ');';
3440
3441         return eval(str);
3442 };
3443
3444 TinyMCE.prototype.executeCallback = function(param, suffix, mode) {
3445         switch (mode) {
3446                 // No chain
3447                 case 0:
3448                         var state = false;
3449
3450                         // Execute each plugin callback
3451                         var plugins = tinyMCE.getParam('plugins', '', true, ',');
3452                         for (var i=0; i<plugins.length; i++) {
3453                                 var func = "TinyMCE_" + plugins[i] + suffix;
3454                                 if (tinyMCE.isFunc(func)) {
3455                                         tinyMCE.exec(func, this.executeCallback.arguments);
3456                                         state = true;
3457                                 }
3458                         }
3459
3460                         // Execute theme callback
3461                         var func = 'TinyMCE_' + tinyMCE.settings['theme'] + suffix;
3462                         if (tinyMCE.isFunc(func)) {
3463                                 tinyMCE.exec(func, this.executeCallback.arguments);
3464                                 state = true;
3465                         }
3466
3467                         // Execute settings callback
3468                         var func = tinyMCE.getParam(param, '');
3469                         if (tinyMCE.isFunc(func)) {
3470                                 tinyMCE.exec(func, this.executeCallback.arguments);
3471                                 state = true;
3472                         }
3473
3474                         return state;
3475
3476                 // Chain mode
3477                 case 1:
3478                         // Execute each plugin callback
3479                         var plugins = tinyMCE.getParam('plugins', '', true, ',');
3480                         for (var i=0; i<plugins.length; i++) {
3481                                 var func = "TinyMCE_" + plugins[i] + suffix;
3482                                 if (tinyMCE.isFunc(func)) {
3483                                         if (tinyMCE.exec(func, this.executeCallback.arguments))
3484                                                 return true;
3485                                 }
3486                         }
3487
3488                         // Execute theme callback
3489                         var func = 'TinyMCE_' + tinyMCE.settings['theme'] + suffix;
3490                         if (tinyMCE.isFunc(func)) {
3491                                 if (tinyMCE.exec(func, this.executeCallback.arguments))
3492                                         return true;
3493                         }
3494
3495                         // Execute settings callback
3496                         var func = tinyMCE.getParam(param, '');
3497                         if (tinyMCE.isFunc(func)) {
3498                                 if (tinyMCE.exec(func, this.executeCallback.arguments))
3499                                         return true;
3500                         }
3501
3502                         return false;
3503         }
3504 };
3505
3506 TinyMCE.prototype.debug = function() {
3507         var msg = "";
3508
3509         var elm = document.getElementById("tinymce_debug");
3510         if (!elm) {
3511                 var debugDiv = document.createElement("div");
3512                 debugDiv.setAttribute("className", "debugger");
3513                 debugDiv.className = "debugger";
3514                 debugDiv.innerHTML = '\
3515                         Debug output:\
3516                         <textarea id="tinymce_debug" style="width: 100%; height: 300px"></textarea>';
3517
3518                 document.body.appendChild(debugDiv);
3519                 elm = document.getElementById("tinymce_debug");
3520         }
3521
3522         var args = this.debug.arguments;
3523         for (var i=0; i<args.length; i++) {
3524                 msg += args[i];
3525                 if (i<args.length-1)
3526                         msg += ', ';
3527         }
3528
3529         elm.value += msg + "\n";
3530 };
3531
3532 // TinyMCEControl
3533 function TinyMCEControl(settings) {
3534         // Undo levels
3535         this.undoLevels = new Array();
3536         this.undoIndex = 0;
3537         this.typingUndoIndex = -1;
3538         this.undoRedo = true;
3539
3540         // Default settings
3541         this.settings = settings;
3542         this.settings['theme'] = tinyMCE.getParam("theme", "default");
3543         this.settings['width'] = tinyMCE.getParam("width", -1);
3544         this.settings['height'] = tinyMCE.getParam("height", -1);
3545 };
3546
3547 TinyMCEControl.prototype.repaint = function() {
3548         if (tinyMCE.isMSIE)
3549                 return;
3550
3551         this.getBody().style.display = 'none';
3552         this.getBody().style.display = 'block';
3553 };
3554
3555 TinyMCEControl.prototype.switchSettings = function() {
3556         if (tinyMCE.configs.length > 1 && tinyMCE.currentConfig != this.settings['index']) {
3557                 tinyMCE.settings = this.settings;
3558                 tinyMCE.currentConfig = this.settings['index'];
3559         }
3560 };
3561
3562 TinyMCEControl.prototype.fixBrokenURLs = function() {
3563         var body = this.getBody();
3564
3565         var elms = body.getElementsByTagName("img");
3566         for (var i=0; i<elms.length; i++) {
3567                 var src = elms[i].getAttribute('mce_real_src');
3568                 if (src && src != "")
3569                         elms[i].setAttribute("src", src);
3570         }
3571
3572         var elms = body.getElementsByTagName("a");
3573         for (var i=0; i<elms.length; i++) {
3574                 var href = elms[i].getAttribute('mce_real_href');
3575                 if (href && href != "")
3576                         elms[i].setAttribute("href", href);
3577         }
3578 };
3579
3580 TinyMCEControl.prototype.convertAllRelativeURLs = function() {
3581         var body = this.getBody();
3582
3583         // Convert all image URL:s to absolute URL
3584         var elms = body.getElementsByTagName("img");
3585         for (var i=0; i<elms.length; i++) {
3586                 var src = elms[i].getAttribute('src');
3587                 if (src && src != "") {
3588                         src = tinyMCE.convertRelativeToAbsoluteURL(tinyMCE.settings['base_href'], src);
3589                         elms[i].setAttribute("src", src);
3590                         elms[i].setAttribute("mce_real_src", src);
3591                 }
3592         }
3593
3594         // Convert all link URL:s to absolute URL
3595         var elms = body.getElementsByTagName("a");
3596         for (var i=0; i<elms.length; i++) {
3597                 var href = elms[i].getAttribute('href');
3598                 if (href && href != "") {
3599                         href = tinyMCE.convertRelativeToAbsoluteURL(tinyMCE.settings['base_href'], href);
3600                         elms[i].setAttribute("href", href);
3601                         elms[i].setAttribute("mce_real_href", href);
3602                 }
3603         }
3604 };
3605
3606 TinyMCEControl.prototype.getSelectedHTML = function() {
3607         if (tinyMCE.isSafari) {
3608                 // Not realy perfect!!
3609
3610                 return this.getRng().toString();
3611         }
3612
3613         var elm = document.createElement("body");
3614
3615         if (tinyMCE.isGecko)
3616                 elm.appendChild(this.getRng().cloneContents());
3617         else
3618                 elm.innerHTML = this.getRng().htmlText;
3619
3620         return tinyMCE._cleanupHTML(this, this.contentDocument, this.settings, elm, this.visualAid);
3621 };
3622
3623 TinyMCEControl.prototype.getBookmark = function() {
3624         var rng = this.getRng();
3625
3626         if (tinyMCE.isSafari)
3627                 return rng;
3628
3629         if (tinyMCE.isMSIE)
3630                 return rng;
3631
3632         if (tinyMCE.isGecko)
3633                 return rng.cloneRange();
3634
3635         return null;
3636 };
3637
3638 TinyMCEControl.prototype.moveToBookmark = function(bookmark) {
3639         if (tinyMCE.isSafari) {
3640                 var sel = this.getSel().realSelection;
3641
3642                 sel.setBaseAndExtent(bookmark.startContainer, bookmark.startOffset, bookmark.endContainer, bookmark.endOffset);
3643
3644                 return true;
3645         }
3646
3647         if (tinyMCE.isMSIE)
3648                 return bookmark.select();
3649
3650         if (tinyMCE.isGecko) {
3651                 var rng = this.getDoc().createRange();
3652                 var sel = this.getSel();
3653
3654                 rng.setStart(bookmark.startContainer, bookmark.startOffset);
3655                 rng.setEnd(bookmark.endContainer, bookmark.endOffset);
3656
3657                 sel.removeAllRanges();
3658                 sel.addRange(rng);
3659
3660                 return true;
3661         }
3662
3663         return false;
3664 };
3665
3666 TinyMCEControl.prototype.getSelectedText = function() {
3667         if (tinyMCE.isMSIE) {
3668                 var doc = this.getDoc();
3669
3670                 if (doc.selection.type == "Text") {
3671                         var rng = doc.selection.createRange();
3672                         selectedText = rng.text;
3673                 } else
3674                         selectedText = '';
3675         } else {
3676                 var sel = this.getSel();
3677
3678                 if (sel && sel.toString)
3679                         selectedText = sel.toString();
3680                 else
3681                         selectedText = '';
3682         }
3683
3684         return selectedText;
3685 };
3686
3687 TinyMCEControl.prototype.selectNode = function(node, collapse, select_text_node, to_start) {
3688         if (!node)
3689                 return;
3690
3691         if (typeof(collapse) == "undefined")
3692                 collapse = true;
3693
3694         if (typeof(select_text_node) == "undefined")
3695                 select_text_node = false;
3696
3697         if (typeof(to_start) == "undefined")
3698                 to_start = true;
3699
3700         if (tinyMCE.isMSIE) {
3701                 var rng = this.getBody().createTextRange();
3702
3703                 try {
3704                         rng.moveToElementText(node);
3705
3706                         if (collapse)
3707                                 rng.collapse(to_start);
3708
3709                         rng.select();
3710                 } catch (e) {
3711                         // Throws illigal agrument in MSIE some times
3712                 }
3713         } else {
3714                 var sel = this.getSel();
3715
3716                 if (!sel)
3717                         return;
3718
3719                 if (tinyMCE.isSafari) {
3720                         sel.realSelection.setBaseAndExtent(node, 0, node, node.innerText.length);
3721
3722                         if (collapse) {
3723                                 if (to_start)
3724                                         sel.realSelection.collapseToStart();
3725                                 else
3726                                         sel.realSelection.collapseToEnd();
3727                         }
3728
3729                         this.scrollToNode(node);
3730
3731                         return;
3732                 }
3733
3734                 var rng = this.getDoc().createRange();
3735
3736                 if (select_text_node) {
3737                         // Find first textnode in tree
3738                         var nodes = tinyMCE.getNodeTree(node, new Array(), 3);
3739                         if (nodes.length > 0)
3740                                 rng.selectNodeContents(nodes[0]);
3741                         else
3742                                 rng.selectNodeContents(node);
3743                 } else
3744                         rng.selectNode(node);
3745
3746                 if (collapse) {
3747                         // Special treatment of textnode collapse
3748                         if (!to_start && node.nodeType == 3) {
3749                                 rng.setStart(node, node.nodeValue.length);
3750                                 rng.setEnd(node, node.nodeValue.length);
3751                         } else
3752                                 rng.collapse(to_start);
3753                 }
3754
3755                 sel.removeAllRanges();
3756                 sel.addRange(rng);
3757         }
3758
3759         this.scrollToNode(node);
3760
3761         // Set selected element
3762         tinyMCE.selectedElement = null;
3763         if (node.nodeType == 1)
3764                 tinyMCE.selectedElement = node;
3765 };
3766
3767 TinyMCEControl.prototype.scrollToNode = function(node) {
3768         // Scroll to node position
3769         var pos = tinyMCE.getAbsPosition(node);
3770         var doc = this.getDoc();
3771         var scrollX = doc.body.scrollLeft + doc.documentElement.scrollLeft;
3772         var scrollY = doc.body.scrollTop + doc.documentElement.scrollTop;
3773         var height = tinyMCE.isMSIE ? document.getElementById(this.editorId).style.pixelHeight : this.targetElement.clientHeight;
3774
3775         // Only scroll if out of visible area
3776         if (!tinyMCE.settings['auto_resize'] && !(node.absTop > scrollY && node.absTop < (scrollY - 25 + height)))
3777                 this.contentWindow.scrollTo(pos.absLeft, pos.absTop - height + 25);
3778 };
3779
3780 TinyMCEControl.prototype.getBody = function() {
3781         return this.getDoc().body;
3782 };
3783
3784 TinyMCEControl.prototype.getDoc = function() {
3785         return this.contentWindow.document;
3786 };
3787
3788 TinyMCEControl.prototype.getWin = function() {
3789         return this.contentWindow;
3790 };
3791
3792 TinyMCEControl.prototype.getSel = function() {
3793         if (tinyMCE.isMSIE)
3794                 return this.getDoc().selection;
3795
3796         var sel = this.contentWindow.getSelection();
3797
3798         // Fake getRangeAt
3799         if (tinyMCE.isSafari && !sel.getRangeAt) {
3800                 var newSel = new Object();
3801                 var doc = this.getDoc();
3802
3803                 function getRangeAt(idx) {
3804                         var rng = new Object();
3805
3806                         rng.startContainer = this.focusNode;
3807                         rng.endContainer = this.anchorNode;
3808                         rng.commonAncestorContainer = this.focusNode;
3809                         rng.createContextualFragment = function (html) {
3810                                 // Seems to be a tag
3811                                 if (html.charAt(0) == '<') {
3812                                         var elm = doc.createElement("div");
3813
3814                                         elm.innerHTML = html;
3815
3816                                         return elm.firstChild;
3817                                 }
3818
3819                                 return doc.createTextNode("UNSUPPORTED, DUE TO LIMITATIONS IN SAFARI!");
3820                         };
3821
3822                         rng.deleteContents = function () {
3823                                 doc.execCommand("Delete", false, "");
3824                         };
3825
3826                         return rng;
3827                 }
3828
3829                 // Patch selection
3830
3831                 newSel.focusNode = sel.baseNode;
3832                 newSel.focusOffset = sel.baseOffset;
3833                 newSel.anchorNode = sel.extentNode;
3834                 newSel.anchorOffset = sel.extentOffset;
3835                 newSel.getRangeAt = getRangeAt;
3836                 newSel.text = "" + sel;
3837                 newSel.realSelection = sel;
3838
3839                 newSel.toString = function () {return this.text;};
3840
3841                 return newSel;
3842         }
3843
3844         return sel;
3845 };
3846
3847 TinyMCEControl.prototype.getRng = function() {
3848         var sel = this.getSel();
3849         if (sel == null)
3850                 return null;
3851
3852         if (tinyMCE.isMSIE)
3853                 return sel.createRange();
3854
3855         if (tinyMCE.isSafari) {
3856                 var rng = this.getDoc().createRange();
3857                 var sel = this.getSel().realSelection;
3858
3859                 rng.setStart(sel.baseNode, sel.baseOffset);
3860                 rng.setEnd(sel.extentNode, sel.extentOffset);
3861
3862                 return rng;
3863         }
3864
3865         return this.getSel().getRangeAt(0);
3866 };
3867
3868 TinyMCEControl.prototype._insertPara = function(e) {
3869         function isEmpty(para) {
3870                 function isEmptyHTML(html) {
3871                         return html.replace(new RegExp('[ \t\r\n]+', 'g'), '').toLowerCase() == "";
3872                 }
3873
3874                 // Check for images
3875                 if (para.getElementsByTagName("img").length > 0)
3876                         return false;
3877
3878                 // Check for tables
3879                 if (para.getElementsByTagName("table").length > 0)
3880                         return false;
3881
3882                 // Check for HRs
3883                 if (para.getElementsByTagName("hr").length > 0)
3884                         return false;
3885
3886                 // Check all textnodes
3887                 var nodes = tinyMCE.getNodeTree(para, new Array(), 3);
3888                 for (var i=0; i<nodes.length; i++) {
3889                         if (!isEmptyHTML(nodes[i].nodeValue))
3890                                 return false;
3891                 }
3892
3893                 // No images, no tables, no hrs, no text content then it's empty
3894                 return true;
3895         }
3896
3897         var doc = this.getDoc();
3898         var sel = this.getSel();
3899         var win = this.contentWindow;
3900         var rng = sel.getRangeAt(0);
3901         var body = doc.body;
3902         var rootElm = doc.documentElement;
3903         var self = this;
3904         var blockName = "P";
3905
3906 //      tinyMCE.debug(body.innerHTML);
3907
3908 //      debug(e.target, sel.anchorNode.nodeName, sel.focusNode.nodeName, rng.startContainer, rng.endContainer, rng.commonAncestorContainer, sel.anchorOffset, sel.focusOffset, rng.toString());
3909
3910         // Setup before range
3911         var rngBefore = doc.createRange();
3912         rngBefore.setStart(sel.anchorNode, sel.anchorOffset);
3913         rngBefore.collapse(true);
3914
3915         // Setup after range
3916         var rngAfter = doc.createRange();
3917         rngAfter.setStart(sel.focusNode, sel.focusOffset);
3918         rngAfter.collapse(true);
3919
3920         // Setup start/end points
3921         var direct = rngBefore.compareBoundaryPoints(rngBefore.START_TO_END, rngAfter) < 0;
3922         var startNode = direct ? sel.anchorNode : sel.focusNode;
3923         var startOffset = direct ? sel.anchorOffset : sel.focusOffset;
3924         var endNode = direct ? sel.focusNode : sel.anchorNode;
3925         var endOffset = direct ? sel.focusOffset : sel.anchorOffset;
3926
3927         startNode = startNode.nodeName == "BODY" ? startNode.firstChild : startNode;
3928         endNode = endNode.nodeName == "BODY" ? endNode.firstChild : endNode;
3929
3930         // tinyMCE.debug(startNode, endNode);
3931
3932         // Get block elements
3933         var startBlock = tinyMCE.getParentBlockElement(startNode);
3934         var endBlock = tinyMCE.getParentBlockElement(endNode);
3935
3936         // Use current block name
3937         if (startBlock != null) {
3938                 blockName = startBlock.nodeName;
3939
3940                 // Use P instead
3941                 if (blockName == "TD" || blockName == "TABLE" || (blockName == "DIV" && new RegExp('left|right', 'gi').test(startBlock.style.cssFloat)))
3942                         blockName = "P";
3943         }
3944
3945         // Within a list item (use normal behavior)
3946         if ((startBlock != null && startBlock.nodeName == "LI") || (endBlock != null && endBlock.nodeName == "LI"))
3947                 return false;
3948
3949         // Within a table create new paragraphs
3950         if ((startBlock != null && startBlock.nodeName == "TABLE") || (endBlock != null && endBlock.nodeName == "TABLE"))
3951                 startBlock = endBlock = null;
3952
3953         // Setup new paragraphs
3954         var paraBefore = (startBlock != null && startBlock.nodeName.toUpperCase() == blockName) ? startBlock.cloneNode(false) : doc.createElement(blockName);
3955         var paraAfter = (endBlock != null && endBlock.nodeName.toUpperCase() == blockName) ? endBlock.cloneNode(false) : doc.createElement(blockName);
3956
3957         // Setup chop nodes
3958         var startChop = startNode;
3959         var endChop = endNode;
3960
3961         // Get startChop node
3962         node = startChop;
3963         do {
3964                 if (node == body || node.nodeType == 9 || tinyMCE.isBlockElement(node))
3965                         break;
3966
3967                 startChop = node;
3968         } while ((node = node.previousSibling ? node.previousSibling : node.parentNode));
3969
3970         // Get endChop node
3971         node = endChop;
3972         do {
3973                 if (node == body || node.nodeType == 9 || tinyMCE.isBlockElement(node))
3974                         break;
3975
3976                 endChop = node;
3977         } while ((node = node.nextSibling ? node.nextSibling : node.parentNode));
3978
3979         // Fix when only a image is within the TD
3980         if (startChop.nodeName == "TD")
3981                 startChop = startChop.firstChild;
3982
3983         if (endChop.nodeName == "TD")
3984                 endChop = endChop.lastChild;
3985
3986         // If not in a block element
3987         if (startBlock == null) {
3988                 // Delete selection
3989                 rng.deleteContents();
3990                 sel.removeAllRanges();
3991
3992                 if (startChop != rootElm && endChop != rootElm) {
3993                         // Insert paragraph before
3994                         rngBefore = rng.cloneRange();
3995
3996                         if (startChop == body)
3997                                 rngBefore.setStart(startChop, 0);
3998                         else
3999                                 rngBefore.setStartBefore(startChop);
4000
4001                         paraBefore.appendChild(rngBefore.cloneContents());
4002
4003                         // Insert paragraph after
4004                         if (endChop.parentNode.nodeName == blockName)
4005                                 endChop = endChop.parentNode;
4006
4007                         rng.setEndAfter(endChop);
4008                         if (endChop.nodeName != "#text" && endChop.nodeName != "BODY")
4009                                 rngBefore.setEndAfter(endChop);
4010
4011                         var contents = rng.cloneContents();
4012                         if (contents.firstChild && (contents.firstChild.nodeName == blockName || contents.firstChild.nodeName == "BODY")) {
4013                                 var nodes = contents.firstChild.childNodes;
4014                                 for (var i=0; i<nodes.length; i++) {
4015                                         if (nodes[i].nodeName != "BODY")
4016                                                 paraAfter.appendChild(nodes[i]);
4017                                 }
4018                         } else
4019                                 paraAfter.appendChild(contents);
4020
4021                         // Check if it's a empty paragraph
4022                         if (isEmpty(paraBefore))
4023                                 paraBefore.innerHTML = "&nbsp;";
4024
4025                         // Check if it's a empty paragraph
4026                         if (isEmpty(paraAfter))
4027                                 paraAfter.innerHTML = "&nbsp;";
4028
4029                         // Delete old contents
4030                         rng.deleteContents();
4031                         rngAfter.deleteContents();
4032                         rngBefore.deleteContents();
4033
4034                         // Insert new paragraphs
4035                         paraAfter.normalize();
4036                         rngBefore.insertNode(paraAfter);
4037                         paraBefore.normalize();
4038                         rngBefore.insertNode(paraBefore);
4039
4040 //                      tinyMCE.debug("1: ", paraBefore.innerHTML, paraAfter.innerHTML);
4041                 } else {
4042                         body.innerHTML = "<" + blockName + ">&nbsp;</" + blockName + "><" + blockName + ">&nbsp;</" + blockName + ">";
4043                         paraAfter = body.childNodes[1];
4044                 }
4045
4046                 this.selectNode(paraAfter, true, true);
4047
4048                 return true;
4049         }
4050
4051         // Place first part within new paragraph
4052         if (startChop.nodeName == blockName)
4053                 rngBefore.setStart(startChop, 0);
4054         else
4055                 rngBefore.setStartBefore(startChop);
4056         rngBefore.setEnd(startNode, startOffset);
4057         paraBefore.appendChild(rngBefore.cloneContents());
4058
4059         // Place secound part within new paragraph
4060         rngAfter.setEndAfter(endChop);
4061         rngAfter.setStart(endNode, endOffset);
4062         var contents = rngAfter.cloneContents();
4063         if (contents.firstChild && contents.firstChild.nodeName == blockName) {
4064                 var nodes = contents.firstChild.childNodes;
4065                 for (var i=0; i<nodes.length; i++) {
4066                         if (nodes[i].nodeName.toLowerCase() != "body")
4067                                 paraAfter.appendChild(nodes[i]);
4068                 }
4069         } else
4070                 paraAfter.appendChild(contents);
4071
4072         // Check if it's a empty paragraph
4073         if (isEmpty(paraBefore))
4074                 paraBefore.innerHTML = "&nbsp;";
4075
4076         // Check if it's a empty paragraph
4077         if (isEmpty(paraAfter))
4078                 paraAfter.innerHTML = "&nbsp;";
4079
4080         // Create a range around everything
4081         var rng = doc.createRange();
4082
4083         if (!startChop.previousSibling && startChop.parentNode.nodeName.toUpperCase() == blockName) {
4084                 rng.setStartBefore(startChop.parentNode);
4085         } else {
4086                 if (rngBefore.startContainer.nodeName.toUpperCase() == blockName && rngBefore.startOffset == 0)
4087                         rng.setStartBefore(rngBefore.startContainer);
4088                 else
4089                         rng.setStart(rngBefore.startContainer, rngBefore.startOffset);
4090         }
4091
4092         if (!endChop.nextSibling && endChop.parentNode.nodeName.toUpperCase() == blockName)
4093                 rng.setEndAfter(endChop.parentNode);
4094         else
4095                 rng.setEnd(rngAfter.endContainer, rngAfter.endOffset);
4096
4097         // Delete all contents and insert new paragraphs
4098         rng.deleteContents();
4099         rng.insertNode(paraAfter);
4100         rng.insertNode(paraBefore);
4101         // debug("2", paraBefore.innerHTML, paraAfter.innerHTML);
4102
4103         // Normalize
4104         paraAfter.normalize();
4105         paraBefore.normalize();
4106
4107         this.selectNode(paraAfter, true, true);
4108
4109         return true;
4110 };
4111
4112 TinyMCEControl.prototype._handleBackSpace = function(evt_type) {
4113         var doc = this.getDoc();
4114         var sel = this.getSel();
4115         if (sel == null)
4116                 return false;
4117
4118         var rng = sel.getRangeAt(0);
4119         var node = rng.startContainer;
4120         var elm = node.nodeType == 3 ? node.parentNode : node;
4121
4122         if (node == null)
4123                 return;
4124
4125         // Empty node, wrap contents in paragraph
4126         if (elm && elm.nodeName == "") {
4127                 var para = doc.createElement("p");
4128
4129                 while (elm.firstChild)
4130                         para.appendChild(elm.firstChild);
4131
4132                 elm.parentNode.insertBefore(para, elm);
4133                 elm.parentNode.removeChild(elm);
4134
4135                 var rng = rng.cloneRange();
4136                 rng.setStartBefore(node.nextSibling);
4137                 rng.setEndAfter(node.nextSibling);
4138                 rng.extractContents();
4139
4140                 this.selectNode(node.nextSibling, true, true);
4141         }
4142
4143         // Remove empty paragraphs
4144         var para = tinyMCE.getParentBlockElement(node);
4145         if (para != null && para.nodeName.toLowerCase() == 'p' && evt_type == "keypress") {
4146                 var htm = para.innerHTML;
4147                 var block = tinyMCE.getParentBlockElement(node);
4148
4149                 // Empty node, we do the killing!!
4150                 if (htm == "" || htm == "&nbsp;" || block.nodeName.toLowerCase() == "li") {
4151                         var prevElm = para.previousSibling;
4152
4153                         while (prevElm != null && prevElm.nodeType != 1)
4154                                 prevElm = prevElm.previousSibling;
4155
4156                         if (prevElm == null)
4157                                 return false;
4158
4159                         // Get previous elements last text node
4160                         var nodes = tinyMCE.getNodeTree(prevElm, new Array(), 3);
4161                         var lastTextNode = nodes.length == 0 ? null : nodes[nodes.length-1];
4162
4163                         // Select the last text node and move curstor to end
4164                         if (lastTextNode != null)
4165                                 this.selectNode(lastTextNode, true, false, false);
4166
4167                         // Remove the empty paragrapsh
4168                         para.parentNode.removeChild(para);
4169
4170                         //debug("within p element" + para.innerHTML);
4171                         //showHTML(this.getBody().innerHTML);
4172                         return true;
4173                 }
4174         }
4175
4176         // Remove BR elements
4177 /*      while (node != null && (node = node.nextSibling) != null) {
4178                 if (node.nodeName.toLowerCase() == 'br')
4179                         node.parentNode.removeChild(node);
4180                 else if (node.nodeType == 1) // Break at other element
4181                         break;
4182         }*/
4183
4184         //showHTML(this.getBody().innerHTML);
4185
4186         return false;
4187 };
4188
4189 TinyMCEControl.prototype._insertSpace = function() {
4190         return true;
4191 };
4192
4193 TinyMCEControl.prototype.autoResetDesignMode = function() {
4194         // Add fix for tab/style.display none/block problems in Gecko
4195         if (!tinyMCE.isMSIE && tinyMCE.settings['auto_reset_designmode']) {
4196                 var sel = this.getSel();
4197
4198                 // Weird, wheres that cursor selection?
4199                 if (!sel || !sel.rangeCount || sel.rangeCount == 0)
4200                         eval('try { this.getDoc().designMode = "On"; } catch(e) {}');
4201         }
4202 };
4203
4204 TinyMCEControl.prototype.isDirty = function() {
4205         // Is content modified and not in a submit procedure
4206         return this.startContent != tinyMCE.trim(this.getBody().innerHTML) && !tinyMCE.isNotDirty;
4207 };
4208
4209 TinyMCEControl.prototype._mergeElements = function(scmd, pa, ch, override) {
4210         if (scmd == "removeformat") {
4211                 pa.className = "";
4212                 pa.style.cssText = "";
4213                 ch.className = "";
4214                 ch.style.cssText = "";
4215                 return;
4216         }
4217
4218         var st = tinyMCE.parseStyle(tinyMCE.getAttrib(pa, "style"));
4219         var stc = tinyMCE.parseStyle(tinyMCE.getAttrib(ch, "style"));
4220         var className = tinyMCE.getAttrib(pa, "class");
4221
4222         className += " " + tinyMCE.getAttrib(ch, "class");
4223
4224         if (override) {
4225                 for (var n in st) {
4226                         if (typeof(st[n]) == 'function')
4227                                 continue;
4228
4229                         stc[n] = st[n];
4230                 }
4231         } else {
4232                 for (var n in stc) {
4233                         if (typeof(stc[n]) == 'function')
4234                                 continue;
4235
4236                         st[n] = stc[n];
4237                 }
4238         }
4239
4240         tinyMCE.setAttrib(pa, "style", tinyMCE.serializeStyle(st));
4241         tinyMCE.setAttrib(pa, "class", tinyMCE.trim(className));
4242         ch.className = "";
4243         ch.style.cssText = "";
4244         ch.removeAttribute("class");
4245         ch.removeAttribute("style");
4246 };
4247
4248 TinyMCEControl.prototype.execCommand = function(command, user_interface, value) {
4249         var doc = this.getDoc();
4250         var win = this.getWin();
4251         var focusElm = this.getFocusElement();
4252
4253         if (this.lastSafariSelection && !new RegExp('mceStartTyping|mceEndTyping|mceBeginUndoLevel|mceEndUndoLevel|mceAddUndoLevel', 'gi').test(command)) {
4254                 this.moveToBookmark(this.lastSafariSelection);
4255                 tinyMCE.selectedElement = this.lastSafariSelectedElement;
4256         }
4257
4258         // Mozilla issue
4259         if (!tinyMCE.isMSIE && !this.useCSS) {
4260                 try {doc.execCommand("styleWithCSS", false, false);} catch (ex) {}
4261                 try {doc.execCommand("useCSS", false, true);} catch (ex) {}
4262                 this.useCSS = true;
4263         }
4264
4265         //debug("command: " + command + ", user_interface: " + user_interface + ", value: " + value);
4266         this.contentDocument = doc; // <-- Strange, unless this is applied Mozilla 1.3 breaks
4267
4268         // Call theme execcommand
4269         if (tinyMCE._themeExecCommand(this.editorId, this.getBody(), command, user_interface, value))
4270                 return;
4271
4272         // Fix align on images
4273         if (focusElm && focusElm.nodeName == "IMG") {
4274                 var align = focusElm.getAttribute('align');
4275                 var img = command == "JustifyCenter" ? focusElm.cloneNode(false) : focusElm;
4276
4277                 switch (command) {
4278                         case "JustifyLeft":
4279                                 if (align == 'left')
4280                                         img.removeAttribute('align');
4281                                 else
4282                                         img.setAttribute('align', 'left');
4283
4284                                 // Remove the div
4285                                 var div = focusElm.parentNode;
4286                                 if (div && div.nodeName == "DIV" && div.childNodes.length == 1 && div.parentNode)
4287                                         div.parentNode.replaceChild(img, div);
4288
4289                                 this.selectNode(img);
4290                                 this.repaint();
4291                                 tinyMCE.triggerNodeChange();
4292                                 return;
4293
4294                         case "JustifyCenter":
4295                                 img.removeAttribute('align');
4296
4297                                 // Is centered
4298                                 var div = tinyMCE.getParentElement(focusElm, "div");
4299                                 if (div && div.style.textAlign == "center") {
4300                                         // Remove div
4301                                         if (div.nodeName == "DIV" && div.childNodes.length == 1 && div.parentNode)
4302                                                 div.parentNode.replaceChild(img, div);
4303                                 } else {
4304                                         // Add div
4305                                         var div = this.getDoc().createElement("div");
4306                                         div.style.textAlign = 'center';
4307                                         div.appendChild(img);
4308                                         focusElm.parentNode.replaceChild(div, focusElm);
4309                                 }
4310
4311                                 this.selectNode(img);
4312                                 this.repaint();
4313                                 tinyMCE.triggerNodeChange();
4314                                 return;
4315
4316                         case "JustifyRight":
4317                                 if (align == 'right')
4318                                         img.removeAttribute('align');
4319                                 else
4320                                         img.setAttribute('align', 'right');
4321
4322                                 // Remove the div
4323                                 var div = focusElm.parentNode;
4324                                 if (div && div.nodeName == "DIV" && div.childNodes.length == 1 && div.parentNode)
4325                                         div.parentNode.replaceChild(img, div);
4326
4327                                 this.selectNode(img);
4328                                 this.repaint();
4329                                 tinyMCE.triggerNodeChange();
4330                                 return;
4331                 }
4332         }
4333
4334         if (tinyMCE.settings['force_br_newlines']) {
4335                 var alignValue = "";
4336
4337                 if (doc.selection.type != "Control") {
4338                         switch (command) {
4339                                         case "JustifyLeft":
4340                                                 alignValue = "left";
4341                                                 break;
4342
4343                                         case "JustifyCenter":
4344                                                 alignValue = "center";
4345                                                 break;
4346
4347                                         case "JustifyFull":
4348                                                 alignValue = "justify";
4349                                                 break;
4350
4351                                         case "JustifyRight":
4352                                                 alignValue = "right";
4353                                                 break;
4354                         }
4355
4356                         if (alignValue != "") {
4357                                 var rng = doc.selection.createRange();
4358
4359                                 if ((divElm = tinyMCE.getParentElement(rng.parentElement(), "div")) != null)
4360                                         divElm.setAttribute("align", alignValue);
4361                                 else if (rng.pasteHTML && rng.htmlText.length > 0)
4362                                         rng.pasteHTML('<div align="' + alignValue + '">' + rng.htmlText + "</div>");
4363
4364                                 tinyMCE.triggerNodeChange();
4365                                 return;
4366                         }
4367                 }
4368         }
4369
4370         switch (command) {
4371                 case "mceRepaint":
4372                         this.repaint();
4373                         return true;
4374
4375                 case "mceStoreSelection":
4376                         this.selectionBookmark = this.getBookmark();
4377                         return true;
4378
4379                 case "mceRestoreSelection":
4380                         this.moveToBookmark(this.selectionBookmark);
4381                         return true;
4382
4383                 case "InsertUnorderedList":
4384                 case "InsertOrderedList":
4385                         var tag = (command == "InsertUnorderedList") ? "ul" : "ol";
4386
4387                         if (tinyMCE.isSafari)
4388                                 this.execCommand("mceInsertContent", false, "<" + tag + "><li>&nbsp;</li><" + tag + ">");
4389                         else
4390                                 this.getDoc().execCommand(command, user_interface, value);
4391
4392                         tinyMCE.triggerNodeChange();
4393                         break;
4394
4395                 case "Strikethrough":
4396                         if (tinyMCE.isSafari)
4397                                 this.execCommand("mceInsertContent", false, "<strike>" + this.getSelectedHTML() + "</strike>");
4398                         else
4399                                 this.getDoc().execCommand(command, user_interface, value);
4400
4401                         tinyMCE.triggerNodeChange();
4402                         break;
4403
4404                 case "mceSelectNode":
4405                         this.selectNode(value);
4406                         tinyMCE.triggerNodeChange();
4407                         tinyMCE.selectedNode = value;
4408                         break;
4409
4410                 case "FormatBlock":
4411                         if (value == null || value == "") {
4412                                 var elm = tinyMCE.getParentElement(this.getFocusElement(), "p,div,h1,h2,h3,h4,h5,h6,pre,address");
4413
4414                                 if (elm)
4415                                         this.execCommand("mceRemoveNode", false, elm);
4416                         } else
4417                                 this.getDoc().execCommand("FormatBlock", false, value);
4418
4419                         tinyMCE.triggerNodeChange();
4420
4421                         break;
4422
4423                 case "mceRemoveNode":
4424                         if (!value)
4425                                 value = tinyMCE.getParentElement(this.getFocusElement());
4426
4427                         if (tinyMCE.isMSIE) {
4428                                 value.outerHTML = value.innerHTML;
4429                         } else {
4430                                 var rng = value.ownerDocument.createRange();
4431                                 rng.setStartBefore(value);
4432                                 rng.setEndAfter(value);
4433                                 rng.deleteContents();
4434                                 rng.insertNode(rng.createContextualFragment(value.innerHTML));
4435                         }
4436
4437                         tinyMCE.triggerNodeChange();
4438
4439                         break;
4440
4441                 case "mceSelectNodeDepth":
4442                         var parentNode = this.getFocusElement();
4443                         for (var i=0; parentNode; i++) {
4444                                 if (parentNode.nodeName.toLowerCase() == "body")
4445                                         break;
4446
4447                                 if (parentNode.nodeName.toLowerCase() == "#text") {
4448                                         i--;
4449                                         parentNode = parentNode.parentNode;
4450                                         continue;
4451                                 }
4452
4453                                 if (i == value) {
4454                                         this.selectNode(parentNode, false);
4455                                         tinyMCE.triggerNodeChange();
4456                                         tinyMCE.selectedNode = parentNode;
4457                                         return;
4458                                 }
4459
4460                                 parentNode = parentNode.parentNode;
4461                         }
4462
4463                         break;
4464
4465                 case "SetStyleInfo":
4466                         var rng = this.getRng();
4467                         var sel = this.getSel();
4468                         var scmd = value['command'];
4469                         var sname = value['name'];
4470                         var svalue = value['value'];
4471                         var wrapper = value['wrapper'] ? value['wrapper'] : "span";
4472                         var parentElm = null;
4473                         var invalidRe = new RegExp("^BODY|HTML$", "g");
4474
4475                         // Whole element selected check
4476                         if (tinyMCE.isMSIE) {
4477                                 // Control range
4478                                 if (rng.item)
4479                                         parentElm = rng.item(0);
4480                                 else {
4481                                         var pelm = rng.parentElement();
4482                                         var prng = doc.selection.createRange();
4483                                         prng.moveToElementText(pelm);
4484
4485                                         if (rng.htmlText == prng.htmlText || rng.boundingWidth == 0)
4486                                                 parentElm = pelm;
4487                                 }
4488                         } else {
4489                                 var felm = this.getFocusElement();
4490                                 if (sel.isCollapsed || (/td|tr|tbody|table/ig.test(felm.nodeName) && sel.anchorNode == felm.parentNode))
4491                                         parentElm = felm;
4492                         }
4493
4494                         // Whole element selected
4495                         if (parentElm && !invalidRe.test(parentElm.nodeName)) {
4496                                 if (scmd == "setstyle")
4497                                         eval("parentElm.style." + sname + " = svalue;");
4498
4499                                 if (scmd == "setattrib")
4500                                         tinyMCE.setAttrib(parentElm, sname, svalue);
4501
4502                                 if (scmd == "removeformat") {
4503                                         parentElm.style.cssText = '';
4504                                         tinyMCE.setAttrib(parentElm, 'class', '');
4505                                 }
4506
4507                                 // Remove style/attribs from all children
4508                                 var ch = tinyMCE.getNodeTree(parentElm, new Array(), 1);
4509                                 for (var z=0; z<ch.length; z++) {
4510                                         if (ch[z] == parentElm)
4511                                                 continue;
4512
4513                                         if (scmd == "setstyle")
4514                                                 eval("ch[z].style." + sname + " = '';");
4515
4516                                         if (scmd == "setattrib")
4517                                                 tinyMCE.setAttrib(ch[z], sname, '');
4518
4519                                         if (scmd == "removeformat") {
4520                                                 ch[z].style.cssText = '';
4521                                                 tinyMCE.setAttrib(ch[z], 'class', '');
4522                                         }
4523                                 }
4524                         } else {
4525                                 doc.execCommand("fontname", false, "#mce_temp_font#");
4526                                 var elementArray = tinyMCE.getElementsByAttributeValue(this.getBody(), "font", "face", "#mce_temp_font#");
4527
4528                                 // Change them all
4529                                 for (var x=0; x<elementArray.length; x++) {
4530                                         elm = elementArray[x];
4531                                         if (elm) {
4532                                                 var spanElm = doc.createElement(wrapper);
4533
4534                                                 if (scmd == "setstyle")
4535                                                         eval("spanElm.style." + sname + " = svalue;");
4536
4537                                                 if (scmd == "setattrib")
4538                                                         tinyMCE.setAttrib(spanElm, sname, svalue);
4539
4540                                                 if (scmd == "removeformat") {
4541                                                         spanElm.style.cssText = '';
4542                                                         tinyMCE.setAttrib(spanElm, 'class', '');
4543                                                 }
4544
4545                                                 if (elm.hasChildNodes()) {
4546                                                         for (var i=0; i<elm.childNodes.length; i++)
4547                                                                 spanElm.appendChild(elm.childNodes[i].cloneNode(true));
4548                                                 }
4549
4550                                                 spanElm.setAttribute("mce_new", "true");
4551                                                 elm.parentNode.replaceChild(spanElm, elm);
4552
4553                                                 // Remove style/attribs from all children
4554                                                 var ch = tinyMCE.getNodeTree(spanElm, new Array(), 1);
4555                                                 for (var z=0; z<ch.length; z++) {
4556                                                         if (ch[z] == spanElm)
4557                                                                 continue;
4558
4559                                                         if (scmd == "setstyle")
4560                                                                 eval("ch[z].style." + sname + " = '';");
4561
4562                                                         if (scmd == "setattrib")
4563                                                                 tinyMCE.setAttrib(ch[z], sname, '');
4564
4565                                                         if (scmd == "removeformat") {
4566                                                                 ch[z].style.cssText = '';
4567                                                                 tinyMCE.setAttrib(ch[z], 'class', '');
4568                                                         }
4569                                                 }
4570                                         }
4571                                 }
4572                         }
4573
4574                         // Cleaup wrappers
4575                         var nodes = doc.getElementsByTagName(wrapper);
4576                         for (var i=nodes.length-1; i>=0; i--) {
4577                                 var elm = nodes[i];
4578                                 var isNew = tinyMCE.getAttrib(elm, "mce_new") == "true";
4579
4580                                 elm.removeAttribute("mce_new");
4581
4582                                 // Is only child a element
4583                                 if (elm.childNodes && elm.childNodes.length == 1 && elm.childNodes[0].nodeType == 1) {
4584                                         //tinyMCE.debug("merge1" + isNew);
4585                                         this._mergeElements(scmd, elm, elm.childNodes[0], isNew);
4586                                         continue;
4587                                 }
4588
4589                                 // Is I the only child
4590                                 if (elm.parentNode.childNodes.length == 1 && !invalidRe.test(elm.nodeName) && !invalidRe.test(elm.parentNode.nodeName)) {
4591                                         //tinyMCE.debug("merge2" + isNew + "," + elm.nodeName + "," + elm.parentNode.nodeName);
4592                                         this._mergeElements(scmd, elm.parentNode, elm, false);
4593                                 }
4594                         }
4595
4596                         // Remove empty wrappers
4597                         var nodes = doc.getElementsByTagName(wrapper);
4598                         for (var i=nodes.length-1; i>=0; i--) {
4599                                 var elm = nodes[i];
4600                                 var isEmpty = true;
4601
4602                                 // Check if it has any attribs
4603                                 var tmp = doc.createElement("body");
4604                                 tmp.appendChild(elm.cloneNode(false));
4605
4606                                 // Is empty span, remove it
4607                                 tmp.innerHTML = tmp.innerHTML.replace(new RegExp('style|class=""', 'gi'), '');
4608                                 if (new RegExp('<span>', 'gi').test(tmp.innerHTML)) {
4609                                         for (var x=0; x<elm.childNodes.length; x++) {
4610                                                 if (elm.parentNode != null)
4611                                                         elm.parentNode.insertBefore(elm.childNodes[x].cloneNode(true), elm);
4612                                         }
4613
4614                                         elm.parentNode.removeChild(elm);
4615                                 }
4616                         }
4617
4618                         // Re add the visual aids
4619                         if (scmd == "removeformat")
4620                                 tinyMCE.handleVisualAid(this.getBody(), true, this.visualAid, this);
4621
4622                         tinyMCE.triggerNodeChange();
4623
4624                         break;
4625
4626                 case "FontName":
4627                         if (tinyMCE.getParam("convert_fonts_to_styles"))
4628                                 this.execCommand("SetStyleInfo", false, {command : "setstyle", name : "fontFamily", value : value});
4629                         else
4630                                 this.getDoc().execCommand('FontName', false, value);
4631                         break;
4632
4633                 case "FontSize":
4634                         if (tinyMCE.getParam("convert_fonts_to_styles")) {
4635                                 var sizes = new Array('', 8, 10, 12, 14, 18, 24, 36);
4636                                 var size = sizes[value] == '' ? '' : sizes[value] + 'px';
4637
4638                                 this.execCommand("SetStyleInfo", false, {command : "setstyle", name : "fontSize", value : size});
4639                         } else
4640                                 this.getDoc().execCommand('FontSize', false, value);
4641
4642                         break;
4643
4644                 case "forecolor":
4645                         if (tinyMCE.getParam("convert_fonts_to_styles"))
4646                                 this.execCommand("SetStyleInfo", false, {command : "setstyle", name : "color", value : value});
4647                         else {
4648                                 if (tinyMCE.isGecko) {
4649                                         this.getDoc().execCommand("useCSS", false, true);
4650                                         this.getDoc().execCommand('forecolor', false, value);
4651                                         this.getDoc().execCommand("useCSS", false, true);
4652                                 } else
4653                                         this.getDoc().execCommand('forecolor', false, value);
4654                         }
4655                         break;
4656
4657                 case "HiliteColor":
4658                         if (tinyMCE.getParam("convert_fonts_to_styles"))
4659                                 this.execCommand("SetStyleInfo", false, {command : "setstyle", name : "backgroundColor", value : value});
4660                         else {
4661                                 if (tinyMCE.isGecko) {
4662                                         this.getDoc().execCommand("useCSS", false, false);
4663                                         this.getDoc().execCommand('hilitecolor', false, value);
4664                                         this.getDoc().execCommand("useCSS", false, true);
4665                                 } else
4666                                         this.getDoc().execCommand('BackColor', false, value);
4667                         }
4668
4669                         break;
4670
4671                 case "Cut":
4672                 case "Copy":
4673                 case "Paste":
4674                         var cmdFailed = false;
4675
4676                         // Try executing command
4677                         eval('try {this.getDoc().execCommand(command, user_interface, value);} catch (e) {cmdFailed = true;}');
4678
4679                         // Alert error in gecko if command failed
4680                         if (tinyMCE.isGecko && cmdFailed) {
4681                                 // Confirm more info
4682                                 if (confirm(tinyMCE.getLang('lang_clipboard_msg')))
4683                                         window.open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', 'mceExternal');
4684
4685                                 return;
4686                         } else
4687                                 tinyMCE.triggerNodeChange();
4688                 break;
4689
4690                 case "mceSetContent":
4691                         if (!value)
4692                                 value = "";
4693
4694                         // Call custom cleanup code
4695                         value = tinyMCE._customCleanup(this, "insert_to_editor", value);
4696                         tinyMCE._setHTML(doc, value);
4697                         doc.body.innerHTML = tinyMCE._cleanupHTML(this, doc, tinyMCE.settings, doc.body);
4698                         tinyMCE.handleVisualAid(doc.body, true, this.visualAid, this);
4699                         tinyMCE._setEventsEnabled(doc.body, true);
4700                         return true;
4701
4702                 case "mceLink":
4703                         var selectedText = "";
4704
4705                         if (tinyMCE.isMSIE) {
4706                                 var rng = doc.selection.createRange();
4707                                 selectedText = rng.text;
4708                         } else
4709                                 selectedText = this.getSel().toString();
4710
4711                         if (!tinyMCE.linkElement) {
4712                                 if ((tinyMCE.selectedElement.nodeName.toLowerCase() != "img") && (selectedText.length <= 0))
4713                                         return;
4714                         }
4715
4716                         var href = "", target = "", title = "", onclick = "", action = "insert", style_class = "";
4717
4718                         if (tinyMCE.selectedElement.nodeName.toLowerCase() == "a")
4719                                 tinyMCE.linkElement = tinyMCE.selectedElement;
4720
4721                         // Is anchor not a link
4722                         if (tinyMCE.linkElement != null && tinyMCE.getAttrib(tinyMCE.linkElement, 'href') == "")
4723                                 tinyMCE.linkElement = null;
4724
4725                         if (tinyMCE.linkElement) {
4726                                 href = tinyMCE.getAttrib(tinyMCE.linkElement, 'href');
4727                                 target = tinyMCE.getAttrib(tinyMCE.linkElement, 'target');
4728                                 title = tinyMCE.getAttrib(tinyMCE.linkElement, 'title');
4729                 onclick = tinyMCE.getAttrib(tinyMCE.linkElement, 'onclick');
4730                                 style_class = tinyMCE.getAttrib(tinyMCE.linkElement, 'class');
4731
4732                                 // Try old onclick to if copy/pasted content
4733                                 if (onclick == "")
4734                                         onclick = tinyMCE.getAttrib(tinyMCE.linkElement, 'onclick');
4735
4736                                 onclick = tinyMCE.cleanupEventStr(onclick);
4737
4738                                 // Fix for drag-drop/copy paste bug in Mozilla
4739                                 mceRealHref = tinyMCE.getAttrib(tinyMCE.linkElement, 'mce_real_href');
4740                                 if (mceRealHref != "")
4741                                         href = mceRealHref;
4742
4743                                 href = eval(tinyMCE.settings['urlconverter_callback'] + "(href, tinyMCE.linkElement, true);");
4744                                 action = "update";
4745                         }
4746
4747                         if (this.settings['insertlink_callback']) {
4748                                 var returnVal = eval(this.settings['insertlink_callback'] + "(href, target, title, onclick, action, style_class);");
4749                                 if (returnVal && returnVal['href'])
4750                                         tinyMCE.insertLink(returnVal['href'], returnVal['target'], returnVal['title'], returnVal['onclick'], returnVal['style_class']);
4751                         } else {
4752                                 tinyMCE.openWindow(this.insertLinkTemplate, {href : href, target : target, title : title, onclick : onclick, action : action, className : style_class});
4753                         }
4754                 break;
4755
4756                 case "mceImage":
4757                         var src = "", alt = "", border = "", hspace = "", vspace = "", width = "", height = "", align = "";
4758                         var title = "", onmouseover = "", onmouseout = "", action = "insert";
4759                         var img = tinyMCE.imgElement;
4760
4761                         if (tinyMCE.selectedElement != null && tinyMCE.selectedElement.nodeName.toLowerCase() == "img") {
4762                                 img = tinyMCE.selectedElement;
4763                                 tinyMCE.imgElement = img;
4764                         }
4765
4766                         if (img) {
4767                                 // Is it a internal MCE visual aid image, then skip this one.
4768                                 if (tinyMCE.getAttrib(img, 'name').indexOf('mce_') == 0)
4769                                         return;
4770
4771                                 src = tinyMCE.getAttrib(img, 'src');
4772                                 alt = tinyMCE.getAttrib(img, 'alt');
4773
4774                                 // Try polling out the title
4775                                 if (alt == "")
4776                                         alt = tinyMCE.getAttrib(img, 'title');
4777
4778                                 // Fix width/height attributes if the styles is specified
4779                                 if (tinyMCE.isGecko) {
4780                                         var w = img.style.width;
4781                                         if (w != null && w != "")
4782                                                 img.setAttribute("width", w);
4783
4784                                         var h = img.style.height;
4785                                         if (h != null && h != "")
4786                                                 img.setAttribute("height", h);
4787                                 }
4788
4789                                 border = tinyMCE.getAttrib(img, 'border');
4790                                 hspace = tinyMCE.getAttrib(img, 'hspace');
4791                                 vspace = tinyMCE.getAttrib(img, 'vspace');
4792                                 width = tinyMCE.getAttrib(img, 'width');
4793                                 height = tinyMCE.getAttrib(img, 'height');
4794                                 align = tinyMCE.getAttrib(img, 'align');
4795                 onmouseover = tinyMCE.getAttrib(img, 'onmouseover');
4796                 onmouseout = tinyMCE.getAttrib(img, 'onmouseout');
4797                 title = tinyMCE.getAttrib(img, 'title');
4798
4799                                 // Is realy specified?
4800                                 if (tinyMCE.isMSIE) {
4801                                         width = img.attributes['width'].specified ? width : "";
4802                                         height = img.attributes['height'].specified ? height : "";
4803                                 }
4804
4805                                 onmouseover = tinyMCE.getImageSrc(tinyMCE.cleanupEventStr(onmouseover));
4806                                 onmouseout = tinyMCE.getImageSrc(tinyMCE.cleanupEventStr(onmouseout));
4807
4808                                 // Fix for drag-drop/copy paste bug in Mozilla
4809                                 mceRealSrc = tinyMCE.getAttrib(img, 'mce_real_src');
4810                                 if (mceRealSrc != "")
4811                                         src = mceRealSrc;
4812
4813                                 src = eval(tinyMCE.settings['urlconverter_callback'] + "(src, img, true);");
4814
4815                                 if (onmouseover != "")
4816                                         onmouseover = eval(tinyMCE.settings['urlconverter_callback'] + "(onmouseover, img, true);");
4817
4818                                 if (onmouseout != "")
4819                                         onmouseout = eval(tinyMCE.settings['urlconverter_callback'] + "(onmouseout, img, true);");
4820
4821                                 action = "update";
4822                         }
4823
4824                         if (this.settings['insertimage_callback']) {
4825                                 var returnVal = eval(this.settings['insertimage_callback'] + "(src, alt, border, hspace, vspace, width, height, align, title, onmouseover, onmouseout, action);");
4826                                 if (returnVal && returnVal['src'])
4827                                         tinyMCE.insertImage(returnVal['src'], returnVal['alt'], returnVal['border'], returnVal['hspace'], returnVal['vspace'], returnVal['width'], returnVal['height'], returnVal['align'], returnVal['title'], returnVal['onmouseover'], returnVal['onmouseout']);
4828                         } else
4829                                 tinyMCE.openWindow(this.insertImageTemplate, {src : src, alt : alt, border : border, hspace : hspace, vspace : vspace, width : width, height : height, align : align, title : title, onmouseover : onmouseover, onmouseout : onmouseout, action : action});
4830                 break;
4831
4832                 case "mceCleanup":
4833                         tinyMCE._setHTML(this.contentDocument, this.getBody().innerHTML);
4834                         this.getBody().innerHTML = tinyMCE._cleanupHTML(this, this.contentDocument, this.settings, this.getBody(), this.visualAid);
4835                         tinyMCE.handleVisualAid(this.getBody(), true, this.visualAid, this);
4836                         tinyMCE._setEventsEnabled(this.getBody(), true);
4837                         this.repaint();
4838                         tinyMCE.triggerNodeChange();
4839                 break;
4840
4841                 case "mceReplaceContent":
4842                         var selectedText = "";
4843
4844                         if (tinyMCE.isMSIE) {
4845                                 var rng = doc.selection.createRange();
4846                                 selectedText = rng.text;
4847                         } else
4848                                 selectedText = this.getSel().toString();
4849
4850                         if (selectedText.length > 0) {
4851                                 value = tinyMCE.replaceVar(value, "selection", selectedText);
4852                                 tinyMCE.execCommand('mceInsertContent', false, value);
4853                         }
4854
4855                         tinyMCE.triggerNodeChange();
4856                 break;
4857
4858                 case "mceSetAttribute":
4859                         if (typeof(value) == 'object') {
4860                                 var targetElms = (typeof(value['targets']) == "undefined") ? "p,img,span,div,td,h1,h2,h3,h4,h5,h6,pre,address" : value['targets'];
4861                                 var targetNode = tinyMCE.getParentElement(this.getFocusElement(), targetElms);
4862
4863                                 if (targetNode) {
4864                                         targetNode.setAttribute(value['name'], value['value']);
4865                                         tinyMCE.triggerNodeChange();
4866                                 }
4867                         }
4868                 break;
4869
4870                 case "mceSetCSSClass":
4871                         this.execCommand("SetStyleInfo", false, {command : "setattrib", name : "class", value : value});
4872                 break;
4873
4874                 case "mceInsertRawHTML":
4875                         var key = 'tiny_mce_marker';
4876
4877                         this.execCommand('mceBeginUndoLevel');
4878
4879                         // Insert marker key
4880                         this.execCommand('mceInsertContent', false, key);
4881
4882                         // Store away scroll pos
4883                         var scrollX = this.getDoc().body.scrollLeft + this.getDoc().documentElement.scrollLeft;
4884                         var scrollY = this.getDoc().body.scrollTop + this.getDoc().documentElement.scrollTop;
4885
4886                         // Find marker and replace with RAW HTML
4887                         var html = this.getBody().innerHTML;
4888                         if ((pos = html.indexOf(key)) != -1)
4889                                 this.getBody().innerHTML = html.substring(0, pos) + value + html.substring(pos + key.length);
4890
4891                         // Restore scoll pos
4892                         this.contentWindow.scrollTo(scrollX, scrollY);
4893
4894                         this.execCommand('mceEndUndoLevel');
4895
4896                         break;
4897
4898                 case "mceInsertContent":
4899                         if (!tinyMCE.isMSIE) {
4900                                 var sel = this.getSel();
4901                                 var rng = this.getRng();
4902                                 var isHTML = value.indexOf('<') != -1;
4903
4904                                 if (isHTML) {
4905                                         if (tinyMCE.isSafari) {
4906                                                 var tmpRng = this.getDoc().createRange();
4907
4908                                                 tmpRng.setStart(this.getBody(), 0);
4909                                                 tmpRng.setEnd(this.getBody(), 0);
4910
4911                                                 value = tmpRng.createContextualFragment(value);
4912                                         } else
4913                                                 value = rng.createContextualFragment(value);
4914                                 } else {
4915                                         // Setup text node
4916                                         var el = document.createElement("div");
4917                                         el.innerHTML = value;
4918                                         value = el.firstChild.nodeValue;
4919                                         value = doc.createTextNode(value);
4920                                 }
4921
4922                                 // Insert plain text in Safari
4923                                 if (tinyMCE.isSafari && !isHTML) {
4924                                         this.execCommand('InsertText', false, value.nodeValue);
4925                                         tinyMCE.triggerNodeChange();
4926                                         return true;
4927                                 } else if (tinyMCE.isSafari && isHTML) {
4928                                         rng.deleteContents();
4929                                         rng.insertNode(value);
4930                                         tinyMCE.triggerNodeChange();
4931                                         return true;
4932                                 }
4933
4934                                 rng.deleteContents();
4935
4936                                 // If target node is text do special treatment, (Mozilla 1.3 fix)
4937                                 if (rng.startContainer.nodeType == 3) {
4938                                         var node = rng.startContainer.splitText(rng.startOffset);
4939                                         node.parentNode.insertBefore(value, node); 
4940                                 } else
4941                                         rng.insertNode(value);
4942
4943                                 if (!isHTML) {
4944                                         // Removes weird selection trails
4945                                         sel.selectAllChildren(doc.body);
4946                                         sel.removeAllRanges();
4947
4948                                         // Move cursor to end of content
4949                                         var rng = doc.createRange();
4950
4951                                         rng.selectNode(value);
4952                                         rng.collapse(false);
4953
4954                                         sel.addRange(rng);
4955                                 } else
4956                                         rng.collapse(false);
4957                         } else {
4958                                 var rng = doc.selection.createRange();
4959
4960                                 if (rng.item)
4961                                         rng.item(0).outerHTML = value;
4962                                 else
4963                                         rng.pasteHTML(value);
4964                         }
4965
4966                         tinyMCE.triggerNodeChange();
4967                 break;
4968
4969                 case "mceStartTyping":
4970                         if (tinyMCE.settings['custom_undo_redo'] && this.typingUndoIndex == -1) {
4971                                 this.typingUndoIndex = this.undoIndex;
4972                                 this.execCommand('mceAddUndoLevel');
4973                                 //tinyMCE.debug("mceStartTyping");
4974                         }
4975                         break;
4976
4977                 case "mceEndTyping":
4978                         if (tinyMCE.settings['custom_undo_redo'] && this.typingUndoIndex != -1) {
4979                                 this.execCommand('mceAddUndoLevel');
4980                                 this.typingUndoIndex = -1;
4981                                 //tinyMCE.debug("mceEndTyping");
4982                         }
4983                         break;
4984
4985                 case "mceBeginUndoLevel":
4986                         this.undoRedo = false;
4987                         break;
4988
4989                 case "mceEndUndoLevel":
4990                         this.undoRedo = true;
4991                         this.execCommand('mceAddUndoLevel');
4992                         break;
4993
4994                 case "mceAddUndoLevel":
4995                         if (tinyMCE.settings['custom_undo_redo'] && this.undoRedo) {
4996                                 // tinyMCE.debug("add level");
4997
4998                                 if (this.typingUndoIndex != -1) {
4999                                         this.undoIndex = this.typingUndoIndex;
5000                                         // tinyMCE.debug("Override: " + this.undoIndex);
5001                                 }
5002
5003                                 var newHTML = tinyMCE.trim(this.getBody().innerHTML);
5004                                 if (newHTML != this.undoLevels[this.undoIndex]) {
5005                                         // tinyMCE.debug("[" + newHTML + "," + this.undoLevels[this.undoIndex] + "]");
5006
5007                                         tinyMCE.executeCallback('onchange_callback', '_onchange', 0, this);
5008
5009                                         // Time to compress
5010                                         var customUndoLevels = tinyMCE.settings['custom_undo_redo_levels'];
5011                                         if (customUndoLevels != -1 && this.undoLevels.length > customUndoLevels) {
5012                                                 for (var i=0; i<this.undoLevels.length-1; i++) {
5013                                                         //tinyMCE.debug(this.undoLevels[i] + "=" + this.undoLevels[i+1]);
5014                                                         this.undoLevels[i] = this.undoLevels[i+1];
5015                                                 }
5016
5017                                                 this.undoLevels.length--;
5018                                                 this.undoIndex--;
5019                                         }
5020
5021                                         this.undoIndex++;
5022                                         this.undoLevels[this.undoIndex] = newHTML;
5023                                         this.undoLevels.length = this.undoIndex + 1;
5024
5025                                         // tinyMCE.debug("level added" + this.undoIndex);
5026                                         tinyMCE.triggerNodeChange(false);
5027
5028                                         // tinyMCE.debug(this.undoIndex + "," + (this.undoLevels.length-1));
5029                                 }
5030                         }
5031                         break;
5032
5033                 case "Undo":
5034                         if (tinyMCE.settings['custom_undo_redo']) {
5035                                 tinyMCE.execCommand("mceEndTyping");
5036
5037                                 // Do undo
5038                                 if (this.undoIndex > 0) {
5039                                         this.undoIndex--;
5040                                         this.getBody().innerHTML = this.undoLevels[this.undoIndex];
5041                                         this.repaint();
5042                                 }
5043
5044                                 // tinyMCE.debug("Undo - undo levels:" + this.undoLevels.length + ", undo index: " + this.undoIndex);
5045                                 tinyMCE.triggerNodeChange();
5046                         } else
5047                                 this.getDoc().execCommand(command, user_interface, value);
5048                         break;
5049
5050                 case "Redo":
5051                         if (tinyMCE.settings['custom_undo_redo']) {
5052                                 tinyMCE.execCommand("mceEndTyping");
5053
5054                                 if (this.undoIndex < (this.undoLevels.length-1)) {
5055                                         this.undoIndex++;
5056                                         this.getBody().innerHTML = this.undoLevels[this.undoIndex];
5057                                         this.repaint();
5058                                         // tinyMCE.debug("Redo - undo levels:" + this.undoLevels.length + ", undo index: " + this.undoIndex);
5059                                 }
5060
5061                                 tinyMCE.triggerNodeChange();
5062                         } else
5063                                 this.getDoc().execCommand(command, user_interface, value);
5064                         break;
5065
5066                 case "mceToggleVisualAid":
5067                         this.visualAid = !this.visualAid;
5068                         tinyMCE.handleVisualAid(this.getBody(), true, this.visualAid, this);
5069                         tinyMCE.triggerNodeChange();
5070                         break;
5071
5072                 case "Indent":
5073                         this.getDoc().execCommand(command, user_interface, value);
5074                         tinyMCE.triggerNodeChange();
5075                         if (tinyMCE.isMSIE) {
5076                                 var n = tinyMCE.getParentElement(this.getFocusElement(), "blockquote");
5077                                 do {
5078                                         if (n && n.nodeName == "BLOCKQUOTE") {
5079                                                 n.removeAttribute("dir");
5080                                                 n.removeAttribute("style");
5081                                         }
5082                                 } while (n = n.parentNode);
5083                         }
5084                         break;
5085
5086                 case "removeformat":
5087                         var text = this.getSelectedText();
5088
5089                         if (tinyMCE.isMSIE) {
5090                                 try {
5091                                         var rng = doc.selection.createRange();
5092                                         rng.execCommand("RemoveFormat", false, null);
5093                                 } catch (e) {
5094                                         // Do nothing
5095                                 }
5096
5097                                 this.execCommand("SetStyleInfo", false, {command : "removeformat"});
5098                         } else {
5099                                 this.getDoc().execCommand(command, user_interface, value);
5100
5101                                 this.execCommand("SetStyleInfo", false, {command : "removeformat"});
5102                         }
5103
5104                         // Remove class
5105                         if (text.length == 0)
5106                                 this.execCommand("mceSetCSSClass", false, "");
5107
5108                         tinyMCE.triggerNodeChange();
5109                         break;
5110
5111                 default:
5112                         this.getDoc().execCommand(command, user_interface, value);
5113                         tinyMCE.triggerNodeChange();
5114         }
5115
5116         // Add undo level after modification
5117         if (command != "mceAddUndoLevel" && command != "Undo" && command != "Redo" && command != "mceStartTyping" && command != "mceEndTyping")
5118                 tinyMCE.execCommand("mceAddUndoLevel");
5119 };
5120
5121 TinyMCEControl.prototype.queryCommandValue = function(command) {
5122         return this.getDoc().queryCommandValue(command);
5123 };
5124
5125 TinyMCEControl.prototype.queryCommandState = function(command) {
5126         return this.getDoc().queryCommandState(command);
5127 };
5128
5129 TinyMCEControl.prototype.onAdd = function(replace_element, form_element_name, target_document) {
5130         var targetDoc = target_document ? target_document : document;
5131
5132         this.targetDoc = targetDoc;
5133
5134         tinyMCE.themeURL = tinyMCE.baseURL + "/themes/" + this.settings['theme'];
5135         this.settings['themeurl'] = tinyMCE.themeURL;
5136
5137         if (!replace_element) {
5138                 alert("Error: Could not find the target element.");
5139                 return false;
5140         }
5141
5142         var templateFunction = tinyMCE._getThemeFunction('_getInsertLinkTemplate');
5143         if (eval("typeof(" + templateFunction + ")") != 'undefined')
5144                 this.insertLinkTemplate = eval(templateFunction + '(this.settings);');
5145
5146         var templateFunction = tinyMCE._getThemeFunction('_getInsertImageTemplate');
5147         if (eval("typeof(" + templateFunction + ")") != 'undefined')
5148                 this.insertImageTemplate = eval(templateFunction + '(this.settings);');
5149
5150         var templateFunction = tinyMCE._getThemeFunction('_getEditorTemplate');
5151         if (eval("typeof(" + templateFunction + ")") == 'undefined') {
5152                 alert("Error: Could not find the template function: " + templateFunction);
5153                 return false;
5154         }
5155
5156         var editorTemplate = eval(templateFunction + '(this.settings, this.editorId);');
5157
5158         var deltaWidth = editorTemplate['delta_width'] ? editorTemplate['delta_width'] : 0;
5159         var deltaHeight = editorTemplate['delta_height'] ? editorTemplate['delta_height'] : 0;
5160         var html = '<span id="' + this.editorId + '_parent">' + editorTemplate['html'];
5161
5162         var templateFunction = tinyMCE._getThemeFunction('_handleNodeChange', true);
5163         if (eval("typeof(" + templateFunction + ")") != 'undefined')
5164                 this.settings['handleNodeChangeCallback'] = templateFunction;
5165
5166         html = tinyMCE.replaceVar(html, "editor_id", this.editorId);
5167         this.settings['default_document'] = tinyMCE.baseURL + "/blank.htm";
5168
5169         this.settings['old_width'] = this.settings['width'];
5170         this.settings['old_height'] = this.settings['height'];
5171
5172         // Set default width, height
5173         if (this.settings['width'] == -1)
5174                 this.settings['width'] = replace_element.offsetWidth;
5175
5176         if (this.settings['height'] == -1)
5177                 this.settings['height'] = replace_element.offsetHeight;
5178
5179         // Try the style width
5180         if (this.settings['width'] == 0)
5181                 this.settings['width'] = replace_element.style.width;
5182
5183         // Try the style height
5184         if (this.settings['height'] == 0)
5185                 this.settings['height'] = replace_element.style.height; 
5186
5187         // If no width/height then default to 320x240, better than nothing
5188         if (this.settings['width'] == 0)
5189                 this.settings['width'] = 320;
5190
5191         if (this.settings['height'] == 0)
5192                 this.settings['height'] = 240;
5193
5194         this.settings['area_width'] = parseInt(this.settings['width']);
5195         this.settings['area_height'] = parseInt(this.settings['height']);
5196         this.settings['area_width'] += deltaWidth;
5197         this.settings['area_height'] += deltaHeight;
5198
5199         // Special % handling
5200         if (("" + this.settings['width']).indexOf('%') != -1)
5201                 this.settings['area_width'] = "100%";
5202
5203         if (("" + this.settings['height']).indexOf('%') != -1)
5204                 this.settings['area_height'] = "100%";
5205
5206         if (("" + replace_element.style.width).indexOf('%') != -1) {
5207                 this.settings['width'] = replace_element.style.width;
5208                 this.settings['area_width'] = "100%";
5209         }
5210
5211         if (("" + replace_element.style.height).indexOf('%') != -1) {
5212                 this.settings['height'] = replace_element.style.height;
5213                 this.settings['area_height'] = "100%";
5214         }
5215
5216         html = tinyMCE.applyTemplate(html);
5217
5218         this.settings['width'] = this.settings['old_width'];
5219         this.settings['height'] = this.settings['old_height'];
5220
5221         this.visualAid = this.settings['visual'];
5222         this.formTargetElementId = form_element_name;
5223
5224         // Get replace_element contents
5225         if (replace_element.nodeName == "TEXTAREA" || replace_element.nodeName == "INPUT")
5226                 this.startContent = replace_element.value;
5227         else
5228                 this.startContent = replace_element.innerHTML;
5229
5230         // If not text area
5231         if (replace_element.nodeName.toLowerCase() != "textarea") {
5232                 this.oldTargetElement = replace_element.cloneNode(true);
5233
5234                 // Debug mode
5235                 if (tinyMCE.settings['debug'])
5236                         html += '<textarea wrap="off" id="' + form_element_name + '" name="' + form_element_name + '" cols="100" rows="15"></textarea>';
5237                 else
5238                         html += '<input type="hidden" type="text" id="' + form_element_name + '" name="' + form_element_name + '" />';
5239
5240                 html += '</span>';
5241
5242                 // Output HTML and set editable
5243                 if (!tinyMCE.isMSIE) {
5244                         var rng = replace_element.ownerDocument.createRange();
5245                         rng.setStartBefore(replace_element);
5246
5247                         var fragment = rng.createContextualFragment(html);
5248                         replace_element.parentNode.replaceChild(fragment, replace_element);
5249                 } else
5250                         replace_element.outerHTML = html;
5251         } else {
5252                 html += '</span>';
5253
5254                 // Just hide the textarea element
5255                 this.oldTargetElement = replace_element;
5256
5257                 if (!tinyMCE.settings['debug'])
5258                         this.oldTargetElement.style.display = "none";
5259
5260                 // Output HTML and set editable
5261                 if (!tinyMCE.isMSIE) {
5262                         var rng = replace_element.ownerDocument.createRange();
5263                         rng.setStartBefore(replace_element);
5264
5265                         var fragment = rng.createContextualFragment(html);
5266                         replace_element.parentNode.insertBefore(fragment, replace_element);
5267                 } else
5268                         replace_element.insertAdjacentHTML("beforeBegin", html);
5269         }
5270
5271         // Setup iframe
5272         var dynamicIFrame = false;
5273         var tElm = targetDoc.getElementById(this.editorId);
5274
5275         if (!tinyMCE.isMSIE) {
5276                 if (tElm && tElm.nodeName.toLowerCase() == "span") {
5277                         tElm = tinyMCE._createIFrame(tElm);
5278                         dynamicIFrame = true;
5279                 }
5280
5281                 this.targetElement = tElm;
5282                 this.iframeElement = tElm;
5283                 this.contentDocument = tElm.contentDocument;
5284                 this.contentWindow = tElm.contentWindow;
5285
5286                 //this.getDoc().designMode = "on";
5287         } else {
5288                 if (tElm && tElm.nodeName.toLowerCase() == "span")
5289                         tElm = tinyMCE._createIFrame(tElm);
5290                 else
5291                         tElm = targetDoc.frames[this.editorId];
5292
5293                 this.targetElement = tElm;
5294                 this.iframeElement = targetDoc.getElementById(this.editorId);
5295                 this.contentDocument = tElm.window.document;
5296                 this.contentWindow = tElm.window;
5297                 this.getDoc().designMode = "on";
5298         }
5299
5300         // Setup base HTML
5301         var doc = this.contentDocument;
5302         if (dynamicIFrame) {
5303         var html = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><html><head><base href="' + tinyMCE.settings['base_href'] + '" /><title>blank_page</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head><body class="mceContentBody"></body></html>';
5304
5305                 try {
5306                         this.getDoc().designMode = "on";
5307                         doc.open();
5308                         doc.write(html);
5309                         doc.close();
5310                 } catch (e) {
5311                         // Failed Mozilla 1.3
5312                         this.getDoc().location.href = tinyMCE.baseURL + "/blank.htm";
5313                 }
5314         }
5315
5316         // This timeout is needed in MSIE 5.5 for some odd reason
5317         // it seems that the document.frames isn't initialized yet?
5318         if (tinyMCE.isMSIE)
5319                 window.setTimeout("TinyMCE.prototype.addEventHandlers('" + this.editorId + "');", 1);
5320
5321         tinyMCE.setupContent(this.editorId, true);
5322
5323         return true;
5324 };
5325
5326 TinyMCEControl.prototype.getFocusElement = function() {
5327         if (tinyMCE.isMSIE) {
5328                 var doc = this.getDoc();
5329                 var rng = doc.selection.createRange();
5330
5331                 if (rng.collapse)
5332                         rng.collapse(true);
5333
5334                 var elm = rng.item ? rng.item(0) : rng.parentElement();
5335         } else {
5336                 var sel = this.getSel();
5337                 var rng = this.getRng();
5338                 var elm = rng.commonAncestorContainer;
5339                 //var elm = (sel && sel.anchorNode) ? sel.anchorNode : null;
5340
5341                 // Handle selection a image or other control like element such as anchors
5342                 if (!rng.collapsed) {
5343                         // Is selection small
5344                         if (rng.startContainer == rng.endContainer) {
5345                                 if (rng.startOffset - rng.endOffset < 2) {
5346                                         if (rng.startContainer.hasChildNodes())
5347                                                 elm = rng.startContainer.childNodes[rng.startOffset];
5348                                 }
5349                         }
5350                 }
5351
5352                 // Get the element parent of the node
5353                 elm = tinyMCE.getParentElement(elm);
5354
5355                 //if (tinyMCE.selectedElement != null && tinyMCE.selectedElement.nodeName.toLowerCase() == "img")
5356                 //      elm = tinyMCE.selectedElement;
5357         }
5358
5359         return elm;
5360 };
5361
5362 // Global instances
5363 var tinyMCE = new TinyMCE();
5364 var tinyMCELang = new Array();