From ceab24db2a7bf459684a650c287770b4ff090f54 Mon Sep 17 00:00:00 2001 From: brettp Date: Tue, 7 Jun 2011 22:46:47 +0000 Subject: Refs #3510, #9113. Updated tinyMCE version in trunk. git-svn-id: http://code.elgg.org/elgg/trunk@9140 36083f99-b078-4883-b0ff-0f9b5a30f544 --- .../tinymce/jscripts/tiny_mce/tiny_mce_src.js | 12325 +++++++++++-------- 1 file changed, 7089 insertions(+), 5236 deletions(-) (limited to 'mod/tinymce/vendor/tinymce/jscripts/tiny_mce/tiny_mce_src.js') diff --git a/mod/tinymce/vendor/tinymce/jscripts/tiny_mce/tiny_mce_src.js b/mod/tinymce/vendor/tinymce/jscripts/tiny_mce/tiny_mce_src.js index b3eef7a31..c1d47fcb7 100644 --- a/mod/tinymce/vendor/tinymce/jscripts/tiny_mce/tiny_mce_src.js +++ b/mod/tinymce/vendor/tinymce/jscripts/tiny_mce/tiny_mce_src.js @@ -1,13 +1,13 @@ (function(win) { var whiteSpaceRe = /^\s*|\s*$/g, - undefined; + undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1'; var tinymce = { majorVersion : '3', - minorVersion : '3.6', + minorVersion : '4.2', - releaseDate : '2010-05-20', + releaseDate : '2011-04-07', _init : function() { var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v; @@ -52,7 +52,7 @@ } function getBase(n) { - if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) { + if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) { if (/_(src|dev)\.js/g.test(n.src)) t.suffix = '_src'; @@ -103,6 +103,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 +203,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 +211,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]) @@ -428,6 +446,29 @@ return u + v; return u.replace('#', v + '#'); + }, + + // Fix function for IE 9 where regexps isn't working correctly + // Todo: remove me once MS fixes the bug + _replace : function(find, replace, str) { + // On IE9 we have to fake $x replacement + if (isRegExpBroken) { + return str.replace(find, function() { + var val = replace, args = arguments, i; + + for (i = 0; i < args.length - 2; i++) { + if (args[i] === undefined) { + val = val.replace(new RegExp('\\$' + i, 'g'), ''); + } else { + val = val.replace(new RegExp('\\$' + i, 'g'), args[i]); + } + } + + return val; + }); + } + + return str.replace(find, replace); } }; @@ -437,7 +478,10 @@ // Expose tinymce namespace to the global namespace (window) win.tinymce = win.tinyMCE = tinymce; -})(window); + + // Describe the different namespaces + + })(window); tinymce.create('tinymce.util.Dispatcher', { @@ -805,9 +849,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'; @@ -817,7 +863,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) @@ -826,13 +876,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 + ']'; } @@ -840,24 +890,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; @@ -948,7 +1001,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({ @@ -971,5728 +1025,6933 @@ 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 makeMap(str) { - var map = {}, i; + var namedEntities, baseEntities, reverseEntities, + attrsCharsRegExp = /[&\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + rawCharsRegExp = /[<>&\"\']/g, + entityRegExp = /&(#)?([\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" + }; - str = str.split(','); - for (i = str.length; i >= 0; i--) - map[str[i]] = 1; + // Raw entities + baseEntities = { + '"' : '"', + "'" : ''', + '<' : '<', + '>' : '>', + '&' : '&' + }; - return map; + // Reverse lookup table for raw entities + reverseEntities = { + '<' : '<', + '>' : '>', + '&' : '&', + '"' : '"', + ''' : "'" }; - 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" - }, + // Decodes text by using the browser + function nativeDecode(text) { + var elm; - DOMUtils : function(d, s) { - var t = this, globalStyle; + elm = document.createElement("div"); + elm.innerHTML = text; - t.doc = d; - t.win = window; - t.files = {}; - t.cssFlicker = false; - t.counter = 0; - t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat"; - t.stdMode = d.documentMode === 8; + return elm.textContent || elm.innerText || text; + }; - t.settings = s = tinymce.extend({ - keep_values : false, - hex_colors : 1, - process_html : 1 - }, s); + // Build a two way lookup table for the entities + function buildEntitiesLookup(items, radix) { + var i, chr, entity, lookup = {}; - // Fix IE6SP2 flicker and check it failed for pre SP2 - if (tinymce.isIE6) { - try { - d.execCommand('BackgroundImageCache', false, true); - } catch (e) { - t.cssFlicker = true; + if (items) { + items = items.split(','); + radix = radix || 10; + + // Build entities lookup table + for (i = 0; i < items.length; i += 2) { + chr = String.fromCharCode(parseInt(items[i], radix)); + + // Only add non base entities + if (!baseEntities[chr]) { + entity = '&' + items[i + 1] + ';'; + lookup[chr] = entity; + lookup[entity] = chr; } } - // Build styles list - if (s.valid_styles) { - t._styles = {}; + return lookup; + } + }; - // Convert styles into a rule list - each(s.valid_styles, function(value, key) { - t._styles[key] = tinymce.explode(value); - }); - } + // 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; + }); + }, - tinymce.addUnload(t.destroy, t); + encodeAllRaw : function(text) { + return ('' + text).replace(rawCharsRegExp, function(chr) { + return baseEntities[chr] || chr; + }); }, - getRoot : function() { - var t = this, s = t.settings; + 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) + ';'; - return (s && t.get(s.root_element)) || t.doc.body; + return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';'; + }); }, - getViewPort : function(w) { - var d, b; - - w = !w ? this.win : w; - d = w.document; - b = this.boxModel ? d.documentElement : d.body; + encodeNamed : function(text, attr, entities) { + entities = entities || namedEntities; - // 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 - }; + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || chr; + }); }, - getRect : function(e) { - var p, t = this, sr; + getEncodeFunc : function(name, entities) { + var Entities = tinymce.html.Entities; - e = t.get(e); - p = t.getPos(e); - sr = t.getSize(e); + entities = buildEntitiesLookup(entities) || namedEntities; - return { - x : p.x, - y : p.y, - w : sr.w, - h : sr.h + function encodeNamedAndNumeric(text, attr) { + return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) { + return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr; + }); }; - }, - - getSize : function(e) { - var t = this, w, h; - e = t.get(e); - w = t.getStyle(e, 'width'); - h = t.getStyle(e, 'height'); + function encodeCustomNamed(text, attr) { + return Entities.encodeNamed(text, attr, entities); + }; - // Non pixel value, then force offset/clientWidth - if (w.indexOf('px') === -1) - w = 0; + // Replace + with , to be compatible with previous TinyMCE versions + name = tinymce.makeMap(name.replace(/\+/g, ',')); - // Non pixel value, then force offset/clientWidth - if (h.indexOf('px') === -1) - h = 0; + // Named and numeric encoder + if (name.named && name.numeric) + return encodeNamedAndNumeric; - return { - w : parseInt(w) || e.offsetWidth || e.clientWidth, - h : parseInt(h) || e.offsetHeight || e.clientHeight - }; - }, + // Named encoder + if (name.named) { + // Custom names + if (entities) + return encodeCustomNamed; - getParent : function(n, f, r) { - return this.getParents(n, f, r, false); - }, + return Entities.encodeNamed; + } - getParents : function(n, f, r, c) { - var t = this, na, se = t.settings, o = []; + // Numeric + if (name.numeric) + return Entities.encodeNumeric; - n = t.get(n); - c = c === undefined; + // Raw encoder + return Entities.encodeRaw; + }, - if (se.strict_root) - r = r || t.getRoot(); + decode : function(text) { + return text.replace(entityRegExp, function(all, numeric, value) { + if (numeric) { + value = parseInt(value); - // Wrap node name as func - if (is(f, 'string')) { - na = f; + // Support upper UTF + if (value > 0xFFFF) { + value -= 0x10000; - if (f === '*') { - f = function(n) {return n.nodeType == 1;}; - } else { - f = function(n) { - return t.is(n, na); - }; + return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF)); + } else + return asciiMap[value] || String.fromCharCode(value); } - } - while (n) { - if (n == r || !n.nodeType || n.nodeType === 9) - break; + return reverseEntities[all] || namedEntities[all] || nativeDecode(all); + }); + } + }; +})(tinymce); - if (!f || f(n)) { - if (c) - o.push(n); - else - return n; - } +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; - n = n.parentNode; - } + settings = settings || {}; - return c ? o : null; - }, + encodingItems = '\\" \\\' \\; \\: ; : _'.split(' '); + for (i = 0; i < encodingItems.length; i++) { + encodingLookup[encodingItems[i]] = '_' + i; + encodingLookup['_' + i] = encodingItems[i]; + } - get : function(e) { - var n; + function toHex(match, r, g, b) { + function hex(val) { + val = parseInt(val).toString(16); - if (e && this.doc && typeof(e) == 'string') { - n = e; - e = this.doc.getElementById(e); + return val.length > 1 ? val : '0' + val; // 0 -> 00 + }; - // 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 '#' + hex(r) + hex(g) + hex(b); + }; - return e; + return { + toHex : function(color) { + return color.replace(rgbRegExp, toHex); }, - getNext : function(node, selector) { - return this._findSib(node, selector, 'nextSibling'); - }, + parse : function(css) { + var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this; - getPrev : function(node, selector) { - return this._findSib(node, selector, 'previousSibling'); - }, + function compress(prefix, suffix) { + var top, right, bottom, left; + // Get values and check it it needs compressing + top = styles[prefix + '-top' + suffix]; + if (!top) + return; - select : function(pa, s) { - var t = this; + right = styles[prefix + '-right' + suffix]; + if (top != right) + return; - return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); - }, + bottom = styles[prefix + '-bottom' + suffix]; + if (right != bottom) + return; - is : function(n, selector) { - var i; + left = styles[prefix + '-left' + suffix]; + if (bottom != left) + return; - // 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; + // 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]; + }; - // Simple selector just elements - if (simpleSelectorRe.test(selector)) { - selector = selector.toLowerCase().split(/,/); - n = n.nodeName.toLowerCase(); + function canCompress(key) { + var value = styles[key], i; - for (i = selector.length - 1; i >= 0; i--) { - if (selector[i] == n) - return true; - } + if (!value || value.indexOf(' ') < 0) + return; - return false; + value = value.split(' '); + i = value.length; + while (i--) { + if (value[i] !== value[0]) + return false; } - } - return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; - }, + styles[key] = value[0]; + return true; + }; - add : function(p, n, a, h, c) { - var t = this; + function compress2(target, a, b, c) { + if (!canCompress(a)) + return; - return this.run(p, function(p) { - var e, k; + if (!canCompress(b)) + return; - e = is(n, 'string') ? t.doc.createElement(n) : n; - t.setAttribs(e, a); + if (!canCompress(c)) + return; - if (h) { - if (h.nodeType) - e.appendChild(h); - else - t.setHTML(e, h); - } + // Compress + styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c]; + delete styles[a]; + delete styles[b]; + delete styles[c]; + }; - return !c ? p.appendChild(e) : e; - }); - }, + // Encodes the specified string by replacing all \" \' ; : with _ + function encode(str) { + isEncoded = true; - create : function(n, a, h) { - return this.add(this.doc.createElement(n), n, a, h, 1); - }, + return encodingLookup[str]; + }; - createHTML : function(n, a, h) { - var o = '', t = this, k; + // 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(/_[0-9]/g, function(str) { + return encodingLookup[str]; + }); + } - o += '<' + n; + if (!keep_slashes) + str = str.replace(/\\([\'\";:])/g, "$1"); - for (k in a) { - if (a.hasOwnProperty(k)) - o += ' ' + k + '="' + t.encode(a[k]) + '"'; + return str; } - if (tinymce.is(h)) - return o + '>' + h + ''; + if (css) { + // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing + css = css.replace(/\\[\"\';:_]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) { + return str.replace(/[;:]/g, encode); + }); - return o + ' />'; - }, + // Parse styles + while (matches = styleRegExp.exec(css)) { + name = matches[1].replace(trimRightRegExp, '').toLowerCase(); + value = matches[2].replace(trimRightRegExp, ''); - remove : function(node, keep_children) { - return this.run(node, function(node) { - var parent, child; + 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(); - parent = node.parentNode; + // Convert RGB colors to HEX + value = value.replace(rgbRegExp, toHex); - if (!parent) - return null; + // Convert URLs and force them into url('value') format + value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) { + str = str || str2; - 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); + if (str) { + str = decode(str); + + // Force strings into single quote format + return "'" + str.replace(/\'/g, "\\'") + "'"; + } + + url = decode(url || url2 || url3); + + // Convert the URL to relative/absolute depending on config + if (urlConverter) + url = urlConverter.call(urlConverterScope, url, 'style'); + + // Output new URL format + return "url('" + url.replace(/\'/g, "\\'") + "')"; + }); + + styles[name] = isEncoded ? decode(value, true) : value; } + + styleRegExp.lastIndex = matches.index + matches[0].length; } - return parent.removeChild(node); - }); + // 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; + } + + return styles; }, - setStyle : function(n, na, v) { - var t = this; + serialize : function(styles, element_name) { + var css = '', name, value; - return t.run(n, function(e) { - var s, i; + function serializeStyles(name) { + var styleList, i, l, name, value; - s = e.style; + styleList = schema.styles[name]; + if (styleList) { + for (i = 0, l = styleList.length; i < l; i++) { + name = styleList[i]; + value = styles[name]; - // Camelcase it, if needed - na = na.replace(/-(\D)/g, function(a, b){ - return b.toUpperCase(); - }); + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } + }; - // Default px suffix on these - if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v))) - v += 'px'; + // Serialize styles according to schema + if (element_name && schema && schema.styles) { + // Serialize global styles and element specific styles + serializeStyles('*'); + serializeStyles(name); + } else { + // Output the styles in the order they are inside the object + for (name in styles) { + value = styles[name]; - switch (na) { - case 'opacity': - // IE specific opacity - if (isIE) { - s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")"; + if (value !== undef && value.length > 0) + css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';'; + } + } - if (!n.currentStyle || !n.currentStyle.hasLayout) - s.display = 'inline-block'; - } + return css; + } + }; +}; - // Fix for older browsers - s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || ''; - break; +(function(tinymce) { + var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, + whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each; - case 'float': - isIE ? s.styleFloat = v : s.cssFloat = v; - break; - - default: - s[na] = v || ''; - } + function split(str, delim) { + return str.split(delim || ','); + }; - // Force update of the style data - if (t.settings.update_styles) - t.setAttrib(e, '_mce_style'); - }); - }, + function unpack(lookup, data) { + var key, elements = {}; - getStyle : function(n, na, c) { - n = this.get(n); + function replace(value) { + return value.replace(/[A-Z]+/g, function(key) { + return replace(lookup[key]); + }); + }; - if (!n) - return false; + // Unpack lookup + for (key in lookup) { + if (lookup.hasOwnProperty(key)) + lookup[key] = replace(lookup[key]); + } - // Gecko - if (this.doc.defaultView && c) { - // Remove camelcase - na = na.replace(/[A-Z]/g, function(a){ - return '-' + a; - }); + // Unpack and parse data into object map + replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) { + attributes = split(attributes, '|'); - try { - return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na); - } catch (ex) { - // Old safari might fail - return null; - } + elements[name] = { + attributes : makeMap(attributes), + attributesOrder : attributes, + children : makeMap(children, '|', {'#comment' : {}}) } + }); - // Camelcase it, if needed - na = na.replace(/-(\D)/g, function(a, b){ - return b.toUpperCase(); - }); + return elements; + }; - if (na == 'float') - na = isIE ? 'styleFloat' : 'cssFloat'; + // 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]' + ); - // IE & Opera - if (n.currentStyle && c) - return n.currentStyle[na]; + boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,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,object'), shortEndedElementsMap); + whiteSpaceElementsMap = makeMap('pre,script,style'); + selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); - return n.style[na]; - }, + tinymce.html.Schema = function(settings) { + var self = this, elements = {}, children = {}, patternElements = [], validStyles; - setStyles : function(e, o) { - var t = this, s = t.settings, ol; + settings = settings || {}; - ol = s.update_styles; - s.update_styles = 0; + // Allow all elements and attributes if verify_html is set to false + if (settings.verify_html === false) + settings.valid_elements = '*[*]'; - each(o, function(v, n) { - t.setStyle(e, n, v); - }); + // Build styles list + if (settings.valid_styles) { + validStyles = {}; - // Update style info - s.update_styles = ol; - if (s.update_styles) - t.setAttrib(e, s.cssText); - }, + // Convert styles into a rule list + each(settings.valid_styles, function(value, key) { + validStyles[key] = tinymce.explode(value); + }); + } - setAttrib : function(e, n, v) { - var t = this; + // Converts a wildcard expression string to a regexp for example *a will become /.*a/. + function patternToRegExp(str) { + return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$'); + }; - // Whats the point - if (!e || !n) - return; + // 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; + } + + // 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 + }; - // Strict XML mode - if (t.settings.strict) - n = n.toLowerCase(); + // Padd empty elements prefix + if (prefix === '#') + element.paddEmpty = true; - return this.run(e, function(e) { - var s = t.settings; + // Remove empty elements prefix + if (prefix === '-') + element.removeEmpty = true; - switch (n) { - case "style": - if (!is(v, 'string')) { - each(v, function(v, n) { - t.setStyle(e, n, v); - }); + // Copy attributes from global rule into current rule + if (globalAttributes) { + for (key in globalAttributes) + attributes[key] = globalAttributes[key]; - return; + attributesOrder.push.apply(attributesOrder, globalAttributesOrder); } - // 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); - } + // 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; + } - e.style.cssText = v; - break; + // Denied from global + if (attrType === '-') { + delete attributes[attrName]; + attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1); + continue; + } - case "class": - e.className = v || ''; // Fix IE null bug - break; + // Default value + if (prefix) { + // Default value + if (prefix === '=') { + element.attributesDefault = element.attributesDefault || []; + element.attributesDefault.push({name: attrName, value: value}); + attr.defaultValue = value; + } - 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); + // Forced value + if (prefix === ':') { + element.attributesForced = element.attributesForced || []; + element.attributesForced.push({name: attrName, value: value}); + attr.forcedValue = value; + } + + // Required values + if (prefix === '<') + attr.validValues = makeMap(value, '?'); + } + + // 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); - t.setAttrib(e, '_mce_' + n, v, 2); + attributes[attrName] = attr; + } + } + } } - break; - - case "shape": - e.setAttribute('_mce_style', v); - break; - } + // Global rule, store away these for later usage + if (!globalAttributes && elementName == '@') { + globalAttributes = attributes; + globalAttributesOrder = attributesOrder; + } - if (is(v) && v !== null && v.length !== 0) - e.setAttribute(n, '' + v, 2); - else - e.removeAttribute(n, 2); - }); - }, + // Handle substitute elements such as b/strong + if (outputName) { + element.outputName = elementName; + elements[outputName] = element; + } - setAttribs : function(e, o) { - var t = this; + // Add pattern or exact element + if (hasPatternsRegExp.test(elementName)) { + element.pattern = patternToRegExp(elementName); + patternElements.push(element); + } else + elements[elementName] = element; + } + } + } + }; - return this.run(e, function(e) { - each(o, function(v, n) { - t.setAttrib(e, n, v); - }); - }); - }, + function setValidElements(valid_elements) { + elements = {}; + patternElements = []; - getAttrib : function(e, n, dv) { - var v, t = this; + addValidElements(valid_elements); - e = t.get(e); + each(transitional, function(element, name) { + children[name] = element.children; + }); + }; - if (!e || e.nodeType !== 1) - return false; + // Adds custom non HTML elements to the schema + function addCustomElements(custom_elements) { + var customElementRegExp = /^(~)?(.+)$/; - if (!is(dv)) - dv = ''; + if (custom_elements) { + each(split(custom_elements), function(rule) { + var matches = customElementRegExp.exec(rule), + cloneName = matches[1] === '~' ? 'span' : 'div', + name = matches[2]; - // Try the mce variant for these - if (/^(src|href|style|coords|shape)$/.test(n)) { - v = e.getAttribute("_mce_" + n); + children[name] = children[cloneName]; - if (v) - return v; + // Add custom elements at span/div positions + each(children, function(element, child) { + if (element[cloneName]) + element[name] = element[cloneName]; + }); + }); } + }; - if (isIE && t.props[n]) { - v = e[t.props[n]]; - v = v && v.nodeValue ? v.nodeValue : v; - } + // Adds valid children to the schema object + function addValidChildren(valid_children) { + var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/; - if (!v) - v = e.getAttribute(n, 2); + if (valid_children) { + each(split(valid_children), function(rule) { + var matches = childRuleRegExp.exec(rule), parent, prefix; - // 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; + if (matches) { + prefix = matches[1]; - return v ? n : ''; - } + // Add/remove items from default + if (prefix) + parent = children[matches[2]]; + else + parent = children[matches[2]] = {'#comment' : {}}; - // Inner input elements will override attributes on form elements - if (e.nodeName === "FORM" && e.getAttributeNode(n)) - return e.getAttributeNode(n).nodeValue; + parent = children[matches[2]]; - if (n === 'style') { - v = v || e.style.cssText; - - if (v) { - v = t.serializeStyle(t.parseStyle(v), e.nodeName); - - if (t.settings.keep_values && !t._isRes(v)) - e.setAttribute('_mce_style', v); - } + each(split(matches[3], '|'), function(child) { + if (prefix === '-') + delete parent[child]; + else + parent[child] = {}; + }); + } + }); } + } - // Remove Apple and WebKit stuff - if (isWebKit && n === "class" && v) - v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); + 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 + }; - // Handle IE issues - if (isIE) { - switch (n) { - case 'rowspan': - case 'colspan': - // IE returns 1 as default value - if (v === 1) - v = ''; + children[name] = element.children; + }); - break; + // Switch these + each(split('strong/b,em/i'), function(item) { + item = split(item, '/'); + elements[item[1]].outputName = item[0]; + }); - case 'size': - // IE returns +0 as default value for size - if (v === '+0' || v === 20 || v === 0) - v = ''; + // Add default alt attribute for images + elements.img.attributesDefault = [{name: 'alt', value: ''}]; - break; + // Remove these if they are empty by default + each(split('ol,ul,li,sub,sup,blockquote,tr,div,span,font,a,table,tbody'), function(name) { + elements[name].removeEmpty = true; + }); - case 'width': - case 'height': - case 'vspace': - case 'checked': - case 'disabled': - case 'readonly': - if (v === 0) - v = ''; + // 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); - break; + addCustomElements(settings.custom_elements); + addValidChildren(settings.valid_children); + addValidElements(settings.extended_valid_elements); - case 'hspace': - // IE returns -1 as default value - if (v === -1) - v = ''; + // Todo: Remove this when we fix list handling to be valid + addValidChildren('+ol[ul|ol],+ul[ul|ol]'); - break; + // Delete invalid elements + if (settings.invalid_elements) { + tinymce.each(tinymce.explode(settings.invalid_elements), function(item) { + if (elements[item]) + delete elements[item]; + }); + } - case 'maxlength': - case 'tabindex': - // IE returns default value - if (v === 32768 || v === 2147483647 || v === '32768') - v = ''; + self.children = children; - break; + self.styles = validStyles; - case 'multiple': - case 'compact': - case 'noshade': - case 'nowrap': - if (v === 65535) - return n; + self.getBoolAttrs = function() { + return boolAttrMap; + }; - return dv; + self.getBlockElements = function() { + return blockElementsMap; + }; - case 'shape': - v = v.toLowerCase(); - break; + self.getShortEndedElements = function() { + return shortEndedElementsMap; + }; - default: - // IE has odd anonymous function for event attributes - if (n.indexOf('on') === 0 && v) - v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1'); - } - } + self.getSelfClosingElements = function() { + return selfClosingElementsMap; + }; - return (v !== undefined && v !== null && v !== '') ? '' + v : dv; - }, + self.getNonEmptyElements = function() { + return nonEmptyElementsMap; + }; - getPos : function(n, ro) { - var t = this, x = 0, y = 0, e, d = t.doc, r; + self.getWhiteSpaceElements = function() { + return whiteSpaceElementsMap; + }; - n = t.get(n); - ro = ro || d.body; + self.isValidChild = function(name, child) { + var parent = children[name]; - 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; - n.top += t.win.self != t.win.top ? 2 : 0; // IE adds some strange extra cord if used in a frameset + return !!(parent && parent[child]); + }; - return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x}; - } + self.getElementRule = function(name) { + var element = elements[name], i; - r = n; - while (r && r != ro && r.nodeType) { - x += r.offsetLeft || 0; - y += r.offsetTop || 0; - r = r.offsetParent; - } + // Exact match found + if (element) + return element; - r = n.parentNode; - while (r && r != ro && r.nodeType) { - x -= r.scrollLeft || 0; - y -= r.scrollTop || 0; - r = r.parentNode; - } + // No exact match then try the patterns + i = patternElements.length; + while (i--) { + element = patternElements[i]; + + if (element.pattern.test(name)) + return element; } + }; - return {x : x, y : y}; - }, + self.addValidElements = addValidElements; - parseStyle : function(st) { - var t = this, s = t.settings, o = {}; + self.setValidElements = setValidElements; - if (!st) - return o; + self.addCustomElements = addCustomElements; - function compress(p, s, ot) { - var t, r, b, l; + self.addValidChildren = addValidChildren; + }; - // Get values and check it it needs compressing - t = o[p + '-top' + s]; - if (!t) - return; + // Expose boolMap and blockElementMap as static properties for usage in DOMUtils + tinymce.html.Schema.boolAttrMap = boolAttrMap; + tinymce.html.Schema.blockElementsMap = blockElementsMap; +})(tinymce); - r = o[p + '-right' + s]; - if (t != r) - return; +(function(tinymce) { + tinymce.html.SaxParser = function(settings, schema) { + var self = this, noop = function() {}; - b = o[p + '-bottom' + s]; - if (r != b) - return; + settings = settings || {}; + self.schema = schema = schema || new tinymce.html.Schema(); - l = o[p + '-left' + s]; - if (b != l) - return; + if (settings.fix_self_closing !== false) + settings.fix_self_closing = true; - // Compress - o[ot] = l; - delete o[p + '-top' + s]; - delete o[p + '-right' + s]; - delete o[p + '-bottom' + s]; - delete o[p + '-left' + s]; - }; + // 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; + }); - function compress2(ta, a, b, c) { - var t; + self.parse = function(html) { + var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, + shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, + validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing, + tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing; - t = o[a]; - if (!t) - return; + function processEndTag(name) { + var pos, i; - t = o[b]; - if (!t) - return; + // Find position of parent of the same type + pos = stack.length; + while (pos--) { + if (stack[pos].name === name) + break; + } - t = o[c]; - if (!t) - return; + // Found parent + if (pos >= 0) { + // Close all the open elements + for (i = stack.length - 1; i >= pos; i--) { + name = stack[i]; - // Compress - o[ta] = o[a] + ' ' + o[b] + ' ' + o[c]; - delete o[a]; - delete o[b]; - delete o[c]; + if (name.valid) + self.end(name.name); + } + + // Remove the open elements from the stack + stack.length = pos; + } }; - st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities + // 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(st.split(';'), function(v) { - var sv, ur = []; + // Setup lookup tables for empty elements and boolean attributes + shortEndedElements = schema.getShortEndedElements(); + selfClosing = schema.getSelfClosingElements(); + fillAttrsMap = schema.getBoolAttrs(); + validate = settings.validate; + fixSelfClosing = settings.fix_self_closing; + + 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 + processEndTag(value.toLowerCase()); + } else if (value = matches[7]) { // Start element + value = value.toLowerCase(); + 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; + } - 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); - }); + // Parse attributes + if (attribsValue = matches[8]) { + attrList = []; + attrList.map = {}; - 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) + ')'; - }); - } + attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) { + var attrRule, i; - o[tinymce.trim(v[0]).toLowerCase()] = sv; - } - }); + name = name.toLowerCase(); + value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute - 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'); + // Validate name and value + if (validate && name.indexOf('data-') !== 0) { + attrRule = validAttributesMap[name]; - if (isIE) { - // Remove pointless border - if (o.border == 'medium none') - o.border = ''; - } + // Find rule by pattern matching + if (!attrRule && validAttributePatterns) { + i = validAttributePatterns.length; + while (i--) { + attrRule = validAttributePatterns[i]; + if (attrRule.pattern.test(name)) + break; + } - return o; - }, + // No rule matched + if (i === -1) + attrRule = null; + } - serializeStyle : function(o, name) { - var t = this, s = ''; + // No attribute rule found + if (!attrRule) + return; - function add(v, k) { - if (k && v) { - // Remove browser specific styles like -moz- or -webkit- - if (k.indexOf('-') === 0) - return; + // Validate value + if (attrRule.validValues && !(value in attrRule.validValues)) + return; + } - switch (k) { - case 'font-weight': - // Opera will output bold as 700 - if (v == 700) - v = 'bold'; + // Add attribute to list and map + attrList.map[name] = value; + attrList.push({ + name: name, + value: value + }); + }); + } else { + attrList = []; + attrList.map = {}; + } - break; + // Process attributes if validation is enabled + if (validate) { + 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}); + } + } - case 'color': - case 'background-color': - v = v.toLowerCase(); - break; - } + // Handle default attributes + if (attributesDefault) { + i = attributesDefault.length; + while (i--) { + attr = attributesDefault[i]; + name = attr.name; - s += (s ? ' ' : '') + k + ': ' + v + ';'; - } - }; + if (!(name in attrList.map)) { + attrValue = attr.value; - // Validate style output - if (name && t._styles) { - each(t._styles['*'], function(name) { - add(o[name], name); - }); + if (attrValue === '{$uid}') + attrValue = 'mce_' + idCount++; - each(t._styles[name.toLowerCase()], function(name) { - add(o[name], name); - }); - } else - each(o, add); + attrList.map[name] = attrValue; + attrList.push({name: name, value: attrValue}); + } + } + } - return s; - }, + // Handle required attributes + if (attributesRequired) { + i = attributesRequired.length; + while (i--) { + if (attributesRequired[i] in attrList.map) + break; + } - loadCSS : function(u) { - var t = this, d = t.doc, head; + // None of the required attributes where found + if (i === -1) + isValidElement = false; + } - if (!u) - u = ''; + // Invalidate element if it's marked as bogus + if (attrList.map['data-mce-bogus']) + isValidElement = false; + } - head = t.select('head')[0]; + if (isValidElement) + self.start(value, attrList, isShortEnded); + } else + isValidElement = false; - each(u.split(','), function(u) { - var link; + // 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; - if (t.files[u]) - return; + if (matches = endRegExp.exec(html)) { + if (isValidElement) + text = html.substr(index, matches.index - index); - t.files[u] = true; - link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); + index = matches.index + matches[0].length; + } else { + text = html.substr(index); + index = html.length; + } - // 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) { - link.onload = function() { - d.recalc(); - link.onload = null; - }; + if (isValidElement && text.length > 0) + self.text(text, true); + + if (isValidElement) + self.end(value); + + tokenRegExp.lastIndex = index; + continue; + } + + // 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]); } - head.appendChild(link); - }); - }, + index = matches.index + matches[0].length; + } - addClass : function(e, c) { - return this.run(e, function(e) { - var o; + // Text + if (index < html.length) + self.text(decode(html.substr(index))); - if (!c) - return 0; + // Close any open elements + for (i = stack.length - 1; i >= 0; i--) { + value = stack[i]; - if (this.hasClass(e, c)) - return e.className; + if (value.valid) + self.end(value.name); + } + }; + } +})(tinymce); - o = this.removeClass(e, c); +(function(tinymce) { + var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = { + '#text' : 3, + '#comment' : 8, + '#cdata' : 4, + '#pi' : 7, + '#doctype' : 10, + '#document-fragment' : 11 + }; - return e.className = (o != '' ? (o + ' ') : '') + c; - }); - }, + // Walks the tree left/right + function walk(node, root_node, prev) { + var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next'; - removeClass : function(e, c) { - var t = this, re; + // Walk into nodes if it has a start + if (node[startName]) + return node[startName]; - return t.run(e, function(e) { - var v; + // Return the sibling if it has one + if (node !== root_node) { + sibling = node[siblingName]; - if (t.hasClass(e, c)) { - if (!re) - re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); + if (sibling) + return sibling; - v = e.className.replace(re, ' '); - v = tinymce.trim(v != ' ' ? v : ''); + // Walk up the parents to look for siblings + for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) { + sibling = parent[siblingName]; - e.className = v; + if (sibling) + return sibling; + } + } + }; - // Empty class attr - if (!v) { - e.removeAttribute('class'); - e.removeAttribute('className'); - } + function Node(name, type) { + this.name = name; + this.type = type; - return v; - } + if (type === 1) { + this.attributes = []; + this.attributes.map = {}; + } + } - return e.className; - }); - }, + tinymce.extend(Node.prototype, { + replace : function(node) { + var self = this; - hasClass : function(n, c) { - n = this.get(n); + if (node.parent) + node.remove(); - if (!n || !c) - return false; + self.insert(node, self); + self.remove(); - return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; + return self; }, - show : function(e) { - return this.setStyle(e, 'display', 'block'); - }, + attr : function(name, value) { + var self = this, attrs, i, undef; - hide : function(e) { - return this.setStyle(e, 'display', 'none'); - }, - - isHidden : function(e) { - e = this.get(e); - - return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; - }, - - uniqueId : function(p) { - return (!p ? 'mce_' : p) + (this.counter++); - }, - - setHTML : function(e, h) { - var t = this; - - return this.run(e, function(e) { - var x, i, nl, n, p, x; + if (typeof name !== "string") { + for (i in name) + self.attr(i, name[i]); - h = t.processHTML(h); + return self; + } - if (isIE) { - function set() { - // Remove all child nodes - while (e.firstChild) - e.firstChild.removeNode(); + if (attrs = self.attributes) { + if (value !== undef) { + // Remove attribute + if (value === null) { + if (name in attrs.map) { + delete attrs.map[name]; - 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); - }); + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs = attrs.splice(i, 1); + return self; + } + } } - }; - - // 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, ' 

    '); - set(); - - 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]; - - if (!n.hasChildNodes()) { - if (!n._mce_keep) { - x = 1; // Is broken - break; - } + return self; + } - n.removeAttribute('_mce_keep'); + // Set attribute + if (name in attrs.map) { + // Set attribute + i = attrs.length; + while (i--) { + if (attrs[i].name === name) { + attrs[i].value = value; + break; } } - } + } else + attrs.push({name: name, value: value}); - // 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, '
    '); + attrs.map[name] = value; - // Set the new HTML with DIVs - set(); + return self; + } else { + return attrs.map[name]; + } + } + }, - // 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]; + clone : function() { + var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs; - // Is it a temp div - if (n._mce_tmp) { - // Create new paragraph - p = t.doc.createElement('p'); + // Clone element attributes + if (selfAttrs = self.attributes) { + cloneAttrs = []; + cloneAttrs.map = {}; - // Copy all attributes - n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) { - var v; + for (i = 0, l = selfAttrs.length; i < l; i++) { + selfAttr = selfAttrs[i]; - if (b !== '_mce_tmp') { - v = n.getAttribute(b); + // Clone everything except id + if (selfAttr.name !== 'id') { + cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value}; + cloneAttrs.map[selfAttr.name] = selfAttr.value; + } + } - if (!v && b === 'class') - v = n.className; + clone.attributes = cloneAttrs; + } - p.setAttribute(b, v); - } - }); + clone.value = self.value; + clone.shortEnded = self.shortEnded; - // Append all children to new paragraph - for (x = 0; x]+)\/>|/gi, ''); // Force open + self.remove(); + }, - // 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, ''); + remove : function() { + var self = this, parent = self.parent, next = self.next, prev = self.prev; - return s; - }; + if (parent) { + if (parent.firstChild === self) { + parent.firstChild = next; - // 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"'; + if (next) + next.prev = null; + } else { + prev.next = next; + } - // 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')); + if (parent.lastChild === self) { + parent.lastChild = prev; - return '_mce_src="' + url + '"'; - }); + if (prev) + prev.next = null; + } else { + next.prev = prev; + } - // Wrap text contents - if (tinymce.trim(text)) { - codeBlocks.push(trim(text)); - text = ''; - } + self.parent = self.next = self.prev = null; + } - return '' + text + ''; - }); + return self; + }, - // Wrap style elements - h = h.replace(/]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) { - // Wrap text contents - if (text) { - codeBlocks.push(trim(text)); - text = ''; - } + append : function(node) { + var self = this, last; - return '' + text + ''; - }); + if (node.parent) + node.remove(); - // Wrap noscript elements - h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { - return ''; - }); - } + last = self.lastChild; + if (last) { + last.next = node; + node.prev = last; + self.lastChild = node; + } else + self.lastChild = self.firstChild = node; - h = h.replace(//g, ''); + node.parent = self; - // 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; + return node; + }, - name = name.toLowerCase(); - value = value || val2 || val3 || ""; + insert : function(node, ref_node, before) { + var parent; - // Treat boolean attributes - if (boolAttrs[name]) { - // false or 0 is treated as a missing attribute - if (value === 'false' || value === '0') - return; + if (node.parent) + node.remove(); - return name + '="' + name + '"'; - } + parent = ref_node.parent || this; - // Is attribute one that needs special treatment - if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) { - mceValue = t.decode(value); + if (before) { + if (ref_node === parent.firstChild) + parent.firstChild = node; + else + ref_node.prev.next = node; - // 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); + 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; - // Process styles lowercases them and compresses them - if (name == 'style') - mceValue = t.serializeStyle(t.parseStyle(mceValue), name); + node.next = ref_node.next; + node.prev = ref_node; + ref_node.next = node; + } - return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"'; - } + node.parent = parent; - return match; - }) + end + '>'; - }); - }; + return node; + }, - h = processTags(h); + getAll : function(name) { + var self = this, node, collection = []; - // Restore script blocks - h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) { - return codeBlocks[idx]; - }); + for (node = self.firstChild; node; node = walk(node, self)) { + if (node.name === name) + collection.push(node); } - return h; + return collection; }, - getOuterHTML : function(e) { - var d; + empty : function() { + var self = this, nodes, i, node; - e = this.get(e); + // Remove all children + if (self.firstChild) { + nodes = []; - if (!e) - return null; + // Collect the children + for (node = self.firstChild; node; node = walk(node, self)) + nodes.push(node); - if (e.outerHTML !== undefined) - return e.outerHTML; + // Remove the children + i = nodes.length; + while (i--) { + node = nodes[i]; + node.parent = node.firstChild = node.lastChild = node.next = node.prev = null; + } + } - d = (e.ownerDocument || this.doc).createElement("body"); - d.appendChild(e.cloneNode(true)); + self.firstChild = self.lastChild = null; - return d.innerHTML; + return self; }, - setOuterHTML : function(e, h, d) { - var t = this; - - function setHTML(e, h, d) { - var n, tp; + isEmpty : function(elements) { + var self = this, node = self.firstChild, i, name; - tp = d.createElement("body"); - tp.innerHTML = h; + if (node) { + do { + if (node.type === 1) { + // Ignore bogus elements + if (node.attributes.map['data-mce-bogus']) + continue; - n = tp.lastChild; - while (n) { - t.insertAfter(n.cloneNode(true), e); - n = n.previousSibling; - } + // Keep empty elements like + if (elements[node.name]) + return false; - t.remove(e); - }; + // 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 this.run(e, function(e) { - e = t.get(e); + // Keep non whitespace text nodes + if ((node.type === 3 && !whiteSpaceRegExp.test(node.value))) + return false; + } while (node = walk(node, self)); + } - // Only set HTML on elements - if (e.nodeType == 1) { - d = d || e.ownerDocument || t.doc; + return true; + } + }); - 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); - } - }); - }, + tinymce.extend(Node, { + create : function(name, attrs) { + var node, attrName; - decode : function(s) { - var e, n, v; + // Create node + node = new Node(name, typeLookup[name] || 1); - // 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 = ''; + // Add attributes if needed + if (attrs) { + for (attrName in attrs) + node.attr(attrName, attrs[attrName]); + } - if (n) { - do { - v += n.nodeValue; - } while (n = n.nextSibling); - } + return node; + } + }); - return v || s; - } + tinymce.html.Node = Node; +})(tinymce); - return s; - }, +(function(tinymce) { + var Node = tinymce.html.Node; - encode : function(str) { - return ('' + str).replace(encodeCharsRe, function(chr) { - return encodedChars[chr]; - }); - }, + tinymce.html.DomParser = function(settings, schema) { + var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {}; - insertAfter : function(node, reference_node) { - reference_node = this.get(reference_node); + 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(); - return this.run(node, function(node) { - var parent, nextSibling; + function fixInvalidChildren(nodes) { + var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i, + childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode; - parent = reference_node.parentNode; - nextSibling = reference_node.nextSibling; + nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table'); + nonEmptyElements = schema.getNonEmptyElements(); - if (nextSibling) - parent.insertBefore(node, nextSibling); - else - parent.appendChild(node); + for (ni = 0; ni < nodes.length; ni++) { + node = nodes[ni]; - return node; - }); - }, + // Already removed + if (!node.parent) + continue; - isBlock : function(n) { - if (n.nodeType && n.nodeType !== 1) - return false; + // 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); - n = n.nodeName || n; + // Found a suitable parent + if (parent && parents.length > 1) { + // Reverse the array since it makes looping easier + parents.reverse(); - return blockRe.test(n); - }, + // Clone the related parent and insert that after the moved node + newParent = currentNode = self.filterNode(parents[0].clone()); - replace : function(n, o, k) { - var t = this; + // 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; - if (is(o, 'array')) - n = n.cloneNode(true); + for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) { + nextNode = childNode.next; + tempNode.append(childNode); + childNode = nextNode; + } - return t.run(o, function(o) { - if (k) { - each(tinymce.grep(o.childNodes), function(c) { - n.appendChild(c); - }); - } + currentNode = tempNode; + } - return o.parentNode.replaceChild(n, o); - }); - }, + if (!newParent.isEmpty(nonEmptyElements)) { + parent.insert(newParent, parents[0], true); + parent.insert(node, newParent); + } else { + parent.insert(node, parents[0], true); + } - rename : function(elm, name) { - var t = this, newElm; + // 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; + } - if (elm.nodeName != name.toUpperCase()) { - // Rename block element - newElm = t.create(name); + sibling = node.next; + if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) { + sibling.insert(node, sibling.firstChild, true); + continue; + } - // Copy attribs to new block - each(t.getAttribs(elm), function(attr_node) { - t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); - }); + node.wrap(self.filterNode(new Node('ul', 1))); + continue; + } - // Replace block - t.replace(newElm, elm, 1); + // 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(); + } + } } + }; - return newElm || elm; - }, + self.filterNode = function(node) { + var i, name, list; - findCommonAncestor : function(a, b) { - var ps = a, pe; + // Run element filters + if (name in nodeFilters) { + list = matchedNodes[name]; - while (ps) { - pe = b; + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } - while (pe && ps != pe) - pe = pe.parentNode; + // Run attribute filters + i = attributeFilters.length; + while (i--) { + name = attributeFilters[i].name; - if (ps == pe) - break; + if (name in node.attributes.map) { + list = matchedAttributes[name]; - ps = ps.parentNode; + if (list) + list.push(node); + else + matchedAttributes[name] = [node]; + } } - if (!ps && a.ownerDocument) - return a.ownerDocument.documentElement; + return node; + }; - return ps; - }, + self.addNodeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var list = nodeFilters[name]; - toHex : function(s) { - var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); + if (!list) + nodeFilters[name] = list = []; - function hex(s) { - s = parseInt(s).toString(16); + list.push(callback); + }); + }; - return s.length > 1 ? s : '0' + s; // 0 -> 00 - }; + self.addAttributeFilter = function(name, callback) { + tinymce.each(tinymce.explode(name), function(name) { + var i; - if (c) { - s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); + for (i = 0; i < attributeFilters.length; i++) { + if (attributeFilters[i].name === name) { + attributeFilters[i].callbacks.push(callback); + return; + } + } - return s; - } + attributeFilters.push({name: name, callbacks: [callback]}); + }); + }; - return s; - }, + 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; - getClasses : function() { - var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; + 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; - if (t.classes) - return t.classes; + whiteSpaceElements = schema.getWhiteSpaceElements(); + startWhiteSpaceRegExp = /^[ \t\r\n]+/; + endWhiteSpaceRegExp = /[ \t\r\n]+$/; + allWhiteSpaceRegExp = /[ \t\r\n]+/g; - function addClasses(s) { - // IE style imports - each(s.imports, function(r) { - addClasses(r); - }); + function createNode(name, type) { + var node = new Node(name, type), list; - 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, ""); + if (name in nodeFilters) { + list = matchedNodes[name]; - // Is internal or it doesn't contain a class - if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) - return; + if (list) + list.push(node); + else + matchedNodes[name] = [node]; + } - // Remove everything but class name - ov = v; - v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1'); + return node; + }; - // Filter classes - if (f && !(v = f(v, ov))) - return; + function removeWhitespaceBefore(node) { + var textNode, textVal, sibling; - if (!lo[v]) { - cl.push({'class' : v}); - lo[v] = 1; - } - }); - } - break; + for (textNode = node.prev; textNode && textNode.type === 3; ) { + textVal = textNode.value.replace(endWhiteSpaceRegExp, ''); - // Import - case 3: - addClasses(r.styleSheet); - break; + if (textVal.length > 0) { + textNode.value = textVal; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; } - }); + } }; - try { - each(t.doc.styleSheets, addClasses); - } catch (ex) { - // Ignore - } + parser = new tinymce.html.SaxParser({ + validate : validate, + fix_self_closing : !validate, // Let the DOM parser handle
  • in
  • or

    in

    for better results - if (cl.length > 0) - t.classes = cl; + cdata: function(text) { + node.append(createNode('#cdata', 4)).value = text; + }, - return cl; - }, + text: function(text, raw) { + var textNode; - run : function(e, f, s) { - var t = this, o; + // Trim all redundant whitespace on non white space elements + if (!whiteSpaceElements[node.name]) { + text = text.replace(allWhiteSpaceRegExp, ' '); - if (t.doc && typeof(e) === 'string') - e = t.get(e); + if (node.lastChild && blockElements[node.lastChild.name]) + text = text.replace(startWhiteSpaceRegExp, ''); + } - if (!e) - return false; + // Do we need to create the node + if (text.length !== 0) { + textNode = createNode('#text', 3); + textNode.raw = !!raw; + node.append(textNode).value = text; + } + }, - s = s || this; - if (!e.nodeType && (e.length || e.length === 0)) { - o = []; + comment: function(text) { + node.append(createNode('#comment', 8)).value = text; + }, - each(e, function(e, i) { - if (e) { - if (typeof(e) == 'string') - e = t.doc.getElementById(e); + pi: function(name, text) { + node.append(createNode(name, 7)).value = text; + removeWhitespaceBefore(node); + }, - o.push(f.call(s, e, i)); - } - }); + doctype: function(text) { + var newNode; + + newNode = node.append(createNode('#doctype', 10)); + newNode.value = text; + removeWhitespaceBefore(node); + }, - return o; - } + start: function(name, attrs, empty) { + var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent; - return f.call(s, e); - }, + elementRule = validate ? schema.getElementRule(name) : {}; + if (elementRule) { + newNode = createNode(elementRule.outputName || name, 1); + newNode.attributes = attrs; + newNode.shortEnded = empty; - getAttribs : function(n) { - var o; + node.append(newNode); - n = this.get(n); + // 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); - if (!n) - return []; + attrFiltersLen = attributeFilters.length; + while (attrFiltersLen--) { + attrName = attributeFilters[attrFiltersLen].name; - if (isIE) { - o = []; + if (attrName in attrs.map) { + list = matchedAttributes[attrName]; - // Object will throw exception in IE - if (n.nodeName == 'OBJECT') - return n.attributes; + if (list) + list.push(newNode); + else + matchedAttributes[attrName] = [newNode]; + } + } - // 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'}); + // Trim whitespace before block + if (blockElements[name]) + removeWhitespaceBefore(newNode); - // 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}); - }); + // Change current node if the element wasn't empty i.e not
    or + if (!empty) + node = newNode; + } + }, - return o; - } + end: function(name) { + var textNode, elementRule, text, sibling, tempNode; - return n.attributes; - }, + 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, ''); - destroy : function(s) { - var t = this; + if (text.length > 0) { + textNode.value = text; + textNode = textNode.next; + } else { + sibling = textNode.next; + textNode.remove(); + textNode = sibling; + } + } - if (t.events) - t.events.destroy(); + // Trim whitespace at end of block + for (textNode = node.lastChild; textNode && textNode.type === 3; ) { + text = textNode.value.replace(endWhiteSpaceRegExp, ''); - t.win = t.doc = t.root = t.events = null; + if (text.length > 0) { + textNode.value = text; + textNode = textNode.prev; + } else { + sibling = textNode.prev; + textNode.remove(); + textNode = sibling; + } + } + } - // Manual destroy then remove unload handler - if (!s) - tinymce.removeUnload(t.destroy); - }, + // Trim start white space + textNode = node.prev; + if (textNode && textNode.type === 3) { + text = textNode.value.replace(startWhiteSpaceRegExp, ''); - createRng : function() { - var d = this.doc; + if (text.length > 0) + textNode.value = text; + else + textNode.remove(); + } + } - return d.createRange ? d.createRange() : new tinymce.dom.Range(this); - }, + // 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; + } + } + } + } - nodeIndex : function(node, normalized) { - var idx = 0, lastNodeType, lastNode, nodeType; + node = node.parent; + } + } + }, schema); - if (node) { - for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) { - nodeType = node.nodeType; + rootNode = node = new Node(settings.root_name, 11); - // Normalize text nodes - if (normalized && nodeType == 3) { - if (nodeType == lastNodeType || !node.nodeValue.length) - continue; - } + parser.parse(html); - idx++; - lastNodeType = nodeType; + if (validate) + fixInvalidChildren(invalidChildren); + + // Run node filters + for (name in matchedNodes) { + list = nodeFilters[name]; + nodes = matchedNodes[name]; + + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); } + + for (i = 0, l = list.length; i < l; i++) + list[i](nodes, name, args); } - return idx; - }, + // Run attribute filters + for (i = 0, l = attributeFilters.length; i < l; i++) { + list = attributeFilters[i]; - split : function(pe, e, re) { - var t = this, r = t.createRng(), bef, aft, pa; + if (list.name in matchedAttributes) { + nodes = matchedAttributes[list.name]; - // 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; + // Remove already removed children + fi = nodes.length; + while (fi--) { + if (!nodes[fi].parent) + nodes.splice(fi, 1); + } - if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark') - return; + for (fi = 0, fl = list.callbacks.length; fi < fl; fi++) + list.callbacks[fi](nodes, list.name, args); + } + } - for (i = children.length - 1; i >= 0; i--) - trim(children[i]); + return rootNode; + }; - if (node.nodeType != 9) { - // Keep non whitespace text nodes - if (node.nodeType == 3 && node.nodeValue.length > 0) - return; + // 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; + + // 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; + } + } - 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); + prev = prev.prev; + } - // Keep non empty elements or img, hr etc - if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName)) - return; - } + if (node) { + node.remove(); - t.remove(node); + // 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.removeEmpty) + parent.remove(); + else if (elementRule.paddEmpty) + parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0'; + } + } + } } + }); + } + } +})(tinymce); - return node; - }; +tinymce.html.Writer = function(settings) { + var html = [], indent, indentBefore, indentAfter, encode, htmlOutput; - if (pe && e) { - // Get before chunk - r.setStart(pe.parentNode, t.nodeIndex(pe)); - r.setEnd(e.parentNode, t.nodeIndex(e)); - bef = r.extractContents(); + 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"; - // 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(); + return { + start: function(name, attrs, empty) { + var i, l, attr, value; - // Insert before chunk - pa = pe.parentNode; - pa.insertBefore(trim(bef), pe); + if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; - // Insert middle chunk - if (re) - pa.replaceChild(re, e); - else - pa.insertBefore(e, pe); + if (value.length > 0 && value !== '\n') + html.push('\n'); + } - // Insert after chunk - pa.insertBefore(trim(aft), pe); - t.remove(pe); + html.push('<', name); - return re || e; + if (attrs) { + for (i = 0, l = attrs.length; i < l; i++) { + attr = attrs[i]; + html.push(' ', attr.name, '="', encode(attr.value, true), '"'); + } } - }, - bind : function(target, name, func, scope) { - var t = this; + if (!empty || htmlOutput) + html[html.length] = '>'; + else + html[html.length] = ' />'; - if (!t.events) - t.events = new tinymce.dom.EventUtils(); + if (empty && indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; - return t.events.add(target, name, func, scope || this); + if (value.length > 0 && value !== '\n') + html.push('\n'); + } }, - unbind : function(target, name, func) { - var t = this; - - if (!t.events) - t.events = new tinymce.dom.EventUtils(); + end: function(name) { + var value; - return t.events.remove(target, name, func); - }, + /*if (indent && indentBefore[name] && html.length > 0) { + value = html[html.length - 1]; + if (value.length > 0 && value !== '\n') + html.push('\n'); + }*/ - _findSib : function(node, selector, name) { - var t = this, f = selector; + html.push(''); - if (node) { - // If expression make a function of it using is - if (is(f, 'string')) { - f = function(node) { - return t.is(node, selector); - }; - } + if (indent && indentAfter[name] && html.length > 0) { + value = html[html.length - 1]; - // Loop all siblings - for (node = node[name]; node; node = node[name]) { - if (f(node)) - return node; - } + if (value.length > 0 && value !== '\n') + html.push('\n'); } + }, - return null; + text: function(text, raw) { + if (text.length > 0) + html[html.length] = raw ? text : encode(text); }, - _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); - } + cdata: function(text) { + html.push(''); + }, - /* - walk : function(n, f, s) { - var d = this.doc, w; + comment: function(text) { + html.push(''); + }, - if (d.createTreeWalker) { - w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + pi: function(name, text) { + if (text) + html.push(''); + else + html.push(''); - while ((n = w.nextNode()) != null) - f.call(s || this, n); - } else - tinymce.walk(n, f, 'childNodes', s); - } - */ - - /* - toRGB : function(s) { - var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); + if (indent) + html.push('\n'); + }, - if (c) { - // #FFF -> #FFFFFF - if (!is(c[3])) - c[3] = c[2] = c[1]; + doctype: function(text) { + html.push('', indent ? '\n' : ''); + }, - return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; - } + reset: function() { + html.length = 0; + }, - return s; + getContent: function() { + return html.join('').replace(/\n$/, ''); } - */ - }); - - tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); -})(tinymce); - -(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; - - extend(t, { - // Inital states - startContainer : doc, - startOffset : 0, - endContainer : doc, - endOffset : 0, - collapsed : TRUE, - commonAncestorContainer : doc, + }; +}; - // Range constants - START_TO_START : 0, - START_TO_END : 1, - END_TO_END : 2, - END_TO_START : 3, +(function(tinymce) { + tinymce.html.Serializer = function(settings, schema) { + var self = this, writer = new tinymce.html.Writer(settings); - // 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 - }); + settings = settings || {}; + settings.validate = "validate" in settings ? settings.validate : true; - function setStart(n, o) { - _setEndPoint(TRUE, n, o); - }; + self.schema = schema = schema || new tinymce.html.Schema(); + self.writer = writer; - function setEnd(n, o) { - _setEndPoint(FALSE, n, o); - }; + self.serialize = function(node) { + var handlers, validate; - function setStartBefore(n) { - setStart(n.parentNode, nodeIndex(n)); - }; + validate = settings.validate; - function setStartAfter(n) { - setStart(n.parentNode, nodeIndex(n) + 1); - }; + handlers = { + // #text + 3: function(node, raw) { + writer.text(node.value, node.raw); + }, - function setEndBefore(n) { - setEnd(n.parentNode, nodeIndex(n)); - }; + // #comment + 8: function(node) { + writer.comment(node.value); + }, - function setEndAfter(n) { - setEnd(n.parentNode, nodeIndex(n) + 1); - }; + // Processing instruction + 7: function(node) { + writer.pi(node.name, node.value); + }, - 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]; - } + // Doctype + 10: function(node) { + writer.doctype(node.value); + }, - t.collapsed = TRUE; - }; + // CDATA + 4: function(node) { + writer.cdata(node.value); + }, - function selectNode(n) { - setStartBefore(n); - setEndAfter(n); - }; + // Document fragment + 11: function(node) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } + } + }; - function selectNodeContents(n) { - setStart(n, 0); - setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); - }; + writer.reset(); - function compareBoundaryPoints(h, r) { - var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET]; + function walk(node) { + var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule; - // Check START_TO_START - if (h === 0) - return _compareBoundaryPoints(sc, so, sc, so); + if (!handler) { + name = node.name; + isEmpty = node.shortEnded; + attrs = node.attributes; - // Check START_TO_END - if (h === 1) - return _compareBoundaryPoints(sc, so, ec, eo); + // Sort attributes + if (validate && attrs && attrs.length > 1) { + sortedAttrs = []; + sortedAttrs.map = {}; - // Check END_TO_END - if (h === 2) - return _compareBoundaryPoints(ec, eo, ec, eo); + elementRule = schema.getElementRule(node.name); + for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) { + attrName = elementRule.attributesOrder[i]; - // Check END_TO_START - if (h === 3) - return _compareBoundaryPoints(ec, eo, sc, so); - }; + if (attrName in attrs.map) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } - function deleteContents() { - _traverse(DELETE); - }; + for (i = 0, l = attrs.length; i < l; i++) { + attrName = attrs[i].name; - function extractContents() { - return _traverse(EXTRACT); - }; + if (!(attrName in sortedAttrs.map)) { + attrValue = attrs.map[attrName]; + sortedAttrs.map[attrName] = attrValue; + sortedAttrs.push({name: attrName, value: attrValue}); + } + } - function cloneContents() { - return _traverse(CLONE); - }; + attrs = sortedAttrs; + } - function insertNode(n) { - var startContainer = this[START_CONTAINER], - startOffset = this[START_OFFSET], nn, o; + writer.start(node.name, attrs, isEmpty); - // 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]; + if (!isEmpty) { + if ((node = node.firstChild)) { + do { + walk(node); + } while (node = node.next); + } - if (o) - startContainer.insertBefore(n, o); - else - startContainer.appendChild(n); + writer.end(name); + } + } else + handler(node); } - }; - function surroundContents(n) { - var f = t.extractContents(); + // Serialize element and treat all non elements as fragments + if (node.type == 1 && !settings.inner) + walk(node); + else + handlers[11](node); - t.insertNode(n); - n.appendChild(f); - t.selectNode(n); + return writer.getContent(); }; + } +})(tinymce); - 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(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]*$/; - // Private methods + 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" + }, - function _getSelectedNode(container, offset) { - var child; + DOMUtils : function(d, s) { + var t = this, globalStyle; - if (container.nodeType == 3 /* TEXT_NODE */) - return container; + 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 (offset < 0) - return container; + 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); - child = container.firstChild; - while (child && offset > 0) { - --offset; - child = child.nextSibling; + // Fix IE6SP2 flicker and check it failed for pre SP2 + if (tinymce.isIE6) { + try { + d.execCommand('BackgroundImageCache', false, true); + } catch (e) { + t.cssFlicker = true; + } } - if (child) - return child; + if (isIE) { + // 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); + }); + } - return container; - }; + tinymce.addUnload(t.destroy, t); + }, - function _isCollapsed() { - return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); - }; - - 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 + getRoot : function() { + var t = this, s = t.settings; - if (offsetA < 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 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; + w = !w ? this.win : w; + d = w.document; + b = this.boxModel ? d.documentElement : d.body; - if (c) { - offsetC = 0; - n = containerA.firstChild; + // 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 + }; + }, - while (n != c && offsetC < offsetA) { - offsetC++; - n = n.nextSibling; - } + getRect : function(e) { + var p, t = this, sr; - if (offsetA <= offsetC) - return -1; // before + e = t.get(e); + p = t.getPos(e); + sr = t.getSize(e); - return 1; // after - } + return { + x : p.x, + y : p.y, + w : sr.w, + h : sr.h + }; + }, - // 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; - } + getSize : function(e) { + var t = this, w, h; - if (c) { - offsetC = 0; - n = containerB.firstChild; + e = t.get(e); + w = t.getStyle(e, 'width'); + h = t.getStyle(e, 'height'); - while (n != c && offsetC < offsetB) { - offsetC++; - n = n.nextSibling; - } + // Non pixel value, then force offset/clientWidth + if (w.indexOf('px') === -1) + w = 0; - if (offsetC < offsetB) - return -1; // before + // Non pixel value, then force offset/clientWidth + if (h.indexOf('px') === -1) + h = 0; - return 1; // after - } + return { + w : parseInt(w) || e.offsetWidth || e.clientWidth, + h : parseInt(h) || e.offsetHeight || e.clientHeight + }; + }, - // 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; + getParent : function(n, f, r) { + return this.getParents(n, f, r, false); + }, - while (childA && childA.parentNode != cmnRoot) - childA = childA.parentNode; + getParents : function(n, f, r, c) { + var t = this, na, se = t.settings, o = []; - if (!childA) - childA = cmnRoot; + n = t.get(n); + c = c === undefined; - childB = containerB; - while (childB && childB.parentNode != cmnRoot) - childB = childB.parentNode; + if (se.strict_root) + r = r || t.getRoot(); - if (!childB) - childB = cmnRoot; + // Wrap node name as func + if (is(f, 'string')) { + na = f; - if (childA == childB) - return 0; // equal + if (f === '*') { + f = function(n) {return n.nodeType == 1;}; + } else { + f = function(n) { + return t.is(n, na); + }; + } + } - n = cmnRoot.firstChild; while (n) { - if (n == childA) - return -1; // before + if (n == r || !n.nodeType || n.nodeType === 9) + break; - if (n == childB) - return 1; // after + if (!f || f(n)) { + if (c) + o.push(n); + else + return n; + } - n = n.nextSibling; + n = n.parentNode; } - }; - - function _setEndPoint(st, n, o) { - var ec, sc; - if (st) { - t[START_CONTAINER] = n; - t[START_OFFSET] = o; - } else { - t[END_CONTAINER] = n; - t[END_OFFSET] = o; - } + return c ? o : null; + }, - // 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; + get : function(e) { + var n; - sc = t[START_CONTAINER]; - while (sc.parentNode) - sc = sc.parentNode; + if (e && this.doc && typeof(e) == 'string') { + n = e; + e = this.doc.getElementById(e); - 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); + // 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]; + } - t.collapsed = _isCollapsed(); - t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); - }; + return e; + }, - function _traverse(how) { - var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; + getNext : function(node, selector) { + return this._findSib(node, selector, 'nextSibling'); + }, - if (t[START_CONTAINER] == t[END_CONTAINER]) - return _traverseSameContainer(how); + getPrev : function(node, selector) { + return this._findSib(node, selector, 'previousSibling'); + }, - for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { - if (p == t[START_CONTAINER]) - return _traverseCommonStartContainer(c, how); - ++endContainerDepth; - } + select : function(pa, s) { + var t = this; - for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { - if (p == t[END_CONTAINER]) - return _traverseCommonEndContainer(c, how); + return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []); + }, - ++startContainerDepth; - } + is : function(n, selector) { + var i; - depthDiff = startContainerDepth - endContainerDepth; + // 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; - startNode = t[START_CONTAINER]; - while (depthDiff > 0) { - startNode = startNode.parentNode; - depthDiff--; - } + // Simple selector just elements + if (simpleSelectorRe.test(selector)) { + selector = selector.toLowerCase().split(/,/); + n = n.nodeName.toLowerCase(); - endNode = t[END_CONTAINER]; - while (depthDiff < 0) { - endNode = endNode.parentNode; - depthDiff++; - } + for (i = selector.length - 1; i >= 0; i--) { + if (selector[i] == n) + return true; + } - // 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 false; + } } - return _traverseCommonAncestors(startNode, endNode, how); - }; + return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0; + }, - function _traverseSameContainer(how) { - var frag, s, sub, n, cnt, sibling, xferNode; - if (how != DELETE) - frag = doc.createDocumentFragment(); - - // If selection is empty, just return the fragment - if (t[START_OFFSET] == t[END_OFFSET]) - return frag; + add : function(p, n, a, h, c) { + var t = this; - // 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]); + return this.run(p, function(p) { + var e, k; - // 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]); + e = is(n, 'string') ? t.doc.createElement(n) : n; + t.setAttribs(e, a); - // Nothing is partially selected, so collapse to start point - t.collapse(TRUE); + if (h) { + if (h.nodeType) + e.appendChild(h); + else + t.setHTML(e, h); } - if (how == DELETE) - return; - - frag.appendChild(doc.createTextNode(sub)); - return frag; - } + return !c ? p.appendChild(e) : e; + }); + }, - // Copy nodes between the start/end offsets. - n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); - cnt = t[END_OFFSET] - t[START_OFFSET]; + create : function(n, a, h) { + return this.add(this.doc.createElement(n), n, a, h, 1); + }, - while (cnt > 0) { - sibling = n.nextSibling; - xferNode = _traverseFullySelected(n, how); + createHTML : function(n, a, h) { + var o = '', t = this, k; - if (frag) - frag.appendChild( xferNode ); + o += '<' + n; - --cnt; - n = sibling; + for (k in a) { + if (a.hasOwnProperty(k)) + o += ' ' + k + '="' + t.encode(a[k]) + '"'; } - // 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(); + // 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 + ''; - n = _traverseRightBoundary(endAncestor, how); + return o + ' />'; + }, - if (frag) - frag.appendChild(n); + remove : function(node, keep_children) { + return this.run(node, function(node) { + var child, parent = node.parentNode; - endIdx = nodeIndex(endAncestor); - cnt = endIdx - t[START_OFFSET]; + if (!parent) + return null; - if (cnt <= 0) { - // Collapse to just before the endAncestor, which - // is partially selected. - if (how != CLONE) { - t.setEndBefore(endAncestor); - t.collapse(FALSE); + 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; - 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 false; - 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; + // Remove Apple and WebKit stuff + if (isWebKit && n === "class" && v) + v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, ''); - if (how == DELETE) - return; + // Handle IE issues + if (isIE) { + switch (n) { + case 'rowspan': + case 'colspan': + // IE returns 1 as default value + if (v === 1) + v = ''; - newNode = n.cloneNode(FALSE); - newNode.nodeValue = newNodeValue; + break; - return newNode; - } + case 'size': + // IE returns +0 as default value for size + if (v === '+0' || v === 20 || v === 0) + v = ''; - if (how == DELETE) - return; + break; - return n.cloneNode(FALSE); - }; + case 'width': + case 'height': + case 'vspace': + case 'checked': + case 'disabled': + case 'readonly': + if (v === 0) + v = ''; - function _traverseFullySelected(n, how) { - if (how != DELETE) - return how == CLONE ? n.cloneNode(TRUE) : n; + break; - n.parentNode.removeChild(n); - }; - }; + case 'hspace': + // IE returns -1 as default value + if (v === -1) + 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 'maxlength': + case 'tabindex': + // IE returns default value + if (v === 32768 || v === 2147483647 || v === '32768') + 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 'multiple': + case 'compact': + case 'noshade': + case 'nowrap': + if (v === 65535) + return n; - // 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); + return dv; - return domRange; + case 'shape': + v = v.toLowerCase(); + break; + + 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); + } } - collapsed = selection.isCollapsed(); + return (v !== undefined && v !== null && v !== '') ? '' + v : dv; + }, - function findEndPoint(start) { - var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position; + getPos : function(n, ro) { + var t = this, x = 0, y = 0, e, d = t.doc, r; - // Setup temp range and collapse it - checkRng = ieRange.duplicate(); - checkRng.collapse(start); + n = t.get(n); + ro = ro || d.body; - // Create marker and insert it at the end of the endpoints parent - marker = dom.create('a'); - parent = checkRng.parentElement(); - 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; + 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; + + return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x}; } - // 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); + r = n; + while (r && r != ro && r.nodeType) { + x += r.offsetLeft || 0; + y += r.offsetTop || 0; + r = r.offsetParent; + } - // 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.parentNode; + while (r && r != ro && r.nodeType) { + x -= r.scrollLeft || 0; + y -= r.scrollTop || 0; + r = r.parentNode; } + } - // Setup container - container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling; + return {x : x, y : y}; + }, - // Handle element selection - if (container.nodeType == 1) { - dom.remove(marker); + parseStyle : function(st) { + return this.styles.parse(st); + }, - // Find offset and container - offset = dom.nodeIndex(container); - container = container.parentNode; + serializeStyle : function(o, name) { + return this.styles.serialize(o, name); + }, - // 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; - } + loadCSS : function(u) { + var t = this, d = t.doc, head; - dom.remove(marker); - } + if (!u) + u = ''; - domRange[start ? 'setStart' : 'setEnd'](container, offset); - }; + head = t.select('head')[0]; - // Find start point - findEndPoint(true); + each(u.split(','), function(u) { + var link; - // Find end point if needed - if (!collapsed) - findEndPoint(); + if (t.files[u]) + return; - return domRange; - }; + t.files[u] = true; + link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)}); - this.addRange = function(rng) { - var ieRng, ieRng2, doc = selection.dom.doc, body = doc.body, startPos, endPos, sc, so, ec, eo, marker, lastIndex, skipStart, skipEnd; + // 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(); - this.destroy(); + link.onload = null; + }; + } - // Setup some shorter versions - sc = rng.startContainer; - so = rng.startOffset; - ec = rng.endContainer; - eo = rng.endOffset; - ieRng = body.createTextRange(); + head.appendChild(link); + }); + }, - // If document selection move caret to first node in document - if (sc == doc || ec == doc) { - ieRng = body.createTextRange(); - ieRng.collapse(); - ieRng.select(); - return; - } + addClass : function(e, c) { + return this.run(e, function(e) { + var o; - // If child index resolve it - if (sc.nodeType == 1 && sc.hasChildNodes()) { - lastIndex = sc.childNodes.length - 1; + if (!c) + return 0; - // Index is higher that the child count then we need to jump over the start container - if (so > lastIndex) { - skipStart = 1; - sc = sc.childNodes[lastIndex]; - } else - sc = sc.childNodes[so]; + if (this.hasClass(e, c)) + return e.className; - // Child was text node then move offset to start of it - if (sc.nodeType == 3) - so = 0; - } + o = this.removeClass(e, c); - // If child index resolve it - if (ec.nodeType == 1 && ec.hasChildNodes()) { - lastIndex = ec.childNodes.length - 1; + return e.className = (o != '' ? (o + ' ') : '') + c; + }); + }, - if (eo == 0) { - skipEnd = 1; - ec = ec.childNodes[0]; - } else { - ec = ec.childNodes[Math.min(lastIndex, eo - 1)]; + removeClass : function(e, c) { + var t = this, re; - // Child was text node then move offset to end of text node - if (ec.nodeType == 3) - eo = ec.nodeValue.length; - } - } + return t.run(e, function(e) { + var v; - // Single element selection - if (sc == ec && sc.nodeType == 1) { - // Make control selection for some elements - if (/^(IMG|TABLE)$/.test(sc.nodeName) && so != eo) { - ieRng = body.createControlRange(); - ieRng.addElement(sc); - } else { - ieRng = body.createTextRange(); + if (t.hasClass(e, c)) { + if (!re) + re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"); - // Padd empty elements with invisible character - if (!sc.hasChildNodes() && sc.canHaveHTML) - sc.innerHTML = invisibleChar; + v = e.className.replace(re, ' '); + v = tinymce.trim(v != ' ' ? v : ''); - // Select element contents - ieRng.moveToElementText(sc); + e.className = v; - // If it's only containing a padding remove it so the caret remains - if (sc.innerHTML == invisibleChar) { - ieRng.collapse(TRUE); - sc.removeChild(sc.firstChild); + // Empty class attr + if (!v) { + e.removeAttribute('class'); + e.removeAttribute('className'); } + + return v; } - if (so == eo) - ieRng.collapse(eo <= rng.endContainer.childNodes.length - 1); + return e.className; + }); + }, - ieRng.select(); - ieRng.scrollIntoView(); - return; - } + hasClass : function(n, c) { + n = this.get(n); - // Create range and marker - ieRng = body.createTextRange(); - marker = doc.createElement('span'); - marker.innerHTML = ' '; - - // Set start of range to startContainer/startOffset - if (sc.nodeType == 3) { - // Insert marker after/before startContainer - if (skipStart) - dom.insertAfter(marker, sc); - else - sc.parentNode.insertBefore(marker, sc); + if (!n || !c) + return false; - // Select marker the caret to offset position - ieRng.moveToElementText(marker); - marker.parentNode.removeChild(marker); - ieRng.move('character', so); - } else { - ieRng.moveToElementText(sc); + return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1; + }, - if (skipStart) - ieRng.collapse(FALSE); - } + show : function(e) { + return this.setStyle(e, 'display', 'block'); + }, - // If same text container then we can do a more simple move - if (sc == ec && sc.nodeType == 3) { - try { - ieRng.moveEnd('character', eo - so); - ieRng.select(); - ieRng.scrollIntoView(); - } catch (ex) { - // Some times a Runtime error of the 800a025e type gets thrown - // especially when the caret is placed before a table. - // This is a somewhat strange location for the caret. - // TODO: Find a better solution for this would possible require a rewrite of the setRng method - } + hide : function(e) { + return this.setStyle(e, 'display', 'none'); + }, - return; - } + isHidden : function(e) { + e = this.get(e); - // Set end of range to endContainer/endOffset - ieRng2 = body.createTextRange(); - if (ec.nodeType == 3) { - // Insert marker after/before startContainer - ec.parentNode.insertBefore(marker, ec); + return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none'; + }, - // Move selection to end marker and move caret to end offset - ieRng2.moveToElementText(marker); - marker.parentNode.removeChild(marker); - ieRng2.move('character', eo); - ieRng.setEndPoint('EndToStart', ieRng2); - } else { - ieRng2.moveToElementText(ec); - ieRng2.collapse(!!skipEnd); - ieRng.setEndPoint('EndToEnd', ieRng2); - } + uniqueId : function(p) { + return (!p ? 'mce_' : p) + (this.counter++); + }, - ieRng.select(); - ieRng.scrollIntoView(); - }; + setHTML : function(element, html) { + var self = this; - this.getRangeAt = function() { - // Setup new range if the cache is empty - if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) { - range = getRange(); + 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); - // Store away text range for next call - lastIERng = selection.getRng(); - } + 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; - // 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 html; + }); + }, - // Return cached range - return range; - }; + getOuterHTML : function(elm) { + var doc, self = this; - this.destroy = function() { - // Destroy cached range and last IE range to avoid memory leaks - lastIERng = range = null; - }; + elm = self.get(elm); - // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode - if (selection.dom.boxModel) { - (function() { - var doc = dom.doc, body = doc.body, started, startRng; + if (!elm) + return null; - // Make HTML element unselectable since we are going to handle selection by hand - doc.documentElement.unselectable = TRUE; + if (elm.nodeType === 1 && self.hasOuterHTML) + return elm.outerHTML; - // Return range from point or null if it failed - function rngFromPoint(x, y) { - var rng = body.createTextRange(); + doc = (elm.ownerDocument || self.doc).createElement("body"); + doc.appendChild(elm.cloneNode(true)); - try { - rng.moveToPoint(x, y); - } catch (ex) { - // IE sometimes throws and exception, so lets just ignore it - rng = null; - } + return doc.innerHTML; + }, - return rng; - }; + setOuterHTML : function(e, h, d) { + var t = this; + + function setHTML(e, h, d) { + var n, tp; + + tp = d.createElement("body"); + tp.innerHTML = h; + + n = tp.lastChild; + while (n) { + t.insertAfter(n.cloneNode(true), e); + n = n.previousSibling; + } - // Fires while the selection is changing - function selectionChange(e) { - var pointRng; + t.remove(e); + }; - // Check if the button is down or not - if (e.button) { - // Create range from mouse position - pointRng = rngFromPoint(e.x, e.y); + return this.run(e, function(e) { + e = t.get(e); - 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); + // Only set HTML on elements + if (e.nodeType == 1) { + d = d || e.ownerDocument || t.doc; - pointRng.select(); + 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 - endSelection(); + setHTML(e, h, d); } + }); + }, - // Removes listeners - function endSelection() { - dom.unbind(doc, 'mouseup', endSelection); - dom.unbind(doc, 'mousemove', selectionChange); - started = 0; - }; - - // Detect when user selects outside BODY - dom.bind(doc, 'mousedown', function(e) { - if (e.target.nodeName === 'HTML') { - if (started) - endSelection(); + decode : Entities.decode, - started = 1; + encode : Entities.encodeAllRaw, - // 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); + insertAfter : function(node, reference_node) { + reference_node = this.get(reference_node); - startRng.select(); - } - } - }); - })(); - } - }; + return this.run(node, function(node) { + var parent, nextSibling; - // Expose the selection object - tinymce.dom.TridentSelection = Selection; -})(); + parent = reference_node.parentNode; + nextSibling = reference_node.nextSibling; + if (nextSibling) + parent.insertBefore(node, nextSibling); + else + parent.appendChild(node); -/* - * 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 node; + }); + }, -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false; + isBlock : function(node) { + var type = node.nodeType; -var Sizzle = function(selector, context, results, seed) { - results = results || []; - var origContext = context = context || document; + // If it's a node then check the type and use the nodeName + if (type) + return !!(type === 1 && blockElementsMap[node.nodeName]); - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } + return !!blockElementsMap[node]; + }, - var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context); - - // Reset the position of the chunker regexp (start from head) - chunker.lastIndex = 0; - - while ( (m = chunker.exec(selector)) !== null ) { - parts.push( m[1] ); - - if ( m[2] ) { - extra = RegExp.rightContext; - break; - } - } + replace : function(n, o, k) { + 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 ); + if (is(o, 'array')) + n = n.cloneNode(true); - while ( parts.length ) { - selector = parts.shift(); + return t.run(o, function(o) { + if (k) { + each(tinymce.grep(o.childNodes), function(c) { + n.appendChild(c); + }); + } - if ( Expr.relative[ selector ] ) - selector += parts.shift(); + return o.parentNode.replaceChild(n, o); + }); + }, - 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]) ) { - var ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; - } + rename : function(elm, name) { + var t = this, newElm; - if ( context ) { - var 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 (elm.nodeName != name.toUpperCase()) { + // Rename block element + newElm = t.create(name); - if ( parts.length > 0 ) { - checkSet = makeArray(set); - } else { - prune = false; - } + // Copy attribs to new block + each(t.getAttribs(elm), function(attr_node) { + t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName)); + }); - while ( parts.length ) { - var cur = parts.pop(), pop = cur; + // Replace block + t.replace(newElm, elm, 1); + } - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } + return newElm || elm; + }, - if ( pop == null ) { - pop = context; - } + findCommonAncestor : function(a, b) { + var ps = a, pe; - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - } else { - checkSet = parts = []; - } - } + while (ps) { + pe = b; - if ( !checkSet ) { - checkSet = set; - } + while (pe && ps != pe) + pe = pe.parentNode; - if ( !checkSet ) { - throw "Syntax error, unrecognized expression: " + (cur || selector); - } + if (ps == pe) + break; - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - } else if ( context && context.nodeType === 1 ) { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - } else { - for ( var i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } + ps = ps.parentNode; } - } - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; -Sizzle.uniqueSort = function(results){ - if ( sortOrder ) { - hasDuplicate = false; - results.sort(sortOrder); + if (!ps && a.ownerDocument) + return a.ownerDocument.documentElement; - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[i-1] ) { - results.splice(i--, 1); - } - } - } - } -}; + return ps; + }, -Sizzle.matches = function(expr, set){ - return Sizzle(expr, null, null, set); -}; + toHex : function(s) { + var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s); -Sizzle.find = function(expr, context, isXML){ - var set, match; + function hex(s) { + s = parseInt(s).toString(16); - if ( !expr ) { - return []; - } + return s.length > 1 ? s : '0' + s; // 0 -> 00 + }; - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var type = Expr.order[i], match; - - if ( (match = Expr.match[ type ].exec( expr )) ) { - var left = RegExp.leftContext; + if (c) { + s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]); - 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; - } + return s; } - } - } - if ( !set ) { - set = context.getElementsByTagName("*"); - } + return s; + }, - return {set: set, expr: expr}; -}; + getClasses : function() { + var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov; -Sizzle.filter = function(expr, set, inplace, not){ - var old = expr, result = [], curLoop = set, match, anyFound, - isXMLFilter = set && set[0] && isXML(set[0]); + if (t.classes) + return t.classes; - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.match[ type ].exec( expr )) != null ) { - var filter = Expr.filter[ type ], found, item; - anyFound = false; + function addClasses(s) { + // IE style imports + each(s.imports, function(r) { + addClasses(r); + }); - if ( curLoop == result ) { - result = []; - } + 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, ""); - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + // Is internal or it doesn't contain a class + if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v)) + return; - if ( !match ) { - anyFound = found = true; - } else if ( match === true ) { - continue; - } - } + // Remove everything but class name + ov = v; + v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v); - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; + // Filter classes + if (f && !(v = f(v, ov))) + return; - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - } else { - curLoop[i] = false; - } - } else if ( pass ) { - result.push( item ); - anyFound = true; + if (!lo[v]) { + cl.push({'class' : v}); + lo[v] = 1; + } + }); } - } - } - } + break; - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; + // Import + case 3: + addClasses(r.styleSheet); + break; } + }); + }; - expr = expr.replace( Expr.match[ type ], "" ); + try { + each(t.doc.styleSheets, addClasses); + } catch (ex) { + // Ignore + } - if ( !anyFound ) { - return []; + if (cl.length > 0) + t.classes = cl; + + return cl; + }, + + run : function(e, f, s) { + var t = this, o; + + if (t.doc && typeof(e) === 'string') + e = t.get(e); + + if (!e) + return false; + + s = s || this; + if (!e.nodeType && (e.length || e.length === 0)) { + o = []; + + each(e, function(e, i) { + if (e) { + if (typeof(e) == 'string') + e = t.doc.getElementById(e); + + o.push(f.call(s, e, i)); } + }); - break; - } + return o; } - } - // Improper expression - if ( expr == old ) { - if ( anyFound == null ) { - throw "Syntax error, unrecognized expression: " + expr; - } else { - break; - } - } + return f.call(s, e); + }, - old = expr; - } + getAttribs : function(n) { + var o; - return curLoop; -}; + n = this.get(n); -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\(\)]*)+)\2\))?/ - }, - attrMap: { - "class": "className", - "for": "htmlFor" - }, - attrHandle: { - href: function(elem){ - return elem.getAttribute("href"); - } - }, - relative: { - "+": function(checkSet, part, isXML){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !/\W/.test(part), - isPartStrNotTag = isPartStr && !isTag; + if (!n) + return []; - if ( isTag && !isXML ) { - part = part.toUpperCase(); - } + if (isIE) { + o = []; - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + // Object will throw exception in IE + if (n.nodeName == 'OBJECT') + return n.attributes; - checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? - elem || false : - elem === part; - } - } + // 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'}); - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); + // 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 n.attributes; }, - ">": function(checkSet, part, isXML){ - var isPartStr = typeof part === "string"; - if ( isPartStr && !/\W/.test(part) ) { - part = isXML ? part : part.toUpperCase(); + isEmpty : function(node, elements) { + var self = this, i, attributes, type, walker, name; - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName === part ? parent : false; - } - } - } else { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; + 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 + if (elements && elements[node.nodeName.toLowerCase()]) + return false; + + // Keep elements with data attributes or name attribute like
    + attributes = self.getAttribs(node); + i = node.attributes.length; + while (i--) { + name = node.attributes[i].nodeName; + if (name === "name" || name.indexOf('data-') === 0) + return false; + } } - } - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } + // Keep non whitespace text nodes + if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue))) + return false; + } while (node = walker.next()); } + + return true; }, - "": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; - if ( !part.match(/\W/) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); - checkFn = dirNodeCheck; - } + destroy : function(s) { + var t = this; - checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); - }, - "~": function(checkSet, part, isXML){ - var doneName = done++, checkFn = dirCheck; + if (t.events) + t.events.destroy(); - if ( typeof part === "string" && !part.match(/\W/) ) { - var nodeCheck = part = isXML ? part : part.toUpperCase(); - checkFn = dirNodeCheck; - } + t.win = t.doc = t.root = t.events = null; - 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] : []; - } + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); }, - NAME: function(match, context, isXML){ - 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] ); - } - } + createRng : function() { + var d = this.doc; - return ret.length === 0 ? null : ret; - } + return d.createRange ? d.createRange() : new tinymce.dom.Range(this); }, - 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; - } + nodeIndex : function(node, normalized) { + var idx = 0, lastNodeType, lastNode, nodeType, nodeValueExists; - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { - if ( !inplace ) - result.push( elem ); - } else if ( inplace ) { - curLoop[i] = 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) { + // ensure that text nodes that have been removed are handled correctly in Internet Explorer. + // (the nodeValue attribute will not exist, and will error here). + nodeValueExists = false; + try {nodeValueExists = node.nodeValue.length} catch (c) {} + if (nodeType == lastNodeType || !nodeValueExists) + continue; } + idx++; + lastNodeType = nodeType; } } - return false; - }, - ID: function(match){ - return match[1].replace(/\\/g, ""); - }, - TAG: function(match, curLoop){ - for ( var i = 0; curLoop[i] === false; i++ ){} - return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + return idx; }, - 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; - } + split : function(pe, e, re) { + var t = this, r = t.createRng(), bef, aft, pa; - // TODO: Move to normal caching system - match[0] = done++; + // 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; - 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 (type == 1 && node.getAttribute('data-mce-type') == 'bookmark') + return; - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } + for (i = children.length - 1; i >= 0; i--) + trim(children[i]); - 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 ( match[3].match(chunker).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 ); + 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; } - return false; + + t.remove(node); } - } 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.toUpperCase() === "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 || "").indexOf(match[3]) >= 0; - } else if ( name === "not" ) { - var not = match[3]; + return node; + }; - for ( var i = 0, l = not.length; i < l; i++ ) { - if ( not[i] === elem ) { - return false; - } - } + if (pe && e) { + // Get before chunk + r.setStart(pe.parentNode, t.nodeIndex(pe)); + r.setEnd(e.parentNode, t.nodeIndex(e)); + bef = r.extractContents(); - return true; - } - }, - 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]; + // 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(); - 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 === 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]; + // Insert before chunk + pa = pe.parentNode; + pa.insertBefore(trim(bef), pe); - 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 ]; + // Insert middle chunk + if (re) + pa.replaceChild(re, e); + else + pa.insertBefore(e, pe); - if ( filter ) { - return filter( elem, i, match, array ); + // Insert after chunk + pa.insertBefore(trim(aft), pe); + t.remove(pe); + + return re || e; } - } - } -}; + }, -var origPOS = Expr.match.POS; + bind : function(target, name, func, scope) { + var t = this; -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); -} + if (!t.events) + t.events = new tinymce.dom.EventUtils(); -var makeArray = function(array, results) { - array = Array.prototype.slice.call( array ); + return t.events.add(target, name, func, scope || this); + }, - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; + unbind : function(target, name, func) { + var t = this; -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -try { - Array.prototype.slice.call( document.documentElement.childNodes ); + if (!t.events) + t.events = new tinymce.dom.EventUtils(); -// Provide a fallback method if it does not work -} catch(e){ - makeArray = function(array, results) { - var ret = results || []; + return t.events.remove(target, name, func); + }, - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - } else { - if ( typeof array.length === "number" ) { - for ( var i = 0, l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - } else { - for ( var i = 0; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - return ret; - }; -} + _findSib : function(node, selector, name) { + var t = this, f = selector; -var sortOrder; + if (node) { + // If expression make a function of it using is + if (is(f, 'string')) { + f = function(node) { + return t.is(node, selector); + }; + } -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - 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 ) { - var ret = a.sourceIndex - b.sourceIndex; - if ( ret === 0 ) { - hasDuplicate = true; + // Loop all siblings + for (node = node[name]; node; node = node[name]) { + if (f(node)) + return node; + } + } + + return null; + }, + + _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); } - return ret; - }; -} else if ( document.createRange ) { - sortOrder = function( a, b ) { - 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; + + /* + walk : function(n, f, s) { + var d = this.doc, w; + + if (d.createTreeWalker) { + w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false); + + while ((n = w.nextNode()) != null) + f.call(s || this, n); + } else + tinymce.walk(n, f, 'childNodes', 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 = ""; + /* + toRGB : function(s) { + var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s); - // Inject it into the root element, check its status, and remove it quickly - var root = document.documentElement; - root.insertBefore( form, root.firstChild ); + if (c) { + // #FFF -> #FFFFFF + if (!is(c[3])) + c[3] = c[2] = c[1]; - // 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 : []; + return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")"; } - }; - - Expr.filter.ID = function(elem, match){ - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - root.removeChild( form ); -})(); + return s; + } + */ + }); -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") + tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0}); +})(tinymce); - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); +(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; - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function(match, context){ - var results = context.getElementsByTagName(match[1]); + extend(t, { + // Inital states + startContainer : doc, + startOffset : 0, + endContainer : doc, + endOffset : 0, + collapsed : TRUE, + commonAncestorContainer : doc, - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; + // Range constants + START_TO_START : 0, + START_TO_END : 1, + END_TO_END : 2, + END_TO_START : 3, - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } + // 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 + }); - results = tmp; - } + function setStart(n, o) { + _setEndPoint(TRUE, n, o); + }; - return results; + function setEnd(n, o) { + _setEndPoint(FALSE, n, o); }; - } - // 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 setStartBefore(n) { + setStart(n.parentNode, nodeIndex(n)); }; - } -})(); -if ( document.querySelectorAll ) (function(){ - var oldSizzle = Sizzle, div = document.createElement("div"); - div.innerHTML = "

    "; + function setStartAfter(n) { + setStart(n.parentNode, nodeIndex(n) + 1); + }; - // 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; + function setEndBefore(n) { + setEnd(n.parentNode, nodeIndex(n)); + }; - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && context.nodeType === 9 && !isXML(context) ) { - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(e){} - } - - return oldSizzle(query, context, extra, seed); - }; + function setEndAfter(n) { + setEnd(n.parentNode, nodeIndex(n) + 1); + }; - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } -})(); + 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]; + } -if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ - var div = document.createElement("div"); - div.innerHTML = "
    "; + t.collapsed = TRUE; + }; - // Opera can't find a second classname (in 9.6) - if ( div.getElementsByClassName("e").length === 0 ) - return; + function selectNode(n) { + setStartBefore(n); + setEndAfter(n); + }; - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; + function selectNodeContents(n) { + setStart(n, 0); + setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length); + }; - if ( div.getElementsByClassName("e").length === 1 ) - return; + 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; - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function(match, context, isXML) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; -})(); + // Check START_TO_START + if (h === 0) + return _compareBoundaryPoints(sc, so, rsc, rso); + + // 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); + }; -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - if ( sibDir && elem.nodeType === 1 ){ - elem.sizcache = doneName; - elem.sizset = i; - } - elem = elem[dir]; - var match = false; + function deleteContents() { + _traverse(DELETE); + }; - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } + function extractContents() { + return _traverse(EXTRACT); + }; - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } + function cloneContents() { + return _traverse(CLONE); + }; - if ( elem.nodeName === cur ) { - match = elem; - break; + function insertNode(n) { + var startContainer = this[START_CONTAINER], + startOffset = this[START_OFFSET], nn, o; + + // 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]; - elem = elem[dir]; + if (o) + startContainer.insertBefore(n, o); + else + startContainer.appendChild(n); } + }; - checkSet[i] = match; - } - } -} + function surroundContents(n) { + var f = t.extractContents(); -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - var sibDir = dir == "previousSibling" && !isXML; - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - if ( elem ) { - if ( sibDir && elem.nodeType === 1 ) { - elem.sizcache = doneName; - elem.sizset = i; - } - elem = elem[dir]; - var match = false; + t.insertNode(n); + n.appendChild(f); + t.selectNode(n); + }; - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } + 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 + }); + }; - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } + // Private methods - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } + function _getSelectedNode(container, offset) { + var child; - elem = elem[dir]; - } + if (container.nodeType == 3 /* TEXT_NODE */) + return container; - checkSet[i] = match; - } - } -} + if (offset < 0) + return container; -var contains = document.compareDocumentPosition ? function(a, b){ - return a.compareDocumentPosition(b) & 16; -} : function(a, b){ - return a !== b && (a.contains ? a.contains(b) : true); -}; + child = container.firstChild; + while (child && offset > 0) { + --offset; + child = child.nextSibling; + } -var isXML = function(elem){ - return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || - !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; -}; + if (child) + return child; -var posProcess = function(selector, context){ - var tmpSet = [], later = "", match, - root = context.nodeType ? [context] : context; + return container; + }; - // 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, "" ); - } + function _isCollapsed() { + return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]); + }; - selector = Expr.relative[selector] ? selector + "*" : selector; + 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 - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } + if (offsetA < offsetB) + return -1; // before - return Sizzle.filter( later, tmpSet ); -}; + return 1; // after + } -// EXPOSE + // 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; -window.tinymce.dom.Sizzle = Sizzle; + if (c) { + offsetC = 0; + n = containerA.firstChild; -})(); + while (n != c && offsetC < offsetA) { + offsetC++; + n = n.nextSibling; + } + if (offsetA <= offsetC) + return -1; // before -(function(tinymce) { - // Shorten names - var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; + return 1; // after + } - tinymce.create('tinymce.dom.EventUtils', { - EventUtils : function() { - this.inits = []; - this.events = []; - }, + // 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; + } - add : function(o, n, f, s) { - var cb, t = this, el = t.events, r; + if (c) { + offsetC = 0; + n = containerB.firstChild; - if (n instanceof Array) { - r = []; + while (n != c && offsetC < offsetB) { + offsetC++; + n = n.nextSibling; + } - each(n, function(n) { - r.push(t.add(o, n, f, s)); - }); + if (offsetC < offsetB) + return -1; // before - return r; + return 1; // after } - // Handle array - if (o && o.hasOwnProperty && o instanceof Array) { - r = []; + // 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; - each(o, function(o) { - o = DOM.get(o); - r.push(t.add(o, n, f, s)); - }); + while (childA && childA.parentNode != cmnRoot) + childA = childA.parentNode; - return r; - } + if (!childA) + childA = cmnRoot; - o = DOM.get(o); + childB = containerB; + while (childB && childB.parentNode != cmnRoot) + childB = childB.parentNode; - if (!o) - return; + if (!childB) + childB = cmnRoot; - // Setup event callback - cb = function(e) { - // Is all events disabled - if (t.disabled) - return; + if (childA == childB) + return 0; // equal - e = e || window.event; + n = cmnRoot.firstChild; + while (n) { + if (n == childA) + return -1; // before - // Patch in target, preventDefault and stopPropagation in IE it's W3C valid - if (e && isIE) { - if (!e.target) - e.target = e.srcElement; + if (n == childB) + return 1; // after - // Patch in preventDefault, stopPropagation methods for W3C compatibility - tinymce.extend(e, t._stoppers); - } + n = n.nextSibling; + } + }; - if (!s) - return f(e); + function _setEndPoint(st, n, o) { + var ec, sc; - return f.call(s, e); - }; - - if (n == 'unload') { - tinymce.unloads.unshift({func : cb}); - return cb; + if (st) { + t[START_CONTAINER] = n; + t[START_OFFSET] = o; + } else { + t[END_CONTAINER] = n; + t[END_OFFSET] = o; } - if (n == 'init') { - if (t.domLoaded) - cb(); - else - t.inits.push(cb); - - return cb; - } + // 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; - // Store away listener reference - el.push({ - obj : o, - name : n, - func : f, - cfunc : cb, - scope : s - }); + sc = t[START_CONTAINER]; + while (sc.parentNode) + sc = sc.parentNode; - t._add(o, n, cb); + 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); - return f; - }, + t.collapsed = _isCollapsed(); + t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]); + }; - remove : function(o, n, f) { - var t = this, a = t.events, s = false, r; + function _traverse(how) { + var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep; - // Handle array - if (o && o.hasOwnProperty && o instanceof Array) { - r = []; + if (t[START_CONTAINER] == t[END_CONTAINER]) + return _traverseSameContainer(how); - each(o, function(o) { - o = DOM.get(o); - r.push(t.remove(o, n, f)); - }); + for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[START_CONTAINER]) + return _traverseCommonStartContainer(c, how); - return r; + ++endContainerDepth; } - o = DOM.get(o); - - 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; - } - }); + for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) { + if (p == t[END_CONTAINER]) + return _traverseCommonEndContainer(c, how); - return s; - }, + ++startContainerDepth; + } - clear : function(o) { - var t = this, a = t.events, i, e; + depthDiff = startContainerDepth - endContainerDepth; - if (o) { - o = DOM.get(o); + startNode = t[START_CONTAINER]; + while (depthDiff > 0) { + startNode = startNode.parentNode; + depthDiff--; + } - for (i = a.length - 1; i >= 0; i--) { - e = a[i]; + endNode = t[END_CONTAINER]; + while (depthDiff < 0) { + endNode = endNode.parentNode; + depthDiff++; + } - if (e.obj === o) { - t._remove(e.obj, e.name, e.cfunc); - e.obj = e.cfunc = null; - a.splice(i, 1); - } - } + // 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; } - }, - cancel : function(e) { - if (!e) - return false; + return _traverseCommonAncestors(startNode, endNode, how); + }; - this.stop(e); + function _traverseSameContainer(how) { + var frag, s, sub, n, cnt, sibling, xferNode; - return this.prevent(e); - }, + if (how != DELETE) + frag = doc.createDocumentFragment(); - stop : function(e) { - if (e.stopPropagation) - e.stopPropagation(); - else - e.cancelBubble = true; + // If selection is empty, just return the fragment + if (t[START_OFFSET] == t[END_OFFSET]) + return frag; - 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]); - prevent : function(e) { - if (e.preventDefault) - e.preventDefault(); - else - e.returnValue = false; + // 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]); - return false; - }, + // Nothing is partially selected, so collapse to start point + t.collapse(TRUE); + } - destroy : function() { - var t = this; + if (how == DELETE) + return; - each(t.events, function(e, i) { - t._remove(e.obj, e.name, e.cfunc); - e.obj = e.cfunc = null; - }); + frag.appendChild(doc.createTextNode(sub)); + return frag; + } - t.events = []; - t = null; - }, + // Copy nodes between the start/end offsets. + n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]); + cnt = t[END_OFFSET] - t[START_OFFSET]; - _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; - }, + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); - _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 - } + if (frag) + frag.appendChild( xferNode ); + + --cnt; + n = sibling; } - }, - _pageInit : function(win) { - var t = this; + // Nothing is partially selected, so collapse to start point + if (how != CLONE) + t.collapse(TRUE); - // Keep it from running more than once - if (t.domLoaded) - return; + return frag; + }; - t.domLoaded = true; + function _traverseCommonStartContainer(endAncestor, how) { + var frag, n, endIdx, cnt, sibling, xferNode; - each(t.inits, function(c) { - c(); - }); + if (how != DELETE) + frag = doc.createDocumentFragment(); - t.inits = []; - }, + n = _traverseRightBoundary(endAncestor, how); - _wait : function(win) { - var t = this, doc = win.document; + if (frag) + frag.appendChild(n); - // No need since the document is already loaded - if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { - t.domLoaded = 1; - return; - } + endIdx = nodeIndex(endAncestor); + cnt = endIdx - t[START_OFFSET]; - // Use IE method - if (doc.attachEvent) { - doc.attachEvent("onreadystatechange", function() { - if (doc.readyState === "complete") { - doc.detachEvent("onreadystatechange", arguments.callee); - t._pageInit(win); - } - }); + if (cnt <= 0) { + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } - if (doc.documentElement.doScroll && win == win.top) { - (function() { - if (t.domLoaded) - return; + return frag; + } - 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; - } + n = endAncestor.previousSibling; + while (cnt > 0) { + sibling = n.previousSibling; + xferNode = _traverseFullySelected(n, how); - t._pageInit(win); - })(); - } - } else if (doc.addEventListener) { - t._add(win, 'DOMContentLoaded', function() { - t._pageInit(win); - }); + if (frag) + frag.insertBefore(xferNode, frag.firstChild); + + --cnt; + n = sibling; } - t._add(win, 'load', function() { - t._pageInit(win); - }); - }, + // Collapse to just before the endAncestor, which + // is partially selected. + if (how != CLONE) { + t.setEndBefore(endAncestor); + t.collapse(FALSE); + } - _stoppers : { - preventDefault : function() { - this.returnValue = false; - }, + return frag; + }; - stopPropagation : function() { - this.cancelBubble = true; + function _traverseCommonEndContainer(startAncestor, how) { + var frag, startIdx, n, cnt, sibling, xferNode; + + if (how != DELETE) + frag = doc.createDocumentFragment(); + + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); + + startIdx = nodeIndex(startAncestor); + ++startIdx; // Because we already traversed it + + cnt = t[END_OFFSET] - startIdx; + n = startAncestor.nextSibling; + while (cnt > 0) { + sibling = n.nextSibling; + xferNode = _traverseFullySelected(n, how); + + if (frag) + frag.appendChild(xferNode); + + --cnt; + n = sibling; } - } - }); - Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } - // Dispatch DOM content loaded event for IE and Safari - Event._wait(window); + return frag; + }; - tinymce.addUnload(function() { - Event.destroy(); - }); -})(tinymce); + function _traverseCommonAncestors(startAncestor, endAncestor, how) { + var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling; -(function(tinymce) { - tinymce.dom.Element = function(id, settings) { - var t = this, dom, el; + if (how != DELETE) + frag = doc.createDocumentFragment(); - t.settings = settings = settings || {}; - t.id = id; - t.dom = dom = settings.dom || tinymce.DOM; + n = _traverseLeftBoundary(startAncestor, how); + if (frag) + frag.appendChild(n); - // Only IE leaks DOM references, this is a lot faster - if (!tinymce.isIE) - el = dom.get(t.id); + commonParent = startAncestor.parentNode; + startOffset = nodeIndex(startAncestor); + endOffset = nodeIndex(endAncestor); + ++startOffset; - 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; + cnt = endOffset - startOffset; + sibling = startAncestor.nextSibling; - for (i = 0; i < arguments.length; i++) - a.push(arguments[i]); + while (cnt > 0) { + nextSibling = sibling.nextSibling; + n = _traverseFullySelected(sibling, how); - a = dom[k].apply(dom, a); - t.update(k); + if (frag) + frag.appendChild(n); - return a; - }; - }); + sibling = nextSibling; + --cnt; + } - tinymce.extend(t, { - on : function(n, f, s) { - return tinymce.dom.Event.add(t.id, n, f, s); - }, + n = _traverseRightBoundary(endAncestor, how); - getXY : function() { - return { - x : parseInt(t.getStyle('left')), - y : parseInt(t.getStyle('top')) - }; - }, + if (frag) + frag.appendChild(n); - getSize : function() { - var n = dom.get(t.id); + if (how != CLONE) { + t.setStartAfter(startAncestor); + t.collapse(TRUE); + } - return { - w : parseInt(t.getStyle('width') || n.clientWidth), - h : parseInt(t.getStyle('height') || n.clientHeight) - }; - }, + return frag; + }; - moveTo : function(x, y) { - t.setStyles({left : x, top : y}); - }, + function _traverseRightBoundary(root, how) { + var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER]; - moveBy : function(x, y) { - var p = t.getXY(); + if (next == root) + return _traverseNode(next, isFullySelected, FALSE, how); - t.moveTo(p.x + x, p.y + y); - }, + parent = next.parentNode; + clonedParent = _traverseNode(parent, FALSE, FALSE, how); - resizeTo : function(w, h) { - t.setStyles({width : w, height : h}); - }, + while (parent) { + while (next) { + prevSibling = next.previousSibling; + clonedChild = _traverseNode(next, isFullySelected, FALSE, how); - resizeBy : function(w, h) { - var s = t.getSize(); + if (how != DELETE) + clonedParent.insertBefore(clonedChild, clonedParent.firstChild); - t.resizeTo(s.w + w, s.h + h); - }, + isFullySelected = TRUE; + next = prevSibling; + } - update : function(k) { - var b; + if (parent == root) + return clonedParent; - if (tinymce.isIE6 && settings.blocker) { - k = k || ''; + next = parent.previousSibling; + parent = parent.parentNode; - // Ignore getters - if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) - return; + clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how); - // Remove blocker on remove - if (k == 'remove') { - dom.remove(t.blocker); - return; + 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 t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false; + + // Returns a W3C DOM compatible range object by using the IE Range API + function getRange() { + var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed; + + // 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 or text selection of a image + if (ieRange.item || !element.hasChildNodes()) { + if (collapsed) { + domRange.setStart(element, 0); + domRange.setEnd(element, 0); + } else { + domRange.setStart(element.parentNode, dom.nodeIndex(element)); + domRange.setEnd(domRange.startContainer, domRange.startOffset + 1); + } + + return domRange; + } + + function findEndPoint(start) { + var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position; + + // Setup temp range and collapse it + checkRng = ieRange.duplicate(); + checkRng.collapse(start); + + // Create marker and insert it at the end of the endpoints parent + marker = dom.create('a'); + parent = checkRng.parentElement(); + + // 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; + } + + 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; + } + + // 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; + } + } + + // Setup container + container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling; + + // Handle element selection + if (container.nodeType == 1) { + dom.remove(marker); + + // Find offset and container + offset = dom.nodeIndex(container); + container = container.parentNode; + + // 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; + } + + dom.remove(marker); + } + + domRange[start ? 'setStart' : 'setEnd'](container, offset); + }; + + // Find start point + findEndPoint(true); + + // Find end point if needed + if (!collapsed) + findEndPoint(); + + return domRange; + }; + + 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(invisibleChar); + container.appendChild(marker); + tmpRng.moveToElementText(marker.parentNode); + tmpRng.collapse(TRUE); + } + + ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng); + dom.remove(marker); + } + } + + // Destroy cached range + this.destroy(); + + // 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(); + }; + + this.getRangeAt = function() { + // Setup new range if the cache is empty + if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) { + range = getRange(); + + // Store away text range for next call + lastIERng = selection.getRng(); + } + + // 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 cached range + return range; + }; + + this.destroy = function() { + // Destroy cached range and last IE range to avoid memory leaks + lastIERng = range = null; + }; + }; + + // 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]; - 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); + 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 ]; - 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 - }); - } + if ( filter ) { + return filter( elem, i, match, array ); } - }); - }; -})(tinymce); + } + } +}; -(function(tinymce) { - function trimNl(s) { - return s.replace(/[\n\r]+/g, ''); +var origPOS = Expr.match.POS, + fescape = function(all, num){ + return "\\" + (num - 0 + 1); }; - // Shorten names - var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; - - tinymce.create('tinymce.dom.Selection', { - Selection : function(dom, win, serializer) { - var t = this; - - t.dom = dom; - t.win = win; - t.serializer = serializer; - - // Add events - each([ - 'onBeforeSetContent', - 'onBeforeGetContent', - 'onSetContent', - 'onGetContent' - ], function(e) { - t[e] = new tinymce.util.Dispatcher(t); - }); - - // No W3C Range support - if (!t.win.getSelection) - t.tridentSel = new tinymce.dom.TridentSelection(t); +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) ); +} - // Prevent leaks - tinymce.addUnload(t.destroy, t); - }, +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); - getContent : function(s) { - var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; - s = s || {}; - wb = wa = ''; - s.get = true; - s.format = s.format || 'html'; - t.onBeforeGetContent.dispatch(t, s); +// 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; - if (s.format == 'text') - return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || [], i = 0; - if (r.cloneContents) { - n = r.cloneContents(); + 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] ); + } + } + } - 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(); + return ret; + }; +} - // Keep whitespace before and after - if (/^\s/.test(e.innerHTML)) - wb = ' '; +var sortOrder; - if (/\s+$/.test(e.innerHTML)) - wa = ' '; +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } - s.getInner = true; + 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; + } - s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; - t.onGetContent.dispatch(t, s); + 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; + } - return s.content; - }, + 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; + }; +} - setContent : function(h, s) { - var t = this, r = t.getRng(), c, d = t.win.document; +// Utility function for retreiving the text value of an array of DOM nodes +Sizzle.getText = function( elems ) { + var ret = "", elem; - s = s || {format : 'html'}; - s.set = true; - h = s.content = t.dom.processHTML(h); + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; - // Dispatch before set content event - t.onBeforeSetContent.dispatch(t, s); - h = s.content; + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; - if (r.insertNode) { - // Make caret marker since insertNode places the caret in the beginning of text after insert - h += '_'; + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += Sizzle.getText( elem.childNodes ); + } + } - // 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 { - r.insertNode(r.createContextualFragment(h)); - } - } + return ret; +}; - // 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); +// 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 = ""; - // 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(); - } + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); - r.pasteHTML(h); + // 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 : []; } + }; - // Dispatch set content event - t.onSetContent.dispatch(t, s); - }, - - getStart : function() { - var t = this, r = t.getRng(), e; - - if (r.duplicate || r.item) { - if (r.item) - return r.item(0); + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } - r = r.duplicate(); - r.collapse(1); - e = r.parentElement(); + root.removeChild( form ); + root = form = null; // release memory in IE +})(); - if (e && e.nodeName == 'BODY') - return e.firstChild || e; +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") - return e; - } else { - e = r.startContainer; + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); - if (e.nodeType == 1 && e.hasChildNodes()) - e = e.childNodes[Math.min(e.childNodes.length - 1, r.startOffset)]; + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); - if (e && e.nodeType == 3) - return e.parentNode; + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; - return e; + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; } - }, - getEnd : function() { - var t = this, r = t.getRng(), e, eo; + return results; + }; + } - if (r.duplicate || r.item) { - if (r.item) - return r.item(0); + // 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); + }; + } - r = r.duplicate(); - r.collapse(0); - e = r.parentElement(); + div = null; // release memory in IE +})(); - if (e && e.nodeName == 'BODY') - return e.lastChild || e; +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; - return e; - } else { - e = r.endContainer; - eo = r.endOffset; + // 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; - if (e.nodeType == 1 && e.hasChildNodes()) - e = e.childNodes[eo > 0 ? eo - 1 : eo]; + // 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); + }; - if (e && e.nodeType == 3) - return e.parentNode; + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } - return e; - } - }, + div = null; // release memory in IE + })(); +} - getBookmark : function(type, normalized) { - var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; +(function(){ + var div = document.createElement("div"); - function findIndex(name, element) { - var index = 0; + div.innerHTML = "
    "; - each(dom.select(name), function(node, i) { - if (node == element) - index = i; - }); + // 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; + } - return index; - }; + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; - if (type == 2) { - function getLocation() { - var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; + 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]); + } + }; - function getPoint(rng, start) { - var container = rng[start ? 'startContainer' : 'endContainer'], - offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; + div = null; // release memory in IE +})(); - if (container.nodeType == 3) { - if (normalized) { - for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) - offset += node.nodeValue.length; - } +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; - point.push(offset); - } else { - childNodes = container.childNodes; + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } - if (offset >= childNodes.length && childNodes.length) { - after = 1; - offset = Math.max(0, childNodes.length - 1); - } + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } - point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); - } + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } - for (; container && container != root; container = container.parentNode) - point.push(t.dom.nodeIndex(container, normalized)); + elem = elem[dir]; + } - return point; - }; + checkSet[i] = match; + } + } +} - bookmark.start = getPoint(rng, 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; - if (!t.isCollapsed()) - bookmark.end = getPoint(rng); + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } - return bookmark; - }; + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } - return getLocation(); + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; } - // Handle simple range - if (type) - return {rng : t.getRng()}; + checkSet[i] = match; + } + } +} - rng = t.getRng(); - id = dom.uniqueId(); - collapsed = tinyMCE.activeEditor.selection.isCollapsed(); - styles = 'overflow:hidden;line-height:0px'; +Sizzle.contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; - // Explorer method - if (rng.duplicate || rng.item) { - // Text selection - if (!rng.item) { - rng2 = rng.duplicate(); +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; +}; - // Insert start marker - rng.collapse(); - rng.pasteHTML('' + chr + ''); +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.pasteHTML('' + chr + ''); - } - } else { - // Control selection - element = rng.item(0); - name = element.nodeName; + // 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 {name : name, index : findIndex(name, element)}; - } - } else { - element = t.getNode(); - name = element.nodeName; - if (name == 'IMG') - return {name : name, index : findIndex(name, element)}; + selector = Expr.relative[selector] ? selector + "*" : selector; - // W3C method - rng2 = rng.cloneRange(); + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } - // Insert end marker - if (!collapsed) { - rng2.collapse(false); - rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr)); - } + return Sizzle.filter( later, tmpSet ); +}; - rng.collapse(true); - rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr)); - } +// EXPOSE - t.moveToBookmark({id : id, keep : 1}); +window.tinymce.dom.Sizzle = Sizzle; - return {id : id}; - }, +})(); - 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(); +(function(tinymce) { + // Shorten names + var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event; - if (bookmark) { - if (bookmark.start) { - rng = dom.createRng(); - root = dom.getRoot(); + tinymce.create('tinymce.dom.EventUtils', { + EventUtils : function() { + this.inits = []; + this.events = []; + }, - function setEndPoint(start) { - var point = bookmark[start ? 'start' : 'end'], i, node, offset; + add : function(o, n, f, s) { + var cb, t = this, el = t.events, r; - if (point) { - // Find container node - for (node = root, i = point.length - 1; i >= 1; i--) - node = node.childNodes[point[i]]; + if (n instanceof Array) { + r = []; - // Set offset within container node - if (start) - rng.setStart(node, point[0]); - else - rng.setEnd(node, point[0]); - } - }; + each(n, function(n) { + r.push(t.add(o, n, f, s)); + }); - setEndPoint(true); - setEndPoint(); + return r; + } - t.setRng(rng); - } else if (bookmark.id) { - function restoreEndPoint(suffix) { - var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep; + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; - if (marker) { - node = marker.parentNode; + each(o, function(o) { + o = DOM.get(o); + r.push(t.add(o, n, f, s)); + }); - if (suffix == 'start') { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker; - idx = 1; - } + return r; + } - startContainer = endContainer = node; - startOffset = endOffset = idx; - } else { - if (!keep) { - idx = dom.nodeIndex(marker); - } else { - node = marker; - idx = 1; - } + o = DOM.get(o); - endContainer = node; - endOffset = idx; - } + if (!o) + return; - if (!keep) { - prev = marker.previousSibling; - next = marker.nextSibling; + // Setup event callback + cb = function(e) { + // Is all events disabled + if (t.disabled) + return; - // Remove all marker text nodes - each(tinymce.grep(marker.childNodes), function(node) { - if (node.nodeType == 3) - node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); - }); + e = e || window.event; - // 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); + // Patch in target, preventDefault and stopPropagation in IE it's W3C valid + if (e && isIE) { + if (!e.target) + e.target = e.srcElement; - // If siblings are text nodes then merge them - if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) { - idx = prev.nodeValue.length; - prev.appendData(next.nodeValue); - dom.remove(next); + // Patch in preventDefault, stopPropagation methods for W3C compatibility + tinymce.extend(e, t._stoppers); + } - if (suffix == 'start') { - startContainer = endContainer = prev; - startOffset = endOffset = idx; - } else { - endContainer = prev; - endOffset = idx; - } - } - } - } - }; + if (!s) + return f(e); - // Restore start/end points - restoreEndPoint('start'); - restoreEndPoint('end'); + return f.call(s, e); + }; - rng = dom.createRng(); - rng.setStart(startContainer, startOffset); - rng.setEnd(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); + if (n == 'unload') { + tinymce.unloads.unshift({func : cb}); + return cb; } - }, - select : function(node, content) { - var t = this, dom = t.dom, rng = dom.createRng(), idx; + if (n == 'init') { + if (t.domLoaded) + cb(); + else + t.inits.push(cb); - idx = dom.nodeIndex(node); - rng.setStart(node.parentNode, idx); - rng.setEnd(node.parentNode, idx + 1); + return cb; + } - // Find first/last text node or BR element - if (content) { - function setPoint(node, start) { - var walker = new tinymce.dom.TreeWalker(node, node); + // Store away listener reference + el.push({ + obj : o, + name : n, + func : f, + cfunc : cb, + scope : s + }); - 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); + t._add(o, n, cb); - return; - } + return f; + }, - // BR element - if (node.nodeName == 'BR') { - if (start) - rng.setStartBefore(node); - else - rng.setEndBefore(node); + remove : function(o, n, f) { + var t = this, a = t.events, s = false, r; - return; - } - } while (node = (start ? walker.next() : walker.prev())); - }; + // Handle array + if (o && o.hasOwnProperty && o instanceof Array) { + r = []; + + each(o, function(o) { + o = DOM.get(o); + r.push(t.remove(o, n, f)); + }); - setPoint(node, 1); - setPoint(node); + return r; } - t.setRng(rng); + o = DOM.get(o); - return node; + 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; + } + }); + + return s; }, - isCollapsed : function() { - var t = this, r = t.getRng(), s = t.getSel(); + clear : function(o) { + var t = this, a = t.events, i, e; - if (!r || r.item) - return false; + if (o) { + o = DOM.get(o); - if (r.compareEndPoints) - return r.compareEndPoints('StartToEnd', r) === 0; + for (i = a.length - 1; i >= 0; i--) { + e = a[i]; - return !s || r.collapsed; + if (e.obj === o) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + a.splice(i, 1); + } + } + } }, - collapse : function(b) { - var t = this, r = t.getRng(), n; + cancel : function(e) { + if (!e) + return false; - // Control range on IE - if (r.item) { - n = r.item(0); - r = this.win.document.body.createTextRange(); - r.moveToElementText(n); - } + this.stop(e); - r.collapse(!!b); - t.setRng(r); + return this.prevent(e); }, - getSel : function() { - var t = this, w = this.win; + stop : function(e) { + if (e.stopPropagation) + e.stopPropagation(); + else + e.cancelBubble = true; - return w.getSelection ? w.getSelection() : w.document.selection; + return false; }, - getRng : function(w3c) { - var t = this, s, r; + prevent : function(e) { + if (e.preventDefault) + e.preventDefault(); + else + e.returnValue = false; - // Found tridentSel object then we need to use that one - if (w3c && t.tridentSel) - return t.tridentSel.getRangeAt(0); + return false; + }, - try { - if (s = t.getSel()) - r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange()); - } catch (ex) { - // IE throws unspecified error here if TinyMCE is placed in a frame/iframe - } + destroy : function() { + var t = this; - // 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 = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange(); + each(t.events, function(e, i) { + t._remove(e.obj, e.name, e.cfunc); + e.obj = e.cfunc = null; + }); - 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; - } - } - return r; + t.events = []; + t = null; }, - setRng : function(r) { - var s, t = this; - - if (!t.tridentSel) { - s = t.getSel(); - - 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; - } + _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; + }, - // Is IE specific range + _remove : function(o, n, f) { + if (o) { try { - r.select(); + if (o.detachEvent) + o.detachEvent('on' + n, f); + else if (o.removeEventListener) + o.removeEventListener(n, f, false); + else + o['on' + n] = null; } catch (ex) { - // Needed for some odd IE bug #1843306 + // Might fail with permission denined on IE so we just ignore that } } }, - setNode : function(n) { + _pageInit : function(win) { var t = this; - t.setContent(t.dom.getOuterHTML(n)); + // Keep it from running more than once + if (t.domLoaded) + return; - return n; + t.domLoaded = true; + + each(t.inits, function(c) { + c(); + }); + + t.inits = []; }, - getNode : function() { - var t = this, rng = t.getRng(), sel = t.getSel(), elm; + _wait : function(win) { + var t = this, doc = win.document; - if (rng.setStart) { - // Range maybe lost after the editor is made visible again - if (!rng) - return t.dom.getRoot(); + // No need since the document is already loaded + if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) { + t.domLoaded = 1; + return; + } - elm = rng.commonAncestorContainer; + // Use IE method + if (doc.attachEvent) { + doc.attachEvent("onreadystatechange", function() { + if (doc.readyState === "complete") { + doc.detachEvent("onreadystatechange", arguments.callee); + t._pageInit(win); + } + }); - // 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]; + if (doc.documentElement.doScroll && win == win.top) { + (function() { + if (t.domLoaded) + return; + + 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; } - } - // 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]; + t._pageInit(win); + })(); } + } else if (doc.addEventListener) { + t._add(win, 'DOMContentLoaded', function() { + t._pageInit(win); + }); + } - if (elm && elm.nodeType == 3) - return elm.parentNode; + t._add(win, 'load', function() { + t._pageInit(win); + }); + }, - return elm; + _stoppers : { + preventDefault : function() { + this.returnValue = false; + }, + + stopPropagation : function() { + this.cancelBubble = true; } + } + }); - return rng.item ? rng.item(0) : rng.parentElement(); - }, + Event = tinymce.dom.Event = new tinymce.dom.EventUtils(); - getSelectedBlocks : function(st, en) { - var t = this, dom = t.dom, sb, eb, n, bl = []; + // Dispatch DOM content loaded event for IE and Safari + Event._wait(window); - sb = dom.getParent(st || t.getStart(), dom.isBlock); - eb = dom.getParent(en || t.getEnd(), dom.isBlock); + tinymce.addUnload(function() { + Event.destroy(); + }); +})(tinymce); - if (sb) - bl.push(sb); +(function(tinymce) { + tinymce.dom.Element = function(id, settings) { + var t = this, dom, el; - if (sb && eb && sb != eb) { - n = sb; + t.settings = settings = settings || {}; + t.id = id; + t.dom = dom = settings.dom || tinymce.DOM; - while ((n = n.nextSibling) && n != eb) { - if (dom.isBlock(n)) - bl.push(n); - } - } + // Only IE leaks DOM references, this is a lot faster + if (!tinymce.isIE) + el = dom.get(t.id); - if (eb && sb != eb) - bl.push(eb); + 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; - return bl; - }, + for (i = 0; i < arguments.length; i++) + a.push(arguments[i]); - destroy : function(s) { - var t = this; + a = dom[k].apply(dom, a); + t.update(k); - t.win = null; + return a; + }; + }); - if (t.tridentSel) - t.tridentSel.destroy(); + tinymce.extend(t, { + on : function(n, f, s) { + return tinymce.dom.Event.add(t.id, n, f, s); + }, - // Manual destroy then remove unload handler - if (!s) - tinymce.removeUnload(t.destroy); - } - }); -})(tinymce); + getXY : function() { + return { + x : parseInt(t.getStyle('left')), + y : parseInt(t.getStyle('top')) + }; + }, -(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); - }; + getSize : function() { + var n = dom.get(t.id); - 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; + return { + w : parseInt(t.getStyle('width') || n.clientWidth), + h : parseInt(t.getStyle('height') || n.clientHeight) + }; + }, - this.reset(); - }, + moveTo : function(x, y) { + t.setStyles({left : x, top : y}); + }, - reset : function() { - var t = this, d = t.doc; + moveBy : function(x, y) { + var p = t.getXY(); - if (d.firstChild) - d.removeChild(d.firstChild); + t.moveTo(p.x + x, p.y + y); + }, - t.node = d.appendChild(d.createElement("html")); - }, + resizeTo : function(w, h) { + t.setStyles({width : w, height : h}); + }, - writeStartElement : function(n) { - var t = this; + resizeBy : function(w, h) { + var s = t.getSize(); - t.node = t.node.appendChild(t.doc.createElement(n)); - }, + t.resizeTo(s.w + w, s.h + h); + }, - writeAttribute : function(n, v) { - if (this.valid) - v = v.replace(/>/g, '%MCGT%'); + update : function(k) { + var b; - this.node.setAttribute(n, v); - }, + if (tinymce.isIE6 && settings.blocker) { + k = k || ''; - writeEndElement : function() { - this.node = this.node.parentNode; - }, + // Ignore getters + if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0) + return; - writeFullEndElement : function() { - var t = this, n = t.node; + // Remove blocker on remove + if (k == 'remove') { + dom.remove(t.blocker); + return; + } - n.appendChild(t.doc.createTextNode("")); - t.node = n.parentNode; - }, + 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); + + 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 + }); + } + } + }); + }; +})(tinymce); + +(function(tinymce) { + function trimNl(s) { + return s.replace(/[\n\r]+/g, ''); + }; + + // Shorten names + var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each; + + tinymce.create('tinymce.dom.Selection', { + Selection : function(dom, win, serializer) { + var t = this; + + t.dom = dom; + t.win = win; + t.serializer = serializer; + + // Add events + each([ + 'onBeforeSetContent', + + 'onBeforeGetContent', + + 'onSetContent', + + 'onGetContent' + ], function(e) { + t[e] = new tinymce.util.Dispatcher(t); + }); - writeText : function(v) { - if (this.valid) - v = v.replace(/>/g, '%MCGT%'); + // No W3C Range support + if (!t.win.getSelection) + t.tridentSel = new tinymce.dom.TridentSelection(t); - this.node.appendChild(this.doc.createTextNode(v)); - }, + if (tinymce.isIE && dom.boxModel) + this._fixIESelection(); - writeCDATA : function(v) { - this.node.appendChild(this.doc.createCDATASection(v)); + // Prevent leaks + tinymce.addUnload(t.destroy, t); }, - writeComment : function(v) { - // Fix for bug #2035694 - if (tinymce.isIE) - v = v.replace(/^\-|\-$/g, ' '); + getContent : function(s) { + var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n; - this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' '))); - }, + s = s || {}; + wb = wa = ''; + s.get = true; + s.format = s.format || 'html'; + t.onBeforeGetContent.dispatch(t, s); - getContent : function() { - var h; + if (s.format == 'text') + return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : '')); - h = this.doc.xml || new XMLSerializer().serializeToString(this.doc); - h = h.replace(/<\?[^?]+\?>||<\/html>||]+>/g, ''); - h = h.replace(/ ?\/>/g, ' />'); + if (r.cloneContents) { + n = r.cloneContents(); - if (this.valid) - h = h.replace(/\%MCGT%/g, '>'); + 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(); - return h; - } - }); -})(tinymce); + // Keep whitespace before and after + if (/^\s/.test(e.innerHTML)) + wb = ' '; -(function(tinymce) { - 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); + if (/\s+$/.test(e.innerHTML)) + wa = ' '; - this.reset(); - }, + s.getInner = true; - reset : function() { - this.indent = ''; - this.str = ""; - this.tags = []; - this.count = 0; - }, + s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa; + t.onGetContent.dispatch(t, s); - writeStartElement : function(n) { - this._writeAttributesEnd(); - this.writeRaw('<' + n); - this.tags.push(n); - this.inAttr = true; - this.count++; - this.elementCount = this.count; + return s.content; }, - writeAttribute : function(n, v) { - var t = this; + setContent : function(content, args) { + var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp; - t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"'); - }, + args = args || {format : 'html'}; + args.set = true; + content = args.content = content; - writeEndElement : function() { - var n; + // Dispatch before set content event + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); - if (this.tags.length > 0) { - n = this.tags.pop(); + content = args.content; - if (this._writeAttributesEnd(1)) - this.writeRaw(''); + if (rng.insertNode) { + // Make caret marker since insertNode places the caret in the beginning of text after insert + content += '_'; - if (this.settings.indentation > 0) - this.writeRaw('\n'); - } - }, + // 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(); - writeFullEndElement : function() { - if (this.tags.length > 0) { - this._writeAttributesEnd(); - this.writeRaw(''); + 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'); - if (this.settings.indentation > 0) - this.writeRaw('\n'); - } - }, + frag.appendChild(temp); + temp.outerHTML = content; - writeText : function(v) { - this._writeAttributesEnd(); - this.writeRaw(this.encode(v)); - this.count++; - }, + rng.insertNode(frag); + } + } + } - writeCDATA : function(v) { - this._writeAttributesEnd(); - this.writeRaw(''); - this.count++; - }, + // Move to caret marker + caretNode = self.dom.get('__caret'); - writeComment : function(v) { - this._writeAttributesEnd(); - this.writeRaw(''); - this.count++; - }, + // 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); - writeRaw : function(v) { - this.str += v; - }, + // Remove the caret position + self.dom.remove('__caret'); + self.setRng(rng); + } else { + if (rng.item) { + // Delete content and get caret text selection + doc.execCommand('Delete', false, null); + rng = self.getRng(); + } - encode : function(s) { - return s.replace(/[<>&"]/g, function(v) { - switch (v) { - case '<': - return '<'; + rng.pasteHTML(content); + } - case '>': - return '>'; + // Dispatch set content event + if (!args.no_events) + self.onSetContent.dispatch(self, args); + }, - case '&': - return '&'; + getStart : function() { + var rng = this.getRng(), startElement, parentElement, checkRng, node; - case '"': - return '"'; + 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; + } } - return v; - }); - }, - - getContent : function() { - return this.str; - }, + return startElement; + } else { + startElement = rng.startContainer; - _writeAttributesEnd : function(s) { - if (!this.inAttr) - return; + if (startElement.nodeType == 1 && startElement.hasChildNodes()) + startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)]; - this.inAttr = false; + if (startElement && startElement.nodeType == 3) + return startElement.parentNode; - if (s && this.elementCount == this.count) { - this.writeRaw(' />'); - return false; + return startElement; } + }, - this.writeRaw('>'); + getEnd : function() { + var t = this, r = t.getRng(), e, eo; - return true; - } - }); -})(tinymce); + if (r.duplicate || r.item) { + if (r.item) + return r.item(0); -(function(tinymce) { - // Shorten names - var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko; + r = r.duplicate(); + r.collapse(0); + e = r.parentElement(); - function wildcardToRE(s) { - return s.replace(/([?+*])/g, '.$1'); - }; + if (e && e.nodeName == 'BODY') + return e.lastChild || e; - tinymce.create('tinymce.dom.Serializer', { - Serializer : function(s) { - var t = this; + return e; + } else { + e = r.endContainer; + eo = r.endOffset; - t.key = 0; - t.onPreProcess = new Dispatcher(t); - t.onPostProcess = new Dispatcher(t); + if (e.nodeType == 1 && e.hasChildNodes()) + e = e.childNodes[eo > 0 ? eo - 1 : eo]; - 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(); + if (e && e.nodeType == 3) + return e.parentNode; + + return e; } + }, - // 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); + getBookmark : function(type, normalized) { + var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles; - t.dom = s.dom; - t.schema = s.schema; + function findIndex(name, element) { + var index = 0; - // Use raw entities if no entities are defined - if (s.entity_encoding == 'named' && !s.entities) - s.entity_encoding = 'raw'; + each(dom.select(name), function(node, i) { + if (node == element) + index = i; + }); - 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 ''; + return index; + }; - return a; - }); - }); - } + if (type == 2) { + function getLocation() { + var rng = t.getRng(true), root = dom.getRoot(), bookmark = {}; - // 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>'); - }); - } + function getPoint(rng, start) { + var container = rng[start ? 'startContainer' : 'endContainer'], + offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0; - if (s.fix_list_elements) { - t.onPreProcess.add(function(se, o) { - var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np; + if (container.nodeType == 3) { + if (normalized) { + for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling) + offset += node.nodeValue.length; + } - function prevNode(e, n) { - var a = n.split(','), i; + point.push(offset); + } else { + childNodes = container.childNodes; - while ((e = e.previousSibling) != null) { - for (i=0; i= childNodes.length && childNodes.length) { + after = 1; + offset = Math.max(0, childNodes.length - 1); } - } - return null; - }; + point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after); + } - for (x=0; x= 1767) { - each(t.dom.select('p table', o.node).reverse(), function(n) { - var parent = t.dom.getParent(n.parentNode, 'table,p'); + return bookmark; + }; - 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 - } - } - }); - } - }); + return getLocation(); } - }, - setEntities : function(s) { - var t = this, a, i, l = {}, v; + // Handle simple range + if (type) + return {rng : t.getRng()}; - // No need to setup more than once - if (t.entityLookup) - return; + rng = t.getRng(); + id = dom.uniqueId(); + collapsed = tinyMCE.activeEditor.selection.isCollapsed(); + styles = 'overflow:hidden;line-height:0px'; - // Build regex and lookup array - a = s.split(','); - for (i = 0; i < a.length; i += 2) { - v = a[i]; + // Explorer method + if (rng.duplicate || rng.item) { + // Text selection + if (!rng.item) { + rng2 = rng.duplicate(); - // Don't add default & " etc. - if (v == 34 || v == 38 || v == 60 || v == 62) - continue; + try { + // Insert start marker + rng.collapse(); + rng.pasteHTML('' + chr + ''); - l[String.fromCharCode(a[i])] = a[i + 1]; + // Insert end marker + if (!collapsed) { + rng2.collapse(false); - v = parseInt(a[i]).toString(16); - } + // 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); - t.entityLookup = l; - }, + 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; - setRules : function(s) { - var t = this; + return {name : name, index : findIndex(name, element)}; + } + } else { + element = t.getNode(); + name = element.nodeName; + if (name == 'IMG') + return {name : name, index : findIndex(name, element)}; - t._setup(); - t.rules = {}; - t.wildRules = []; - t.validElements = {}; + // W3C method + rng2 = rng.cloneRange(); - return t.addRules(s); - }, + // Insert end marker + if (!collapsed) { + rng2.collapse(false); + rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr)); + } - addRules : function(s) { - var t = this, dr; + rng.collapse(true); + rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr)); + } - if (!s) - return; + t.moveToBookmark({id : id, keep : 1}); - t._setup(); + return {id : id}; + }, - each(s.split(','), function(s) { - var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = []; + moveToBookmark : function(bookmark) { + var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset; - // Extend with default rules - if (dr) - at = tinymce.extend([], dr.attribs); + // Clear selection cache + if (t.tridentSel) + t.tridentSel.destroy(); - // Parse attributes - if (p.length > 1) { - each(p[1].split('|'), function(s) { - var ar = {}, i; + if (bookmark) { + if (bookmark.start) { + rng = dom.createRng(); + root = dom.getRoot(); - at = at || []; + function setEndPoint(start) { + var point = bookmark[start ? 'start' : 'end'], i, node, offset, children; - // Parse attribute rule - s = s.replace(/::/g, '~'); - s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s); - s[2] = s[2].replace(/~/g, ':'); + if (point) { + offset = point[0]; - // Add required attributes - if (s[1] == '!') { - ra = ra || []; - ra.push(s[2]); - } + // Find container node + for (node = root, i = point.length - 1; i >= 1; i--) { + children = node.childNodes; - // Remove inherited attributes - if (s[1] == '-') { - for (i = 0; i children.length - 1) return; - } - } - } - - switch (s[3]) { - // Add default attrib values - case '=': - ar.defaultVal = s[4] || ''; - break; - // Add forced attrib values - case ':': - ar.forcedVal = s[4]; - break; - - // Add validation values - case '<': - ar.validVals = s[4].split('?'); - break; - } - - if (/[*.?]/.test(s[2])) { - wat = wat || []; - ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$'); - wat.push(ar); - } else { - ar.name = s[2]; - at.push(ar); - } + node = children[point[i]]; + } - va.push(s[2]); - }); - } + // Move text offset to best suitable location + if (node.nodeType === 3) + offset = Math.min(point[0], node.nodeValue.length); - // Handle element names - each(tn, function(s, i) { - var pr = s.charAt(0), x = 1, ru = {}; + // Move element offset to best suitable location + if (node.nodeType === 1) + offset = Math.min(point[0], node.childNodes.length); - // Extend with default rule data - if (dr) { - if (dr.noEmpty) - ru.noEmpty = dr.noEmpty; + // Set offset within container node + if (start) + rng.setStart(node, offset); + else + rng.setEnd(node, offset); + } - if (dr.fullEnd) - ru.fullEnd = dr.fullEnd; + return true; + }; - if (dr.padd) - ru.padd = dr.padd; + 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; - // Handle prefixes - switch (pr) { - case '-': - ru.noEmpty = true; - break; + if (marker) { + node = marker.parentNode; - case '+': - ru.fullEnd = true; - break; + if (suffix == 'start') { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } - case '#': - ru.padd = true; - break; + startContainer = endContainer = node; + startOffset = endOffset = idx; + } else { + if (!keep) { + idx = dom.nodeIndex(marker); + } else { + node = marker.firstChild; + idx = 1; + } - default: - x = 0; - } + endContainer = node; + endOffset = idx; + } - tn[i] = s = s.substring(x); - t.validElements[s] = 1; + if (!keep) { + prev = marker.previousSibling; + next = marker.nextSibling; - // 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]; + // Remove all marker text nodes + each(tinymce.grep(marker.childNodes), function(node) { + if (node.nodeType == 3) + node.nodeValue = node.nodeValue.replace(/\uFEFF/g, ''); + }); + + // 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); - // Store away default rule - if (tn[0] == '@') - dr = ru; + // 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); - t.rules[s] = ru; - } + if (suffix == 'start') { + startContainer = endContainer = prev; + startOffset = endOffset = idx; + } else { + endContainer = prev; + endOffset = idx; + } + } + } + } + }; - ru.attribs = at; + function addBogus(node) { + // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly + if (dom.isBlock(node) && !node.innerHTML) + node.innerHTML = !isIE ? '
    ' : ' '; - if (ra) - ru.requiredAttribs = ra; + return node; + }; - if (wat) { - // Build valid attributes regexp - s = ''; - each(va, function(v) { - if (s) - s += '|'; + // Restore start/end points + restoreEndPoint('start'); + restoreEndPoint('end'); - s += '(' + wildcardToRE(v) + ')'; - }); - ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$'); - ru.wildAttribs = wat; + 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); + } + }, - // Build valid elements regexp - s = ''; - each(t.validElements, function(v, k) { - if (s) - s += '|'; + select : function(node, content) { + var t = this, dom = t.dom, rng = dom.createRng(), idx; - if (k != '@') - s += k; - }); - t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$'); + 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); - //console.debug(t.validElementsRE.toString()); - //console.dir(t.rules); - //console.dir(t.wildRules); - }, + return; + } - findRule : function(n) { - var t = this, rl = t.rules, i, r; + // BR element + if (node.nodeName == 'BR') { + if (start) + rng.setStartBefore(node); + else + rng.setEndBefore(node); - t._setup(); + return; + } + } while (node = (start ? walker.next() : walker.prev())); + }; - // Exact match - r = rl[n]; - if (r) - return r; + setPoint(node, 1); + setPoint(node); + } - // Try wildcards - rl = t.wildRules; - for (i = 0; i < rl.length; i++) { - if (rl[i].nameRE.test(n)) - return rl[i]; + t.setRng(rng); } - return null; + return node; }, - findAttribRule : function(ru, n) { - var i, wa = ru.wildAttribs; - - for (i = 0; i < wa.length; i++) { - if (wa[i].nameRE.test(n)) - return wa[i]; - } + isCollapsed : function() { + var t = this, r = t.getRng(), s = t.getSel(); - return null; - }, + if (!r || r.item) + return false; - serialize : function(n, o) { - var h, t = this, doc, oldDoc, impl, selected; + if (r.compareEndPoints) + return r.compareEndPoints('StartToEnd', r) === 0; - t._setup(); - o = o || {}; - o.format = o.format || 'html'; - t.processObj = o; + return !s || r.collapsed; + }, - // 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'); + collapse : function(to_start) { + var self = this, rng = self.getRng(), node; - selected.push(v ? v : null); - }); + // Control range on IE + if (rng.item) { + node = rng.item(0); + rng = self.win.document.body.createTextRange(); + rng.moveToElementText(node); } - n = n.cloneNode(true); + rng.collapse(!!to_start); + self.setRng(rng); + }, + + getSel : function() { + var t = this, w = this.win; - // 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]); - }); - } + return w.getSelection ? w.getSelection() : w.document.selection; + }, - // 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(""); + getRng : function(w3c) { + var t = this, s, r, elm, doc = t.win.document; - // 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)); - }); + // Found tridentSel object then we need to use that one + if (w3c && t.tridentSel) + return t.tridentSel.getRangeAt(0); - // Grab first child or body element for serialization - if (n.nodeName != 'BODY') - n = doc.body.firstChild; - else - n = doc.body; + 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 + } - // set the new document in DOMUtils so createElement etc works - oldDoc = t.dom.doc; - t.dom.doc = doc; + // 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); } - t.key = '' + (parseInt(t.key) + 1); + // 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(); - // Pre process - if (!o.no_events) { - o.node = n; - t.onPreProcess.dispatch(t, o); + 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; + } } - // Serialize HTML DOM into a string - t.writer.reset(); - t._info = o; - t._serializeNode(n, o.getInner); + return r; + }, - // Post process - o.content = t.writer.getContent(); + setRng : function(r) { + var s, t = this; + + if (!t.tridentSel) { + s = t.getSel(); - // Restore the old document if it was changed - if (oldDoc) - t.dom.doc = oldDoc; + if (s) { + t.explicitRange = r; - if (!o.no_events) - t.onPostProcess.dispatch(t, o); + try { + s.removeAllRanges(); + } catch (ex) { + // IE9 might throw errors here don't know why + } - t._postProcess(o); - o.node = null; + s.addRange(r); + t.selectedRange = s.getRangeAt(0); + } + } else { + // Is W3C Range + if (r.cloneRange) { + t.tridentSel.addRange(r); + return; + } - return tinymce.trim(o.content); + // Is IE specific range + try { + r.select(); + } catch (ex) { + // Needed for some odd IE bug #1843306 + } + } }, - // Internal functions + setNode : function(n) { + var t = this; - _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} - ] - }); + t.setContent(t.dom.getOuterHTML(n)); - h = p.content; + return n; + }, - // Entity encode - if (s.entity_encoding !== 'raw') - h = t._encode(h); + getNode : function() { + var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer; - // 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

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

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

    ' : ' 

    '); + if (rng.setStart) { + elm = rng.commonAncestorContainer; - if (s.remove_linebreaks) { - h = h.replace(/\r?\n|\r/g, ' '); - h = h.replace(/(<[^>]+>)\s+/g, '$1 '); - h = h.replace(/\s+(<\/[^>]+>)/g, ' $1'); - h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start - h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>'); // Trim block start - h = h.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, ''); // Trim block end + // 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]; + } } - // Simple indentation - if (s.apply_source_formatting && s.indent_mode == 'simple') { - // Add line breaks before and after block elements - h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n'); - h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>'); - h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '\n'); - h = h.replace(/\n\n/g, '\n'); + // 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; + } + + if (start && start === end) + return start; } } - h = t._unprotect(h, p); - - // Restore CDATA sections - h = h.replace(//g, ''); - - // Restore the \u00a0 character if raw mode is enabled - if (s.entity_encoding == 'raw') - h = h.replace(/

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

    '); + if (elm && elm.nodeType == 3) + return elm.parentNode; - // Restore noscript elements - h = h.replace(/]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) { - return '' + t.dom.decode(text.replace(//g, '')) + ''; - }); + return elm; } - o.content = h; + return rng.item ? rng.item(0) : rng.parentElement(); }, - _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; + getSelectedBlocks : function(st, en) { + var t = this, dom = t.dom, sb, eb, n, bl = []; - 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; + sb = dom.getParent(st || t.getStart(), dom.isBlock); + eb = dom.getParent(en || t.getEnd(), dom.isBlock); - iv = keep = false; - hc = n.hasChildNodes(); - nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase(); + if (sb) + bl.push(sb); - // Get internal type - type = n.getAttribute('_mce_type'); - if (type) { - if (!t._info.cleanup) { - iv = true; - return; - } else - keep = 1; - } + if (sb && eb && sb != eb) { + n = sb; - // Add correct prefix on IE - if (isIE) { - if (n.scopeName !== 'HTML' && n.scopeName !== 'html') - nn = n.scopeName + ':' + nn; - } + while ((n = n.nextSibling) && n != eb) { + if (dom.isBlock(n)) + bl.push(n); + } + } - // Remove mce prefix on IE needed for the abbr element - if (nn.indexOf('mce:') === 0) - nn = nn.substring(4); + if (eb && sb != eb) + bl.push(eb); - // Check if valid - if (!keep) { - if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) { - iv = true; - break; - } - } + return bl; + }, - 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; + destroy : function(s) { + var t = this; - n._mce_serialized = t.key; - } + t.win = null; - // 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; - } + if (t.tridentSel) + t.tridentSel.destroy(); - // Check if valid child - if (s.validate_children) { - if (t.elementName && !t.schema.isValid(t.elementName, nn)) { - iv = true; - break; - } + // Manual destroy then remove unload handler + if (!s) + tinymce.removeUnload(t.destroy); + }, - t.elementName = nn; - } + // 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; + + // Make HTML element unselectable since we are going to handle selection by hand + doc.documentElement.unselectable = true; + + // Return range from point or null if it failed + function rngFromPoint(x, y) { + var rng = body.createTextRange(); + + 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); + while (i--) { + node = nodes[i]; + value = node.firstChild ? node.firstChild.value : ''; - o.items.push(b); - return a + '' + c; - })); - }); + if (name === "script") { + // Remove mce- prefix from script elements + node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, '')); - return o; - }, + if (value.length > 0) + node.firstChild.value = '// '; + } else { + if (value.length > 0) + node.firstChild.value = ''; + } + } + }); - _unprotect : function(h, o) { - h = h.replace(/\')); - 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; tinymce.create('tinymce.AddOnManager', { - items : [], - urls : {}, - lookup : {}, + AddOnManager : function() { + var self = this; - onAdd : new Dispatcher(this), + self.items = []; + self.urls = {}; + self.lookup = {}; + self.onAdd = new Dispatcher(self); + }, get : function(n) { return this.lookup[n]; @@ -8680,7 +10103,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { 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'); }, @@ -8699,10 +10122,12 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return; if (u.indexOf('/') != 0 && u.indexOf('://') == -1) - u = tinymce.baseURL + '/' + u; + u = tinymce.baseURL + '/' + u; t.urls[n] = u.substring(0, u.lastIndexOf('/')); - tinymce.ScriptLoader.add(u, cb, s); + + if (!t.lookup[n]) + tinymce.ScriptLoader.add(u, cb, s); } }); @@ -9159,7 +10584,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'; - t.iframeHTML += ''; + // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode. + if (s.ie7_compat) + t.iframeHTML += ''; + else + t.iframeHTML += ''; + + t.iframeHTML += ''; - if (tinymce.relaxedDomain) - t.iframeHTML += ''; + // Firefox 2 doesn't load stylesheets correctly this way + if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) { + for (i = 0; i < t.contentCSS.length; i++) + t.iframeHTML += ''; + + t.contentCSS = []; + } bi = s.body_id || 'tinymce'; if (bi.indexOf('=') != -1) { @@ -9437,19 +10888,18 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { t.iframeHTML += ''; // Domain relaxing enabled, then set document domain - if (tinymce.relaxedDomain) { + if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) { // We need to write the contents here in IE since multiple writes messes up refresh button and back button - if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5)) - u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; - else if (tinymce.isOpera) - u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()'; + u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()'; } // Create iframe - n = DOM.add(o.iframeContainer, 'iframe', { + // TODO: ACC add the appropriate description on this. + n = DOM.add(o.iframeContainer, 'iframe', { id : t.id + "_ifr", src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7 - frameBorder : '0', + frameBorder : '0', + title : s.aria_label, style : { width : '100%', height : h @@ -9459,8 +10909,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { t.contentAreaContainer = o.iframeContainer; DOM.get(o.editorContainer).style.display = t.orgDisplay; DOM.get(t.id).style.display = 'none'; + DOM.setAttrib(t.id, 'aria-hidden', true); - if (!isIE || !tinymce.relaxedDomain) + if (!tinymce.relaxedDomain || !u) t.setupIframe(); e = n = o = null; // Cleanup @@ -9474,6 +10925,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { d.open(); d.write(t.iframeHTML); d.close(); + + if (tinymce.relaxedDomain) + d.domain = tinymce.relaxedDomain; } // Design mode needs to be added here Ctrl+A will fail otherwise @@ -9499,6 +10953,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { DOM.show(b); } + t.schema = new tinymce.html.Schema(s); + t.dom = new tinymce.dom.DOMUtils(t.getDoc(), { keep_values : true, url_converter : t.convertURL, @@ -9507,16 +10963,77 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { class_filter : s.class_filter, update_styles : 1, fix_ie_paragraphs : 1, - valid_styles : s.valid_styles + schema : t.schema }); - t.schema = new tinymce.dom.Schema(); + t.parser = new tinymce.html.DomParser(s, t.schema); - t.serializer = new tinymce.dom.Serializer(extend(s, { - valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements, - dom : t.dom, - schema : t.schema - })); + // Force anchor names closed + t.parser.addAttributeFilter('name', function(nodes, name) { + var i = nodes.length, sibling, prevSibling, parent, node; + + while (i--) { + node = nodes[i]; + if (node.name === 'a' && node.firstChild) { + parent = node.parent; + + // Move children after current node + sibling = node.lastChild; + do { + prevSibling = sibling.prev; + parent.insert(sibling, node); + sibling = prevSibling; + } while (sibling); + } + } + }); + + // Convert src and href into data-mce-src, data-mce-href and data-mce-style + t.parser.addAttributeFilter('src,href,style', function(nodes, name) { + var i = nodes.length, node, dom = t.dom, value; + + while (i--) { + node = nodes[i]; + value = node.attr(name); + + if (name === "style") + node.attr('data-mce-style', dom.serializeStyle(dom.parseStyle(value), node.name)); + else + node.attr('data-mce-' + name, t.convertURL(value, name, node.name)); + } + }); + + // Keep scripts from executing + t.parser.addNodeFilter('script', function(nodes, name) { + var i = nodes.length; + + while (i--) + nodes[i].attr('type', 'mce-text/javascript'); + }); + + t.parser.addNodeFilter('#cdata', function(nodes, name) { + var i = nodes.length, node; + + while (i--) { + node = nodes[i]; + node.type = 8; + node.name = '#comment'; + node.value = '[CDATA[' + node.value + ']]'; + } + }); + + t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) { + var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements(); + + while (i--) { + node = nodes[i]; + + if (node.isEmpty(nonEmptyElements)) + node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true; + } + }); + + t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema); t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer); @@ -9526,18 +11043,18 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { t.formatter.register({ alignleft : [ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}}, - {selector : 'img,table', styles : {'float' : 'left'}} + {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}} ], aligncenter : [ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}}, - {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, - {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}} + {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}}, + {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}} ], alignright : [ {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}}, - {selector : 'img,table', styles : {'float' : 'right'}} + {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}} ], alignfull : [ @@ -9545,33 +11062,35 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { ], bold : [ - {inline : 'strong'}, + {inline : 'strong', remove : 'all'}, {inline : 'span', styles : {fontWeight : 'bold'}}, - {inline : 'b'} + {inline : 'b', remove : 'all'} ], italic : [ - {inline : 'em'}, + {inline : 'em', remove : 'all'}, {inline : 'span', styles : {fontStyle : 'italic'}}, - {inline : 'i'} + {inline : 'i', remove : 'all'} ], underline : [ {inline : 'span', styles : {textDecoration : 'underline'}, exact : true}, - {inline : 'u'} + {inline : 'u', remove : 'all'} ], strikethrough : [ {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true}, - {inline : 'u'} + {inline : 'strike', remove : 'all'} ], - forecolor : {inline : 'span', styles : {color : '%value'}}, - hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}}, + forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false}, + hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false}, fontname : {inline : 'span', styles : {fontFamily : '%value'}}, fontsize : {inline : 'span', styles : {fontSize : '%value'}}, fontsize_class : {inline : 'span', attributes : {'class' : '%value'}}, blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'}, + subscript : {inline : 'sub'}, + superscript : {inline : 'sup'}, removeformat : [ {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true}, @@ -9592,7 +11111,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Pass through t.undoManager.onAdd.add(function(um, l) { - if (!l.initial) + if (um.hasUndo()) return t.onChange.dispatch(t, l, um); }); @@ -9636,29 +11155,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (s.nowrap) t.getBody().style.whiteSpace = "nowrap"; - if (s.custom_elements) { - function handleCustom(ed, o) { - each(explode(s.custom_elements), function(v) { - var n; - - if (v.indexOf('~') === 0) { - v = v.substring(1); - n = 'span'; - } else - n = 'div'; - - o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>'); - o.content = o.content.replace(new RegExp('', 'g'), ''); - }); - }; - - t.onBeforeSetContent.add(handleCustom); - t.onPostProcess.add(function(ed, o) { - if (o.set) - handleCustom(ed, o); - }); - } - if (s.handle_node_change_callback) { t.onNodeChange.add(function(ed, cm, n) { t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed()); @@ -9680,16 +11176,22 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }); } - if (s.convert_newlines_to_brs) { + if (s.protect) { t.onBeforeSetContent.add(function(ed, o) { - if (o.initial) - o.content = o.content.replace(/\r?\n/g, '
    '); + if (s.protect) { + each(s.protect, function(pattern) { + o.content = o.content.replace(pattern, function(str) { + return ''; + }); + }); + } }); } - if (s.fix_nesting && isIE) { + if (s.convert_newlines_to_brs) { t.onBeforeSetContent.add(function(ed, o) { - o.content = t._fixNesting(o.content); + if (o.initial) + o.content = o.content.replace(/\r?\n/g, '
    '); }); } @@ -9786,7 +11288,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { var pn = n.parentNode; if (ed.dom.isBlock(pn) && pn.lastChild === n) - ed.dom.add(pn, 'br', {'_mce_bogus' : 1}); + ed.dom.add(pn, 'br', {'data-mce-bogus' : 1}); }); }; @@ -9815,8 +11317,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (t.removed) return; - t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')}); + t.load({initial : true, format : 'html'}); t.startContent = t.getContent({format : 'raw'}); + t.undoManager.add(); t.initialized = true; t.onInit.dispatch(t); @@ -9826,11 +11329,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { t.nodeChanged({initial : 1}); // Load specified content CSS last - if (s.content_css) { - tinymce.each(explode(s.content_css), function(u) { - t.dom.loadCSS(t.documentBaseURI.toAbsolute(u)); - }); - } + each(t.contentCSS, function(u) { + t.dom.loadCSS(u); + }); // Handle auto focus if (s.auto_focus) { @@ -9946,7 +11447,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, nodeChanged : function(o) { - var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody(); + var t = this, s = t.selection, n = s.getStart() || t.getBody(); // Fix for bug #1896577 it seems that this can not be fired while the editor is loading if (t.initialized) { @@ -9979,16 +11480,16 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { t.buttons[n] = s; }, - addCommand : function(n, f, s) { - this.execCommands[n] = {func : f, scope : s || this}; + addCommand : function(name, callback, scope) { + this.execCommands[name] = {func : callback, scope : scope || this}; }, - addQueryStateHandler : function(n, f, s) { - this.queryStateCommands[n] = {func : f, scope : s || this}; + addQueryStateHandler : function(name, callback, scope) { + this.queryStateCommands[name] = {func : callback, scope : scope || this}; }, - addQueryValueHandler : function(n, f, s) { - this.queryValueCommands[n] = {func : f, scope : s || this}; + addQueryValueHandler : function(name, callback, scope) { + this.queryValueCommands[name] = {func : callback, scope : scope || this}; }, addShortcut : function(pa, desc, cmd_func, sc) { @@ -10091,12 +11592,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return true; } - // Execute global commands - if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) { - t.onExecCommand.dispatch(t, cmd, ui, val, a); - return true; - } - // Editor commands if (t.editorCommands.execCommand(cmd, ui, val)) { t.onExecCommand.dispatch(t, cmd, ui, val, a); @@ -10228,7 +11723,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Add undo level will trigger onchange event if (!o.no_events) { - t.undoManager.typing = 0; + t.undoManager.typing = false; t.undoManager.add(); } @@ -10260,66 +11755,77 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return h; }, - setContent : function(h, o) { - var t = this; + setContent : function(content, args) { + var self = this, rootNode, body = self.getBody(); - o = o || {}; - o.format = o.format || 'html'; - o.set = true; - o.content = h; + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.set = true; + args.content = content; - if (!o.no_events) - t.onBeforeSetContent.dispatch(t, o); + // Do preprocessing + if (!args.no_events) + self.onBeforeSetContent.dispatch(self, args); + + content = args.content; // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content // It will also be impossible to place the caret in the editor unless there is a BR element present - if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) { - o.content = t.dom.setHTML(t.getBody(), '
    '); - o.format = 'raw'; + if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) { + body.innerHTML = '
    '; + return; } - o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content)); - - if (o.format != 'raw' && t.settings.cleanup) { - o.getInner = true; - o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o)); + // Parse and serialize the html + if (args.format !== 'raw') { + content = new tinymce.html.Serializer({}, self.schema).serialize( + self.parser.parse(content) + ); } - if (!o.no_events) - t.onSetContent.dispatch(t, o); + // Set the new cleaned contents to the editor + args.content = tinymce.trim(content); + self.dom.setHTML(body, args.content); + + // Do post processing + if (!args.no_events) + self.onSetContent.dispatch(self, args); - return o.content; + return args.content; }, - getContent : function(o) { - var t = this, h; + getContent : function(args) { + var self = this, content; - o = o || {}; - o.format = o.format || 'html'; - o.get = true; + // Setup args object + args = args || {}; + args.format = args.format || 'html'; + args.get = true; - if (!o.no_events) - t.onBeforeGetContent.dispatch(t, o); + // Do preprocessing + if (!args.no_events) + self.onBeforeGetContent.dispatch(self, args); - if (o.format != 'raw' && t.settings.cleanup) { - o.getInner = true; - h = t.serializer.serialize(t.getBody(), o); - } else - h = t.getBody().innerHTML; + // Get raw contents or by default the cleaned contents + if (args.format == 'raw') + content = self.getBody().innerHTML; + else + content = self.serializer.serialize(self.getBody(), args); - h = h.replace(/^\s*|\s*$/g, ''); - o.content = h; + args.content = tinymce.trim(content); - if (!o.no_events) - t.onGetContent.dispatch(t, o); + // Do post processing + if (!args.no_events) + self.onGetContent.dispatch(self, args); - return o.content; + return args.content; }, isDirty : function() { - var t = this; + var self = this; - return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty; + return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty; }, getContainer : function() { @@ -10497,7 +12003,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { _addEvents : function() { // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset - var t = this, i, s = t.settings, lo = { + var t = this, i, s = t.settings, dom = t.dom, lo = { mouseup : 'onMouseUp', mousedown : 'onMouseDown', click : 'onClick', @@ -10529,35 +12035,26 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { each(lo, function(v, k) { switch (k) { case 'contextmenu': - if (tinymce.isOpera) { - // Fake contextmenu on Opera - t.dom.bind(t.getBody(), 'mousedown', function(e) { - if (e.ctrlKey) { - e.fakeType = 'contextmenu'; - eventHandler(e); - } - }); - } else - t.dom.bind(t.getBody(), k, eventHandler); + dom.bind(t.getDoc(), k, eventHandler); break; case 'paste': - t.dom.bind(t.getBody(), k, function(e) { + dom.bind(t.getBody(), k, function(e) { eventHandler(e); }); break; case 'submit': case 'reset': - t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler); + dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler); break; default: - t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler); + dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler); } }); - t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) { + dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) { t.focus(true); }); @@ -10565,22 +12062,12 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Fixes bug where a specified document_base_uri could result in broken images // This will also fix drag drop of images in Gecko if (tinymce.isGecko) { - // Convert all images to absolute URLs -/* t.onSetContent.add(function(ed, o) { - each(ed.dom.select('img'), function(e) { - var v; - - if (v = e.getAttribute('_mce_src')) - e.src = t.documentBaseURI.toAbsolute(v); - }) - });*/ - - t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) { + dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) { var v; e = e.target; - if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src'))) + if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src'))) e.src = t.documentBaseURI.toAbsolute(v); }); } @@ -10629,14 +12116,16 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { e = e.target; // Needs tobe the setBaseAndExtend or it will fail to select floated images - if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor'))) + if (e.nodeName == 'IMG' || (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))) { t.selection.getSel().setBaseAndExtent(e, 0, e, 1); + t.nodeChanged(); + } }); } // Add node change handlers t.onMouseUp.add(t.nodeChanged); - t.onClick.add(t.nodeChanged); + //t.onClick.add(t.nodeChanged); t.onKeyUp.add(function(ed, e) { var c = e.keyCode; @@ -10723,7 +12212,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (tinymce.isIE) { // Fix so resize will only update the width and height attributes not the styles of an image // It will also block mceItemNoResize items - t.dom.bind(t.getDoc(), 'controlselect', function(e) { + dom.bind(t.getDoc(), 'controlselect', function(e) { var re = t.resizeInfo, cb; e = e.target; @@ -10733,28 +12222,28 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return; if (re) - t.dom.unbind(re.node, re.ev, re.cb); + dom.unbind(re.node, re.ev, re.cb); - if (!t.dom.hasClass(e, 'mceItemNoResize')) { + if (!dom.hasClass(e, 'mceItemNoResize')) { ev = 'resizeend'; - cb = t.dom.bind(e, ev, function(e) { + cb = dom.bind(e, ev, function(e) { var v; e = e.target; - if (v = t.dom.getStyle(e, 'width')) { - t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, '')); - t.dom.setStyle(e, 'width', ''); + if (v = dom.getStyle(e, 'width')) { + dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'width', ''); } - if (v = t.dom.getStyle(e, 'height')) { - t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, '')); - t.dom.setStyle(e, 'height', ''); + if (v = dom.getStyle(e, 'height')) { + dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, '')); + dom.setStyle(e, 'height', ''); } }); } else { ev = 'resizestart'; - cb = t.dom.bind(e, 'resizestart', Event.cancel, Event); + cb = dom.bind(e, 'resizestart', Event.cancel, Event); } re = t.resizeInfo = { @@ -10765,25 +12254,19 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }); t.onKeyDown.add(function(ed, e) { + var sel; + switch (e.keyCode) { case 8: + sel = t.getDoc().selection; + // Fix IE control + backspace browser bug - if (t.selection.getRng().item) { - ed.dom.remove(t.selection.getRng().item(0)); + if (sel.createRange && sel.createRange().item) { + ed.dom.remove(sel.createRange().item(0)); return Event.cancel(e); } } }); - - /*if (t.dom.boxModel) { - t.getBody().style.height = '100%'; - - Event.add(t.getWin(), 'resize', function(e) { - var docElm = t.getDoc().documentElement; - - docElm.style.height = (docElm.offsetHeight - 10) + 'px'; - }); - }*/ } if (tinymce.isOpera) { @@ -10795,32 +12278,105 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Add custom undo/redo handlers if (s.custom_undo_redo) { function addUndo() { - t.undoManager.typing = 0; + t.undoManager.typing = false; t.undoManager.add(); }; - t.dom.bind(t.getDoc(), 'focusout', function(e) { + dom.bind(t.getDoc(), 'focusout', function(e) { if (!t.removed && t.undoManager.typing) addUndo(); }); + // Add undo level when contents is drag/dropped within the editor + t.dom.bind(t.dom.getRoot(), 'dragend', function(e) { + addUndo(); + }); + t.onKeyUp.add(function(ed, e) { + var rng, parent, bookmark; + + // Fix for bug #3168, to remove odd ".." nodes from the DOM we need to get/set the HTML of the parent node. + if (isIE && e.keyCode == 8) { + rng = t.selection.getRng(); + if (rng.parentElement) { + parent = rng.parentElement(); + bookmark = t.selection.getBookmark(); + parent.innerHTML = parent.innerHTML; + t.selection.moveToBookmark(bookmark); + } + } + if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey) addUndo(); }); t.onKeyDown.add(function(ed, e) { - // Is caracter positon keys - if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) { + var rng, parent, bookmark, keyCode = e.keyCode; + + // IE has a really odd bug where the DOM might include an node that doesn't have + // a proper structure. If you try to access nodeValue it would throw an illegal value exception. + // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element + // after you delete contents from it. See: #3008923 + if (isIE && keyCode == 46) { + rng = t.selection.getRng(); + + if (rng.parentElement) { + parent = rng.parentElement(); + + if (!t.undoManager.typing) { + t.undoManager.beforeChange(); + t.undoManager.typing = true; + t.undoManager.add(); + } + + // Select next word when ctrl key is used in combo with delete + if (e.ctrlKey) { + rng.moveEnd('word', 1); + rng.select(); + } + + // Delete contents + t.selection.getSel().clear(); + + // Check if we are within the same parent + if (rng.parentElement() == parent) { + bookmark = t.selection.getBookmark(); + + try { + // Update the HTML and hopefully it will remove the artifacts + parent.innerHTML = parent.innerHTML; + } catch (ex) { + // And since it's IE it can sometimes produce an unknown runtime error + } + + // Restore the caret position + t.selection.moveToBookmark(bookmark); + } + + // Block the default delete behavior since it might be broken + e.preventDefault(); + return; + } + } + + // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter + if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) { + // Add position before enter key is pressed, used by IE since it still uses the default browser behavior + // Todo: Remove this once we normalize enter behavior on IE + if (tinymce.isIE && keyCode == 13) + t.undoManager.beforeChange(); + if (t.undoManager.typing) addUndo(); return; } - if (!t.undoManager.typing) { + // If key isn't shift,ctrl,alt,capslock,metakey + if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) { + t.undoManager.beforeChange(); t.undoManager.add(); - t.undoManager.typing = 1; + t.undoManager.typing = true; } }); @@ -10829,68 +12385,64 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { addUndo(); }); } - }, - - _isHidden : function() { - var s; + + // Bug fix for FireFox keeping styles from end of selection instead of start. + if (tinymce.isGecko) { + function getAttributeApplyFunction() { + var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false)); + + return function() { + var target = t.selection.getStart(); + t.dom.removeAllAttribs(target); + each(template, function(attr) { + target.setAttributeNode(attr.cloneNode(true)); + }); + }; + } - if (!isGecko) - return 0; + function isSelectionAcrossElements() { + var s = t.selection; - // Weird, wheres that cursor selection? - s = this.selection.getSel(); - return (!s || !s.rangeCount || s.rangeCount == 0); - }, + return !s.isCollapsed() && s.getStart() != s.getEnd(); + } - // Fix for bug #1867292 - _fixNesting : function(s) { - var d = [], i; + t.onKeyPress.add(function(ed, e) { + var applyAttributes; - s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) { - var e; + if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.getDoc().execCommand('delete', false, null); + applyAttributes(); - // Handle end element - if (b === '/') { - if (!d.length) - return ''; + return Event.cancel(e); + } + }); - if (c !== d[d.length - 1].tag) { - for (i=d.length - 1; i>=0; i--) { - if (d[i].tag === c) { - d[i].close = 1; - break; - } - } + t.dom.bind(t.getDoc(), 'cut', function(e) { + var applyAttributes; - return ''; - } else { - d.pop(); + if (isSelectionAcrossElements()) { + applyAttributes = getAttributeApplyFunction(); + t.onKeyUp.addToTop(Event.cancel, Event); - if (d.length && d[d.length - 1].close) { - a = a + ''; - d.pop(); - } + setTimeout(function() { + applyAttributes(); + t.onKeyUp.remove(Event.cancel, Event); + }, 0); } - } else { - // Ignore these - if (/^(br|hr|input|meta|img|link|param)$/i.test(c)) - return a; - - // Ignore closed ones - if (/\/>$/.test(a)) - return a; - - d.push({tag : c}); // Push start element - } + }); + } + }, - return a; - }); + _isHidden : function() { + var s; - // End all open tags - for (i=d.length - 1; i>=0; i--) - s += ''; + if (!isGecko) + return 0; - return s; + // Weird, wheres that cursor selection? + s = this.selection.getSel(); + return (!s || !s.rangeCount || s.rangeCount == 0); } }); })(tinymce); @@ -11037,6 +12589,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }); toggleFormat('align' + align); + execCommand('mceRepaint'); }, // Override list commands to fix WebKit bug @@ -11062,7 +12615,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, // Override commands to use the text formatter engine - 'Bold,Italic,Underline,Strikethrough' : function(command) { + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { toggleFormat(command); }, @@ -11097,13 +12650,15 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, FormatBlock : function(command, ui, value) { - return toggleFormat(value); + return toggleFormat(value || 'p'); }, mceCleanup : function() { - storeSelection(); + var bookmark = selection.getBookmark(); + editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE}); - restoreSelection(); + + selection.moveToBookmark(bookmark); }, mceRemoveNode : function(command, ui, value) { @@ -11133,12 +12688,120 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, mceInsertContent : function(command, ui, value) { - selection.setContent(value); + var caretNode, rng, rootNode, parent, node, rng, nodeRect, viewPortRect, args; + + function findSuitableCaretNode(node, root_node, next) { + var walker = new tinymce.dom.TreeWalker(next ? node.nextSibling : node.previousSibling, root_node); + + while ((node = walker.current())) { + if ((node.nodeType == 3 && tinymce.trim(node.nodeValue).length) || node.nodeName == 'BR' || node.nodeName == 'IMG') + return node; + + if (next) + walker.next(); + else + walker.prev(); + } + }; + + args = {content: value, format: 'html'}; + selection.onBeforeSetContent.dispatch(selection, args); + value = args.content; + + // Add caret at end of contents if it's missing + if (value.indexOf('{$caret}') == -1) + value += '{$caret}'; + + // Set the content at selection to a span and replace it's contents with the value + selection.setContent('\uFEFF', {no_events : false}); + dom.setOuterHTML('__mce', value.replace(/\{\$caret\}/, '\uFEFF')); + + caretNode = dom.select('#__mce')[0]; + rootNode = dom.getRoot(); + + // Move the caret into the last suitable location within the previous sibling if it's a block since the block might be split + if (caretNode.previousSibling && dom.isBlock(caretNode.previousSibling) || caretNode.parentNode == rootNode) { + node = findSuitableCaretNode(caretNode, rootNode); + if (node) { + if (node.nodeName == 'BR') + node.parentNode.insertBefore(caretNode, node); + else + dom.insertAfter(caretNode, node); + } + } + + // Find caret root parent and clean it up using the serializer to avoid nesting + while (caretNode) { + if (caretNode === rootNode) { + // Clean up the parent element by parsing and serializing it + // This will remove invalid elements/attributes and fix nesting issues + dom.setOuterHTML(parent, + new tinymce.html.Serializer({}, editor.schema).serialize( + editor.parser.parse(dom.getOuterHTML(parent)) + ) + ); + + break; + } + + parent = caretNode; + caretNode = caretNode.parentNode; + } + + // Find caret after cleanup and move selection to that location + caretNode = dom.select('#__mce')[0]; + if (caretNode) { + node = findSuitableCaretNode(caretNode, rootNode) || findSuitableCaretNode(caretNode, rootNode, true); + dom.remove(caretNode); + + if (node) { + rng = dom.createRng(); + + if (node.nodeType == 3) { + rng.setStart(node, node.length); + rng.setEnd(node, node.length); + } else { + if (node.nodeName == 'BR') { + rng.setStartBefore(node); + rng.setEndBefore(node); + } else { + rng.setStartAfter(node); + rng.setEndAfter(node); + } + } + + selection.setRng(rng); + + // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well + if (!tinymce.isIE) { + node = dom.create('span', null, '\u00a0'); + rng.insertNode(node); + nodeRect = dom.getRect(node); + viewPortRect = dom.getViewPort(editor.getWin()); + + // Check if node is out side the viewport if it is then scroll to it + if ((nodeRect.y > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) || + (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) { + editor.getBody().scrollLeft = nodeRect.x; + editor.getBody().scrollTop = nodeRect.y; + } + + dom.remove(node); + } + + // Make sure that the selection is collapsed after we removed the node fixes a WebKit bug + // where WebKit would place the endContainer/endOffset at a different location than the startContainer/startOffset + selection.collapse(true); + } + } + + selection.onSetContent.dispatch(selection, args); + editor.addVisual(); }, mceInsertRawHTML : function(command, ui, value) { selection.setContent('tiny_mce_marker'); - editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value)); + editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value })); }, mceSetContent : function(command, ui, value) { @@ -11188,7 +12851,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, InsertHorizontalRule : function() { - selection.setContent('
    '); + editor.execCommand('mceInsertContent', false, '
    '); }, mceToggleVisualAid : function() { @@ -11197,18 +12860,36 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, mceReplaceContent : function(command, ui, value) { - selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); + editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'}))); }, mceInsertLink : function(command, ui, value) { - var link = dom.getParent(selection.getNode(), 'a'); + var link = dom.getParent(selection.getNode(), 'a'), img, floatVal; if (tinymce.is(value, 'string')) value = {href : value}; + // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here. + value.href = value.href.replace(' ', '%20'); + if (!link) { + // WebKit can't create links on float images for some odd reason so just remove it and restore it later + if (tinymce.isWebKit) { + img = dom.getParent(selection.getNode(), 'img'); + + if (img) { + floatVal = img.style.cssFloat; + img.style.cssFloat = null; + } + } + execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);'); - each(dom.select('a[href=javascript:mctmp(0);]'), function(link) { + + // Restore float value + if (floatVal) + img.style.cssFloat = floatVal; + + each(dom.select("a[href='javascript:mctmp(0);']"), function(link) { dom.setAttribs(link, value); }); } else { @@ -11220,10 +12901,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }, selectAll : function() { - var root = dom.getRoot(); - var rng = dom.createRng(); + var root = dom.getRoot(), rng = dom.createRng(); + rng.setStart(root, 0); rng.setEnd(root, root.childNodes.length); + editor.selection.setRng(rng); } }); @@ -11235,7 +12917,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return isFormatMatch('align' + command.substring(7)); }, - 'Bold,Italic,Underline,Strikethrough' : function(command) { + 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) { return isFormatMatch(command); }, @@ -11292,6 +12974,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } }; })(tinymce); + (function(tinymce) { var Dispatcher = tinymce.util.Dispatcher; @@ -11303,12 +12986,20 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }; return self = { - typing : 0, + typing : false, onAdd : new Dispatcher(self), + onUndo : new Dispatcher(self), + onRedo : new Dispatcher(self), + beforeChange : function() { + // Set before bookmark on previous level + if (data[index]) + data[index].beforeBookmark = editor.selection.getBookmark(2, true); + }, + add : function(level) { var i, settings = editor.settings, lastLevel; @@ -11317,10 +13008,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Add undo level if needed lastLevel = data[index]; - if (lastLevel && lastLevel.content == level.content) { - if (index > 0 || data.length == 1) - return null; - } + if (lastLevel && lastLevel.content == level.content) + return null; // Time to compress if (settings.custom_undo_redo_levels) { @@ -11337,13 +13026,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { level.bookmark = editor.selection.getBookmark(2, true); // Crop array if needed - if (index < data.length - 1) { - // Treat first level as initial - if (index == 0) - data = []; - else - data.length = index + 1; - } + if (index < data.length - 1) + data.length = index + 1; data.push(level); index = data.length - 1; @@ -11359,14 +13043,14 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (self.typing) { self.add(); - self.typing = 0; + self.typing = false; } if (index > 0) { level = data[--index]; editor.setContent(level.content, {format : 'raw'}); - editor.selection.moveToBookmark(level.bookmark); + editor.selection.moveToBookmark(level.beforeBookmark); self.onUndo.dispatch(self, level); } @@ -11391,15 +13075,16 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { clear : function() { data = []; - index = self.typing = 0; + index = 0; + self.typing = false; }, hasUndo : function() { - return index > 0 || self.typing; + return index > 0 || this.typing; }, hasRedo : function() { - return index < data.length - 1; + return index < data.length - 1 && !this.typing; } }; }; @@ -11416,6 +13101,27 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { TRUE = true, FALSE = false; + function cloneFormats(node) { + var clone, temp, inner; + + do { + if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) { + if (clone) { + temp = node.cloneNode(false); + temp.appendChild(clone); + clone = temp; + } else { + clone = inner = node.cloneNode(false); + } + + clone.removeAttribute('id'); + } + } while (node = node.parentNode); + + if (clone) + return {wrapper : clone, inner : inner}; + }; + // Checks if the selection/caret is at the end of the specified block element function isAtEnd(rng, par) { var rng2 = par.ownerDocument.createRange(); @@ -11427,24 +13133,15 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return rng2.cloneContents().textContent.length == 0; }; - function isEmpty(n) { - n = n.innerHTML; - - n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars - n = n.replace(/<[^>]+>/g, ''); // Remove all tags - - return n.replace(/[ \u00a0\t\r\n]+/g, '') == ''; - }; - function splitList(selection, dom, li) { var listBlock, block; - if (isEmpty(li)) { + if (dom.isEmpty(li)) { listBlock = dom.getParent(li, 'ul,ol'); if (!dom.getParent(listBlock.parentNode, 'ul,ol')) { dom.split(listBlock, li); - block = dom.create('p', 0, '
    '); + block = dom.create('p', 0, '
    '); dom.replace(block, li); selection.select(block, 1); } @@ -11466,33 +13163,16 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { ed.onPreInit.add(t.setup, t); - t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi'); - t.rePadd = new RegExp(']+)><\\\/p>|]+)\\\/>|]+)>\\s+<\\\/p>|

    <\\\/p>||

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

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

    \\s*
    \\s*<\\\/p>'.replace(/p/g, elm), 'gi'); - - function padd(ed, o) { - if (isOpera) - o.content = o.content.replace(t.reOpera, ''); - - o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0'); - - if (!isIE && !isOpera && o.set) { - // Use   instead of BR in padded paragraphs - o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2>
    '); - o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2>
    '); - } else - o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0'); - }; - - ed.onBeforeSetContent.add(padd); - ed.onPostProcess.add(padd); - if (s.forced_root_block) { ed.onInit.add(t.forceRoots, t); ed.onSetContent.add(t.forceRoots, t); ed.onBeforeGetContent.add(t.forceRoots, t); + ed.onExecCommand.add(function(ed, cmd) { + if (cmd == 'mceInsertContent') { + t.forceRoots(); + ed.nodeChanged(); + } + }); } }, @@ -11524,11 +13204,56 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } } - if (!isIE && s.force_p_newlines) { - ed.onKeyPress.add(function(ed, e) { - if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e)) - Event.cancel(e); - }); + if (s.force_p_newlines) { + if (!isIE) { + ed.onKeyPress.add(function(ed, e) { + if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e)) + Event.cancel(e); + }); + } else { + // Ungly hack to for IE to preserve the formatting when you press + // enter at the end of a block element with formatted contents + // This logic overrides the browsers default logic with + // custom logic that enables us to control the output + tinymce.addUnload(function() { + t._previousFormats = 0; // Fix IE leak + }); + + ed.onKeyPress.add(function(ed, e) { + t._previousFormats = 0; + + // Clone the current formats, this will later be applied to the new block contents + if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles) + t._previousFormats = cloneFormats(ed.selection.getStart()); + }); + + ed.onKeyUp.add(function(ed, e) { + // Let IE break the element and the wrap the new caret location in the previous formats + if (e.keyCode == 13 && !e.shiftKey) { + var parent = ed.selection.getStart(), fmt = t._previousFormats; + + // Parent is an empty block + if (!parent.hasChildNodes() && fmt) { + parent = dom.getParent(parent, dom.isBlock); + + if (parent && parent.nodeName != 'LI') { + parent.innerHTML = ''; + + if (t._previousFormats) { + parent.appendChild(fmt.wrapper); + fmt.inner.innerHTML = '\uFEFF'; + } else + parent.innerHTML = '\uFEFF'; + + selection.select(parent, 1); + selection.collapse(true); + ed.getDoc().execCommand('Delete', false, null); + t._previousFormats = 0; + } + } + } + }); + } if (isGecko) { ed.onKeyDown.add(function(ed, e) { @@ -11576,21 +13301,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }); } - // Padd empty inline elements within block elements - // For example:

    becomes

     

    - ed.onPreProcess.add(function(ed, o) { - each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) { - if (isEmpty(p)) { - each(dom.select('span,em,strong,b,i', o.node), function(n) { - if (!n.hasChildNodes()) { - n.appendChild(ed.getDoc().createTextNode('\u00a0')); - return FALSE; // Break the loop one padding is enough - } - }); - } - }); - }); - // IE specific fixes if (isIE) { // Replaces IE:s auto generated paragraphs with the specified element name @@ -11651,7 +13361,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { nx = nl[i]; // Ignore internal elements - if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) { + if (nx.nodeType === 1 && nx.getAttribute('data-mce-type')) { bl = null; continue; } @@ -11663,7 +13373,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) { // Store selection if (si == -2 && r) { - if (!isIE) { + if (!isIE || r.setStart) { // If selection is element then mark it if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) { // Save the id of the selected element @@ -11722,7 +13432,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Restore selection if (si != -2) { - if (!isIE) { + if (!isIE || r.setStart) { bl = b.getElementsByTagName(ed.settings.element)[0]; r = d.createRange(); @@ -11754,7 +13464,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Ignore } } - } else if (!isIE && (n = ed.dom.get('__mce'))) { + } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) { // Restore the id of the selected element if (eid) n.setAttribute('id', eid); @@ -11779,6 +13489,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body; var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car; + ed.undoManager.beforeChange(); + // If root blocks are forced then use Operas default behavior since it's really good // Removed due to bug: #1853816 // if (se.forced_root_block && isOpera) @@ -11955,7 +13667,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { aft.innerHTML = aft.firstChild.innerHTML; // Padd empty blocks - if (isEmpty(bef)) + if (dom.isEmpty(bef)) bef.innerHTML = '
    '; function appendStyles(e, en) { @@ -11982,14 +13694,14 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { nn = nn.appendChild(nl[i]); // Padd most inner style element - nl[0].innerHTML = isOpera ? ' ' : '
    '; // Extra space for Opera so that the caret can move there + nl[0].innerHTML = isOpera ? '\u00a0' : '
    '; // Extra space for Opera so that the caret can move there return nl[0]; // Move caret to most inner element } else - e.innerHTML = isOpera ? ' ' : '
    '; // Extra space for Opera so that the caret can move there + e.innerHTML = isOpera ? '\u00a0' : '
    '; // Extra space for Opera so that the caret can move there }; // Fill empty afterblook with current style - if (isEmpty(aft)) + if (dom.isEmpty(aft)) car = appendStyles(aft, en); // Opera needs this one backwards for older versions @@ -12018,19 +13730,40 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs y = ed.dom.getPos(aft).y; - ch = aft.clientHeight; + //ch = aft.clientHeight; // Is element within viewport - if (y < vp.y || y + ch > vp.y + vp.h) { + if (y < vp.y || y + 25 > vp.y + vp.h) { ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks - //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight)); + + /*console.debug( + 'Element: y=' + y + ', h=' + ch + ', ' + + 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h) + );*/ } + ed.undoManager.add(); + return FALSE; }, backspaceDelete : function(e, bs) { - var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn; + var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker; + + // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651 + if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) { + walker = new tinymce.dom.TreeWalker(sc.lastChild, sc); + + // Walk the dom backwards until we find a text node + for (n = sc.lastChild; n; n = walker.prev()) { + if (n.nodeType == 3) { + r.setStart(n, n.nodeValue.length); + r.collapse(true); + se.setRng(r); + return; + } + } + } // The caret sometimes gets stuck in Gecko if you delete empty paragraphs // This workaround removes the element by hand and moves the caret to the previous element @@ -12061,37 +13794,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } } } - - // Gecko generates BR elements here and there, we don't like those so lets remove them - function handler(e) { - var pr; - - e = e.target; - - // A new BR was created in a block element, remove it - if (e && e.parentNode && e.nodeName == 'BR' && (n = t.getParentBlock(e))) { - pr = e.previousSibling; - - Event.remove(b, 'DOMNodeInserted', handler); - - // Is there whitespace at the end of the node before then we might need the pesky BR - // to place the caret at a correct location see bug: #2013943 - if (pr && pr.nodeType == 3 && /\s+$/.test(pr.nodeValue)) - return; - - // Only remove BR elements that got inserted in the middle of the text - if (e.previousSibling || e.nextSibling) - ed.dom.remove(e); - } - }; - - // Listen for new nodes - Event._add(b, 'DOMNodeInserted', handler); - - // Remove listener - window.setTimeout(function() { - Event._remove(b, 'DOMNodeInserted', handler); - }, 1); } }); })(tinymce); @@ -12256,7 +13958,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { c = new tinymce.ui.NativeListBox(id, s); else { cls = cc || t._cls.listbox || tinymce.ui.ListBox; - c = new cls(id, s); + c = new cls(id, s, ed); } t.controls[id] = c; @@ -12311,7 +14013,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (s.menu_button) { cls = cc || t._cls.menubutton || tinymce.ui.MenuButton; - c = new cls(id, s); + c = new cls(id, s, ed); ed.onMouseDown.add(c.hideMenu, c); } else { cls = t._cls.button || tinymce.ui.Button; @@ -12358,7 +14060,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { id = t.prefix + id; cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton; - c = t.add(new cls(id, s)); + c = t.add(new cls(id, s, ed)); ed.onMouseDown.add(c.hideMenu, c); return c; @@ -12398,7 +14100,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { id = t.prefix + id; cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton; - c = new cls(id, s); + c = new cls(id, s, ed); ed.onMouseDown.add(c.hideMenu, c); // Remove the menu element when the editor is removed @@ -12430,13 +14132,25 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { id = t.prefix + id; cls = cc || t._cls.toolbar || tinymce.ui.Toolbar; - c = new cls(id, s); + c = new cls(id, s, t.editor); if (t.get(id)) return null; return t.add(c); }, + + createToolbarGroup : function(id, s, cc) { + var c, t = this, cls; + id = t.prefix + id; + cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup; + c = new cls(id, s, t.editor); + + if (t.get(id)) + return null; + + return t.add(c); + }, createSeparator : function(cc) { var cls = cc || this._cls.separator || tinymce.ui.Separator; @@ -12573,53 +14287,6 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } }); }(tinymce)); -(function(tinymce) { - function CommandManager() { - var execCommands = {}, queryStateCommands = {}, queryValueCommands = {}; - - function add(collection, cmd, func, scope) { - if (typeof(cmd) == 'string') - cmd = [cmd]; - - tinymce.each(cmd, function(cmd) { - collection[cmd.toLowerCase()] = {func : func, scope : scope}; - }); - }; - - tinymce.extend(this, { - add : function(cmd, func, scope) { - add(execCommands, cmd, func, scope); - }, - - addQueryStateHandler : function(cmd, func, scope) { - add(queryStateCommands, cmd, func, scope); - }, - - addQueryValueHandler : function(cmd, func, scope) { - add(queryValueCommands, cmd, func, scope); - }, - - execCommand : function(scope, cmd, ui, value, args) { - if (cmd = execCommands[cmd.toLowerCase()]) { - if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false) - return true; - } - }, - - queryCommandValue : function() { - if (cmd = queryValueCommands[cmd.toLowerCase()]) - return cmd.func.call(scope || cmd.scope, ui, value, args); - }, - - queryCommandState : function() { - if (cmd = queryStateCommands[cmd.toLowerCase()]) - return cmd.func.call(scope || cmd.scope, ui, value, args); - } - }); - }; - - tinymce.GlobalCommands = new CommandManager(); -})(tinymce); (function(tinymce) { tinymce.Formatter = function(ed) { var formats = {}, @@ -12628,7 +14295,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { selection = ed.selection, TreeWalker = tinymce.dom.TreeWalker, rangeUtils = new tinymce.dom.RangeUtils(dom), - isValid = ed.schema.isValid, + isValid = ed.schema.isValidChild, isBlock = dom.isBlock, forcedRootBlock = ed.settings.forced_root_block, nodeIndex = dom.nodeIndex, @@ -12697,8 +14364,31 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } }; + var getTextDecoration = function(node) { + var decoration; + + ed.dom.getParent(node, function(n) { + decoration = ed.dom.getStyle(n, 'text-decoration'); + return decoration && decoration !== 'none'; + }); + + return decoration; + }; + + var processUnderlineAndColor = function(node) { + var textDecoration; + if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) { + textDecoration = getTextDecoration(node.parentNode); + if (ed.dom.getStyle(node, 'color') && textDecoration) { + ed.dom.setStyle(node, 'text-decoration', textDecoration); + } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) { + ed.dom.setStyle(node, 'text-decoration', null); + } + } + }; + function apply(name, vars, node) { - var formatList = get(name), format = formatList[0], bookmark, rng, i; + var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed(); function moveStart(rng) { var container = rng.startContainer, @@ -12708,11 +14398,15 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Move startContainer/startOffset in to a suitable node if (container.nodeType == 1 || container.nodeValue === "") { container = container.nodeType == 1 ? container.childNodes[offset] : container; - walker = new TreeWalker(container, container.parentNode); - for (node = walker.current(); node; node = walker.next()) { - if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) { - rng.setStart(node, 0); - break; + + // Might fail if the offset is behind the last element in it's container + if (container) { + walker = new TreeWalker(container, container.parentNode); + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + rng.setStart(node, 0); + break; + } } } } @@ -12785,6 +14479,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (format.selector) { // Look for matching formats each(formatList, function(format) { + // Check collapsed state if it exists + if ('collapsed' in format && format.collapsed !== isCollapsed) { + return; + } + if (dom.is(node, format.selector) && !isCaretNode(node)) { setElementFormat(node, format); found = true; @@ -12799,7 +14498,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { } // Is it valid to wrap this item - if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) { + if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) && + !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) { // Start wrapping if (!currentWrapElm) { // Wrap the node @@ -12824,6 +14524,30 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { each(nodes, process); }); + // Wrap links inside as well, for example color inside a link when the wrapper is around the link + if (format.wrap_links === false) { + each(newWrappers, function(node) { + function process(node) { + var i, currentWrapElm, children; + + if (node.nodeName === 'A') { + currentWrapElm = wrapElm.cloneNode(FALSE); + newWrappers.push(currentWrapElm); + + children = tinymce.grep(node.childNodes); + for (i = 0; i < children.length; i++) + currentWrapElm.appendChild(children[i]); + + node.appendChild(currentWrapElm); + } + + each(tinymce.grep(node.childNodes), process); + }; + + process(node); + }); + } + // Cleanup each(newWrappers, function(node) { var childCount; @@ -12863,8 +14587,9 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { childCount = getChildCount(node); - // Remove empty nodes - if (childCount === 0) { + // Remove empty nodes but only if there is multiple wrappers and they are not block + // elements so never remove single

    since that would remove the currrent empty block element where the caret is at + if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) { dom.remove(node, 1); return; } @@ -12880,6 +14605,19 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // this: text // will become: text each(dom.select(format.inline, node), function(child) { + var parent; + + // When wrap_links is set to false we don't want + // to remove the format on children within links + if (format.wrap_links === false) { + parent = child.parentNode; + + do { + if (parent.nodeName === 'A') + return; + } while (parent = parent.parentNode); + } + removeFormat(format, vars, child, format.exact ? child : null); }); }); @@ -12918,13 +14656,22 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { rng.setStartBefore(node); rng.setEndAfter(node); - applyRngStyle(rng); + applyRngStyle(expandRng(rng, formatList)); } else { - if (!selection.isCollapsed() || !format.inline) { + if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { + // Obtain selection node before selection is unselected by applyRngStyle() + var curSelNode = ed.selection.getNode(); + // Apply formatting to selection bookmark = selection.getBookmark(); applyRngStyle(expandRng(selection.getRng(TRUE), formatList)); + // Colored nodes should be underlined so that the color of the underline matches the text color. + if (format.styles && (format.styles.color || format.styles.textDecoration)) { + tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes'); + processUnderlineAndColor(curSelNode); + } + selection.moveToBookmark(bookmark); selection.setRng(moveStart(selection.getRng(TRUE))); ed.nodeChanged(); @@ -12937,6 +14684,45 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { function remove(name, vars, node) { var formatList = get(name), format = formatList[0], bookmark, i, rng; + function moveStart(rng) { + var container = rng.startContainer, + offset = rng.startOffset, + walker, node, nodes, tmpNode; + + // Convert text node into index if possible + if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) { + container = container.parentNode; + offset = nodeIndex(container) + 1; + } + + // Move startContainer/startOffset in to a suitable node + if (container.nodeType == 1) { + nodes = container.childNodes; + container = nodes[Math.min(offset, nodes.length - 1)]; + walker = new TreeWalker(container); + + // If offset is at end of the parent node walk to the next one + if (offset > nodes.length - 1) + walker.next(); + + for (node = walker.current(); node; node = walker.next()) { + if (node.nodeType == 3 && !isWhiteSpaceNode(node)) { + // IE has a "neat" feature where it moves the start node into the closest element + // we can avoid this by inserting an element before it and then remove it after we set the selection + tmpNode = dom.create('a', null, INVISIBLE_CHAR); + node.parentNode.insertBefore(tmpNode, node); + + // Set selection and remove tmpNode + rng.setStart(node, 0); + selection.setRng(rng); + dom.remove(tmpNode); + + return; + } + } + } + }; + // Merges the styles for each node function process(node) { var children, i, l; @@ -13049,8 +14835,8 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (startContainer != endContainer) { // Wrap start/end nodes in span element since these might be cloned/moved - startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'}); - endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'}); + startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'}); + endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'}); // Split start/end splitToFormatRoot(startContainer); @@ -13073,6 +14859,11 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { rangeUtils.walk(rng, function(nodes) { each(nodes, function(node) { process(node); + + // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined. + if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') { + removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node); + } }); }); }; @@ -13086,17 +14877,25 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return; } - if (!selection.isCollapsed() || !format.inline) { + if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) { bookmark = selection.getBookmark(); removeRngStyle(selection.getRng(TRUE)); selection.moveToBookmark(bookmark); + + // Check if start element still has formatting then we are at: "text|text" and need to move the start into the next text node + if (match(name, vars, selection.getStart())) { + moveStart(selection.getRng(true)); + } + ed.nodeChanged(); } else performCaretAction('remove', name, vars); }; function toggle(name, vars, node) { - if (match(name, vars, node)) + var fmt = get(name); + + if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle'])) remove(name, vars, node); else apply(name, vars, node); @@ -13344,7 +15143,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }; function isWhiteSpaceNode(node) { - return node && node.nodeType === 3 && /^\s*$/.test(node.nodeValue); + return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue); }; function wrap(node, name, attrs) { @@ -13360,7 +15159,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { var startContainer = rng.startContainer, startOffset = rng.startOffset, endContainer = rng.endContainer, - endOffset = rng.endOffset, sibling, lastIdx; + endOffset = rng.endOffset, sibling, lastIdx, leaf; // This function walks up the tree if there is no siblings before/after the node function findParentContainer(container, child_name, sibling_name, root) { @@ -13390,6 +15189,19 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { return container; }; + // This function walks down the tree to find the leaf at the selection. + // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node. + function findLeaf(node, offset) { + if (offset === undefined) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + while (node && node.hasChildNodes()) { + node = node.childNodes[offset]; + if (node) + offset = node.nodeType === 3 ? node.length : node.childNodes.length; + } + return { node: node, offset: offset }; + } + // If index based start position then resolve it if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) { lastIdx = startContainer.childNodes.length - 1; @@ -13415,12 +15227,36 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (isBookmarkNode(startContainer)) startContainer = startContainer.nextSibling || startContainer; - if (isBookmarkNode(endContainer.parentNode)) + if (isBookmarkNode(endContainer.parentNode)) { + endOffset = dom.nodeIndex(endContainer); endContainer = endContainer.parentNode; + } + + if (isBookmarkNode(endContainer) && endContainer.previousSibling) { + endContainer = endContainer.previousSibling; + endOffset = endContainer.length; + } - if (isBookmarkNode(endContainer)) - endContainer = endContainer.previousSibling || endContainer; + if (format[0].inline) { + // Avoid applying formatting to a trailing space. + leaf = findLeaf(endContainer, endOffset); + if (leaf.node) { + while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling) + leaf = findLeaf(leaf.node.previousSibling); + if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 && + leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') { + + if (leaf.offset > 1) { + endContainer = leaf.node; + endContainer.splitText(leaf.offset - 1); + } else if (leaf.node.previousSibling) { + endContainer = leaf.node.previousSibling; + } + } + } + } + // Move start/end point up the tree if the leaves are sharp and if we are in different containers // Example * becomes !: !

    *texttext*

    ! // This will reduce the number of wrapper elements that needs to be created @@ -13433,7 +15269,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Expand start/end container to matching selector if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) { function findSelectorEndPoint(container, sibling_name) { - var parents, i, y; + var parents, i, y, curFormat; if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name]) container = container[sibling_name]; @@ -13441,7 +15277,13 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { parents = getParents(container); for (i = 0; i < parents.length; i++) { for (y = 0; y < format.length; y++) { - if (dom.is(parents[i], format[y].selector)) + curFormat = format[y]; + + // If collapsed state is set then skip formats that doesn't match that + if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed) + continue; + + if (dom.is(parents[i], curFormat.selector)) return parents[i]; } } @@ -13551,7 +15393,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Remove style attribute if it's empty if (stylesModified && dom.getAttrib(node, 'style') == '') { node.removeAttribute('style'); - node.removeAttribute('_mce_style'); + node.removeAttribute('data-mce-style'); } // Remove attributes @@ -13592,7 +15434,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Remove mce prefixed attributes if (MCE_ATTR_RE.test(name)) - node.removeAttribute('_mce_' + name); + node.removeAttribute('data-mce-' + name); node.removeAttribute(name); } @@ -13677,7 +15519,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { }; function isBookmarkNode(node) { - return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark'; + return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark'; }; function mergeSiblings(prev, next) { @@ -13748,7 +15590,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (prev && next) { function findElementSibling(node, sibling_name) { for (sibling = node; sibling; sibling = sibling[sibling_name]) { - if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling)) + if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0) return node; if (sibling.nodeType == 1 && !isBookmarkNode(sibling)) @@ -13825,6 +15667,10 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { // Apply pending formats each(pendingFormats.apply.reverse(), function(item) { apply(item.name, item.vars, caret_node); + + // Colored nodes should be underlined so that the color of the underline matches the text color. + if (item.name === 'forecolor' && item.vars.value) + processUnderlineAndColor(caret_node.parentNode); }); // Remove pending formats @@ -13882,13 +15728,16 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', { if (isCaretNode(node)) { textNode = node.firstChild; - perform(node); + if (textNode) { + perform(node); - rng = dom.createRng(); - rng.setStart(textNode, textNode.nodeValue.length); - rng.setEnd(textNode, textNode.nodeValue.length); - selection.setRng(rng); - ed.nodeChanged(); + rng = dom.createRng(); + rng.setStart(textNode, textNode.nodeValue.length); + rng.setEnd(textNode, textNode.nodeValue.length); + selection.setRng(rng); + ed.nodeChanged(); + } else + dom.remove(node); } }); @@ -13911,9 +15760,12 @@ tinymce.onAddEditor.add(function(tinymce, ed) { fontSizes = tinymce.explode(settings.font_size_style_values); function replaceWithSpan(node, styles) { - dom.replace(dom.create('span', { - style : styles - }), node, 1); + tinymce.each(styles, function(value, name) { + if (value) + dom.setStyle(node, name, value); + }); + + dom.rename(node, 'span'); }; filters = { @@ -13950,6 +15802,7 @@ tinymce.onAddEditor.add(function(tinymce, ed) { }; ed.onPreProcess.add(convert); + ed.onSetContent.add(convert); ed.onInit.add(function() { ed.selection.onSetContent.add(convert); -- cgit v1.2.3