diff -r 5841df0ab575 -r dffcbfbc4e59 includes/clientside/tinymce/tiny_mce_src.js --- a/includes/clientside/tinymce/tiny_mce_src.js Mon May 05 20:06:37 2008 -0400 +++ b/includes/clientside/tinymce/tiny_mce_src.js Mon May 05 20:08:44 2008 -0400 @@ -3,25 +3,26 @@ var tinymce = { majorVersion : '3', - minorVersion : '0.3', - releaseDate : '2008-03-03', + minorVersion : '0.8', + releaseDate : '2008-04-30', _init : function() { - var t = this, ua = navigator.userAgent, i, nl, n, base; + var t = this, d = document, w = window, na = navigator, ua = na.userAgent, i, nl, n, base, p; // Browser checks - t.isOpera = window.opera && opera.buildNumber; + t.isOpera = w.opera && opera.buildNumber; t.isWebKit = /WebKit/.test(ua); - t.isOldWebKit = t.isWebKit && !window.getSelection().getRangeAt; - t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(navigator.appName); + t.isOldWebKit = t.isWebKit && !w.getSelection().getRangeAt; + t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName); t.isIE6 = t.isIE && /MSIE [56]/.test(ua); t.isGecko = !t.isWebKit && /Gecko/.test(ua); t.isMac = ua.indexOf('Mac') != -1; // TinyMCE .NET webcontrol might be setting the values for TinyMCE - if (window.tinyMCEPreInit) { + if (w.tinyMCEPreInit) { t.suffix = tinyMCEPreInit.suffix; t.baseURL = tinyMCEPreInit.base; + t.query = tinyMCEPreInit.query; return; } @@ -29,7 +30,7 @@ t.suffix = ''; // If base element found, add that infront of baseURL - nl = document.getElementsByTagName('base'); + nl = d.getElementsByTagName('base'); for (i=0; i|]+)>/gi, '<$1b$2>'); h = h.replace(/<(\/?)em>|]+)>/gi, '<$1i$2>'); - } + } else if (isIE) + h = h.replace(/'/g, '''); // IE can't handle apos // Fix some issues h = h.replace(/]+)\/>|/gi, ''); // Force open @@ -1927,6 +1997,10 @@ //if (isIE) // u = t.serializeStyle(t.parseStyle(u)); + // No mce_style for elements with these since they might get resized by the user + if (t._isRes(c)) + return m; + if (s.hex_colors) { u = u.replace(/rgb\([^\)]+\)/g, function(v) { return t.toHex(v); @@ -2080,6 +2154,14 @@ }); } + // Fix IE psuedo leak for elements since replacing elements if fairly common + // Will break parentNode for some unknown reason + /* if (isIE && o.nodeType === 1) { + o.parentNode.insertBefore(n, o); + o.outerHTML = ''; + return n; + }*/ + return o.parentNode.replaceChild(n, o); }); }, @@ -2168,7 +2250,7 @@ run : function(e, f, s) { var t = this, o; - if (typeof(e) === 'string') + if (t.doc && typeof(e) === 'string') e = t.doc.getElementById(e); if (!e) @@ -2191,6 +2273,48 @@ } return f.call(s, e); + }, + + getAttribs : function(n) { + var o; + + n = this.get(n); + + if (!n) + return []; + + if (isIE) { + o = []; + + // Object will throw exception in IE + if (n.nodeName == 'OBJECT') + return n.attributes; + + // It's crazy that this is faster in IE but it's because it returns all attributes all the time + n.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi, function(a, b) { + o.push({specified : 1, nodeName : b}); + }); + + return o; + } + + return n.attributes; + }, + + destroy : function(s) { + var t = this; + + t.win = t.doc = t.root = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, + + _isRes : function(c) { + // Is live resizble element + + return /^(top|left|bottom|right|width|height)/i.test(c) || /^[;\s](top|left|bottom|right|width|height)/i.test(c); } /* @@ -2332,6 +2456,24 @@ return s; }, + clear : function(o) { + var t = this, a = t.events, i, e; + + if (o) { + o = DOM.get(o); + + for (i = a.length - 1; i >= 0; i--) { + e = a[i]; + + if (e.obj === o) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + a.splice(i, 1); + } + } + } + }, + // #endif cancel : function(e) { @@ -2382,12 +2524,18 @@ }, _remove : function(o, n, f) { - if (o.detachEvent) - o.detachEvent('on' + n, f); - else if (o.removeEventListener) - o.removeEventListener(n, f, false); - else - o['on' + n] = null; + if (o) { + try { + if (o.detachEvent) + o.detachEvent('on' + n, f); + else if (o.removeEventListener) + o.removeEventListener(n, f, false); + else + o['on' + n] = null; + } catch (ex) { + // Might fail with permission denined on IE so we just ignore that + } + } }, _pageInit : function() { @@ -2583,6 +2731,10 @@ /* file:jscripts/tiny_mce/classes/dom/Selection.js */ (function() { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; + // Shorten names var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; @@ -2595,9 +2747,7 @@ t.serializer = serializer; // Prevent leaks - tinymce.addUnload(function() { - t.win = null; - }); + tinymce.addUnload(t.destroy, t); }, getContent : function(s) { @@ -2682,7 +2832,7 @@ r.collapse(1); e = r.parentElement(); - if (e.nodeName == 'BODY') + if (e && e.nodeName == 'BODY') return e.firstChild; return e; @@ -2707,7 +2857,7 @@ r.collapse(0); e = r.parentElement(); - if (e.nodeName == 'BODY') + if (e && e.nodeName == 'BODY') return e.lastChild; return e; @@ -2722,8 +2872,7 @@ }, getBookmark : function(si) { - var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(); - + var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv; sx = vp.x; sy = vp.y; @@ -2792,7 +2941,7 @@ // Text selection function getPos(r, sn, en) { - var w = document.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}; while ((n = w.nextNode()) != null) { if (n == sn) @@ -2803,7 +2952,7 @@ return d; } - p += n.nodeValue ? tinymce.trim(n.nodeValue).length : 0; + p += (n.nodeValue || '').length; } return null; @@ -2816,52 +2965,66 @@ if (!e) return {scrollX : sx, scrollY : sy}; + // Count whitespace before + trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + return { - start : e.start + s.anchorOffset, - end : e.end + s.focusOffset, + start : Math.max(e.start + s.anchorOffset - wb, 0), + end : Math.max(e.end + s.focusOffset - wb, 0), scrollX : sx, scrollY : sy, - beg : s.anchorOffset == 0 + beg : s.anchorOffset - wb == 0 }; } else { e = getPos(ro, r.startContainer, r.endContainer); + // Count whitespace before start and end container + //(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;}); + //(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;}); + if (!e) return {scrollX : sx, scrollY : sy}; return { - start : e.start + r.startOffset, - end : e.end + r.endOffset, + start : Math.max(e.start + r.startOffset - wb, 0), + end : Math.max(e.end + r.endOffset - wa, 0), scrollX : sx, scrollY : sy, - beg : r.startOffset == 0 + beg : r.startOffset - wb == 0 }; } }, moveToBookmark : function(b) { - var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd; + var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv; function getPos(r, sp, ep) { - var w = document.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v; + var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb; while ((n = w.nextNode()) != null) { - p += n.nodeValue ? tinymce.trim(n.nodeValue).length : 0; + wa = wb = 0; + + nv = n.nodeValue || ''; + //nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;}); + //nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;}); + + nvl = trimNl(nv).length; + p += nvl; if (p >= sp && !d.startNode) { - o = sp - (p - n.nodeValue.length); + o = sp - (p - nvl); // Fix for odd quirk in FF - if (b.beg && o >= n.nodeValue.length) + if (b.beg && o >= nvl) continue; d.startNode = n; - d.startOffset = o; + d.startOffset = o + wb; } if (p >= ep) { d.endNode = n; - d.endOffset = ep - (p - n.nodeValue.length); + d.endOffset = ep - (p - nvl) + wb; return d; } } @@ -3111,6 +3274,16 @@ } return r.item ? r.item(0) : r.parentElement(); + }, + + destroy : function(s) { + var t = this; + + t.win = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); } }); @@ -3188,7 +3361,7 @@ }, writeComment : function(v) { - this.node.appendChild(this.doc.createComment(v)); + this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' '))); }, getContent : function() { @@ -3341,7 +3514,7 @@ (function() { // Shorten names - var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE; + var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko; // Returns only attribites that have values not all attributes in IE function getIEAtts(n) { @@ -3351,7 +3524,7 @@ if (n.nodeName == 'OBJECT') return n.attributes; - n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) { + n.cloneNode(false).outerHTML.replace(/([a-z0-9\:\-_]+)=/gi, function(a, b) { o.push({specified : 1, nodeName : b}); }); @@ -3635,7 +3808,7 @@ // Parse attribute rule s = s.replace(/::/g, '~'); - s = /^([!\-])?([\w*.?~]+|)([=:<])?(.+)?$/.exec(s); + s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s); s[2] = s[2].replace(/~/g, ':'); // Add required attributes @@ -3763,7 +3936,7 @@ s += '|'; if (k != '@') - s += k; + s += k; }); t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$'); @@ -3838,55 +4011,40 @@ // Internal functions _postProcess : function(o) { - var t = this, s = t.settings, h = o.content, sc = [], p, l; + var t = this, s = t.settings, h = o.content, sc = [], p; if (o.format == 'html') { // Protect some elements p = t._protect({ content : h, patterns : [ - /(]*>)(.*?)(<\/script>)/g, - /(]*>)(.*?)(<\/style>)/g, - /(]*>)(.*?)(<\/pre>)/g + {pattern : /(]*>)(.*?)(<\/script>)/g}, + {pattern : /(]*>)(.*?)(<\/style>)/g}, + {pattern : /(]*>)(.*?)(<\/pre>)/g, encode : 1} ] }); h = p.content; // Entity encode - if (s.entity_encoding !== 'raw') { - if (s.entity_encoding.indexOf('named') != -1) { - t.setEntities(s.entities); - l = t.entityLookup; - - h = h.replace(t.entitiesRE, function(a) { - var v; - - if (v = l[a]) - a = '&' + v + ';'; - - return a; - }); - } - - if (s.entity_encoding.indexOf('numeric') != -1) { - h = h.replace(/[\u007E-\uFFFF]/g, function(a) { - return '&#' + a.charCodeAt(0) + ';'; - }); - } - } + if (s.entity_encoding !== 'raw') + h = t._encode(h); // Use BR instead of   padded P elements inside editor and use

 

outside editor - if (o.set) +/* if (o.set) h = h.replace(/

\s+( | |\u00a0|
)\s+<\/p>/g, '


'); else - h = h.replace(/

\s+( | |\u00a0|
)\s+<\/p>/g, '

$1

'); + h = h.replace(/

\s+( | |\u00a0|
)\s+<\/p>/g, '

$1

');*/ // Since Gecko and Safari keeps whitespace in the DOM we need to // remove it inorder to match other browsers. But I think Gecko and Safari is right. // This process is only done when getting contents out from the editor. if (!o.set) { + // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char + h = h.replace(/

\s+<\/p>|]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? ' 

' : ' 

'); + if (s.remove_linebreaks) { + h = h.replace(/\r?\n|\r/g, ' '); h = h.replace(/(<[^>]+>)\s+/g, '$1 '); h = h.replace(/\s+(<\/[^>]+>)/g, ' $1'); h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start @@ -3905,6 +4063,10 @@ } h = t._unprotect(h, p); + + // Restore the \u00a0 character if raw mode is enabled + if (s.entity_encoding == 'raw') + h = h.replace(/

 <\/p>|]+)> <\/p>/g, '\u00a0

