X-Git-Url: https://code.citadel.org/?p=citadel.git;a=blobdiff_plain;f=webcit%2Ftiny_mce%2Ftiny_mce_src.js;h=8c665b09535c46d2bdac3012c7b881f0948b6e2c;hp=2b3453ba9b4b9f68a3bdd71bc9713030e072b14d;hb=b05f4eef4014db3885787ef15107cba93c932ac9;hpb=b347fec899815ec89b2738a0877880ee81e74b59 diff --git a/webcit/tiny_mce/tiny_mce_src.js b/webcit/tiny_mce/tiny_mce_src.js index 2b3453ba9..8c665b095 100644 --- a/webcit/tiny_mce/tiny_mce_src.js +++ b/webcit/tiny_mce/tiny_mce_src.js @@ -5,9 +5,9 @@ var tinymce = { majorVersion : '3', - minorVersion : '3.9.2', + minorVersion : '4.5', - releaseDate : '2010-09-29', + releaseDate : '2011-09-06', _init : function() { var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; @@ -20,6 +20,12 @@ t.isIE6 = t.isIE && /MSIE [56]/.test(ua); + t.isIE7 = t.isIE && /MSIE [7]/.test(ua); + + t.isIE8 = t.isIE && /MSIE [8]/.test(ua); + + t.isIE9 = t.isIE && /MSIE [9]/.test(ua); + t.isGecko = !t.isWebKit && /Gecko/.test(ua); t.isMac = ua.indexOf('Mac') != -1; @@ -27,6 +33,8 @@ t.isAir = /adobeair/i.test(ua); t.isIDevice = /(iPad|iPhone)/.test(ua); + + t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534; // TinyMCE .NET webcontrol might be setting the values for TinyMCE if (win.tinyMCEPreInit) { @@ -103,6 +111,24 @@ return typeof(o) == t; }, + makeMap : function(items, delim, map) { + var i; + + items = items || []; + delim = delim || ','; + + if (typeof(items) == "string") + items = items.split(delim); + + map = map || {}; + + i = items.length; + while (i--) + map[items[i]] = {}; + + return map; + }, + each : function(o, cb, s) { var n, l; @@ -185,7 +211,7 @@ return (s ? '' + s : '').replace(whiteSpaceRe, ''); }, - create : function(s, p) { + create : function(s, p, root) { var t = this, sp, ns, cn, scn, c, de = 0; // Parse : : @@ -193,7 +219,7 @@ cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name // Create namespace for new class - ns = t.createNS(s[3].replace(/\.\w+$/, '')); + ns = t.createNS(s[3].replace(/\.\w+$/, ''), root); // Class already exists if (ns[cn]) @@ -460,7 +486,11 @@ // Expose tinymce namespace to the global namespace (window) win.tinymce = win.tinyMCE = tinymce; -})(window); + + // Describe the different namespaces + + })(window); + tinymce.create('tinymce.util.Dispatcher', { @@ -521,7 +551,7 @@ tinymce.create('tinymce.util.Dispatcher', { tinymce.create('tinymce.util.URI', { URI : function(u, s) { - var t = this, o, a, b; + var t = this, o, a, b, base_url; // Trim whitespace u = tinymce.trim(u); @@ -529,8 +559,9 @@ tinymce.create('tinymce.util.Dispatcher', { // Default settings s = t.settings = s || {}; - // Strange app protocol or local anchor - if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) { + // Strange app protocol that isn't http/https or local anchor + // For example: mailto,skype,tel etc. + if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) { t.source = u; return; } @@ -540,8 +571,10 @@ tinymce.create('tinymce.util.Dispatcher', { u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u; // Relative path http:// or protocol relative //path - if (!/^\w*:?\/\//.test(u)) - u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u); + if (!/^[\w-]*:?\/\//.test(u)) { + base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory; + u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u); + } // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri) u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something @@ -828,9 +861,11 @@ tinymce.create('tinymce.util.Dispatcher', { }); })(); -tinymce.create('static tinymce.util.JSON', { - serialize : function(o) { - var i, v, s = tinymce.util.JSON.serialize, t; +(function() { + function serialize(o, quote) { + var i, v, t; + + quote = quote || '"'; if (o == null) return 'null'; @@ -840,7 +875,11 @@ tinymce.create('static tinymce.util.JSON', { if (t == 'string') { v = '\bb\tt\nn\ff\rr\""\'\'\\\\'; - return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) { + return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) { + // Make sure single quotes never get encoded inside double quotes for JSON compatibility + if (quote === '"' && a === "'") + return a; + i = v.indexOf(b); if (i + 1) @@ -849,13 +888,13 @@ tinymce.create('static tinymce.util.JSON', { a = b.charCodeAt().toString(16); return '\\u' + '0000'.substring(a.length) + a; - }) + '"'; + }) + quote; } if (t == 'object') { if (o.hasOwnProperty && o instanceof Array) { for (i=0, v = '['; i 0 ? ',' : '') + s(o[i]); + v += (i > 0 ? ',' : '') + serialize(o[i], quote); return v + ']'; } @@ -863,24 +902,27 @@ tinymce.create('static tinymce.util.JSON', { v = '{'; for (i in o) - v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : ''; + v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : ''; return v + '}'; } return '' + o; - }, + }; - parse : function(s) { - try { - return eval('(' + s + ')'); - } catch (ex) { - // Ignore - } - } + tinymce.util.JSON = { + serialize: serialize, - }); + parse: function(s) { + try { + return eval('(' + s + ')'); + } catch (ex) { + // Ignore + } + } + }; +})(); tinymce.create('static tinymce.util.XHR', { send : function(o) { var x, t, w = window, c = 0; @@ -971,7 +1013,8 @@ tinymce.create('static tinymce.util.XHR', { }; o.error = function(ty, x) { - ecb.call(o.error_scope || o.scope, ty, x); + if (ecb) + ecb.call(o.error_scope || o.scope, ty, x); }; o.data = JSON.serialize({ @@ -993,5820 +1036,7420 @@ tinymce.create('static tinymce.util.XHR', { } }); }()); -(function(tinymce) { - // Shorten names - var each = tinymce.each, - is = tinymce.is, - isWebKit = tinymce.isWebKit, - isIE = tinymce.isIE, - blockRe = /^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/, - boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'), - mceAttribs = makeMap('src,href,style,coords,shape'), - encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'}, - encodeCharsRe = /[<>&\"]/g, - simpleSelectorRe = /^([a-z0-9],?)+$/i, - tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g, - attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; +(function(tinymce){ + tinymce.VK = { + DELETE:46, + BACKSPACE:8 + + } - function makeMap(str) { - var map = {}, i; +})(tinymce); - str = str.split(','); - for (i = str.length; i >= 0; i--) - map[str[i]] = 1; +(function(tinymce) { + var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE; - return map; - }; + function cleanupStylesWhenDeleting(ed) { + var dom = ed.dom, selection = ed.selection; - tinymce.create('tinymce.dom.DOMUtils', { - doc : null, - root : null, - files : null, - pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, - props : { - "for" : "htmlFor", - "class" : "className", - className : "className", - checked : "checked", - disabled : "disabled", - maxlength : "maxLength", - readonly : "readOnly", - selected : "selected", - value : "value", - id : "id", - name : "name", - type : "type" - }, + ed.onKeyDown.add(function(ed, e) { + var rng, blockElm, node, clonedSpan, isDelete; - DOMUtils : function(d, s) { - var t = this, globalStyle; + isDelete = e.keyCode == DELETE; + if (isDelete || e.keyCode == BACKSPACE) { + e.preventDefault(); + rng = selection.getRng(); - t.doc = d; - t.win = window; - t.files = {}; - t.cssFlicker = false; - t.counter = 0; - t.stdMode = d.documentMode >= 8; - t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; + // Find root block + blockElm = dom.getParent(rng.startContainer, dom.isBlock); - t.settings = s = tinymce.extend({ - keep_values : false, - hex_colors : 1, - process_html : 1 - }, s); + // On delete clone the root span of the next block element + if (isDelete) + blockElm = dom.getNext(blockElm, dom.isBlock); - // Fix IE6SP2 flicker and check it failed for pre SP2 - if (tinymce.isIE6) { - try { - d.execCommand('BackgroundImageCache', false, true); - } catch (e) { - t.cssFlicker = true; + // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace + if (blockElm) { + node = blockElm.firstChild; + + if (node && node.nodeName === 'SPAN') { + clonedSpan = node.cloneNode(false); + } } - } - // Build styles list - if (s.valid_styles) { - t._styles = {}; + // Do the backspace/delete actiopn + ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null); + + // Find all odd apple-style-spans + blockElm = dom.getParent(rng.startContainer, dom.isBlock); + tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) { + var rng = dom.createRng(); + + // Set range selection before the span we are about to remove + rng.setStartBefore(span); + rng.setEndBefore(span); + + if (clonedSpan) { + dom.replace(clonedSpan.cloneNode(false), span, true); + } else { + dom.remove(span, true); + } - // Convert styles into a rule list - each(s.valid_styles, function(value, key) { - t._styles[key] = tinymce.explode(value); + // Restore the selection + selection.setRng(rng); }); } + }); + }; - tinymce.addUnload(t.destroy, t); - }, + function emptyEditorWhenDeleting(ed) { + ed.onKeyUp.add(function(ed, e) { + var keyCode = e.keyCode; - getRoot : function() { - var t = this, s = t.settings; + if (keyCode == DELETE || keyCode == BACKSPACE) { + if (ed.dom.isEmpty(ed.getBody())) { + ed.setContent('', {format : 'raw'}); + ed.nodeChanged(); + return; + } + } + }); + }; + + tinymce.create('tinymce.util.Quirks', { + Quirks: function(ed) { + // Load WebKit specific fixed + if (tinymce.isWebKit) { + cleanupStylesWhenDeleting(ed); + emptyEditorWhenDeleting(ed); + } - return (s && t.get(s.root_element)) || t.doc.body; - }, + // Load IE specific fixes + if (tinymce.isIE) { + emptyEditorWhenDeleting(ed); + } + } + }); +})(tinymce); +(function(tinymce) { + var namedEntities, baseEntities, reverseEntities, + attrsCharsRegExp = /[&<>\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + rawCharsRegExp = /[<>&\"\']/g, + entityRegExp = /&(#x|#)?([\w]+);/g, + asciiMap = { + 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020", + 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152", + 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022", + 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A", + 156 : "\u0153", 158 : "\u017E", 159 : "\u0178" + }; - getViewPort : function(w) { - var d, b; + // Raw entities + baseEntities = { + '\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code + "'" : ''', + '<' : '<', + '>' : '>', + '&' : '&' + }; - w = !w ? this.win : w; - d = w.document; - b = this.boxModel ? d.documentElement : d.body; + // Reverse lookup table for raw entities + reverseEntities = { + '<' : '<', + '>' : '>', + '&' : '&', + '"' : '"', + ''' : "'" + }; - // Returns viewport size excluding scrollbars - return { - x : w.pageXOffset || b.scrollLeft, - y : w.pageYOffset || b.scrollTop, - w : w.innerWidth || b.clientWidth, - h : w.innerHeight || b.clientHeight - }; - }, + // Decodes text by using the browser + function nativeDecode(text) { + var elm; - getRect : function(e) { - var p, t = this, sr; + elm = document.createElement("div"); + elm.innerHTML = text; - e = t.get(e); - p = t.getPos(e); - sr = t.getSize(e); + return elm.textContent || elm.innerText || text; + }; - return { - x : p.x, - y : p.y, - w : sr.w, - h : sr.h - }; - }, + // Build a two way lookup table for the entities + function buildEntitiesLookup(items, radix) { + var i, chr, entity, lookup = {}; - getSize : function(e) { - var t = this, w, h; + if (items) { + items = items.split(','); + radix = radix || 10; - e = t.get(e); - w = t.getStyle(e, 'width'); - h = t.getStyle(e, 'height'); + // Build entities lookup table + for (i = 0; i < items.length; i += 2) { + chr = String.fromCharCode(parseInt(items[i], radix)); - // Non pixel value, then force offset/clientWidth - if (w.indexOf('px') === -1) - w = 0; + // Only add non base entities + if (!baseEntities[chr]) { + entity = '&' + items[i + 1] + ';'; + lookup[chr] = entity; + lookup[entity] = chr; + } + } - // Non pixel value, then force offset/clientWidth - if (h.indexOf('px') === -1) - h = 0; + return lookup; + } + }; - return { - w : parseInt(w) || e.offsetWidth || e.clientWidth, - h : parseInt(h) || e.offsetHeight || e.clientHeight - }; + // Unpack entities lookup where the numbers are in radix 32 to reduce the size + namedEntities = buildEntitiesLookup( + '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + + '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + + '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + + '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + + '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + + '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + + '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + + '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + + '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + + '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + + 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + + 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + + 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + + 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + + 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + + '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + + '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + + '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + + '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + + '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + + 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + + 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + + 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + + '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + + '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro' + , 32); + + tinymce.html = tinymce.html || {}; + + tinymce.html.Entities = { + encodeRaw : function(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); }, - getParent : function(n, f, r) { - return this.getParents(n, f, r, false); + encodeAllRaw : function(text) { + return ('' + text).replace(rawCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); }, - getParents : function(n, f, r, c) { - var t = this, na, se = t.settings, o = []; + encodeNumeric : function(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + // Multi byte sequence convert it to a single entity + if (chr.length > 1) + return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';'; - n = t.get(n); - c = c === undefined; + return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; + }); + }, - if (se.strict_root) - r = r || t.getRoot(); + encodeNamed : function(text, attr, entities) { + entities = entities || namedEntities; - // Wrap node name as func - if (is(f, 'string')) { - na = f; + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || chr; + }); + }, - if (f === '*') { - f = function(n) {return n.nodeType == 1;}; - } else { - f = function(n) { - return t.is(n, na); - }; - } - } + getEncodeFunc : function(name, entities) { + var Entities = tinymce.html.Entities; - while (n) { - if (n == r || !n.nodeType || n.nodeType === 9) - break; + entities = buildEntitiesLookup(entities) || namedEntities; - if (!f || f(n)) { - if (c) - o.push(n); - else - return n; - } + function encodeNamedAndNumeric(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; + }); + }; - n = n.parentNode; - } + function encodeCustomNamed(text, attr) { + return Entities.encodeNamed(text, attr, entities); + }; - return c ? o : null; - }, + // Replace + with , to be compatible with previous TinyMCE versions + name = tinymce.makeMap(name.replace(/\+/g, ',')); - get : function(e) { - var n; + // Named and numeric encoder + if (name.named && name.numeric) + return encodeNamedAndNumeric; - if (e && this.doc && typeof(e) == 'string') { - n = e; - e = this.doc.getElementById(e); + // Named encoder + if (name.named) { + // Custom names + if (entities) + return encodeCustomNamed; - // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick - if (e && e.id !== n) - return this.doc.getElementsByName(n)[1]; + return Entities.encodeNamed; } - return e; - }, + // Numeric + if (name.numeric) + return Entities.encodeNumeric; - getNext : function(node, selector) { - return this._findSib(node, selector, 'nextSibling'); + // Raw encoder + return Entities.encodeRaw; }, - getPrev : function(node, selector) { - return this._findSib(node, selector, 'previousSibling'); - }, + decode : function(text) { + return text.replace(entityRegExp, function(all, numeric, value) { + if (numeric) { + value = parseInt(value, numeric.length === 2 ? 16 : 10); + // Support upper UTF + if (value > 0xFFFF) { + value -= 0x10000; - select : function(pa, s) { - var t = this; + return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); + } else + return asciiMap[value] || String.fromCharCode(value); + } - return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); - }, + return reverseEntities[all] || namedEntities[all] || nativeDecode(all); + }); + } + }; +})(tinymce); - is : function(n, selector) { - var i; +tinymce.html.Styles = function(settings, schema) { + var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi, + urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi, + styleRegExp = /\s*([^:]+):\s*([^;]+);?/g, + trimRightRegExp = /\s+$/, + urlColorRegExp = /rgb/, + undef, i, encodingLookup = {}, encodingItems; - // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance - if (n.length === undefined) { - // Simple all selector - if (selector === '*') - return n.nodeType == 1; + settings = settings || {}; - // Simple selector just elements - if (simpleSelectorRe.test(selector)) { - selector = selector.toLowerCase().split(/,/); - n = n.nodeName.toLowerCase(); + encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' '); + for (i = 0; i < encodingItems.length; i++) { + encodingLookup[encodingItems[i]] = '\uFEFF' + i; + encodingLookup['\uFEFF' + i] = encodingItems[i]; + } - for (i = selector.length - 1; i >= 0; i--) { - if (selector[i] == n) - return true; - } + function toHex(match, r, g, b) { + function hex(val) { + val = parseInt(val).toString(16); - return false; - } - } + return val.length > 1 ? val : '0' + val; // 0 -> 00 + }; - return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; + return '#' + hex(r) + hex(g) + hex(b); + }; + + return { + toHex : function(color) { + return color.replace(rgbRegExp, toHex); }, + parse : function(css) { + var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; - add : function(p, n, a, h, c) { - var t = this; + function compress(prefix, suffix) { + var top, right, bottom, left; - return this.run(p, function(p) { - var e, k; + // Get values and check it it needs compressing + top = styles[prefix + '-top' + suffix]; + if (!top) + return; - e = is(n, 'string') ? t.doc.createElement(n) : n; - t.setAttribs(e, a); + right = styles[prefix + '-right' + suffix]; + if (top != right) + return; - if (h) { - if (h.nodeType) - e.appendChild(h); - else - t.setHTML(e, h); - } + bottom = styles[prefix + '-bottom' + suffix]; + if (right != bottom) + return; - return !c ? p.appendChild(e) : e; - }); - }, + left = styles[prefix + '-left' + suffix]; + if (bottom != left) + return; - create : function(n, a, h) { - return this.add(this.doc.createElement(n), n, a, h, 1); - }, + // Compress + styles[prefix + suffix] = left; + delete styles[prefix + '-top' + suffix]; + delete styles[prefix + '-right' + suffix]; + delete styles[prefix + '-bottom' + suffix]; + delete styles[prefix + '-left' + suffix]; + }; - createHTML : function(n, a, h) { - var o = '', t = this, k; + function canCompress(key) { + var value = styles[key], i; - o += '<' + n; + if (!value || value.indexOf(' ') < 0) + return; - for (k in a) { - if (a.hasOwnProperty(k)) - o += ' ' + k + '="' + t.encode(a[k]) + '"'; - } + value = value.split(' '); + i = value.length; + while (i--) { + if (value[i] !== value[0]) + return false; + } - // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime - if (typeof(h) != "undefined") - return o + '>' + h + ''; + styles[key] = value[0]; - return o + ' />'; - }, + return true; + }; - remove : function(node, keep_children) { - return this.run(node, function(node) { - var parent, child; + function compress2(target, a, b, c) { + if (!canCompress(a)) + return; - parent = node.parentNode; + if (!canCompress(b)) + return; - if (!parent) - return null; + if (!canCompress(c)) + return; - if (keep_children) { - while (child = node.firstChild) { - // IE 8 will crash if you don't remove completely empty text nodes - if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) - parent.insertBefore(child, node); - else - node.removeChild(child); - } - } + // Compress + styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; + delete styles[a]; + delete styles[b]; + delete styles[c]; + }; - return parent.removeChild(node); - }); - }, + // Encodes the specified string by replacing all \" \' ; : with _ + function encode(str) { + isEncoded = true; - setStyle : function(n, na, v) { - var t = this; + return encodingLookup[str]; + }; - return t.run(n, function(e) { - var s, i; + // Decodes the specified string by replacing all _ with it's original value \" \' etc + // It will also decode the \" \' if keep_slashes is set to fale or omitted + function decode(str, keep_slashes) { + if (isEncoded) { + str = str.replace(/\uFEFF[0-9]/g, function(str) { + return encodingLookup[str]; + }); + } - s = e.style; + if (!keep_slashes) + str = str.replace(/\\([\'\";:])/g, "$1"); - // Camelcase it, if needed - na = na.replace(/-(\D)/g, function(a, b){ - return b.toUpperCase(); + return str; + } + + if (css) { + // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing + css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { + return str.replace(/[;:]/g, encode); }); - // Default px suffix on these - if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) - v += 'px'; + // Parse styles + while (matches = styleRegExp.exec(css)) { + name = matches[1].replace(trimRightRegExp, '').toLowerCase(); + value = matches[2].replace(trimRightRegExp, ''); - switch (na) { - case 'opacity': - // IE specific opacity - if (isIE) { - s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; + if (name && value.length > 0) { + // Opera will produce 700 instead of bold in their style values + if (name === 'font-weight' && value === '700') + value = 'bold'; + else if (name === 'color' || name === 'background-color') // Lowercase colors like RED + value = value.toLowerCase(); - if (!n.currentStyle || !n.currentStyle.hasLayout) - s.display = 'inline-block'; - } + // Convert RGB colors to HEX + value = value.replace(rgbRegExp, toHex); - // Fix for older browsers - s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; - break; + // Convert URLs and force them into url('value') format + value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) { + str = str || str2; - case 'float': - isIE ? s.styleFloat = v : s.cssFloat = v; - break; - - default: - s[na] = v || ''; - } + if (str) { + str = decode(str); - // Force update of the style data - if (t.settings.update_styles) - t.setAttrib(e, '_mce_style'); - }); - }, + // Force strings into single quote format + return "'" + str.replace(/\'/g, "\\'") + "'"; + } - getStyle : function(n, na, c) { - n = this.get(n); + url = decode(url || url2 || url3); - if (!n) - return false; + // Convert the URL to relative/absolute depending on config + if (urlConverter) + url = urlConverter.call(urlConverterScope, url, 'style'); - // Gecko - if (this.doc.defaultView && c) { - // Remove camelcase - na = na.replace(/[A-Z]/g, function(a){ - return '-' + a; - }); + // Output new URL format + return "url('" + url.replace(/\'/g, "\\'") + "')"; + }); - try { - return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); - } catch (ex) { - // Old safari might fail - return null; + styles[name] = isEncoded ? decode(value, true) : value; + } + + styleRegExp.lastIndex = matches.index + matches[0].length; } + + // Compress the styles to reduce it's size for example IE will expand styles + compress("border", ""); + compress("border", "-width"); + compress("border", "-color"); + compress("border", "-style"); + compress("padding", ""); + compress("margin", ""); + compress2('border', 'border-width', 'border-style', 'border-color'); + + // Remove pointless border, IE produces these + if (styles.border === 'medium none') + delete styles.border; } - // Camelcase it, if needed - na = na.replace(/-(\D)/g, function(a, b){ - return b.toUpperCase(); - }); + return styles; + }, - if (na == 'float') - na = isIE ? 'styleFloat' : 'cssFloat'; + serialize : function(styles, element_name) { + var css = '', name, value; - // IE & Opera - if (n.currentStyle && c) - return n.currentStyle[na]; + function serializeStyles(name) { + var styleList, i, l, value; - return n.style[na]; - }, + styleList = schema.styles[name]; + if (styleList) { + for (i = 0, l = styleList.length; i < l; i++) { + name = styleList[i]; + value = styles[name]; - setStyles : function(e, o) { - var t = this, s = t.settings, ol; + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } + }; - ol = s.update_styles; - s.update_styles = 0; + // Serialize styles according to schema + if (element_name && schema && schema.styles) { + // Serialize global styles and element specific styles + serializeStyles('*'); + serializeStyles(element_name); + } else { + // Output the styles in the order they are inside the object + for (name in styles) { + value = styles[name]; - each(o, function(v, n) { - t.setStyle(e, n, v); - }); + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } - // Update style info - s.update_styles = ol; - if (s.update_styles) - t.setAttrib(e, s.cssText); - }, + return css; + } + }; +}; - setAttrib : function(e, n, v) { - var t = this; +(function(tinymce) { + var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {}, + defaultWhiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each; - // Whats the point - if (!e || !n) - return; + function split(str, delim) { + return str.split(delim || ','); + }; - // Strict XML mode - if (t.settings.strict) - n = n.toLowerCase(); + function unpack(lookup, data) { + var key, elements = {}; - return this.run(e, function(e) { - var s = t.settings; + function replace(value) { + return value.replace(/[A-Z]+/g, function(key) { + return replace(lookup[key]); + }); + }; - switch (n) { - case "style": - if (!is(v, 'string')) { - each(v, function(v, n) { - t.setStyle(e, n, v); - }); + // Unpack lookup + for (key in lookup) { + if (lookup.hasOwnProperty(key)) + lookup[key] = replace(lookup[key]); + } - return; - } + // Unpack and parse data into object map + replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { + attributes = split(attributes, '|'); - // No mce_style for elements with these since they might get resized by the user - if (s.keep_values) { - if (v && !t._isRes(v)) - e.setAttribute('_mce_style', v, 2); - else - e.removeAttribute('_mce_style', 2); - } + elements[name] = { + attributes : makeMap(attributes), + attributesOrder : attributes, + children : makeMap(children, '|', {'#comment' : {}}) + } + }); - e.style.cssText = v; - break; + return elements; + }; - case "class": - e.className = v || ''; // Fix IE null bug - break; + // Build a lookup table for block elements both lowercase and uppercase + blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + + 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + + 'noscript,menu,isindex,samp,header,footer,article,section,hgroup'; + blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase())); + + // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size + transitional = unpack({ + Z : 'H|K|N|O|P', + Y : 'X|form|R|Q', + ZG : 'E|span|width|align|char|charoff|valign', + X : 'p|T|div|U|W|isindex|fieldset|table', + ZF : 'E|align|char|charoff|valign', + W : 'pre|hr|blockquote|address|center|noframes', + ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height', + ZD : '[E][S]', + U : 'ul|ol|dl|menu|dir', + ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', + T : 'h1|h2|h3|h4|h5|h6', + ZB : 'X|S|Q', + S : 'R|P', + ZA : 'a|G|J|M|O|P', + R : 'a|H|K|N|O', + Q : 'noscript|P', + P : 'ins|del|script', + O : 'input|select|textarea|label|button', + N : 'M|L', + M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', + L : 'sub|sup', + K : 'J|I', + J : 'tt|i|b|u|s|strike', + I : 'big|small|font|basefont', + H : 'G|F', + G : 'br|span|bdo', + F : 'object|applet|img|map|iframe', + E : 'A|B|C', + D : 'accesskey|tabindex|onfocus|onblur', + C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup', + B : 'lang|xml:lang|dir', + A : 'id|class|style|title' + }, 'script[id|charset|type|language|src|defer|xml:space][]' + + 'style[B|id|type|media|title|xml:space][]' + + 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + + 'param[id|name|value|valuetype|type][]' + + 'p[E|align][#|S]' + + 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + + 'br[A|clear][]' + + 'span[E][#|S]' + + 'bdo[A|C|B][#|S]' + + 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + + 'h1[E|align][#|S]' + + 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + + 'map[B|C|A|name][X|form|Q|area]' + + 'h2[E|align][#|S]' + + 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + + 'h3[E|align][#|S]' + + 'tt[E][#|S]' + + 'i[E][#|S]' + + 'b[E][#|S]' + + 'u[E][#|S]' + + 's[E][#|S]' + + 'strike[E][#|S]' + + 'big[E][#|S]' + + 'small[E][#|S]' + + 'font[A|B|size|color|face][#|S]' + + 'basefont[id|size|color|face][]' + + 'em[E][#|S]' + + 'strong[E][#|S]' + + 'dfn[E][#|S]' + + 'code[E][#|S]' + + 'q[E|cite][#|S]' + + 'samp[E][#|S]' + + 'kbd[E][#|S]' + + 'var[E][#|S]' + + 'cite[E][#|S]' + + 'abbr[E][#|S]' + + 'acronym[E][#|S]' + + 'sub[E][#|S]' + + 'sup[E][#|S]' + + 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + + 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + + 'optgroup[E|disabled|label][option]' + + 'option[E|selected|disabled|label|value][]' + + 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + + 'label[E|for|accesskey|onfocus|onblur][#|S]' + + 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + + 'h4[E|align][#|S]' + + 'ins[E|cite|datetime][#|Y]' + + 'h5[E|align][#|S]' + + 'del[E|cite|datetime][#|Y]' + + 'h6[E|align][#|S]' + + 'div[E|align][#|Y]' + + 'ul[E|type|compact][li]' + + 'li[E|type|value][#|Y]' + + 'ol[E|type|compact|start][li]' + + 'dl[E|compact][dt|dd]' + + 'dt[E][#|S]' + + 'dd[E][#|Y]' + + 'menu[E|compact][li]' + + 'dir[E|compact][li]' + + 'pre[E|width|xml:space][#|ZA]' + + 'hr[E|align|noshade|size|width][]' + + 'blockquote[E|cite][#|Y]' + + 'address[E][#|S|p]' + + 'center[E][#|Y]' + + 'noframes[E][#|Y]' + + 'isindex[A|B|prompt][]' + + 'fieldset[E][#|legend|Y]' + + 'legend[E|accesskey|align][#|S]' + + 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + + 'caption[E|align][#|S]' + + 'col[ZG][]' + + 'colgroup[ZG][col]' + + 'thead[ZF][tr]' + + 'tr[ZF|bgcolor][th|td]' + + 'th[E|ZE][#|Y]' + + 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + + 'noscript[E][#|Y]' + + 'td[E|ZE][#|Y]' + + 'tfoot[ZF][tr]' + + 'tbody[ZF][tr]' + + 'area[E|D|shape|coords|href|nohref|alt|target][]' + + 'base[id|href|target][]' + + 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]' + ); - case "src": - case "href": - if (s.keep_values) { - if (s.url_converter) - v = s.url_converter.call(s.url_converter_scope || t, v, n, e); + boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls'); + shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source'); + nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap); + defaultWhiteSpaceElementsMap = makeMap('pre,script,style,textarea'); + selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); - t.setAttrib(e, '_mce_' + n, v, 2); - } + tinymce.html.Schema = function(settings) { + var self = this, elements = {}, children = {}, patternElements = [], validStyles, whiteSpaceElementsMap; - break; - - case "shape": - e.setAttribute('_mce_style', v); - break; - } + settings = settings || {}; - if (is(v) && v !== null && v.length !== 0) - e.setAttribute(n, '' + v, 2); - else - e.removeAttribute(n, 2); - }); - }, + // Allow all elements and attributes if verify_html is set to false + if (settings.verify_html === false) + settings.valid_elements = '*[*]'; - setAttribs : function(e, o) { - var t = this; + // Build styles list + if (settings.valid_styles) { + validStyles = {}; - return this.run(e, function(e) { - each(o, function(v, n) { - t.setAttrib(e, n, v); - }); + // Convert styles into a rule list + each(settings.valid_styles, function(value, key) { + validStyles[key] = tinymce.explode(value); }); - }, + } - getAttrib : function(e, n, dv) { - var v, t = this; + whiteSpaceElementsMap = settings.whitespace_elements ? makeMap(settings.whitespace_elements) : defaultWhiteSpaceElementsMap; - e = t.get(e); + // Converts a wildcard expression string to a regexp for example *a will become /.*a/. + function patternToRegExp(str) { + return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); + }; - if (!e || e.nodeType !== 1) - return false; + // Parses the specified valid_elements string and adds to the current rules + // This function is a bit hard to read since it's heavily optimized for speed + function addValidElements(valid_elements) { + var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder, + prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value, + elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/, + attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/, + hasPatternsRegExp = /[*?+]/; + + if (valid_elements) { + // Split valid elements into an array with rules + valid_elements = split(valid_elements); + + if (elements['@']) { + globalAttributes = elements['@'].attributes; + globalAttributesOrder = elements['@'].attributesOrder; + } - if (!is(dv)) - dv = ''; + // Loop all rules + for (ei = 0, el = valid_elements.length; ei < el; ei++) { + // Parse element rule + matches = elementRuleRegExp.exec(valid_elements[ei]); + if (matches) { + // Setup local names for matches + prefix = matches[1]; + elementName = matches[2]; + outputName = matches[3]; + attrData = matches[4]; + + // Create new attributes and attributesOrder + attributes = {}; + attributesOrder = []; + + // Create the new element + element = { + attributes : attributes, + attributesOrder : attributesOrder + }; - // Try the mce variant for these - if (/^(src|href|style|coords|shape)$/.test(n)) { - v = e.getAttribute("_mce_" + n); + // Padd empty elements prefix + if (prefix === '#') + element.paddEmpty = true; - if (v) - return v; - } + // Remove empty elements prefix + if (prefix === '-') + element.removeEmpty = true; - if (isIE && t.props[n]) { - v = e[t.props[n]]; - v = v && v.nodeValue ? v.nodeValue : v; - } + // Copy attributes from global rule into current rule + if (globalAttributes) { + for (key in globalAttributes) + attributes[key] = globalAttributes[key]; - if (!v) - v = e.getAttribute(n, 2); + attributesOrder.push.apply(attributesOrder, globalAttributesOrder); + } - // Check boolean attribs - if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { - if (e[t.props[n]] === true && v === '') - return n; + // Attributes defined + if (attrData) { + attrData = split(attrData, '|'); + for (ai = 0, al = attrData.length; ai < al; ai++) { + matches = attrRuleRegExp.exec(attrData[ai]); + if (matches) { + attr = {}; + attrType = matches[1]; + attrName = matches[2].replace(/::/g, ':'); + prefix = matches[3]; + value = matches[4]; + + // Required + if (attrType === '!') { + element.attributesRequired = element.attributesRequired || []; + element.attributesRequired.push(attrName); + attr.required = true; + } - return v ? n : ''; - } + // Denied from global + if (attrType === '-') { + delete attributes[attrName]; + attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); + continue; + } - // Inner input elements will override attributes on form elements - if (e.nodeName === "FORM" && e.getAttributeNode(n)) - return e.getAttributeNode(n).nodeValue; + // Default value + if (prefix) { + // Default value + if (prefix === '=') { + element.attributesDefault = element.attributesDefault || []; + element.attributesDefault.push({name: attrName, value: value}); + attr.defaultValue = value; + } - if (n === 'style') { - v = v || e.style.cssText; + // Forced value + if (prefix === ':') { + element.attributesForced = element.attributesForced || []; + element.attributesForced.push({name: attrName, value: value}); + attr.forcedValue = value; + } - if (v) { - v = t.serializeStyle(t.parseStyle(v), e.nodeName); + // Required values + if (prefix === '<') + attr.validValues = makeMap(value, '?'); + } - if (t.settings.keep_values && !t._isRes(v)) - e.setAttribute('_mce_style', v); + // Check for attribute patterns + if (hasPatternsRegExp.test(attrName)) { + element.attributePatterns = element.attributePatterns || []; + attr.pattern = patternToRegExp(attrName); + element.attributePatterns.push(attr); + } else { + // Add attribute to order list if it doesn't already exist + if (!attributes[attrName]) + attributesOrder.push(attrName); + + attributes[attrName] = attr; + } + } + } + } + + // Global rule, store away these for later usage + if (!globalAttributes && elementName == '@') { + globalAttributes = attributes; + globalAttributesOrder = attributesOrder; + } + + // Handle substitute elements such as b/strong + if (outputName) { + element.outputName = elementName; + elements[outputName] = element; + } + + // Add pattern or exact element + if (hasPatternsRegExp.test(elementName)) { + element.pattern = patternToRegExp(elementName); + patternElements.push(element); + } else + elements[elementName] = element; + } } } + }; - // Remove Apple and WebKit stuff - if (isWebKit && n === "class" && v) - v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); + function setValidElements(valid_elements) { + elements = {}; + patternElements = []; - // Handle IE issues - if (isIE) { - switch (n) { - case 'rowspan': - case 'colspan': - // IE returns 1 as default value - if (v === 1) - v = ''; + addValidElements(valid_elements); - break; + each(transitional, function(element, name) { + children[name] = element.children; + }); + }; - case 'size': - // IE returns +0 as default value for size - if (v === '+0' || v === 20 || v === 0) - v = ''; + // Adds custom non HTML elements to the schema + function addCustomElements(custom_elements) { + var customElementRegExp = /^(~)?(.+)$/; - break; + if (custom_elements) { + each(split(custom_elements), function(rule) { + var matches = customElementRegExp.exec(rule), + inline = matches[1] === '~', + cloneName = inline ? 'span' : 'div', + name = matches[2]; - case 'width': - case 'height': - case 'vspace': - case 'checked': - case 'disabled': - case 'readonly': - if (v === 0) - v = ''; + children[name] = children[cloneName]; + customElementsMap[name] = cloneName; - break; + // If it's not marked as inline then add it to valid block elements + if (!inline) + blockElementsMap[name] = {}; - case 'hspace': - // IE returns -1 as default value - if (v === -1) - v = ''; + // Add custom elements at span/div positions + each(children, function(element, child) { + if (element[cloneName]) + element[name] = element[cloneName]; + }); + }); + } + }; - break; + // Adds valid children to the schema object + function addValidChildren(valid_children) { + var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; - case 'maxlength': - case 'tabindex': - // IE returns default value - if (v === 32768 || v === 2147483647 || v === '32768') - v = ''; + if (valid_children) { + each(split(valid_children), function(rule) { + var matches = childRuleRegExp.exec(rule), parent, prefix; - break; + if (matches) { + prefix = matches[1]; - case 'multiple': - case 'compact': - case 'noshade': - case 'nowrap': - if (v === 65535) - return n; + // Add/remove items from default + if (prefix) + parent = children[matches[2]]; + else + parent = children[matches[2]] = {'#comment' : {}}; - return dv; + parent = children[matches[2]]; - case 'shape': - v = v.toLowerCase(); - break; + each(split(matches[3], '|'), function(child) { + if (prefix === '-') + delete parent[child]; + else + parent[child] = {}; + }); + } + }); + } + }; - default: - // IE has odd anonymous function for event attributes - if (n.indexOf('on') === 0 && v) - v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); - } + function getElementRule(name) { + var element = elements[name], i; + + // Exact match found + if (element) + return element; + + // No exact match then try the patterns + i = patternElements.length; + while (i--) { + element = patternElements[i]; + + if (element.pattern.test(name)) + return element; } + }; - return (v !== undefined && v !== null && v !== '') ? '' + v : dv; - }, + if (!settings.valid_elements) { + // No valid elements defined then clone the elements from the transitional spec + each(transitional, function(element, name) { + elements[name] = { + attributes : element.attributes, + attributesOrder : element.attributesOrder + }; - getPos : function(n, ro) { - var t = this, x = 0, y = 0, e, d = t.doc, r; + children[name] = element.children; + }); - n = t.get(n); - ro = ro || d.body; + // Switch these + each(split('strong/b,em/i'), function(item) { + item = split(item, '/'); + elements[item[1]].outputName = item[0]; + }); - if (n) { - // Use getBoundingClientRect on IE, Opera has it but it's not perfect - if (isIE && !t.stdMode) { - n = n.getBoundingClientRect(); - e = t.boxModel ? d.documentElement : d.body; - x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border - x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x; + // Add default alt attribute for images + elements.img.attributesDefault = [{name: 'alt', value: ''}]; - return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x}; - } + // Remove these if they are empty by default + each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) { + elements[name].removeEmpty = true; + }); - r = n; - while (r && r != ro && r.nodeType) { - x += r.offsetLeft || 0; - y += r.offsetTop || 0; - r = r.offsetParent; - } + // Padd these by default + each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) { + elements[name].paddEmpty = true; + }); + } else + setValidElements(settings.valid_elements); - r = n.parentNode; - while (r && r != ro && r.nodeType) { - x -= r.scrollLeft || 0; - y -= r.scrollTop || 0; - r = r.parentNode; - } - } + addCustomElements(settings.custom_elements); + addValidChildren(settings.valid_children); + addValidElements(settings.extended_valid_elements); - return {x : x, y : y}; - }, + // Todo: Remove this when we fix list handling to be valid + addValidChildren('+ol[ul|ol],+ul[ul|ol]'); - parseStyle : function(st) { - var t = this, s = t.settings, o = {}; + // If the user didn't allow span only allow internal spans + if (!getElementRule('span')) + addValidElements('span[!data-mce-type|*]'); - if (!st) - return o; + // Delete invalid elements + if (settings.invalid_elements) { + tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { + if (elements[item]) + delete elements[item]; + }); + } - function compress(p, s, ot) { - var t, r, b, l; + self.children = children; - // Get values and check it it needs compressing - t = o[p + '-top' + s]; - if (!t) - return; + self.styles = validStyles; - r = o[p + '-right' + s]; - if (t != r) - return; + self.getBoolAttrs = function() { + return boolAttrMap; + }; - b = o[p + '-bottom' + s]; - if (r != b) - return; + self.getBlockElements = function() { + return blockElementsMap; + }; - l = o[p + '-left' + s]; - if (b != l) - return; + self.getShortEndedElements = function() { + return shortEndedElementsMap; + }; - // Compress - o[ot] = l; - delete o[p + '-top' + s]; - delete o[p + '-right' + s]; - delete o[p + '-bottom' + s]; - delete o[p + '-left' + s]; - }; + self.getSelfClosingElements = function() { + return selfClosingElementsMap; + }; - function compress2(ta, a, b, c) { - var t; + self.getNonEmptyElements = function() { + return nonEmptyElementsMap; + }; - t = o[a]; - if (!t) - return; + self.getWhiteSpaceElements = function() { + return whiteSpaceElementsMap; + }; - t = o[b]; - if (!t) - return; + self.isValidChild = function(name, child) { + var parent = children[name]; - t = o[c]; - if (!t) - return; + return !!(parent && parent[child]); + }; - // Compress - o[ta] = o[a] + ' ' + o[b] + ' ' + o[c]; - delete o[a]; - delete o[b]; - delete o[c]; - }; + self.getElementRule = getElementRule; - st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities + self.getCustomElements = function() { + return customElementsMap; + }; - each(st.split(';'), function(v) { - var sv, ur = []; + self.addValidElements = addValidElements; - if (v) { - v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities - v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';}); - v = v.split(':'); - sv = tinymce.trim(v[1]); - sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];}); - - sv = sv.replace(/rgb\([^\)]+\)/g, function(v) { - return t.toHex(v); - }); + self.setValidElements = setValidElements; - if (s.url_converter) { - sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) { - return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')'; - }); - } + self.addCustomElements = addCustomElements; - o[tinymce.trim(v[0]).toLowerCase()] = sv; - } - }); + self.addValidChildren = addValidChildren; + }; - compress("border", "", "border"); - compress("border", "-width", "border-width"); - compress("border", "-color", "border-color"); - compress("border", "-style", "border-style"); - compress("padding", "", "padding"); - compress("margin", "", "margin"); - compress2('border', 'border-width', 'border-style', 'border-color'); + // Expose boolMap and blockElementMap as static properties for usage in DOMUtils + tinymce.html.Schema.boolAttrMap = boolAttrMap; + tinymce.html.Schema.blockElementsMap = blockElementsMap; +})(tinymce); - if (isIE) { - // Remove pointless border - if (o.border == 'medium none') - o.border = ''; - } +(function(tinymce) { + tinymce.html.SaxParser = function(settings, schema) { + var self = this, noop = function() {}; - return o; - }, + settings = settings || {}; + self.schema = schema = schema || new tinymce.html.Schema(); - serializeStyle : function(o, name) { - var t = this, s = ''; + if (settings.fix_self_closing !== false) + settings.fix_self_closing = true; - function add(v, k) { - if (k && v) { - // Remove browser specific styles like -moz- or -webkit- - if (k.indexOf('-') === 0) - return; + // Add handler functions from settings and setup default handlers + tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) { + if (name) + self[name] = settings[name] || noop; + }); - switch (k) { - case 'font-weight': - // Opera will output bold as 700 - if (v == 700) - v = 'bold'; + self.parse = function(html) { + var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements, + shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp, + validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, + tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE; - break; + function processEndTag(name) { + var pos, i; - case 'color': - case 'background-color': - v = v.toLowerCase(); - break; + // Find position of parent of the same type + pos = stack.length; + while (pos--) { + if (stack[pos].name === name) + break; + } + + // Found parent + if (pos >= 0) { + // Close all the open elements + for (i = stack.length - 1; i >= pos; i--) { + name = stack[i]; + + if (name.valid) + self.end(name.name); } - s += (s ? ' ' : '') + k + ': ' + v + ';'; + // Remove the open elements from the stack + stack.length = pos; } }; - // Validate style output - if (name && t._styles) { - each(t._styles['*'], function(name) { - add(o[name], name); - }); + // Precompile RegExps and map objects + tokenRegExp = new RegExp('<(?:' + + '(?:!--([\\w\\W]*?)-->)|' + // Comment + '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA + '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE + '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI + '(?:\\/([^>]+)>)|' + // End element + '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element + ')', 'g'); + + attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g; + specialElements = { + 'script' : /<\/script[^>]*>/gi, + 'style' : /<\/style[^>]*>/gi, + 'noscript' : /<\/noscript[^>]*>/gi + }; - each(t._styles[name.toLowerCase()], function(name) { - add(o[name], name); - }); - } else - each(o, add); + // Setup lookup tables for empty elements and boolean attributes + shortEndedElements = schema.getShortEndedElements(); + selfClosing = schema.getSelfClosingElements(); + fillAttrsMap = schema.getBoolAttrs(); + validate = settings.validate; + removeInternalElements = settings.remove_internals; + fixSelfClosing = settings.fix_self_closing; + isIE = tinymce.isIE; + invalidPrefixRegExp = /^:/; + + while (matches = tokenRegExp.exec(html)) { + // Text + if (index < matches.index) + self.text(decode(html.substr(index, matches.index - index))); + + if (value = matches[6]) { // End element + value = value.toLowerCase(); + + // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements + if (isIE && invalidPrefixRegExp.test(value)) + value = value.substr(1); + + processEndTag(value); + } else if (value = matches[7]) { // Start element + value = value.toLowerCase(); + + // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements + if (isIE && invalidPrefixRegExp.test(value)) + value = value.substr(1); + + isShortEnded = value in shortEndedElements; + + // Is self closing tag for example an
  • after an open
  • + if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value) + processEndTag(value); + + // Validate element + if (!validate || (elementRule = schema.getElementRule(value))) { + isValidElement = true; + + // Grab attributes map and patters when validation is enabled + if (validate) { + validAttributesMap = elementRule.attributes; + validAttributePatterns = elementRule.attributePatterns; + } - return s; - }, + // Parse attributes + if (attribsValue = matches[8]) { + isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element - loadCSS : function(u) { - var t = this, d = t.doc, head; + // If the element has internal attributes then remove it if we are told to do so + if (isInternalElement && removeInternalElements) + isValidElement = false; - if (!u) - u = ''; + attrList = []; + attrList.map = {}; - head = t.select('head')[0]; + attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) { + var attrRule, i; - each(u.split(','), function(u) { - var link; + name = name.toLowerCase(); + value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute - if (t.files[u]) - return; + // Validate name and value + if (validate && !isInternalElement && name.indexOf('data-') !== 0) { + attrRule = validAttributesMap[name]; - t.files[u] = true; - link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); + // Find rule by pattern matching + if (!attrRule && validAttributePatterns) { + i = validAttributePatterns.length; + while (i--) { + attrRule = validAttributePatterns[i]; + if (attrRule.pattern.test(name)) + break; + } - // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug - // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading - // It's ugly but it seems to work fine. - if (isIE && d.documentMode && d.recalc) { - link.onload = function() { - d.recalc(); - link.onload = null; - }; - } + // No rule matched + if (i === -1) + attrRule = null; + } - head.appendChild(link); - }); - }, + // No attribute rule found + if (!attrRule) + return; - addClass : function(e, c) { - return this.run(e, function(e) { - var o; + // Validate value + if (attrRule.validValues && !(value in attrRule.validValues)) + return; + } - if (!c) - return 0; + // Add attribute to list and map + attrList.map[name] = value; + attrList.push({ + name: name, + value: value + }); + }); + } else { + attrList = []; + attrList.map = {}; + } - if (this.hasClass(e, c)) - return e.className; + // Process attributes if validation is enabled + if (validate && !isInternalElement) { + attributesRequired = elementRule.attributesRequired; + attributesDefault = elementRule.attributesDefault; + attributesForced = elementRule.attributesForced; + + // Handle forced attributes + if (attributesForced) { + i = attributesForced.length; + while (i--) { + attr = attributesForced[i]; + name = attr.name; + attrValue = attr.value; + + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; + + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } - o = this.removeClass(e, c); + // Handle default attributes + if (attributesDefault) { + i = attributesDefault.length; + while (i--) { + attr = attributesDefault[i]; + name = attr.name; - return e.className = (o != '' ? (o + ' ') : '') + c; - }); - }, + if (!(name in attrList.map)) { + attrValue = attr.value; - removeClass : function(e, c) { - var t = this, re; + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; - return t.run(e, function(e) { - var v; + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + } - if (t.hasClass(e, c)) { - if (!re) - re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); + // Handle required attributes + if (attributesRequired) { + i = attributesRequired.length; + while (i--) { + if (attributesRequired[i] in attrList.map) + break; + } - v = e.className.replace(re, ' '); - v = tinymce.trim(v != ' ' ? v : ''); + // None of the required attributes where found + if (i === -1) + isValidElement = false; + } - e.className = v; + // Invalidate element if it's marked as bogus + if (attrList.map['data-mce-bogus']) + isValidElement = false; + } - // Empty class attr - if (!v) { - e.removeAttribute('class'); - e.removeAttribute('className'); - } + if (isValidElement) + self.start(value, attrList, isShortEnded); + } else + isValidElement = false; - return v; - } + // Treat script, noscript and style a bit different since they may include code that looks like elements + if (endRegExp = specialElements[value]) { + endRegExp.lastIndex = index = matches.index + matches[0].length; - return e.className; - }); - }, + if (matches = endRegExp.exec(html)) { + if (isValidElement) + text = html.substr(index, matches.index - index); - hasClass : function(n, c) { - n = this.get(n); - - if (!n || !c) - return false; + index = matches.index + matches[0].length; + } else { + text = html.substr(index); + index = html.length; + } - return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; - }, + if (isValidElement && text.length > 0) + self.text(text, true); - show : function(e) { - return this.setStyle(e, 'display', 'block'); - }, + if (isValidElement) + self.end(value); - hide : function(e) { - return this.setStyle(e, 'display', 'none'); - }, + tokenRegExp.lastIndex = index; + continue; + } - isHidden : function(e) { - e = this.get(e); + // Push value on to stack + if (!isShortEnded) { + if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1) + stack.push({name: value, valid: isValidElement}); + else if (isValidElement) + self.end(value); + } + } else if (value = matches[1]) { // Comment + self.comment(value); + } else if (value = matches[2]) { // CDATA + self.cdata(value); + } else if (value = matches[3]) { // DOCTYPE + self.doctype(value); + } else if (value = matches[4]) { // PI + self.pi(value, matches[5]); + } - return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; - }, + index = matches.index + matches[0].length; + } - uniqueId : function(p) { - return (!p ? 'mce_' : p) + (this.counter++); - }, + // Text + if (index < html.length) + self.text(decode(html.substr(index))); - setHTML : function(e, h) { - var t = this; + // Close any open elements + for (i = stack.length - 1; i >= 0; i--) { + value = stack[i]; - return this.run(e, function(e) { - var x, i, nl, n, p, x; + if (value.valid) + self.end(value.name); + } + }; + } +})(tinymce); - h = t.processHTML(h); +(function(tinymce) { + var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { + '#text' : 3, + '#comment' : 8, + '#cdata' : 4, + '#pi' : 7, + '#doctype' : 10, + '#document-fragment' : 11 + }; - if (isIE) { - function set() { - // Remove all child nodes - while (e.firstChild) - e.firstChild.removeNode(); + // Walks the tree left/right + function walk(node, root_node, prev) { + var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; - try { - // IE will remove comments from the beginning - // unless you padd the contents with something - e.innerHTML = '
    ' + h; - e.removeChild(e.firstChild); - } catch (ex) { - // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p - // This seems to fix this problem - - // Create new div with HTML contents and a BR infront to keep comments - x = t.create('div'); - x.innerHTML = '
    ' + h; - - // Add all children from div to target - each (x.childNodes, function(n, i) { - // Skip br element - if (i) - e.appendChild(n); - }); - } - }; + // Walk into nodes if it has a start + if (node[startName]) + return node[startName]; - // IE has a serious bug when it comes to paragraphs it can produce an invalid - // DOM tree if contents like this

    • Item 1

    is inserted - // It seems to be that IE doesn't like a root block element placed inside another root block element - if (t.settings.fix_ie_paragraphs) - h = h.replace(/

    <\/p>|]+)><\/p>|/gi, ' 

    '); + // Return the sibling if it has one + if (node !== root_node) { + sibling = node[siblingName]; - set(); + if (sibling) + return sibling; - if (t.settings.fix_ie_paragraphs) { - // Check for odd paragraphs this is a sign of a broken DOM - nl = e.getElementsByTagName("p"); - for (i = nl.length - 1, x = 0; i >= 0; i--) { - n = nl[i]; + // Walk up the parents to look for siblings + for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { + sibling = parent[siblingName]; - if (!n.hasChildNodes()) { - if (!n._mce_keep) { - x = 1; // Is broken - break; - } + if (sibling) + return sibling; + } + } + }; - n.removeAttribute('_mce_keep'); - } - } - } + function Node(name, type) { + this.name = name; + this.type = type; - // Time to fix the madness IE left us - if (x) { - // So if we replace the p elements with divs and mark them and then replace them back to paragraphs - // after we use innerHTML we can fix the DOM tree - h = h.replace(/

    ]+)>|

    /ig, '

    '); - h = h.replace(/<\/p>/gi, '
    '); + if (type === 1) { + this.attributes = []; + this.attributes.map = {}; + } + } - // Set the new HTML with DIVs - set(); + tinymce.extend(Node.prototype, { + replace : function(node) { + var self = this; - // Replace all DIV elements with the _mce_tmp attibute back to paragraphs - // This is needed since IE has a annoying bug see above for details - // This is a slow process but it has to be done. :( - if (t.settings.fix_ie_paragraphs) { - nl = e.getElementsByTagName("DIV"); - for (i = nl.length - 1; i >= 0; i--) { - n = nl[i]; + if (node.parent) + node.remove(); - // Is it a temp div - if (n._mce_tmp) { - // Create new paragraph - p = t.doc.createElement('p'); + self.insert(node, self); + self.remove(); - // Copy all attributes - n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) { - var v; + return self; + }, - if (b !== '_mce_tmp') { - v = n.getAttribute(b); + attr : function(name, value) { + var self = this, attrs, i, undef; - if (!v && b === 'class') - v = n.className; + if (typeof name !== "string") { + for (i in name) + self.attr(i, name[i]); - p.setAttribute(b, v); - } - }); + return self; + } - // Append all children to new paragraph - for (x = 0; x]+)\/>|/gi, '', h); // Force open - - // Store away src and href in _mce_src and mce_href since browsers mess them up - if (s.keep_values) { - // Wrap scripts and styles in comments for serialization purposes - if (/)/g, '\n'); - s = s.replace(/^[\r\n]*|[\r\n]*$/g, ''); - s = s.replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g, ''); - - return s; - }; - - // Wrap the script contents in CDATA and keep them from executing - h = h.replace(/]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) { - // Force type attribute - if (!attribs) - attribs = ' type="text/javascript"'; - - // Convert the src attribute of the scripts - attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) { - if (s.url_converter) - url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script')); + clone : function() { + var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; - return '_mce_src="' + url + '"'; - }); + // Clone element attributes + if (selfAttrs = self.attributes) { + cloneAttrs = []; + cloneAttrs.map = {}; - // Wrap text contents - if (tinymce.trim(text)) { - codeBlocks.push(trim(text)); - text = ''; - } + for (i = 0, l = selfAttrs.length; i < l; i++) { + selfAttr = selfAttrs[i]; - return '' + text + ''; - }); + // Clone everything except id + if (selfAttr.name !== 'id') { + cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; + cloneAttrs.map[selfAttr.name] = selfAttr.value; + } + } - // Wrap style elements - h = h.replace(/]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) { - // Wrap text contents - if (text) { - codeBlocks.push(trim(text)); - text = ''; - } + clone.attributes = cloneAttrs; + } - return '' + text + ''; - }); + clone.value = self.value; + clone.shortEnded = self.shortEnded; - // Wrap noscript elements - h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { - return ''; - }); - } + return clone; + }, - h = tinymce._replace(//g, '', h); + wrap : function(wrapper) { + var self = this; - // This function processes the attributes in the HTML string to force boolean - // attributes to the attr="attr" format and convert style, src and href to _mce_ versions - function processTags(html) { - return html.replace(tagRegExp, function(match, elm_name, attrs, end) { - return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) { - var mceValue; + self.parent.insert(wrapper, self); + wrapper.append(self); - name = name.toLowerCase(); - value = value || val2 || val3 || ""; + return self; + }, - // Treat boolean attributes - if (boolAttrs[name]) { - // false or 0 is treated as a missing attribute - if (value === 'false' || value === '0') - return; + unwrap : function() { + var self = this, node, next; - return name + '="' + name + '"'; - } + for (node = self.firstChild; node; ) { + next = node.next; + self.insert(node, self, true); + node = next; + } - // Is attribute one that needs special treatment - if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) { - mceValue = t.decode(value); + self.remove(); + }, - // Convert URLs to relative/absolute ones - if (s.url_converter && (name == "src" || name == "href")) - mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name); + remove : function() { + var self = this, parent = self.parent, next = self.next, prev = self.prev; - // Process styles lowercases them and compresses them - if (name == 'style') - mceValue = t.serializeStyle(t.parseStyle(mceValue), name); + if (parent) { + if (parent.firstChild === self) { + parent.firstChild = next; - return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"'; - } + if (next) + next.prev = null; + } else { + prev.next = next; + } - return match; - }) + end + '>'; - }); - }; + if (parent.lastChild === self) { + parent.lastChild = prev; - h = processTags(h); + if (prev) + prev.next = null; + } else { + next.prev = prev; + } - // Restore script blocks - h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) { - return codeBlocks[idx]; - }); + self.parent = self.next = self.prev = null; } - return h; + return self; }, - getOuterHTML : function(e) { - var d; - - e = this.get(e); + append : function(node) { + var self = this, last; - if (!e) - return null; + if (node.parent) + node.remove(); - if (e.outerHTML !== undefined) - return e.outerHTML; + last = self.lastChild; + if (last) { + last.next = node; + node.prev = last; + self.lastChild = node; + } else + self.lastChild = self.firstChild = node; - d = (e.ownerDocument || this.doc).createElement("body"); - d.appendChild(e.cloneNode(true)); + node.parent = self; - return d.innerHTML; + return node; }, - setOuterHTML : function(e, h, d) { - var t = this; + insert : function(node, ref_node, before) { + var parent; - function setHTML(e, h, d) { - var n, tp; + if (node.parent) + node.remove(); - tp = d.createElement("body"); - tp.innerHTML = h; + parent = ref_node.parent || this; - n = tp.lastChild; - while (n) { - t.insertAfter(n.cloneNode(true), e); - n = n.previousSibling; - } + if (before) { + if (ref_node === parent.firstChild) + parent.firstChild = node; + else + ref_node.prev.next = node; - t.remove(e); - }; + node.prev = ref_node.prev; + node.next = ref_node; + ref_node.prev = node; + } else { + if (ref_node === parent.lastChild) + parent.lastChild = node; + else + ref_node.next.prev = node; - return this.run(e, function(e) { - e = t.get(e); + node.next = ref_node.next; + node.prev = ref_node; + ref_node.next = node; + } - // Only set HTML on elements - if (e.nodeType == 1) { - d = d || e.ownerDocument || t.doc; + node.parent = parent; - if (isIE) { - try { - // Try outerHTML for IE it sometimes produces an unknown runtime error - if (isIE && e.nodeType == 1) - e.outerHTML = h; - else - setHTML(e, h, d); - } catch (ex) { - // Fix for unknown runtime error - setHTML(e, h, d); - } - } else - setHTML(e, h, d); - } - }); + return node; }, - decode : function(s) { - var e, n, v; - - // Look for entities to decode - if (/&[\w#]+;/.test(s)) { - // Decode the entities using a div element not super efficient but less code - e = this.doc.createElement("div"); - e.innerHTML = s; - n = e.firstChild; - v = ''; - - if (n) { - do { - v += n.nodeValue; - } while (n = n.nextSibling); - } + getAll : function(name) { + var self = this, node, collection = []; - return v || s; + for (node = self.firstChild; node; node = walk(node, self)) { + if (node.name === name) + collection.push(node); } - return s; + return collection; }, - encode : function(str) { - return ('' + str).replace(encodeCharsRe, function(chr) { - return encodedChars[chr]; - }); - }, + empty : function() { + var self = this, nodes, i, node; - insertAfter : function(node, reference_node) { - reference_node = this.get(reference_node); + // Remove all children + if (self.firstChild) { + nodes = []; - return this.run(node, function(node) { - var parent, nextSibling; + // Collect the children + for (node = self.firstChild; node; node = walk(node, self)) + nodes.push(node); - parent = reference_node.parentNode; - nextSibling = reference_node.nextSibling; + // Remove the children + i = nodes.length; + while (i--) { + node = nodes[i]; + node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; + } + } - if (nextSibling) - parent.insertBefore(node, nextSibling); - else - parent.appendChild(node); + self.firstChild = self.lastChild = null; - return node; - }); + return self; }, - isBlock : function(n) { - if (n.nodeType && n.nodeType !== 1) - return false; - - n = n.nodeName || n; + isEmpty : function(elements) { + var self = this, node = self.firstChild, i, name; - return blockRe.test(n); - }, + if (node) { + do { + if (node.type === 1) { + // Ignore bogus elements + if (node.attributes.map['data-mce-bogus']) + continue; - replace : function(n, o, k) { - var t = this; + // Keep empty elements like + if (elements[node.name]) + return false; - if (is(o, 'array')) - n = n.cloneNode(true); + // Keep elements with data attributes or name attribute like + i = node.attributes.length; + while (i--) { + name = node.attributes[i].name; + if (name === "name" || name.indexOf('data-') === 0) + return false; + } + } - return t.run(o, function(o) { - if (k) { - each(tinymce.grep(o.childNodes), function(c) { - n.appendChild(c); - }); - } + // Keep non whitespace text nodes + if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) + return false; + } while (node = walk(node, self)); + } - return o.parentNode.replaceChild(n, o); - }); + return true; }, - rename : function(elm, name) { - var t = this, newElm; + walk : function(prev) { + return walk(this, null, prev); + } + }); - if (elm.nodeName != name.toUpperCase()) { - // Rename block element - newElm = t.create(name); + tinymce.extend(Node, { + create : function(name, attrs) { + var node, attrName; - // Copy attribs to new block - each(t.getAttribs(elm), function(attr_node) { - t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); - }); + // Create node + node = new Node(name, typeLookup[name] || 1); - // Replace block - t.replace(newElm, elm, 1); + // Add attributes if needed + if (attrs) { + for (attrName in attrs) + node.attr(attrName, attrs[attrName]); } - return newElm || elm; - }, + return node; + } + }); - findCommonAncestor : function(a, b) { - var ps = a, pe; + tinymce.html.Node = Node; +})(tinymce); - while (ps) { - pe = b; +(function(tinymce) { + var Node = tinymce.html.Node; - while (pe && ps != pe) - pe = pe.parentNode; + tinymce.html.DomParser = function(settings, schema) { + var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; - if (ps == pe) - break; + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; + settings.root_name = settings.root_name || 'body'; + self.schema = schema = schema || new tinymce.html.Schema(); - ps = ps.parentNode; - } + function fixInvalidChildren(nodes) { + var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, + childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; - if (!ps && a.ownerDocument) - return a.ownerDocument.documentElement; + nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); + nonEmptyElements = schema.getNonEmptyElements(); - return ps; - }, + for (ni = 0; ni < nodes.length; ni++) { + node = nodes[ni]; - toHex : function(s) { - var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); + // Already removed + if (!node.parent) + continue; - function hex(s) { - s = parseInt(s).toString(16); + // Get list of all parent nodes until we find a valid parent to stick the child into + parents = [node]; + for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent) + parents.push(parent); - return s.length > 1 ? s : '0' + s; // 0 -> 00 - }; + // Found a suitable parent + if (parent && parents.length > 1) { + // Reverse the array since it makes looping easier + parents.reverse(); - if (c) { - s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); + // Clone the related parent and insert that after the moved node + newParent = currentNode = self.filterNode(parents[0].clone()); - return s; - } + // Start cloning and moving children on the left side of the target node + for (i = 0; i < parents.length - 1; i++) { + if (schema.isValidChild(currentNode.name, parents[i].name)) { + tempNode = self.filterNode(parents[i].clone()); + currentNode.append(tempNode); + } else + tempNode = currentNode; - return s; - }, + for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { + nextNode = childNode.next; + tempNode.append(childNode); + childNode = nextNode; + } - getClasses : function() { - var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; + currentNode = tempNode; + } - if (t.classes) - return t.classes; + if (!newParent.isEmpty(nonEmptyElements)) { + parent.insert(newParent, parents[0], true); + parent.insert(node, newParent); + } else { + parent.insert(node, parents[0], true); + } - function addClasses(s) { - // IE style imports - each(s.imports, function(r) { - addClasses(r); - }); - - each(s.cssRules || s.rules, function(r) { - // Real type or fake it on IE - switch (r.type || 1) { - // Rule - case 1: - if (r.selectorText) { - each(r.selectorText.split(','), function(v) { - v = v.replace(/^\s*|\s*$|^\s\./g, ""); + // Check if the element is empty by looking through it's contents and special treatment for


    + parent = parents[0]; + if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') { + parent.empty().remove(); + } + } else if (node.parent) { + // If it's an LI try to find a UL/OL for it or wrap it + if (node.name === 'li') { + sibling = node.prev; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.append(node); + continue; + } - // Is internal or it doesn't contain a class - if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) - return; + sibling = node.next; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.insert(node, sibling.firstChild, true); + continue; + } - // Remove everything but class name - ov = v; - v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); + node.wrap(self.filterNode(new Node('ul', 1))); + continue; + } - // Filter classes - if (f && !(v = f(v, ov))) - return; + // Try wrapping the element in a DIV + if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) { + node.wrap(self.filterNode(new Node('div', 1))); + } else { + // We failed wrapping it, then remove or unwrap it + if (node.name === 'style' || node.name === 'script') + node.empty().remove(); + else + node.unwrap(); + } + } + } + }; - if (!lo[v]) { - cl.push({'class' : v}); - lo[v] = 1; - } - }); - } - break; + self.filterNode = function(node) { + var i, name, list; - // Import - case 3: - addClasses(r.styleSheet); - break; - } - }); - }; + // Run element filters + if (name in nodeFilters) { + list = matchedNodes[name]; - try { - each(t.doc.styleSheets, addClasses); - } catch (ex) { - // Ignore + if (list) + list.push(node); + else + matchedNodes[name] = [node]; } - if (cl.length > 0) - t.classes = cl; + // Run attribute filters + i = attributeFilters.length; + while (i--) { + name = attributeFilters[i].name; - return cl; - }, + if (name in node.attributes.map) { + list = matchedAttributes[name]; - run : function(e, f, s) { - var t = this, o; + if (list) + list.push(node); + else + matchedAttributes[name] = [node]; + } + } - if (t.doc && typeof(e) === 'string') - e = t.get(e); + return node; + }; - if (!e) - return false; + self.addNodeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var list = nodeFilters[name]; - s = s || this; - if (!e.nodeType && (e.length || e.length === 0)) { - o = []; + if (!list) + nodeFilters[name] = list = []; - each(e, function(e, i) { - if (e) { - if (typeof(e) == 'string') - e = t.doc.getElementById(e); + list.push(callback); + }); + }; - o.push(f.call(s, e, i)); + self.addAttributeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var i; + + for (i = 0; i < attributeFilters.length; i++) { + if (attributeFilters[i].name === name) { + attributeFilters[i].callbacks.push(callback); + return; } - }); + } - return o; - } + attributeFilters.push({name: name, callbacks: [callback]}); + }); + }; - return f.call(s, e); - }, + self.parse = function(html, args) { + var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate, + blockElements, startWhiteSpaceRegExp, invalidChildren = [], + endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName; + + args = args || {}; + matchedNodes = {}; + matchedAttributes = {}; + blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements()); + nonEmptyElements = schema.getNonEmptyElements(); + children = schema.children; + validate = settings.validate; + rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block; + + whiteSpaceElements = schema.getWhiteSpaceElements(); + startWhiteSpaceRegExp = /^[ \t\r\n]+/; + endWhiteSpaceRegExp = /[ \t\r\n]+$/; + allWhiteSpaceRegExp = /[ \t\r\n]+/g; + + function addRootBlocks() { + var node = rootNode.firstChild, next, rootBlockNode; + + while (node) { + next = node.next; + + if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) { + if (!rootBlockNode) { + // Create a new root block element + rootBlockNode = createNode(rootBlockName, 1); + rootNode.insert(rootBlockNode, node); + rootBlockNode.append(node); + } else + rootBlockNode.append(node); + } else { + rootBlockNode = null; + } - getAttribs : function(n) { - var o; + node = next; + }; + }; - n = this.get(n); + function createNode(name, type) { + var node = new Node(name, type), list; - if (!n) - return []; + if (name in nodeFilters) { + list = matchedNodes[name]; - if (isIE) { - o = []; + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } - // Object will throw exception in IE - if (n.nodeName == 'OBJECT') - return n.attributes; + return node; + }; - // IE doesn't keep the selected attribute if you clone option elements - if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) - o.push({specified : 1, nodeName : 'selected'}); + function removeWhitespaceBefore(node) { + var textNode, textVal, sibling; - // 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(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { - o.push({specified : 1, nodeName : a}); - }); + for (textNode = node.prev; textNode && textNode.type === 3; ) { + textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); - return o; - } + if (textVal.length > 0) { + textNode.value = textVal; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + }; - return n.attributes; - }, + parser = new tinymce.html.SaxParser({ + validate : validate, + fix_self_closing : !validate, // Let the DOM parser handle
  • in
  • or

    in

    for better results - destroy : function(s) { - var t = this; + cdata: function(text) { + node.append(createNode('#cdata', 4)).value = text; + }, - if (t.events) - t.events.destroy(); + text: function(text, raw) { + var textNode; - t.win = t.doc = t.root = t.events = null; + // Trim all redundant whitespace on non white space elements + if (!whiteSpaceElements[node.name]) { + text = text.replace(allWhiteSpaceRegExp, ' '); - // Manual destroy then remove unload handler - if (!s) - tinymce.removeUnload(t.destroy); - }, + if (node.lastChild && blockElements[node.lastChild.name]) + text = text.replace(startWhiteSpaceRegExp, ''); + } - createRng : function() { - var d = this.doc; + // Do we need to create the node + if (text.length !== 0) { + textNode = createNode('#text', 3); + textNode.raw = !!raw; + node.append(textNode).value = text; + } + }, - return d.createRange ? d.createRange() : new tinymce.dom.Range(this); - }, + comment: function(text) { + node.append(createNode('#comment', 8)).value = text; + }, - nodeIndex : function(node, normalized) { - var idx = 0, lastNodeType, lastNode, nodeType; + pi: function(name, text) { + node.append(createNode(name, 7)).value = text; + removeWhitespaceBefore(node); + }, - if (node) { - for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { - nodeType = node.nodeType; + doctype: function(text) { + var newNode; + + newNode = node.append(createNode('#doctype', 10)); + newNode.value = text; + removeWhitespaceBefore(node); + }, - // Normalize text nodes - if (normalized && nodeType == 3) { - if (nodeType == lastNodeType || !node.nodeValue.length) - continue; - } + start: function(name, attrs, empty) { + var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; - idx++; - lastNodeType = nodeType; - } - } + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + newNode = createNode(elementRule.outputName || name, 1); + newNode.attributes = attrs; + newNode.shortEnded = empty; - return idx; - }, + node.append(newNode); - split : function(pe, e, re) { - var t = this, r = t.createRng(), bef, aft, pa; + // Check if node is valid child of the parent node is the child is + // unknown we don't collect it since it's probably a custom element + parent = children[node.name]; + if (parent && children[newNode.name] && !parent[newNode.name]) + invalidChildren.push(newNode); - // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense - // but we don't want that in our code since it serves no purpose for the end user - // For example if this is chopped: - //

    text 1CHOPtext 2

    - // would produce: - //

    text 1

    CHOP

    text 2

    - // this function will then trim of empty edges and produce: - //

    text 1

    CHOP

    text 2

    - function trim(node) { - var i, children = node.childNodes; + attrFiltersLen = attributeFilters.length; + while (attrFiltersLen--) { + attrName = attributeFilters[attrFiltersLen].name; - if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark') - return; + if (attrName in attrs.map) { + list = matchedAttributes[attrName]; - for (i = children.length - 1; i >= 0; i--) - trim(children[i]); + if (list) + list.push(newNode); + else + matchedAttributes[attrName] = [newNode]; + } + } - if (node.nodeType != 9) { - // Keep non whitespace text nodes - if (node.nodeType == 3 && node.nodeValue.length > 0) { - // If parent element isn't a block or there isn't any useful contents for example "

    " - if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0) - return; + // Trim whitespace before block + if (blockElements[name]) + removeWhitespaceBefore(newNode); + + // Change current node if the element wasn't empty i.e not
    or + if (!empty) + node = newNode; } + }, - if (node.nodeType == 1) { - // If the only child is a bookmark then move it up - children = node.childNodes; - if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark') - node.parentNode.insertBefore(children[0], node); + end: function(name) { + var textNode, elementRule, text, sibling, tempNode; - // Keep non empty elements or img, hr etc - if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) - return; - } + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + if (blockElements[name]) { + if (!whiteSpaceElements[node.name]) { + // Trim whitespace at beginning of block + for (textNode = node.firstChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); - t.remove(node); - } + if (text.length > 0) { + textNode.value = text; + textNode = textNode.next; + } else { + sibling = textNode.next; + textNode.remove(); + textNode = sibling; + } + } - return node; - }; + // Trim whitespace at end of block + for (textNode = node.lastChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(endWhiteSpaceRegExp, ''); - if (pe && e) { - // Get before chunk - r.setStart(pe.parentNode, t.nodeIndex(pe)); - r.setEnd(e.parentNode, t.nodeIndex(e)); - bef = r.extractContents(); + if (text.length > 0) { + textNode.value = text; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + } - // Get after chunk - r = t.createRng(); - r.setStart(e.parentNode, t.nodeIndex(e) + 1); - r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); - aft = r.extractContents(); + // Trim start white space + textNode = node.prev; + if (textNode && textNode.type === 3) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); - // Insert before chunk - pa = pe.parentNode; - pa.insertBefore(trim(bef), pe); + if (text.length > 0) + textNode.value = text; + else + textNode.remove(); + } + } - // Insert middle chunk - if (re) - pa.replaceChild(re, e); - else - pa.insertBefore(e, pe); + // Handle empty nodes + if (elementRule.removeEmpty || elementRule.paddEmpty) { + if (node.isEmpty(nonEmptyElements)) { + if (elementRule.paddEmpty) + node.empty().append(new Node('#text', '3')).value = '\u00a0'; + else { + // Leave nodes that have a name like + if (!node.attributes.map.name) { + tempNode = node.parent; + node.empty().remove(); + node = tempNode; + return; + } + } + } + } - // Insert after chunk - pa.insertBefore(trim(aft), pe); - t.remove(pe); + node = node.parent; + } + } + }, schema); - return re || e; + rootNode = node = new Node(args.context || settings.root_name, 11); + + parser.parse(html); + + // Fix invalid children or report invalid children in a contextual parsing + if (validate && invalidChildren.length) { + if (!args.context) + fixInvalidChildren(invalidChildren); + else + args.invalid = true; } - }, - bind : function(target, name, func, scope) { - var t = this; + // Wrap nodes in the root into block elements if the root is body + if (rootBlockName && rootNode.name == 'body') + addRootBlocks(); - if (!t.events) - t.events = new tinymce.dom.EventUtils(); + // Run filters only when the contents is valid + if (!args.invalid) { + // Run node filters + for (name in matchedNodes) { + list = nodeFilters[name]; + nodes = matchedNodes[name]; - return t.events.add(target, name, func, scope || this); - }, + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } - unbind : function(target, name, func) { - var t = this; + for (i = 0, l = list.length; i < l; i++) + list[i](nodes, name, args); + } - if (!t.events) - t.events = new tinymce.dom.EventUtils(); + // Run attribute filters + for (i = 0, l = attributeFilters.length; i < l; i++) { + list = attributeFilters[i]; - return t.events.remove(target, name, func); - }, + if (list.name in matchedAttributes) { + nodes = matchedAttributes[list.name]; + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } - _findSib : function(node, selector, name) { - var t = this, f = selector; + for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) + list.callbacks[fi](nodes, list.name, args); + } + } + } - if (node) { - // If expression make a function of it using is - if (is(f, 'string')) { - f = function(node) { - return t.is(node, selector); - }; - } + return rootNode; + }; - // Loop all siblings - for (node = node[name]; node; node = node[name]) { - if (f(node)) - return node; - } - } + // Remove
    at end of block elements Gecko and WebKit injects BR elements to + // make it possible to place the caret inside empty blocks. This logic tries to remove + // these elements and keep br elements that where intended to be there intact + if (settings.remove_trailing_brs) { + self.addNodeFilter('br', function(nodes, name) { + var i, l = nodes.length, node, blockElements = schema.getBlockElements(), + nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName; + + // Remove brs from body element as well + blockElements.body = 1; + + // Must loop forwards since it will otherwise remove all brs in

    a


    + for (i = 0; i < l; i++) { + node = nodes[i]; + parent = node.parent; + + if (blockElements[node.parent.name] && node === parent.lastChild) { + // Loop all nodes to the right of the current node and check for other BR elements + // excluding bookmarks since they are invisible + prev = node.prev; + while (prev) { + prevName = prev.name; + + // Ignore bookmarks + if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') { + // Found a non BR element + if (prevName !== "br") + break; + + // Found another br it's a

    structure then don't remove anything + if (prevName === 'br') { + node = null; + break; + } + } - return null; - }, + prev = prev.prev; + } - _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); + if (node) { + node.remove(); + + // Is the parent to be considered empty after we removed the BR + if (parent.isEmpty(nonEmptyElements)) { + elementRule = schema.getElementRule(parent.name); + + // Remove or padd the element depending on schema rule + if (elementRule) { + if (elementRule.removeEmpty) + parent.remove(); + else if (elementRule.paddEmpty) + parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; + } + } + } + } + } + }); } + } +})(tinymce); - /* - walk : function(n, f, s) { - var d = this.doc, w; +tinymce.html.Writer = function(settings) { + var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; - if (d.createTreeWalker) { - w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + settings = settings || {}; + indent = settings.indent; + indentBefore = tinymce.makeMap(settings.indent_before || ''); + indentAfter = tinymce.makeMap(settings.indent_after || ''); + encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities); + htmlOutput = settings.element_format == "html"; - while ((n = w.nextNode()) != null) - f.call(s || this, n); - } else - tinymce.walk(n, f, 'childNodes', s); - } - */ + return { + start: function(name, attrs, empty) { + var i, l, attr, value; - /* - toRGB : function(s) { - var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); + if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; - if (c) { - // #FFF -> #FFFFFF - if (!is(c[3])) - c[3] = c[2] = c[1]; + if (value.length > 0 && value !== '\n') + html.push('\n'); + } - return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; + html.push('<', name); + + if (attrs) { + for (i = 0, l = attrs.length; i < l; i++) { + attr = attrs[i]; + html.push(' ', attr.name, '="', encode(attr.value, true), '"'); + } } - return s; - } - */ - }); + if (!empty || htmlOutput) + html[html.length] = '>'; + else + html[html.length] = ' />'; - tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); -})(tinymce); + if (empty && indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; -(function(ns) { - // Range constructor - function Range(dom) { - var t = this, - doc = dom.doc, - EXTRACT = 0, - CLONE = 1, - DELETE = 2, - TRUE = true, - FALSE = false, - START_OFFSET = 'startOffset', - START_CONTAINER = 'startContainer', - END_CONTAINER = 'endContainer', - END_OFFSET = 'endOffset', - extend = tinymce.extend, - nodeIndex = dom.nodeIndex; + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, - extend(t, { - // Inital states - startContainer : doc, - startOffset : 0, - endContainer : doc, - endOffset : 0, - collapsed : TRUE, - commonAncestorContainer : doc, + end: function(name) { + var value; - // Range constants - START_TO_START : 0, - START_TO_END : 1, - END_TO_END : 2, - END_TO_START : 3, + /*if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; - // Public methods - setStart : setStart, - setEnd : setEnd, - setStartBefore : setStartBefore, - setStartAfter : setStartAfter, - setEndBefore : setEndBefore, - setEndAfter : setEndAfter, - collapse : collapse, - selectNode : selectNode, - selectNodeContents : selectNodeContents, - compareBoundaryPoints : compareBoundaryPoints, - deleteContents : deleteContents, - extractContents : extractContents, - cloneContents : cloneContents, - insertNode : insertNode, - surroundContents : surroundContents, - cloneRange : cloneRange - }); + if (value.length > 0 && value !== '\n') + html.push('\n'); + }*/ - function setStart(n, o) { - _setEndPoint(TRUE, n, o); - }; + html.push(''); - function setEnd(n, o) { - _setEndPoint(FALSE, n, o); - }; + if (indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; - function setStartBefore(n) { - setStart(n.parentNode, nodeIndex(n)); - }; + if (value.length > 0 && value !== '\n') + html.push('\n'); + } + }, - function setStartAfter(n) { - setStart(n.parentNode, nodeIndex(n) + 1); - }; + text: function(text, raw) { + if (text.length > 0) + html[html.length] = raw ? text : encode(text); + }, - function setEndBefore(n) { - setEnd(n.parentNode, nodeIndex(n)); - }; + cdata: function(text) { + html.push(''); + }, - function setEndAfter(n) { - setEnd(n.parentNode, nodeIndex(n) + 1); - }; + comment: function(text) { + html.push(''); + }, - function collapse(ts) { - if (ts) { - t[END_CONTAINER] = t[START_CONTAINER]; - t[END_OFFSET] = t[START_OFFSET]; - } else { - t[START_CONTAINER] = t[END_CONTAINER]; - t[START_OFFSET] = t[END_OFFSET]; - } + pi: function(name, text) { + if (text) + html.push(''); + else + html.push(''); - t.collapsed = TRUE; - }; + if (indent) + html.push('\n'); + }, - function selectNode(n) { - setStartBefore(n); - setEndAfter(n); - }; + doctype: function(text) { + html.push('', indent ? '\n' : ''); + }, - function selectNodeContents(n) { - setStart(n, 0); - setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); - }; + reset: function() { + html.length = 0; + }, - function compareBoundaryPoints(h, r) { - var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET]; + getContent: function() { + return html.join('').replace(/\n$/, ''); + } + }; +}; - // Check START_TO_START - if (h === 0) - return _compareBoundaryPoints(sc, so, sc, so); +(function(tinymce) { + tinymce.html.Serializer = function(settings, schema) { + var self = this, writer = new tinymce.html.Writer(settings); - // Check START_TO_END - if (h === 1) - return _compareBoundaryPoints(sc, so, ec, eo); + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; - // Check END_TO_END - if (h === 2) - return _compareBoundaryPoints(ec, eo, ec, eo); + self.schema = schema = schema || new tinymce.html.Schema(); + self.writer = writer; - // Check END_TO_START - if (h === 3) - return _compareBoundaryPoints(ec, eo, sc, so); - }; + self.serialize = function(node) { + var handlers, validate; - function deleteContents() { - _traverse(DELETE); - }; + validate = settings.validate; - function extractContents() { - return _traverse(EXTRACT); - }; + handlers = { + // #text + 3: function(node, raw) { + writer.text(node.value, node.raw); + }, - function cloneContents() { - return _traverse(CLONE); - }; + // #comment + 8: function(node) { + writer.comment(node.value); + }, - function insertNode(n) { - var startContainer = this[START_CONTAINER], - startOffset = this[START_OFFSET], nn, o; + // Processing instruction + 7: function(node) { + writer.pi(node.name, node.value); + }, - // Node is TEXT_NODE or CDATA - if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { - if (!startOffset) { - // At the start of text - startContainer.parentNode.insertBefore(n, startContainer); - } else if (startOffset >= startContainer.nodeValue.length) { - // At the end of text - dom.insertAfter(n, startContainer); - } else { - // Middle, need to split - nn = startContainer.splitText(startOffset); - startContainer.parentNode.insertBefore(n, nn); + // Doctype + 10: function(node) { + writer.doctype(node.value); + }, + + // CDATA + 4: function(node) { + writer.cdata(node.value); + }, + + // Document fragment + 11: function(node) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } } - } else { - // Insert element node - if (startContainer.childNodes.length > 0) - o = startContainer.childNodes[startOffset]; + }; - if (o) - startContainer.insertBefore(n, o); - else - startContainer.appendChild(n); - } - }; + writer.reset(); - function surroundContents(n) { - var f = t.extractContents(); + function walk(node) { + var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; - t.insertNode(n); - n.appendChild(f); - t.selectNode(n); - }; + if (!handler) { + name = node.name; + isEmpty = node.shortEnded; + attrs = node.attributes; - function cloneRange() { - return extend(new Range(dom), { - startContainer : t[START_CONTAINER], - startOffset : t[START_OFFSET], - endContainer : t[END_CONTAINER], - endOffset : t[END_OFFSET], - collapsed : t.collapsed, - commonAncestorContainer : t.commonAncestorContainer - }); - }; + // Sort attributes + if (validate && attrs && attrs.length > 1) { + sortedAttrs = []; + sortedAttrs.map = {}; - // Private methods + elementRule = schema.getElementRule(node.name); + for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { + attrName = elementRule.attributesOrder[i]; - function _getSelectedNode(container, offset) { - var child; + if (attrName in attrs.map) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } - if (container.nodeType == 3 /* TEXT_NODE */) - return container; + for (i = 0, l = attrs.length; i < l; i++) { + attrName = attrs[i].name; - if (offset < 0) - return container; + if (!(attrName in sortedAttrs.map)) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } - child = container.firstChild; - while (child && offset > 0) { - --offset; - child = child.nextSibling; - } + attrs = sortedAttrs; + } - if (child) - return child; + writer.start(node.name, attrs, isEmpty); - return container; - }; + if (!isEmpty) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } - function _isCollapsed() { - return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); - }; + writer.end(name); + } + } else + handler(node); + } - function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { - var c, offsetC, n, cmnRoot, childA, childB; + // Serialize element and treat all non elements as fragments + if (node.type == 1 && !settings.inner) + walk(node); + else + handlers[11](node); - // In the first case the boundary-points have the same container. A is before B - // if its offset is less than the offset of B, A is equal to B if its offset is - // equal to the offset of B, and A is after B if its offset is greater than the - // offset of B. - if (containerA == containerB) { - if (offsetA == offsetB) - return 0; // equal - - if (offsetA < offsetB) - return -1; // before + return writer.getContent(); + }; + } +})(tinymce); - return 1; // after - } +(function(tinymce) { + // Shorten names + var each = tinymce.each, + is = tinymce.is, + isWebKit = tinymce.isWebKit, + isIE = tinymce.isIE, + Entities = tinymce.html.Entities, + simpleSelectorRe = /^([a-z0-9],?)+$/i, + blockElementsMap = tinymce.html.Schema.blockElementsMap, + whiteSpaceRegExp = /^[ \t\r\n]*$/; - // In the second case a child node C of the container of A is an ancestor - // container of B. In this case, A is before B if the offset of A is less than or - // equal to the index of the child node C and A is after B otherwise. - c = containerB; - while (c && c.parentNode != containerA) - c = c.parentNode; + tinymce.create('tinymce.dom.DOMUtils', { + doc : null, + root : null, + files : null, + pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/, + props : { + "for" : "htmlFor", + "class" : "className", + className : "className", + checked : "checked", + disabled : "disabled", + maxlength : "maxLength", + readonly : "readOnly", + selected : "selected", + value : "value", + id : "id", + name : "name", + type : "type" + }, - if (c) { - offsetC = 0; - n = containerA.firstChild; + DOMUtils : function(d, s) { + var t = this, globalStyle, name; - while (n != c && offsetC < offsetA) { - offsetC++; - n = n.nextSibling; - } + t.doc = d; + t.win = window; + t.files = {}; + t.cssFlicker = false; + t.counter = 0; + t.stdMode = !tinymce.isIE || d.documentMode >= 8; + t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode; + t.hasOuterHTML = "outerHTML" in d.createElement("a"); - if (offsetA <= offsetC) - return -1; // before + t.settings = s = tinymce.extend({ + keep_values : false, + hex_colors : 1 + }, s); + + t.schema = s.schema; + t.styles = new tinymce.html.Styles({ + url_converter : s.url_converter, + url_converter_scope : s.url_converter_scope + }, s.schema); - return 1; // after + // Fix IE6SP2 flicker and check it failed for pre SP2 + if (tinymce.isIE6) { + try { + d.execCommand('BackgroundImageCache', false, true); + } catch (e) { + t.cssFlicker = true; + } } - // In the third case a child node C of the container of B is an ancestor container - // of A. In this case, A is before B if the index of the child node C is less than - // the offset of B and A is after B otherwise. - c = containerA; - while (c && c.parentNode != containerB) { - c = c.parentNode; + if (isIE && s.schema) { + // Add missing HTML 4/5 elements to IE + ('abbr article aside audio canvas ' + + 'details figcaption figure footer ' + + 'header hgroup mark menu meter nav ' + + 'output progress section summary ' + + 'time video').replace(/\w+/g, function(name) { + d.createElement(name); + }); + + // Create all custom elements + for (name in s.schema.getCustomElements()) { + d.createElement(name); + } } - if (c) { - offsetC = 0; - n = containerB.firstChild; + tinymce.addUnload(t.destroy, t); + }, - while (n != c && offsetC < offsetB) { - offsetC++; - n = n.nextSibling; - } + getRoot : function() { + var t = this, s = t.settings; - if (offsetC < offsetB) - return -1; // before + return (s && t.get(s.root_element)) || t.doc.body; + }, - return 1; // after - } + getViewPort : function(w) { + var d, b; - // In the fourth case, none of three other cases hold: the containers of A and B - // are siblings or descendants of sibling nodes. In this case, A is before B if - // the container of A is before the container of B in a pre-order traversal of the - // Ranges' context tree and A is after B otherwise. - cmnRoot = dom.findCommonAncestor(containerA, containerB); - childA = containerA; + w = !w ? this.win : w; + d = w.document; + b = this.boxModel ? d.documentElement : d.body; - while (childA && childA.parentNode != cmnRoot) - childA = childA.parentNode; + // Returns viewport size excluding scrollbars + return { + x : w.pageXOffset || b.scrollLeft, + y : w.pageYOffset || b.scrollTop, + w : w.innerWidth || b.clientWidth, + h : w.innerHeight || b.clientHeight + }; + }, - if (!childA) - childA = cmnRoot; + getRect : function(e) { + var p, t = this, sr; - childB = containerB; - while (childB && childB.parentNode != cmnRoot) - childB = childB.parentNode; + e = t.get(e); + p = t.getPos(e); + sr = t.getSize(e); - if (!childB) - childB = cmnRoot; + return { + x : p.x, + y : p.y, + w : sr.w, + h : sr.h + }; + }, - if (childA == childB) - return 0; // equal + getSize : function(e) { + var t = this, w, h; - n = cmnRoot.firstChild; - while (n) { - if (n == childA) - return -1; // before + e = t.get(e); + w = t.getStyle(e, 'width'); + h = t.getStyle(e, 'height'); - if (n == childB) - return 1; // after + // Non pixel value, then force offset/clientWidth + if (w.indexOf('px') === -1) + w = 0; - n = n.nextSibling; - } - }; + // Non pixel value, then force offset/clientWidth + if (h.indexOf('px') === -1) + h = 0; - function _setEndPoint(st, n, o) { - var ec, sc; + return { + w : parseInt(w) || e.offsetWidth || e.clientWidth, + h : parseInt(h) || e.offsetHeight || e.clientHeight + }; + }, - if (st) { - t[START_CONTAINER] = n; - t[START_OFFSET] = o; - } else { - t[END_CONTAINER] = n; - t[END_OFFSET] = o; - } + getParent : function(n, f, r) { + return this.getParents(n, f, r, false); + }, - // If one boundary-point of a Range is set to have a root container - // other than the current one for the Range, the Range is collapsed to - // the new position. This enforces the restriction that both boundary- - // points of a Range must have the same root container. - ec = t[END_CONTAINER]; - while (ec.parentNode) - ec = ec.parentNode; + getParents : function(n, f, r, c) { + var t = this, na, se = t.settings, o = []; - sc = t[START_CONTAINER]; - while (sc.parentNode) - sc = sc.parentNode; + n = t.get(n); + c = c === undefined; - if (sc == ec) { - // The start position of a Range is guaranteed to never be after the - // end position. To enforce this restriction, if the start is set to - // be at a position after the end, the Range is collapsed to that - // position. - if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) - t.collapse(st); - } else - t.collapse(st); + if (se.strict_root) + r = r || t.getRoot(); - t.collapsed = _isCollapsed(); - t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); - }; + // Wrap node name as func + if (is(f, 'string')) { + na = f; - function _traverse(how) { - var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + if (f === '*') { + f = function(n) {return n.nodeType == 1;}; + } else { + f = function(n) { + return t.is(n, na); + }; + } + } - if (t[START_CONTAINER] == t[END_CONTAINER]) - return _traverseSameContainer(how); + while (n) { + if (n == r || !n.nodeType || n.nodeType === 9) + break; - for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { - if (p == t[START_CONTAINER]) - return _traverseCommonStartContainer(c, how); + if (!f || f(n)) { + if (c) + o.push(n); + else + return n; + } - ++endContainerDepth; + n = n.parentNode; } - for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { - if (p == t[END_CONTAINER]) - return _traverseCommonEndContainer(c, how); + return c ? o : null; + }, - ++startContainerDepth; + get : function(e) { + var n; + + if (e && this.doc && typeof(e) == 'string') { + n = e; + e = this.doc.getElementById(e); + + // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick + if (e && e.id !== n) + return this.doc.getElementsByName(n)[1]; } - depthDiff = startContainerDepth - endContainerDepth; + return e; + }, - startNode = t[START_CONTAINER]; - while (depthDiff > 0) { - startNode = startNode.parentNode; - depthDiff--; - } + getNext : function(node, selector) { + return this._findSib(node, selector, 'nextSibling'); + }, - endNode = t[END_CONTAINER]; - while (depthDiff < 0) { - endNode = endNode.parentNode; - depthDiff++; - } + getPrev : function(node, selector) { + return this._findSib(node, selector, 'previousSibling'); + }, - // ascend the ancestor hierarchy until we have a common parent. - for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { - startNode = sp; - endNode = ep; - } - return _traverseCommonAncestors(startNode, endNode, how); - }; + select : function(pa, s) { + var t = this; - function _traverseSameContainer(how) { - var frag, s, sub, n, cnt, sibling, xferNode; + return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); + }, - if (how != DELETE) - frag = doc.createDocumentFragment(); + is : function(n, selector) { + var i; - // If selection is empty, just return the fragment - if (t[START_OFFSET] == t[END_OFFSET]) - return frag; + // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance + if (n.length === undefined) { + // Simple all selector + if (selector === '*') + return n.nodeType == 1; - // Text node needs special case handling - if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { - // get the substring - s = t[START_CONTAINER].nodeValue; - sub = s.substring(t[START_OFFSET], t[END_OFFSET]); + // Simple selector just elements + if (simpleSelectorRe.test(selector)) { + selector = selector.toLowerCase().split(/,/); + n = n.nodeName.toLowerCase(); - // set the original text node to its new value - if (how != CLONE) { - t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]); + for (i = selector.length - 1; i >= 0; i--) { + if (selector[i] == n) + return true; + } - // Nothing is partially selected, so collapse to start point - t.collapse(TRUE); + return false; } + } - if (how == DELETE) - return; + return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; + }, - frag.appendChild(doc.createTextNode(sub)); - return frag; - } - // Copy nodes between the start/end offsets. - n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); - cnt = t[END_OFFSET] - t[START_OFFSET]; + add : function(p, n, a, h, c) { + var t = this; - while (cnt > 0) { - sibling = n.nextSibling; - xferNode = _traverseFullySelected(n, how); + return this.run(p, function(p) { + var e, k; - if (frag) - frag.appendChild( xferNode ); + e = is(n, 'string') ? t.doc.createElement(n) : n; + t.setAttribs(e, a); - --cnt; - n = sibling; - } + if (h) { + if (h.nodeType) + e.appendChild(h); + else + t.setHTML(e, h); + } - // Nothing is partially selected, so collapse to start point - if (how != CLONE) - t.collapse(TRUE); + return !c ? p.appendChild(e) : e; + }); + }, - return frag; - }; + create : function(n, a, h) { + return this.add(this.doc.createElement(n), n, a, h, 1); + }, - function _traverseCommonStartContainer(endAncestor, how) { - var frag, n, endIdx, cnt, sibling, xferNode; + createHTML : function(n, a, h) { + var o = '', t = this, k; - if (how != DELETE) - frag = doc.createDocumentFragment(); + o += '<' + n; - n = _traverseRightBoundary(endAncestor, how); + for (k in a) { + if (a.hasOwnProperty(k)) + o += ' ' + k + '="' + t.encode(a[k]) + '"'; + } - if (frag) - frag.appendChild(n); + // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime + if (typeof(h) != "undefined") + return o + '>' + h + ''; - endIdx = nodeIndex(endAncestor); - cnt = endIdx - t[START_OFFSET]; + return o + ' />'; + }, - if (cnt <= 0) { - // Collapse to just before the endAncestor, which - // is partially selected. - if (how != CLONE) { - t.setEndBefore(endAncestor); - t.collapse(FALSE); + remove : function(node, keep_children) { + return this.run(node, function(node) { + var child, parent = node.parentNode; + + if (!parent) + return null; + + if (keep_children) { + while (child = node.firstChild) { + // IE 8 will crash if you don't remove completely empty text nodes + if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue) + parent.insertBefore(child, node); + else + node.removeChild(child); + } } - return frag; - } + return parent.removeChild(node); + }); + }, - n = endAncestor.previousSibling; - while (cnt > 0) { - sibling = n.previousSibling; - xferNode = _traverseFullySelected(n, how); + setStyle : function(n, na, v) { + var t = this; - if (frag) - frag.insertBefore(xferNode, frag.firstChild); + return t.run(n, function(e) { + var s, i; - --cnt; - n = sibling; - } + s = e.style; - // Collapse to just before the endAncestor, which - // is partially selected. - if (how != CLONE) { - t.setEndBefore(endAncestor); - t.collapse(FALSE); - } + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); - return frag; - }; + // Default px suffix on these + if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) + v += 'px'; - function _traverseCommonEndContainer(startAncestor, how) { - var frag, startIdx, n, cnt, sibling, xferNode; + switch (na) { + case 'opacity': + // IE specific opacity + if (isIE) { + s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; - if (how != DELETE) - frag = doc.createDocumentFragment(); + if (!n.currentStyle || !n.currentStyle.hasLayout) + s.display = 'inline-block'; + } - n = _traverseLeftBoundary(startAncestor, how); - if (frag) - frag.appendChild(n); + // Fix for older browsers + s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; + break; - startIdx = nodeIndex(startAncestor); - ++startIdx; // Because we already traversed it.... + case 'float': + isIE ? s.styleFloat = v : s.cssFloat = v; + break; + + default: + s[na] = v || ''; + } - cnt = t[END_OFFSET] - startIdx; - n = startAncestor.nextSibling; - while (cnt > 0) { - sibling = n.nextSibling; - xferNode = _traverseFullySelected(n, how); + // Force update of the style data + if (t.settings.update_styles) + t.setAttrib(e, 'data-mce-style'); + }); + }, - if (frag) - frag.appendChild(xferNode); + getStyle : function(n, na, c) { + n = this.get(n); - --cnt; - n = sibling; - } + if (!n) + return; - if (how != CLONE) { - t.setStartAfter(startAncestor); - t.collapse(TRUE); + // Gecko + if (this.doc.defaultView && c) { + // Remove camelcase + na = na.replace(/[A-Z]/g, function(a){ + return '-' + a; + }); + + try { + return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); + } catch (ex) { + // Old safari might fail + return null; + } } - return frag; - }; + // Camelcase it, if needed + na = na.replace(/-(\D)/g, function(a, b){ + return b.toUpperCase(); + }); - function _traverseCommonAncestors(startAncestor, endAncestor, how) { - var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; + if (na == 'float') + na = isIE ? 'styleFloat' : 'cssFloat'; - if (how != DELETE) - frag = doc.createDocumentFragment(); + // IE & Opera + if (n.currentStyle && c) + return n.currentStyle[na]; - n = _traverseLeftBoundary(startAncestor, how); - if (frag) - frag.appendChild(n); + return n.style ? n.style[na] : undefined; + }, - commonParent = startAncestor.parentNode; - startOffset = nodeIndex(startAncestor); - endOffset = nodeIndex(endAncestor); - ++startOffset; + setStyles : function(e, o) { + var t = this, s = t.settings, ol; - cnt = endOffset - startOffset; - sibling = startAncestor.nextSibling; + ol = s.update_styles; + s.update_styles = 0; - while (cnt > 0) { - nextSibling = sibling.nextSibling; - n = _traverseFullySelected(sibling, how); + each(o, function(v, n) { + t.setStyle(e, n, v); + }); - if (frag) - frag.appendChild(n); + // Update style info + s.update_styles = ol; + if (s.update_styles) + t.setAttrib(e, s.cssText); + }, - sibling = nextSibling; - --cnt; - } + removeAllAttribs: function(e) { + return this.run(e, function(e) { + var i, attrs = e.attributes; + for (i = attrs.length - 1; i >= 0; i--) { + e.removeAttributeNode(attrs.item(i)); + } + }); + }, - n = _traverseRightBoundary(endAncestor, how); + setAttrib : function(e, n, v) { + var t = this; - if (frag) - frag.appendChild(n); + // Whats the point + if (!e || !n) + return; - if (how != CLONE) { - t.setStartAfter(startAncestor); - t.collapse(TRUE); - } + // Strict XML mode + if (t.settings.strict) + n = n.toLowerCase(); - return frag; - }; + return this.run(e, function(e) { + var s = t.settings; - function _traverseRightBoundary(root, how) { - var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; + switch (n) { + case "style": + if (!is(v, 'string')) { + each(v, function(v, n) { + t.setStyle(e, n, v); + }); - if (next == root) - return _traverseNode(next, isFullySelected, FALSE, how); + return; + } - parent = next.parentNode; - clonedParent = _traverseNode(parent, FALSE, FALSE, how); + // No mce_style for elements with these since they might get resized by the user + if (s.keep_values) { + if (v && !t._isRes(v)) + e.setAttribute('data-mce-style', v, 2); + else + e.removeAttribute('data-mce-style', 2); + } - while (parent) { - while (next) { - prevSibling = next.previousSibling; - clonedChild = _traverseNode(next, isFullySelected, FALSE, how); + e.style.cssText = v; + break; - if (how != DELETE) - clonedParent.insertBefore(clonedChild, clonedParent.firstChild); + case "class": + e.className = v || ''; // Fix IE null bug + break; - isFullySelected = TRUE; - next = prevSibling; - } + case "src": + case "href": + if (s.keep_values) { + if (s.url_converter) + v = s.url_converter.call(s.url_converter_scope || t, v, n, e); - if (parent == root) - return clonedParent; + t.setAttrib(e, 'data-mce-' + n, v, 2); + } - next = parent.previousSibling; - parent = parent.parentNode; + break; - clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); + case "shape": + e.setAttribute('data-mce-style', v); + break; + } - if (how != DELETE) - clonedGrandParent.appendChild(clonedParent); + if (is(v) && v !== null && v.length !== 0) + e.setAttribute(n, '' + v, 2); + else + e.removeAttribute(n, 2); + }); + }, - clonedParent = clonedGrandParent; - } - }; + setAttribs : function(e, o) { + var t = this; - function _traverseLeftBoundary(root, how) { - var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + return this.run(e, function(e) { + each(o, function(v, n) { + t.setAttrib(e, n, v); + }); + }); + }, - if (next == root) - return _traverseNode(next, isFullySelected, TRUE, how); + getAttrib : function(e, n, dv) { + var v, t = this, undef; - parent = next.parentNode; - clonedParent = _traverseNode(parent, FALSE, TRUE, how); + e = t.get(e); - while (parent) { - while (next) { - nextSibling = next.nextSibling; - clonedChild = _traverseNode(next, isFullySelected, TRUE, how); + if (!e || e.nodeType !== 1) + return dv === undef ? false : dv; - if (how != DELETE) - clonedParent.appendChild(clonedChild); + if (!is(dv)) + dv = ''; - isFullySelected = TRUE; - next = nextSibling; - } + // Try the mce variant for these + if (/^(src|href|style|coords|shape)$/.test(n)) { + v = e.getAttribute("data-mce-" + n); - if (parent == root) - return clonedParent; + if (v) + return v; + } - next = parent.nextSibling; - parent = parent.parentNode; + if (isIE && t.props[n]) { + v = e[t.props[n]]; + v = v && v.nodeValue ? v.nodeValue : v; + } - clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); + if (!v) + v = e.getAttribute(n, 2); - if (how != DELETE) - clonedGrandParent.appendChild(clonedParent); + // Check boolean attribs + if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) { + if (e[t.props[n]] === true && v === '') + return n; - clonedParent = clonedGrandParent; + return v ? n : ''; } - }; - function _traverseNode(n, isFullySelected, isLeft, how) { - var txtValue, newNodeValue, oldNodeValue, offset, newNode; + // Inner input elements will override attributes on form elements + if (e.nodeName === "FORM" && e.getAttributeNode(n)) + return e.getAttributeNode(n).nodeValue; - if (isFullySelected) - return _traverseFullySelected(n, how); + if (n === 'style') { + v = v || e.style.cssText; - if (n.nodeType == 3 /* TEXT_NODE */) { - txtValue = n.nodeValue; + if (v) { + v = t.serializeStyle(t.parseStyle(v), e.nodeName); - if (isLeft) { - offset = t[START_OFFSET]; - newNodeValue = txtValue.substring(offset); - oldNodeValue = txtValue.substring(0, offset); - } else { - offset = t[END_OFFSET]; - newNodeValue = txtValue.substring(0, offset); - oldNodeValue = txtValue.substring(offset); + if (t.settings.keep_values && !t._isRes(v)) + e.setAttribute('data-mce-style', v); } - - if (how != CLONE) - n.nodeValue = oldNodeValue; - - if (how == DELETE) - return; - - newNode = n.cloneNode(FALSE); - newNode.nodeValue = newNodeValue; - - return newNode; } - if (how == DELETE) - return; + // Remove Apple and WebKit stuff + if (isWebKit && n === "class" && v) + v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); - return n.cloneNode(FALSE); - }; + // Handle IE issues + if (isIE) { + switch (n) { + case 'rowspan': + case 'colspan': + // IE returns 1 as default value + if (v === 1) + v = ''; - function _traverseFullySelected(n, how) { - if (how != DELETE) - return how == CLONE ? n.cloneNode(TRUE) : n; + break; - n.parentNode.removeChild(n); - }; - }; + case 'size': + // IE returns +0 as default value for size + if (v === '+0' || v === 20 || v === 0) + v = ''; - ns.Range = Range; -})(tinymce.dom); + break; -(function() { - function Selection(selection) { - var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false; + case 'width': + case 'height': + case 'vspace': + case 'checked': + case 'disabled': + case 'readonly': + if (v === 0) + v = ''; - // Returns a W3C DOM compatible range object by using the IE Range API - function getRange() { - var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed; + break; - // If selection is outside the current document just return an empty range - element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); - if (element.ownerDocument != dom.doc) - return domRange; + case 'hspace': + // IE returns -1 as default value + if (v === -1) + v = ''; - // Handle control selection or text selection of a image - if (ieRange.item || !element.hasChildNodes()) { - domRange.setStart(element.parentNode, dom.nodeIndex(element)); - domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + break; - return domRange; - } + case 'maxlength': + case 'tabindex': + // IE returns default value + if (v === 32768 || v === 2147483647 || v === '32768') + v = ''; - collapsed = selection.isCollapsed(); + break; - function findEndPoint(start) { - var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position; + case 'multiple': + case 'compact': + case 'noshade': + case 'nowrap': + if (v === 65535) + return n; - // Setup temp range and collapse it - checkRng = ieRange.duplicate(); - checkRng.collapse(start); + return dv; - // Create marker and insert it at the end of the endpoints parent - marker = dom.create('a'); - parent = checkRng.parentElement(); + case 'shape': + v = v.toLowerCase(); + break; - // If parent doesn't have any children then set the container to that parent and the index to 0 - if (!parent.hasChildNodes()) { - domRange[start ? 'setStart' : 'setEnd'](parent, 0); - return; + default: + // IE has odd anonymous function for event attributes + if (n.indexOf('on') === 0 && v) + v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v); } + } - parent.appendChild(marker); - checkRng.moveToElementText(marker); - position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng); - if (position > 0) { - // The position is after the end of the parent element. - // This is the case where IE puts the caret to the left edge of a table. - domRange[start ? 'setStartAfter' : 'setEndAfter'](parent); - dom.remove(marker); - return; + return (v !== undef && v !== null && v !== '') ? '' + v : dv; + }, + + getPos : function(n, ro) { + var t = this, x = 0, y = 0, e, d = t.doc, r; + + n = t.get(n); + ro = ro || d.body; + + if (n) { + // Use getBoundingClientRect if it exists since it's faster than looping offset nodes + if (n.getBoundingClientRect) { + n = n.getBoundingClientRect(); + e = t.boxModel ? d.documentElement : d.body; + + // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit + // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position + x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop; + y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft; + + return {x : x, y : y}; } - // Setup node list and endIndex - nodes = tinymce.grep(parent.childNodes); - endIndex = nodes.length - 1; - // Perform a binary search for the position - while (startIndex <= endIndex) { - index = Math.floor((startIndex + endIndex) / 2); - - // Insert marker and check it's position relative to the selection - parent.insertBefore(marker, nodes[index]); - checkRng.moveToElementText(marker); - position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng); - if (position > 0) { - // Marker is to the right - startIndex = index + 1; - } else if (position < 0) { - // Marker is to the left - endIndex = index - 1; - } else { - // Maker is where we are - found = true; - break; - } + r = n; + while (r && r != ro && r.nodeType) { + x += r.offsetLeft || 0; + y += r.offsetTop || 0; + r = r.offsetParent; } - // Setup container - container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling; + r = n.parentNode; + while (r && r != ro && r.nodeType) { + x -= r.scrollLeft || 0; + y -= r.scrollTop || 0; + r = r.parentNode; + } + } - // Handle element selection - if (container.nodeType == 1) { - dom.remove(marker); + return {x : x, y : y}; + }, - // Find offset and container - offset = dom.nodeIndex(container); - container = container.parentNode; + parseStyle : function(st) { + return this.styles.parse(st); + }, - // Move the offset if we are setting the end or the position is after an element - if (!start || index > 0) - offset++; - } else { - // Calculate offset within text node - if (position > 0 || index == 0) { - checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange); - offset = checkRng.text.length; - } else { - checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange); - offset = container.nodeValue.length - checkRng.text.length; - } + serializeStyle : function(o, name) { + return this.styles.serialize(o, name); + }, - dom.remove(marker); + loadCSS : function(u) { + var t = this, d = t.doc, head; + + if (!u) + u = ''; + + head = t.select('head')[0]; + + each(u.split(','), function(u) { + var link; + + if (t.files[u]) + return; + + t.files[u] = true; + link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); + + // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug + // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading + // It's ugly but it seems to work fine. + if (isIE && d.documentMode && d.recalc) { + link.onload = function() { + if (d.recalc) + d.recalc(); + + link.onload = null; + }; } - domRange[start ? 'setStart' : 'setEnd'](container, offset); - }; + head.appendChild(link); + }); + }, - // Find start point - findEndPoint(true); + addClass : function(e, c) { + return this.run(e, function(e) { + var o; - // Find end point if needed - if (!collapsed) - findEndPoint(); + if (!c) + return 0; - return domRange; - }; + if (this.hasClass(e, c)) + return e.className; - this.addRange = function(rng) { - var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body; + o = this.removeClass(e, c); - function setEndPoint(start) { - var container, offset, marker, tmpRng, nodes; + return e.className = (o != '' ? (o + ' ') : '') + c; + }); + }, - marker = dom.create('a'); - container = start ? startContainer : endContainer; - offset = start ? startOffset : endOffset; - tmpRng = ieRng.duplicate(); + removeClass : function(e, c) { + var t = this, re; - if (container == doc) { - container = body; - offset = 0; - } + return t.run(e, function(e) { + var v; - if (container.nodeType == 3) { - container.parentNode.insertBefore(marker, container); - tmpRng.moveToElementText(marker); - tmpRng.moveStart('character', offset); - dom.remove(marker); - ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); - } else { - nodes = container.childNodes; + if (t.hasClass(e, c)) { + if (!re) + re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); - if (nodes.length) { - if (offset >= nodes.length) { - dom.insertAfter(marker, nodes[nodes.length - 1]); - } else { - container.insertBefore(marker, nodes[offset]); - } + v = e.className.replace(re, ' '); + v = tinymce.trim(v != ' ' ? v : ''); - tmpRng.moveToElementText(marker); - } else { - // Empty node selection for example
    |
    - marker = doc.createTextNode(invisibleChar); - container.appendChild(marker); - tmpRng.moveToElementText(marker.parentNode); - tmpRng.collapse(TRUE); + e.className = v; + + // Empty class attr + if (!v) { + e.removeAttribute('class'); + e.removeAttribute('className'); } - ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); - dom.remove(marker); + return v; } - } - // Destroy cached range - this.destroy(); + return e.className; + }); + }, - // Setup some shorter versions - startContainer = rng.startContainer; - startOffset = rng.startOffset; - endContainer = rng.endContainer; - endOffset = rng.endOffset; - ieRng = body.createTextRange(); + hasClass : function(n, c) { + n = this.get(n); - // If single element selection then try making a control selection out of it - if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) { - if (startOffset == endOffset - 1) { - try { - ctrlRng = body.createControlRange(); - ctrlRng.addElement(startContainer.childNodes[startOffset]); - ctrlRng.select(); - ctrlRng.scrollIntoView(); - return; - } catch (ex) { - // Ignore - } - } - } + if (!n || !c) + return false; - // Set start/end point of selection - setEndPoint(true); - setEndPoint(); + return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; + }, - // Select the new range and scroll it into view - ieRng.select(); - ieRng.scrollIntoView(); - }; + show : function(e) { + return this.setStyle(e, 'display', 'block'); + }, - this.getRangeAt = function() { - // Setup new range if the cache is empty - if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) { - range = getRange(); + hide : function(e) { + return this.setStyle(e, 'display', 'none'); + }, - // Store away text range for next call - lastIERng = selection.getRng(); - } + isHidden : function(e) { + e = this.get(e); - // IE will say that the range is equal then produce an invalid argument exception - // if you perform specific operations in a keyup event. For example Ctrl+Del. - // This hack will invalidate the range cache if the exception occurs - try { - range.startContainer.nextSibling; - } catch (ex) { - range = getRange(); - lastIERng = null; - } + return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; + }, - // Return cached range - return range; - }; + uniqueId : function(p) { + return (!p ? 'mce_' : p) + (this.counter++); + }, - this.destroy = function() { - // Destroy cached range and last IE range to avoid memory leaks - lastIERng = range = null; - }; - }; + setHTML : function(element, html) { + var self = this; - // Expose the selection object - tinymce.dom.TridentSelection = Selection; -})(); + return self.run(element, function(element) { + if (isIE) { + // Remove all child nodes, IE keeps empty text nodes in DOM + while (element.firstChild) + element.removeChild(element.firstChild); + try { + // IE will remove comments from the beginning + // unless you padd the contents with something + element.innerHTML = '
    ' + html; + element.removeChild(element.firstChild); + } catch (ex) { + // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p + // This seems to fix this problem + + // Create new div with HTML contents and a BR infront to keep comments + element = self.create('div'); + element.innerHTML = '
    ' + html; + + // Add all children from div to target + each (element.childNodes, function(node, i) { + // Skip br element + if (i) + element.appendChild(node); + }); + } + } else + element.innerHTML = html; -/* - * Sizzle CSS Selector Engine - v1.0 - * Copyright 2009, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ + return html; + }); + }, -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true; + getOuterHTML : function(elm) { + var doc, self = this; -// Here we check if the JavaScript engine is using some sort of -// optimization where it does not always call our comparision -// function. If that is the case, discard the hasDuplicate value. -// Thus far that includes Google Chrome. -[0, 0].sort(function(){ - baseHasDuplicate = false; - return 0; -}); + elm = self.get(elm); -var Sizzle = function(selector, context, results, seed) { - results = results || []; - context = context || document; + if (!elm) + return null; - var origContext = context; + if (elm.nodeType === 1 && self.hasOuterHTML) + return elm.outerHTML; - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } + doc = (elm.ownerDocument || self.doc).createElement("body"); + doc.appendChild(elm.cloneNode(true)); - var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), - soFar = selector, ret, cur, pop, i; - - // Reset the position of the chunker regexp (start from head) - do { - chunker.exec(""); - m = chunker.exec(soFar); + return doc.innerHTML; + }, - if ( m ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - } while ( m ); + setOuterHTML : function(e, h, d) { + var t = this; - if ( parts.length > 1 && origPOS.exec( selector ) ) { - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); + function setHTML(e, h, d) { + var n, tp; - while ( parts.length ) { - selector = parts.shift(); + tp = d.createElement("body"); + tp.innerHTML = h; - if ( Expr.relative[ selector ] ) { - selector += parts.shift(); + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; } - - set = posProcess( selector, set ); - } - } - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; - } - if ( context ) { - ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + t.remove(e); + }; - if ( parts.length > 0 ) { - checkSet = makeArray(set); - } else { - prune = false; - } + return this.run(e, function(e) { + e = t.get(e); - while ( parts.length ) { - cur = parts.pop(); - pop = cur; + // Only set HTML on elements + if (e.nodeType == 1) { + d = d || e.ownerDocument || t.doc; - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); + if (isIE) { + try { + // Try outerHTML for IE it sometimes produces an unknown runtime error + if (isIE && e.nodeType == 1) + e.outerHTML = h; + else + setHTML(e, h, d); + } catch (ex) { + // Fix for unknown runtime error + setHTML(e, h, d); + } + } else + setHTML(e, h, d); } + }); + }, - if ( pop == null ) { - pop = context; - } + decode : Entities.decode, - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - } else { - checkSet = parts = []; - } - } + encode : Entities.encodeAllRaw, - if ( !checkSet ) { - checkSet = set; - } + insertAfter : function(node, reference_node) { + reference_node = this.get(reference_node); - if ( !checkSet ) { - Sizzle.error( cur || selector ); - } + return this.run(node, function(node) { + var parent, nextSibling; - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - } else if ( context && context.nodeType === 1 ) { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - } else { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - } else { - makeArray( checkSet, results ); - } + parent = reference_node.parentNode; + nextSibling = reference_node.nextSibling; - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } + if (nextSibling) + parent.insertBefore(node, nextSibling); + else + parent.appendChild(node); - return results; -}; + return node; + }); + }, -Sizzle.uniqueSort = function(results){ - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort(sortOrder); + isBlock : function(node) { + var type = node.nodeType; - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[i-1] ) { - results.splice(i--, 1); - } - } - } - } + // If it's a node then check the type and use the nodeName + if (type) + return !!(type === 1 && blockElementsMap[node.nodeName]); - return results; -}; + return !!blockElementsMap[node]; + }, -Sizzle.matches = function(expr, set){ - return Sizzle(expr, null, null, set); -}; + replace : function(n, o, k) { + var t = this; -Sizzle.find = function(expr, context, isXML){ - var set; + if (is(o, 'array')) + n = n.cloneNode(true); - if ( !expr ) { - return []; - } + return t.run(o, function(o) { + if (k) { + each(tinymce.grep(o.childNodes), function(c) { + n.appendChild(c); + }); + } - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var type = Expr.order[i], match; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; - match.splice(1,1); + return o.parentNode.replaceChild(n, o); + }); + }, - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace(/\\/g, ""); - set = Expr.find[ type ]( match, context, isXML ); - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } + rename : function(elm, name) { + var t = this, newElm; - if ( !set ) { - set = context.getElementsByTagName("*"); - } + if (elm.nodeName != name.toUpperCase()) { + // Rename block element + newElm = t.create(name); - return {set: set, expr: expr}; -}; + // Copy attribs to new block + each(t.getAttribs(elm), function(attr_node) { + t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); + }); -Sizzle.filter = function(expr, set, inplace, not){ - var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); + // Replace block + t.replace(newElm, elm, 1); + } - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var filter = Expr.filter[ type ], found, item, left = match[1]; - anyFound = false; + return newElm || elm; + }, - match.splice(1,1); + findCommonAncestor : function(a, b) { + var ps = a, pe; - if ( left.substr( left.length - 1 ) === "\\" ) { - continue; - } + while (ps) { + pe = b; - if ( curLoop === result ) { - result = []; - } + while (pe && ps != pe) + pe = pe.parentNode; - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + if (ps == pe) + break; - if ( !match ) { - anyFound = found = true; - } else if ( match === true ) { - continue; - } - } + ps = ps.parentNode; + } - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; + if (!ps && a.ownerDocument) + return a.ownerDocument.documentElement; - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - } else { - curLoop[i] = false; - } - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } + return ps; + }, - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } + toHex : function(s) { + var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); - expr = expr.replace( Expr.match[ type ], "" ); + function hex(s) { + s = parseInt(s).toString(16); - if ( !anyFound ) { - return []; - } + return s.length > 1 ? s : '0' + s; // 0 -> 00 + }; - break; - } - } - } + if (c) { + s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); - // Improper expression - if ( expr === old ) { - if ( anyFound == null ) { - Sizzle.error( expr ); - } else { - break; + return s; } - } - old = expr; - } + return s; + }, - return curLoop; -}; + getClasses : function() { + var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; -Sizzle.error = function( msg ) { - throw "Syntax error, unrecognized expression: " + msg; -}; + if (t.classes) + return t.classes; -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - match: { - ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ - }, - leftMatch: {}, - attrMap: { - "class": "className", - "for": "htmlFor" - }, - attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); - } - }, - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), - isPartStrNotTag = isPartStr && !isTag; + function addClasses(s) { + // IE style imports + each(s.imports, function(r) { + addClasses(r); + }); - if ( isTag ) { - part = part.toLowerCase(); - } + each(s.cssRules || s.rules, function(r) { + // Real type or fake it on IE + switch (r.type || 1) { + // Rule + case 1: + if (r.selectorText) { + each(r.selectorText.split(','), function(v) { + v = v.replace(/^\s*|\s*$|^\s\./g, ""); - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + // Is internal or it doesn't contain a class + if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) + return; - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } + // Remove everything but class name + ov = v; + v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - ">": function(checkSet, part){ - var isPartStr = typeof part === "string", - elem, i = 0, l = checkSet.length; + // Filter classes + if (f && !(v = f(v, ov))) + return; - if ( isPartStr && !/\W/.test(part) ) { - part = part.toLowerCase(); + if (!lo[v]) { + cl.push({'class' : v}); + lo[v] = 1; + } + }); + } + break; - for ( ; i < l; i++ ) { - elem = checkSet[i]; - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - } else { - for ( ; i < l; i++ ) { - elem = checkSet[i]; - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; + // Import + case 3: + addClasses(r.styleSheet); + break; } - } + }); + }; - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } + try { + each(t.doc.styleSheets, addClasses); + } catch (ex) { + // Ignore } - }, - "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck, nodeCheck; - if ( typeof part === "string" && !/\W/.test(part) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } + if (cl.length > 0) + t.classes = cl; - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + return cl; }, - "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck, nodeCheck; - if ( typeof part === "string" && !/\W/.test(part) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } + run : function(e, f, s) { + var t = this, o; - checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); - } - }, - find: { - ID: function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? [m] : []; - } - }, - NAME: function(match, context){ - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], results = context.getElementsByName(match[1]); + if (t.doc && typeof(e) === 'string') + e = t.get(e); - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } + if (!e) + return false; - return ret.length === 0 ? null : ret; - } - }, - TAG: function(match, context){ - return context.getElementsByTagName(match[1]); - } - }, - preFilter: { - CLASS: function(match, curLoop, inplace, result, not, isXML){ - match = " " + match[1].replace(/\\/g, "") + " "; + s = s || this; + if (!e.nodeType && (e.length || e.length === 0)) { + o = []; - if ( isXML ) { - return match; - } + each(e, function(e, i) { + if (e) { + if (typeof(e) == 'string') + e = t.doc.getElementById(e); - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); - } - } else if ( inplace ) { - curLoop[i] = false; + o.push(f.call(s, e, i)); } - } + }); + + return o; } - return false; - }, - ID: function(match){ - return match[1].replace(/\\/g, ""); - }, - TAG: function(match, curLoop){ - return match[1].toLowerCase(); + return f.call(s, e); }, - CHILD: function(match){ - if ( match[1] === "nth" ) { - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } + getAttribs : function(n) { + var o; - // TODO: Move to normal caching system - match[0] = done++; + n = this.get(n); - return match; - }, - ATTR: function(match, curLoop, inplace, result, not, isXML){ - var name = match[1].replace(/\\/g, ""); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } + if (!n) + return []; - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; + if (isIE) { + o = []; + + // Object will throw exception in IE + if (n.nodeName == 'OBJECT') + return n.attributes; + + // IE doesn't keep the selected attribute if you clone option elements + if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected')) + o.push({specified : 1, nodeName : 'selected'}); + + // 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(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) { + o.push({specified : 1, nodeName : a}); + }); + + return o; } - return match; + return n.attributes; }, - PSEUDO: function(match, curLoop, inplace, result, not){ - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - if ( !inplace ) { - result.push.apply( result, ret ); + + isEmpty : function(node, elements) { + var self = this, i, attributes, type, walker, name, parentNode; + + node = node.firstChild; + if (node) { + walker = new tinymce.dom.TreeWalker(node); + elements = elements || self.schema ? self.schema.getNonEmptyElements() : null; + + do { + type = node.nodeType; + + if (type === 1) { + // Ignore bogus elements + if (node.getAttribute('data-mce-bogus')) + continue; + + // Keep empty elements like + name = node.nodeName.toLowerCase(); + if (elements && elements[name]) { + // Ignore single BR elements in blocks like


    + parentNode = node.parentNode; + if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) { + continue; + } + + return false; + } + + // Keep elements with data-bookmark attributes or name attribute like
    + attributes = self.getAttribs(node); + i = node.attributes.length; + while (i--) { + name = node.attributes[i].nodeName; + if (name === "name" || name === 'data-mce-bookmark') + return false; + } } - return false; - } - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; + + // Keep non whitespace text nodes + if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) + return false; + } while (node = walker.next()); } - - return match; - }, - POS: function(match){ - match.unshift( true ); - return match; - } - }, - filters: { - enabled: function(elem){ - return elem.disabled === false && elem.type !== "hidden"; - }, - disabled: function(elem){ - return elem.disabled === true; - }, - checked: function(elem){ - return elem.checked === true; - }, - selected: function(elem){ - // Accessing this property makes selected-by-default - // options in Safari work properly - elem.parentNode.selectedIndex; - return elem.selected === true; - }, - parent: function(elem){ - return !!elem.firstChild; - }, - empty: function(elem){ - return !elem.firstChild; - }, - has: function(elem, i, match){ - return !!Sizzle( match[3], elem ).length; - }, - header: function(elem){ - return (/h\d/i).test( elem.nodeName ); - }, - text: function(elem){ - return "text" === elem.type; - }, - radio: function(elem){ - return "radio" === elem.type; - }, - checkbox: function(elem){ - return "checkbox" === elem.type; - }, - file: function(elem){ - return "file" === elem.type; - }, - password: function(elem){ - return "password" === elem.type; - }, - submit: function(elem){ - return "submit" === elem.type; - }, - image: function(elem){ - return "image" === elem.type; - }, - reset: function(elem){ - return "reset" === elem.type; - }, - button: function(elem){ - return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; - }, - input: function(elem){ - return (/input|select|textarea|button/i).test(elem.nodeName); - } - }, - setFilters: { - first: function(elem, i){ - return i === 0; - }, - last: function(elem, i, match, array){ - return i === array.length - 1; - }, - even: function(elem, i){ - return i % 2 === 0; - }, - odd: function(elem, i){ - return i % 2 === 1; - }, - lt: function(elem, i, match){ - return i < match[3] - 0; + + return true; }, - gt: function(elem, i, match){ - return i > match[3] - 0; + + destroy : function(s) { + var t = this; + + if (t.events) + t.events.destroy(); + + t.win = t.doc = t.root = t.events = null; + + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); }, - nth: function(elem, i, match){ - return match[3] - 0 === i; + + createRng : function() { + var d = this.doc; + + return d.createRange ? d.createRange() : new tinymce.dom.Range(this); }, - eq: function(elem, i, match){ - return match[3] - 0 === i; - } - }, - filter: { - PSEUDO: function(elem, match, i, array){ - var name = match[1], filter = Expr.filters[ name ]; - if ( filter ) { - return filter( elem, i, match, array ); - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; - } else if ( name === "not" ) { - var not = match[3]; + nodeIndex : function(node, normalized) { + var idx = 0, lastNodeType, lastNode, nodeType; - for ( var j = 0, l = not.length; j < l; j++ ) { - if ( not[j] === elem ) { - return false; + if (node) { + for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { + nodeType = node.nodeType; + + // Normalize text nodes + if (normalized && nodeType == 3) { + if (nodeType == lastNodeType || !node.nodeValue.length) + continue; } + idx++; + lastNodeType = nodeType; } - - return true; - } else { - Sizzle.error( "Syntax error, unrecognized expression: " + name ); } + + return idx; }, - CHILD: function(elem, match){ - var type = match[1], node = elem; - switch (type) { - case 'only': - case 'first': - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - if ( type === "first" ) { - return true; - } - node = elem; - case 'last': - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - return true; - case 'nth': - var first = match[2], last = match[3]; - if ( first === 1 && last === 0 ) { - return true; - } - - var doneName = match[0], - parent = elem.parentNode; - - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - parent.sizcache = doneName; - } - - var diff = elem.nodeIndex - last; - if ( first === 0 ) { - return diff === 0; - } else { - return ( diff % first === 0 && diff / first >= 0 ); + split : function(pe, e, re) { + var t = this, r = t.createRng(), bef, aft, pa; + + // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense + // but we don't want that in our code since it serves no purpose for the end user + // For example if this is chopped: + //

    text 1CHOPtext 2

    + // would produce: + //

    text 1

    CHOP

    text 2

    + // this function will then trim of empty edges and produce: + //

    text 1

    CHOP

    text 2

    + function trim(node) { + var i, children = node.childNodes, type = node.nodeType; + + if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') + return; + + for (i = children.length - 1; i >= 0; i--) + trim(children[i]); + + if (type != 9) { + // Keep non whitespace text nodes + if (type == 3 && node.nodeValue.length > 0) { + // If parent element isn't a block or there isn't any useful contents for example "

    " + if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0) + return; + } else if (type == 1) { + // If the only child is a bookmark then move it up + children = node.childNodes; + if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark') + node.parentNode.insertBefore(children[0], node); + + // Keep non empty elements or img, hr etc + if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) + return; } + + t.remove(node); + } + + return node; + }; + + if (pe && e) { + // Get before chunk + r.setStart(pe.parentNode, t.nodeIndex(pe)); + r.setEnd(e.parentNode, t.nodeIndex(e)); + bef = r.extractContents(); + + // Get after chunk + r = t.createRng(); + r.setStart(e.parentNode, t.nodeIndex(e) + 1); + r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1); + aft = r.extractContents(); + + // Insert before chunk + pa = pe.parentNode; + pa.insertBefore(trim(bef), pe); + + // Insert middle chunk + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); + + // Insert after chunk + pa.insertBefore(trim(aft), pe); + t.remove(pe); + + return re || e; } }, - ID: function(elem, match){ - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - TAG: function(elem, match){ - return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; - }, - CLASS: function(elem, match){ - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - ATTR: function(elem, match){ - var name = match[1], - result = Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - POS: function(elem, match, i, array){ - var name = match[2], filter = Expr.setFilters[ name ]; + bind : function(target, name, func, scope) { + var t = this; - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; + if (!t.events) + t.events = new tinymce.dom.EventUtils(); -var origPOS = Expr.match.POS, - fescape = function(all, num){ - return "\\" + (num - 0 + 1); - }; + return t.events.add(target, name, func, scope || this); + }, -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); -} + unbind : function(target, name, func) { + var t = this; -var makeArray = function(array, results) { - array = Array.prototype.slice.call( array, 0 ); + if (!t.events) + t.events = new tinymce.dom.EventUtils(); - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; + return t.events.remove(target, name, func); + }, -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -// Also verifies that the returned array holds DOM nodes -// (which is not the case in the Blackberry browser) -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; -// Provide a fallback method if it does not work -} catch(e){ - makeArray = function(array, results) { - var ret = results || [], i = 0; + _findSib : function(node, selector, name) { + var t = this, f = selector; - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - } else { - if ( typeof array.length === "number" ) { - for ( var l = array.length; i < l; i++ ) { - ret.push( array[i] ); + if (node) { + // If expression make a function of it using is + if (is(f, 'string')) { + f = function(node) { + return t.is(node, selector); + }; } - } else { - for ( ; array[i]; i++ ) { - ret.push( array[i] ); + + // Loop all siblings + for (node = node[name]; node; node = node[name]) { + if (f(node)) + return node; } } - } - - return ret; - }; -} -var sortOrder; + return null; + }, -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - if ( a == b ) { - hasDuplicate = true; - } - return a.compareDocumentPosition ? -1 : 1; + _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); } - var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} else if ( "sourceIndex" in document.documentElement ) { - sortOrder = function( a, b ) { - if ( !a.sourceIndex || !b.sourceIndex ) { - if ( a == b ) { - hasDuplicate = true; - } - return a.sourceIndex ? -1 : 1; - } + /* + walk : function(n, f, s) { + var d = this.doc, w; - var ret = a.sourceIndex - b.sourceIndex; - if ( ret === 0 ) { - hasDuplicate = true; - } - return ret; - }; -} else if ( document.createRange ) { - sortOrder = function( a, b ) { - if ( !a.ownerDocument || !b.ownerDocument ) { - if ( a == b ) { - hasDuplicate = true; - } - return a.ownerDocument ? -1 : 1; - } + if (d.createTreeWalker) { + w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); - var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); - aRange.setStart(a, 0); - aRange.setEnd(a, 0); - bRange.setStart(b, 0); - bRange.setEnd(b, 0); - var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); - if ( ret === 0 ) { - hasDuplicate = true; + while ((n = w.nextNode()) != null) + f.call(s || this, n); + } else + tinymce.walk(n, f, 'childNodes', s); } - return ret; - }; -} + */ -// Utility function for retreiving the text value of an array of DOM nodes -Sizzle.getText = function( elems ) { - var ret = "", elem; + /* + toRGB : function(s) { + var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; + if (c) { + // #FFF -> #FFFFFF + if (!is(c[3])) + c[3] = c[2] = c[1]; - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; + return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; + } - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += Sizzle.getText( elem.childNodes ); + return s; } - } - - return ret; -}; + */ + }); -// Check to see if the browser returns elements by name when -// querying by getElementById (and provide a workaround) -(function(){ - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date()).getTime(); - form.innerHTML = ""; + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); - // Inject it into the root element, check its status, and remove it quickly - var root = document.documentElement; - root.insertBefore( form, root.firstChild ); +(function(ns) { + // Range constructor + function Range(dom) { + var t = this, + doc = dom.doc, + EXTRACT = 0, + CLONE = 1, + DELETE = 2, + TRUE = true, + FALSE = false, + START_OFFSET = 'startOffset', + START_CONTAINER = 'startContainer', + END_CONTAINER = 'endContainer', + END_OFFSET = 'endOffset', + extend = tinymce.extend, + nodeIndex = dom.nodeIndex; - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if ( document.getElementById( id ) ) { - Expr.find.ID = function(match, context, isXML){ - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; - } - }; + extend(t, { + // Inital states + startContainer : doc, + startOffset : 0, + endContainer : doc, + endOffset : 0, + collapsed : TRUE, + commonAncestorContainer : doc, - Expr.filter.ID = function(elem, match){ - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3, - root.removeChild( form ); - root = form = null; // release memory in IE -})(); + // Public methods + setStart : setStart, + setEnd : setEnd, + setStartBefore : setStartBefore, + setStartAfter : setStartAfter, + setEndBefore : setEndBefore, + setEndAfter : setEndAfter, + collapse : collapse, + selectNode : selectNode, + selectNodeContents : selectNodeContents, + compareBoundaryPoints : compareBoundaryPoints, + deleteContents : deleteContents, + extractContents : extractContents, + cloneContents : cloneContents, + insertNode : insertNode, + surroundContents : surroundContents, + cloneRange : cloneRange + }); -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") + function setStart(n, o) { + _setEndPoint(TRUE, n, o); + }; - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); + function setEnd(n, o) { + _setEndPoint(FALSE, n, o); + }; - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function(match, context){ - var results = context.getElementsByTagName(match[1]); + function setStartBefore(n) { + setStart(n.parentNode, nodeIndex(n)); + }; - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; + function setStartAfter(n) { + setStart(n.parentNode, nodeIndex(n) + 1); + }; - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } + function setEndBefore(n) { + setEnd(n.parentNode, nodeIndex(n)); + }; - results = tmp; + function setEndAfter(n) { + setEnd(n.parentNode, nodeIndex(n) + 1); + }; + + function collapse(ts) { + if (ts) { + t[END_CONTAINER] = t[START_CONTAINER]; + t[END_OFFSET] = t[START_OFFSET]; + } else { + t[START_CONTAINER] = t[END_CONTAINER]; + t[START_OFFSET] = t[END_OFFSET]; } - return results; + t.collapsed = TRUE; }; - } - // Check to see if an attribute returns normalized href attributes - div.innerHTML = ""; - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - Expr.attrHandle.href = function(elem){ - return elem.getAttribute("href", 2); + function selectNode(n) { + setStartBefore(n); + setEndAfter(n); }; - } - div = null; // release memory in IE -})(); + function selectNodeContents(n) { + setStart(n, 0); + setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }; -if ( document.querySelectorAll ) { - (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); - div.innerHTML = "

    "; + function compareBoundaryPoints(h, r) { + var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET], + rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset; - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } + // Check START_TO_START + if (h === 0) + return _compareBoundaryPoints(sc, so, rsc, rso); - Sizzle = function(query, context, extra, seed){ - context = context || document; + // Check START_TO_END + if (h === 1) + return _compareBoundaryPoints(ec, eo, rsc, rso); + + // Check END_TO_END + if (h === 2) + return _compareBoundaryPoints(ec, eo, rec, reo); + + // Check END_TO_START + if (h === 3) + return _compareBoundaryPoints(sc, so, rec, reo); + }; - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) { - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(e){} - } - - return oldSizzle(query, context, extra, seed); + function deleteContents() { + _traverse(DELETE); }; - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } + function extractContents() { + return _traverse(EXTRACT); + }; - div = null; // release memory in IE - })(); -} + function cloneContents() { + return _traverse(CLONE); + }; -(function(){ - var div = document.createElement("div"); + function insertNode(n) { + var startContainer = this[START_CONTAINER], + startOffset = this[START_OFFSET], nn, o; - div.innerHTML = "
    "; + // Node is TEXT_NODE or CDATA + if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) { + if (!startOffset) { + // At the start of text + startContainer.parentNode.insertBefore(n, startContainer); + } else if (startOffset >= startContainer.nodeValue.length) { + // At the end of text + dom.insertAfter(n, startContainer); + } else { + // Middle, need to split + nn = startContainer.splitText(startOffset); + startContainer.parentNode.insertBefore(n, nn); + } + } else { + // Insert element node + if (startContainer.childNodes.length > 0) + o = startContainer.childNodes[startOffset]; - // Opera can't find a second classname (in 9.6) - // Also, make sure that getElementsByClassName actually exists - if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { - return; - } + if (o) + startContainer.insertBefore(n, o); + else + startContainer.appendChild(n); + } + }; - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; + function surroundContents(n) { + var f = t.extractContents(); - if ( div.getElementsByClassName("e").length === 1 ) { - return; - } - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function(match, context, isXML) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }; - div = null; // release memory in IE -})(); + function cloneRange() { + return extend(new Range(dom), { + startContainer : t[START_CONTAINER], + startOffset : t[START_OFFSET], + endContainer : t[END_CONTAINER], + endOffset : t[END_OFFSET], + collapsed : t.collapsed, + commonAncestorContainer : t.commonAncestorContainer + }); + }; -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - elem = elem[dir]; - var match = false; + // Private methods - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } + function _getSelectedNode(container, offset) { + var child; - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } + if (container.nodeType == 3 /* TEXT_NODE */) + return container; - if ( elem.nodeName.toLowerCase() === cur ) { - match = elem; - break; - } + if (offset < 0) + return container; - elem = elem[dir]; + child = container.firstChild; + while (child && offset > 0) { + --offset; + child = child.nextSibling; } - checkSet[i] = match; - } - } -} + if (child) + return child; -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - elem = elem[dir]; - var match = false; + return container; + }; - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } + function _isCollapsed() { + return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); + }; - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } + function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) { + var c, offsetC, n, cmnRoot, childA, childB; + + // In the first case the boundary-points have the same container. A is before B + // if its offset is less than the offset of B, A is equal to B if its offset is + // equal to the offset of B, and A is after B if its offset is greater than the + // offset of B. + if (containerA == containerB) { + if (offsetA == offsetB) + return 0; // equal - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } + if (offsetA < offsetB) + return -1; // before - elem = elem[dir]; + return 1; // after } - checkSet[i] = match; - } - } -} + // In the second case a child node C of the container of A is an ancestor + // container of B. In this case, A is before B if the offset of A is less than or + // equal to the index of the child node C and A is after B otherwise. + c = containerB; + while (c && c.parentNode != containerA) + c = c.parentNode; -Sizzle.contains = document.compareDocumentPosition ? function(a, b){ - return !!(a.compareDocumentPosition(b) & 16); -} : function(a, b){ - return a !== b && (a.contains ? a.contains(b) : true); -}; + if (c) { + offsetC = 0; + n = containerA.firstChild; -Sizzle.isXML = function(elem){ - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; + while (n != c && offsetC < offsetA) { + offsetC++; + n = n.nextSibling; + } -var posProcess = function(selector, context){ - var tmpSet = [], later = "", match, - root = context.nodeType ? [context] : context; + if (offsetA <= offsetC) + return -1; // before - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } + return 1; // after + } - selector = Expr.relative[selector] ? selector + "*" : selector; + // In the third case a child node C of the container of B is an ancestor container + // of A. In this case, A is before B if the index of the child node C is less than + // the offset of B and A is after B otherwise. + c = containerA; + while (c && c.parentNode != containerB) { + c = c.parentNode; + } - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } + if (c) { + offsetC = 0; + n = containerB.firstChild; - return Sizzle.filter( later, tmpSet ); -}; + while (n != c && offsetC < offsetB) { + offsetC++; + n = n.nextSibling; + } -// EXPOSE + if (offsetC < offsetB) + return -1; // before -window.tinymce.dom.Sizzle = Sizzle; + return 1; // after + } -})(); + // In the fourth case, none of three other cases hold: the containers of A and B + // are siblings or descendants of sibling nodes. In this case, A is before B if + // the container of A is before the container of B in a pre-order traversal of the + // Ranges' context tree and A is after B otherwise. + cmnRoot = dom.findCommonAncestor(containerA, containerB); + childA = containerA; + while (childA && childA.parentNode != cmnRoot) + childA = childA.parentNode; -(function(tinymce) { - // Shorten names - var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; + if (!childA) + childA = cmnRoot; - tinymce.create('tinymce.dom.EventUtils', { - EventUtils : function() { - this.inits = []; - this.events = []; - }, + childB = containerB; + while (childB && childB.parentNode != cmnRoot) + childB = childB.parentNode; - add : function(o, n, f, s) { - var cb, t = this, el = t.events, r; + if (!childB) + childB = cmnRoot; - if (n instanceof Array) { - r = []; + if (childA == childB) + return 0; // equal - each(n, function(n) { - r.push(t.add(o, n, f, s)); - }); + n = cmnRoot.firstChild; + while (n) { + if (n == childA) + return -1; // before - return r; - } + if (n == childB) + return 1; // after - // Handle array - if (o && o.hasOwnProperty && o instanceof Array) { - r = []; + n = n.nextSibling; + } + }; - each(o, function(o) { - o = DOM.get(o); - r.push(t.add(o, n, f, s)); - }); + function _setEndPoint(st, n, o) { + var ec, sc; - return r; + if (st) { + t[START_CONTAINER] = n; + t[START_OFFSET] = o; + } else { + t[END_CONTAINER] = n; + t[END_OFFSET] = o; } - o = DOM.get(o); - - if (!o) - return; + // If one boundary-point of a Range is set to have a root container + // other than the current one for the Range, the Range is collapsed to + // the new position. This enforces the restriction that both boundary- + // points of a Range must have the same root container. + ec = t[END_CONTAINER]; + while (ec.parentNode) + ec = ec.parentNode; - // Setup event callback - cb = function(e) { - // Is all events disabled - if (t.disabled) - return; + sc = t[START_CONTAINER]; + while (sc.parentNode) + sc = sc.parentNode; - e = e || window.event; + if (sc == ec) { + // The start position of a Range is guaranteed to never be after the + // end position. To enforce this restriction, if the start is set to + // be at a position after the end, the Range is collapsed to that + // position. + if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0) + t.collapse(st); + } else + t.collapse(st); - // Patch in target, preventDefault and stopPropagation in IE it's W3C valid - if (e && isIE) { - if (!e.target) - e.target = e.srcElement; + t.collapsed = _isCollapsed(); + t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); + }; - // Patch in preventDefault, stopPropagation methods for W3C compatibility - tinymce.extend(e, t._stoppers); - } + function _traverse(how) { + var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; - if (!s) - return f(e); + if (t[START_CONTAINER] == t[END_CONTAINER]) + return _traverseSameContainer(how); - return f.call(s, e); - }; + for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[START_CONTAINER]) + return _traverseCommonStartContainer(c, how); - if (n == 'unload') { - tinymce.unloads.unshift({func : cb}); - return cb; + ++endContainerDepth; } - if (n == 'init') { - if (t.domLoaded) - cb(); - else - t.inits.push(cb); + for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[END_CONTAINER]) + return _traverseCommonEndContainer(c, how); - return cb; + ++startContainerDepth; } - // Store away listener reference - el.push({ - obj : o, - name : n, - func : f, - cfunc : cb, - scope : s - }); + depthDiff = startContainerDepth - endContainerDepth; - t._add(o, n, cb); + startNode = t[START_CONTAINER]; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } - return f; - }, + endNode = t[END_CONTAINER]; + while (depthDiff < 0) { + endNode = endNode.parentNode; + depthDiff++; + } - remove : function(o, n, f) { - var t = this, a = t.events, s = false, r; + // ascend the ancestor hierarchy until we have a common parent. + for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) { + startNode = sp; + endNode = ep; + } - // Handle array - if (o && o.hasOwnProperty && o instanceof Array) { - r = []; + return _traverseCommonAncestors(startNode, endNode, how); + }; - each(o, function(o) { - o = DOM.get(o); - r.push(t.remove(o, n, f)); - }); + function _traverseSameContainer(how) { + var frag, s, sub, n, cnt, sibling, xferNode; - return r; - } + if (how != DELETE) + frag = doc.createDocumentFragment(); - o = DOM.get(o); + // If selection is empty, just return the fragment + if (t[START_OFFSET] == t[END_OFFSET]) + return frag; - each(a, function(e, i) { - if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) { - a.splice(i, 1); - t._remove(o, n, e.cfunc); - s = true; - return false; + // Text node needs special case handling + if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) { + // get the substring + s = t[START_CONTAINER].nodeValue; + sub = s.substring(t[START_OFFSET], t[END_OFFSET]); + + // set the original text node to its new value + if (how != CLONE) { + t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]); + + // Nothing is partially selected, so collapse to start point + t.collapse(TRUE); } - }); - return s; - }, + if (how == DELETE) + return; - clear : function(o) { - var t = this, a = t.events, i, e; + frag.appendChild(doc.createTextNode(sub)); + return frag; + } - if (o) { - o = DOM.get(o); + // Copy nodes between the start/end offsets. + n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); + cnt = t[END_OFFSET] - t[START_OFFSET]; - for (i = a.length - 1; i >= 0; i--) { - e = a[i]; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); - if (e.obj === o) { - t._remove(e.obj, e.name, e.cfunc); - e.obj = e.cfunc = null; - a.splice(i, 1); - } + if (frag) + frag.appendChild( xferNode ); + + --cnt; + n = sibling; + } + + // Nothing is partially selected, so collapse to start point + if (how != CLONE) + t.collapse(TRUE); + + return frag; + }; + + function _traverseCommonStartContainer(endAncestor, how) { + var frag, n, endIdx, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + endIdx = nodeIndex(endAncestor); + cnt = endIdx - t[START_OFFSET]; + + if (cnt <= 0) { + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); } + + return frag; } - }, - cancel : function(e) { - if (!e) - return false; + n = endAncestor.previousSibling; + while (cnt > 0) { + sibling = n.previousSibling; + xferNode = _traverseFullySelected(n, how); - this.stop(e); + if (frag) + frag.insertBefore(xferNode, frag.firstChild); - return this.prevent(e); - }, + --cnt; + n = sibling; + } - stop : function(e) { - if (e.stopPropagation) - e.stopPropagation(); - else - e.cancelBubble = true; + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } - return false; - }, + return frag; + }; - prevent : function(e) { - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; + function _traverseCommonEndContainer(startAncestor, how) { + var frag, startIdx, n, cnt, sibling, xferNode; - return false; - }, + if (how != DELETE) + frag = doc.createDocumentFragment(); - destroy : function() { - var t = this; + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); - each(t.events, function(e, i) { - t._remove(e.obj, e.name, e.cfunc); - e.obj = e.cfunc = null; - }); + startIdx = nodeIndex(startAncestor); + ++startIdx; // Because we already traversed it - t.events = []; - t = null; - }, + cnt = t[END_OFFSET] - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); - _add : function(o, n, f) { - if (o.attachEvent) - o.attachEvent('on' + n, f); - else if (o.addEventListener) - o.addEventListener(n, f, false); - else - o['on' + n] = f; - }, + if (frag) + frag.appendChild(xferNode); - _remove : function(o, n, f) { - 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 - } + --cnt; + n = sibling; } - }, - _pageInit : function(win) { - var t = this; + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } - // Keep it from running more than once - if (t.domLoaded) - return; + return frag; + }; - t.domLoaded = true; + function _traverseCommonAncestors(startAncestor, endAncestor, how) { + var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; - each(t.inits, function(c) { - c(); - }); + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + commonParent = startAncestor.parentNode; + startOffset = nodeIndex(startAncestor); + endOffset = nodeIndex(endAncestor); + ++startOffset; + + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; + + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = _traverseFullySelected(sibling, how); + + if (frag) + frag.appendChild(n); + + sibling = nextSibling; + --cnt; + } + + n = _traverseRightBoundary(endAncestor, how); + + if (frag) + frag.appendChild(n); + + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } + + return frag; + }; + + function _traverseRightBoundary(root, how) { + var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; + + if (next == root) + return _traverseNode(next, isFullySelected, FALSE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, FALSE, how); + + while (parent) { + while (next) { + prevSibling = next.previousSibling; + clonedChild = _traverseNode(next, isFullySelected, FALSE, how); + + if (how != DELETE) + clonedParent.insertBefore(clonedChild, clonedParent.firstChild); + + isFullySelected = TRUE; + next = prevSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.previousSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseLeftBoundary(root, how) { + var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent; + + if (next == root) + return _traverseNode(next, isFullySelected, TRUE, how); + + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, TRUE, how); + + while (parent) { + while (next) { + nextSibling = next.nextSibling; + clonedChild = _traverseNode(next, isFullySelected, TRUE, how); + + if (how != DELETE) + clonedParent.appendChild(clonedChild); + + isFullySelected = TRUE; + next = nextSibling; + } + + if (parent == root) + return clonedParent; + + next = parent.nextSibling; + parent = parent.parentNode; + + clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how); + + if (how != DELETE) + clonedGrandParent.appendChild(clonedParent); + + clonedParent = clonedGrandParent; + } + }; + + function _traverseNode(n, isFullySelected, isLeft, how) { + var txtValue, newNodeValue, oldNodeValue, offset, newNode; + + if (isFullySelected) + return _traverseFullySelected(n, how); + + if (n.nodeType == 3 /* TEXT_NODE */) { + txtValue = n.nodeValue; + + if (isLeft) { + offset = t[START_OFFSET]; + newNodeValue = txtValue.substring(offset); + oldNodeValue = txtValue.substring(0, offset); + } else { + offset = t[END_OFFSET]; + newNodeValue = txtValue.substring(0, offset); + oldNodeValue = txtValue.substring(offset); + } + + if (how != CLONE) + n.nodeValue = oldNodeValue; + + if (how == DELETE) + return; + + newNode = n.cloneNode(FALSE); + newNode.nodeValue = newNodeValue; + + return newNode; + } + + if (how == DELETE) + return; + + return n.cloneNode(FALSE); + }; + + function _traverseFullySelected(n, how) { + if (how != DELETE) + return how == CLONE ? n.cloneNode(TRUE) : n; + + n.parentNode.removeChild(n); + }; + }; + + ns.Range = Range; +})(tinymce.dom); + +(function() { + function Selection(selection) { + var self = this, dom = selection.dom, TRUE = true, FALSE = false; + + function getPosition(rng, start) { + var checkRng, startIndex = 0, endIndex, inside, + children, child, offset, index, position = -1, parent; + + // Setup test range, collapse it and get the parent + checkRng = rng.duplicate(); + checkRng.collapse(start); + parent = checkRng.parentElement(); + + // Check if the selection is within the right document + if (parent.ownerDocument !== selection.dom.doc) + return; + + // IE will report non editable elements as it's parent so look for an editable one + while (parent.contentEditable === "false") { + parent = parent.parentNode; + } + + // If parent doesn't have any children then return that we are inside the element + if (!parent.hasChildNodes()) { + return {node : parent, inside : 1}; + } + + // Setup node list and endIndex + children = parent.children; + endIndex = children.length - 1; + + // Perform a binary search for the position + while (startIndex <= endIndex) { + index = Math.floor((startIndex + endIndex) / 2); + + // Move selection to node and compare the ranges + child = children[index]; + checkRng.moveToElementText(child); + position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng); + + // Before/after or an exact match + if (position > 0) { + endIndex = index - 1; + } else if (position < 0) { + startIndex = index + 1; + } else { + return {node : child}; + } + } + + // Check if child position is before or we didn't find a position + if (position < 0) { + // No element child was found use the parent element and the offset inside that + if (!child) { + checkRng.moveToElementText(parent); + checkRng.collapse(true); + child = parent; + inside = true; + } else + checkRng.collapse(false); + + checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng); + + // Fix for edge case:
    ..
    ab|c
    + if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) { + checkRng = rng.duplicate(); + checkRng.collapse(start); + + offset = -1; + while (parent == checkRng.parentElement()) { + if (checkRng.move('character', -1) == 0) + break; + + offset++; + } + } + + offset = offset || checkRng.text.replace('\r\n', ' ').length; + } else { + // Child position is after the selection endpoint + checkRng.collapse(true); + checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng); + + // Get the length of the text to find where the endpoint is relative to it's container + offset = checkRng.text.replace('\r\n', ' ').length; + } + + return {node : child, position : position, offset : offset, inside : inside}; + }; + + // Returns a W3C DOM compatible range object by using the IE Range API + function getRange() { + var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail; + + // If selection is outside the current document just return an empty range + element = ieRange.item ? ieRange.item(0) : ieRange.parentElement(); + if (element.ownerDocument != dom.doc) + return domRange; + + collapsed = selection.isCollapsed(); + + // Handle control selection + if (ieRange.item) { + domRange.setStart(element.parentNode, dom.nodeIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + + return domRange; + } + + function findEndPoint(start) { + var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue; + + container = endPoint.node; + offset = endPoint.offset; + + if (endPoint.inside && !container.hasChildNodes()) { + domRange[start ? 'setStart' : 'setEnd'](container, 0); + return; + } + + if (offset === undef) { + domRange[start ? 'setStartBefore' : 'setEndAfter'](container); + return; + } + + if (endPoint.position < 0) { + sibling = endPoint.inside ? container.firstChild : container.nextSibling; + + if (!sibling) { + domRange[start ? 'setStartAfter' : 'setEndAfter'](container); + return; + } + + if (!offset) { + if (sibling.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, 0); + else + domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling); + + return; + } + + // Find the text node and offset + while (sibling) { + nodeValue = sibling.nodeValue; + textNodeOffset += nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + textNodeOffset = nodeValue.length - textNodeOffset; + break; + } + + sibling = sibling.nextSibling; + } + } else { + // Find the text node and offset + sibling = container.previousSibling; + + if (!sibling) + return domRange[start ? 'setStartBefore' : 'setEndBefore'](container); + + // If there isn't any text to loop then use the first position + if (!offset) { + if (container.nodeType == 3) + domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length); + else + domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling); + + return; + } + + while (sibling) { + textNodeOffset += sibling.nodeValue.length; + + // We are at or passed the position we where looking for + if (textNodeOffset >= offset) { + container = sibling; + textNodeOffset -= offset; + break; + } + + sibling = sibling.previousSibling; + } + } + + domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset); + }; + + try { + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } catch (ex) { + // IE has a nasty bug where text nodes might throw "invalid argument" when you + // access the nodeValue or other properties of text nodes. This seems to happend when + // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it. + if (ex.number == -2147024809) { + // Get the current selection + bookmark = self.getBookmark(2); + + // Get start element + tmpRange = ieRange.duplicate(); + tmpRange.collapse(true); + element = tmpRange.parentElement(); + + // Get end element + if (!collapsed) { + tmpRange = ieRange.duplicate(); + tmpRange.collapse(false); + element2 = tmpRange.parentElement(); + element2.innerHTML = element2.innerHTML; + } + + // Remove the broken elements + element.innerHTML = element.innerHTML; + + // Restore the selection + self.moveToBookmark(bookmark); + + // Since the range has moved we need to re-get it + ieRange = selection.getRng(); + + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + } else + throw ex; // Throw other errors + } + + return domRange; + }; + + this.getBookmark = function(type) { + var rng = selection.getRng(), start, end, bookmark = {}; + + function getIndexes(node) { + var node, parent, root, children, i, indexes = []; + + parent = node.parentNode; + root = dom.getRoot().parentNode; + + while (parent != root) { + children = parent.children; + + i = children.length; + while (i--) { + if (node === children[i]) { + indexes.push(i); + break; + } + } + + node = parent; + parent = parent.parentNode; + } + + return indexes; + }; + + function getBookmarkEndPoint(start) { + var position; + + position = getPosition(rng, start); + if (position) { + return { + position : position.position, + offset : position.offset, + indexes : getIndexes(position.node), + inside : position.inside + }; + } + }; + + // Non ubstructive bookmark + if (type === 2) { + // Handle text selection + if (!rng.item) { + bookmark.start = getBookmarkEndPoint(true); + + if (!selection.isCollapsed()) + bookmark.end = getBookmarkEndPoint(); + } else + bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))}; + } + + return bookmark; + }; + + this.moveToBookmark = function(bookmark) { + var rng, body = dom.doc.body; + + function resolveIndexes(indexes) { + var node, i, idx, children; + + node = dom.getRoot(); + for (i = indexes.length - 1; i >= 0; i--) { + children = node.children; + idx = indexes[i]; + + if (idx <= children.length - 1) { + node = children[idx]; + } + } + + return node; + }; + + function setBookmarkEndPoint(start) { + var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef; + + if (endPoint) { + moveLeft = endPoint.position > 0; + + moveRng = body.createTextRange(); + moveRng.moveToElementText(resolveIndexes(endPoint.indexes)); + + offset = endPoint.offset; + if (offset !== undef) { + moveRng.collapse(endPoint.inside || moveLeft); + moveRng.moveStart('character', moveLeft ? -offset : offset); + } else + moveRng.collapse(start); + + rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng); + + if (start) + rng.collapse(true); + } + }; + + if (bookmark.start) { + if (bookmark.start.ctrl) { + rng = body.createControlRange(); + rng.addElement(resolveIndexes(bookmark.start.indexes)); + rng.select(); + } else { + rng = body.createTextRange(); + setBookmarkEndPoint(true); + setBookmarkEndPoint(); + rng.select(); + } + } + }; + + this.addRange = function(rng) { + var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body; + + function setEndPoint(start) { + var container, offset, marker, tmpRng, nodes; + + marker = dom.create('a'); + container = start ? startContainer : endContainer; + offset = start ? startOffset : endOffset; + tmpRng = ieRng.duplicate(); + + if (container == doc || container == doc.documentElement) { + container = body; + offset = 0; + } + + if (container.nodeType == 3) { + container.parentNode.insertBefore(marker, container); + tmpRng.moveToElementText(marker); + tmpRng.moveStart('character', offset); + dom.remove(marker); + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + } else { + nodes = container.childNodes; + + if (nodes.length) { + if (offset >= nodes.length) { + dom.insertAfter(marker, nodes[nodes.length - 1]); + } else { + container.insertBefore(marker, nodes[offset]); + } + + tmpRng.moveToElementText(marker); + } else { + // Empty node selection for example
    |
    + marker = doc.createTextNode('\uFEFF'); + container.appendChild(marker); + tmpRng.moveToElementText(marker.parentNode); + tmpRng.collapse(TRUE); + } + + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + dom.remove(marker); + } + } + + // Setup some shorter versions + startContainer = rng.startContainer; + startOffset = rng.startOffset; + endContainer = rng.endContainer; + endOffset = rng.endOffset; + ieRng = body.createTextRange(); + + // If single element selection then try making a control selection out of it + if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) { + if (startOffset == endOffset - 1) { + try { + ctrlRng = body.createControlRange(); + ctrlRng.addElement(startContainer.childNodes[startOffset]); + ctrlRng.select(); + return; + } catch (ex) { + // Ignore + } + } + } + + // Set start/end point of selection + setEndPoint(true); + setEndPoint(); + + // Select the new range and scroll it into view + ieRng.select(); + }; + + // Expose range method + this.getRangeAt = getRange; + }; + + // Expose the selection object + tinymce.dom.TridentSelection = Selection; +})(); + + +/* + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + context = context || document; + + var origContext = context; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), + soFar = selector, ret, cur, pop, i; + + // Reset the position of the chunker regexp (start from head) + do { + chunker.exec(""); + m = chunker.exec(soFar); + + if ( m ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + } while ( m ); + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + cur = parts.pop(); + pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string", + elem, i = 0, l = checkSet.length; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( ; i < l; i++ ) { + elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck, nodeCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck, nodeCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + part = part.toLowerCase(); + nodeCheck = part; + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return (/h\d/i).test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return (/input|select|textarea|button/i).test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var j = 0, l = not.length; j < l; j++ ) { + if ( not[j] === elem ) { + return false; + } + } + + return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; - t.inits = []; - }, +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); + }; - _wait : function(win) { - var t = this, doc = win.document; +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); +} - // No need since the document is already loaded - if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { - t.domLoaded = 1; - return; - } +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); - // Use IE method - if (doc.attachEvent) { - doc.attachEvent("onreadystatechange", function() { - if (doc.readyState === "complete") { - doc.detachEvent("onreadystatechange", arguments.callee); - t._pageInit(win); - } - }); + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; - if (doc.documentElement.doScroll && win == win.top) { - (function() { - if (t.domLoaded) - return; +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - doc.documentElement.doScroll("left"); - } catch (ex) { - setTimeout(arguments.callee, 0); - return; - } +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || [], i = 0; - t._pageInit(win); - })(); + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( ; array[i]; i++ ) { + ret.push( array[i] ); } - } else if (doc.addEventListener) { - t._add(win, 'DOMContentLoaded', function() { - t._pageInit(win); - }); } + } - t._add(win, 'load', function() { - t._pageInit(win); - }); - }, + return ret; + }; +} - _stoppers : { - preventDefault : function() { - this.returnValue = false; - }, +var sortOrder; - stopPropagation : function() { - this.cancelBubble = true; +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; } + return a.compareDocumentPosition ? -1 : 1; } - }); - Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); - - // Dispatch DOM content loaded event for IE and Safari - Event._wait(window); + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } - tinymce.addUnload(function() { - Event.destroy(); - }); -})(tinymce); + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } -(function(tinymce) { - tinymce.dom.Element = function(id, settings) { - var t = this, dom, el; + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} - t.settings = settings = settings || {}; - t.id = id; - t.dom = dom = settings.dom || tinymce.DOM; +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; - // Only IE leaks DOM references, this is a lot faster - if (!tinymce.isIE) - el = dom.get(t.id); + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; - tinymce.each( - ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + - 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + - 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + - 'isHidden,setHTML,get').split(/,/) - , function(k) { - t[k] = function() { - var a = [id], i; + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; - for (i = 0; i < arguments.length; i++) - a.push(arguments[i]); + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } - a = dom[k].apply(dom, a); - t.update(k); + return ret; +}; - return a; - }; - }); +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date()).getTime(); + form.innerHTML = ""; - tinymce.extend(t, { - on : function(n, f, s) { - return tinymce.dom.Event.add(t.id, n, f, s); - }, + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); - getXY : function() { - return { - x : parseInt(t.getStyle('left')), - y : parseInt(t.getStyle('top')) - }; - }, + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; - getSize : function() { - var n = dom.get(t.id); + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } - return { - w : parseInt(t.getStyle('width') || n.clientWidth), - h : parseInt(t.getStyle('height') || n.clientHeight) - }; - }, + root.removeChild( form ); + root = form = null; // release memory in IE +})(); - moveTo : function(x, y) { - t.setStyles({left : x, top : y}); - }, +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") - moveBy : function(x, y) { - var p = t.getXY(); + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); - t.moveTo(p.x + x, p.y + y); - }, + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); - resizeTo : function(w, h) { - t.setStyles({width : w, height : h}); - }, + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; - resizeBy : function(w, h) { - var s = t.getSize(); + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } - t.resizeTo(s.w + w, s.h + h); - }, + results = tmp; + } - update : function(k) { - var b; + return results; + }; + } - if (tinymce.isIE6 && settings.blocker) { - k = k || ''; + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } - // Ignore getters - if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) - return; + div = null; // release memory in IE +})(); - // Remove blocker on remove - if (k == 'remove') { - dom.remove(t.blocker); - return; - } +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; - if (!t.blocker) { - t.blocker = dom.uniqueId(); - b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); - dom.setStyle(b, 'opacity', 0); - } else - b = dom.get(t.blocker); + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; - dom.setStyles(b, { - left : t.getStyle('left', 1), - top : t.getStyle('top', 1), - width : t.getStyle('width', 1), - height : t.getStyle('height', 1), - display : t.getStyle('display', 1), - zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 - }); - } + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} } - }); - }; -})(tinymce); - -(function(tinymce) { - function trimNl(s) { - return s.replace(/[\n\r]+/g, ''); - }; + + return oldSizzle(query, context, extra, seed); + }; - // Shorten names - var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } - tinymce.create('tinymce.dom.Selection', { - Selection : function(dom, win, serializer) { - var t = this; + div = null; // release memory in IE + })(); +} - t.dom = dom; - t.win = win; - t.serializer = serializer; +(function(){ + var div = document.createElement("div"); - // Add events - each([ - 'onBeforeSetContent', - 'onBeforeGetContent', - 'onSetContent', - 'onGetContent' - ], function(e) { - t[e] = new tinymce.util.Dispatcher(t); - }); + div.innerHTML = "
    "; - // No W3C Range support - if (!t.win.getSelection) - t.tridentSel = new tinymce.dom.TridentSelection(t); + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } - if (tinymce.isIE && dom.boxModel) - this._fixIESelection(); + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; - // Prevent leaks - tinymce.addUnload(t.destroy, t); - }, + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; - getContent : function(s) { - var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; + div = null; // release memory in IE +})(); - s = s || {}; - wb = wa = ''; - s.get = true; - s.format = s.format || 'html'; - t.onBeforeGetContent.dispatch(t, s); +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; - if (s.format == 'text') - return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } - if (r.cloneContents) { - n = r.cloneContents(); + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } - if (n) - e.appendChild(n); - } else if (is(r.item) || is(r.htmlText)) - e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText; - else - e.innerHTML = r.toString(); + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } - // Keep whitespace before and after - if (/^\s/.test(e.innerHTML)) - wb = ' '; + elem = elem[dir]; + } - if (/\s+$/.test(e.innerHTML)) - wa = ' '; + checkSet[i] = match; + } + } +} - s.getInner = true; +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; - s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; - t.onGetContent.dispatch(t, s); + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } - return s.content; - }, + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } - setContent : function(h, s) { - var t = this, r = t.getRng(), c, d = t.win.document; + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } - s = s || {format : 'html'}; - s.set = true; - h = s.content = t.dom.processHTML(h); + elem = elem[dir]; + } - // Dispatch before set content event - t.onBeforeSetContent.dispatch(t, s); - h = s.content; + checkSet[i] = match; + } + } +} - if (r.insertNode) { - // Make caret marker since insertNode places the caret in the beginning of text after insert - h += '_'; +Sizzle.contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; - // Delete and insert new node - - if (r.startContainer == d && r.endContainer == d) { - // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents - d.body.innerHTML = h; - } else { - r.deleteContents(); - if (d.body.childNodes.length == 0) { - d.body.innerHTML = h; - } else { - // createContextualFragment doesn't exists in IE 9 DOMRanges - if (r.createContextualFragment) { - r.insertNode(r.createContextualFragment(h)); - } else { - // Fake createContextualFragment call in IE 9 - var frag = d.createDocumentFragment(), temp = d.createElement('div'); +Sizzle.isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; - frag.appendChild(temp); - temp.outerHTML = h; +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; - r.insertNode(frag); - } - } - } + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } - // Move to caret marker - c = t.dom.get('__caret'); - // Make sure we wrap it compleatly, Opera fails with a simple select call - r = d.createRange(); - r.setStartBefore(c); - r.setEndBefore(c); - t.setRng(r); + selector = Expr.relative[selector] ? selector + "*" : selector; - // Remove the caret position - t.dom.remove('__caret'); - } else { - if (r.item) { - // Delete content and get caret text selection - d.execCommand('Delete', false, null); - r = t.getRng(); - } + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } - r.pasteHTML(h); - } + return Sizzle.filter( later, tmpSet ); +}; - // Dispatch set content event - t.onSetContent.dispatch(t, s); - }, +// EXPOSE - getStart : function() { - var rng = this.getRng(), startElement, parentElement, checkRng, node; +window.tinymce.dom.Sizzle = Sizzle; - if (rng.duplicate || rng.item) { - // Control selection, return first item - if (rng.item) - return rng.item(0); +})(); - // Get start element - checkRng = rng.duplicate(); - checkRng.collapse(1); - startElement = checkRng.parentElement(); - // Check if range parent is inside the start element, then return the inner parent element - // This will fix issues when a single element is selected, IE would otherwise return the wrong start element - parentElement = node = rng.parentElement(); - while (node = node.parentNode) { - if (node == startElement) { - startElement = parentElement; - break; - } - } +(function(tinymce) { + // Shorten names + var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; - // If start element is body element try to move to the first child if it exists - if (startElement && startElement.nodeName == 'BODY') - return startElement.firstChild || startElement; + tinymce.create('tinymce.dom.EventUtils', { + EventUtils : function() { + this.inits = []; + this.events = []; + }, - return startElement; - } else { - startElement = rng.startContainer; + add : function(o, n, f, s) { + var cb, t = this, el = t.events, r; - if (startElement.nodeType == 1 && startElement.hasChildNodes()) - startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; + if (n instanceof Array) { + r = []; - if (startElement && startElement.nodeType == 3) - return startElement.parentNode; + each(n, function(n) { + r.push(t.add(o, n, f, s)); + }); - return startElement; + return r; } - }, - - getEnd : function() { - var t = this, r = t.getRng(), e, eo; - if (r.duplicate || r.item) { - if (r.item) - return r.item(0); + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; - r = r.duplicate(); - r.collapse(0); - e = r.parentElement(); + each(o, function(o) { + o = DOM.get(o); + r.push(t.add(o, n, f, s)); + }); - if (e && e.nodeName == 'BODY') - return e.lastChild || e; + return r; + } - return e; - } else { - e = r.endContainer; - eo = r.endOffset; + o = DOM.get(o); - if (e.nodeType == 1 && e.hasChildNodes()) - e = e.childNodes[eo > 0 ? eo - 1 : eo]; + if (!o) + return; - if (e && e.nodeType == 3) - return e.parentNode; + // Setup event callback + cb = function(e) { + // Is all events disabled + if (t.disabled) + return; - return e; - } - }, + e = e || window.event; - getBookmark : function(type, normalized) { - var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; + // Patch in target, preventDefault and stopPropagation in IE it's W3C valid + if (e && isIE) { + if (!e.target) + e.target = e.srcElement; - function findIndex(name, element) { - var index = 0; + // Patch in preventDefault, stopPropagation methods for W3C compatibility + tinymce.extend(e, t._stoppers); + } - each(dom.select(name), function(node, i) { - if (node == element) - index = i; - }); + if (!s) + return f(e); - return index; + return f.call(s, e); }; - if (type == 2) { - function getLocation() { - var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; + if (n == 'unload') { + tinymce.unloads.unshift({func : cb}); + return cb; + } - function getPoint(rng, start) { - var container = rng[start ? 'startContainer' : 'endContainer'], - offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; + if (n == 'init') { + if (t.domLoaded) + cb(); + else + t.inits.push(cb); - if (container.nodeType == 3) { - if (normalized) { - for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) - offset += node.nodeValue.length; - } + return cb; + } - point.push(offset); - } else { - childNodes = container.childNodes; + // Store away listener reference + el.push({ + obj : o, + name : n, + func : f, + cfunc : cb, + scope : s + }); - if (offset >= childNodes.length && childNodes.length) { - after = 1; - offset = Math.max(0, childNodes.length - 1); - } + t._add(o, n, cb); - point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); - } + return f; + }, - for (; container && container != root; container = container.parentNode) - point.push(t.dom.nodeIndex(container, normalized)); + remove : function(o, n, f) { + var t = this, a = t.events, s = false, r; - return point; - }; + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; - bookmark.start = getPoint(rng, true); + each(o, function(o) { + o = DOM.get(o); + r.push(t.remove(o, n, f)); + }); - if (!t.isCollapsed()) - bookmark.end = getPoint(rng); + return r; + } - return bookmark; - }; + o = DOM.get(o); - return getLocation(); - } + each(a, function(e, i) { + if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) { + a.splice(i, 1); + t._remove(o, n, e.cfunc); + s = true; + return false; + } + }); - // Handle simple range - if (type) - return {rng : t.getRng()}; + return s; + }, - rng = t.getRng(); - id = dom.uniqueId(); - collapsed = tinyMCE.activeEditor.selection.isCollapsed(); - styles = 'overflow:hidden;line-height:0px'; + clear : function(o) { + var t = this, a = t.events, i, e; - // Explorer method - if (rng.duplicate || rng.item) { - // Text selection - if (!rng.item) { - rng2 = rng.duplicate(); + if (o) { + o = DOM.get(o); - // Insert start marker - rng.collapse(); - rng.pasteHTML('' + chr + ''); + for (i = a.length - 1; i >= 0; i--) { + e = a[i]; - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.pasteHTML('' + chr + ''); + if (e.obj === o) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + a.splice(i, 1); } - } else { - // Control selection - element = rng.item(0); - name = element.nodeName; - - return {name : name, index : findIndex(name, element)}; } - } else { - element = t.getNode(); - name = element.nodeName; - if (name == 'IMG') - return {name : name, index : findIndex(name, element)}; + } + }, - // W3C method - rng2 = rng.cloneRange(); + cancel : function(e) { + if (!e) + return false; - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr)); - } + this.stop(e); - rng.collapse(true); - rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr)); - } + return this.prevent(e); + }, - t.moveToBookmark({id : id, keep : 1}); + stop : function(e) { + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; - return {id : id}; + return false; }, - moveToBookmark : function(bookmark) { - var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; - - // Clear selection cache - if (t.tridentSel) - t.tridentSel.destroy(); + prevent : function(e) { + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; - if (bookmark) { - if (bookmark.start) { - rng = dom.createRng(); - root = dom.getRoot(); + return false; + }, - function setEndPoint(start) { - var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; + destroy : function() { + var t = this; - if (point) { - // Find container node - for (node = root, i = point.length - 1; i >= 1; i--) { - children = node.childNodes; + each(t.events, function(e, i) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + }); - if (children.length) - node = children[point[i]]; - } + t.events = []; + t = null; + }, - // Set offset within container node - if (start) - rng.setStart(node, point[0]); - else - rng.setEnd(node, point[0]); - } - }; + _add : function(o, n, f) { + if (o.attachEvent) + o.attachEvent('on' + n, f); + else if (o.addEventListener) + o.addEventListener(n, f, false); + else + o['on' + n] = f; + }, - setEndPoint(true); - setEndPoint(); + _remove : function(o, n, f) { + 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 + } + } + }, - t.setRng(rng); - } else if (bookmark.id) { - function restoreEndPoint(suffix) { - var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; + _pageInit : function(win) { + var t = this; - if (marker) { - node = marker.parentNode; + // Keep it from running more than once + if (t.domLoaded) + return; - if (suffix == 'start') { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker.firstChild; - idx = 1; - } + t.domLoaded = true; - startContainer = endContainer = node; - startOffset = endOffset = idx; - } else { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker.firstChild; - idx = 1; - } + each(t.inits, function(c) { + c(); + }); - endContainer = node; - endOffset = idx; - } + t.inits = []; + }, - if (!keep) { - prev = marker.previousSibling; - next = marker.nextSibling; + _wait : function(win) { + var t = this, doc = win.document; - // Remove all marker text nodes - each(tinymce.grep(marker.childNodes), function(node) { - if (node.nodeType == 3) - node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); - }); + // No need since the document is already loaded + if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { + t.domLoaded = 1; + return; + } - // Remove marker but keep children if for example contents where inserted into the marker - // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature - while (marker = dom.get(bookmark.id + '_' + suffix)) - dom.remove(marker, 1); + // Use IE method + if (doc.attachEvent) { + doc.attachEvent("onreadystatechange", function() { + if (doc.readyState === "complete") { + doc.detachEvent("onreadystatechange", arguments.callee); + t._pageInit(win); + } + }); - // If siblings are text nodes then merge them unless it's Opera since it some how removes the node - // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact - if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { - idx = prev.nodeValue.length; - prev.appendData(next.nodeValue); - dom.remove(next); + if (doc.documentElement.doScroll && win == win.top) { + (function() { + if (t.domLoaded) + return; - if (suffix == 'start') { - startContainer = endContainer = prev; - startOffset = endOffset = idx; - } else { - endContainer = prev; - endOffset = idx; - } - } - } + try { + // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author. + // http://javascript.nwbox.com/IEContentLoaded/ + doc.documentElement.doScroll("left"); + } catch (ex) { + setTimeout(arguments.callee, 0); + return; } - }; - function addBogus(node) { - // Adds a bogus BR element for empty block elements - // on non IE browsers just to have a place to put the caret - if (!isIE && dom.isBlock(node) && !node.innerHTML) - node.innerHTML = '
    '; + t._pageInit(win); + })(); + } + } else if (doc.addEventListener) { + t._add(win, 'DOMContentLoaded', function() { + t._pageInit(win); + }); + } - return node; - }; + t._add(win, 'load', function() { + t._pageInit(win); + }); + }, - // Restore start/end points - restoreEndPoint('start'); - restoreEndPoint('end'); + _stoppers : { + preventDefault : function() { + this.returnValue = false; + }, - if (startContainer) { - rng = dom.createRng(); - rng.setStart(addBogus(startContainer), startOffset); - rng.setEnd(addBogus(endContainer), endOffset); - t.setRng(rng); - } - } else if (bookmark.name) { - t.select(dom.select(bookmark.name)[bookmark.index]); - } else if (bookmark.rng) - t.setRng(bookmark.rng); + stopPropagation : function() { + this.cancelBubble = true; } - }, + } + }); - select : function(node, content) { - var t = this, dom = t.dom, rng = dom.createRng(), idx; + Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); - idx = dom.nodeIndex(node); - rng.setStart(node.parentNode, idx); - rng.setEnd(node.parentNode, idx + 1); + // Dispatch DOM content loaded event for IE and Safari + Event._wait(window); - // Find first/last text node or BR element - if (content) { - function setPoint(node, start) { - var walker = new tinymce.dom.TreeWalker(node, node); + tinymce.addUnload(function() { + Event.destroy(); + }); +})(tinymce); - do { - // Text node - if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { - if (start) - rng.setStart(node, 0); - else - rng.setEnd(node, node.nodeValue.length); +(function(tinymce) { + tinymce.dom.Element = function(id, settings) { + var t = this, dom, el; - return; - } + t.settings = settings = settings || {}; + t.id = id; + t.dom = dom = settings.dom || tinymce.DOM; - // BR element - if (node.nodeName == 'BR') { - if (start) - rng.setStartBefore(node); - else - rng.setEndBefore(node); + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = dom.get(t.id); - return; - } - } while (node = (start ? walker.next() : walker.prev())); - }; + tinymce.each( + ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + + 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + + 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + + 'isHidden,setHTML,get').split(/,/) + , function(k) { + t[k] = function() { + var a = [id], i; - setPoint(node, 1); - setPoint(node); - } + for (i = 0; i < arguments.length; i++) + a.push(arguments[i]); - t.setRng(rng); + a = dom[k].apply(dom, a); + t.update(k); - return node; - }, + return a; + }; + }); - isCollapsed : function() { - var t = this, r = t.getRng(), s = t.getSel(); + tinymce.extend(t, { + on : function(n, f, s) { + return tinymce.dom.Event.add(t.id, n, f, s); + }, - if (!r || r.item) - return false; + getXY : function() { + return { + x : parseInt(t.getStyle('left')), + y : parseInt(t.getStyle('top')) + }; + }, - if (r.compareEndPoints) - return r.compareEndPoints('StartToEnd', r) === 0; + getSize : function() { + var n = dom.get(t.id); - return !s || r.collapsed; - }, + return { + w : parseInt(t.getStyle('width') || n.clientWidth), + h : parseInt(t.getStyle('height') || n.clientHeight) + }; + }, + + moveTo : function(x, y) { + t.setStyles({left : x, top : y}); + }, - collapse : function(b) { - var t = this, r = t.getRng(), n; + moveBy : function(x, y) { + var p = t.getXY(); - // Control range on IE - if (r.item) { - n = r.item(0); - r = this.win.document.body.createTextRange(); - r.moveToElementText(n); - } + t.moveTo(p.x + x, p.y + y); + }, - r.collapse(!!b); - t.setRng(r); - }, + resizeTo : function(w, h) { + t.setStyles({width : w, height : h}); + }, - getSel : function() { - var t = this, w = this.win; + resizeBy : function(w, h) { + var s = t.getSize(); - return w.getSelection ? w.getSelection() : w.document.selection; - }, + t.resizeTo(s.w + w, s.h + h); + }, - getRng : function(w3c) { - var t = this, s, r, elm, doc = t.win.document; + update : function(k) { + var b; - // Found tridentSel object then we need to use that one - if (w3c && t.tridentSel) - return t.tridentSel.getRangeAt(0); + if (tinymce.isIE6 && settings.blocker) { + k = k || ''; - try { - if (s = t.getSel()) - r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange()); - } catch (ex) { - // IE throws unspecified error here if TinyMCE is placed in a frame/iframe - } + // Ignore getters + if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) + return; - // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet - if (tinymce.isIE && r.setStart && doc.selection.createRange().item) { - elm = doc.selection.createRange().item(0); - r = doc.createRange(); - r.setStartBefore(elm); - r.setEndAfter(elm); - } + // Remove blocker on remove + if (k == 'remove') { + dom.remove(t.blocker); + return; + } - // No range found then create an empty one - // This can occur when the editor is placed in a hidden container element on Gecko - // Or on IE when there was an exception - if (!r) - r = doc.createRange ? doc.createRange() : doc.body.createTextRange(); + if (!t.blocker) { + t.blocker = dom.uniqueId(); + b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'}); + dom.setStyle(b, 'opacity', 0); + } else + b = dom.get(t.blocker); - if (t.selectedRange && t.explicitRange) { - if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) { - // Safari, Opera and Chrome only ever select text which causes the range to change. - // This lets us use the originally set range if the selection hasn't been changed by the user. - r = t.explicitRange; - } else { - t.selectedRange = null; - t.explicitRange = null; + dom.setStyles(b, { + left : t.getStyle('left', 1), + top : t.getStyle('top', 1), + width : t.getStyle('width', 1), + height : t.getStyle('height', 1), + display : t.getStyle('display', 1), + zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1 + }); } } - return r; - }, - - setRng : function(r) { - var s, t = this; - - if (!t.tridentSel) { - s = t.getSel(); + }); + }; +})(tinymce); - if (s) { - t.explicitRange = r; - s.removeAllRanges(); - s.addRange(r); - t.selectedRange = s.getRangeAt(0); - } - } else { - // Is W3C Range - if (r.cloneRange) { - t.tridentSel.addRange(r); - return; - } +(function(tinymce) { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; - // Is IE specific range - try { - r.select(); - } catch (ex) { - // Needed for some odd IE bug #1843306 - } - } - }, + // Shorten names + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; - setNode : function(n) { + tinymce.create('tinymce.dom.Selection', { + Selection : function(dom, win, serializer) { var t = this; - t.setContent(t.dom.getOuterHTML(n)); - - return n; - }, + t.dom = dom; + t.win = win; + t.serializer = serializer; - getNode : function() { - var t = this, rng = t.getRng(), sel = t.getSel(), elm; + // Add events + each([ + 'onBeforeSetContent', - if (rng.setStart) { - // Range maybe lost after the editor is made visible again - if (!rng) - return t.dom.getRoot(); + 'onBeforeGetContent', - elm = rng.commonAncestorContainer; + 'onSetContent', - // Handle selection a image or other control like element such as anchors - if (!rng.collapsed) { - if (rng.startContainer == rng.endContainer) { - if (rng.startOffset - rng.endOffset < 2) { - if (rng.startContainer.hasChildNodes()) - elm = rng.startContainer.childNodes[rng.startOffset]; - } - } + 'onGetContent' + ], function(e) { + t[e] = new tinymce.util.Dispatcher(t); + }); - // If the anchor node is a element instead of a text node then return this element - if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) - return sel.anchorNode.childNodes[sel.anchorOffset]; - } + // No W3C Range support + if (!t.win.getSelection) + t.tridentSel = new tinymce.dom.TridentSelection(t); - if (elm && elm.nodeType == 3) - return elm.parentNode; + if (tinymce.isIE && dom.boxModel) + this._fixIESelection(); - return elm; - } + // Prevent leaks + tinymce.addUnload(t.destroy, t); + }, - return rng.item ? rng.item(0) : rng.parentElement(); + setCursorLocation: function(node, offset) { + var t = this; var r = t.dom.createRng(); + r.setStart(node, offset); + r.setEnd(node, offset); + t.setRng(r); + t.collapse(false); }, + getContent : function(s) { + var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; - getSelectedBlocks : function(st, en) { - var t = this, dom = t.dom, sb, eb, n, bl = []; + s = s || {}; + wb = wa = ''; + s.get = true; + s.format = s.format || 'html'; + s.forced_root_block = ''; + t.onBeforeGetContent.dispatch(t, s); - sb = dom.getParent(st || t.getStart(), dom.isBlock); - eb = dom.getParent(en || t.getEnd(), dom.isBlock); + if (s.format == 'text') + return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); - if (sb) - bl.push(sb); + if (r.cloneContents) { + n = r.cloneContents(); - if (sb && eb && sb != eb) { - n = sb; + if (n) + e.appendChild(n); + } else if (is(r.item) || is(r.htmlText)) { + // IE will produce invalid markup if elements are present that + // it doesn't understand like custom elements or HTML5 elements. + // Adding a BR in front of the contents and then remoiving it seems to fix it though. + e.innerHTML = '
    ' + (r.item ? r.item(0).outerHTML : r.htmlText); + e.removeChild(e.firstChild); + } else + e.innerHTML = r.toString(); - while ((n = n.nextSibling) && n != eb) { - if (dom.isBlock(n)) - bl.push(n); - } - } + // Keep whitespace before and after + if (/^\s/.test(e.innerHTML)) + wb = ' '; - if (eb && sb != eb) - bl.push(eb); + if (/\s+$/.test(e.innerHTML)) + wa = ' '; - return bl; + s.getInner = true; + + s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; + t.onGetContent.dispatch(t, s); + + return s.content; }, - destroy : function(s) { - var t = this; + setContent : function(content, args) { + var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; - t.win = null; + args = args || {format : 'html'}; + args.set = true; + content = args.content = content; - if (t.tridentSel) - t.tridentSel.destroy(); + // Dispatch before set content event + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); - // Manual destroy then remove unload handler - if (!s) - tinymce.removeUnload(t.destroy); - }, + content = args.content; - // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode - _fixIESelection : function() { - var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng; + if (rng.insertNode) { + // Make caret marker since insertNode places the caret in the beginning of text after insert + content += '_'; - // Make HTML element unselectable since we are going to handle selection by hand - doc.documentElement.unselectable = true; + // Delete and insert new node + if (rng.startContainer == doc && rng.endContainer == doc) { + // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents + doc.body.innerHTML = content; + } else { + rng.deleteContents(); - // Return range from point or null if it failed - function rngFromPoint(x, y) { - var rng = body.createTextRange(); + if (doc.body.childNodes.length == 0) { + doc.body.innerHTML = content; + } else { + // createContextualFragment doesn't exists in IE 9 DOMRanges + if (rng.createContextualFragment) { + rng.insertNode(rng.createContextualFragment(content)); + } else { + // Fake createContextualFragment call in IE 9 + frag = doc.createDocumentFragment(); + temp = doc.createElement('div'); - try { - rng.moveToPoint(x, y); - } catch (ex) { - // IE sometimes throws and exception, so lets just ignore it - rng = null; + frag.appendChild(temp); + temp.outerHTML = content; + + rng.insertNode(frag); + } + } } - return rng; - }; + // Move to caret marker + caretNode = self.dom.get('__caret'); - // Fires while the selection is changing - function selectionChange(e) { - var pointRng; + // Make sure we wrap it compleatly, Opera fails with a simple select call + rng = doc.createRange(); + rng.setStartBefore(caretNode); + rng.setEndBefore(caretNode); + self.setRng(rng); - // Check if the button is down or not - if (e.button) { - // Create range from mouse position - pointRng = rngFromPoint(e.x, e.y); + // Remove the caret position + self.dom.remove('__caret'); - if (pointRng) { - // Check if pointRange is before/after selection then change the endPoint - if (pointRng.compareEndPoints('StartToStart', startRng) > 0) - pointRng.setEndPoint('StartToStart', startRng); - else - pointRng.setEndPoint('EndToEnd', startRng); + try { + self.setRng(rng); + } catch (ex) { + // Might fail on Opera for some odd reason + } + } else { + if (rng.item) { + // Delete content and get caret text selection + doc.execCommand('Delete', false, null); + rng = self.getRng(); + } - pointRng.select(); - } + // Explorer removes spaces from the beginning of pasted contents + if (/^\s+/.test(content)) { + rng.pasteHTML('_' + content); + self.dom.remove('__mce_tmp'); } else - endSelection(); + rng.pasteHTML(content); } - // Removes listeners - function endSelection() { - dom.unbind(doc, 'mouseup', endSelection); - dom.unbind(doc, 'mousemove', selectionChange); - started = 0; - }; + // Dispatch set content event + if (!args.no_events) + self.onSetContent.dispatch(self, args); + }, - // Detect when user selects outside BODY - dom.bind(doc, 'mousedown', function(e) { - if (e.target.nodeName === 'HTML') { - if (started) - endSelection(); + getStart : function() { + var rng = this.getRng(), startElement, parentElement, checkRng, node; - started = 1; + if (rng.duplicate || rng.item) { + // Control selection, return first item + if (rng.item) + return rng.item(0); - // Setup start position - startRng = rngFromPoint(e.x, e.y); - if (startRng) { - // Listen for selection change events - dom.bind(doc, 'mouseup', endSelection); - dom.bind(doc, 'mousemove', selectionChange); + // Get start element + checkRng = rng.duplicate(); + checkRng.collapse(1); + startElement = checkRng.parentElement(); - dom.win.focus(); - startRng.select(); + // Check if range parent is inside the start element, then return the inner parent element + // This will fix issues when a single element is selected, IE would otherwise return the wrong start element + parentElement = node = rng.parentElement(); + while (node = node.parentNode) { + if (node == startElement) { + startElement = parentElement; + break; } } - }); - } - }); -})(tinymce); -(function(tinymce) { - tinymce.create('tinymce.dom.XMLWriter', { - node : null, - - XMLWriter : function(s) { - // Get XML document - function getXML() { - var i = document.implementation; - - if (!i || !i.createDocument) { - // Try IE objects - try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {} - try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {} - } else - return i.createDocument('', '', null); - }; + return startElement; + } else { + startElement = rng.startContainer; - this.doc = getXML(); - - // Since Opera and WebKit doesn't escape > into > we need to do it our self to normalize the output for all browsers - this.valid = tinymce.isOpera || tinymce.isWebKit; + if (startElement.nodeType == 1 && startElement.hasChildNodes()) + startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; + + if (startElement && startElement.nodeType == 3) + return startElement.parentNode; - this.reset(); + return startElement; + } }, - reset : function() { - var t = this, d = t.doc; + getEnd : function() { + var t = this, r = t.getRng(), e, eo; - if (d.firstChild) - d.removeChild(d.firstChild); + if (r.duplicate || r.item) { + if (r.item) + return r.item(0); - t.node = d.appendChild(d.createElement("html")); - }, + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); - writeStartElement : function(n) { - var t = this; + if (e && e.nodeName == 'BODY') + return e.lastChild || e; - t.node = t.node.appendChild(t.doc.createElement(n)); - }, + return e; + } else { + e = r.endContainer; + eo = r.endOffset; - writeAttribute : function(n, v) { - if (this.valid) - v = v.replace(/>/g, '%MCGT%'); + if (e.nodeType == 1 && e.hasChildNodes()) + e = e.childNodes[eo > 0 ? eo - 1 : eo]; - this.node.setAttribute(n, v); - }, + if (e && e.nodeType == 3) + return e.parentNode; - writeEndElement : function() { - this.node = this.node.parentNode; + return e; + } }, - writeFullEndElement : function() { - var t = this, n = t.node; + getBookmark : function(type, normalized) { + var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; - n.appendChild(t.doc.createTextNode("")); - t.node = n.parentNode; - }, + function findIndex(name, element) { + var index = 0; - writeText : function(v) { - if (this.valid) - v = v.replace(/>/g, '%MCGT%'); + each(dom.select(name), function(node, i) { + if (node == element) + index = i; + }); - this.node.appendChild(this.doc.createTextNode(v)); - }, + return index; + }; - writeCDATA : function(v) { - this.node.appendChild(this.doc.createCDATASection(v)); - }, + if (type == 2) { + function getLocation() { + var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; - writeComment : function(v) { - // Fix for bug #2035694 - if (tinymce.isIE) - v = v.replace(/^\-|\-$/g, ' '); + function getPoint(rng, start) { + var container = rng[start ? 'startContainer' : 'endContainer'], + offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; - this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' '))); - }, + if (container.nodeType == 3) { + if (normalized) { + for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) + offset += node.nodeValue.length; + } - getContent : function() { - var h; + point.push(offset); + } else { + childNodes = container.childNodes; - h = this.doc.xml || new XMLSerializer().serializeToString(this.doc); - h = h.replace(/<\?[^?]+\?>|]*>|<\/html>||]+>/g, ''); - h = h.replace(/ ?\/>/g, ' />'); + if (offset >= childNodes.length && childNodes.length) { + after = 1; + offset = Math.max(0, childNodes.length - 1); + } - if (this.valid) - h = h.replace(/\%MCGT%/g, '>'); + point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); + } - return h; - } - }); -})(tinymce); + for (; container && container != root; container = container.parentNode) + point.push(t.dom.nodeIndex(container, normalized)); -(function(tinymce) { - var attrsCharsRegExp = /[&\"<>]/g, textCharsRegExp = /[<>&]/g, encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'}; - - tinymce.create('tinymce.dom.StringWriter', { - str : null, - tags : null, - count : 0, - settings : null, - indent : null, - - StringWriter : function(s) { - this.settings = tinymce.extend({ - indent_char : ' ', - indentation : 0 - }, s); + return point; + }; - this.reset(); - }, + bookmark.start = getPoint(rng, true); - reset : function() { - this.indent = ''; - this.str = ""; - this.tags = []; - this.count = 0; - }, + if (!t.isCollapsed()) + bookmark.end = getPoint(rng); - writeStartElement : function(n) { - this._writeAttributesEnd(); - this.writeRaw('<' + n); - this.tags.push(n); - this.inAttr = true; - this.count++; - this.elementCount = this.count; - this.attrs = {}; - }, + return bookmark; + }; - writeAttribute : function(n, v) { - var t = this; + if (t.tridentSel) + return t.tridentSel.getBookmark(type); - if (!t.attrs[n]) { - t.writeRaw(" " + t.encode(n, true) + '="' + t.encode(v, true) + '"'); - t.attrs[n] = v; + return getLocation(); } - }, - writeEndElement : function() { - var n; + // Handle simple range + if (type) + return {rng : t.getRng()}; - if (this.tags.length > 0) { - n = this.tags.pop(); + rng = t.getRng(); + id = dom.uniqueId(); + collapsed = tinyMCE.activeEditor.selection.isCollapsed(); + styles = 'overflow:hidden;line-height:0px'; - if (this._writeAttributesEnd(1)) - this.writeRaw(''); + // Explorer method + if (rng.duplicate || rng.item) { + // Text selection + if (!rng.item) { + rng2 = rng.duplicate(); - if (this.settings.indentation > 0) - this.writeRaw('\n'); - } - }, + try { + // Insert start marker + rng.collapse(); + rng.pasteHTML('' + chr + ''); - writeFullEndElement : function() { - if (this.tags.length > 0) { - this._writeAttributesEnd(); - this.writeRaw(''); + // Insert end marker + if (!collapsed) { + rng2.collapse(false); - if (this.settings.indentation > 0) - this.writeRaw('\n'); - } - }, + // Detect the empty space after block elements in IE and move the end back one character

    ] becomes

    ]

    + rng.moveToElementText(rng2.parentElement()); + if (rng.compareEndPoints('StartToEnd', rng2) == 0) + rng2.move('character', -1); - writeText : function(v) { - this._writeAttributesEnd(); - this.writeRaw(this.encode(v)); - this.count++; - }, + rng2.pasteHTML('' + chr + ''); + } + } catch (ex) { + // IE might throw unspecified error so lets ignore it + return null; + } + } else { + // Control selection + element = rng.item(0); + name = element.nodeName; - writeCDATA : function(v) { - this._writeAttributesEnd(); - this.writeRaw(''); - this.count++; - }, + return {name : name, index : findIndex(name, element)}; + } + } else { + element = t.getNode(); + name = element.nodeName; + if (name == 'IMG') + return {name : name, index : findIndex(name, element)}; - writeComment : function(v) { - this._writeAttributesEnd(); - this.writeRaw(''); - this.count++; - }, + // W3C method + rng2 = rng.cloneRange(); - writeRaw : function(v) { - this.str += v; - }, + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); + } - encode : function(s, attr) { - return s.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(v) { - return encodedChars[v]; - }); - }, + rng.collapse(true); + rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); + } + + t.moveToBookmark({id : id, keep : 1}); - getContent : function() { - return this.str; + return {id : id}; }, - _writeAttributesEnd : function(s) { - if (!this.inAttr) - return; + moveToBookmark : function(bookmark) { + var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; - this.inAttr = false; + if (bookmark) { + if (bookmark.start) { + rng = dom.createRng(); + root = dom.getRoot(); - if (s && this.elementCount == this.count) { - this.writeRaw(' />'); - return false; - } + function setEndPoint(start) { + var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; - this.writeRaw('>'); + if (point) { + offset = point[0]; - return true; - } - }); -})(tinymce); + // Find container node + for (node = root, i = point.length - 1; i >= 1; i--) { + children = node.childNodes; -(function(tinymce) { - // Shorten names - var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko; + if (point[i] > children.length - 1) + return; - function wildcardToRE(s) { - return s.replace(/([?+*])/g, '.$1'); - }; + node = children[point[i]]; + } - tinymce.create('tinymce.dom.Serializer', { - Serializer : function(s) { - var t = this; + // Move text offset to best suitable location + if (node.nodeType === 3) + offset = Math.min(point[0], node.nodeValue.length); - t.key = 0; - t.onPreProcess = new Dispatcher(t); - t.onPostProcess = new Dispatcher(t); + // Move element offset to best suitable location + if (node.nodeType === 1) + offset = Math.min(point[0], node.childNodes.length); - try { - t.writer = new tinymce.dom.XMLWriter(); - } catch (ex) { - // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter - t.writer = new tinymce.dom.StringWriter(); - } + // Set offset within container node + if (start) + rng.setStart(node, offset); + else + rng.setEnd(node, offset); + } - // IE9 broke the XML attributes order so it can't be used anymore - if (tinymce.isIE && document.documentMode > 8) { - t.writer = new tinymce.dom.StringWriter(); - } + return true; + }; - // Default settings - t.settings = s = extend({ - dom : tinymce.DOM, - valid_nodes : 0, - node_filter : 0, - attr_filter : 0, - invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/, - closed : /^(br|hr|input|meta|img|link|param|area)$/, - entity_encoding : 'named', - entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,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,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,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,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro', - valid_elements : '*[*]', - extended_valid_elements : 0, - invalid_elements : 0, - fix_table_elements : 1, - fix_list_elements : true, - fix_content_duplication : true, - convert_fonts_to_spans : false, - font_size_classes : 0, - apply_source_formatting : 0, - indent_mode : 'simple', - indent_char : '\t', - indent_levels : 1, - remove_linebreaks : 1, - remove_redundant_brs : 1, - element_format : 'xhtml' - }, s); + if (t.tridentSel) + return t.tridentSel.moveToBookmark(bookmark); - t.dom = s.dom; - t.schema = s.schema; + if (setEndPoint(true) && setEndPoint()) { + t.setRng(rng); + } + } else if (bookmark.id) { + function restoreEndPoint(suffix) { + var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; + + if (marker) { + node = marker.parentNode; + + if (suffix == 'start') { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } - // Use raw entities if no entities are defined - if (s.entity_encoding == 'named' && !s.entities) - s.entity_encoding = 'raw'; + startContainer = endContainer = node; + startOffset = endOffset = idx; + } else { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } - if (s.remove_redundant_brs) { - t.onPostProcess.add(function(se, o) { - // Remove single BR at end of block elements since they get rendered - o.content = o.content.replace(/(
    \s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) { - // Check if it's a single element - if (/^
    \s*<\//.test(a)) - return ''; + endContainer = node; + endOffset = idx; + } - return a; - }); - }); - } + if (!keep) { + prev = marker.previousSibling; + next = marker.nextSibling; - // Remove XHTML element endings i.e. produce crap :) XHTML is better - if (s.element_format == 'html') { - t.onPostProcess.add(function(se, o) { - o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>'); - }); - } + // Remove all marker text nodes + each(tinymce.grep(marker.childNodes), function(node) { + if (node.nodeType == 3) + node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); + }); - if (s.fix_list_elements) { - t.onPreProcess.add(function(se, o) { - var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np; + // Remove marker but keep children if for example contents where inserted into the marker + // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature + while (marker = dom.get(bookmark.id + '_' + suffix)) + dom.remove(marker, 1); - function prevNode(e, n) { - var a = n.split(','), i; + // If siblings are text nodes then merge them unless it's Opera since it some how removes the node + // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact + if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) { + idx = prev.nodeValue.length; + prev.appendData(next.nodeValue); + dom.remove(next); - while ((e = e.previousSibling) != null) { - for (i=0; i' : ' '; - if (!np) { - np = t.dom.create('li'); - np.innerHTML = ' '; - np.appendChild(n); - p.insertBefore(np, p.firstChild); - } else - np.appendChild(n); - } - } - } - }); - } + return node; + }; - if (s.fix_table_elements) { - t.onPreProcess.add(function(se, o) { - // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build - // so Opera users with an older version will have to live with less compaible output not much we can do here - if (!tinymce.isOpera || opera.buildNumber() >= 1767) { - each(t.dom.select('p table', o.node).reverse(), function(n) { - var parent = t.dom.getParent(n.parentNode, 'table,p'); + // Restore start/end points + restoreEndPoint('start'); + restoreEndPoint('end'); - if (parent.nodeName != 'TABLE') { - try { - t.dom.split(parent, n); - } catch (ex) { - // IE can sometimes fire an unknown runtime error so we just ignore it - } - } - }); + if (startContainer) { + rng = dom.createRng(); + rng.setStart(addBogus(startContainer), startOffset); + rng.setEnd(addBogus(endContainer), endOffset); + t.setRng(rng); } - }); + } else if (bookmark.name) { + t.select(dom.select(bookmark.name)[bookmark.index]); + } else if (bookmark.rng) + t.setRng(bookmark.rng); } }, - setEntities : function(s) { - var t = this, a, i, l = {}, v; - - // No need to setup more than once - if (t.entityLookup) - return; - - // Build regex and lookup array - a = s.split(','); - for (i = 0; i < a.length; i += 2) { - v = a[i]; + select : function(node, content) { + var t = this, dom = t.dom, rng = dom.createRng(), idx; - // Don't add default & " etc. - if (v == 34 || v == 38 || v == 60 || v == 62) - continue; + if (node) { + idx = dom.nodeIndex(node); + rng.setStart(node.parentNode, idx); + rng.setEnd(node.parentNode, idx + 1); + + // Find first/last text node or BR element + if (content) { + function setPoint(node, start) { + var walker = new tinymce.dom.TreeWalker(node, node); + + do { + // Text node + if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) { + if (start) + rng.setStart(node, 0); + else + rng.setEnd(node, node.nodeValue.length); - l[String.fromCharCode(a[i])] = a[i + 1]; + return; + } - v = parseInt(a[i]).toString(16); - } + // BR element + if (node.nodeName == 'BR') { + if (start) + rng.setStartBefore(node); + else + rng.setEndBefore(node); - t.entityLookup = l; - }, + return; + } + } while (node = (start ? walker.next() : walker.prev())); + }; - setRules : function(s) { - var t = this; + setPoint(node, 1); + setPoint(node); + } - t._setup(); - t.rules = {}; - t.wildRules = []; - t.validElements = {}; + t.setRng(rng); + } - return t.addRules(s); + return node; }, - addRules : function(s) { - var t = this, dr; + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); - if (!s) - return; + if (!r || r.item) + return false; - t._setup(); + if (r.compareEndPoints) + return r.compareEndPoints('StartToEnd', r) === 0; - each(s.split(','), function(s) { - var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = []; + return !s || r.collapsed; + }, - // Extend with default rules - if (dr) - at = tinymce.extend([], dr.attribs); + collapse : function(to_start) { + var self = this, rng = self.getRng(), node; - // Parse attributes - if (p.length > 1) { - each(p[1].split('|'), function(s) { - var ar = {}, i; + // Control range on IE + if (rng.item) { + node = rng.item(0); + rng = self.win.document.body.createTextRange(); + rng.moveToElementText(node); + } - at = at || []; + rng.collapse(!!to_start); + self.setRng(rng); + }, - // Parse attribute rule - s = s.replace(/::/g, '~'); - s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s); - s[2] = s[2].replace(/~/g, ':'); + getSel : function() { + var t = this, w = this.win; - // Add required attributes - if (s[1] == '!') { - ra = ra || []; - ra.push(s[2]); - } + return w.getSelection ? w.getSelection() : w.document.selection; + }, - // Remove inherited attributes - if (s[1] == '-') { - for (i = 0; i 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange()); + } catch (ex) { + // IE throws unspecified error here if TinyMCE is placed in a frame/iframe + } - // Add validation values - case '<': - ar.validVals = s[4].split('?'); - break; - } + // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet + if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) { + elm = doc.selection.createRange().item(0); + r = doc.createRange(); + r.setStartBefore(elm); + r.setEndAfter(elm); + } - if (/[*.?]/.test(s[2])) { - wat = wat || []; - ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$'); - wat.push(ar); - } else { - ar.name = s[2]; - at.push(ar); - } + // No range found then create an empty one + // This can occur when the editor is placed in a hidden container element on Gecko + // Or on IE when there was an exception + if (!r) + r = doc.createRange ? doc.createRange() : doc.body.createTextRange(); - va.push(s[2]); - }); + if (t.selectedRange && t.explicitRange) { + if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) { + // Safari, Opera and Chrome only ever select text which causes the range to change. + // This lets us use the originally set range if the selection hasn't been changed by the user. + r = t.explicitRange; + } else { + t.selectedRange = null; + t.explicitRange = null; } + } - // Handle element names - each(tn, function(s, i) { - var pr = s.charAt(0), x = 1, ru = {}; - - // Extend with default rule data - if (dr) { - if (dr.noEmpty) - ru.noEmpty = dr.noEmpty; - - if (dr.fullEnd) - ru.fullEnd = dr.fullEnd; - - if (dr.padd) - ru.padd = dr.padd; - } - - // Handle prefixes - switch (pr) { - case '-': - ru.noEmpty = true; - break; - - case '+': - ru.fullEnd = true; - break; - - case '#': - ru.padd = true; - break; - - default: - x = 0; - } - - tn[i] = s = s.substring(x); - t.validElements[s] = 1; - - // Add element name or element regex - if (/[*.?]/.test(tn[0])) { - ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$'); - t.wildRules = t.wildRules || {}; - t.wildRules.push(ru); - } else { - ru.name = tn[0]; - - // Store away default rule - if (tn[0] == '@') - dr = ru; - - t.rules[s] = ru; - } - - ru.attribs = at; + return r; + }, - if (ra) - ru.requiredAttribs = ra; + setRng : function(r) { + var s, t = this; + + if (!t.tridentSel) { + s = t.getSel(); - if (wat) { - // Build valid attributes regexp - s = ''; - each(va, function(v) { - if (s) - s += '|'; + if (s) { + t.explicitRange = r; - s += '(' + wildcardToRE(v) + ')'; - }); - ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$'); - ru.wildAttribs = wat; + try { + s.removeAllRanges(); + } catch (ex) { + // IE9 might throw errors here don't know why } - }); - }); - - // Build valid elements regexp - s = ''; - each(t.validElements, function(v, k) { - if (s) - s += '|'; - - if (k != '@') - s += k; - }); - t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$'); - - //console.debug(t.validElementsRE.toString()); - //console.dir(t.rules); - //console.dir(t.wildRules); - }, - findRule : function(n) { - var t = this, rl = t.rules, i, r; - - t._setup(); - - // Exact match - r = rl[n]; - if (r) - return r; + s.addRange(r); + t.selectedRange = s.getRangeAt(0); + } + } else { + // Is W3C Range + if (r.cloneRange) { + t.tridentSel.addRange(r); + return; + } - // Try wildcards - rl = t.wildRules; - for (i = 0; i < rl.length; i++) { - if (rl[i].nameRE.test(n)) - return rl[i]; + // Is IE specific range + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } } - - return null; }, - findAttribRule : function(ru, n) { - var i, wa = ru.wildAttribs; + setNode : function(n) { + var t = this; - for (i = 0; i < wa.length; i++) { - if (wa[i].nameRE.test(n)) - return wa[i]; - } + t.setContent(t.dom.getOuterHTML(n)); - return null; + return n; }, - serialize : function(n, o) { - var h, t = this, doc, oldDoc, impl, selected; - - t._setup(); - o = o || {}; - o.format = o.format || 'html'; - t.processObj = o; - - // IE looses the selected attribute on option elements so we need to store it - // See: http://support.microsoft.com/kb/829907 - if (isIE) { - selected = []; - each(n.getElementsByTagName('option'), function(n) { - var v = t.dom.getAttrib(n, 'selected'); + getNode : function() { + var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; - selected.push(v ? v : null); - }); - } + // Range maybe lost after the editor is made visible again + if (!rng) + return t.dom.getRoot(); - n = n.cloneNode(true); + if (rng.setStart) { + elm = rng.commonAncestorContainer; - // IE looses the selected attribute on option elements so we need to restore it - if (isIE) { - each(n.getElementsByTagName('option'), function(n, i) { - t.dom.setAttrib(n, 'selected', selected[i]); - }); - } + // Handle selection a image or other control like element such as anchors + if (!rng.collapsed) { + if (rng.startContainer == rng.endContainer) { + if (rng.endOffset - rng.startOffset < 2) { + if (rng.startContainer.hasChildNodes()) + elm = rng.startContainer.childNodes[rng.startOffset]; + } + } - // Nodes needs to be attached to something in WebKit/Opera - // Older builds of Opera crashes if you attach the node to an document created dynamically - // and since we can't feature detect a crash we need to sniff the acutal build number - // This fix will make DOM ranges and make Sizzle happy! - impl = n.ownerDocument.implementation; - if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) { - // Create an empty HTML document - doc = impl.createHTMLDocument(""); + // If the anchor node is a element instead of a text node then return this element + //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) + // return sel.anchorNode.childNodes[sel.anchorOffset]; + + // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent. + // This happens when you double click an underlined word in FireFox. + if (start.nodeType === 3 && end.nodeType === 3) { + function skipEmptyTextNodes(n, forwards) { + var orig = n; + while (n && n.nodeType === 3 && n.length === 0) { + n = forwards ? n.nextSibling : n.previousSibling; + } + return n || orig; + } + if (start.length === rng.startOffset) { + start = skipEmptyTextNodes(start.nextSibling, true); + } else { + start = start.parentNode; + } + if (rng.endOffset === 0) { + end = skipEmptyTextNodes(end.previousSibling, false); + } else { + end = end.parentNode; + } - // Add the element or it's children if it's a body element to the new document - each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) { - doc.body.appendChild(doc.importNode(node, true)); - }); + if (start && start === end) + return start; + } + } - // Grab first child or body element for serialization - if (n.nodeName != 'BODY') - n = doc.body.firstChild; - else - n = doc.body; + if (elm && elm.nodeType == 3) + return elm.parentNode; - // set the new document in DOMUtils so createElement etc works - oldDoc = t.dom.doc; - t.dom.doc = doc; + return elm; } - t.key = '' + (parseInt(t.key) + 1); + return rng.item ? rng.item(0) : rng.parentElement(); + }, - // Pre process - if (!o.no_events) { - o.node = n; - t.onPreProcess.dispatch(t, o); - } + getSelectedBlocks : function(st, en) { + var t = this, dom = t.dom, sb, eb, n, bl = []; - // Serialize HTML DOM into a string - t.writer.reset(); - t._info = o; - t._serializeNode(n, o.getInner); + sb = dom.getParent(st || t.getStart(), dom.isBlock); + eb = dom.getParent(en || t.getEnd(), dom.isBlock); - // Post process - o.content = t.writer.getContent(); + if (sb) + bl.push(sb); - // Restore the old document if it was changed - if (oldDoc) - t.dom.doc = oldDoc; + if (sb && eb && sb != eb) { + n = sb; - if (!o.no_events) - t.onPostProcess.dispatch(t, o); + while ((n = n.nextSibling) && n != eb) { + if (dom.isBlock(n)) + bl.push(n); + } + } - t._postProcess(o); - o.node = null; + if (eb && sb != eb) + bl.push(eb); - return tinymce.trim(o.content); + return bl; }, - // Internal functions + normalize : function() { + var self = this, rng, normalized; - _postProcess : function(o) { - var t = this, s = t.settings, h = o.content, sc = [], p; - - if (o.format == 'html') { - // Protect some elements - p = t._protect({ - content : h, - patterns : [ - {pattern : /(]*>)(.*?)(<\/script>)/g}, - {pattern : /(]*>)(.*?)(<\/noscript>)/g}, - {pattern : /(]*>)(.*?)(<\/style>)/g}, - {pattern : /(]*>)(.*?)(<\/pre>)/g, encode : 1}, - {pattern : /()/g} - ] - }); + // Normalize only on non IE browsers for now + if (tinymce.isIE) + return; - h = p.content; + function normalizeEndPoint(start) { + var container, offset, walker, dom = self.dom, body = dom.getRoot(), node; - // Entity encode - if (s.entity_encoding !== 'raw') - h = t._encode(h); + container = rng[(start ? 'start' : 'end') + 'Container']; + offset = rng[(start ? 'start' : 'end') + 'Offset']; - // Use BR instead of   padded P elements inside editor and use

     

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

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


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

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

    $1

    ');*/ + // If the container is a document move it to the body element + if (container.nodeType === 9) { + container = container.body; + offset = 0; + } - // 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 = tinymce._replace(/

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

    ' : ' 

    ', h); + // If the container is body try move it into the closest text node or position + // TODO: Add more logic here to handle element selection cases + if (container === body) { + // Resolve the index + if (container.hasChildNodes()) { + container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)]; + offset = 0; + + // Don't walk into elements that doesn't have any child nodes like a IMG + if (container.hasChildNodes()) { + // Walk the DOM to find a text node to place the caret at or a BR + node = container; + walker = new tinymce.dom.TreeWalker(container, body); + do { + // Found a text node use that position + if (node.nodeType === 3) { + offset = start ? 0 : node.nodeValue.length - 1; + container = node; + break; + } - if (s.remove_linebreaks) { - h = h.replace(/\r?\n|\r/g, ' '); - h = tinymce._replace(/(<[^>]+>)\s+/g, '$1 ', h); - h = tinymce._replace(/\s+(<\/[^>]+>)/g, ' $1', h); - h = tinymce._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>', h); // Trim block start - h = tinymce._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>', h); // Trim block start - h = tinymce._replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, '', h); // Trim block end - } + // Found a BR element that we can place the caret before + if (node.nodeName === 'BR') { + offset = dom.nodeIndex(node); + container = node.parentNode; + break; + } + } while (node = (start ? walker.next() : walker.prev())); - // Simple indentation - if (s.apply_source_formatting && s.indent_mode == 'simple') { - // Add line breaks before and after block elements - h = tinymce._replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n', h); - h = tinymce._replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>', h); - h = tinymce._replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '\n', h); - h = h.replace(/\n\n/g, '\n'); + normalized = true; + } } } - h = t._unprotect(h, p); + // Set endpoint if it was normalized + if (normalized) + rng['set' + (start ? 'Start' : 'End')](container, offset); + }; - // Restore CDATA sections - h = tinymce._replace(//g, '', h); + rng = self.getRng(); - // Restore the \u00a0 character if raw mode is enabled - if (s.entity_encoding == 'raw') - h = tinymce._replace(/

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

    ', h); + // Normalize the end points + normalizeEndPoint(true); + + if (rng.collapsed) + normalizeEndPoint(); - // Restore noscript elements - h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { - return '' + t.dom.decode(text.replace(//g, '')) + ''; - }); + // Set the selection if it was normalized + if (normalized) { + //console.log(self.dom.dumpRng(rng)); + self.setRng(rng); } - - o.content = h; }, - _serializeNode : function(n, inner) { - var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type, scopeName; - - if (!s.node_filter || s.node_filter(n)) { - switch (n.nodeType) { - case 1: // Element - if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus')) - return; - - iv = keep = false; - hc = n.hasChildNodes(); - nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase(); - - // Get internal type - type = n.getAttribute('_mce_type'); - if (type) { - if (!t._info.cleanup) { - iv = true; - return; - } else - keep = 1; - } - - // Add correct prefix on IE - if (isIE) { - scopeName = n.scopeName; - if (scopeName && scopeName !== 'HTML' && scopeName !== 'html') - nn = scopeName + ':' + nn; - } - - // Remove mce prefix on IE needed for the abbr element - if (nn.indexOf('mce:') === 0) - nn = nn.substring(4); + destroy : function(s) { + var t = this; - // Check if valid - if (!keep) { - if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) { - iv = true; - break; - } - } + t.win = null; - if (isIE) { - // Fix IE content duplication (DOM can have multiple copies of the same node) - if (s.fix_content_duplication) { - if (n._mce_serialized == t.key) - return; + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, - n._mce_serialized = t.key; - } + // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode + _fixIESelection : function() { + var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm; - // 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; - } + // Make HTML element unselectable since we are going to handle selection by hand + doc.documentElement.unselectable = true; - // Check if valid child - if (s.validate_children) { - if (t.elementName && !t.schema.isValid(t.elementName, nn)) { - iv = true; - break; - } + // Return range from point or null if it failed + function rngFromPoint(x, y) { + var rng = body.createTextRange(); - t.elementName = nn; - } + try { + rng.moveToPoint(x, y); + } catch (ex) { + // IE sometimes throws and exception, so lets just ignore it + rng = null; + } - ru = t.findRule(nn); - - // No valid rule for this element could be found then skip it - if (!ru) { - iv = true; - break; - } + return rng; + }; - nn = ru.name || nn; - closed = s.closed.test(nn); + // Fires while the selection is changing + function selectionChange(e) { + var pointRng; - // Skip empty nodes or empty node name in IE - if ((!hc && ru.noEmpty) || (isIE && !nn)) { - iv = true; - break; - } + // Check if the button is down or not + if (e.button) { + // Create range from mouse position + pointRng = rngFromPoint(e.x, e.y); - // Check required - if (ru.requiredAttribs) { - a = ru.requiredAttribs; + if (pointRng) { + // Check if pointRange is before/after selection then change the endPoint + if (pointRng.compareEndPoints('StartToStart', startRng) > 0) + pointRng.setEndPoint('StartToStart', startRng); + else + pointRng.setEndPoint('EndToEnd', startRng); - for (i = a.length - 1; i >= 0; i--) { - if (this.dom.getAttrib(n, a[i]) !== '') - break; - } + pointRng.select(); + } + } else + endSelection(); + } - // None of the required was there - if (i == -1) { - iv = true; - break; - } - } + // Removes listeners + function endSelection() { + var rng = doc.selection.createRange(); - w.writeStartElement(nn); + // If the range is collapsed then use the last start range + if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0) + startRng.select(); - // Add ordered attributes - if (ru.attribs) { - for (i=0, at = ru.attribs, l = at.length; i-1; i--) { - no = at[i]; + // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML + htmlElm = doc.documentElement; + if (htmlElm.scrollHeight > htmlElm.clientHeight) + return; - if (no.specified) { - a = no.nodeName.toLowerCase(); + started = 1; + // Setup start position + startRng = rngFromPoint(e.x, e.y); + if (startRng) { + // Listen for selection change events + dom.bind(doc, 'mouseup', endSelection); + dom.bind(doc, 'mousemove', selectionChange); - if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a)) - continue; + dom.win.focus(); + startRng.select(); + } + } + }); + } + }); +})(tinymce); - ar = t.findAttribRule(ru, a); - v = t._getAttrib(n, ar, a); +(function(tinymce) { + tinymce.dom.Serializer = function(settings, dom, schema) { + var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser; - if (v !== null) - w.writeAttribute(a, v); - } - } - } + // Support the old apply_source_formatting option + if (!settings.apply_source_formatting) + settings.indent = false; - // Keep type attribute - if (type && keep) - w.writeAttribute('_mce_type', type); + settings.remove_trailing_brs = true; - // Write text from script - if (nn === 'script' && tinymce.trim(n.innerHTML)) { - w.writeText('// '); // Padd it with a comment so it will parse on older browsers - w.writeCDATA(n.innerHTML.replace(/|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures - hc = false; - break; - } + // Default DOM and Schema if they are undefined + dom = dom || tinymce.DOM; + schema = schema || new tinymce.html.Schema(settings); + settings.entity_encoding = settings.entity_encoding || 'named'; - // Padd empty nodes with a   - if (ru.padd) { - // If it has only one bogus child, padd it anyway workaround for
    bug - if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) { - if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus')) - w.writeText('\u00a0'); - } else if (!hc) - w.writeText('\u00a0'); // No children then padd it - } + onPreProcess = new tinymce.util.Dispatcher(self); - break; + onPostProcess = new tinymce.util.Dispatcher(self); - case 3: // Text - // Check if valid child - if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text')) - return; + htmlParser = new tinymce.html.DomParser(settings, schema); - return w.writeText(n.nodeValue); + // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed + htmlParser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef; - case 4: // CDATA - return w.writeCDATA(n.nodeValue); + while (i--) { + node = nodes[i]; - case 8: // Comment - return w.writeComment(n.nodeValue); - } - } else if (n.nodeType == 1) - hc = n.hasChildNodes(); + value = node.attributes.map[internalName]; + if (value !== undef) { + // Set external name to internal value and remove internal + node.attr(name, value.length > 0 ? value : null); + node.attr(internalName, null); + } else { + // No internal attribute found then convert the value we have in the DOM + value = node.attributes.map[name]; - if (hc && !closed) { - cn = n.firstChild; + if (name === "style") + value = dom.serializeStyle(dom.parseStyle(value), node.name); + else if (urlConverter) + value = urlConverter.call(urlConverterScope, value, name, node.name); - while (cn) { - t._serializeNode(cn); - t.elementName = nn; - cn = cn.nextSibling; + node.attr(name, value.length > 0 ? value : null); } } + }); - // Write element end - if (!iv) { - if (!closed) - w.writeFullEndElement(); - else - w.writeEndElement(); - } - }, + // Remove internal classes mceItem<..> + htmlParser.addAttributeFilter('class', function(nodes, name) { + var i = nodes.length, node, value; - _protect : function(o) { - var t = this; + while (i--) { + node = nodes[i]; + value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, ''); + node.attr('class', value.length > 0 ? value : null); + } + }); - o.items = o.items || []; + // Remove bookmark elements + htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) { + var i = nodes.length, node; - function enc(s) { - return s.replace(/[\r\n\\]/g, function(c) { - if (c === '\n') - return '\\n'; - else if (c === '\\') - return '\\\\'; + while (i--) { + node = nodes[i]; - return '\\r'; - }); - }; + if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup) + node.remove(); + } + }); - function dec(s) { - return s.replace(/\\[\\rn]/g, function(c) { - if (c === '\\n') - return '\n'; - else if (c === '\\\\') - return '\\'; + // Force script into CDATA sections and remove the mce- prefix also add comments around styles + htmlParser.addNodeFilter('script,style', function(nodes, name) { + var i = nodes.length, node, value; - return '\r'; - }); + function trim(value) { + return value.replace(/()/g, '\n') + .replace(/^[\r\n]*|[\r\n]*$/g, '') + .replace(/^\s*(\/\/\s*|\]\]>|-->|\]\]-->)\s*$/g, ''); }; - each(o.patterns, function(p) { - 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; - })); - }); - - return o; - }, + while (i--) { + node = nodes[i]; + value = node.firstChild ? node.firstChild.value : ''; - _unprotect : function(h, o) { - h = h.replace(/\'; + } + } + }); - return h; - }, + // Convert comments to cdata and handle protected comments + htmlParser.addNodeFilter('#comment', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + + if (node.value.indexOf('[CDATA[') === 0) { + node.name = '#cdata'; + node.type = 4; + node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, ''); + } else if (node.value.indexOf('mce:protected ') === 0) { + node.name = "#text"; + node.type = 3; + node.raw = true; + node.value = unescape(node.value).substr(14); + } + } + }); - _encode : function(h) { - var t = this, s = t.settings, l; + htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) { + var i = nodes.length, node; - // Entity encode - if (s.entity_encoding !== 'raw') { - if (s.entity_encoding.indexOf('named') != -1) { - t.setEntities(s.entities); - l = t.entityLookup; + while (i--) { + node = nodes[i]; + if (node.type === 7) + node.remove(); + else if (node.type === 1) { + if (name === "input" && !("type" in node.attributes.map)) + node.attr('type', 'text'); + } + } + }); - h = h.replace(/[\u007E-\uFFFF]/g, function(a) { - var v; + // Fix list elements, TODO: Replace this later + if (settings.fix_list_elements) { + htmlParser.addNodeFilter('ul,ol', function(nodes, name) { + var i = nodes.length, node, parentNode; - if (v = l[a]) - a = '&' + v + ';'; + while (i--) { + node = nodes[i]; + parentNode = node.parent; - return a; - }); + if (parentNode.name === 'ul' || parentNode.name === 'ol') { + if (node.prev && node.prev.name === 'li') { + node.prev.append(node); + } + } } + }); + } - if (s.entity_encoding.indexOf('numeric') != -1) { - h = h.replace(/[\u007E-\uFFFF]/g, function(a) { - return '&#' + a.charCodeAt(0) + ';'; - }); - } + // Remove internal data attributes + htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) { + var i = nodes.length; + + while (i--) { + nodes[i].attr(name, null); } + }); - return h; - }, + // Return public methods + return { + schema : schema, - _setup : function() { - var t = this, s = this.settings; + addNodeFilter : htmlParser.addNodeFilter, - if (t.done) - return; + addAttributeFilter : htmlParser.addAttributeFilter, - t.done = 1; + onPreProcess : onPreProcess, - t.setRules(s.valid_elements); - t.addRules(s.extended_valid_elements); + onPostProcess : onPostProcess, - if (s.invalid_elements) - t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$'); + serialize : function(node, args) { + var impl, doc, oldDoc, htmlSerializer, content; - if (s.attrib_value_filter) - t.attribValueFilter = s.attribValueFilter; - }, + // Explorer won't clone contents of script and style and the + // selected index of select elements are cleared on a clone operation. + if (isIE && dom.select('script,style,select,map').length > 0) { + content = node.innerHTML; + node = node.cloneNode(false); + dom.setHTML(node, content); + } else + node = node.cloneNode(true); + + // Nodes needs to be attached to something in WebKit/Opera + // Older builds of Opera crashes if you attach the node to an document created dynamically + // and since we can't feature detect a crash we need to sniff the acutal build number + // This fix will make DOM ranges and make Sizzle happy! + impl = node.ownerDocument.implementation; + if (impl.createHTMLDocument) { + // Create an empty HTML document + doc = impl.createHTMLDocument(""); + + // Add the element or it's children if it's a body element to the new document + each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) { + doc.body.appendChild(doc.importNode(node, true)); + }); - _getAttrib : function(n, a, na) { - var i, v; + // Grab first child or body element for serialization + if (node.nodeName != 'BODY') + node = doc.body.firstChild; + else + node = doc.body; - na = na || a.name; + // set the new document in DOMUtils so createElement etc works + oldDoc = dom.doc; + dom.doc = doc; + } - if (a.forcedVal && (v = a.forcedVal)) { - if (v === '{$uid}') - return this.dom.uniqueId(); + args = args || {}; + args.format = args.format || 'html'; - return v; - } + // Pre process + if (!args.no_events) { + args.node = node; + onPreProcess.dispatch(self, args); + } - v = this.dom.getAttrib(n, na); + // Setup serializer + htmlSerializer = new tinymce.html.Serializer(settings, schema); - switch (na) { - case 'rowspan': - case 'colspan': - // Whats the point? Remove usless attribute value - if (v == '1') - v = ''; + // Parse and serialize HTML + args.content = htmlSerializer.serialize( + htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args) + ); - break; - } + // Replace all BOM characters for now until we can find a better solution + if (!args.cleanup) + args.content = args.content.replace(/\uFEFF/g, ''); - if (this.attribValueFilter) - v = this.attribValueFilter(na, v, n); + // Post process + if (!args.no_events) + onPostProcess.dispatch(self, args); - if (a.validVals) { - for (i = a.validVals.length - 1; i >= 0; i--) { - if (v == a.validVals[i]) - break; - } + // Restore the old document if it was changed + if (oldDoc) + dom.doc = oldDoc; - if (i == -1) - return null; - } + args.node = null; - if (v === '' && typeof(a.defaultVal) != 'undefined') { - v = a.defaultVal; + return args.content; + }, - if (v === '{$uid}') - return this.dom.uniqueId(); + addRules : function(rules) { + schema.addValidElements(rules); + }, - return v; - } else { - // Remove internal mceItemXX classes when content is extracted from editor - if (na == 'class' && this.processObj.get) - v = v.replace(/\s?mceItem\w+\s?/g, ''); + setRules : function(rules) { + schema.setValidElements(rules); } - - if (v === '') - return null; - - - return v; - } - }); + }; + }; })(tinymce); - (function(tinymce) { tinymce.dom.ScriptLoader = function(settings) { var QUEUED = 0, @@ -6831,6 +8474,17 @@ window.tinymce.dom.Sizzle = Sizzle; callback(); }; + + function error() { + // Report the error so it's easier for people to spot loading errors + if (typeof(console) !== "undefined" && console.log) + console.log("Failed to load: " + url); + + // We can't mark it as done if there is a load error since + // A) We don't want to produce 404 errors on the server and + // B) the onerror event won't fire on all browsers. + // done(); + }; id = dom.uniqueId(); @@ -6840,7 +8494,7 @@ window.tinymce.dom.Sizzle = Sizzle; // If script is from same domain and we // use IE 6 then use XHR since it's more reliable - if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) { + if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') { tinymce.util.XHR.send({ url : tinymce._addVer(uri.getURI()), success : function(content) { @@ -6855,7 +8509,9 @@ window.tinymce.dom.Sizzle = Sizzle; dom.remove(script); done(); - } + }, + + error : error }); return; @@ -6874,15 +8530,21 @@ window.tinymce.dom.Sizzle = Sizzle; if (!tinymce.isIE) elm.onload = done; - elm.onreadystatechange = function() { - var state = elm.readyState; + // Add onerror event will get fired on some browsers but not all of them + elm.onerror = error; - // Loaded state is passed on IE 6 however there - // are known issues with this method but we can't use - // XHR in a cross domain loading - if (state == 'complete' || state == 'loaded') - done(); - }; + // Opera 9.60 doesn't seem to fire the onreadystate event at correctly + if (!tinymce.isOpera) { + elm.onreadystatechange = function() { + var state = elm.readyState; + + // Loaded state is passed on IE 6 however there + // are known issues with this method but we can't use + // XHR in a cross domain loading + if (state == 'complete' || state == 'loaded') + done(); + }; + } // Most browsers support this feature so we report errors // for those at least to help users track their missing plugins etc @@ -7016,178 +8678,24 @@ tinymce.dom.TreeWalker = function(start_node, root_node) { sibling = parent[sibling_name]; if (sibling) return sibling; - } - } - } - }; - - this.current = function() { - return node; - }; - - this.next = function(shallow) { - return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); - }; - - this.prev = function(shallow) { - return (node = findSibling(node, 'lastChild', 'lastSibling', shallow)); - }; -}; - -(function() { - var transitional = {}; - - function unpack(lookup, data) { - var key; - - function replace(value) { - return value.replace(/[A-Z]+/g, function(key) { - return replace(lookup[key]); - }); - }; - - // Unpack lookup - for (key in lookup) { - if (lookup.hasOwnProperty(key)) - lookup[key] = replace(lookup[key]); - } - - // Unpack and parse data into object map - replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) { - var i, map = {}; - - children = children.split(/\|/); - - for (i = children.length - 1; i >= 0; i--) - map[children[i]] = 1; - - transitional[name] = map; - }); - }; - - // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size - // we will later include the attributes here and use it as a default for valid elements but it - // requires us to rewrite the serializer engine - unpack({ - Z : '#|H|K|N|O|P', - Y : '#|X|form|R|Q', - X : 'p|T|div|U|W|isindex|fieldset|table', - W : 'pre|hr|blockquote|address|center|noframes', - U : 'ul|ol|dl|menu|dir', - ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q', - T : 'h1|h2|h3|h4|h5|h6', - ZB : '#|X|S|Q', - S : 'R|P', - ZA : '#|a|G|J|M|O|P', - R : '#|a|H|K|N|O', - Q : 'noscript|P', - P : 'ins|del|script', - O : 'input|select|textarea|label|button', - N : 'M|L', - M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym', - L : 'sub|sup', - K : 'J|I', - J : 'tt|i|b|u|s|strike', - I : 'big|small|font|basefont', - H : 'G|F', - G : 'br|span|bdo', - F : 'object|applet|img|map|iframe' - }, 'script[]' + - 'style[]' + - 'object[#|param|X|form|a|H|K|N|O|Q]' + - 'param[]' + - 'p[S]' + - 'a[Z]' + - 'br[]' + - 'span[S]' + - 'bdo[S]' + - 'applet[#|param|X|form|a|H|K|N|O|Q]' + - 'h1[S]' + - 'img[]' + - 'map[X|form|Q|area]' + - 'h2[S]' + - 'iframe[#|X|form|a|H|K|N|O|Q]' + - 'h3[S]' + - 'tt[S]' + - 'i[S]' + - 'b[S]' + - 'u[S]' + - 's[S]' + - 'strike[S]' + - 'big[S]' + - 'small[S]' + - 'font[S]' + - 'basefont[]' + - 'em[S]' + - 'strong[S]' + - 'dfn[S]' + - 'code[S]' + - 'q[S]' + - 'samp[S]' + - 'kbd[S]' + - 'var[S]' + - 'cite[S]' + - 'abbr[S]' + - 'acronym[S]' + - 'sub[S]' + - 'sup[S]' + - 'input[]' + - 'select[optgroup|option]' + - 'optgroup[option]' + - 'option[]' + - 'textarea[]' + - 'label[S]' + - 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + - 'h4[S]' + - 'ins[#|X|form|a|H|K|N|O|Q]' + - 'h5[S]' + - 'del[#|X|form|a|H|K|N|O|Q]' + - 'h6[S]' + - 'div[#|X|form|a|H|K|N|O|Q]' + - 'ul[li]' + - 'li[#|X|form|a|H|K|N|O|Q]' + - 'ol[li]' + - 'dl[dt|dd]' + - 'dt[S]' + - 'dd[#|X|form|a|H|K|N|O|Q]' + - 'menu[li]' + - 'dir[li]' + - 'pre[ZA]' + - 'hr[]' + - 'blockquote[#|X|form|a|H|K|N|O|Q]' + - 'address[S|p]' + - 'center[#|X|form|a|H|K|N|O|Q]' + - 'noframes[#|X|form|a|H|K|N|O|Q]' + - 'isindex[]' + - 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' + - 'legend[S]' + - 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' + - 'caption[S]' + - 'col[]' + - 'colgroup[col]' + - 'thead[tr]' + - 'tr[th|td]' + - 'th[#|X|form|a|H|K|N|O|Q]' + - 'form[#|X|a|H|K|N|O|Q]' + - 'noscript[#|X|form|a|H|K|N|O|Q]' + - 'td[#|X|form|a|H|K|N|O|Q]' + - 'tfoot[tr]' + - 'tbody[tr]' + - 'area[]' + - 'base[]' + - 'body[#|X|form|a|H|K|N|O|Q]' - ); + } + } + } + }; - tinymce.dom.Schema = function() { - var t = this, elements = transitional; + this.current = function() { + return node; + }; - t.isValid = function(name, child_name) { - var element = elements[name]; + this.next = function(shallow) { + return (node = findSibling(node, 'firstChild', 'nextSibling', shallow)); + }; - return !!(element && (!child_name || element[child_name])); - }; + this.prev = function(shallow) { + return (node = findSibling(node, 'lastChild', 'previousSibling', shallow)); }; -})(); +}; + (function(tinymce) { tinymce.dom.RangeUtils = function(dom) { var INVISIBLE_CHAR = '\uFEFF'; @@ -7251,7 +8759,7 @@ tinymce.dom.TreeWalker = function(start_node, root_node) { // If index based end position then resolve it if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) - endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)]; + endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)]; // Find common ancestor and end points ancestor = dom.findCommonAncestor(startContainer, endContainer); @@ -7371,12 +8879,158 @@ tinymce.dom.TreeWalker = function(start_node, root_node) { }; })(tinymce); +(function(tinymce) { + var Event = tinymce.dom.Event, each = tinymce.each; + + tinymce.create('tinymce.ui.KeyboardNavigation', { + KeyboardNavigation: function(settings, dom) { + var t = this, root = settings.root, items = settings.items, + enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown, + excludeFromTabOrder = settings.excludeFromTabOrder, + itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId; + + dom = dom || tinymce.DOM; + + itemFocussed = function(evt) { + focussedId = evt.target.id; + }; + + itemBlurred = function(evt) { + dom.setAttrib(evt.target.id, 'tabindex', '-1'); + }; + + rootFocussed = function(evt) { + var item = dom.get(focussedId); + dom.setAttrib(item, 'tabindex', '0'); + item.focus(); + }; + + t.focus = function() { + dom.get(focussedId).focus(); + }; + + t.destroy = function() { + each(items, function(item) { + dom.unbind(dom.get(item.id), 'focus', itemFocussed); + dom.unbind(dom.get(item.id), 'blur', itemBlurred); + }); + + dom.unbind(dom.get(root), 'focus', rootFocussed); + dom.unbind(dom.get(root), 'keydown', rootKeydown); + + items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null; + t.destroy = function() {}; + }; + + t.moveFocus = function(dir, evt) { + var idx = -1, controls = t.controls, newFocus; + + if (!focussedId) + return; + + each(items, function(item, index) { + if (item.id === focussedId) { + idx = index; + return false; + } + }); + + idx += dir; + if (idx < 0) { + idx = items.length - 1; + } else if (idx >= items.length) { + idx = 0; + } + + newFocus = items[idx]; + dom.setAttrib(focussedId, 'tabindex', '-1'); + dom.setAttrib(newFocus.id, 'tabindex', '0'); + dom.get(newFocus.id).focus(); + + if (settings.actOnFocus) { + settings.onAction(newFocus.id); + } + + if (evt) + Event.cancel(evt); + }; + + rootKeydown = function(evt) { + var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32; + + switch (evt.keyCode) { + case DOM_VK_LEFT: + if (enableLeftRight) t.moveFocus(-1); + break; + + case DOM_VK_RIGHT: + if (enableLeftRight) t.moveFocus(1); + break; + + case DOM_VK_UP: + if (enableUpDown) t.moveFocus(-1); + break; + + case DOM_VK_DOWN: + if (enableUpDown) t.moveFocus(1); + break; + + case DOM_VK_ESCAPE: + if (settings.onCancel) { + settings.onCancel(); + Event.cancel(evt); + } + break; + + case DOM_VK_ENTER: + case DOM_VK_RETURN: + case DOM_VK_SPACE: + if (settings.onAction) { + settings.onAction(focussedId); + Event.cancel(evt); + } + break; + } + }; + + // Set up state and listeners for each item. + each(items, function(item, idx) { + var tabindex; + + if (!item.id) { + item.id = dom.uniqueId('_mce_item_'); + } + + if (excludeFromTabOrder) { + dom.bind(item.id, 'blur', itemBlurred); + tabindex = '-1'; + } else { + tabindex = (idx === 0 ? '0' : '-1'); + } + + dom.setAttrib(item.id, 'tabindex', tabindex); + dom.bind(dom.get(item.id), 'focus', itemFocussed); + }); + + // Setup initial state for root element. + if (items[0]){ + focussedId = items[0].id; + } + + dom.setAttrib(root, 'tabindex', '-1'); + + // Setup listeners for root element. + dom.bind(dom.get(root), 'focus', rootFocussed); + dom.bind(dom.get(root), 'keydown', rootKeydown); + } + }); +})(tinymce); (function(tinymce) { // Shorten class names var DOM = tinymce.DOM, is = tinymce.is; tinymce.create('tinymce.ui.Control', { - Control : function(id, s) { + Control : function(id, s, editor) { this.id = id; this.settings = s = s || {}; this.rendered = false; @@ -7385,22 +9039,23 @@ tinymce.dom.TreeWalker = function(start_node, root_node) { this.scope = s.scope || this; this.disabled = 0; this.active = 0; + this.editor = editor; + }, + + setAriaProperty : function(property, value) { + var element = DOM.get(this.id + '_aria') || DOM.get(this.id); + if (element) { + DOM.setAttrib(element, 'aria-' + property, !!value); + } + }, + + focus : function() { + DOM.get(this.id).focus(); }, setDisabled : function(s) { - var e; - if (s != this.disabled) { - e = DOM.get(this.id); - - // Add accessibility title for unavailable actions - if (e && this.settings.unavailable_prefix) { - if (s) { - this.prevTitle = e.title; - e.title = this.settings.unavailable_prefix + ": " + e.title; - } else - e.title = this.prevTitle; - } + this.setAriaProperty('disabled', s); this.setState('Disabled', s); this.setState('Enabled', !s); @@ -7416,6 +9071,7 @@ tinymce.dom.TreeWalker = function(start_node, root_node) { if (s != this.active) { this.setState('Active', s); this.active = s; + this.setAriaProperty('pressed', s); } }, @@ -7473,8 +9129,8 @@ tinymce.dom.TreeWalker = function(start_node, root_node) { }); })(tinymce); tinymce.create('tinymce.ui.Container:tinymce.ui.Control', { - Container : function(id, s) { - this.parent(id, s); + Container : function(id, s, editor) { + this.parent(id, s, editor); this.controls = []; @@ -7498,10 +9154,11 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { Separator : function(id, s) { this.parent(id, s); this.classPrefix = 'mceSeparator'; + this.setDisabled(true); }, renderHTML : function() { - return tinymce.DOM.createHTML('span', {'class' : this.classPrefix}); + return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'}); } }); @@ -7516,6 +9173,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { setSelected : function(s) { this.setState('Selected', s); + this.setAriaProperty('checked', !!s); this.selected = s; }, @@ -7663,12 +9321,20 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { s['class'] = s['class'] || cs['class']; s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x; s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y; + s.keyboard_focus = cs.keyboard_focus; m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s); m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem); return m; }, + + focus : function() { + var t = this; + if (t.keyboardNav) { + t.keyboardNav.focus(); + } + }, update : function() { var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th; @@ -7792,13 +9458,13 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { } }); } + + Event.add(co, 'keydown', t._keyHandler, t); 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 - t._focusIdx = 0; + if (s.keyboard_focus) { + t._setupKeyboardNav(); } }, @@ -7808,6 +9474,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { if (!t.isMenuVisible) return; + if (t.keyboardNav) t.keyboardNav.destroy(); Event.remove(co, 'mouseover', t.mouseOverFunc); Event.remove(co, 'click', t.mouseClickFunc); Event.remove(co, 'keydown', t._keyHandler); @@ -7852,8 +9519,11 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { destroy : function() { var t = this, co = DOM.get('menu_' + t.id); + if (t.keyboardNav) t.keyboardNav.destroy(); Event.remove(co, 'mouseover', t.mouseOverFunc); + Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc); Event.remove(co, 'click', t.mouseClickFunc); + Event.remove(co, 'keydown', t._keyHandler); if (t.element) t.element.remove(); @@ -7864,15 +9534,18 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 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:200000'}); - co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')}); + w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'}); + if (t.settings.parent) { + DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id); + } + co = DOM.add(w, 'div', {role: 'presentation', 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' : 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}); + n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0}); tb = DOM.add(n, 'tbody'); each(t.items, function(o) { @@ -7885,33 +9558,36 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { }, // Internal functions + _setupKeyboardNav : function(){ + var contextMenu, menuItems, t=this; + contextMenu = DOM.select('#menu_' + t.id)[0]; + menuItems = DOM.select('a[role=option]', 'menu_' + t.id); + menuItems.splice(0,0,contextMenu); + t.keyboardNav = new tinymce.ui.KeyboardNavigation({ + root: 'menu_' + t.id, + items: menuItems, + onCancel: function() { + t.hideMenu(); + }, + enableUpDown: true + }); + contextMenu.focus(); + }, - _keyHandler : function(e) { - var t = this, kc = e.keyCode; - - function focus(d) { - var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i]; - - if (e) { - t._focusIdx = i; - e.focus(); - } - }; - - switch (kc) { - case 38: - focus(-1); // Select first link - return; - - case 40: - focus(1); - return; - - case 13: - return; - - case 27: - return this.hideMenu(); + _keyHandler : function(evt) { + var t = this, e; + switch (evt.keyCode) { + case 37: // Left + if (t.settings.parent) { + t.hideMenu(); + t.settings.parent.focus(); + Event.cancel(evt); + } + break; + case 39: // Right + if (t.mouseOverFunc) + t.mouseOverFunc(evt); + break; } }, @@ -7929,8 +9605,13 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { } 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;'}); + n = it = DOM.add(n, s.titleItem ? 'th' : 'td'); + n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'}); + + if (s.parent) { + DOM.setAttrib(a, 'aria-haspopup', 'true'); + DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id); + } DOM.addClass(it, s['class']); // n = DOM.add(n, 'span', {'class' : 'item'}); @@ -7965,8 +9646,8 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { var DOM = tinymce.DOM; tinymce.create('tinymce.ui.Button:tinymce.ui.Control', { - Button : function(id, s) { - this.parent(id, s); + Button : function(id, s, ed) { + this.parent(id, s, ed); this.classPrefix = 'mceButton'; }, @@ -7974,13 +9655,14 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { var cp = this.classPrefix, s = this.settings, h, l; l = DOM.encode(s.label || ''); - h = ''; - - if (s.image) - h += '' + l + ''; + h = ''; + if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) ) + h += '' + DOM.encode(s.title) + '' + l; else - h += '' + (l ? '' + l + '' : '') + ''; + h += '' + (l ? '' + l + '' : ''); + h += ''; + h += ''; return h; }, @@ -7999,10 +9681,10 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher; tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', { - ListBox : function(id, s) { + ListBox : function(id, s, ed) { var t = this; - t.parent(id, s); + t.parent(id, s, ed); t.items = []; @@ -8060,12 +9742,13 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { t.selectedIndex = idx; DOM.setHTML(e, DOM.encode(o.title)); DOM.removeClass(e, 'mceTitle'); + DOM.setAttrib(t.id, 'aria-valuenow', o.title); } else { DOM.setHTML(e, DOM.encode(t.settings.title)); DOM.addClass(e, 'mceTitle'); t.selectedValue = t.selectedIndex = null; + DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title); } - e = 0; } }, @@ -8090,16 +9773,17 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { renderHTML : function() { var h = '', t = this, s = t.settings, cp = t.classPrefix; - 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', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '') + '
    '; + h = ''; + h += ''; + h += ''; + h += ''; return h; }, showMenu : function() { - var t = this, p1, p2, e = DOM.get(this.id), m; + var t = this, p2, e = DOM.get(this.id), m; if (t.isDisabled() || t.items.length == 0) return; @@ -8112,7 +9796,6 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { t.isMenuRendered = true; } - p1 = DOM.getPos(this.settings.menu_container); p2 = DOM.getPos(e); m = t.menu; @@ -8143,6 +9826,8 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { var t = this; if (t.menu && t.menu.isMenuVisible) { + DOM.removeClass(t.id, t.classPrefix + 'Selected'); + // 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; @@ -8165,7 +9850,10 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { max_height : 150 }); - m.onHideMenu.add(t.hideMenu, t); + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); m.add({ title : t.settings.title, @@ -8206,40 +9894,39 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { var t = this, cp = t.classPrefix; Event.add(t.id, 'click', t.showMenu, t); - Event.add(t.id + '_text', 'focus', function() { + Event.add(t.id, 'keydown', function(evt) { + if (evt.keyCode == 32) { // Space + t.showMenu(evt); + Event.cancel(evt); + } + }); + Event.add(t.id, 'focus', function() { if (!t._focused) { - t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) { - var idx = -1, v, kc = e.keyCode; - - // Find current index - each(t.items, function(v, i) { - if (t.selectedValue == v.value) - idx = i; - }); - - // Move up/down - if (kc == 38) - v = t.items[idx - 1]; - else if (kc == 40) - v = t.items[idx + 1]; - else if (kc == 13) { + t.keyDownHandler = Event.add(t.id, 'keydown', function(e) { + if (e.keyCode == 40) { + t.showMenu(); + Event.cancel(e); + } + }); + t.keyPressHandler = Event.add(t.id, 'keypress', function(e) { + var v; + if (e.keyCode == 13) { // Fake select on enter v = t.selectedValue; t.selectedValue = null; // Needs to be null to fake change + Event.cancel(e); t.settings.onselect(v); - return Event.cancel(e); - } - - if (v) { - t.hideMenu(); - t.select(v.value); } }); } t._focused = 1; }); - Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;}); + Event.add(t.id, 'blur', function() { + Event.remove(t.id, 'keydown', t.keyDownHandler); + Event.remove(t.id, 'keypress', t.keyPressHandler); + t._focused = 0; + }); // Old IE doesn't have hover on all elements if (tinymce.isIE6 || !DOM.boxModel) { @@ -8276,6 +9963,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { setDisabled : function(s) { DOM.get(this.id).disabled = s; + this.setAriaProperty('disabled', s); }, isDisabled : function() { @@ -8350,13 +10038,13 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { h += DOM.createHTML('option', {value : it.value}, it.title); }); - h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h); - + h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h); + h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title); return h; }, postRender : function() { - var t = this, ch; + var t = this, ch, changeListenerAdded = true; t.rendered = true; @@ -8378,12 +10066,20 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { var bf; Event.remove(t.id, 'change', ch); + changeListenerAdded = false; bf = Event.add(t.id, 'blur', function() { + if (changeListenerAdded) return; + changeListenerAdded = true; Event.add(t.id, 'change', onChange); Event.remove(t.id, 'blur', bf); }); + //prevent default left and right keys on chrome - so that the keyboard navigation is used. + if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) { + return Event.prevent(e); + } + if (e.keyCode == 13 || e.keyCode == 32) { onChange(e); return Event.cancel(e); @@ -8394,12 +10090,13 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { } }); })(tinymce); + (function(tinymce) { var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', { - MenuButton : function(id, s) { - this.parent(id, s); + MenuButton : function(id, s, ed) { + this.parent(id, s, ed); this.onRenderMenu = new tinymce.util.Dispatcher(this); @@ -8446,7 +10143,10 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { icons : t.settings.icons }); - m.onHideMenu.add(t.hideMenu, t); + m.onHideMenu.add(function() { + t.hideMenu(); + t.focus(); + }); t.onRenderMenu.dispatch(t, m); t.menu = m; @@ -8488,8 +10188,8 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each; tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', { - SplitButton : function(id, s) { - this.parent(id, s); + SplitButton : function(id, s, ed) { + this.parent(id, s, ed); this.classPrefix = 'mceSplitButton'; }, @@ -8499,33 +10199,50 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { h = ''; if (s.image) - h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']}); + h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']}); else 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) + ''; + h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; - h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}); - h += '' + DOM.createHTML('a', {id : t.id + '_open', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; + h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, ''); + h += '' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + ''; h += ''; - - return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h); + h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h); + return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h); }, postRender : function() { - var t = this, s = t.settings; + var t = this, s = t.settings, activate; if (s.onclick) { - Event.add(t.id + '_action', 'click', function() { - if (!t.isDisabled()) + activate = function(evt) { + if (!t.isDisabled()) { s.onclick(t.value); + Event.cancel(evt); + } + }; + Event.add(t.id + '_action', 'click', activate); + Event.add(t.id, ['click', 'keydown'], function(evt) { + var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40; + if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) { + activate(); + Event.cancel(evt); + } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) { + t.showMenu(); + Event.cancel(evt); + } }); } - 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;}); + Event.add(t.id + '_open', 'click', function (evt) { + t.showMenu(); + Event.cancel(evt); + }); + Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;}); + Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;}); // Old IE doesn't have hover on all elements if (tinymce.isIE6 || !DOM.boxModel) { @@ -8546,6 +10263,7 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { Event.clear(this.id + '_action'); Event.clear(this.id + '_open'); + Event.clear(this.id); } }); })(tinymce); @@ -8554,10 +10272,10 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each; tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', { - ColorSplitButton : function(id, s) { + ColorSplitButton : function(id, s, ed) { var t = this; - t.parent(id, s); + t.parent(id, s, ed); t.settings = s = tinymce.extend({ colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF', @@ -8615,30 +10333,31 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { 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, '.mceSplitButtonMenu')) { - DOM.removeClass(t.id, 'mceSplitButtonSelected'); - Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); - Event.remove(t.id + '_menu', 'keydown', t._keyHandler); - DOM.hide(t.id + '_menu'); - } + if (t.isMenuVisible) { + // 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; - t.onHideMenu.dispatch(t); + if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) { + DOM.removeClass(t.id, 'mceSplitButtonSelected'); + Event.remove(DOM.doc, 'mousedown', t.hideMenu, t); + Event.remove(t.id + '_menu', 'keydown', t._keyHandler); + DOM.hide(t.id + '_menu'); + } - t.isMenuVisible = 0; + t.isMenuVisible = 0; + t.onHideMenu.dispatch(); + } }, renderMenu : function() { - var t = this, m, i = 0, s = t.settings, n, tb, tr, w; + var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context; - w = DOM.add(s.menu_container, 'div', {id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); + w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'}); m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'}); DOM.add(m, 'span', {'class' : 'mceMenuLine'}); - n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'}); + n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'}); tb = DOM.add(n, 'tbody'); // Generate color grid @@ -8652,20 +10371,32 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { } n = DOM.add(tr, 'td'); - n = DOM.add(n, 'a', { + role : 'option', href : 'javascript:;', style : { backgroundColor : '#' + c }, - _mce_color : '#' + c + 'title': t.editor.getLang('colors.' + c, c), + 'data-mce-color' : '#' + c }); + + if (t.editor.forcedHighContrastMode) { + n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' }); + if (n.getContext && (context = n.getContext("2d"))) { + context.fillStyle = '#' + c; + context.fillRect(0, 0, 16, 16); + } else { + // No point leaving a canvas element around if it's not supported for drawing on anyway. + DOM.remove(n); + } + } }); 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', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title); + n = DOM.add(n, 'a', {role: 'option', 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); @@ -8674,13 +10405,25 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { } DOM.addClass(m, 'mceColorSplitMenu'); + + new tinymce.ui.KeyboardNavigation({ + root: t.id + '_menu', + items: DOM.select('a', t.id + '_menu'), + onCancel: function() { + t.hideMenu(); + t.focus(); + } + }); + + // Prevent IE from scrolling and hindering click to occur #4019 + Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);}); Event.add(t.id + '_menu', 'click', function(e) { var c; - e = e.target; + e = DOM.getParent(e.target, 'a', tb); - if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color'))) + if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color'))) t.setColor(c); return Event.cancel(e); // Prevent IE auto save warning @@ -8690,13 +10433,17 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { }, setColor : function(c) { + this.displayColor(c); + this.hideMenu(); + this.settings.onselect(c); + }, + + displayColor : function(c) { var t = this; DOM.setStyle(t.id + '_preview', 'backgroundColor', c); t.value = c; - t.hideMenu(); - t.settings.onselect(c); }, postRender : function() { @@ -8717,9 +10464,67 @@ tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', { }); })(tinymce); +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event; +tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', { + renderHTML : function() { + var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings; + + h.push('
    '); + //TODO: ACC test this out - adding a role = application for getting the landmarks working well. + h.push(""); + h.push(''); + each(controls, function(toolbar) { + h.push(toolbar.renderHTML()); + }); + h.push(""); + h.push('
    '); + + return h.join(''); + }, + + focus : function() { + this.keyNav.focus(); + }, + + postRender : function() { + var t = this, items = []; + + each(t.controls, function(toolbar) { + each (toolbar.controls, function(control) { + if (control.id) { + items.push(control); + } + }); + }); + + t.keyNav = new tinymce.ui.KeyboardNavigation({ + root: t.id, + items: items, + onCancel: function() { + t.editor.focus(); + }, + excludeFromTabOrder: !t.settings.tab_focus_toolbar + }); + }, + + destroy : function() { + var self = this; + + self.parent(); + self.keyNav.destroy(); + Event.clear(self.id); + } +}); +})(tinymce); + +(function(tinymce) { +// Shorten class names +var dom = tinymce.DOM, each = tinymce.each; tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { renderHTML : function() { - var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl; + var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl; cl = t.controls; for (i=0; i')); - return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '' + h + ''); + return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '' + h + ''); } }); +})(tinymce); (function(tinymce) { var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each; @@ -8794,37 +10600,83 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, get : function(n) { - return this.lookup[n]; + if (this.lookup[n]) { + return this.lookup[n].instance; + } else { + return undefined; + } + }, + + dependencies : function(n) { + var result; + if (this.lookup[n]) { + result = this.lookup[n].dependencies; + } + return result || []; }, requireLangPack : function(n) { var s = tinymce.settings; - if (s && s.language) + if (s && s.language && s.language_load !== false) tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js'); }, - add : function(id, o) { + add : function(id, o, dependencies) { this.items.push(o); - this.lookup[id] = o; + this.lookup[id] = {instance:o, dependencies:dependencies}; this.onAdd.dispatch(this, id, o); return o; }, + createUrl: function(baseUrl, dep) { + if (typeof dep === "object") { + return dep + } else { + return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix}; + } + }, + + addComponents: function(pluginName, scripts) { + var pluginUrl = this.urls[pluginName]; + tinymce.each(scripts, function(script){ + tinymce.ScriptLoader.add(pluginUrl+"/"+script); + }); + }, load : function(n, u, cb, s) { - var t = this; + var t = this, url = u; + + function loadDependencies() { + var dependencies = t.dependencies(n); + tinymce.each(dependencies, function(dep) { + var newUrl = t.createUrl(u, dep); + t.load(newUrl.resource, newUrl, undefined, undefined); + }); + if (cb) { + if (s) { + cb.call(s); + } else { + cb.call(tinymce.ScriptLoader); + } + } + } if (t.urls[n]) return; + if (typeof u === "object") + url = u.prefix + u.resource + u.suffix; - if (u.indexOf('/') != 0 && u.indexOf('://') == -1) - u = tinymce.baseURL + '/' + u; + if (url.indexOf('/') != 0 && url.indexOf('://') == -1) + url = tinymce.baseURL + '/' + url; - t.urls[n] = u.substring(0, u.lastIndexOf('/')); + t.urls[n] = url.substring(0, url.lastIndexOf('/')); - if (!t.lookup[n]) - tinymce.ScriptLoader.add(u, cb, s); + if (t.lookup[n]) { + loadDependencies(); + } else { + tinymce.ScriptLoader.add(url, loadDependencies, s); + } } }); @@ -9281,7 +11133,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { apply_source_formatting : 1, directionality : 'ltr', forced_root_block : 'p', - valid_elements : '@[id|class|style|title|dir