'); } o.content = h; @@ -3951,6 +4113,10 @@ // IE sometimes adds a / infront of the node name if (nn.charAt(0) == '/') nn = nn.substring(1); + } else if (isGecko) { + // Ignore br elements + if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz') + return; } // Check if valid child @@ -4068,6 +4234,8 @@ }, _protect : function(o) { + var t = this; + o.items = o.items || []; function enc(s) { @@ -4093,8 +4261,13 @@ }; each(o.patterns, function(p) { - o.content = dec(enc(o.content).replace(p, function(x, a, b, c) { - o.items.push(dec(b)); + o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) { + b = dec(b); + + if (p.encode) + b = t._encode(b); + + o.items.push(b); return a + '' + c; })); }); @@ -4112,6 +4285,35 @@ return h; }, + _encode : function(h) { + var t = this, s = t.settings, l; + + // Entity encode + if (s.entity_encoding !== 'raw') { + if (s.entity_encoding.indexOf('named') != -1) { + t.setEntities(s.entities); + l = t.entityLookup; + + h = h.replace(t.entitiesRE, function(a) { + var v; + + if (v = l[a]) + a = '&' + v + ';'; + + return a; + }); + } + + if (s.entity_encoding.indexOf('numeric') != -1) { + h = h.replace(/[\u007E-\uFFFF]/g, function(a) { + return '&#' + a.charCodeAt(0) + ';'; + }); + } + } + + return h; + }, + _setup : function() { var t = this, s = this.settings; @@ -4237,10 +4439,18 @@ load : function(u, cb, s) { var t = this, o; + if (o = t.lookup[u]) { + // Is loaded fire callback + if (cb && o.state == 2) + cb.call(s || t); + + return o; + } + function loadScript(u) { if (tinymce.dom.Event.domLoaded || t.settings.strict_mode) { tinymce.util.XHR.send({ - url : u, + url : tinymce._addVer(u), error : t.settings.error, async : false, success : function(co) { @@ -4248,7 +4458,7 @@ } }); } else - document.write(''); + document.write(''); }; if (!tinymce.is(u, 'string')) { @@ -4385,7 +4595,7 @@ ol += 'tinymce.dom.ScriptLoader._onLoad(this,\'' + u + '\',' + ix + ');"'; } - document.write(''); + document.write(''); if (!o.func) done(o); @@ -4512,8 +4722,13 @@ } }, + remove : function() { + DOM.remove(this.id); + this.destroy(); + }, + destroy : function() { - DOM.remove(this.id); + tinymce.dom.Event.clear(this.id); } }); @@ -4544,8 +4759,13 @@ /* file:jscripts/tiny_mce/classes/ui/Separator.js */ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { + Separator : function(id, s) { + this.parent(id, s); + this.classPrefix = 'mceSeparator'; + }, + renderHTML : function() { - return tinymce.DOM.createHTML('span', {'class' : 'mceSeparator'}); + return tinymce.DOM.createHTML('span', {'class' : this.classPrefix}); } }); @@ -4665,6 +4885,8 @@ walk(t, function(o) { if (o.removeAll) o.removeAll(); + else + o.remove(); o.destroy(); }, 'items', t); @@ -4690,7 +4912,7 @@ tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', { DropMenu : function(id, s) { s = s || {}; - s.container = s.container || document.body; + s.container = s.container || DOM.doc.body; s.offset_x = s.offset_x || 0; s.offset_y = s.offset_y || 0; s.vp_offset_x = s.vp_offset_x || 0; @@ -4704,8 +4926,8 @@ this.onHideMenu = new tinymce.util.Dispatcher(this); this.classPrefix = 'mceMenu'; - // Fix for odd IE bug: #1903622 - this.fixIE = tinymce.isIE && window.top != window; + // Fix for odd IE bug: #1903622 (Frames selection) + this.fixIE = tinymce.isIE && (DOM.win.top != DOM.win); }, createMenu : function(s) { @@ -4747,7 +4969,7 @@ }, showMenu : function(x, y, px) { - var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb; + var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix; t.collapse(1); @@ -4800,7 +5022,7 @@ e = e.target; - if (e && (e = DOM.getParent(e, 'TR')) && !DOM.hasClass(e, 'mceMenuItemSub')) { + if (e && (e = DOM.getParent(e, 'TR')) && !DOM.hasClass(e, cp + 'ItemSub')) { m = t.items[e.id]; if (m.isDisabled()) @@ -4808,15 +5030,22 @@ dm = t; - while (dm) { - if (dm.hideMenu) - dm.hideMenu(); - - dm = dm.settings.parent; - } - - if (m.settings.onclick) - m.settings.onclick(e); + // Wait a while to fix IE bug where it looses the selection if the user clicks on a menu + // item when the editor is placed within an frame or iframe + DOM.win.setTimeout(function() { + while (dm) { + if (dm.hideMenu) + dm.hideMenu(); + + dm = dm.settings.parent; + } + }, 0); + + // Yield on IE to prevent loosing image focus when context menu is used + window.setTimeout(function() { + if (m.settings.onclick) + m.settings.onclick(e); + }, 0); return Event.cancel(e); // Cancel to fix onbeforeunload problem } @@ -4836,18 +5065,23 @@ if (m.isDisabled()) return; - if (e && DOM.hasClass(e, 'mceMenuItemSub')) { + if (e && DOM.hasClass(e, cp + 'ItemSub')) { //p = DOM.getPos(s.container); r = DOM.getRect(e); m.showMenu((r.x + r.w - ot), r.y - ot, r.x); t.lastMenu = m; - DOM.addClass(DOM.get(m.id).firstChild, 'mceMenuItemActive'); + DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive'); } } }); } t.onShowMenu.dispatch(t); + + if (s.keyboard_focus) { + Event.add(co, 'keydown', t._keyHandler, t); + DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link + } }, hideMenu : function(c) { @@ -4858,6 +5092,7 @@ Event.remove(co, 'mouseover', t.mouseOverFunc); Event.remove(co, t.fixIE ? 'mousedown' : 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); DOM.hide(co); t.isMenuVisible = 0; @@ -4868,7 +5103,7 @@ t.element.hide(); if (e = DOM.get(t.id)) - DOM.removeClass(e.firstChild, 'mceMenuItemActive'); + DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive'); t.onHideMenu.dispatch(t); }, @@ -4891,6 +5126,7 @@ remove : function(o) { DOM.remove(o.id); + this.destroy(); return this.parent(o); }, @@ -4910,12 +5146,12 @@ renderNode : function() { var t = this, s = t.settings, n, tb, co, w; - w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:150'}); - co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenu' + (s['class'] ? ' ' + s['class'] : '')}); + w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'}); + co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container}); if (s.menu_line) - DOM.add(co, 'span', {'class' : 'mceMenuLine'}); + DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'}); // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'}); n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); @@ -4932,12 +5168,18 @@ // Internal functions + _keyHandler : function(e) { + // Accessibility feature + if (e.keyCode == 27) + this.hideMenu(); + }, + _add : function(tb, o) { - var n, s = o.settings, a, ro, it; + var n, s = o.settings, a, ro, it, cp = this.classPrefix; if (s.separator) { - ro = DOM.add(tb, 'tr', {id : o.id, 'class' : 'mceMenuItemSeparator'}); - DOM.add(ro, 'td', {'class' : 'mceMenuItemSeparator'}); + ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'}); + DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'}); if (n = ro.previousSibling) DOM.addClass(n, 'mceLast'); @@ -4945,7 +5187,7 @@ return; } - n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : 'mceMenuItem mceMenuItemEnabled'}); + n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'}); n = it = DOM.add(n, 'td'); n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); @@ -4960,11 +5202,11 @@ if (tb.childNodes.length == 1) DOM.addClass(ro, 'mceFirst'); - if ((n = ro.previousSibling) && DOM.hasClass(n, 'mceMenuItemSeparator')) + if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator')) DOM.addClass(ro, 'mceFirst'); if (o.collapse) - DOM.addClass(ro, 'mceMenuItemSub'); + DOM.addClass(ro, cp + 'ItemSub'); if (n = ro.previousSibling) DOM.removeClass(n, 'mceLast'); @@ -4986,12 +5228,15 @@ }, renderHTML : function() { - var s = this.settings, h = ''; + var cp = this.classPrefix, s = this.settings, h, l; + + l = DOM.encode(s.label || ''); + h = ''; if (s.image) - h += ''; + h += '' + l + ''; else - h += ''; + h += '' + (l ? '' + l + '' : '') + ''; return h; }, @@ -5074,11 +5319,11 @@ }, renderHTML : function() { - var h = '', t = this, s = t.settings; - - h = ''; + var h = '', t = this, s = t.settings, cp = t.classPrefix; + + h = '
'; h += ''; - h += ''; + h += ''; h += '
' + DOM.createHTML('a', {id : t.id + '_text', href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '' + DOM.createHTML('a', {id : t.id + '_open', href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '') + '' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '') + '
'; return h; @@ -5090,6 +5335,9 @@ if (t.isDisabled() || t.items.length == 0) return; + if (t.menu && t.menu.isMenuVisible) + return t.hideMenu(); + if (!t.isMenuRendered) { t.renderMenu(); t.isMenuRendered = true; @@ -5101,6 +5349,7 @@ m = t.menu; m.settings.offset_x = p2.x; m.settings.offset_y = p2.y; + m.settings.keyboard_focus = t._focused; // Select in menu if (t.oldID) @@ -5115,16 +5364,20 @@ m.showMenu(0, e.clientHeight); - Event.add(document, 'mousedown', t.hideMenu, t); - DOM.addClass(t.id, 'mceListBoxSelected'); + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + DOM.addClass(t.id, t.classPrefix + 'Selected'); }, hideMenu : function(e) { var t = this; + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open')) + return; + if (!e || !DOM.getParent(e.target, function(n) {return DOM.hasClass(n, 'mceMenu');})) { - DOM.removeClass(t.id, 'mceListBoxSelected'); - Event.remove(document, 'mousedown', t.hideMenu, t); + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); if (t.menu) t.menu.hideMenu(); @@ -5136,7 +5389,7 @@ m = t.settings.control_manager.createDropMenu(t.id + '_menu', { menu_line : 1, - 'class' : 'mceListBoxMenu mceNoIcons', + 'class' : t.classPrefix + 'Menu mceNoIcons', max_width : 150, max_height : 150 }); @@ -5163,24 +5416,32 @@ }, postRender : function() { - var t = this; + var t = this, cp = t.classPrefix; Event.add(t.id, 'click', t.showMenu, t); + Event.add(t.id + '_text', 'focus', function() {t._focused = 1;}); + Event.add(t.id + '_text', 'blur', function() {t._focused = 0;}); // Old IE doesn't have hover on all elements if (tinymce.isIE6 || !DOM.boxModel) { Event.add(t.id, 'mouseover', function() { - if (!DOM.hasClass(t.id, 'mceListBoxDisabled')) - DOM.addClass(t.id, 'mceListBoxHover'); + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.addClass(t.id, cp + 'Hover'); }); Event.add(t.id, 'mouseout', function() { - if (!DOM.hasClass(t.id, 'mceListBoxDisabled')) - DOM.removeClass(t.id, 'mceListBoxHover'); + if (!DOM.hasClass(t.id, cp + 'Disabled')) + DOM.removeClass(t.id, cp + 'Hover'); }); } t.onPostRender.dispatch(t, DOM.get(t.id)); + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_text'); } }); @@ -5302,7 +5563,7 @@ MenuButton : function(id, s) { this.parent(id, s); this.onRenderMenu = new tinymce.util.Dispatcher(this); - s.menu_container = s.menu_container || document.body; + s.menu_container = s.menu_container || DOM.doc.body; }, showMenu : function() { @@ -5316,6 +5577,9 @@ t.isMenuRendered = true; } + if (t.isMenuVisible) + return t.hideMenu(); + p1 = DOM.getPos(t.settings.menu_container); p2 = DOM.getPos(e); @@ -5324,10 +5588,13 @@ m.settings.offset_y = p2.y; m.settings.vp_offset_x = p2.x; m.settings.vp_offset_y = p2.y; + m.settings.keyboard_focus = t._focused; m.showMenu(0, e.clientHeight); - Event.add(document, 'mousedown', t.hideMenu, t); + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); t.setState('Selected', 1); + + t.isMenuVisible = 1; }, renderMenu : function() { @@ -5348,12 +5615,18 @@ hideMenu : function(e) { var t = this; + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';})) + return; + if (!e || !DOM.getParent(e.target, function(n) {return DOM.hasClass(n, 'mceMenu');})) { t.setState('Selected', 0); - Event.remove(document, 'mousedown', t.hideMenu, t); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); if (t.menu) t.menu.hideMenu(); } + + t.isMenuVisible = 0; }, postRender : function() { @@ -5391,7 +5664,7 @@ if (s.image) h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']}); else - h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}); + h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, ''); h += '' + DOM.createHTML('a', {id : t.id + '_action', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; @@ -5414,6 +5687,8 @@ } Event.add(t.id + '_open', 'click', t.showMenu, t); + Event.add(t.id + '_open', 'focus', function() {t._focused = 1;}); + Event.add(t.id + '_open', 'blur', function() {t._focused = 0;}); // Old IE doesn't have hover on all elements if (tinymce.isIE6 || !DOM.boxModel) { @@ -5427,6 +5702,13 @@ DOM.removeClass(t.id, 'mceSplitButtonHover'); }); } + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_action'); + Event.clear(this.id + '_open'); } }); @@ -5453,7 +5735,7 @@ }, showMenu : function() { - var t = this, r, p, e; + var t = this, r, p, e, p2; if (t.isDisabled()) return; @@ -5463,6 +5745,9 @@ t.isMenuRendered = true; } + if (t.isMenuVisible) + return t.hideMenu(); + e = DOM.get(t.id); DOM.show(t.id + '_menu'); DOM.addClass(e, 'mceSplitButtonSelected'); @@ -5470,21 +5755,39 @@ DOM.setStyles(t.id + '_menu', { left : p2.x, top : p2.y + e.clientHeight, - zIndex : 150 + zIndex : 200000 }); e = 0; - Event.add(document, 'mousedown', t.hideMenu, t); + Event.add(DOM.doc, 'mousedown', t.hideMenu, t); + + if (t._focused) { + t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) { + if (e.keyCode == 27) + t.hideMenu(); + }); + + DOM.select('a', t.id + '_menu')[0].focus(); // Select first link + } + + t.isMenuVisible = 1; }, hideMenu : function(e) { var t = this; + // Prevent double toogles by canceling the mouse click event to the button + if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';})) + return; + if (!e || !DOM.getParent(e.target, function(n) {return DOM.hasClass(n, 'mceSplitButtonMenu');})) { DOM.removeClass(t.id, 'mceSplitButtonSelected'); - Event.remove(document, 'mousedown', t.hideMenu, t); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + Event.remove(t.id + '_menu', 'keydown', t._keyHandler); DOM.hide(t.id + '_menu'); } + + t.isMenuVisible = 0; }, renderMenu : function() { @@ -5513,18 +5816,15 @@ href : 'javascript:;', style : { backgroundColor : '#' + c - } - }); - - Event.add(n, 'mousedown', function() { - t.setColor('#' + c); + }, + mce_color : '#' + c }); }); if (s.more_colors_func) { n = DOM.add(tb, 'tr'); n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'}); - n = DOM.add(n, 'a', {href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); + n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); Event.add(n, 'click', function(e) { s.more_colors_func.call(s.more_colors_scope || this); @@ -5534,22 +5834,43 @@ DOM.addClass(m, 'mceColorSplitMenu'); + Event.add(t.id + '_menu', 'click', function(e) { + var c; + + e = e.target; + + if (e.nodeName == 'A' && (c = e.getAttribute('mce_color'))) + t.setColor(c); + + return Event.cancel(e); // Prevent IE auto save warning + }); + return w; }, setColor : function(c) { - var t = this, p, s = this.settings, co = s.menu_container, po, cp, id = t.id + '_preview'; - - if (!(p = DOM.get(id))) { - DOM.setStyle(t.id + '_action', 'position', 'relative'); - p = DOM.add(t.id + '_action', 'div', {id : id, 'class' : 'mceColorPreview'}); - } - - p.style.backgroundColor = c; + var t = this; + + DOM.setStyle(t.id + '_preview', 'backgroundColor', c); t.value = c; t.hideMenu(); - s.onselect(c); + t.settings.onselect(c); + }, + + postRender : function() { + var t = this, id = t.id; + + t.parent(); + DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'}); + }, + + destroy : function() { + this.parent(); + + Event.clear(this.id + '_menu'); + Event.clear(this.id + '_more'); + DOM.remove(this.id + '_menu'); } }); @@ -5559,21 +5880,59 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { renderHTML : function() { - var t = this, h = '', c = 'mceToolbarEnd', co, dom = tinymce.DOM, s = t.settings; - - h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '')); - - tinymce.each(t.controls, function(c) { - h += '' + c.renderHTML() + ''; - }); - - co = t.controls[t.controls.length - 1].constructor; - - if (co === tinymce.ui.Button) + var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl; + + cl = t.controls; + for (i=0; i')); + } + + // Add toolbar end before list box and after the previous button + // This is to fix the o2k7 editor skins + if (pr && co.ListBox) { + if (pr.Button || pr.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '')); + } + + // Render control HTML + + // IE 8 quick fix, needed to propertly generate a hit area for anchors + if (dom.stdMode) + h += '' + co.renderHTML() + ''; + else + h += '' + co.renderHTML() + ''; + + // Add toolbar start after list box and before the next button + // This is to fix the o2k7 editor skins + if (nx && co.ListBox) { + if (nx.Button || nx.SplitButton) + h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '')); + } + } + + c = 'mceToolbarEnd'; + + if (co.Button) c += ' mceToolbarEndButton'; - else if (co === tinymce.ui.SplitButton) + else if (co.SplitButton) c += ' mceToolbarEndSplitButton'; - else if (co === tinymce.ui.ListBox) + else if (co.ListBox) c += ' mceToolbarEndListBox'; h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '')); @@ -5623,10 +5982,15 @@ }, load : function(n, u, cb, s) { + var t = this; + + if (t.urls[n]) + return; + if (u.indexOf('/') != 0 && u.indexOf('://') == -1) u = tinymce.baseURL + '/' + u; - this.urls[n] = u.substring(0, u.lastIndexOf('/')); + t.urls[n] = u.substring(0, u.lastIndexOf('/')); tinymce.ScriptLoader.add(u, cb, s); } @@ -5640,15 +6004,41 @@ (function() { // Shorten names - var each = tinymce.each, extend = tinymce.extend, DOM = tinymce.DOM, Event = tinymce.dom.Event, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager; + var each = tinymce.each, extend = tinymce.extend, DOM = tinymce.DOM, Event = tinymce.dom.Event, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, explode = tinymce.explode; tinymce.create('static tinymce.EditorManager', { editors : {}, i18n : {}, activeEditor : null, + preInit : function() { + var t = this, lo = window.location; + + // Setup some URLs where the editor API is located and where the document is + tinymce.documentBaseURL = lo.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); + if (!/[\/\\]$/.test(tinymce.documentBaseURL)) + tinymce.documentBaseURL += '/'; + + tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); + tinymce.EditorManager.baseURI = new tinymce.util.URI(tinymce.baseURL); + + // Setup document domain + if (tinymce.EditorManager.baseURI.host != lo.hostname && lo.hostname) + document.domain = tinymce.relaxedDomain = lo.hostname.replace(/.*\.(.+\..+)$/, '$1'); + + // Add before unload listener + // This was required since IE was leaking memory if you added and removed beforeunload listeners + // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event + t.onBeforeUnload = new tinymce.util.Dispatcher(t); + + // Must be on window or IE will leak if the editor is placed in frame or iframe + Event.add(window, 'beforeunload', function(e) { + t.onBeforeUnload.dispatch(t, e); + }); + }, + init : function(s) { - var t = this, pl, sl = tinymce.ScriptLoader, c; + var t = this, pl, sl = tinymce.ScriptLoader, c, e; function execCallback(se, n, s) { var f = se[n]; @@ -5685,7 +6075,7 @@ // Load plugins if (s.plugins) { - pl = s.plugins.split(','); + pl = explode(s.plugins); // Load compat2x first if (tinymce.inArray(pl, 'compat2x') != -1) @@ -5716,7 +6106,7 @@ if (s.browsers) { l = false; - each(s.browsers.split(','), function(v) { + each(explode(s.browsers), function(v) { switch (v) { case 'ie': case 'msie': @@ -5753,7 +6143,7 @@ l = s.elements || ''; if(l.length > 0) { - each(l.split(','), function(v) { + each(explode(l), function(v) { if (DOM.get(v)) new tinymce.Editor(v, s).render(1); else { @@ -5776,15 +6166,25 @@ case "textareas": case "specific_textareas": function hasClass(n, c) { - return new RegExp('\\b' + c + '\\b', 'g').test(n.className); + return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c); }; each(DOM.select('textarea'), function(v) { if (s.editor_deselector && hasClass(v, s.editor_deselector)) return; - if (!s.editor_selector || hasClass(v, s.editor_selector)) - new tinymce.Editor(v.id = (v.id || v.name || (v.id = DOM.uniqueId())), s).render(1); + if (!s.editor_selector || hasClass(v, s.editor_selector)) { + // Can we use the name + e = DOM.get(v.name); + if (!v.id && !e) + v.id = v.name; + + // Generate unique name if missing or already exists + if (!v.id || t.get(v.id)) + v.id = DOM.uniqueId(); + + new tinymce.Editor(v.id, s).render(1); + } }); break; } @@ -5848,13 +6248,13 @@ }); } - e._destroy(); + e.destroy(); return e; }, execCommand : function(c, u, v) { - var t = this, ed = t.get(v); + var t = this, ed = t.get(v), w; // Manager commands switch (c) { @@ -5864,11 +6264,37 @@ case "mceAddEditor": case "mceAddControl": - new tinymce.Editor(v, t.settings).render(); + if (!t.get(v)) + new tinymce.Editor(v, t.settings).render(); + return true; case "mceAddFrameControl": - // TODO: Implement this + w = v.window; + + // Add tinyMCE global instance and tinymce namespace to specified window + w.tinyMCE = tinyMCE; + w.tinymce = tinymce; + + tinymce.DOM.doc = w.document; + tinymce.DOM.win = w; + + ed = new tinymce.Editor(v.element_id, v); + ed.render(); + + // Fix IE memory leaks + if (tinymce.isIE) { + function clr() { + ed.destroy(); + w.detachEvent('onunload', clr); + w = w.tinyMCE = w.tinymce = null; // IE leak + }; + + w.attachEvent('onunload', clr); + } + + v.page_window = null; + return true; case "mceRemoveEditor": @@ -5941,16 +6367,7 @@ }); - // Setup some URLs where the editor API is located and where the document is - tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, ''); - if (!/[\/\\]$/.test(tinymce.documentBaseURL)) - tinymce.documentBaseURL += '/'; - - tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL); - tinymce.EditorManager.baseURI = new tinymce.util.URI(tinymce.baseURL); - - if (tinymce.EditorManager.baseURI.host != window.location.hostname) - document.domain = tinymce.relaxedDomain = window.location.hostname.replace(/.*\.(.+\..+)$/, '$1'); + tinymce.EditorManager.preInit(); })(); // Short for editor manager window.tinyMCE is needed when TinyMCE gets loaded though a XHR call @@ -5962,7 +6379,7 @@ var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, Dispatcher = tinymce.util.Dispatcher; var each = tinymce.each, isGecko = tinymce.isGecko, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit; var is = tinymce.is, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, EditorManager = tinymce.EditorManager; - var inArray = tinymce.inArray, grep = tinymce.grep; + var inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode; tinymce.create('tinymce.Editor', { Editor : function(id, s) { @@ -6050,7 +6467,7 @@ apply_source_formatting : 1, directionality : 'ltr', forced_root_block : 'p', - valid_elements : '@[id|class|style|title|dir'; + t.iframeHTML = s.doctype + ''; t.iframeHTML += ''; if (tinymce.relaxedDomain) @@ -6372,7 +6793,7 @@ }, setupIframe : function() { - var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h; + var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b; // Setup iframe body if (!isIE || !tinymce.relaxedDomain) { @@ -6392,8 +6813,13 @@ } // IE needs to use contentEditable or it will display non secure items for HTTPS - if (isIE) - t.getBody().contentEditable = true; + if (isIE) { + // It will not steal focus if we hide it while setting contentEditable + b = t.getBody(); + DOM.hide(b); + b.contentEditable = true; + DOM.show(b); + } // Setup objects t.dom = new tinymce.DOM.DOMUtils(t.getDoc(), { @@ -6460,7 +6886,7 @@ if (s.custom_elements) { function handleCustom(ed, o) { - each(s.custom_elements.split(','), function(v) { + each(explode(s.custom_elements), function(v) { var n; if (v.indexOf('~') === 0) { @@ -6605,7 +7031,7 @@ // Remove empty contents if (s.padd_empty_editor) { t.onPostProcess.add(function(ed, o) { - o.content = o.content.replace(/^

( |#160;|\s)<\/p>$/, ''); + o.content = o.content.replace(/^(

( | |\s|\u00a0|)<\/p>[\r\n]*|
[\r\n]*)$/, ''); }); } @@ -6639,7 +7065,7 @@ // Load specified content CSS last if (s.content_css) { - tinymce.each(s.content_css.split(','), function(u) { + tinymce.each(explode(s.content_css), function(u) { t.dom.loadCSS(t.documentBaseURI.toAbsolute(u)); }); } @@ -6723,7 +7149,7 @@ o = {}; if (is(v, 'string')) { - each(v.split(/[;,]/), function(v) { + each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) { v = v.split('='); if (v.length > 1) @@ -6798,7 +7224,7 @@ }; } - each(pa.split(','), function(pa) { + each(explode(pa), function(pa) { var o = { func : cmd_func, scope : sc || this, @@ -6808,7 +7234,7 @@ shift : false }; - each(pa.split('+'), function(v) { + each(explode(pa, '+'), function(v) { switch (v) { case 'alt': case 'ctrl': @@ -6829,7 +7255,7 @@ }, execCommand : function(cmd, ui, val, a) { - var t = this, s = 0, o; + var t = this, s = 0, o, st; if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus)) t.focus(); @@ -6847,9 +7273,13 @@ // Registred commands if (o = t.execCommands[cmd]) { - s = o.func.call(o.scope, ui, val); - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return s; + st = o.func.call(o.scope, ui, val); + + // Fall through on true + if (st !== true) { + t.onExecCommand.dispatch(t, cmd, ui, val, a); + return st; + } } // Plugin commands @@ -6882,15 +7312,20 @@ }, queryCommandState : function(c) { - var t = this, o; + var t = this, o, s; // Is hidden then return undefined if (t._isHidden()) return; // Registred commands - if (o = t.queryStateCommands[c]) - return o.func.call(o.scope); + if (o = t.queryStateCommands[c]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } // Registred commands o = t.editorCommands.queryCommandState(c); @@ -6906,15 +7341,20 @@ }, queryCommandValue : function(c) { - var t = this, o; + var t = this, o, s; // Is hidden then return undefined if (t._isHidden()) return; // Registred commands - if (o = t.queryValueCommands[c]) - return o.func.call(o.scope); + if (o = t.queryValueCommands[c]) { + s = o.func.call(o.scope); + + // Fall though on true + if (s !== true) + return s; + } // Registred commands o = t.editorCommands.queryCommandValue(c); @@ -6960,22 +7400,6 @@ return b; }, - remove : function() { - var t = this; - - t.removed = 1; // Cancels post remove event execution - t.hide(); - DOM.remove(t.getContainer()); - - t.execCallback('remove_instance_callback', t); - t.onRemove.dispatch(t); - - // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command - t.onExecCommand.listeners = []; - - EditorManager.remove(t); - }, - resizeToContent : function() { var t = this; @@ -7205,6 +7629,68 @@ t.onVisualAid.dispatch(t, e, t.hasVisual); }, + remove : function() { + var t = this, e = t.getContainer(); + + t.removed = 1; // Cancels post remove event execution + t.hide(); + + t.execCallback('remove_instance_callback', t); + t.onRemove.dispatch(t); + + // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command + t.onExecCommand.listeners = []; + + EditorManager.remove(t); + DOM.remove(e); + }, + + destroy : function(s) { + var t = this; + + // One time is enough + if (t.destroyed) + return; + + if (!s) { + tinymce.removeUnload(t.destroy); + tinyMCE.onBeforeUnload.remove(t._beforeUnload); + + // Manual destroy + if (t.theme.destroy) + t.theme.destroy(); + + // Destroy controls, selection and dom + t.controlManager.destroy(); + t.selection.destroy(); + t.dom.destroy(); + + // Remove all events + + // Don't clear the window or document if content editable + // is enabled since other instances might still be present + if (!t.settings.content_editable) { + Event.clear(t.getWin()); + Event.clear(t.getDoc()); + } + + Event.clear(t.getBody()); + Event.clear(t.formElement); + } + + if (t.formElement) { + t.formElement.submit = t.formElement._mceOldSubmit; + t.formElement._mceOldSubmit = null; + } + + t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null; + + if (t.selection) + t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; + + t.destroyed = 1; + }, + // Internal functions _addEvents : function() { @@ -7265,8 +7751,8 @@ // Get HTML data /*if (tinymce.isIE) { - el = DOM.add(document.body, 'div', {style : 'visibility:hidden;overflow:hidden;position:absolute;width:1px;height:1px'}); - r = document.body.createTextRange(); + el = DOM.add(DOM.doc.body, 'div', {style : 'visibility:hidden;overflow:hidden;position:absolute;width:1px;height:1px'}); + r = DOM.doc.body.createTextRange(); r.moveToElementText(el); r.execCommand('Paste'); h = el.innerHTML; @@ -7291,6 +7777,7 @@ t.focus(true); }); + // Fixes bug where a specified document_base_uri could result in broken images // This will also fix drag drop of images in Gecko if (tinymce.isGecko) { @@ -7322,7 +7809,8 @@ if (isGecko) { if (t._isHidden()) { try { - d.designMode = 'On'; + if (!s.content_editable) + d.designMode = 'On'; } catch (ex) { // Fails if it's hidden } @@ -7400,7 +7888,7 @@ }; if (e.keyCode === 9) { - v = ed.getParam('tab_focus').split(','); + v = explode(ed.getParam('tab_focus')); if (v.length == 1) { v[1] = v[0]; @@ -7519,6 +8007,10 @@ e = e.target; + // Don't do this action for non image elements + if (e.nodeName !== 'IMG') + return; + if (re) Event.remove(re.node, re.ev, re.cb); @@ -7625,22 +8117,6 @@ } }, - _destroy : function() { - var t = this; - - if (t.formElement) { - t.formElement.submit = t.formElement._mceOldSubmit; - t.formElement._mceOldSubmit = null; - } - - t.contentAreaContainer = t.formElement = t.container = t.contentDocument = t.contentWindow = null; - - if (t.selection) - t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null; - - t.destroyed = 1; - }, - _convertInlineElements : function() { var t = this, s = t.settings, dom = t.dom, v, e, na, st, sp; @@ -7660,15 +8136,15 @@ case 'U': case 'STRIKE': - sp = dom.create('span', {style : dom.getAttrib(n, 'style')}); - sp.style.textDecoration = n.nodeName == 'U' ? 'underline' : 'line-through'; - dom.setAttrib(sp, 'mce_style', ''); - dom.replace(sp, n, 1); + //sp = dom.create('span', {style : dom.getAttrib(n, 'style')}); + n.style.textDecoration = n.nodeName == 'U' ? 'underline' : 'line-through'; + dom.setAttrib(n, 'mce_style', ''); + dom.setAttrib(n, 'mce_name', 'span'); break; } }); } else if (o.set) { - each(t.dom.select('table,span', o.node), function(n) { + each(t.dom.select('table,span', o.node).reverse(), function(n) { if (n.nodeName == 'TABLE') { if (v = dom.getStyle(n, 'height')) dom.setAttrib(n, 'height', v.replace(/[^0-9%]+/g, '')); @@ -7699,14 +8175,15 @@ t.onPreProcess.add(convert); if (!s.cleanup_on_startup) { - t.onInit.add(function() { - convert(t, {node : t.getBody(), set : 1}); + t.onSetContent.add(function(ed, o) { + if (o.initial) + convert(t, {node : t.getBody(), set : 1}); }); } }, _convertFonts : function() { - var t = this, s = t.settings, dom = t.dom, sl, cl, fz, fzn, v, i, st, x, nl, sp, f, n; + var t = this, s = t.settings, dom = t.dom, fz, fzn, sl, cl; // No need if (!s.inline_styles) @@ -7717,12 +8194,14 @@ fzn = ['xx-small', 'x-small','small','medium','large','x-large', 'xx-large']; if (sl = s.font_size_style_values) - sl = sl.split(','); + sl = explode(sl); if (cl = s.font_size_classes) - cl = cl.split(','); + cl = explode(cl); function convertToFonts(no) { + var n, f, nl, x, i, v, st; + // Convert spans to fonts on non WebKit browsers if (tinymce.isWebKit || !s.inline_styles) return; @@ -7734,15 +8213,23 @@ f = dom.create('font', { color : dom.toHex(dom.getStyle(n, 'color')), face : dom.getStyle(n, 'fontFamily'), - style : dom.getAttrib(n, 'style') + style : dom.getAttrib(n, 'style'), + 'class' : dom.getAttrib(n, 'class') }); + // Clear color and font family + st = f.style; + if (st.color || st.fontFamily) { + st.color = st.fontFamily = ''; + dom.setAttrib(f, 'mce_style', ''); // Remove cached style data + } + if (sl) { i = inArray(sl, dom.getStyle(n, 'fontSize')); if (i != -1) { dom.setAttrib(f, 'size', '' + (i + 1 || 1)); - f.style.fontSize = ''; + //f.style.fontSize = ''; } } else if (cl) { i = inArray(cl, dom.getAttrib(n, 'class')); @@ -7765,6 +8252,8 @@ dom.setAttrib(f, 'mce_style', ''); dom.replace(f, n, 1); } + + f = n = null; } }; @@ -7775,6 +8264,8 @@ // Run on cleanup t.onPreProcess.add(function(ed, o) { + var n, sp, nl, x; + // Keep unit tests happy if (!s.inline_styles) return; @@ -7785,7 +8276,8 @@ n = nl[x]; sp = dom.create('span', { - style : dom.getAttrib(n, 'style') + style : dom.getAttrib(n, 'style'), + 'class' : dom.getAttrib(n, 'class') }); dom.setStyles(sp, { @@ -8011,7 +8503,7 @@ }, mceInsertLink : function(u, v) { - var ed = this.editor, e = ed.dom.getParent(ed.selection.getNode(), 'A'); + var ed = this.editor, s = ed.selection, e = ed.dom.getParent(s.getNode(), 'A'); if (tinymce.is(v, 'string')) v = {href : v}; @@ -8058,6 +8550,24 @@ ed.getDoc().execCommand('FontName', false, v); }, + FontSize : function(u, v) { + var ed = this.editor, s = ed.settings, fz = tinymce.explode(s.font_size_style_values), fzc = tinymce.explode(s.font_size_classes); + + ed.getDoc().execCommand('FontSize', false, v); + + // Add style values + if (s.inline_styles) { + each(ed.dom.select('font'), function(e) { + if (e.size === v) { + if (fzc && fzc.length > 0) + ed.dom.setAttrib(e, 'class', fzc[parseInt(v) - 1]); + else + ed.dom.setStyle(e, 'fontSize', fz[parseInt(v) - 1]); + } + }); + } + }, + queryCommandValue : function(c) { var f = this['queryValue' + c]; @@ -8086,6 +8596,22 @@ return -1; }, + _queryState : function(c) { + try { + return this.editor.getDoc().queryCommandState(c); + } catch (ex) { + // Ignore exception + } + }, + + _queryVal : function(c) { + try { + return this.editor.getDoc().queryCommandValue(c); + } catch (ex) { + // Ignore exception + } + }, + queryValueFontSize : function() { var ed = this.editor, v = 0, p; @@ -8096,7 +8622,7 @@ return v; } - return ed.getDoc().queryCommandValue('FontSize'); + return this._queryVal('FontSize'); }, queryValueFontName : function() { @@ -8106,7 +8632,7 @@ v = p.face; if (!v) - v = ed.getDoc().queryCommandValue('FontName'); + v = this._queryVal('FontName'); return v; }, @@ -8367,8 +8893,6 @@ each(dom.select(nn).reverse(), function(n) { var p = n.parentNode; - dom.setAttrib(n, 'mce_new', ''); - // Check if it's an old span in a new wrapper if (!dom.getAttrib(n, 'mce_new')) { // Find new wrapper @@ -8385,7 +8909,7 @@ each(dom.select(nn).reverse(), function(n) { var p = n.parentNode; - if (!p) + if (!p || !dom.getAttrib(n, 'mce_new')) return; // Has parent of the same type and only child @@ -8401,8 +8925,12 @@ // Remove empty wrappers each(dom.select(nn).reverse(), function(n) { - if (!dom.getAttrib(n, 'class') && !dom.getAttrib(n, 'style')) - return dom.remove(n, 1); + if (dom.getAttrib(n, 'mce_new') || (dom.getAttribs(n).length <= 1 && n.className === '')) { + if (!dom.getAttrib(n, 'class') && !dom.getAttrib(n, 'style')) + return dom.remove(n, 1); + + dom.setAttrib(n, 'mce_new', ''); // Remove mce_new marker + } }); s.moveToBookmark(b); @@ -8428,7 +8956,7 @@ if (ed.settings.inline_styles) return (n && n.style.textAlign == v); - return ed.getDoc().queryCommandState(c); + return this._queryState(c); }, HiliteColor : function(ui, val) { @@ -8558,12 +9086,12 @@ }, queryStateUnderline : function() { - var ed = this.editor, n; + var ed = this.editor, n = ed.selection.getNode(); if (n && n.nodeName == 'A') return false; - return ed.getDoc().queryCommandState('Underline'); + return this._queryState('Underline'); }, queryStateOutdent : function() { @@ -8827,7 +9355,7 @@ // Add undo level if needed l.content = l.content.replace(/^\s*|\s*$/g, ''); - la = t.data[t.index > 0 ? t.index - 1 : 0]; + la = t.data[t.index > 0 && (t.index == 0 || t.index == t.data.length) ? t.index - 1 : t.index]; if (!l.initial && la && l.content == la.content) return null; @@ -8845,9 +9373,14 @@ if (s.custom_undo_redo_restore_selection && !l.initial) l.bookmark = b = l.bookmark || ed.selection.getBookmark(); - if (t.index < t.data.length && t.data[t.index].initial) + if (t.index < t.data.length) t.index++; + // Only initial marked undo levels should be allowed as first item + // This to workaround a bug with Firefox and the blur event + if (t.data.length === 0 && !l.initial) + return null; + // Add level t.data.length = t.index + 1; t.data[t.index++] = l; @@ -8953,11 +9486,12 @@ ed.onPreInit.add(t.setup, t); - t.reOpera = new RegExp('(\u00a0| | )<\/' + elm + '>', 'gi'); - t.rePadd = new RegExp(']+)><\/p>|]+)\/>|]+)>\s+<\/p>|

<\/p>||

\s+<\/p>'.replace(/p/g, elm), 'gi'); - t.reNbsp2BR = new RegExp(']+)>[\s\u00a0]+<\/p>|

[\s\u00a0]+<\/p>'.replace(/p/g, elm), 'gi'); - t.reBR2Nbsp = new RegExp(']+)>\s*
\s*<\/p>|

\s*
\s*<\/p>'.replace(/p/g, elm), 'gi'); - t.reTrailBr = new RegExp('\s*
\s*<\/p>'.replace(/p/g, elm), 'gi'); + t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi'); + t.rePadd = new RegExp(']+)><\\\/p>|]+)\\\/>|]+)>\\s+<\\\/p>|

<\\\/p>||

\\s+<\\\/p>'.replace(/p/g, elm), 'gi'); + t.reNbsp2BR1 = new RegExp(']+)>[\\s\\u00a0]+<\\\/p>|

[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi'); + t.reNbsp2BR2 = new RegExp(']+)>( | )<\\\/p>|

( | )<\\\/p>'.replace(/p/g, elm), 'gi'); + t.reBR2Nbsp = new RegExp(']+)>\\s*
\\s*<\\\/p>|

\\s*
\\s*<\\\/p>'.replace(/p/g, elm), 'gi'); + t.reTrailBr = new RegExp('\\s*
\\s*<\\\/p>'.replace(/p/g, elm), 'gi'); function padd(ed, o) { if (isOpera) @@ -8965,9 +9499,10 @@ o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0'); - if (!isIE && o.set) { + if (!isIE && !isOpera && o.set) { // Use   instead of BR in padded paragraphs - o.content = o.content.replace(t.reNbsp2BR, '<' + elm + '$1$2>
'); + o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>
'); + o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>
'); } else { o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0'); o.content = o.content.replace(t.reTrailBr, ''); @@ -9114,7 +9649,7 @@ nx = nl[i]; // Is text or non block element - if (nx.nodeType == 3 || !t.dom.isBlock(nx)) { + if (nx.nodeType == 3 || (!t.dom.isBlock(nx) && nx.nodeType != 8)) { if (!bl) { // Create new block but ignore whitespace if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) { @@ -9202,8 +9737,8 @@ }, insertPara : function(e) { - var t = this, ed = t.editor, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; - var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n; + var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; + var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch; function isEmpty(n) { n = n.innerHTML; @@ -9239,6 +9774,23 @@ en = dir ? s.focusNode : s.anchorNode; eo = dir ? s.focusOffset : s.anchorOffset; + // If selection is in empty table cell + if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) { + dom.remove(sn.firstChild); // Remove BR + + // Create two new block elements + ed.dom.add(sn, se.element, null, '
'); + aft = ed.dom.add(sn, se.element, null, '
'); + + // Move caret into the last one + r = d.createRange(); + r.selectNodeContents(aft); + r.collapse(1); + ed.selection.setRng(r); + + return false; + } + // If the caret is in an invalid location in FF we need to move it into the first block if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) { sn = en = sn.firstChild; @@ -9349,6 +9901,9 @@ // Delete and replace it with new block elements r.deleteContents(); + if (isOpera) + ed.getWin().scrollTo(0, vp.y); + // Never wrap blocks in blocks if (bef.firstChild && bef.firstChild.nodeName == bn) bef.innerHTML = bef.firstChild.innerHTML; @@ -9376,34 +9931,58 @@ aft.normalize(); bef.normalize(); + function first(n) { + return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false).nextNode() || n; + }; + // Move cursor and scroll into view r = d.createRange(); - r.selectNodeContents(aft); + r.selectNodeContents(isGecko ? first(aft) : aft); r.collapse(1); s.removeAllRanges(); s.addRange(r); - // Safari bug fix, http://bugs.webkit.org/show_bug.cgi?id=16117 - if (tinymce.isWebKit) - ed.getWin().scrollTo(0, ed.dom.getPos(aft).y); - else - aft.scrollIntoView(0); + // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs + y = ed.dom.getPos(aft).y; + ch = aft.clientHeight; + + // Is element within viewport + if (y < vp.y || y + ch > vp.y + vp.h) { + ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + ch); + //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight)); + } return false; }, backspaceDelete : function(e, bs) { - var t = this, ed = t.editor, b = ed.getBody(), n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n; + var t = this, ed = t.editor, b = ed.getBody(), n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn; // The caret sometimes gets stuck in Gecko if you delete empty paragraphs // This workaround removes the element by hand and moves the caret to the previous element - if (sc && ed.dom.isBlock(sc) && bs) { - if (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR') { - n = sc.previousSibling; + if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) { + if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) { + // Find previous block element + n = sc; + while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ; + if (n) { - ed.dom.remove(sc); - se.select(n.firstChild); - se.collapse(0); + if (sc != b.firstChild) { + // Find last text node + w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + while (tn = w.nextNode()) + n = tn; + + // Place caret at the end of last text node + r = ed.getDoc().createRange(); + r.setStart(n, n.nodeValue ? n.nodeValue.length : 0); + r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0); + se.setRng(r); + + // Remove the target container + ed.dom.remove(sc); + } + return Event.cancel(e); } } @@ -9414,9 +9993,12 @@ e = e.target; // A new BR was created in a block element, remove it - if (e && e.parentNode && e.nodeName == 'BR' && t.getParentBlock(e)) { - ed.dom.remove(e); + if (e && e.parentNode && e.nodeName == 'BR' && (n = t.getParentBlock(e))) { Event.remove(b, 'DOMNodeInserted', handler); + + // Only remove BR elements that got inserted in the middle of the text + if (e.previousSibling || e.nextSibling) + ed.dom.remove(e); } }; @@ -9447,6 +10029,7 @@ t.onAdd = new tinymce.util.Dispatcher(t); t.onPostRender = new tinymce.util.Dispatcher(t); t.prefix = s.prefix || ed.id + '_'; + t._cls = {}; t.onPostRender.add(function() { each(t.controls, function(c) { @@ -9512,11 +10095,12 @@ return t.add(c); }, - createDropMenu : function(id, s) { - var t = this, ed = t.editor, c, bm, v; + createDropMenu : function(id, s, cc) { + var t = this, ed = t.editor, c, bm, v, cls; s = extend({ - 'class' : 'mceDropDown' + 'class' : 'mceDropDown', + constrain : ed.settings.constrain_menus }, s); s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin'; @@ -9524,7 +10108,8 @@ s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1); id = t.prefix + id; - c = t.controls[id] = new tinymce.ui.DropMenu(id, s); + cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu; + c = t.controls[id] = new cls(id, s); c.onAddItem.add(function(c, o) { var s = o.settings; @@ -9561,8 +10146,8 @@ return t.add(c); }, - createListBox : function(id, s) { - var t = this, ed = t.editor, cmd, c; + createListBox : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; if (t.get(id)) return null; @@ -9587,8 +10172,10 @@ if (ed.settings.use_native_selects) c = new tinymce.ui.NativeListBox(id, s); - else - c = new tinymce.ui.ListBox(id, s); + else { + cls = cc || t._cls.listbox || tinymce.ui.ListBox; + c = new cls(id, s); + } t.controls[id] = c; @@ -9614,13 +10201,14 @@ return t.add(c); }, - createButton : function(id, s) { - var t = this, ed = t.editor, o, c; + createButton : function(id, s, cc) { + var t = this, ed = t.editor, o, c, cls; if (t.get(id)) return null; s.title = ed.translate(s.title); + s.label = ed.translate(s.label); s.scope = s.scope || ed; if (!s.onclick && !s.menu_button) { @@ -9640,10 +10228,13 @@ id = t.prefix + id; if (s.menu_button) { - c = new tinymce.ui.MenuButton(id, s); + cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; + c = new cls(id, s); ed.onMouseDown.add(c.hideMenu, c); - } else - c = new tinymce.ui.Button(id, s); + } else { + cls = t._cls.button || tinymce.ui.Button; + c = new cls(id, s); + } return t.add(c); }, @@ -9655,8 +10246,8 @@ return this.createButton(id, s); }, - createSplitButton : function(id, s) { - var t = this, ed = t.editor, cmd, c; + createSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; if (t.get(id)) return null; @@ -9684,14 +10275,15 @@ }, s); id = t.prefix + id; - c = t.add(new tinymce.ui.SplitButton(id, s)); + cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; + c = t.add(new cls(id, s)); ed.onMouseDown.add(c.hideMenu, c); return c; }, - createColorSplitButton : function(id, s) { - var t = this, ed = t.editor, cmd, c; + createColorSplitButton : function(id, s, cc) { + var t = this, ed = t.editor, cmd, c, cls; if (t.get(id)) return null; @@ -9720,17 +10312,24 @@ }, s); id = t.prefix + id; - c = new tinymce.ui.ColorSplitButton(id, s); + cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; + c = new cls(id, s); ed.onMouseDown.add(c.hideMenu, c); + // Remove the menu element when the editor is removed + ed.onRemove.add(function() { + c.destroy(); + }); + return t.add(c); }, - createToolbar : function(id, s) { - var c, t = this; + createToolbar : function(id, s, cc) { + var c, t = this, cls; id = t.prefix + id; - c = new tinymce.ui.Toolbar(id, s); + cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; + c = new cls(id, s); if (t.get(id)) return null; @@ -9738,8 +10337,22 @@ return t.add(c); }, - createSeparator : function() { - return new tinymce.ui.Separator(); + createSeparator : function(cc) { + var cls = cc || this._cls.separator || tinymce.ui.Separator; + + return new cls(); + }, + + setControlType : function(n, c) { + return this._cls[n.toLowerCase()] = c; + }, + + destroy : function() { + each(this.controls, function(c) { + c.destroy(); + }); + + this.controls = null; } }); @@ -9778,6 +10391,7 @@ p.inline = false; p.mce_width = s.width; p.mce_height = s.height; + p.mce_auto_focus = s.auto_focus; if (mo) { if (isIE) { @@ -9811,10 +10425,12 @@ if (tinymce.relaxedDomain) u += (u.indexOf('?') == -1 ? '?' : '&') + 'mce_rdomain=' + tinymce.relaxedDomain; + u = tinymce._addVer(u); + try { if (isIE && mo) { w = 1; - window.showModalDialog(s.url || s.file, window, f); + window.showModalDialog(u, window, f); } else w = window.open(u, s.name, f); } catch (ex) { @@ -9840,11 +10456,13 @@ cb.call(s || this, confirm(this._decode(this.editor.getLang(t, t)))); }, - alert : function(t, cb, s) { - alert(this._decode(t)); + alert : function(tx, cb, s) { + var t = this; + + alert(t._decode(t.editor.getLang(tx, tx))); if (cb) - cb.call(s || this); + cb.call(s || t); }, // Internal functions