You are on page 1of 479

/**

* This is necessary in booting up the baseURL of the tinyMCE object.


* It normally looks up all script tags to determine the base url to fetch extra
files from.
* This does not work well when using a bundle since the path of the bundle will
differ greatly from the path of the tinyMCE core files.
*/
window.tinyMCEPreInit = {
suffix: '',
base: window.location.protocol + '//' + window.location.host +
'/sites/all/libraries/tinymce/jscripts/tiny_mce',
query: ''
};
;// FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T
MODIFY THIS FILE DIRECTLY
/**
* Modifications
*
* SGY-5281
* @see issue: http://www.tinymce.com/develop/bugtracker_view.php?id=5641
*/
(function(win) {
var whiteSpaceRe = /^\s*|\s*$/g,
undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';

var tinymce = {
majorVersion : '3',

minorVersion : '5.8',

releaseDate : '2012-11-20',

_init : function() {
var t = this, d = document, na = navigator, ua = na.userAgent, i,
nl, n, base, p, v;

t.isOpera = win.opera && opera.buildNumber;

t.isWebKit = /WebKit/.test(ua);

t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) &&


(/Explorer/gi).test(na.appName);

t.isIE6 = t.isIE && /MSIE [56]/.test(ua);

t.isIE7 = t.isIE && /MSIE [7]/.test(ua);

t.isIE8 = t.isIE && /MSIE [8]/.test(ua);

t.isIE9 = t.isIE && /MSIE [9]/.test(ua);

// SGY-12411 -- Added IE11 detection and updated isGecko


detection. Reused logic from tinyMCE source v4.2.1
t.isIE11 = ua.indexOf('Trident/') != -1 && (ua.indexOf('rv:') !=
-1 || na.appName.indexOf('Netscape') != -1);

t.isGecko = !t.isWebKit && !t.isIE11 && /Gecko/.test(ua);

t.isMac = ua.indexOf('Mac') != -1;


t.isAir = /adobeair/i.test(ua);

t.isIDevice = /(iPad|iPhone)/.test(ua);

t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;

// TinyMCE .NET webcontrol might be setting the values for


TinyMCE
if (win.tinyMCEPreInit) {
t.suffix = tinyMCEPreInit.suffix;
t.baseURL = tinyMCEPreInit.base;
t.query = tinyMCEPreInit.query;
return;
}

// Get suffix and base


t.suffix = '';

// If base element found, add that infront of baseURL


nl = d.getElementsByTagName('base');
for (i=0; i<nl.length; i++) {
v = nl[i].href;
if (v) {
// Host only value like http://site.com or
http://site.com:8008
if (/^https?:\/\/[^\/]+$/.test(v))
v += '/';

base = v ? v.match(/.*\//)[0] : ''; // Get only


directory
}
}

function getBase(n) {
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';

if ((p = n.src.indexOf('?')) != -1)


t.query = n.src.substring(p + 1);

t.baseURL = n.src.substring(0,
n.src.lastIndexOf('/'));

// If path to script is relative and a base href was


found add that one infront
// the src property will always be an absolute one on
non IE browsers and IE 8
// so this logic will basically only be executed on
older IE versions
if (base && t.baseURL.indexOf('://') == -1 &&
t.baseURL.indexOf('/') !== 0)
t.baseURL = base + t.baseURL;

return t.baseURL;
}
return null;
};

// Check document
nl = d.getElementsByTagName('script');
for (i=0; i<nl.length; i++) {
if (getBase(nl[i]))
return;
}

// Check head
n = d.getElementsByTagName('head')[0];
if (n) {
nl = n.getElementsByTagName('script');
for (i=0; i<nl.length; i++) {
if (getBase(nl[i]))
return;
}
}

return;
},

is : function(o, t) {
if (!t)
return o !== undef;

if (t == 'array' && tinymce.isArray(o))


return true;

return typeof(o) == t;
},

isArray: Array.isArray || function(obj) {


return Object.prototype.toString.call(obj) === "[object Array]";
},

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;

if (!o)
return 0;

s = s || o;

if (o.length !== undef) {


// Indexed arrays, needed for Safari
for (n=0, l = o.length; n < l; n++) {
if (cb.call(s, o[n], n, o) === false)
return 0;
}
} else {
// Hashtables
for (n in o) {
if (o.hasOwnProperty(n)) {
if (cb.call(s, o[n], n, o) === false)
return 0;
}
}
}

return 1;
},

map : function(a, f) {
var o = [];

tinymce.each(a, function(v) {
o.push(f(v));
});

return o;
},

grep : function(a, f) {
var o = [];

tinymce.each(a, function(v) {
if (!f || f(v))
o.push(v);
});

return o;
},

inArray : function(a, v) {
var i, l;

if (a) {
for (i = 0, l = a.length; i < l; i++) {
if (a[i] === v)
return i;
}
}

return -1;
},

extend : function(obj, ext) {


var i, l, name, args = arguments, value;

for (i = 1, l = args.length; i < l; i++) {


ext = args[i];
for (name in ext) {
if (ext.hasOwnProperty(name)) {
value = ext[name];

if (value !== undef) {


obj[name] = value;
}
}
}
}

return obj;
},

trim : function(s) {
return (s ? '' + s : '').replace(whiteSpaceRe, '');
},

create : function(s, p, root) {


var t = this, sp, ns, cn, scn, c, de = 0;

// Parse : <prefix> <class>:<super class>


s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name

// Create namespace for new class


ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);

// Class already exists


if (ns[cn])
return;

// Make pure static class


if (s[2] == 'static') {
ns[cn] = p;

if (this.onCreate)
this.onCreate(s[2], s[3], ns[cn]);

return;
}

// Create default constructor


if (!p[cn]) {
p[cn] = function() {};
de = 1;
}

// Add constructor and methods


ns[cn] = p[cn];
t.extend(ns[cn].prototype, p);

// Extend
if (s[5]) {
sp = t.resolve(s[5]).prototype;
scn = s[5].match(/\.(\w+)$/i)[1]; // Class name

// Extend constructor
c = ns[cn];
if (de) {
// Add passthrough constructor
ns[cn] = function() {
return sp[scn].apply(this, arguments);
};
} else {
// Add inherit constructor
ns[cn] = function() {
this.parent = sp[scn];
return c.apply(this, arguments);
};
}
ns[cn].prototype[cn] = ns[cn];

// Add super methods


t.each(sp, function(f, n) {
ns[cn].prototype[n] = sp[n];
});

// Add overridden methods


t.each(p, function(f, n) {
// Extend methods if needed
if (sp[n]) {
ns[cn].prototype[n] = function() {
this.parent = sp[n];
return f.apply(this, arguments);
};
} else {
if (n != cn)
ns[cn].prototype[n] = f;
}
});
}

// Add static methods


t.each(p['static'], function(f, n) {
ns[cn][n] = f;
});

if (this.onCreate)
this.onCreate(s[2], s[3], ns[cn].prototype);
},

walk : function(o, f, n, s) {
s = s || this;

if (o) {
if (n)
o = o[n];

tinymce.each(o, function(o, i) {
if (f.call(s, o, i, n) === false)
return false;
tinymce.walk(o, f, n, s);
});
}
},

createNS : function(n, o) {
var i, v;

o = o || win;

n = n.split('.');
for (i=0; i<n.length; i++) {
v = n[i];

if (!o[v])
o[v] = {};

o = o[v];
}

return o;
},

resolve : function(n, o) {
var i, l;

o = o || win;

n = n.split('.');
for (i = 0, l = n.length; i < l; i++) {
o = o[n[i]];

if (!o)
break;
}

return o;
},

addUnload : function(f, s) {
var t = this, unload;

unload = function() {
var li = t.unloads, o, n;

if (li) {
// Call unload handlers
for (n in li) {
o = li[n];

if (o && o.func)
o.func.call(o.scope, 1); // Send in one
arg to distinct unload and user destroy
}

// Detach unload function


if (win.detachEvent) {
win.detachEvent('onbeforeunload', fakeUnload);
win.detachEvent('onunload', unload);
} else if (win.removeEventListener)
win.removeEventListener('unload', unload,
false);

// Destroy references
t.unloads = o = li = w = unload = 0;

// Run garbarge collector on IE


if (win.CollectGarbage)
CollectGarbage();
}
};

function fakeUnload() {
var d = document;

function stop() {
// Prevent memory leak
d.detachEvent('onstop', stop);

// Call unload handler


if (unload)
unload();

d = 0;
};

// Is there things still loading, then do some magic


if (d.readyState == 'interactive') {
// Fire unload when the currently loading page is
stopped
if (d)
d.attachEvent('onstop', stop);

// Remove onstop listener after a while to prevent


the unload function
// to execute if the user presses cancel in an
onbeforeunload
// confirm dialog and then presses the browser stop
button
win.setTimeout(function() {
if (d)
d.detachEvent('onstop', stop);
}, 0);
}
};

f = {func : f, scope : s || this};

if (!t.unloads) {
// Attach unload handler
if (win.attachEvent) {
win.attachEvent('onunload', unload);
win.attachEvent('onbeforeunload', fakeUnload);
} else if (win.addEventListener)
win.addEventListener('unload', unload, false);

// Setup initial unload handler array


t.unloads = [f];
} else
t.unloads.push(f);

return f;
},

removeUnload : function(f) {
var u = this.unloads, r = null;

tinymce.each(u, function(o, i) {
if (o && o.func == f) {
u.splice(i, 1);
r = f;
return false;
}
});

return r;
},

explode : function(s, d) {
if (!s || tinymce.is(s, 'array')) {
return s;
}

return tinymce.map(s.split(d || ','), tinymce.trim);


},

_addVer : function(u) {
var v;

if (!this.query)
return u;

v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;

if (u.indexOf('#') == -1)
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] === undef) {
val = val.replace(new RegExp('\\$' + i,
'g'), '');
} else {
val = val.replace(new RegExp('\\$' + i,
'g'), args[i]);
}
}
return val;
});
}

return str.replace(find, replace);


}

};

// Initialize the API


tinymce._init();

// Expose tinymce namespace to the global namespace (window)


win.tinymce = win.tinyMCE = tinymce;

// Describe the different namespaces

})(window);

tinymce.create('tinymce.util.Dispatcher', {
scope : null,
listeners : null,
inDispatch: false,

Dispatcher : function(scope) {
this.scope = scope || this;
this.listeners = [];
},

add : function(callback, scope) {


this.listeners.push({cb : callback, scope : scope || this.scope});

return callback;
},

addToTop : function(callback, scope) {


var self = this, listener = {cb : callback, scope : scope ||
self.scope};

// Create new listeners if addToTop is executed in a dispatch loop


if (self.inDispatch) {
self.listeners = [listener].concat(self.listeners);
} else {
self.listeners.unshift(listener);
}

return callback;
},

remove : function(callback) {
var listeners = this.listeners, output = null;

tinymce.each(listeners, function(listener, i) {
if (callback == listener.cb) {
output = listener;
listeners.splice(i, 1);
return false;
}
});

return output;
},

dispatch : function() {
var self = this, returnValue, args = arguments, i, listeners =
self.listeners, listener;

self.inDispatch = true;

// Needs to be a real loop since the listener count might change while
looping
// And this is also more efficient
for (i = 0; i < listeners.length; i++) {
listener = listeners[i];
returnValue = listener.cb.apply(listener.scope, args.length > 0 ?
args : [listener.scope]);

if (returnValue === false)


break;
}

self.inDispatch = false;

return returnValue;
}

});

(function() {
var each = tinymce.each;

tinymce.create('tinymce.util.URI', {
URI : function(u, s) {
var t = this, o, a, b, base_url;

// Trim whitespace
u = tinymce.trim(u);

// Default settings
s = t.settings = s || {};

// Strange app protocol that isn't http/https or local anchor


// For example: mailto,skype,tel etc.
if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
t.source = u;
return;
}

// Absolute path with no host, fake host and protocol


if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') +
'://mce_host' + u;

// Relative path http:// or protocol relative //path


if (!/^[\w\-]*:?\/\//.test(u)) {
base_url = s.base_uri ? s.base_uri.path : new
tinymce.util.URI(location.href).directory;
u = ((s.base_uri && s.base_uri.protocol) || 'http') +
'://mce_host' + t.toAbsPath(base_url, u);
}

// Parse URL (Credits goes to Steave,


http://blog.stevenlevithan.com/archives/parseuri)
u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use
@@something
u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:
(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?
#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);

each(["source","protocol","authority","userInfo","user","password","host","port","r
elative","path","directory","file","query","anchor"], function(v, i) {
var s = u[i];

// Zope 3 workaround, they use @@something


if (s)
s = s.replace(/\(mce_at\)/g, '@@');

t[v] = s;
});

b = s.base_uri;
if (b) {
if (!t.protocol)
t.protocol = b.protocol;

if (!t.userInfo)
t.userInfo = b.userInfo;

if (!t.port && t.host === 'mce_host')


t.port = b.port;

if (!t.host || t.host === 'mce_host')


t.host = b.host;

t.source = '';
}

//t.path = t.path || '/';


},

setPath : function(p) {
var t = this;

p = /^(.*?)\/?(\w+)?$/.exec(p);

// Update path parts


t.path = p[0];
t.directory = p[1];
t.file = p[2];

// Rebuild source
t.source = '';
t.getURI();
},

toRelative : function(u) {
var t = this, o;

if (u === "./")
return u;

u = new tinymce.util.URI(u, {base_uri : t});

// Not on same domain/port or protocol


if ((u.host != 'mce_host' && t.host != u.host && u.host) ||
t.port != u.port || t.protocol != u.protocol)
return u.getURI();

var tu = t.getURI(), uu = u.getURI();

// Allow usage of the base_uri when relative_urls = true


if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0,
tu.length - 1) == uu))
return tu;

o = t.toRelPath(t.path, u.path);

// Add query
if (u.query)
o += '?' + u.query;

// Add anchor
if (u.anchor)
o += '#' + u.anchor;

return o;
},

toAbsolute : function(u, nh) {


u = new tinymce.util.URI(u, {base_uri : this});

return u.getURI(this.host == u.host && this.protocol ==


u.protocol ? nh : 0);
},

toRelPath : function(base, path) {


var items, bp = 0, out = '', i, l;

// Split the paths


base = base.substring(0, base.lastIndexOf('/'));
base = base.split('/');
items = path.split('/');

if (base.length >= items.length) {


for (i = 0, l = base.length; i < l; i++) {
if (i >= items.length || base[i] != items[i]) {
bp = i + 1;
break;
}
}
}
if (base.length < items.length) {
for (i = 0, l = items.length; i < l; i++) {
if (i >= base.length || base[i] != items[i]) {
bp = i + 1;
break;
}
}
}

if (bp === 1)
return path;

for (i = 0, l = base.length - (bp - 1); i < l; i++)


out += "../";

for (i = bp - 1, l = items.length; i < l; i++) {


if (i != bp - 1)
out += "/" + items[i];
else
out += items[i];
}

return out;
},

toAbsPath : function(base, path) {


var i, nb = 0, o = [], tr, outPath;

// Split paths
tr = /\/$/.test(path) ? '/' : '';
base = base.split('/');
path = path.split('/');

// Remove empty chunks


each(base, function(k) {
if (k)
o.push(k);
});

base = o;

// Merge relURLParts chunks


for (i = path.length - 1, o = []; i >= 0; i--) {
// Ignore empty or .
if (path[i].length === 0 || path[i] === ".")
continue;

// Is parent
if (path[i] === '..') {
nb++;
continue;
}

// Move up
if (nb > 0) {
nb--;
continue;
}
o.push(path[i]);
}

i = base.length - nb;

// If /a/b/c or /
if (i <= 0)
outPath = o.reverse().join('/');
else
outPath = base.slice(0, i).join('/') + '/' +
o.reverse().join('/');

// Add front / if it's needed


if (outPath.indexOf('/') !== 0)
outPath = '/' + outPath;

// Add traling / if it's needed


if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
outPath += tr;

return outPath;
},

getURI : function(nh) {
var s, t = this;

// Rebuild source
if (!t.source || nh) {
s = '';

if (!nh) {
if (t.protocol)
s += t.protocol + '://';

if (t.userInfo)
s += t.userInfo + '@';

if (t.host)
s += t.host;

if (t.port)
s += ':' + t.port;
}

if (t.path)
s += t.path;

if (t.query)
s += '?' + t.query;

if (t.anchor)
s += '#' + t.anchor;

t.source = s;
}

return t.source;
}
});
})();

(function() {
var each = tinymce.each;

tinymce.create('static tinymce.util.Cookie', {
getHash : function(n) {
var v = this.get(n), h;

if (v) {
each(v.split('&'), function(v) {
v = v.split('=');
h = h || {};
h[unescape(v[0])] = unescape(v[1]);
});
}

return h;
},

setHash : function(n, v, e, p, d, s) {
var o = '';

each(v, function(v, k) {
o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
});

this.set(n, o, e, p, d, s);
},

get : function(n) {
var c = document.cookie, e, p = n + "=", b;

// Strict mode
if (!c)
return;

b = c.indexOf("; " + p);

if (b == -1) {
b = c.indexOf(p);

if (b !== 0)
return null;
} else
b += 2;

e = c.indexOf(";", b);

if (e == -1)
e = c.length;

return unescape(c.substring(b + p.length, e));


},

set : function(n, v, e, p, d, s) {
document.cookie = n + "=" + escape(v) +
((e) ? "; expires=" + e.toGMTString() : "") +
((p) ? "; path=" + escape(p) : "") +
((d) ? "; domain=" + d : "") +
((s) ? "; secure" : "");
},

remove : function(name, path, domain) {


var date = new Date();

date.setTime(date.getTime() - 1000);

this.set(name, '', date, path, domain);


}
});
})();

(function() {
function serialize(o, quote) {
var i, v, t, name;

quote = quote || '"';

if (o == null)
return 'null';

t = typeof o;

if (t == 'string') {
v = '\bb\tt\nn\ff\rr\""\'\'\\\\';

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)
return '\\' + v.charAt(i + 1);

a = b.charCodeAt().toString(16);

return '\\u' + '0000'.substring(a.length) + a;


}) + quote;
}

if (t == 'object') {
if (o.hasOwnProperty && Object.prototype.toString.call(o) ===
'[object Array]') {
for (i=0, v = '['; i<o.length; i++)
v += (i > 0 ? ',' : '') + serialize(o[i],
quote);

return v + ']';
}

v = '{';

for (name in o) {
if (o.hasOwnProperty(name)) {
v += typeof o[name] != 'function' ? (v.length >
1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
}
}

return v + '}';
}

return '' + o;
};

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;

function ready() {
if (!o.async || x.readyState == 4 || c++ > 10000) {
if (o.success && c < 10000 && x.status == 200)
o.success.call(o.success_scope, '' + x.responseText,
x, o);
else if (o.error)
o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' :
'GENERAL', x, o);

x = null;
} else
w.setTimeout(ready, 10);
};

// Default settings
o.scope = o.scope || this;
o.success_scope = o.success_scope || o.scope;
o.error_scope = o.error_scope || o.scope;
o.async = o.async === false ? false : true;
o.data = o.data || '';

function get(s) {
x = 0;

try {
x = new ActiveXObject(s);
} catch (ex) {
}
return x;
};

x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP')


|| get('Msxml2.XMLHTTP');

if (x) {
if (x.overrideMimeType)
x.overrideMimeType(o.content_type);

x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);

if (o.content_type)
x.setRequestHeader('Content-Type', o.content_type);

x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

x.send(o.data);

// Syncronous request
if (!o.async)
return ready();

// Wait for response, onReadyStateChange can not be used since it


leaks memory in IE
t = w.setTimeout(ready, 10);
}
}
});

(function() {
var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR =
tinymce.util.XHR;

tinymce.create('tinymce.util.JSONRequest', {
JSONRequest : function(s) {
this.settings = extend({
}, s);
this.count = 0;
},

send : function(o) {
var ecb = o.error, scb = o.success;

o = extend(this.settings, o);

o.success = function(c, x) {
c = JSON.parse(c);

if (typeof(c) == 'undefined') {
c = {
error : 'JSON Parse error.'
};
}

if (c.error)
ecb.call(o.error_scope || o.scope, c.error, x);
else
scb.call(o.success_scope || o.scope, c.result);
};

o.error = function(ty, x) {
if (ecb)
ecb.call(o.error_scope || o.scope, ty, x);
};

o.data = JSON.serialize({
id : o.id || 'c' + (this.count++),
method : o.method,
params : o.params
});

// JSON content type for Ruby on rails. Bug: #1883287


o.content_type = 'application/json';

XHR.send(o);
},

'static' : {
sendRPC : function(o) {
return new tinymce.util.JSONRequest().send(o);
}
}
});
}());
(function(tinymce){
tinymce.VK = {
BACKSPACE: 8,
DELETE: 46,
DOWN: 40,
ENTER: 13,
LEFT: 37,
RIGHT: 39,
SPACEBAR: 32,
TAB: 9,
UP: 38,

modifierPressed: function (e) {


return e.shiftKey || e.ctrlKey || e.altKey;
},

metaKeyPressed: function(e) {
// Check if ctrl or meta key is pressed also check if alt is
false for Polish users
return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey;
}
};
})(tinymce);

tinymce.util.Quirks = function(editor) {
var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom =
editor.dom, selection = editor.selection,
settings = editor.settings, parser = editor.parser, serializer =
editor.serializer, each = tinymce.each;

function setEditorCommandState(cmd, state) {


try {
editor.getDoc().execCommand(cmd, false, state);
} catch (ex) {
// Ignore
}
}

function getDocumentMode() {
var documentMode = editor.getDoc().documentMode;

return documentMode ? documentMode : 6;


};

function isDefaultPrevented(e) {
return e.isDefaultPrevented();
};

// SGY-5281 patched
// @see
https://github.com/tinymce/tinymce/commit/113bd1ceaef1e1de5f2fdf54633d58424817182c
function cleanupStylesWhenDeleting() {
function removeMergedFormatSpans(isDelete) {
var rng, blockElm, wrapperElm, bookmark, container, offset;

function isAtStartOrEndOfElm() {
if (container.nodeType == 3) {
if (isDelete && offset == container.length) {
return true;
}

if (!isDelete && offset === 0) {


return true;
}
}
}

rng = selection.getRng();
container = rng[(isDelete ? 'start' : 'end') + 'Container'];
offset = rng[(isDelete ? 'start' : 'end') + 'Offset'];

if (container.nodeType == 3) {
blockElm = dom.getParent(rng.startContainer, dom.isBlock);

// On delete clone the root span of the next block element


if (isDelete) {
blockElm = dom.getNext(blockElm, dom.isBlock);
}

if (blockElm && isAtStartOrEndOfElm()) {


// Wrap children of block in a EM and let WebKit stick is
// runtime styles junk into that EM
wrapperElm = dom.create('em', {'id': '__mceDel'});

each(tinymce.grep(blockElm.childNodes), function(node) {
wrapperElm.appendChild(node);
});

blockElm.appendChild(wrapperElm);
}
}
// Do the backspace/delete action
editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false,
null);

// Remove temp wrapper element


if (wrapperElm) {
bookmark = selection.getBookmark();
dom.remove(dom.get('__mceDel'), true);
selection.moveToBookmark(bookmark);
}
}

editor.onKeyDown.add(function(editor, e) {
var isDelete;

isDelete = e.keyCode == DELETE;


if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !
VK.modifierPressed(e)) {
e.preventDefault();
removeMergedFormatSpans(isDelete);
}
});

editor.addCommand('Delete', function() {removeMergedFormatSpans();});


};

function emptyEditorWhenDeleting() {
function serializeRng(rng) {
var body = dom.create("body");
var contents = rng.cloneContents();
body.appendChild(contents);
return selection.serializer.serialize(body, {format: 'html'});
}

function allContentsSelected(rng) {
var selection = serializeRng(rng);

var allRng = dom.createRng();


allRng.selectNode(editor.getBody());

var allSelection = serializeRng(allRng);


return selection === allSelection;
}

editor.onKeyDown.add(function(editor, e) {
var keyCode = e.keyCode, isCollapsed;

// Empty the editor if it's needed for example backspace at


<p><b>|</b></p>
if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode ==
BACKSPACE)) {
isCollapsed = editor.selection.isCollapsed();

// Selection is collapsed but the editor isn't empty


if (isCollapsed && !dom.isEmpty(editor.getBody())) {
return;
}

// IE deletes all contents correctly when everything is


selected
if (tinymce.isIE && !isCollapsed) {
return;
}

// Selection isn't collapsed but not all the contents is


selected
if (!isCollapsed && !
allContentsSelected(editor.selection.getRng())) {
return;
}

// Manually empty the editor


editor.setContent('');
editor.selection.setCursorLocation(editor.getBody(), 0);
editor.nodeChanged();
}
});
};

function selectAll() {
editor.onKeyDown.add(function(editor, e) {
if (!isDefaultPrevented(e) && e.keyCode == 65 &&
VK.metaKeyPressed(e)) {
e.preventDefault();
editor.execCommand('SelectAll');
}
});
};

function inputMethodFocus() {
if (!editor.settings.content_editable) {
// Case 1 IME doesn't initialize if you focus the document
dom.bind(editor.getDoc(), 'focusin', function(e) {
selection.setRng(selection.getRng());
});

// Case 2 IME doesn't initialize if you click the documentElement


it also doesn't properly fire the focusin event
dom.bind(editor.getDoc(), 'mousedown', function(e) {
if (e.target == editor.getDoc().documentElement) {
editor.getWin().focus();
selection.setRng(selection.getRng());
}
});
}
};

function removeHrOnBackspace() {
editor.onKeyDown.add(function(editor, e) {
if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
if (selection.isCollapsed() &&
selection.getRng(true).startOffset === 0) {
var node = selection.getNode();
var previousSibling = node.previousSibling;

if (previousSibling && previousSibling.nodeName &&


previousSibling.nodeName.toLowerCase() === "hr") {
dom.remove(previousSibling);
tinymce.dom.Event.cancel(e);
}
}
}
})
}

function focusBody() {
// Fix for a focus bug in FF 3.x where the body element
// wouldn't get proper focus if the user clicked on the HTML element
if (!Range.prototype.getClientRects) { // Detect getClientRects got
introduced in FF 4
editor.onMouseDown.add(function(editor, e) {
if (!isDefaultPrevented(e) && e.target.nodeName === "HTML")
{
var body = editor.getBody();

// Blur the body it's focused but not correctly


focused
body.blur();

// Refocus the body after a little while


setTimeout(function() {
body.focus();
}, 0);
}
});
}
};

function selectControlElements() {
editor.onClick.add(function(editor, e) {
e = e.target;

if (/^(IMG|HR)$/.test(e.nodeName) || e.nodeName == 'A' &&


dom.hasClass(e, 'mceItemAnchor')) {
selection.select(e);
}

editor.nodeChanged();
});
};

function removeStylesWhenDeletingAccrossBlockElements() {
function getAttributeApplyFunction() {
var template =
dom.getAttribs(selection.getStart().cloneNode(false));

return function() {
var target = selection.getStart();

if (target !== editor.getBody()) {


dom.setAttrib(target, "style", null);

each(template, function(attr) {
target.setAttributeNode(attr.cloneNode(true));
});
}
};
}

function isSelectionAcrossElements() {
return !selection.isCollapsed() &&
dom.getParent(selection.getStart(), dom.isBlock) !=
dom.getParent(selection.getEnd(), dom.isBlock);
}

function blockEvent(editor, e) {
e.preventDefault();
return false;
}

editor.onKeyPress.add(function(editor, e) {
var applyAttributes;

if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46)


&& isSelectionAcrossElements()) {
applyAttributes = getAttributeApplyFunction();
editor.getDoc().execCommand('delete', false, null);
applyAttributes();
e.preventDefault();
return false;
}
});

dom.bind(editor.getDoc(), 'cut', function(e) {


var applyAttributes;

if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {


applyAttributes = getAttributeApplyFunction();
editor.onKeyUp.addToTop(blockEvent);

setTimeout(function() {
applyAttributes();
editor.onKeyUp.remove(blockEvent);
}, 0);
}
});
}

function selectionChangeNodeChanged() {
var lastRng, selectionTimer;

dom.bind(editor.getDoc(), 'selectionchange', function() {


if (selectionTimer) {
clearTimeout(selectionTimer);
selectionTimer = 0;
}

selectionTimer = window.setTimeout(function() {
var rng = selection.getRng();

// Compare the ranges to see if it was a real change or not


if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng,
lastRng)) {
editor.nodeChanged();
lastRng = rng;
}
}, 50);
});
}

function ensureBodyHasRoleApplication() {
document.body.setAttribute("role", "application");
}

function disableBackspaceIntoATable() {
editor.onKeyDown.add(function(editor, e) {
if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
if (selection.isCollapsed() &&
selection.getRng(true).startOffset === 0) {
var previousSibling =
selection.getNode().previousSibling;
if (previousSibling && previousSibling.nodeName &&
previousSibling.nodeName.toLowerCase() === "table") {
return tinymce.dom.Event.cancel(e);
}
}
}
})
}

function addNewLinesBeforeBrInPre() {
// IE8+ rendering mode does the right thing with BR in PRE
if (getDocumentMode() > 7) {
return;
}

// Enable display: none in area and add a specific class that hides
all BR elements in PRE to
// avoid the caret from getting stuck at the BR elements while
pressing the right arrow key
setEditorCommandState('RespectVisibilityInDesign', true);
editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
dom.addClass(editor.getBody(), 'mceHideBrInPre');

// Adds a \n before all BR elements in PRE to get them visual


parser.addNodeFilter('pre', function(nodes, name) {
var i = nodes.length, brNodes, j, brElm, sibling;

while (i--) {
brNodes = nodes[i].getAll('br');
j = brNodes.length;
while (j--) {
brElm = brNodes[j];

// Add \n before BR in PRE elements on older IE:s so


the new lines get rendered
sibling = brElm.prev;
if (sibling && sibling.type === 3 &&
sibling.value.charAt(sibling.value - 1) != '\n') {
sibling.value += '\n';
} else {
brElm.parent.insert(new
tinymce.html.Node('#text', 3), brElm, true).value = '\n';
}
}
}
});

// Removes any \n before BR elements in PRE since other browsers and in


contentEditable=false mode they will be visible
serializer.addNodeFilter('pre', function(nodes, name) {
var i = nodes.length, brNodes, j, brElm, sibling;

while (i--) {
brNodes = nodes[i].getAll('br');
j = brNodes.length;
while (j--) {
brElm = brNodes[j];
sibling = brElm.prev;
if (sibling && sibling.type == 3) {
sibling.value = sibling.value.replace(/\r?\n$/,
'');
}
}
}
});
}

function removePreSerializedStylesWhenSelectingControls() {
dom.bind(editor.getBody(), 'mouseup', function(e) {
var value, node = selection.getNode();

// Moved styles to attributes on IMG eements


if (node.nodeName == 'IMG') {
// Convert style width to width attribute
if (value = dom.getStyle(node, 'width')) {
dom.setAttrib(node, 'width', value.replace(/[^0-9%]
+/g, ''));
dom.setStyle(node, 'width', '');
}

// Convert style height to height attribute


if (value = dom.getStyle(node, 'height')) {
dom.setAttrib(node, 'height', value.replace(/[^0-9%]
+/g, ''));
dom.setStyle(node, 'height', '');
}
}
});
}

function keepInlineElementOnDeleteBackspace() {
editor.onKeyDown.add(function(editor, e) {
var isDelete, rng, container, offset, brElm, sibling, collapsed;

isDelete = e.keyCode == DELETE;


if (!isDefaultPrevented(e) && (isDelete || e.keyCode ==
BACKSPACE) && !VK.modifierPressed(e)) {
rng = selection.getRng();
container = rng.startContainer;
offset = rng.startOffset;
collapsed = rng.collapsed;

// Override delete if the start container is a text node


and is at the beginning of text or
// just before/after the last character to be deleted in
collapsed mode
if (container.nodeType == 3 && container.nodeValue.length >
0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 :
1)))) {
nonEmptyElements =
editor.schema.getNonEmptyElements();

// Prevent default logic since it's broken


e.preventDefault();

// Insert a BR before the text node this will prevent


the containing element from being deleted/converted
brElm = dom.create('br', {id: '__tmp'});
container.parentNode.insertBefore(brElm, container);

// Do the browser delete


editor.getDoc().execCommand(isDelete ?
'ForwardDelete' : 'Delete', false, null);

// Check if the previous sibling is empty after


deleting for example: <p><b></b>|</p>
container = selection.getRng().startContainer;
sibling = container.previousSibling;
if (sibling && sibling.nodeType == 1 && !
dom.isBlock(sibling) && dom.isEmpty(sibling) && !
nonEmptyElements[sibling.nodeName.toLowerCase()]) {
dom.remove(sibling);
}

// Remove the temp element we inserted


dom.remove('__tmp');
}
}
});
}

function removeBlockQuoteOnBackSpace() {
// Add block quote deletion handler
editor.onKeyDown.add(function(editor, e) {
var rng, container, offset, root, parent;

if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {


return;
}

rng = selection.getRng();
container = rng.startContainer;
offset = rng.startOffset;
root = dom.getRoot();
parent = container;

if (!rng.collapsed || offset !== 0) {


return;
}

while (parent && parent.parentNode &&


parent.parentNode.firstChild == parent && parent.parentNode != root) {
parent = parent.parentNode;
}

// Is the cursor at the beginning of a blockquote?


if (parent.tagName === 'BLOCKQUOTE') {
// Remove the blockquote
editor.formatter.toggle('blockquote', null, parent);

// Move the caret to the beginning of container


rng = dom.createRng();
rng.setStart(container, 0);
rng.setEnd(container, 0);
selection.setRng(rng);
}
});
};

function setGeckoEditingOptions() {
function setOpts() {
editor._refreshContentEditable();

setEditorCommandState("StyleWithCSS", false);
setEditorCommandState("enableInlineTableEditing", false);

if (!settings.object_resizing) {
setEditorCommandState("enableObjectResizing", false);
}
};

if (!settings.readonly) {
editor.onBeforeExecCommand.add(setOpts);
editor.onMouseDown.add(setOpts);
}
};

function addBrAfterLastLinks() {
function fixLinks(editor, o) {
each(dom.select('a'), function(node) {
var parentNode = node.parentNode, root = dom.getRoot();

if (parentNode.lastChild === node) {


while (parentNode && !dom.isBlock(parentNode)) {
if (parentNode.parentNode.lastChild !==
parentNode || parentNode === root) {
return;
}

parentNode = parentNode.parentNode;
}

dom.add(parentNode, 'br', {'data-mce-bogus' : 1});


}
});
};

editor.onExecCommand.add(function(editor, cmd) {
if (cmd === 'CreateLink') {
fixLinks(editor);
}
});

editor.onSetContent.add(selection.onSetContent.add(fixLinks));
};

function setDefaultBlockType() {
if (settings.forced_root_block) {
editor.onInit.add(function() {
setEditorCommandState('DefaultParagraphSeparator',
settings.forced_root_block);
});
}
}

function removeGhostSelection() {
function repaint(sender, args) {
if (!sender || !args.initial) {
editor.execCommand('mceRepaint');
}
};

editor.onUndo.add(repaint);
editor.onRedo.add(repaint);
editor.onSetContent.add(repaint);
};

function deleteControlItemOnBackSpace() {
editor.onKeyDown.add(function(editor, e) {
var rng;

if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {


rng = editor.getDoc().selection.createRange();
if (rng && rng.item) {
e.preventDefault();
editor.undoManager.beforeChange();
dom.remove(rng.item(0));
editor.undoManager.add();
}
}
});
};

function renderEmptyBlocksFix() {
var emptyBlocksCSS;

// IE10+
if (getDocumentMode() >= 10) {
emptyBlocksCSS = '';
each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
});

editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !


important}');
}
};

function fakeImageResize() {
var selectedElmX, selectedElmY, selectedElm, selectedElmGhost,
selectedHandle, startX, startY, startW, startH, ratio,
resizeHandles, width, height, rootDocument = document,
editableDoc = editor.getDoc();

if (!settings.object_resizing || settings.webkit_fake_resize === false)


{
return;
}

// Try disabling object resizing if WebKit implements resizing in the


future
setEditorCommandState("enableObjectResizing", false);

// Details about each resize handle how to scale etc


resizeHandles = {
// Name: x multiplier, y multiplier, delta size x, delta size y
n: [.5, 0, 0, -1],
e: [1, .5, 1, 0],
s: [.5, 1, 0, 1],
w: [0, .5, -1, 0],
nw: [0, 0, -1, -1],
ne: [1, 0, 1, -1],
se: [1, 1, 1, 1],
sw : [0, 1, -1, 1]
};

function resizeElement(e) {
var deltaX, deltaY;

// Calc new width/height


deltaX = e.screenX - startX;
deltaY = e.screenY - startY;

// Calc new size


width = deltaX * selectedHandle[2] + startW;
height = deltaY * selectedHandle[3] + startH;

// Never scale down lower than 5 pixels


width = width < 5 ? 5 : width;
height = height < 5 ? 5 : height;

// Constrain proportions when modifier key is pressed or if the


nw, ne, sw, se corners are moved on an image
if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" &&
selectedHandle[2] * selectedHandle[3] !== 0)) {
width = Math.round(height / ratio);
height = Math.round(width * ratio);
}

// Update ghost size


dom.setStyles(selectedElmGhost, {
width: width,
height: height
});

// Update ghost X position if needed


if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <=
width) {
dom.setStyle(selectedElmGhost, 'left', selectedElmX +
(startW - width));
}

// Update ghost Y position if needed


if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <=
height) {
dom.setStyle(selectedElmGhost, 'top', selectedElmY +
(startH - height));
}
}

function endResize() {
function setSizeProp(name, value) {
if (value) {
// Resize by using style or attribute
if (selectedElm.style[name] || !
editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
dom.setStyle(selectedElm, name, value);
} else {
dom.setAttrib(selectedElm, name, value);
}
}
}

// Set width/height properties


setSizeProp('width', width);
setSizeProp('height', height);

dom.unbind(editableDoc, 'mousemove', resizeElement);


dom.unbind(editableDoc, 'mouseup', endResize);

if (rootDocument != editableDoc) {
dom.unbind(rootDocument, 'mousemove', resizeElement);
dom.unbind(rootDocument, 'mouseup', endResize);
}

// Remove ghost and update resize handle positions


dom.remove(selectedElmGhost);
showResizeRect(selectedElm);
}

function showResizeRect(targetElm) {
var position, targetWidth, targetHeight;

hideResizeRect();

// Get position and size of target


position = dom.getPos(targetElm);
selectedElmX = position.x;
selectedElmY = position.y;
targetWidth = targetElm.offsetWidth;
targetHeight = targetElm.offsetHeight;

// Reset width/height if user selects a new image/table


if (selectedElm != targetElm) {
selectedElm = targetElm;
width = height = 0;
}
each(resizeHandles, function(handle, name) {
var handleElm;

// Get existing or render resize handle


handleElm = dom.get('mceResizeHandle' + name);
if (!handleElm) {
handleElm = dom.add(editableDoc.documentElement,
'div', {
id: 'mceResizeHandle' + name,
'class': 'mceResizeHandle',
style: 'cursor:' + name + '-resize; margin:0;
padding:0'
});

dom.bind(handleElm, 'mousedown', function(e) {


e.preventDefault();

endResize();

startX = e.screenX;
startY = e.screenY;
startW = selectedElm.clientWidth;
startH = selectedElm.clientHeight;
ratio = startH / startW;
selectedHandle = handle;

selectedElmGhost = selectedElm.cloneNode(true);
dom.addClass(selectedElmGhost,
'mceClonedResizable');
dom.setStyles(selectedElmGhost, {
left: selectedElmX,
top: selectedElmY,
margin: 0
});

editableDoc.documentElement.appendChild(selectedElmGhost);

dom.bind(editableDoc, 'mousemove',
resizeElement);
dom.bind(editableDoc, 'mouseup', endResize);

if (rootDocument != editableDoc) {
dom.bind(rootDocument, 'mousemove',
resizeElement);
dom.bind(rootDocument, 'mouseup',
endResize);
}
});
} else {
dom.show(handleElm);
}

// Position element
dom.setStyles(handleElm, {
left: (targetWidth * handle[0] + selectedElmX) -
(handleElm.offsetWidth / 2),
top: (targetHeight * handle[1] + selectedElmY) -
(handleElm.offsetHeight / 2)
});
});

// Only add resize rectangle on WebKit and only on images


if (!tinymce.isOpera && selectedElm.nodeName == "IMG") {
selectedElm.setAttribute('data-mce-selected', '1');
}
}

function hideResizeRect() {
if (selectedElm) {
selectedElm.removeAttribute('data-mce-selected');
}

for (var name in resizeHandles) {


dom.hide('mceResizeHandle' + name);
}
}

// Add CSS for resize handles, cloned element and selected


editor.contentStyles.push(
'.mceResizeHandle {' +
'position: absolute;' +
'border: 1px solid black;' +
'background: #FFF;' +
'width: 5px;' +
'height: 5px;' +
'z-index: 10000' +
'}' +
'.mceResizeHandle:hover {' +
'background: #000' +
'}' +
'img[data-mce-selected] {' +
'outline: 1px solid black' +
'}' +
'img.mceClonedResizable, table.mceClonedResizable {' +
'position: absolute;' +
'outline: 1px dashed black;' +
'opacity: .5;' +
'z-index: 10000' +
'}'
);

function updateResizeRect() {
var controlElm = dom.getParent(selection.getNode(), 'table,img');

// Remove data-mce-selected from all elements since they might


have been copied using Ctrl+c/v
each(dom.select('img[data-mce-selected]'), function(img) {
img.removeAttribute('data-mce-selected');
});

if (controlElm) {
showResizeRect(controlElm);
} else {
hideResizeRect();
}
}
// Show/hide resize rect when image is selected
editor.onNodeChange.add(updateResizeRect);

// Fixes WebKit quirk where it returns IMG on getNode if caret is after


last image in container
dom.bind(editableDoc, 'selectionchange', updateResizeRect);

// Remove the internal attribute when serializing the DOM


editor.serializer.addAttributeFilter('data-mce-selected',
function(nodes, name) {
var i = nodes.length;

while (i--) {
nodes[i].attr(name, null);
}
});
}

function keepNoScriptContents() {
if (getDocumentMode() < 9) {
parser.addNodeFilter('noscript', function(nodes) {
var i = nodes.length, node, textNode;

while (i--) {
node = nodes[i];
textNode = node.firstChild;

if (textNode) {
node.attr('data-mce-innertext',
textNode.value);
}
}
});

serializer.addNodeFilter('noscript', function(nodes) {
var i = nodes.length, node, textNode, value;

while (i--) {
node = nodes[i];
textNode = nodes[i].firstChild;

if (textNode) {
textNode.value =
tinymce.html.Entities.decode(textNode.value);
} else {
// Old IE can't retain noscript value so an
attribute is used to store it
value = node.attributes.map['data-mce-
innertext'];
if (value) {
node.attr('data-mce-innertext', null);
textNode = new tinymce.html.Node('#text',
3);
textNode.value = value;
textNode.raw = true;
node.append(textNode);
}
}
}
});
}
}

// All browsers
disableBackspaceIntoATable();
removeBlockQuoteOnBackSpace();
emptyEditorWhenDeleting();

// WebKit
if (tinymce.isWebKit) {
keepInlineElementOnDeleteBackspace();
cleanupStylesWhenDeleting();
inputMethodFocus();
selectControlElements();
setDefaultBlockType();

// iOS
if (tinymce.isIDevice) {
selectionChangeNodeChanged();
} else {
fakeImageResize();
selectAll();
}
}

// IE
if (tinymce.isIE) {
removeHrOnBackspace();
ensureBodyHasRoleApplication();
addNewLinesBeforeBrInPre();
removePreSerializedStylesWhenSelectingControls();
deleteControlItemOnBackSpace();
renderEmptyBlocksFix();
keepNoScriptContents();
}

// Gecko
if (tinymce.isGecko) {
removeHrOnBackspace();
focusBody();
removeStylesWhenDeletingAccrossBlockElements();
setGeckoEditingOptions();
addBrAfterLastLinks();
removeGhostSelection();
}

// Opera
if (tinymce.isOpera) {
fakeImageResize();
}
};
(function(tinymce) {
var namedEntities, baseEntities, reverseEntities,
attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF]
[\uDC00-\uDFFF]/g,
textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF]
[\uDC00-\uDFFF]/g,
rawCharsRegExp = /[<>&\"\']/g,
entityRegExp = /&(#x|#)?([\w]+);/g,
asciiMap = {
128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 :
"\u201E", 133 : "\u2026", 134 : "\u2020",
135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 :
"\u0160", 139 : "\u2039", 140 : "\u0152",
142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 :
"\u201C", 148 : "\u201D", 149 : "\u2022",
150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 :
"\u2122", 154 : "\u0161", 155 : "\u203A",
156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
};

// Raw entities
baseEntities = {
'\"' : '&quot;', // Needs to be escaped since the YUI compressor would
otherwise break the code
"'" : '&#39;',
'<' : '&lt;',
'>' : '&gt;',
'&' : '&amp;'
};

// Reverse lookup table for raw entities


reverseEntities = {
'&lt;' : '<',
'&gt;' : '>',
'&amp;' : '&',
'&quot;' : '"',
'&apos;' : "'"
};

// Decodes text by using the browser


function nativeDecode(text) {
var elm;

elm = document.createElement("div");
elm.innerHTML = text;

return elm.textContent || elm.innerText || text;


};

// Build a two way lookup table for the entities


function buildEntitiesLookup(items, radix) {
var i, chr, entity, lookup = {};

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;
}
}

return lookup;
}
};

// 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,5
8,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,A
Elig,67,Ccedil,' +
'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,I
uml,6g,ETH,6h,Ntilde,' +
'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,U
grave,6q,Uacute,' +
'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,ac
irc,73,atilde,74,auml,' +
'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,ig
rave,7d,iacute,7e,icirc,' +
'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,oum
l,7n,divide,7o,oslash,' +
'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fno
f,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,8
12,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,uA
rr,8ei,rArr,8ej,dArr,' +
'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g
9,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,lcei
l,8o9,rceil,8oa,lfloor,8ob,' +
'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,d
iams,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,80
t,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;
});
},

encodeAllRaw : function(text) {
return ('' + text).replace(rawCharsRegExp, function(chr) {
return baseEntities[chr] || chr;
});
},

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 baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';


});
},

encodeNamed : function(text, attr, entities) {


entities = entities || namedEntities;

return text.replace(attr ? attrsCharsRegExp : textCharsRegExp,


function(chr) {
return baseEntities[chr] || entities[chr] || chr;
});
},

getEncodeFunc : function(name, entities) {


var Entities = tinymce.html.Entities;

entities = buildEntitiesLookup(entities) || namedEntities;

function encodeNamedAndNumeric(text, attr) {


return text.replace(attr ? attrsCharsRegExp :
textCharsRegExp, function(chr) {
return baseEntities[chr] || entities[chr] || '&#' +
chr.charCodeAt(0) + ';' || chr;
});
};

function encodeCustomNamed(text, attr) {


return Entities.encodeNamed(text, attr, entities);
};

// Replace + with , to be compatible with previous TinyMCE


versions
name = tinymce.makeMap(name.replace(/\+/g, ','));
// Named and numeric encoder
if (name.named && name.numeric)
return encodeNamedAndNumeric;

// Named encoder
if (name.named) {
// Custom names
if (entities)
return encodeCustomNamed;

return Entities.encodeNamed;
}

// Numeric
if (name.numeric)
return Entities.encodeNumeric;

// Raw encoder
return Entities.encodeRaw;
},

decode : function(text) {
return text.replace(entityRegExp, function(all, numeric, value) {
if (numeric) {
value = parseInt(value, numeric.length === 2 ? 16 :
10);

// Support upper UTF


if (value > 0xFFFF) {
value -= 0x10000;

return String.fromCharCode(0xD800 + (value >>


10), 0xDC00 + (value & 0x3FF));
} else
return asciiMap[value] ||
String.fromCharCode(value);
}

return reverseEntities[all] || namedEntities[all] ||


nativeDecode(all);
});
}
};
})(tinymce);

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;

settings = settings || {};

encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');


for (i = 0; i < encodingItems.length; i++) {
encodingLookup[encodingItems[i]] = '\uFEFF' + i;
encodingLookup['\uFEFF' + i] = encodingItems[i];
}

function toHex(match, r, g, b) {
function hex(val) {
val = parseInt(val).toString(16);

return val.length > 1 ? val : '0' + val; // 0 -> 00


};

return '#' + hex(r) + hex(g) + hex(b);


};

return {
toHex : function(color) {
return color.replace(rgbRegExp, toHex);
},

parse : function(css) {
var styles = {}, matches, name, value, isEncoded, urlConverter =
settings.url_converter, urlConverterScope = settings.url_converter_scope || this;

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;

right = styles[prefix + '-right' + suffix];


if (top != right)
return;

bottom = styles[prefix + '-bottom' + suffix];


if (right != bottom)
return;

left = styles[prefix + '-left' + suffix];


if (bottom != left)
return;

// 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];
};

function canCompress(key) {
var value = styles[key], i;

if (!value || value.indexOf(' ') < 0)


return;

value = value.split(' ');


i = value.length;
while (i--) {
if (value[i] !== value[0])
return false;
}

styles[key] = value[0];

return true;
};

function compress2(target, a, b, c) {
if (!canCompress(a))
return;

if (!canCompress(b))
return;

if (!canCompress(c))
return;

// Compress
styles[target] = styles[a] + ' ' + styles[b] + ' ' +
styles[c];
delete styles[a];
delete styles[b];
delete styles[c];
};

// Encodes the specified string by replacing all \" \' ; : with


_<num>
function encode(str) {
isEncoded = true;

return encodingLookup[str];
};

// Decodes the specified string by replacing all _<num> with it's


original value \" \' etc
// It will also decode the \" \' if keep_slashes is set to fale
or omitted
function decode(str, keep_slashes) {
if (isEncoded) {
str = str.replace(/\uFEFF[0-9]/g, function(str) {
return encodingLookup[str];
});
}

if (!keep_slashes)
str = str.replace(/\\([\'\";:])/g, "$1");

return str;
};

function processUrl(match, url, url2, url3, str, str2) {


str = str || str2;

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, "\\'") + "')";
};

if (css) {
// Encode \" \' % and ; and : inside strings so they don't
interfere with the style parsing
css = css.replace(/\\[\"\';:\uFEFF]/g,
encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
return str.replace(/[;:]/g, encode);
});

// Parse styles
while (matches = styleRegExp.exec(css)) {
name = matches[1].replace(trimRightRegExp,
'').toLowerCase();
value = matches[2].replace(trimRightRegExp, '');

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();

// Convert RGB colors to HEX


value = value.replace(rgbRegExp, toHex);

// Convert URLs and force them into


url('value') format
value = value.replace(urlOrStrRegExp,
processUrl);
styles[name] = isEncoded ? decode(value,
true) : value;
}

styleRegExp.lastIndex = matches.index +
matches[0].length;
}

// Compress the styles to reduce it's size for example IE


will expand styles
compress("border", "");
compress("border", "-width");
compress("border", "-color");
compress("border", "-style");
compress("padding", "");
compress("margin", "");
compress2('border', 'border-width', 'border-style',
'border-color');

// Remove pointless border, IE produces these


if (styles.border === 'medium none')
delete styles.border;
}

return styles;
},

serialize : function(styles, element_name) {


var css = '', name, value;

function serializeStyles(name) {
var styleList, i, l, value;

styleList = schema.styles[name];
if (styleList) {
for (i = 0, l = styleList.length; i < l; i++) {
name = styleList[i];
value = styles[name];

if (value !== undef && value.length > 0)


css += (css.length > 0 ? ' ' : '') + name
+ ': ' + value + ';';
}
}
};

// Serialize styles according to schema


if (element_name && schema && schema.styles) {
// Serialize global styles and element specific styles
serializeStyles('*');
serializeStyles(element_name);
} else {
// Output the styles in the order they are inside the
object
for (name in styles) {
value = styles[name];

if (value !== undef && value.length > 0)


css += (css.length > 0 ? ' ' : '') + name + ':
' + value + ';';
}
}

return css;
}
};
};

(function(tinymce) {
var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;

function split(str, delim) {


return str.split(delim || ',');
};
function unpack(lookup, data) {
var key, elements = {};

function replace(value) {
return value.replace(/[A-Z]+/g, function(key) {
return replace(lookup[key]);
});
};

// Unpack lookup
for (key in lookup) {
if (lookup.hasOwnProperty(key))
lookup[key] = replace(lookup[key]);
}

// Unpack and parse data into object map


replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\
[([^\]]*)\]/g, function(str, name, attributes, children) {
attributes = split(attributes, '|');

elements[name] = {
attributes : makeMap(attributes),
attributesOrder : attributes,
children : makeMap(children, '|', {'#comment' : {}})
}
});

return elements;
};

function getHTML5() {
var html5 = mapCache.html5;

if (!html5) {
html5 = mapCache.html5 = unpack({
A : 'id|accesskey|class|dir|draggable|item|hidden|
itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|
onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|
code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|
link|map|mark|meta|' +
'meter|noscript|object|output|progress|q|ruby|
samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
C : '#|a|abbr|area|address|article|aside|audio|b|bdo|
blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|
dl|em|embed|fieldset|' +
'figure|footer|form|h1|h2|h3|h4|h5|h6|header|
hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|
nav|noscript|ol|object|output|' +
'p|pre|progress|q|ruby|samp|script|section|
select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
}, 'html[A|manifest][body|head]' +
'head[A][base|command|link|meta|noscript|script|
style|title]' +
'title[A][#]' +
'base[A|href|target][]' +
'link[A|href|rel|media|type|sizes][]' +
'meta[A|http-equiv|name|content|charset][]' +
'style[A|type|media|scoped][#]' +
'script[A|charset|type|src|defer|async][#]' +
'noscript[A][C]' +
'body[A][C]' +
'section[A][C]' +
'nav[A][C]' +
'article[A][C]' +
'aside[A][C]' +
'h1[A][B]' +
'h2[A][B]' +
'h3[A][B]' +
'h4[A][B]' +
'h5[A][B]' +
'h6[A][B]' +
'hgroup[A][h1|h2|h3|h4|h5|h6]' +
'header[A][C]' +
'footer[A][C]' +
'address[A][C]' +
'p[A][B]' +
'br[A][]' +
'pre[A][B]' +
'dialog[A][dd|dt]' +
'blockquote[A|cite][C]' +
'ol[A|start|reversed][li]' +
'ul[A][li]' +
'li[A|value][C]' +
'dl[A][dd|dt]' +
'dt[A][B]' +
'dd[A][C]' +
'a[A|href|target|ping|rel|media|type][B]' +
'em[A][B]' +
'strong[A][B]' +
'small[A][B]' +
'cite[A][B]' +
'q[A|cite][B]' +
'dfn[A][B]' +
'abbr[A][B]' +
'code[A][B]' +
'var[A][B]' +
'samp[A][B]' +
'kbd[A][B]' +
'sub[A][B]' +
'sup[A][B]' +
'i[A][B]' +
'b[A][B]' +
'mark[A][B]' +
'progress[A|value|max][B]' +
'meter[A|value|min|max|low|high|optimum][B]' +
'time[A|datetime][B]' +
'ruby[A][B|rt|rp]' +
'rt[A][B]' +
'rp[A][B]' +
'bdo[A][B]' +
'span[A][B]' +
'ins[A|cite|datetime][B]' +
'del[A|cite|datetime][B]' +
'figure[A][C|legend|figcaption]' +
'figcaption[A][C]' +
'img[A|alt|src|height|width|usemap|ismap][]' +
'iframe[A|name|src|height|width|sandbox|seamless][]'
+
'embed[A|src|height|width|type][]' +
'object[A|data|type|height|width|usemap|name|form|
classid][param]' +
'param[A|name|value][]' +
'details[A|open][C|legend]' +
'command[A|type|label|icon|disabled|checked|
radiogroup][]' +
'menu[A|type|label][C|li]' +
'legend[A][C|B]' +
'div[A][C]' +
'source[A|src|type|media][]' +
'audio[A|src|autobuffer|autoplay|loop|controls]
[source]' +
'video[A|src|autobuffer|autoplay|loop|controls|width|
height|poster][source]' +
'hr[A][]' +
'form[A|accept-charset|action|autocomplete|enctype|
method|name|novalidate|target][C]' +
'fieldset[A|disabled|form|name][C|legend]' +
'label[A|form|for][B]' +
'input[A|type|accept|alt|autocomplete|autofocus|
checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|
height|list|max|maxlength|min|' +
'multiple|pattern|placeholder|readonly|
required|size|src|step|width|files|value|name][]' +
'button[A|autofocus|disabled|form|formaction|
formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
'select[A|autofocus|disabled|form|multiple|name|size]
[option|optgroup]' +
'datalist[A][B|option]' +
'optgroup[A|disabled|label][option]' +
'option[A|disabled|selected|label|value][]' +
'textarea[A|autofocus|disabled|form|maxlength|name|
placeholder|readonly|required|rows|cols|wrap][]' +
'keygen[A|autofocus|challenge|disabled|form|keytype|
name][]' +
'output[A|for|form|name][B]' +
'canvas[A|width|height][]' +
'map[A|name][B|C]' +
'area[A|shape|coords|href|alt|target|media|rel|ping|
type][]' +
'mathml[A][]' +
'svg[A][]' +
'table[A|border][caption|colgroup|thead|tfoot|tbody|
tr]' +
'caption[A][C]' +
'colgroup[A|span][col]' +
'col[A|span][]' +
'thead[A][tr]' +
'tfoot[A][tr]' +
'tbody[A][tr]' +
'tr[A][th|td]' +
'th[A|headers|rowspan|colspan|scope][B]' +
'td[A|headers|rowspan|colspan][C]' +
'wbr[A][]'
);
}
return html5;
};

function getHTML4() {
var html4 = mapCache.html4;

if (!html4) {
// This is the XHTML 1.0 transitional elements with it's
attributes and children packed to reduce it's size
html4 = mapCache.html4 = 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|allowfullscreen][#|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]'
);
}

return html4;
};

tinymce.html.Schema = function(settings) {
var self = this, elements = {}, children = {}, patternElements = [],
validStyles, schemaItems;
var whiteSpaceElementsMap, selfClosingElementsMap,
shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap,
customElementsMap = {};

// Creates an lookup table map object for the specified option or the
default value
function createLookupTable(option, default_value, extend) {
var value = settings[option];

if (!value) {
// Get cached default map or make it if needed
value = mapCache[option];

if (!value) {
value = makeMap(default_value, ' ',
makeMap(default_value.toUpperCase(), ' '));
value = tinymce.extend(value, extend);

mapCache[option] = value;
}
} else {
// Create custom map
value = makeMap(value, ',', makeMap(value.toUpperCase(), '
'));
}
return value;
};

settings = settings || {};


schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();

// Allow all elements and attributes if verify_html is set to false


if (settings.verify_html === false)
settings.valid_elements = '*[*]';

// Build styles list


if (settings.valid_styles) {
validStyles = {};

// Convert styles into a rule list


each(settings.valid_styles, function(value, key) {
validStyles[key] = tinymce.explode(value);
});
}

// Setup map objects


whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre
script noscript style textarea');
selfClosingElementsMap = createLookupTable('self_closing_elements',
'colgroup dd dt li option p td tfoot th thead tr');
shortEndedElementsMap = createLookupTable('short_ended_elements', 'area
base basefont br col frame hr img input isindex link meta param embed source wbr');
boolAttrMap = createLookupTable('boolean_attributes', 'checked compact
declare defer disabled ismap multiple nohref noresize noshade nowrap readonly
selected autoplay loop controls');
nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th
iframe video audio object', shortEndedElementsMap);
textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2
h3 h4 h5 h6 p div address pre form ' +
'blockquote center dir fieldset header footer
article section hgroup aside nav figure');
blockElementsMap = createLookupTable('block_elements', 'hr table tbody
thead tfoot ' +
'th tr td li ol ul caption dl dt dd noscript
menu isindex samp option datalist select optgroup', textBlockElementsMap);

// Converts a wildcard expression string to a regexp for example *a


will become /.*a/.
function patternToRegExp(str) {
return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
};

// 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
};

// Padd empty elements prefix


if (prefix === '#')
element.paddEmpty = true;

// Remove empty elements prefix


if (prefix === '-')
element.removeEmpty = true;

// Copy attributes from global rule into


current rule
if (globalAttributes) {
for (key in globalAttributes)
attributes[key] =
globalAttributes[key];

attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
}

// 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;
}

// Denied from global


if (attrType === '-') {
delete
attributes[attrName];

attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);


continue;
}

// Default value
if (prefix) {
// Default value
if (prefix === '=') {

element.attributesDefault = element.attributesDefault || [];

element.attributesDefault.push({name: attrName, value: value});


attr.defaultValue
= value;
}

// 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);

attributes[attrName] =
attr;
}
}
}
}

// Global rule, store away these for later


usage
if (!globalAttributes && elementName == '@') {
globalAttributes = attributes;
globalAttributesOrder = attributesOrder;
}

// Handle substitute elements such as b/strong


if (outputName) {
element.outputName = elementName;
elements[outputName] = element;
}

// Add pattern or exact element


if (hasPatternsRegExp.test(elementName)) {
element.pattern =
patternToRegExp(elementName);
patternElements.push(element);
} else
elements[elementName] = element;
}
}
}
};

function setValidElements(valid_elements) {
elements = {};
patternElements = [];

addValidElements(valid_elements);

each(schemaItems, function(element, name) {


children[name] = element.children;
});
};

// Adds custom non HTML elements to the schema


function addCustomElements(custom_elements) {
var customElementRegExp = /^(~)?(.+)$/;

if (custom_elements) {
each(split(custom_elements), function(rule) {
var matches = customElementRegExp.exec(rule),
inline = matches[1] === '~',
cloneName = inline ? 'span' : 'div',
name = matches[2];

children[name] = children[cloneName];
customElementsMap[name] = cloneName;

// If it's not marked as inline then add it to valid


block elements
if (!inline) {
blockElementsMap[name.toUpperCase()] = {};
blockElementsMap[name] = {};
}

// Add elements clone if needed


if (!elements[name]) {
elements[name] = elements[cloneName];
}

// Add custom elements at span/div positions


each(children, function(element, child) {
if (element[cloneName])
element[name] = element[cloneName];
});
});
}
};

// Adds valid children to the schema object


function addValidChildren(valid_children) {
var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;

if (valid_children) {
each(split(valid_children), function(rule) {
var matches = childRuleRegExp.exec(rule), parent,
prefix;

if (matches) {
prefix = matches[1];

// Add/remove items from default


if (prefix)
parent = children[matches[2]];
else
parent = children[matches[2]] =
{'#comment' : {}};

parent = children[matches[2]];

each(split(matches[3], '|'), function(child) {


if (prefix === '-')
delete parent[child];
else
parent[child] = {};
});
}
});
}
};

function getElementRule(name) {
var element = elements[name], i;

// Exact match found


if (element)
return element;

// No exact match then try the patterns


i = patternElements.length;
while (i--) {
element = patternElements[i];

if (element.pattern.test(name))
return element;
}
};

if (!settings.valid_elements) {
// No valid elements defined then clone the elements from the
schema spec
each(schemaItems, function(element, name) {
elements[name] = {
attributes : element.attributes,
attributesOrder : element.attributesOrder
};

children[name] = element.children;
});

// Switch these on HTML4


if (settings.schema != "html5") {
each(split('strong/b,em/i'), function(item) {
item = split(item, '/');
elements[item[1]].outputName = item[0];
});
}

// Add default alt attribute for images


elements.img.attributesDefault = [{name: 'alt', value: ''}];

// Remove these if they are empty by default

each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'),
function(name) {
if (elements[name]) {
elements[name].removeEmpty = true;
}
});

// 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);

addCustomElements(settings.custom_elements);
addValidChildren(settings.valid_children);
addValidElements(settings.extended_valid_elements);

// Todo: Remove this when we fix list handling to be valid


addValidChildren('+ol[ul|ol],+ul[ul|ol]');

// Delete invalid elements


if (settings.invalid_elements) {
tinymce.each(tinymce.explode(settings.invalid_elements),
function(item) {
if (elements[item])
delete elements[item];
});
}

// If the user didn't allow span only allow internal spans


if (!getElementRule('span'))
addValidElements('span[!data-mce-type|*]');

self.children = children;

self.styles = validStyles;

self.getBoolAttrs = function() {
return boolAttrMap;
};

self.getBlockElements = function() {
return blockElementsMap;
};

self.getTextBlockElements = function() {
return textBlockElementsMap;
};

self.getShortEndedElements = function() {
return shortEndedElementsMap;
};

self.getSelfClosingElements = function() {
return selfClosingElementsMap;
};

self.getNonEmptyElements = function() {
return nonEmptyElementsMap;
};

self.getWhiteSpaceElements = function() {
return whiteSpaceElementsMap;
};

self.isValidChild = function(name, child) {


var parent = children[name];

return !!(parent && parent[child]);


};

self.isValid = function(name, attr) {


var attrPatterns, i, rule = getElementRule(name);

// Check if it's a valid element


if (rule) {
if (attr) {
// Check if attribute name exists
if (rule.attributes[attr]) {
return true;
}

// Check if attribute matches a regexp pattern


attrPatterns = rule.attributePatterns;
if (attrPatterns) {
i = attrPatterns.length;
while (i--) {
if (attrPatterns[i].pattern.test(name)) {
return true;
}
}
}
} else {
return true;
}
}

// No match
return false;
};

self.getElementRule = getElementRule;

self.getCustomElements = function() {
return customElementsMap;
};

self.addValidElements = addValidElements;

self.setValidElements = setValidElements;

self.addCustomElements = addCustomElements;

self.addValidChildren = addValidChildren;

self.elements = elements;
};
})(tinymce);

(function(tinymce) {
tinymce.html.SaxParser = function(settings, schema) {
var self = this, noop = function() {};

settings = settings || {};


self.schema = schema = schema || new tinymce.html.Schema();
if (settings.fix_self_closing !== false)
settings.fix_self_closing = true;

// 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;
});

self.parse = function(html) {
var self = this, matches, index = 0, value, endRegExp, stack =
[], attrList, i, text, name, isInternalElement, removeInternalElements,
shortEndedElements, fillAttrsMap, isShortEnded, validate,
elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
validAttributesMap, validAttributePatterns,
attributesRequired, attributesDefault, attributesForced, selfClosing,
tokenRegExp, attrRegExp, specialElements, attrValue,
idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;

function processEndTag(name) {
var pos, i;

// Find position of parent of the same type


pos = stack.length;
while (pos--) {
if (stack[pos].name === name)
break;
}

// Found parent
if (pos >= 0) {
// Close all the open elements
for (i = stack.length - 1; i >= pos; i--) {
name = stack[i];

if (name.valid)
self.end(name.name);
}

// Remove the open elements from the stack


stack.length = pos;
}
};

function parseAttribute(match, name, value, val2, val3) {


var attrRule, i;

name = name.toLowerCase();
value = name in fillAttrsMap ? name : decode(value || val2
|| val3 || ''); // Handle boolean attribute than value attribute

// Validate name and value


if (validate && !isInternalElement && name.indexOf('data-')
!== 0) {
attrRule = validAttributesMap[name];

// Find rule by pattern matching


if (!attrRule && validAttributePatterns) {
i = validAttributePatterns.length;
while (i--) {
attrRule = validAttributePatterns[i];
if (attrRule.pattern.test(name))
break;
}

// No rule matched
if (i === -1)
attrRule = null;
}

// No attribute rule found


if (!attrRule)
return;

// Validate value
if (attrRule.validValues && !(value in
attrRule.validValues))
return;
}

// Add attribute to list and map


attrList.map[name] = value;
attrList.push({
name: name,
value: value
});
};

// 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
'(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|
(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
')', 'g');

attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|
(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
specialElements = {
'script' : /<\/script[^>]*>/gi,
'style' : /<\/style[^>]*>/gi,
'noscript' : /<\/noscript[^>]*>/gi
};

// Setup lookup tables for empty elements and boolean attributes


shortEndedElements = schema.getShortEndedElements();
selfClosing = settings.self_closing_elements ||
schema.getSelfClosingElements();
fillAttrsMap = schema.getBoolAttrs();
validate = settings.validate;
removeInternalElements = settings.remove_internals;
fixSelfClosing = settings.fix_self_closing;
isIE = tinymce.isIE;
invalidPrefixRegExp = /^:/;

while (matches = tokenRegExp.exec(html)) {


// Text
if (index < matches.index)
self.text(decode(html.substr(index, matches.index -
index)));

if (value = matches[6]) { // End element


value = value.toLowerCase();

// IE will add a ":" in front of elements it doesn't


understand like custom elements or HTML5 elements
if (isIE && invalidPrefixRegExp.test(value))
value = value.substr(1);

processEndTag(value);
} else if (value = matches[7]) { // Start element
value = value.toLowerCase();

// IE will add a ":" in front of elements it doesn't


understand like custom elements or HTML5 elements
if (isIE && invalidPrefixRegExp.test(value))
value = value.substr(1);

isShortEnded = value in shortEndedElements;

// Is self closing tag for example an <li> after an


open <li>
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;
}

// Parse attributes
if (attribsValue = matches[8]) {
isInternalElement =
attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an
internal element

// If the element has internal attributes


then remove it if we are told to do so
if (isInternalElement &&
removeInternalElements)
isValidElement = false;
attrList = [];
attrList.map = {};

attribsValue.replace(attrRegExp,
parseAttribute);
} else {
attrList = [];
attrList.map = {};
}

// Process attributes if validation is enabled


if (validate && !isInternalElement) {
attributesRequired =
elementRule.attributesRequired;
attributesDefault =
elementRule.attributesDefault;
attributesForced =
elementRule.attributesForced;

// Handle forced attributes


if (attributesForced) {
i = attributesForced.length;
while (i--) {
attr = attributesForced[i];
name = attr.name;
attrValue = attr.value;

if (attrValue === '{$uid}')


attrValue = 'mce_' +
idCount++;

attrList.map[name] =
attrValue;
attrList.push({name: name,
value: attrValue});
}
}

// Handle default attributes


if (attributesDefault) {
i = attributesDefault.length;
while (i--) {
attr = attributesDefault[i];
name = attr.name;

if (!(name in attrList.map))
{
attrValue = attr.value;

if (attrValue ===
'{$uid}')
attrValue = 'mce_'
+ idCount++;

attrList.map[name] =
attrValue;
attrList.push({name:
name, value: attrValue});
}
}
}

// Handle required attributes


if (attributesRequired) {
i = attributesRequired.length;
while (i--) {
if (attributesRequired[i] in
attrList.map)
break;
}

// None of the required attributes


where found
if (i === -1)
isValidElement = false;
}

// Invalidate element if it's marked as


bogus
if (attrList.map['data-mce-bogus'])
isValidElement = false;
}

if (isValidElement)
self.start(value, attrList,
isShortEnded);
} else
isValidElement = false;

// 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 (matches = endRegExp.exec(html)) {
if (isValidElement)
text = html.substr(index,
matches.index - index);

index = matches.index +
matches[0].length;
} else {
text = html.substr(index);
index = html.length;
}

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]);
}

index = matches.index + matches[0].length;


}

// Text
if (index < html.length)
self.text(decode(html.substr(index)));

// Close any open elements


for (i = stack.length - 1; i >= 0; i--) {
value = stack[i];

if (value.valid)
self.end(value.name);
}
};
}
})(tinymce);

(function(tinymce) {
var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
'#text' : 3,
'#comment' : 8,
'#cdata' : 4,
'#pi' : 7,
'#doctype' : 10,
'#document-fragment' : 11
};

// Walks the tree left/right


function walk(node, root_node, prev) {
var sibling, parent, startName = prev ? 'lastChild' : 'firstChild',
siblingName = prev ? 'prev' : 'next';

// Walk into nodes if it has a start


if (node[startName])
return node[startName];

// Return the sibling if it has one


if (node !== root_node) {
sibling = node[siblingName];
if (sibling)
return sibling;

// Walk up the parents to look for siblings


for (parent = node.parent; parent && parent !== root_node; parent
= parent.parent) {
sibling = parent[siblingName];

if (sibling)
return sibling;
}
}
};

function Node(name, type) {


this.name = name;
this.type = type;

if (type === 1) {
this.attributes = [];
this.attributes.map = {};
}
}

tinymce.extend(Node.prototype, {
replace : function(node) {
var self = this;

if (node.parent)
node.remove();

self.insert(node, self);
self.remove();

return self;
},

attr : function(name, value) {


var self = this, attrs, i, undef;

if (typeof name !== "string") {


for (i in name)
self.attr(i, name[i]);

return self;
}

if (attrs = self.attributes) {
if (value !== undef) {
// Remove attribute
if (value === null) {
if (name in attrs.map) {
delete attrs.map[name];

i = attrs.length;
while (i--) {
if (attrs[i].name === name) {
attrs = attrs.splice(i, 1);
return self;
}
}
}

return self;
}

// 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});

attrs.map[name] = value;

return self;
} else {
return attrs.map[name];
}
}
},

clone : function() {
var self = this, clone = new Node(self.name, self.type), i, l,
selfAttrs, selfAttr, cloneAttrs;

// Clone element attributes


if (selfAttrs = self.attributes) {
cloneAttrs = [];
cloneAttrs.map = {};

for (i = 0, l = selfAttrs.length; i < l; i++) {


selfAttr = selfAttrs[i];

// Clone everything except id


if (selfAttr.name !== 'id') {
cloneAttrs[cloneAttrs.length] = {name:
selfAttr.name, value: selfAttr.value};
cloneAttrs.map[selfAttr.name] = selfAttr.value;
}
}

clone.attributes = cloneAttrs;
}

clone.value = self.value;
clone.shortEnded = self.shortEnded;

return clone;
},

wrap : function(wrapper) {
var self = this;

self.parent.insert(wrapper, self);
wrapper.append(self);

return self;
},

unwrap : function() {
var self = this, node, next;

for (node = self.firstChild; node; ) {


next = node.next;
self.insert(node, self, true);
node = next;
}

self.remove();
},

remove : function() {
var self = this, parent = self.parent, next = self.next, prev =
self.prev;

if (parent) {
if (parent.firstChild === self) {
parent.firstChild = next;

if (next)
next.prev = null;
} else {
prev.next = next;
}

if (parent.lastChild === self) {


parent.lastChild = prev;

if (prev)
prev.next = null;
} else {
next.prev = prev;
}

self.parent = self.next = self.prev = null;


}

return self;
},

append : function(node) {
var self = this, last;

if (node.parent)
node.remove();

last = self.lastChild;
if (last) {
last.next = node;
node.prev = last;
self.lastChild = node;
} else
self.lastChild = self.firstChild = node;

node.parent = self;

return node;
},

insert : function(node, ref_node, before) {


var parent;

if (node.parent)
node.remove();

parent = ref_node.parent || this;

if (before) {
if (ref_node === parent.firstChild)
parent.firstChild = node;
else
ref_node.prev.next = node;

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;

node.next = ref_node.next;
node.prev = ref_node;
ref_node.next = node;
}

node.parent = parent;

return node;
},

getAll : function(name) {
var self = this, node, collection = [];

for (node = self.firstChild; node; node = walk(node, self)) {


if (node.name === name)
collection.push(node);
}

return collection;
},

empty : function() {
var self = this, nodes, i, node;

// Remove all children


if (self.firstChild) {
nodes = [];
// Collect the children
for (node = self.firstChild; node; node = walk(node, self))
nodes.push(node);

// Remove the children


i = nodes.length;
while (i--) {
node = nodes[i];
node.parent = node.firstChild = node.lastChild =
node.next = node.prev = null;
}
}

self.firstChild = self.lastChild = null;

return self;
},

isEmpty : function(elements) {
var self = this, node = self.firstChild, i, name;

if (node) {
do {
if (node.type === 1) {
// Ignore bogus elements
if (node.attributes.map['data-mce-bogus'])
continue;

// Keep empty elements like <img />


if (elements[node.name])
return false;

// Keep elements with data attributes or name


attribute like <a name="1"></a>
i = node.attributes.length;
while (i--) {
name = node.attributes[i].name;
if (name === "name" ||
name.indexOf('data-mce-') === 0)
return false;
}
}

// Keep comments
if (node.type === 8)
return false;

// Keep non whitespace text nodes


if ((node.type === 3 && !
whiteSpaceRegExp.test(node.value)))
return false;
} while (node = walk(node, self));
}

return true;
},

walk : function(prev) {
return walk(this, null, prev);
}
});

tinymce.extend(Node, {
create : function(name, attrs) {
var node, attrName;

// Create node
node = new Node(name, typeLookup[name] || 1);

// Add attributes if needed


if (attrs) {
for (attrName in attrs)
node.attr(attrName, attrs[attrName]);
}

return node;
}
});

tinymce.html.Node = Node;
})(tinymce);

(function(tinymce) {
var Node = tinymce.html.Node;

tinymce.html.DomParser = function(settings, schema) {


var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes
= {}, matchedAttributes = {};

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();

function fixInvalidChildren(nodes) {
var ni, node, parent, parents, newParent, currentNode, tempNode,
childNode, i,
childClone, nonEmptyElements, nonSplitableElements,
textBlockElements, sibling, nextNode;

nonSplitableElements =
tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
nonEmptyElements = schema.getNonEmptyElements();
textBlockElements = schema.getTextBlockElements();

for (ni = 0; ni < nodes.length; ni++) {


node = nodes[ni];

// Already removed or fixed


if (!node.parent || node.fixed)
continue;

// If the invalid element is a text block and the text


block is within a parent LI element
// Then unwrap the first text block and convert other
sibling text blocks to LI elements similar to Word/Open Office
if (textBlockElements[node.name] && node.parent.name ==
'li') {
// Move sibling text blocks after LI element
sibling = node.next;
while (sibling) {
if (textBlockElements[sibling.name]) {
sibling.name = 'li';
sibling.fixed = true;
node.parent.insert(sibling, node.parent);
} else {
break;
}

sibling = sibling.next;
}

// Unwrap current text block


node.unwrap(node);
continue;
}

// 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);

// Found a suitable parent


if (parent && parents.length > 1) {
// Reverse the array since it makes looping easier
parents.reverse();

// Clone the related parent and insert that after the


moved node
newParent = currentNode =
self.filterNode(parents[0].clone());

// 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;

for (childNode = parents[i].firstChild;


childNode && childNode != parents[i + 1]; ) {
nextNode = childNode.next;
tempNode.append(childNode);
childNode = nextNode;
}

currentNode = tempNode;
}
if (!newParent.isEmpty(nonEmptyElements)) {
parent.insert(newParent, parents[0], true);
parent.insert(node, newParent);
} else {
parent.insert(node, parents[0], true);
}

// Check if the element is empty by looking through


its contents and special treatment for <p><br /></p>
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;
}

sibling = node.next;
if (sibling && (sibling.name === 'ul' ||
sibling.name === 'ul')) {
sibling.insert(node, sibling.firstChild,
true);
continue;
}

node.wrap(self.filterNode(new Node('ul', 1)));


continue;
}

// 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();
}
}
}
};

self.filterNode = function(node) {
var i, name, list;

// Run element filters


if (name in nodeFilters) {
list = matchedNodes[name];

if (list)
list.push(node);
else
matchedNodes[name] = [node];
}

// Run attribute filters


i = attributeFilters.length;
while (i--) {
name = attributeFilters[i].name;

if (name in node.attributes.map) {
list = matchedAttributes[name];

if (list)
list.push(node);
else
matchedAttributes[name] = [node];
}
}

return node;
};

self.addNodeFilter = function(name, callback) {


tinymce.each(tinymce.explode(name), function(name) {
var list = nodeFilters[name];

if (!list)
nodeFilters[name] = list = [];

list.push(callback);
});
};

self.addAttributeFilter = function(name, callback) {


tinymce.each(tinymce.explode(name), function(name) {
var i;

for (i = 0; i < attributeFilters.length; i++) {


if (attributeFilters[i].name === name) {
attributeFilters[i].callbacks.push(callback);
return;
}
}

attributeFilters.push({name: name, callbacks: [callback]});


});
};

self.parse = function(html, args) {


var parser, rootNode, node, nodes, i, l, fi, fl, list, name,
validate,
blockElements, startWhiteSpaceRegExp, invalidChildren = [],
isInWhiteSpacePreservedElement,
endWhiteSpaceRegExp, allWhiteSpaceRegExp,
isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements,
rootBlockName;

args = args || {};


matchedNodes = {};
matchedAttributes = {};
blockElements =
tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'),
schema.getBlockElements());
nonEmptyElements = schema.getNonEmptyElements();
children = schema.children;
validate = settings.validate;
rootBlockName = "forced_root_block" in args ?
args.forced_root_block : settings.forced_root_block;

whiteSpaceElements = schema.getWhiteSpaceElements();
startWhiteSpaceRegExp = /^[ \t\r\n]+/;
endWhiteSpaceRegExp = /[ \t\r\n]+$/;
allWhiteSpaceRegExp = /[ \t\r\n]+/g;
isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;

function addRootBlocks() {
var node = rootNode.firstChild, next, rootBlockNode;

while (node) {
next = node.next;

if (node.type == 3 || (node.type == 1 && node.name !


== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
if (!rootBlockNode) {
// Create a new root block element
rootBlockNode = createNode(rootBlockName,
1);
rootNode.insert(rootBlockNode, node);
rootBlockNode.append(node);
} else
rootBlockNode.append(node);
} else {
rootBlockNode = null;
}

node = next;
};
};

function createNode(name, type) {


var node = new Node(name, type), list;

if (name in nodeFilters) {
list = matchedNodes[name];

if (list)
list.push(node);
else
matchedNodes[name] = [node];
}

return node;
};
function removeWhitespaceBefore(node) {
var textNode, textVal, sibling;

for (textNode = node.prev; textNode && textNode.type === 3;


) {
textVal = textNode.value.replace(endWhiteSpaceRegExp,
'');

if (textVal.length > 0) {
textNode.value = textVal;
textNode = textNode.prev;
} else {
sibling = textNode.prev;
textNode.remove();
textNode = sibling;
}
}
};

function cloneAndExcludeBlocks(input) {
var name, output = {};

for (name in input) {


if (name !== 'li' && name != 'p') {
output[name] = input[name];
}
}

return output;
};

parser = new tinymce.html.SaxParser({


validate : validate,

// Exclude P and LI from DOM parsing since it's treated


better by the DOM parser
self_closing_elements:
cloneAndExcludeBlocks(schema.getSelfClosingElements()),

cdata: function(text) {
node.append(createNode('#cdata', 4)).value = text;
},

text: function(text, raw) {


var textNode;

// Trim all redundant whitespace on non white space


elements
if (!isInWhiteSpacePreservedElement) {
text = text.replace(allWhiteSpaceRegExp, ' ');

if (node.lastChild &&
blockElements[node.lastChild.name])
text =
text.replace(startWhiteSpaceRegExp, '');
}

// Do we need to create the node


if (text.length !== 0) {
textNode = createNode('#text', 3);
textNode.raw = !!raw;
node.append(textNode).value = text;
}
},

comment: function(text) {
node.append(createNode('#comment', 8)).value = text;
},

pi: function(name, text) {


node.append(createNode(name, 7)).value = text;
removeWhitespaceBefore(node);
},

doctype: function(text) {
var newNode;

newNode = node.append(createNode('#doctype', 10));


newNode.value = text;
removeWhitespaceBefore(node);
},

start: function(name, attrs, empty) {


var newNode, attrFiltersLen, elementRule, textNode,
attrName, text, sibling, parent;

elementRule = validate ?
schema.getElementRule(name) : {};
if (elementRule) {
newNode = createNode(elementRule.outputName ||
name, 1);
newNode.attributes = attrs;
newNode.shortEnded = empty;

node.append(newNode);

// 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);

attrFiltersLen = attributeFilters.length;
while (attrFiltersLen--) {
attrName =
attributeFilters[attrFiltersLen].name;

if (attrName in attrs.map) {
list = matchedAttributes[attrName];

if (list)
list.push(newNode);
else
matchedAttributes[attrName] =
[newNode];
}
}

// Trim whitespace before block


if (blockElements[name])
removeWhitespaceBefore(newNode);

// Change current node if the element wasn't


empty i.e not <br /> or <img />
if (!empty)
node = newNode;

// Check if we are inside a whitespace


preserved element
if (!isInWhiteSpacePreservedElement &&
whiteSpaceElements[name]) {
isInWhiteSpacePreservedElement = true;
}
}
},

end: function(name) {
var textNode, elementRule, text, sibling, tempNode;

elementRule = validate ?
schema.getElementRule(name) : {};
if (elementRule) {
if (blockElements[name]) {
if (!isInWhiteSpacePreservedElement) {
// Trim whitespace of the first
node in a block
textNode = node.firstChild;
if (textNode && textNode.type ===
3) {
text =
textNode.value.replace(startWhiteSpaceRegExp, '');

// Any characters left after


trim or should we remove it
if (text.length > 0) {
textNode.value = text;
textNode =
textNode.next;
} else {
sibling = textNode.next;
textNode.remove();
textNode = sibling;
}

// Remove any pure whitespace


siblings
while (textNode &&
textNode.type === 3) {
text = textNode.value;
sibling = textNode.next;

if (text.length === 0 ||
isAllWhiteSpaceRegExp.test(text)) {
textNode.remove();
textNode =
sibling;
}

textNode = sibling;
}
}

// Trim whitespace of the last node


in a block
textNode = node.lastChild;
if (textNode && textNode.type ===
3) {
text =
textNode.value.replace(endWhiteSpaceRegExp, '');

// Any characters left after


trim or should we remove it
if (text.length > 0) {
textNode.value = text;
textNode =
textNode.prev;
} else {
sibling = textNode.prev;
textNode.remove();
textNode = sibling;
}

// Remove any pure whitespace


siblings
while (textNode &&
textNode.type === 3) {
text = textNode.value;
sibling = textNode.prev;

if (text.length === 0 ||
isAllWhiteSpaceRegExp.test(text)) {
textNode.remove();
textNode =
sibling;
}

textNode = sibling;
}
}
}

// Trim start white space


// Removed due to: #5424
/*textNode = node.prev;
if (textNode && textNode.type === 3) {
text =
textNode.value.replace(startWhiteSpaceRegExp, '');

if (text.length > 0)
textNode.value = text;
else
textNode.remove();
}*/
}

// Check if we exited a whitespace preserved


element
if (isInWhiteSpacePreservedElement &&
whiteSpaceElements[name]) {
isInWhiteSpacePreservedElement = false;
}

// 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 <a name="name">
if (!node.attributes.map.name
&& !node.attributes.map.id) {
tempNode = node.parent;
node.empty().remove();
node = tempNode;
return;
}
}
}
}

node = node.parent;
}
}
}, schema);

rootNode = node = new Node(args.context || settings.root_name,


11);

parser.parse(html);

// Fix invalid children or report invalid children in a


contextual parsing
if (validate && invalidChildren.length) {
if (!args.context)
fixInvalidChildren(invalidChildren);
else
args.invalid = true;
}

// Wrap nodes in the root into block elements if the root is body
if (rootBlockName && rootNode.name == 'body')
addRootBlocks();

// Run filters only when the contents is valid


if (!args.invalid) {
// 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);
}

// Run attribute filters


for (i = 0, l = attributeFilters.length; i < l; i++) {
list = attributeFilters[i];

if (list.name in matchedAttributes) {
nodes = matchedAttributes[list.name];

// Remove already removed children


fi = nodes.length;
while (fi--) {
if (!nodes[fi].parent)
nodes.splice(fi, 1);
}

for (fi = 0, fl = list.callbacks.length; fi <


fl; fi++)
list.callbacks[fi](nodes, list.name,
args);
}
}
}

return rootNode;
};

// Remove <br> 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 =
tinymce.extend({}, schema.getBlockElements()),
nonEmptyElements = schema.getNonEmptyElements(),
parent, lastParent, prev, prevName;

// Remove brs from body element as well


blockElements.body = 1;

// Must loop forwards since it will otherwise remove all


brs in <p>a<br><br><br></p>
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 left 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 <br><br>


structure then don't remove anything
if (prevName === 'br') {
node = null;
break;
}
}

prev = prev.prev;
}

if (node) {
node.remove();

// Is the parent to be considered empty


after we removed the BR
if (parent.isEmpty(nonEmptyElements)) {
elementRule =
schema.getElementRule(parent.name);

// Remove or padd the element


depending on schema rule
if (elementRule) {
if (elementRule.removeEmpty)
parent.remove();
else if
(elementRule.paddEmpty)

parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';


}
}
}
} else {
// Replaces BR elements inside inline elements
like <p><b><i><br></i></b></p> so they become <p><b><i>&nbsp;</i></b></p>
lastParent = node;
while (parent.firstChild === lastParent &&
parent.lastChild === lastParent) {
lastParent = parent;

if (blockElements[parent.name]) {
break;
}
parent = parent.parent;
}

if (lastParent === parent) {


textNode = new tinymce.html.Node('#text',
3);
textNode.value = '\u00a0';
node.replace(textNode);
}
}
}
});
}

// Force anchor names closed, unless the setting


"allow_html_in_named_anchor" is explicitly included.
if (!settings.allow_html_in_named_anchor) {
self.addAttributeFilter('id,name', function(nodes, name) {
var i = nodes.length, sibling, prevSibling, parent, node;

while (i--) {
node = nodes[i];
if (node.name === 'a' && node.firstChild && !
node.attr('href')) {
parent = node.parent;

// Move children after current node


sibling = node.lastChild;
do {
prevSibling = sibling.prev;
parent.insert(sibling, node);
sibling = prevSibling;
} while (sibling);
}
}
});
}
}
})(tinymce);

tinymce.html.Writer = function(settings) {
var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;

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";

return {
start: function(name, attrs, empty) {
var i, l, attr, value;

if (indent && indentBefore[name] && html.length > 0) {


value = html[html.length - 1];
if (value.length > 0 && value !== '\n')
html.push('\n');
}

html.push('<', name);

if (attrs) {
for (i = 0, l = attrs.length; i < l; i++) {
attr = attrs[i];
html.push(' ', attr.name, '="', encode(attr.value,
true), '"');
}
}

if (!empty || htmlOutput)
html[html.length] = '>';
else
html[html.length] = ' />';

if (empty && indent && indentAfter[name] && html.length > 0) {


value = html[html.length - 1];

if (value.length > 0 && value !== '\n')


html.push('\n');
}
},

end: function(name) {
var value;

/*if (indent && indentBefore[name] && html.length > 0) {


value = html[html.length - 1];

if (value.length > 0 && value !== '\n')


html.push('\n');
}*/

html.push('</', name, '>');

if (indent && indentAfter[name] && html.length > 0) {


value = html[html.length - 1];

if (value.length > 0 && value !== '\n')


html.push('\n');
}
},

text: function(text, raw) {


if (text.length > 0)
html[html.length] = raw ? text : encode(text);
},

cdata: function(text) {
html.push('<![CDATA[', text, ']]>');
},

comment: function(text) {
html.push('<!--', text, '-->');
},
pi: function(name, text) {
if (text)
html.push('<?', name, ' ', text, '?>');
else
html.push('<?', name, '?>');

if (indent)
html.push('\n');
},

doctype: function(text) {
html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
},

reset: function() {
html.length = 0;
},

getContent: function() {
return html.join('').replace(/\n$/, '');
}
};
};

(function(tinymce) {
tinymce.html.Serializer = function(settings, schema) {
var self = this, writer = new tinymce.html.Writer(settings);

settings = settings || {};


settings.validate = "validate" in settings ? settings.validate : true;

self.schema = schema = schema || new tinymce.html.Schema();


self.writer = writer;

self.serialize = function(node) {
var handlers, validate;

validate = settings.validate;

handlers = {
// #text
3: function(node, raw) {
writer.text(node.value, node.raw);
},

// #comment
8: function(node) {
writer.comment(node.value);
},

// Processing instruction
7: function(node) {
writer.pi(node.name, node.value);
},

// Doctype
10: function(node) {
writer.doctype(node.value);
},

// CDATA
4: function(node) {
writer.cdata(node.value);
},

// Document fragment
11: function(node) {
if ((node = node.firstChild)) {
do {
walk(node);
} while (node = node.next);
}
}
};

writer.reset();

function walk(node) {
var handler = handlers[node.type], name, isEmpty, attrs,
attrName, attrValue, sortedAttrs, i, l, elementRule;

if (!handler) {
name = node.name;
isEmpty = node.shortEnded;
attrs = node.attributes;

// Sort attributes
if (validate && attrs && attrs.length > 1) {
sortedAttrs = [];
sortedAttrs.map = {};

elementRule = schema.getElementRule(node.name);
for (i = 0, l =
elementRule.attributesOrder.length; i < l; i++) {
attrName =
elementRule.attributesOrder[i];

if (attrName in attrs.map) {
attrValue = attrs.map[attrName];
sortedAttrs.map[attrName] =
attrValue;
sortedAttrs.push({name: attrName,
value: attrValue});
}
}

for (i = 0, l = attrs.length; i < l; i++) {


attrName = attrs[i].name;

if (!(attrName in sortedAttrs.map)) {
attrValue = attrs.map[attrName];
sortedAttrs.map[attrName] =
attrValue;
sortedAttrs.push({name: attrName,
value: attrValue});
}
}
attrs = sortedAttrs;
}

writer.start(node.name, attrs, isEmpty);

if (!isEmpty) {
if ((node = node.firstChild)) {
do {
walk(node);
} while (node = node.next);
}

writer.end(name);
}
} else
handler(node);
}

// Serialize element and treat all non elements as fragments


if (node.type == 1 && !settings.inner)
walk(node);
else
handlers[11](node);

return writer.getContent();
};
}
})(tinymce);

// JSLint defined globals


/*global tinymce:false, window:false */

tinymce.dom = {};

(function(namespace, expando) {
var w3cEventModel = !!document.addEventListener;

function addEvent(target, name, callback, capture) {


if (target.addEventListener) {
target.addEventListener(name, callback, capture || false);
} else if (target.attachEvent) {
target.attachEvent('on' + name, callback);
}
}

function removeEvent(target, name, callback, capture) {


if (target.removeEventListener) {
target.removeEventListener(name, callback, capture || false);
} else if (target.detachEvent) {
target.detachEvent('on' + name, callback);
}
}

function fix(original_event, data) {


var name, event = data || {};

// Dummy function that gets replaced on the delegation state functions


function returnFalse() {
return false;
}

// Dummy function that gets replaced on the delegation state functions


function returnTrue() {
return true;
}

// Copy all properties from the original event


for (name in original_event) {
// layerX/layerY is deprecated in Chrome and produces a warning
if (name !== "layerX" && name !== "layerY") {
event[name] = original_event[name];
}
}

// Normalize target IE uses srcElement


if (!event.target) {
event.target = event.srcElement || document;
}

// Add preventDefault method


event.preventDefault = function() {
event.isDefaultPrevented = returnTrue;

// Execute preventDefault on the original event object


if (original_event) {
if (original_event.preventDefault) {
original_event.preventDefault();
} else {
original_event.returnValue = false; // IE
}
}
};

// Add stopPropagation
event.stopPropagation = function() {
event.isPropagationStopped = returnTrue;

// Execute stopPropagation on the original event object


if (original_event) {
if (original_event.stopPropagation) {
original_event.stopPropagation();
} else {
original_event.cancelBubble = true; // IE
}
}
};

// Add stopImmediatePropagation
event.stopImmediatePropagation = function() {
event.isImmediatePropagationStopped = returnTrue;
event.stopPropagation();
};

// Add event delegation states


if (!event.isDefaultPrevented) {
event.isDefaultPrevented = returnFalse;
event.isPropagationStopped = returnFalse;
event.isImmediatePropagationStopped = returnFalse;
}

return event;
}

function bindOnReady(win, callback, event_utils) {


var doc = win.document, event = {type: 'ready'};

// Gets called when the DOM is ready


function readyHandler() {
if (!event_utils.domLoaded) {
event_utils.domLoaded = true;
callback(event);
}
}

// Page already loaded then fire it directly


if (doc.readyState == "complete") {
readyHandler();
return;
}

// Use W3C method


if (w3cEventModel) {
addEvent(win, 'DOMContentLoaded', readyHandler);
} else {
// Use IE method
addEvent(doc, "readystatechange", function() {
if (doc.readyState === "complete") {
removeEvent(doc, "readystatechange",
arguments.callee);
readyHandler();
}
});

// Wait until we can scroll, when we can the DOM is initialized


if (doc.documentElement.doScroll && win === win.top) {
(function() {
try {
// If IE is used, use the trick by Diego Perini
licensed under MIT by request to the author.
// http://javascript.nwbox.com/IEContentLoaded/
doc.documentElement.doScroll("left");
} catch (ex) {
setTimeout(arguments.callee, 0);
return;
}

readyHandler();
})();
}
}

// Fallback if any of the above methods should fail for some odd reason
addEvent(win, 'load', readyHandler);
}

function EventUtils(proxy) {
var self = this, events = {}, count, isFocusBlurBound, hasFocusIn,
hasMouseEnterLeave, mouseEnterLeave;

hasMouseEnterLeave = "onmouseenter" in document.documentElement;


hasFocusIn = "onfocusin" in document.documentElement;
mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
count = 1;

// State if the DOMContentLoaded was executed or not


self.domLoaded = false;
self.events = events;

function executeHandlers(evt, id) {


var callbackList, i, l, callback;

callbackList = events[id][evt.type];
if (callbackList) {
for (i = 0, l = callbackList.length; i < l; i++) {
callback = callbackList[i];

// Check if callback exists might be removed if a


unbind is called inside the callback
if (callback && callback.func.call(callback.scope,
evt) === false) {
evt.preventDefault();
}

// Should we stop propagation to immediate listeners


if (evt.isImmediatePropagationStopped()) {
return;
}
}
}
}

self.bind = function(target, names, callback, scope) {


var id, callbackList, i, name, fakeName, nativeHandler, capture,
win = window;

// Native event handler function patches the event and executes


the callbacks for the expando
function defaultNativeHandler(evt) {
executeHandlers(fix(evt || win.event), id);
}

// Don't bind to text nodes or comments


if (!target || target.nodeType === 3 || target.nodeType === 8) {
return;
}

// Create or get events id for the target


if (!target[expando]) {
id = count++;
target[expando] = id;
events[id] = {};
} else {
id = target[expando];

if (!events[id]) {
events[id] = {};
}
}

// Setup the specified scope or use the target as a default


scope = scope || target;

// Split names and bind each event, enables you to bind multiple
events with one call
names = names.split(' ');
i = names.length;
while (i--) {
name = names[i];
nativeHandler = defaultNativeHandler;
fakeName = capture = false;

// Use ready instead of DOMContentLoaded


if (name === "DOMContentLoaded") {
name = "ready";
}

// DOM is already ready


if ((self.domLoaded || target.readyState == 'complete') &&
name === "ready") {
self.domLoaded = true;
callback.call(scope, fix({type: name}));
continue;
}

// Handle mouseenter/mouseleaver
if (!hasMouseEnterLeave) {
fakeName = mouseEnterLeave[name];

if (fakeName) {
nativeHandler = function(evt) {
var current, related;

current = evt.currentTarget;
related = evt.relatedTarget;

// Check if related is inside the current


target if it's not then the event should be ignored since it's a mouseover/mouseout
inside the element
if (related && current.contains) {
// Use contains for performance
related =
current.contains(related);
} else {
while (related && related !==
current) {
related = related.parentNode;
}
}

// Fire fake event


if (!related) {
evt = fix(evt || win.event);
evt.type = evt.type ===
'mouseout' ? 'mouseleave' : 'mouseenter';
evt.target = current;
executeHandlers(evt, id);
}
};
}
}

// Fake bubbeling of focusin/focusout


if (!hasFocusIn && (name === "focusin" || name ===
"focusout")) {
capture = true;
fakeName = name === "focusin" ? "focus" : "blur";
nativeHandler = function(evt) {
evt = fix(evt || win.event);
evt.type = evt.type === 'focus' ? 'focusin' :
'focusout';
executeHandlers(evt, id);
};
}

// Setup callback list and bind native event


callbackList = events[id][name];
if (!callbackList) {
events[id][name] = callbackList = [{func: callback,
scope: scope}];
callbackList.fakeName = fakeName;
callbackList.capture = capture;

// Add the nativeHandler to the callback list so that


we can later unbind it
callbackList.nativeHandler = nativeHandler;
if (!w3cEventModel) {
callbackList.proxyHandler = proxy(id);
}

// Check if the target has native events support


if (name === "ready") {
bindOnReady(target, nativeHandler, self);
} else {
addEvent(target, fakeName || name,
w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
}
} else {
// If it already has an native handler then just push
the callback
callbackList.push({func: callback, scope: scope});
}
}

target = callbackList = 0; // Clean memory for IE

return callback;
};

self.unbind = function(target, names, callback) {


var id, callbackList, i, ci, name, eventMap;

// Don't bind to text nodes or comments


if (!target || target.nodeType === 3 || target.nodeType === 8) {
return self;
}

// Unbind event or events if the target has the expando


id = target[expando];
if (id) {
eventMap = events[id];

// Specific callback
if (names) {
names = names.split(' ');
i = names.length;
while (i--) {
name = names[i];
callbackList = eventMap[name];

// Unbind the event if it exists in the map


if (callbackList) {
// Remove specified callback
if (callback) {
ci = callbackList.length;
while (ci--) {
if (callbackList[ci].func ===
callback) {
callbackList.splice(ci,
1);
}
}
}

// Remove all callbacks if there isn't a


specified callback or there is no callbacks left
if (!callback || callbackList.length ===
0) {
delete eventMap[name];
removeEvent(target,
callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler :
callbackList.proxyHandler, callbackList.capture);
}
}
}
} else {
// All events for a specific element
for (name in eventMap) {
callbackList = eventMap[name];
removeEvent(target, callbackList.fakeName ||
name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler,
callbackList.capture);
}

eventMap = {};
}

// Check if object is empty, if it isn't then we won't


remove the expando map
for (name in eventMap) {
return self;
}
// Delete event object
delete events[id];

// Remove expando from target


try {
// IE will fail here since it can't delete properties
from window
delete target[expando];
} catch (ex) {
// IE will set it to null
target[expando] = null;
}
}

return self;
};

self.fire = function(target, name, args) {


var id, event;

// Don't bind to text nodes or comments


if (!target || target.nodeType === 3 || target.nodeType === 8) {
return self;
}

// Build event object by patching the args


event = fix(null, args);
event.type = name;

do {
// Found an expando that means there is listeners to
execute
id = target[expando];
if (id) {
executeHandlers(event, id);
}

// Walk up the DOM


target = target.parentNode || target.ownerDocument ||
target.defaultView || target.parentWindow;
} while (target && !event.isPropagationStopped());

return self;
};

self.clean = function(target) {
var i, children, unbind = self.unbind;

// Don't bind to text nodes or comments


if (!target || target.nodeType === 3 || target.nodeType === 8) {
return self;
}

// Unbind any element on the specificed target


if (target[expando]) {
unbind(target);
}

// Target doesn't have getElementsByTagName it's probably a


window object then use it's document to find the children
if (!target.getElementsByTagName) {
target = target.document;
}

// Remove events from each child element


if (target && target.getElementsByTagName) {
unbind(target);

children = target.getElementsByTagName('*');
i = children.length;
while (i--) {
target = children[i];

if (target[expando]) {
unbind(target);
}
}
}

return self;
};

self.callNativeHandler = function(id, evt) {


if (events) {
events[id][evt.type].nativeHandler(evt);
}
};

self.destory = function() {
events = {};
};

// Legacy function calls

self.add = function(target, events, func, scope) {


// Old API supported direct ID assignment
if (typeof(target) === "string") {
target = document.getElementById(target);
}

// Old API supported multiple targets


if (target && target instanceof Array) {
var i = target.length;

while (i--) {
self.add(target[i], events, func, scope);
}

return;
}

// Old API called ready init


if (events === "init") {
events = "ready";
}

return self.bind(target, events instanceof Array ? events.join('


') : events, func, scope);
};

self.remove = function(target, events, func, scope) {


if (!target) {
return self;
}

// Old API supported direct ID assignment


if (typeof(target) === "string") {
target = document.getElementById(target);
}

// Old API supported multiple targets


if (target instanceof Array) {
var i = target.length;

while (i--) {
self.remove(target[i], events, func, scope);
}

return self;
}

return self.unbind(target, events instanceof Array ?


events.join(' ') : events, func);
};

self.clear = function(target) {
// Old API supported direct ID assignment
if (typeof(target) === "string") {
target = document.getElementById(target);
}

return self.clean(target);
};

self.cancel = function(e) {
if (e) {
self.prevent(e);
self.stop(e);
}

return false;
};

self.prevent = function(e) {
if (!e.preventDefault) {
e = fix(e);
}

e.preventDefault();

return false;
};

self.stop = function(e) {
if (!e.stopPropagation) {
e = fix(e);
}
e.stopPropagation();

return false;
};
}

namespace.EventUtils = EventUtils;

namespace.Event = new EventUtils(function(id) {


return function(evt) {
tinymce.dom.Event.callNativeHandler(id, evt);
};
});

// Bind ready event when tinymce script is loaded


namespace.Event.bind(window, 'ready', function() {});

namespace = 0;
})(tinymce.dom, 'data-mce-expando'); // Namespace and expando

tinymce.dom.TreeWalker = function(start_node, root_node) {


var node = start_node;

function findSibling(node, start_name, sibling_name, shallow) {


var sibling, parent;

if (node) {
// Walk into nodes if it has a start
if (!shallow && node[start_name])
return node[start_name];

// Return the sibling if it has one


if (node != root_node) {
sibling = node[sibling_name];
if (sibling)
return sibling;

// Walk up the parents to look for siblings


for (parent = node.parentNode; parent && parent !=
root_node; parent = parent.parentNode) {
sibling = parent[sibling_name];
if (sibling)
return sibling;
}
}
}
};

this.current = function() {
return node;
};

this.next = function(shallow) {
return (node = findSibling(node, 'firstChild', 'nextSibling',
shallow));
};

this.prev = function(shallow) {
return (node = findSibling(node, 'lastChild', 'previousSibling',
shallow));
};
};

(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,
whiteSpaceRegExp = /^[ \t\r\n]*$/;

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"
},

DOMUtils : function(d, s) {
var t = this, globalStyle, name, blockElementsMap;

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");

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);
// Fix IE6SP2 flicker and check it failed for pre SP2
if (tinymce.isIE6) {
try {
d.execCommand('BackgroundImageCache', false, true);
} catch (e) {
t.cssFlicker = true;
}
}

t.fixDoc(d);
t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) :
tinymce.dom.Event;
tinymce.addUnload(t.destroy, t);
blockElementsMap = s.schema ? s.schema.getBlockElements() : {};

t.isBlock = function(node) {
// Fix for #5446
if (!node) {
return false;
}

// This function is called in module pattern style since it


might be executed with the wrong this scope
var type = node.nodeType;

// If it's a node then check the type and use the nodeName
if (type)
return !!(type === 1 &&
blockElementsMap[node.nodeName]);

return !!blockElementsMap[node];
};
},

fixDoc: function(doc) {
var settings = this.settings, name;

if (isIE && settings.schema) {


// Add missing HTML 4/5 elements to IE
('abbr article aside audio canvas ' +
'details figcaption figure footer ' +
'header hgroup mark menu meter nav ' +
'output progress section summary ' +
'time video').replace(/\w+/g, function(name) {
doc.createElement(name);
});

// Create all custom elements


for (name in settings.schema.getCustomElements()) {
doc.createElement(name);
}
}
},

clone: function(node, deep) {


var self = this, clone, doc;

// TODO: Add feature detection here in the future


if (!isIE || node.nodeType !== 1 || deep) {
return node.cloneNode(deep);
}

doc = self.doc;

// Make a HTML5 safe shallow copy


if (!deep) {
clone = doc.createElement(node.nodeName);

// Copy attribs
each(self.getAttribs(node), function(attr) {
self.setAttrib(clone, attr.nodeName,
self.getAttrib(node, attr.nodeName));
});

return clone;
}
/*
// Setup HTML5 patched document fragment
if (!self.frag) {
self.frag = doc.createDocumentFragment();
self.fixDoc(self.frag);
}

// Make a deep copy by adding it to the document fragment then


removing it this removed the :section
clone = doc.createElement('div');
self.frag.appendChild(clone);
clone.innerHTML = node.outerHTML;
self.frag.removeChild(clone);
*/
return clone.firstChild;
},

getRoot : function() {
var t = this, s = t.settings;

return (s && t.get(s.root_element)) || t.doc.body;


},

getViewPort : function(w) {
var d, b;

w = !w ? this.win : w;
d = w.document;
b = this.boxModel ? d.documentElement : d.body;

// 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
};
},

getRect : function(e) {
var p, t = this, sr;
e = t.get(e);
p = t.getPos(e);
sr = t.getSize(e);

return {
x : p.x,
y : p.y,
w : sr.w,
h : sr.h
};
},

getSize : function(e) {
var t = this, w, h;

e = t.get(e);
w = t.getStyle(e, 'width');
h = t.getStyle(e, 'height');

// Non pixel value, then force offset/clientWidth


if (w.indexOf('px') === -1)
w = 0;

// Non pixel value, then force offset/clientWidth


if (h.indexOf('px') === -1)
h = 0;

return {
w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
};
},

getParent : function(n, f, r) {
return this.getParents(n, f, r, false);
},

getParents : function(n, f, r, c) {
var t = this, na, se = t.settings, o = [];

n = t.get(n);
c = c === undefined;

if (se.strict_root)
r = r || t.getRoot();

// Wrap node name as func


if (is(f, 'string')) {
na = f;

if (f === '*') {
f = function(n) {return n.nodeType == 1;};
} else {
f = function(n) {
return t.is(n, na);
};
}
}
while (n) {
if (n == r || !n.nodeType || n.nodeType === 9)
break;

if (!f || f(n)) {
if (c)
o.push(n);
else
return n;
}

n = n.parentNode;
}

return c ? o : null;
},

get : function(e) {
var n;

if (e && this.doc && typeof(e) == 'string') {


n = e;
e = this.doc.getElementById(e);

// IE and Opera returns meta elements when they match the


specified input ID, but getElementsByName seems to do the trick
if (e && e.id !== n)
return this.doc.getElementsByName(n)[1];
}

return e;
},

getNext : function(node, selector) {


return this._findSib(node, selector, 'nextSibling');
},

getPrev : function(node, selector) {


return this._findSib(node, selector, 'previousSibling');
},

select : function(pa, s) {
var t = this;

return tinymce.dom.Sizzle(pa, t.get(s) ||


t.get(t.settings.root_element) || t.doc, []);
},

is : function(n, selector) {
var i;

// 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;
// Simple selector just elements
if (simpleSelectorRe.test(selector)) {
selector = selector.toLowerCase().split(/,/);
n = n.nodeName.toLowerCase();

for (i = selector.length - 1; i >= 0; i--) {


if (selector[i] == n)
return true;
}

return false;
}
}

return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] :


n).length > 0;
},

add : function(p, n, a, h, c) {
var t = this;

return this.run(p, function(p) {


var e, k;

e = is(n, 'string') ? t.doc.createElement(n) : n;


t.setAttribs(e, a);

if (h) {
if (h.nodeType)
e.appendChild(h);
else
t.setHTML(e, h);
}

return !c ? p.appendChild(e) : e;
});
},

create : function(n, a, h) {
return this.add(this.doc.createElement(n), n, a, h, 1);
},

createHTML : function(n, a, h) {
var o = '', t = this, k;

o += '<' + n;

for (k in a) {
if (a.hasOwnProperty(k))
o += ' ' + k + '="' + t.encode(a[k]) + '"';
}

// 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 + '>';

return o + ' />';


},

remove : function(node, keep_children) {


return this.run(node, function(node) {
var child, parent = node.parentNode;

if (!parent)
return null;

if (keep_children) {
while (child = node.firstChild) {
// IE 8 will crash if you don't remove
completely empty text nodes
if (!tinymce.isIE || child.nodeType !== 3 ||
child.nodeValue)
parent.insertBefore(child, node);
else
node.removeChild(child);
}
}

return parent.removeChild(node);
});
},

setStyle : function(n, na, v) {


var t = this;

return t.run(n, function(e) {


var s, i;

s = e.style;

// Camelcase it, if needed


na = na.replace(/-(\D)/g, function(a, b){
return b.toUpperCase();
});

// Default px suffix on these


if (t.pixelStyles.test(na) && (tinymce.is(v, 'number')
|| /^[\-0-9\.]+$/.test(v)))
v += 'px';

switch (na) {
case 'opacity':
// IE specific opacity
if (isIE) {
s.filter = v === '' ? '' :
"alpha(opacity=" + (v * 100) + ")";

if (!n.currentStyle || !
n.currentStyle.hasLayout)
s.display = 'inline-block';
}

// Fix for older browsers


s[na] = s['-moz-opacity'] = s['-khtml-opacity']
= v || '';
break;
case 'float':
isIE ? s.styleFloat = v : s.cssFloat = v;
break;

default:
s[na] = v || '';
}

// Force update of the style data


if (t.settings.update_styles)
t.setAttrib(e, 'data-mce-style');
});
},

getStyle : function(n, na, c) {


n = this.get(n);

if (!n)
return;

// 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;
}
}

// Camelcase it, if needed


na = na.replace(/-(\D)/g, function(a, b){
return b.toUpperCase();
});

if (na == 'float')
na = isIE ? 'styleFloat' : 'cssFloat';

// IE & Opera
if (n.currentStyle && c)
return n.currentStyle[na];

return n.style ? n.style[na] : undefined;


},

setStyles : function(e, o) {
var t = this, s = t.settings, ol;

ol = s.update_styles;
s.update_styles = 0;

each(o, function(v, n) {
t.setStyle(e, n, v);
});

// Update style info


s.update_styles = ol;
if (s.update_styles)
t.setAttrib(e, s.cssText);
},

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));
}
});
},

setAttrib : function(e, n, v) {
var t = this;

// Whats the point


if (!e || !n)
return;

// Strict XML mode


if (t.settings.strict)
n = n.toLowerCase();

return this.run(e, function(e) {


var s = t.settings;
var originalValue = e.getAttribute(n);
if (v !== null) {
switch (n) {
case "style":
if (!is(v, 'string')) {
each(v, function(v, n) {
t.setStyle(e, n, v);
});

return;
}

// 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);
}

e.style.cssText = v;
break;

case "class":
e.className = v || ''; // Fix IE null bug
break;

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);

t.setAttrib(e, 'data-mce-' + n, v,
2);
}

break;

case "shape":
e.setAttribute('data-mce-style', v);
break;
}
}
if (is(v) && v !== null && v.length !== 0)
e.setAttribute(n, '' + v, 2);
else
e.removeAttribute(n, 2);

// fire onChangeAttrib event for attributes that have


changed
if (tinyMCE.activeEditor && originalValue != v) {
var ed = tinyMCE.activeEditor;
ed.onSetAttrib.dispatch(ed, e, n, v);
}
});
},

setAttribs : function(e, o) {
var t = this;

return this.run(e, function(e) {


each(o, function(v, n) {
t.setAttrib(e, n, v);
});
});
},

getAttrib : function(e, n, dv) {


var v, t = this, undef;

e = t.get(e);

if (!e || e.nodeType !== 1)


return dv === undef ? false : dv;

if (!is(dv))
dv = '';

// Try the mce variant for these


if (/^(src|href|style|coords|shape)$/.test(n)) {
v = e.getAttribute("data-mce-" + n);
if (v)
return v;
}

if (isIE && t.props[n]) {


v = e[t.props[n]];
v = v && v.nodeValue ? v.nodeValue : v;
}

if (!v)
v = e.getAttribute(n, 2);

// 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;

return v ? n : '';
}

// Inner input elements will override attributes on form elements


if (e.nodeName === "FORM" && e.getAttributeNode(n))
return e.getAttributeNode(n).nodeValue;

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('data-mce-style', v);
}
}

// Remove Apple and WebKit stuff


if (isWebKit && n === "class" && v)
v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');

// Handle IE issues
if (isIE) {
switch (n) {
case 'rowspan':
case 'colspan':
// IE returns 1 as default value
if (v === 1)
v = '';

break;

case 'size':
// IE returns +0 as default value for size
if (v === '+0' || v === 20 || v === 0)
v = '';

break;

case 'width':
case 'height':
case 'vspace':
case 'checked':
case 'disabled':
case 'readonly':
if (v === 0)
v = '';

break;

case 'hspace':
// IE returns -1 as default value
if (v === -1)
v = '';

break;

case 'maxlength':
case 'tabindex':
// IE returns default value
if (v === 32768 || v === 2147483647 || v ===
'32768')
v = '';

break;

case 'multiple':
case 'compact':
case 'noshade':
case 'nowrap':
if (v === 65535)
return n;

return dv;

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);
}
}

return (v !== undef && v !== null && v !== '') ? '' + v : dv;
},

getPos : function(n, ro) {


var t = this, x = 0, y = 0, e, d = t.doc, r;

n = t.get(n);
ro = ro || d.body;

if (n) {
// Use getBoundingClientRect if it exists since it's faster
than looping offset nodes
if (n.getBoundingClientRect) {
n = n.getBoundingClientRect();
e = t.boxModel ? d.documentElement : d.body;

// Add scroll offsets from documentElement or body


since IE with the wrong box model will use d.body and so do WebKit
// Also remove the body/documentelement
clientTop/clientLeft on IE 6, 7 since they offset the position
x = n.left + (d.documentElement.scrollLeft ||
d.body.scrollLeft) - e.clientTop;
y = n.top + (d.documentElement.scrollTop ||
d.body.scrollTop) - e.clientLeft;

return {x : x, y : y};
}

r = n;
while (r && r != ro && r.nodeType) {
x += r.offsetLeft || 0;
y += r.offsetTop || 0;
r = r.offsetParent;
}

r = n.parentNode;
while (r && r != ro && r.nodeType) {
x -= r.scrollLeft || 0;
y -= r.scrollTop || 0;
r = r.parentNode;
}
}

return {x : x, y : y};
},

parseStyle : function(st) {
return this.styles.parse(st);
},

serializeStyle : function(o, name) {


return this.styles.serialize(o, name);
},

addStyle: function(cssText) {
var doc = this.doc, head;

// Create style element if needed


styleElm = doc.getElementById('mceDefaultStyles');
if (!styleElm) {
styleElm = doc.createElement('style'),
styleElm.id = 'mceDefaultStyles';
styleElm.type = 'text/css';

head = doc.getElementsByTagName('head')[0];
if (head.firstChild) {
head.insertBefore(styleElm, head.firstChild);
} else {
head.appendChild(styleElm);
}
}

// Append style data to old or new style element


if (styleElm.styleSheet) {
styleElm.styleSheet.cssText += cssText;
} else {
styleElm.appendChild(doc.createTextNode(cssText));
}
},

loadCSS : function(u) {
var t = this, d = t.doc, head;

if (!u)
u = '';

head = d.getElementsByTagName('head')[0];

each(u.split(','), function(u) {
var link;

if (t.files[u])
return;

t.files[u] = true;
link = t.create('link', {rel : 'stylesheet', href :
tinymce._addVer(u)});

// IE 8 has a bug where dynamically loading stylesheets


would produce a 1 item remaining bug
// This fix seems to resolve that issue by realcing the
document ones a stylesheet finishes loading
// It's ugly but it seems to work fine.
if (isIE && d.documentMode && d.recalc) {
link.onload = function() {
if (d.recalc)
d.recalc();

link.onload = null;
};
}

head.appendChild(link);
});
},

addClass : function(e, c) {
return this.run(e, function(e) {
var o;

if (!c)
return 0;

if (this.hasClass(e, c))
return e.className;

o = this.removeClass(e, c);

return e.className = (o != '' ? (o + ' ') : '') + c;


});
},

removeClass : function(e, c) {
var t = this, re;

return t.run(e, function(e) {


var v;

if (t.hasClass(e, c)) {
if (!re)
re = new RegExp("(^|\\s+)" + c + "(\\s+|$)",
"g");

v = e.className.replace(re, ' ');


v = tinymce.trim(v != ' ' ? v : '');

e.className = v;

// Empty class attr


if (!v) {
e.removeAttribute('class');
e.removeAttribute('className');
}

return v;
}

return e.className;
});
},

hasClass : function(n, c) {
n = this.get(n);

if (!n || !c)
return false;

return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
},

show : function(e) {
return this.setStyle(e, 'display', 'block');
},

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(element, html) {
var self = this;

return self.run(element, function(element) {


if (isIE) {
// Remove all child nodes, IE keeps empty text nodes
in DOM
while (element.firstChild)
element.removeChild(element.firstChild);

try {
// IE will remove comments from the beginning
// unless you padd the contents with something
element.innerHTML = '<br />' + 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
var newElement = self.create('div');
newElement.innerHTML = '<br />' + html;

// Add all children from div to target


each (tinymce.grep(newElement.childNodes),
function(node, i) {
// Skip br element
if (i && element.canHaveHTML)
element.appendChild(node);
});
}
} else
element.innerHTML = html;

return html;
});
},

getOuterHTML : function(elm) {
var doc, self = this;

elm = self.get(elm);

if (!elm)
return null;

if (elm.nodeType === 1 && self.hasOuterHTML)


return elm.outerHTML;

doc = (elm.ownerDocument || self.doc).createElement("body");


doc.appendChild(elm.cloneNode(true));

return doc.innerHTML;
},
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;
}

t.remove(e);
};

return this.run(e, function(e) {


e = t.get(e);

// Only set HTML on elements


if (e.nodeType == 1) {
d = d || e.ownerDocument || t.doc;

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);
}
});
},

decode : Entities.decode,

encode : Entities.encodeAllRaw,

insertAfter : function(node, reference_node) {


reference_node = this.get(reference_node);

return this.run(node, function(node) {


var parent, nextSibling;

parent = reference_node.parentNode;
nextSibling = reference_node.nextSibling;

if (nextSibling)
parent.insertBefore(node, nextSibling);
else
parent.appendChild(node);

return node;
});
},

replace : function(n, o, k) {
var t = this;

if (is(o, 'array'))
n = n.cloneNode(true);

return t.run(o, function(o) {


if (k) {
each(tinymce.grep(o.childNodes), function(c) {
n.appendChild(c);
});
}

return o.parentNode.replaceChild(n, o);


});
},

rename : function(elm, name) {


var t = this, newElm;

if (elm.nodeName != name.toUpperCase()) {
// Rename block element
newElm = t.create(name);

// Copy attribs to new block


each(t.getAttribs(elm), function(attr_node) {
t.setAttrib(newElm, attr_node.nodeName,
t.getAttrib(elm, attr_node.nodeName));
});

// Replace block
t.replace(newElm, elm, 1);
}

return newElm || elm;


},

findCommonAncestor : function(a, b) {
var ps = a, pe;

while (ps) {
pe = b;

while (pe && ps != pe)


pe = pe.parentNode;

if (ps == pe)
break;

ps = ps.parentNode;
}

if (!ps && a.ownerDocument)


return a.ownerDocument.documentElement;

return ps;
},

toHex : function(s) {
var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-
9]+)\s*?\)\s*$/i.exec(s);

function hex(s) {
s = parseInt(s, 10).toString(16);

return s.length > 1 ? s : '0' + s; // 0 -> 00


};

if (c) {
s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);

return s;
}

return s;
},

getClasses : function() {
var t = this, cl = [], i, lo = {}, f = t.settings.class_filter,
ov;

if (t.classes)
return t.classes;

function addClasses(s) {
// IE style imports
each(s.imports, function(r) {
addClasses(r);
});

each(s.cssRules || s.rules, function(r) {


// Real type or fake it on IE
switch (r.type || 1) {
// Rule
case 1:
if (r.selectorText) {
each(r.selectorText.split(','),
function(v) {
v = v.replace(/^\s*|\s*$|
^\s\./g, "");

// Is internal or it doesn't
contain a class
// added s_ for custom internal use
if (/\.(mce|s_)/.test(v)
|| !/\.[\w\-]+$/.test(v))
return;

// Remove everything but


class name
ov = v;
v = tinymce._replace(/.*\.
([a-z0-9_\-]+).*/i, '$1', v);

// Filter classes
if (f && !(v = f(v, ov)))
return;

if (!lo[v]) {
cl.push({'class' : v});
lo[v] = 1;
}
});
}
break;

// Import
case 3:
addClasses(r.styleSheet);
break;
}
});
};

try {
each(t.doc.styleSheets, addClasses);
} catch (ex) {
// Ignore
}

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));
}
});

return o;
}
return f.call(s, e);
},

getAttribs : function(n) {
var o;

n = this.get(n);

if (!n)
return [];

if (isIE) {
o = [];

// Object will throw exception in IE


if (n.nodeName == 'OBJECT')
return n.attributes;

// IE doesn't keep the selected attribute if you clone


option elements
if (n.nodeName === 'OPTION' && this.getAttrib(n,
'selected'))
o.push({specified : 1, nodeName : 'selected'});

// It's crazy that this is faster in IE but it's because it


returns all attributes all the time
n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"]
[^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
o.push({specified : 1, nodeName : a});
});

return o;
}

return n.attributes;
},

isEmpty : function(node, elements) {


var self = this, i, attributes, type, walker, name, brCount = 0;

node = node.firstChild;
if (node) {
walker = new tinymce.dom.TreeWalker(node, node.parentNode);
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 <img />


name = node.nodeName.toLowerCase();
if (elements && elements[name]) {
// Ignore single BR elements in blocks
like <p><br /></p> or <p><span><br /></span></p>
if (name === 'br') {
brCount++;
continue;
}

return false;
}

// Keep elements with data-bookmark attributes


or name attribute like <a name="1"></a>
attributes = self.getAttribs(node);
i = node.attributes.length;
while (i--) {
name = node.attributes[i].nodeName;
if (name === "name" || name === 'data-
mce-bookmark')
return false;
}
}

// Keep comment nodes


if (type == 8)
return false;

// Keep non whitespace text nodes


if ((type === 3 && !
whiteSpaceRegExp.test(node.nodeValue)))
return false;
} while (node = walker.next());
}

return brCount <= 1;


},

destroy : function(s) {
var t = this;

t.win = t.doc = t.root = t.events = t.frag = null;

// Manual destroy then remove unload handler


if (!s)
tinymce.removeUnload(t.destroy);
},

createRng : function() {
var d = this.doc;

return d.createRange ? d.createRange() : new


tinymce.dom.Range(this);
},

nodeIndex : function(node, normalized) {


var idx = 0, lastNodeType, lastNode, nodeType;

if (node) {
for (lastNodeType = node.nodeType, node =
node.previousSibling, lastNode = node; node; node = node.previousSibling) {
nodeType = node.nodeType;
// Normalize text nodes
if (normalized && nodeType == 3) {
if (nodeType == lastNodeType || !
node.nodeValue.length)
continue;
}
idx++;
lastNodeType = nodeType;
}
}

return idx;
},

split : function(pe, e, re) {


var t = this, r = t.createRng(), bef, aft, pa;

// W3C valid browsers tend to leave empty nodes to the left/right


side of the contents, this makes sense
// but we don't want that in our code since it serves no purpose
for the end user
// For example if this is chopped:
// <p>text 1<span><b>CHOP</b></span>text 2</p>
// would produce:
// <p>text 1<span></span></p><b>CHOP</b><p><span></span>text
2</p>
// this function will then trim of empty edges and produce:
// <p>text 1</p><b>CHOP</b><p>text 2</p>
function trim(node) {
var i, children = node.childNodes, type = node.nodeType;

function surroundedBySpans(node) {
var previousIsSpan = node.previousSibling &&
node.previousSibling.nodeName == 'SPAN';
var nextIsSpan = node.nextSibling &&
node.nextSibling.nodeName == 'SPAN';
return previousIsSpan && nextIsSpan;
}

if (type == 1 && node.getAttribute('data-mce-type') ==


'bookmark')
return;

for (i = children.length - 1; i >= 0; i--)


trim(children[i]);

if (type != 9) {
// Keep non whitespace text nodes
if (type == 3 && node.nodeValue.length > 0) {
// If parent element isn't a block or there
isn't any useful contents for example "<p> </p>"
// Also keep text nodes with only spaces if
surrounded by spans.
// eg. "<p><span>a</span> <span>b</span></p>"
should keep space between a and b
var trimmedLength =
tinymce.trim(node.nodeValue).length;
if (!t.isBlock(node.parentNode) ||
trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
return;
} else if (type == 1) {
// If the only child is a bookmark then move it
up
children = node.childNodes;
if (children.length == 1 && children[0] &&
children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') ==
'bookmark')
node.parentNode.insertBefore(children[0],
node);

// Keep non empty elements or img, hr etc


if (children.length || /^(br|hr|input|img)
$/i.test(node.nodeName))
return;
}

t.remove(node);
}

return node;
};

if (pe && e) {
// Get before chunk
r.setStart(pe.parentNode, t.nodeIndex(pe));
r.setEnd(e.parentNode, t.nodeIndex(e));
bef = r.extractContents();

// Get after chunk


r = t.createRng();
r.setStart(e.parentNode, t.nodeIndex(e) + 1);
r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
aft = r.extractContents();

// Insert before chunk


pa = pe.parentNode;
pa.insertBefore(trim(bef), pe);

// Insert middle chunk


if (re)
pa.replaceChild(re, e);
else
pa.insertBefore(e, pe);

// Insert after chunk


pa.insertBefore(trim(aft), pe);
t.remove(pe);

return re || e;
}
},

bind : function(target, name, func, scope) {


return this.events.add(target, name, func, scope || this);
},

unbind : function(target, name, func) {


return this.events.remove(target, name, func);
},

fire : function(target, name, evt) {


return this.events.fire(target, name, evt);
},

// Returns the content editable state of a node


getContentEditable: function(node) {
var contentEditable;

// Check type
if (node.nodeType != 1) {
return null;
}

// Check for fake content editable


contentEditable = node.getAttribute("data-mce-contenteditable");
if (contentEditable && contentEditable !== "inherit") {
return contentEditable;
}

// Check for real content editable


return node.contentEditable !== "inherit" ?
node.contentEditable : null;
},

_findSib : function(node, selector, name) {


var t = this, f = selector;

if (node) {
// If expression make a function of it using is
if (is(f, 'string')) {
f = function(node) {
return t.is(node, selector);
};
}

// 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);
}

/*
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);
}
*/

/*
toRGB : function(s) {
var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?
$/.exec(s);

if (c) {
// #FFF -> #FFFFFF
if (!is(c[3]))
c[3] = c[2] = c[1];

return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2],


16) + "," + parseInt(c[3], 16) + ")";
}

return s;
}
*/
});

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,

// 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,
toStringIE : toStringIE
});

function createDocumentFragment() {
return doc.createDocumentFragment();
};

function setStart(n, o) {
_setEndPoint(TRUE, n, o);
};

function setEnd(n, o) {
_setEndPoint(FALSE, n, o);
};

function setStartBefore(n) {
setStart(n.parentNode, nodeIndex(n));
};

function setStartAfter(n) {
setStart(n.parentNode, nodeIndex(n) + 1);
};

function setEndBefore(n) {
setEnd(n.parentNode, nodeIndex(n));
};

function setEndAfter(n) {
setEnd(n.parentNode, nodeIndex(n) + 1);
};

function collapse(ts) {
if (ts) {
t[END_CONTAINER] = t[START_CONTAINER];
t[END_OFFSET] = t[START_OFFSET];
} else {
t[START_CONTAINER] = t[END_CONTAINER];
t[START_OFFSET] = t[END_OFFSET];
}

t.collapsed = TRUE;
};

function selectNode(n) {
setStartBefore(n);
setEndAfter(n);
};

function selectNodeContents(n) {
setStart(n, 0);
setEnd(n, n.nodeType === 1 ? n.childNodes.length :
n.nodeValue.length);
};

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;

// 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 deleteContents() {
_traverse(DELETE);
};

function extractContents() {
return _traverse(EXTRACT);
};

function cloneContents() {
return _traverse(CLONE);
};

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];

if (o)
startContainer.insertBefore(n, o);
else
startContainer.appendChild(n);
}
};

function surroundContents(n) {
var f = t.extractContents();

t.insertNode(n);
n.appendChild(f);
t.selectNode(n);
};

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
});
};

// Private methods

function _getSelectedNode(container, offset) {


var child;

if (container.nodeType == 3 /* TEXT_NODE */)


return container;

if (offset < 0)
return container;

child = container.firstChild;
while (child && offset > 0) {
--offset;
child = child.nextSibling;
}

if (child)
return child;

return container;
};

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

if (offsetA < offsetB)


return -1; // before

return 1; // after
}

// 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;

if (c) {
offsetC = 0;
n = containerA.firstChild;

while (n != c && offsetC < offsetA) {


offsetC++;
n = n.nextSibling;
}

if (offsetA <= offsetC)


return -1; // before

return 1; // after
}
// In the third case a child node C of the container of B is an
ancestor container
// of A. In this case, A is before B if the index of the child
node C is less than
// the offset of B and A is after B otherwise.
c = containerA;
while (c && c.parentNode != containerB) {
c = c.parentNode;
}

if (c) {
offsetC = 0;
n = containerB.firstChild;

while (n != c && offsetC < offsetB) {


offsetC++;
n = n.nextSibling;
}

if (offsetC < offsetB)


return -1; // before

return 1; // after
}

// In the fourth case, none of three other cases hold: the


containers of A and B
// are siblings or descendants of sibling nodes. In this case, A
is before B if
// the container of A is before the container of B in a pre-order
traversal of the
// Ranges' context tree and A is after B otherwise.
cmnRoot = dom.findCommonAncestor(containerA, containerB);
childA = containerA;

while (childA && childA.parentNode != cmnRoot)


childA = childA.parentNode;

if (!childA)
childA = cmnRoot;

childB = containerB;
while (childB && childB.parentNode != cmnRoot)
childB = childB.parentNode;

if (!childB)
childB = cmnRoot;

if (childA == childB)
return 0; // equal

n = cmnRoot.firstChild;
while (n) {
if (n == childA)
return -1; // before

if (n == childB)
return 1; // after
n = n.nextSibling;
}
};

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;
}

// 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;

sc = t[START_CONTAINER];
while (sc.parentNode)
sc = sc.parentNode;

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);

t.collapsed = _isCollapsed();
t.commonAncestorContainer =
dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
};

function _traverse(how) {
var c, endContainerDepth = 0, startContainerDepth = 0, p,
depthDiff, startNode, endNode, sp, ep;

if (t[START_CONTAINER] == t[END_CONTAINER])
return _traverseSameContainer(how);

for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p =


p.parentNode) {
if (p == t[START_CONTAINER])
return _traverseCommonStartContainer(c, how);

++endContainerDepth;
}

for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p =


p.parentNode) {
if (p == t[END_CONTAINER])
return _traverseCommonEndContainer(c, how);

++startContainerDepth;
}

depthDiff = startContainerDepth - endContainerDepth;

startNode = t[START_CONTAINER];
while (depthDiff > 0) {
startNode = startNode.parentNode;
depthDiff--;
}

endNode = t[END_CONTAINER];
while (depthDiff < 0) {
endNode = endNode.parentNode;
depthDiff++;
}

// ascend the ancestor hierarchy until we have a common parent.


for (sp = startNode.parentNode, ep = endNode.parentNode; sp !=
ep; sp = sp.parentNode, ep = ep.parentNode) {
startNode = sp;
endNode = ep;
}

return _traverseCommonAncestors(startNode, endNode, how);


};

function _traverseSameContainer(how) {
var frag, s, sub, n, cnt, sibling, xferNode, start, len;

if (how != DELETE)
frag = createDocumentFragment();

// If selection is empty, just return the fragment


if (t[START_OFFSET] == t[END_OFFSET])
return frag;

// Text node needs special case handling


if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
// get the substring
s = t[START_CONTAINER].nodeValue;
sub = s.substring(t[START_OFFSET], t[END_OFFSET]);

// set the original text node to its new value


if (how != CLONE) {
n = t[START_CONTAINER];
start = t[START_OFFSET];
len = t[END_OFFSET] - t[START_OFFSET];
if (start === 0 && len >= n.nodeValue.length - 1) {
n.parentNode.removeChild(n);
} else {
n.deleteData(start, len);
}

// Nothing is partially selected, so collapse to


start point
t.collapse(TRUE);
}

if (how == DELETE)
return;

if (sub.length > 0) {
frag.appendChild(doc.createTextNode(sub));
}

return frag;
}

// Copy nodes between the start/end offsets.


n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
cnt = t[END_OFFSET] - t[START_OFFSET];

while (n && cnt > 0) {


sibling = n.nextSibling;
xferNode = _traverseFullySelected(n, how);

if (frag)
frag.appendChild( xferNode );

--cnt;
n = sibling;
}

// Nothing is partially selected, so collapse to start point


if (how != CLONE)
t.collapse(TRUE);

return frag;
};

function _traverseCommonStartContainer(endAncestor, how) {


var frag, n, endIdx, cnt, sibling, xferNode;

if (how != DELETE)
frag = createDocumentFragment();

n = _traverseRightBoundary(endAncestor, how);

if (frag)
frag.appendChild(n);

endIdx = nodeIndex(endAncestor);
cnt = endIdx - t[START_OFFSET];

if (cnt <= 0) {
// Collapse to just before the endAncestor, which
// is partially selected.
if (how != CLONE) {
t.setEndBefore(endAncestor);
t.collapse(FALSE);
}

return frag;
}

n = endAncestor.previousSibling;
while (cnt > 0) {
sibling = n.previousSibling;
xferNode = _traverseFullySelected(n, how);

if (frag)
frag.insertBefore(xferNode, frag.firstChild);

--cnt;
n = sibling;
}

// Collapse to just before the endAncestor, which


// is partially selected.
if (how != CLONE) {
t.setEndBefore(endAncestor);
t.collapse(FALSE);
}

return frag;
};

function _traverseCommonEndContainer(startAncestor, how) {


var frag, startIdx, n, cnt, sibling, xferNode;

if (how != DELETE)
frag = 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 (n && cnt > 0) {
sibling = n.nextSibling;
xferNode = _traverseFullySelected(n, how);

if (frag)
frag.appendChild(xferNode);

--cnt;
n = sibling;
}

if (how != CLONE) {
t.setStartAfter(startAncestor);
t.collapse(TRUE);
}

return frag;
};

function _traverseCommonAncestors(startAncestor, endAncestor, how) {


var n, frag, commonParent, startOffset, endOffset, cnt, sibling,
nextSibling;

if (how != DELETE)
frag = createDocumentFragment();

n = _traverseLeftBoundary(startAncestor, how);
if (frag)
frag.appendChild(n);

commonParent = startAncestor.parentNode;
startOffset = nodeIndex(startAncestor);
endOffset = nodeIndex(endAncestor);
++startOffset;

cnt = endOffset - startOffset;


sibling = startAncestor.nextSibling;

while (cnt > 0) {


nextSibling = sibling.nextSibling;
n = _traverseFullySelected(sibling, how);

if (frag)
frag.appendChild(n);

sibling = nextSibling;
--cnt;
}

n = _traverseRightBoundary(endAncestor, how);

if (frag)
frag.appendChild(n);

if (how != CLONE) {
t.setStartAfter(startAncestor);
t.collapse(TRUE);
}

return frag;
};

function _traverseRightBoundary(root, how) {


var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1),
parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected
= next != t[END_CONTAINER];

if (next == root)
return _traverseNode(next, isFullySelected, FALSE, how);

parent = next.parentNode;
clonedParent = _traverseNode(parent, FALSE, FALSE, how);
while (parent) {
while (next) {
prevSibling = next.previousSibling;
clonedChild = _traverseNode(next, isFullySelected,
FALSE, how);

if (how != DELETE)
clonedParent.insertBefore(clonedChild,
clonedParent.firstChild);

isFullySelected = TRUE;
next = prevSibling;
}

if (parent == root)
return clonedParent;

next = parent.previousSibling;
parent = parent.parentNode;

clonedGrandParent = _traverseNode(parent, FALSE, FALSE,


how);

if (how != DELETE)
clonedGrandParent.appendChild(clonedParent);

clonedParent = clonedGrandParent;
}
};

function _traverseLeftBoundary(root, how) {


var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]),
isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling,
clonedChild, clonedGrandParent;

if (next == root)
return _traverseNode(next, isFullySelected, TRUE, how);

parent = next.parentNode;
clonedParent = _traverseNode(parent, FALSE, TRUE, how);

while (parent) {
while (next) {
nextSibling = next.nextSibling;
clonedChild = _traverseNode(next, isFullySelected,
TRUE, how);

if (how != DELETE)
clonedParent.appendChild(clonedChild);

isFullySelected = TRUE;
next = nextSibling;
}

if (parent == root)
return clonedParent;

next = parent.nextSibling;
parent = parent.parentNode;

clonedGrandParent = _traverseNode(parent, FALSE, TRUE,


how);

if (how != DELETE)
clonedGrandParent.appendChild(clonedParent);

clonedParent = clonedGrandParent;
}
};

function _traverseNode(n, isFullySelected, isLeft, how) {


var txtValue, newNodeValue, oldNodeValue, offset, newNode;

if (isFullySelected)
return _traverseFullySelected(n, how);

if (n.nodeType == 3 /* TEXT_NODE */) {


txtValue = n.nodeValue;

if (isLeft) {
offset = t[START_OFFSET];
newNodeValue = txtValue.substring(offset);
oldNodeValue = txtValue.substring(0, offset);
} else {
offset = t[END_OFFSET];
newNodeValue = txtValue.substring(0, offset);
oldNodeValue = txtValue.substring(offset);
}

if (how != CLONE)
n.nodeValue = oldNodeValue;

if (how == DELETE)
return;

newNode = dom.clone(n, FALSE);


newNode.nodeValue = newNodeValue;

return newNode;
}

if (how == DELETE)
return;

return dom.clone(n, FALSE);


};

function _traverseFullySelected(n, how) {


if (how != DELETE)
return how == CLONE ? dom.clone(n, TRUE) : n;

n.parentNode.removeChild(n);
};

function toStringIE() {
return dom.create('body', null, cloneContents()).outerText;
}
return t;
};

ns.Range = Range;

// Older IE versions doesn't let you override toString by it's constructor so


we have to stick it in the prototype
Range.prototype.toString = function() {
return this.toStringIE();
};
})(tinymce.dom);

(function() {
function Selection(selection) {
var self = this, dom = selection.dom, TRUE = true, FALSE = false;

function getPosition(rng, start) {


var checkRng, startIndex = 0, endIndex, inside,
children, child, offset, index, position = -1, parent;

// Setup test range, collapse it and get the parent


checkRng = rng.duplicate();
checkRng.collapse(start);
parent = checkRng.parentElement();

// Check if the selection is within the right document


if (parent.ownerDocument !== selection.dom.doc)
return;

// IE will report non editable elements as it's parent so look


for an editable one
while (parent.contentEditable === "false") {
parent = parent.parentNode;
}

// If parent doesn't have any children then return that we are


inside the element
if (!parent.hasChildNodes()) {
return {node : parent, inside : 1};
}

// Setup node list and endIndex


children = parent.children;
endIndex = children.length - 1;

// Perform a binary search for the position


while (startIndex <= endIndex) {
index = Math.floor((startIndex + endIndex) / 2);

// Move selection to node and compare the ranges


child = children[index];
checkRng.moveToElementText(child);
position = checkRng.compareEndPoints(start ? 'StartToStart'
: 'EndToEnd', rng);

// Before/after or an exact match


if (position > 0) {
endIndex = index - 1;
} else if (position < 0) {
startIndex = index + 1;
} else {
return {node : child};
}
}

// Check if child position is before or we didn't find a position


if (position < 0) {
// No element child was found use the parent element and
the offset inside that
if (!child) {
checkRng.moveToElementText(parent);
checkRng.collapse(true);
child = parent;
inside = true;
} else
checkRng.collapse(false);

// Walk character by character in text node until we hit


the selected range endpoint, hit the end of document or parent isn't the right one
// We need to walk char by char since rng.text or
rng.htmlText will trim line endings
offset = 0;
while (checkRng.compareEndPoints(start ? 'StartToStart' :
'StartToEnd', rng) !== 0) {
if (checkRng.move('character', 1) === 0 || parent !=
checkRng.parentElement()) {
break;
}

offset++;
}
} else {
// Child position is after the selection endpoint
checkRng.collapse(true);

// Walk character by character in text node until we hit


the selected range endpoint, hit the end of document or parent isn't the right one
offset = 0;
while (checkRng.compareEndPoints(start ? 'StartToStart' :
'StartToEnd', rng) !== 0) {
if (checkRng.move('character', -1) === 0 || parent !=
checkRng.parentElement()) {
break;
}

offset++;
}
}

return {node : child, position : position, offset : offset,


inside : inside};
};

// Returns a W3C DOM compatible range object by using the IE Range API
function getRange() {
var ieRange = selection.getRng(), domRange = dom.createRng(),
element, collapsed, tmpRange, element2, bookmark, fail;
// If selection is outside the current document just return an
empty range
element = ieRange.item ? ieRange.item(0) :
ieRange.parentElement();
if (element.ownerDocument != dom.doc)
return domRange;

collapsed = selection.isCollapsed();

// Handle control selection


if (ieRange.item) {
domRange.setStart(element.parentNode,
dom.nodeIndex(element));
domRange.setEnd(domRange.startContainer,
domRange.startOffset + 1);

return domRange;
}

function findEndPoint(start) {
var endPoint = getPosition(ieRange, start), container,
offset, textNodeOffset = 0, sibling, undef, nodeValue;

container = endPoint.node;
offset = endPoint.offset;

if (endPoint.inside && !container.hasChildNodes()) {


domRange[start ? 'setStart' : 'setEnd'](container,
0);
return;
}

if (offset === undef) {


domRange[start ? 'setStartBefore' : 'setEndAfter']
(container);
return;
}

if (endPoint.position < 0) {
sibling = endPoint.inside ? container.firstChild :
container.nextSibling;

if (!sibling) {
domRange[start ? 'setStartAfter' :
'setEndAfter'](container);
return;
}

if (!offset) {
if (sibling.nodeType == 3)
domRange[start ? 'setStart' : 'setEnd']
(sibling, 0);
else
domRange[start ? 'setStartBefore' :
'setEndBefore'](sibling);

return;
}
// Find the text node and offset
while (sibling) {
nodeValue = sibling.nodeValue;
textNodeOffset += nodeValue.length;

// We are at or passed the position we where


looking for
if (textNodeOffset >= offset) {
container = sibling;
textNodeOffset -= offset;
textNodeOffset = nodeValue.length -
textNodeOffset;
break;
}

sibling = sibling.nextSibling;
}
} else {
// Find the text node and offset
sibling = container.previousSibling;

if (!sibling)
return domRange[start ? 'setStartBefore' :
'setEndBefore'](container);

// If there isn't any text to loop then use the first


position
if (!offset) {
if (container.nodeType == 3)
domRange[start ? 'setStart' : 'setEnd']
(sibling, container.nodeValue.length);
else
domRange[start ? 'setStartAfter' :
'setEndAfter'](sibling);

return;
}

while (sibling) {
textNodeOffset += sibling.nodeValue.length;

// We are at or passed the position we where


looking for
if (textNodeOffset >= offset) {
container = sibling;
textNodeOffset -= offset;
break;
}

sibling = sibling.previousSibling;
}
}

domRange[start ? 'setStart' : 'setEnd'](container,


textNodeOffset);
};

try {
// Find start point
findEndPoint(true);

// Find end point if needed


if (!collapsed)
findEndPoint();
} catch (ex) {
// IE has a nasty bug where text nodes might throw "invalid
argument" when you
// access the nodeValue or other properties of text nodes.
This seems to happend when
// text nodes are split into two nodes by a
delete/backspace call. So lets detect it and try to fix it.
if (ex.number == -2147024809) {
// Get the current selection
bookmark = self.getBookmark(2);

// Get start element


tmpRange = ieRange.duplicate();
tmpRange.collapse(true);
element = tmpRange.parentElement();

// Get end element


if (!collapsed) {
tmpRange = ieRange.duplicate();
tmpRange.collapse(false);
element2 = tmpRange.parentElement();
element2.innerHTML = element2.innerHTML;
}

// Remove the broken elements


element.innerHTML = element.innerHTML;

// Restore the selection


self.moveToBookmark(bookmark);

// Since the range has moved we need to re-get it


ieRange = selection.getRng();

// Find start point


findEndPoint(true);

// Find end point if needed


if (!collapsed)
findEndPoint();
} else
throw ex; // Throw other errors
}

return domRange;
};

this.getBookmark = function(type) {
var rng = selection.getRng(), start, end, bookmark = {};

function getIndexes(node) {
var parent, root, children, i, indexes = [];

parent = node.parentNode;
root = dom.getRoot().parentNode;

while (parent != root && parent.nodeType !== 9) {


children = parent.children;

i = children.length;
while (i--) {
if (node === children[i]) {
indexes.push(i);
break;
}
}

node = parent;
parent = parent.parentNode;
}

return indexes;
};

function getBookmarkEndPoint(start) {
var position;

position = getPosition(rng, start);


if (position) {
return {
position : position.position,
offset : position.offset,
indexes : getIndexes(position.node),
inside : position.inside
};
}
};

// Non ubstructive bookmark


if (type === 2) {
// Handle text selection
if (!rng.item) {
bookmark.start = getBookmarkEndPoint(true);

if (!selection.isCollapsed())
bookmark.end = getBookmarkEndPoint();
} else
bookmark.start = {ctrl : true, indexes :
getIndexes(rng.item(0))};
}

return bookmark;
};

this.moveToBookmark = function(bookmark) {
var rng, body = dom.doc.body;

function resolveIndexes(indexes) {
var node, i, idx, children;

node = dom.getRoot();
for (i = indexes.length - 1; i >= 0; i--) {
children = node.children;
idx = indexes[i];

if (idx <= children.length - 1) {


node = children[idx];
}
}

return node;
};

function setBookmarkEndPoint(start) {
var endPoint = bookmark[start ? 'start' : 'end'], moveLeft,
moveRng, undef;

if (endPoint) {
moveLeft = endPoint.position > 0;

moveRng = body.createTextRange();

moveRng.moveToElementText(resolveIndexes(endPoint.indexes));

offset = endPoint.offset;
if (offset !== undef) {
moveRng.collapse(endPoint.inside || moveLeft);
moveRng.moveStart('character', moveLeft ?
-offset : offset);
} else
moveRng.collapse(start);

rng.setEndPoint(start ? 'StartToStart' :
'EndToStart', moveRng);

if (start)
rng.collapse(true);
}
};

if (bookmark.start) {
if (bookmark.start.ctrl) {
rng = body.createControlRange();

rng.addElement(resolveIndexes(bookmark.start.indexes));
rng.select();
} else {
rng = body.createTextRange();
setBookmarkEndPoint(true);
setBookmarkEndPoint();
rng.select();
}
}
};

this.addRange = function(rng) {
var ieRng, ctrlRng, startContainer, startOffset, endContainer,
endOffset, sibling,
doc = selection.dom.doc, body = doc.body, nativeRng,
ctrlElm;

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 if (container.canHaveHTML) {
// Empty node selection for example <div>|
</div>
// Setting innerHTML with a span marker then
remove that marker seems to keep empty block elements open
container.innerHTML = '<span>\uFEFF</span>';
marker = container.firstChild;
tmpRng.moveToElementText(marker);
tmpRng.collapse(FALSE); // Collapse false works
better than true for some odd reason
}

ieRng.setEndPoint(start ? 'StartToStart' :
'EndToEnd', tmpRng);
dom.remove(marker);
}
}

// Setup some shorter versions


startContainer = rng.startContainer;
startOffset = rng.startOffset;
endContainer = rng.endContainer;
endOffset = rng.endOffset;
ieRng = body.createTextRange();

// If single element selection then try making a control


selection out of it
if (startContainer == endContainer && startContainer.nodeType ==
1) {
// Trick to place the caret inside an empty block element
like <p></p>
if (startOffset == endOffset && !
startContainer.hasChildNodes()) {
if (startContainer.canHaveHTML) {
// Check if previous sibling is an empty block
if it is then we need to render it
// IE would otherwise move the caret into the
sibling instead of the empty startContainer see: #5236
// Example this: <p></p><p>|</p> would become
this: <p>|</p><p></p>
sibling = startContainer.previousSibling;
if (sibling && !sibling.hasChildNodes() &&
dom.isBlock(sibling)) {
sibling.innerHTML = '\uFEFF';
} else {
sibling = null;
}

startContainer.innerHTML =
'<span>\uFEFF</span><span>\uFEFF</span>';

ieRng.moveToElementText(startContainer.lastChild);
ieRng.select();
dom.doc.selection.clear();
startContainer.innerHTML = '';

if (sibling) {
sibling.innerHTML = '';
}
return;
} else {
startOffset = dom.nodeIndex(startContainer);
startContainer = startContainer.parentNode;
}
}

if (startOffset == endOffset - 1) {
try {
ctrlElm =
startContainer.childNodes[startOffset];
ctrlRng = body.createControlRange();
ctrlRng.addElement(ctrlElm);
ctrlRng.select();

// Check if the range produced is on the


correct element and is a control range
// On IE 8 it will select the parent
contentEditable container if you select an inner element see: #5398
nativeRng = selection.getRng();
if (nativeRng.item && ctrlElm ===
nativeRng.item(0)) {
return;
}
} catch (ex) {
// Ignore
}
}
}

// Set start/end point of selection


setEndPoint(true);
setEndPoint();

// Select the new range and scroll it into view


ieRng.select();
};

// Expose range method


this.getRangeAt = getRange;
};

// Expose the selection object


tinymce.dom.TridentSelection = Selection;
})();

/*
* Sizzle CSS Selector Engine
* Copyright, The Dojo Foundation
* Released under the MIT, BSD, and GPL Licenses.
* More information: http://sizzlejs.com/
*/
(function(){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\


[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
expando = "sizcache",
done = 0,
toString = Object.prototype.toString,
hasDuplicate = false,
baseHasDuplicate = true,
rBackslash = /\\/g,
rReturn = /\r\n/g,
rNonWord = /\W/;

// 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 m, set, checkSet, extra, ret, cur, pop, i,


prune = true,
contextXML = Sizzle.isXML( context ),
parts = [],
soFar = selector;

// 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, seed );

} 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, seed );


}
}

} 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.matchesSelector = function( node, expr ) {


return Sizzle( expr, null, null, [node] ).length > 0;
};

Sizzle.find = function( expr, context, isXML ) {


var set, i, len, match, type, left;

if ( !expr ) {
return [];
}

for ( i = 0, len = Expr.order.length; i < len; i++ ) {


type = Expr.order[i];

if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {


left = match[1];
match.splice( 1, 1 );

if ( left.substr( left.length - 1 ) !== "\\" ) {


match[1] = (match[1] || "").replace( rBackslash, "" );
set = Expr.find[ type ]( match, context, isXML );

if ( set != null ) {
expr = expr.replace( Expr.match[ type ], "" );
break;
}
}
}
}

if ( !set ) {
set = typeof context.getElementsByTagName !== "undefined" ?
context.getElementsByTagName( "*" ) :
[];
}

return { set: set, expr: expr };


};

Sizzle.filter = function( expr, set, inplace, not ) {


var match, anyFound,
type, found, item, filter, left,
i, pass,
old = expr,
result = [],
curLoop = set,
isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );

while ( expr && set.length ) {


for ( type in Expr.filter ) {
if ( (match = Expr.leftMatch[ type ].exec( expr )) != null &&
match[2] ) {
filter = Expr.filter[ type ];
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 ( i = 0; (item = curLoop[i]) != null; i++ ) {
if ( item ) {
found = filter( item, match, i,
curLoop );
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 new Error( "Syntax error, unrecognized expression: " + msg );
};

var getText = Sizzle.getText = function( elem ) {


var i, node,
nodeType = elem.nodeType,
ret = "";

if ( nodeType ) {
if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
// Use textContent || innerText for elements
if ( typeof elem.textContent === 'string' ) {
return elem.textContent;
} else if ( typeof elem.innerText === 'string' ) {
// Replace IE's carriage returns
return elem.innerText.replace( rReturn, '' );
} else {
// Traverse it's children
for ( elem = elem.firstChild; elem; elem =
elem.nextSibling) {
ret += getText( elem );
}
}
} else if ( nodeType === 3 || nodeType === 4 ) {
return elem.nodeValue;
}
} else {

// If no nodeType, this is expected to be an array


for ( i = 0; (node = elem[i]); i++ ) {
// Do not traverse comment nodes
if ( node.nodeType !== 8 ) {
ret += getText( node );
}
}
}
return ret;
};

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|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:
[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
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" );
},
type: function( elem ) {
return elem.getAttribute( "type" );
}
},

relative: {
"+": function(checkSet, part){
var isPartStr = typeof part === "string",
isTag = isPartStr && !rNonWord.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 elem,
isPartStr = typeof part === "string",
i = 0,
l = checkSet.length;

if ( isPartStr && !rNonWord.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 nodeCheck,
doneName = done++,
checkFn = dirCheck;

if ( typeof part === "string" && !rNonWord.test( part ) ) {


part = part.toLowerCase();
nodeCheck = part;
checkFn = dirNodeCheck;
}

checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML


);
},

"~": function( checkSet, part, isXML ) {


var nodeCheck,
doneName = done++,
checkFn = dirCheck;

if ( typeof part === "string" && !rNonWord.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]);
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
return m && m.parentNode ? [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 ) {


if ( typeof context.getElementsByTagName !== "undefined" ) {
return context.getElementsByTagName( match[1] );
}
}
},
preFilter: {
CLASS: function( match, curLoop, inplace, result, not, isXML ) {
match = " " + match[1].replace( rBackslash, "" ) + " ";

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\r]/g, " ").indexOf(match) >= 0) ) {
if ( !inplace ) {
result.push( elem );
}

} else if ( inplace ) {
curLoop[i] = false;
}
}
}

return false;
},

ID: function( match ) {


return match[1].replace( rBackslash, "" );
},

TAG: function( match, curLoop ) {


return match[1].replace( rBackslash, "" ).toLowerCase();
},

CHILD: function( match ) {


if ( match[1] === "nth" ) {
if ( !match[2] ) {
Sizzle.error( match[0] );
}

match[2] = match[2].replace(/^\+|\s*/g, '');

// 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;
}
else if ( match[2] ) {
Sizzle.error( match[0] );
}

// TODO: Move to normal caching system


match[0] = done++;

return match;
},

ATTR: function( match, curLoop, inplace, result, not, isXML ) {


var name = match[1] = match[1].replace( rBackslash, "" );

if ( !isXML && Expr.attrMap[name] ) {


match[1] = Expr.attrMap[name];
}

// Handle if an un-quoted value was used


match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, ""
);

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
if ( elem.parentNode ) {
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 ) {
var attr = elem.getAttribute( "type" ), type = elem.type;
// IE6 and 7 will map elem.type to 'text' for new HTML5 types
(search, etc)
// use getAttribute instead to test this case
return elem.nodeName.toLowerCase() === "input" && "text" === type
&& ( attr === type || attr === null );
},

radio: function( elem ) {


return elem.nodeName.toLowerCase() === "input" && "radio" ===
elem.type;
},

checkbox: function( elem ) {


return elem.nodeName.toLowerCase() === "input" && "checkbox" ===
elem.type;
},

file: function( elem ) {


return elem.nodeName.toLowerCase() === "input" && "file" ===
elem.type;
},

password: function( elem ) {


return elem.nodeName.toLowerCase() === "input" && "password" ===
elem.type;
},

submit: function( elem ) {


var name = elem.nodeName.toLowerCase();
return (name === "input" || name === "button") && "submit" ===
elem.type;
},

image: function( elem ) {


return elem.nodeName.toLowerCase() === "input" && "image" ===
elem.type;
},

reset: function( elem ) {


var name = elem.nodeName.toLowerCase();
return (name === "input" || name === "button") && "reset" ===
elem.type;
},

button: function( elem ) {


var name = elem.nodeName.toLowerCase();
return name === "input" && "button" === elem.type || name ===
"button";
},

input: function( elem ) {


return (/input|select|textarea|button/i).test( elem.nodeName );
},

focus: function( elem ) {


return elem === elem.ownerDocument.activeElement;
}
},
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 ||
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( name );
}
},

CHILD: function( elem, match ) {


var first, last,
doneName, parent, cache,
count, diff,
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;

/* falls through */
case "last":
while ( (node = node.nextSibling) ) {
if ( node.nodeType === 1 ) {
return false;
}
}

return true;

case "nth":
first = match[2];
last = match[3];

if ( first === 1 && last === 0 ) {


return true;
}

doneName = match[0];
parent = elem.parentNode;

if ( parent && (parent[ expando ] !== doneName || !


elem.nodeIndex) ) {
count = 0;

for ( node = parent.firstChild; node; node =


node.nextSibling ) {
if ( node.nodeType === 1 ) {
node.nodeIndex = ++count;
}
}

parent[ expando ] = doneName;


}
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
&& 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 = Sizzle.attr ?
Sizzle.attr( elem, name ) :
Expr.attrHandle[ name ] ?
Expr.attrHandle[ name ]( elem ) :
elem[ name ] != null ?
elem[ name ] :
elem.getAttribute( name ),
value = result + "",
type = match[2],
check = match[4];

return result == null ?


type === "!=" :
!type && Sizzle.attr ?
result != null :
type === "=" ?
value === check :
type === "*=" ?
value.indexOf(check) >= 0 :
type === "~=" ?
(" " + value + " ").indexOf(check) >= 0 :
!check ?
value && result !== false :
type === "!=" ?
value !== check :
type === "^=" ?
value.indexOf(check) === 0 :
type === "$=" ?
value.substr(value.length - check.length) === check :
type === "|=" ?
value === check || value.substr(0, check.length + 1) ===
check + "-" :
false;
},

POS: function( elem, match, i, array ) {


var name = match[2],
filter = Expr.setFilters[ name ];

if ( filter ) {
return filter( elem, i, match, array );
}
}
}
};

var origPOS = Expr.match.POS,


fescape = function(all, num){
return "\\" + (num - 0 + 1);
};

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) );
}
// Expose origPOS
// "global" as in regardless of relation to brackets/parens
Expr.match.globalPOS = origPOS;

var makeArray = function( array, results ) {


array = Array.prototype.slice.call( array, 0 );

if ( results ) {
results.push.apply( results, array );
return results;
}

return array;
};

// Perform a simple check to determine if the browser is capable of


// converting a NodeList to an array using builtin methods.
// Also verifies that the returned array holds DOM nodes
// (which is not the case in the Blackberry browser)
try {
Array.prototype.slice.call( document.documentElement.childNodes, 0 )
[0].nodeType;

// Provide a fallback method if it does not work


} catch( e ) {
makeArray = function( array, results ) {
var i = 0,
ret = results || [];

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] );
}
}
}

return ret;
};
}

var sortOrder, siblingCheck;

if ( document.documentElement.compareDocumentPosition ) {
sortOrder = function( a, b ) {
if ( a === b ) {
hasDuplicate = true;
return 0;
}

if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
return a.compareDocumentPosition ? -1 : 1;
}

return a.compareDocumentPosition(b) & 4 ? -1 : 1;


};

} else {
sortOrder = function( a, b ) {
// The nodes are identical, we can exit early
if ( a === b ) {
hasDuplicate = true;
return 0;

// Fallback to using sourceIndex (in IE) if it's available on both


nodes
} else if ( a.sourceIndex && b.sourceIndex ) {
return a.sourceIndex - b.sourceIndex;
}

var al, bl,


ap = [],
bp = [],
aup = a.parentNode,
bup = b.parentNode,
cur = aup;

// If the nodes are siblings (or identical) we can do a quick check


if ( aup === bup ) {
return siblingCheck( a, b );

// If no parents were found then the nodes are disconnected


} else if ( !aup ) {
return -1;

} else if ( !bup ) {
return 1;
}

// Otherwise they're somewhere else in the tree so we need


// to build up a full list of the parentNodes for comparison
while ( cur ) {
ap.unshift( cur );
cur = cur.parentNode;
}

cur = bup;

while ( cur ) {
bp.unshift( cur );
cur = cur.parentNode;
}

al = ap.length;
bl = bp.length;

// Start walking down the tree looking for a discrepancy


for ( var i = 0; i < al && i < bl; i++ ) {
if ( ap[i] !== bp[i] ) {
return siblingCheck( ap[i], bp[i] );
}
}

// We ended someplace up the tree so do a sibling check


return i === al ?
siblingCheck( a, bp[i], -1 ) :
siblingCheck( ap[i], b, 1 );
};

siblingCheck = function( a, b, ret ) {


if ( a === b ) {
return ret;
}

var cur = a.nextSibling;

while ( cur ) {
if ( cur === b ) {
return -1;
}

cur = cur.nextSibling;
}

return 1;
};
}

// 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(),
root = document.documentElement;

form.innerHTML = "<a name='" + id + "'/>";

// Inject it into the root element, check its status, and remove it quickly
root.insertBefore( form, root.firstChild );

// 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 :
[];
}
};

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 );

// release memory in IE
root = form = null;
})();

(function(){
// Check to see if the browser returns only elements
// when doing getElementsByTagName("*")

// Create a fake element


var div = document.createElement("div");
div.appendChild( document.createComment("") );

// Make sure no comments are found


if ( div.getElementsByTagName("*").length > 0 ) {
Expr.find.TAG = function( match, context ) {
var results = context.getElementsByTagName( match[1] );

// Filter out possible comments


if ( match[1] === "*" ) {
var tmp = [];

for ( var i = 0; results[i]; i++ ) {


if ( results[i].nodeType === 1 ) {
tmp.push( results[i] );
}
}

results = tmp;
}

return results;
};
}

// Check to see if an attribute returns normalized href attributes


div.innerHTML = "<a href='#'></a>";

if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&


div.firstChild.getAttribute("href") !== "#" ) {

Expr.attrHandle.href = function( elem ) {


return elem.getAttribute( "href", 2 );
};
}

// release memory in IE
div = null;
})();

if ( document.querySelectorAll ) {
(function(){
var oldSizzle = Sizzle,
div = document.createElement("div"),
id = "__sizzle__";

div.innerHTML = "<p class='TEST'></p>";

// 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;

// Only use querySelectorAll on non-XML documents


// (ID selectors don't work in non-HTML documents)
if ( !seed && !Sizzle.isXML(context) ) {
// See if we find a selector to speed up
var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+
$)/.exec( query );

if ( match && (context.nodeType === 1 || context.nodeType


=== 9) ) {
// Speed-up: Sizzle("TAG")
if ( match[1] ) {
return makeArray( context.getElementsByTagName(
query ), extra );

// Speed-up: Sizzle(".CLASS")
} else if ( match[2] && Expr.find.CLASS &&
context.getElementsByClassName ) {
return
makeArray( context.getElementsByClassName( match[2] ), extra );
}
}

if ( context.nodeType === 9 ) {
// Speed-up: Sizzle("body")
// The body element only exists once, optimize
finding it
if ( query === "body" && context.body ) {
return makeArray( [ context.body ], extra );

// Speed-up: Sizzle("#ID")
} else if ( match && match[3] ) {
var elem = context.getElementById( match[3] );

// Check parentNode to catch when Blackberry


4.6 returns
// nodes that are no longer in the document
#6963
if ( elem && elem.parentNode ) {
// Handle the case where IE and Opera
return items
// by name instead of ID
if ( elem.id === match[3] ) {
return makeArray( [ elem ],
extra );
}

} else {
return makeArray( [], extra );
}
}

try {
return
makeArray( context.querySelectorAll(query), extra );
} catch(qsaError) {}

// qSA works strangely on Element-rooted queries


// We can work around this by specifying an extra ID on the
root
// and working up from there (Thanks to Andrew Dupont for
the technique)
// IE 8 doesn't work on object elements
} else if ( context.nodeType === 1 &&
context.nodeName.toLowerCase() !== "object" ) {
var oldContext = context,
old = context.getAttribute( "id" ),
nid = old || id,
hasParent = context.parentNode,
relativeHierarchySelector =
/^\s*[+~]/.test( query );

if ( !old ) {
context.setAttribute( "id", nid );
} else {
nid = nid.replace( /'/g, "\\$&" );
}
if ( relativeHierarchySelector && hasParent ) {
context = context.parentNode;
}

try {
if ( !relativeHierarchySelector || hasParent )
{
return
makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
}

} catch(pseudoError) {
} finally {
if ( !old ) {
oldContext.removeAttribute( "id" );
}
}
}
}

return oldSizzle(query, context, extra, seed);


};

for ( var prop in oldSizzle ) {


Sizzle[ prop ] = oldSizzle[ prop ];
}

// release memory in IE
div = null;
})();
}

(function(){
var html = document.documentElement,
matches = html.matchesSelector || html.mozMatchesSelector ||
html.webkitMatchesSelector || html.msMatchesSelector;

if ( matches ) {
// Check to see if it's possible to do matchesSelector
// on a disconnected node (IE 9 fails this)
var disconnectedMatch = !matches.call( document.createElement( "div" ),
"div" ),
pseudoWorks = false;

try {
// This should fail with an exception
// Gecko does not error, returns false instead
matches.call( document.documentElement, "[test!='']:sizzle" );

} catch( pseudoError ) {
pseudoWorks = true;
}

Sizzle.matchesSelector = function( node, expr ) {


// Make sure that attribute selectors are quoted
expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
if ( !Sizzle.isXML( node ) ) {
try {
if ( pseudoWorks || !Expr.match.PSEUDO.test( expr )
&& !/!=/.test( expr ) ) {
var ret = matches.call( node, expr );

// IE 9's matchesSelector returns false on


disconnected nodes
if ( ret || !disconnectedMatch ||
// As well, disconnected nodes are
said to be in a document
// fragment in IE 9, so check for
that
node.document &&
node.document.nodeType !== 11 ) {
return ret;
}
}
} catch(e) {}
}

return Sizzle(expr, null, null, [node]).length > 0;


};
}
})();

(function(){
var div = document.createElement("div");

div.innerHTML = "<div class='test e'></div><div class='test'></div>";

// 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;
}

// Safari caches class attributes, doesn't catch changes (in 3.2)


div.lastChild.className = "e";

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]);
}
};

// release memory in IE
div = null;
})();

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {


for ( var i = 0, l = checkSet.length; i < l; i++ ) {
var elem = checkSet[i];

if ( elem ) {
var match = false;

elem = elem[dir];

while ( elem ) {
if ( elem[ expando ] === doneName ) {
match = checkSet[elem.sizset];
break;
}

if ( elem.nodeType === 1 && !isXML ){


elem[ expando ] = doneName;
elem.sizset = i;
}

if ( elem.nodeName.toLowerCase() === cur ) {


match = elem;
break;
}

elem = elem[dir];
}

checkSet[i] = match;
}
}
}

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {


for ( var i = 0, l = checkSet.length; i < l; i++ ) {
var elem = checkSet[i];

if ( elem ) {
var match = false;

elem = elem[dir];

while ( elem ) {
if ( elem[ expando ] === doneName ) {
match = checkSet[elem.sizset];
break;
}

if ( elem.nodeType === 1 ) {
if ( !isXML ) {
elem[ expando ] = doneName;
elem.sizset = i;
}

if ( typeof cur !== "string" ) {


if ( elem === cur ) {
match = true;
break;
}

} else if ( Sizzle.filter( cur, [elem] ).length > 0 )


{
match = elem;
break;
}
}

elem = elem[dir];
}

checkSet[i] = match;
}
}
}

if ( document.documentElement.contains ) {
Sizzle.contains = function( a, b ) {
return a !== b && (a.contains ? a.contains(b) : true);
};

} else if ( document.documentElement.compareDocumentPosition ) {
Sizzle.contains = function( a, b ) {
return !!(a.compareDocumentPosition(b) & 16);
};

} else {
Sizzle.contains = function() {
return false;
};
}

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;


};

var posProcess = function( selector, context, seed ) {


var match,
tmpSet = [],
later = "",
root = context.nodeType ? [context] : context;

// 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, "" );
}

selector = Expr.relative[selector] ? selector + "*" : selector;

for ( var i = 0, l = root.length; i < l; i++ ) {


Sizzle( selector, root[i], tmpSet, seed );
}

return Sizzle.filter( later, tmpSet );


};

// EXPOSE

window.tinymce.dom.Sizzle = Sizzle;

})();

(function(tinymce) {
tinymce.dom.Element = function(id, settings) {
var t = this, dom, el;

t.settings = settings = settings || {};


t.id = id;
t.dom = dom = settings.dom || tinymce.DOM;

// Only IE leaks DOM references, this is a lot faster


if (!tinymce.isIE)
el = dom.get(t.id);

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;

for (i = 0; i < arguments.length; i++)


a.push(arguments[i]);

a = dom[k].apply(dom, a);
t.update(k);

return a;
};
}
);

tinymce.extend(t, {
on : function(n, f, s) {
return tinymce.dom.Event.add(t.id, n, f, s);
},

getXY : function() {
return {
x : parseInt(t.getStyle('left')),
y : parseInt(t.getStyle('top'))
};
},

getSize : function() {
var n = dom.get(t.id);

return {
w : parseInt(t.getStyle('width') || n.clientWidth),
h : parseInt(t.getStyle('height') || n.clientHeight)
};
},

moveTo : function(x, y) {
t.setStyles({left : x, top : y});
},

moveBy : function(x, y) {
var p = t.getXY();

t.moveTo(p.x + x, p.y + y);


},

resizeTo : function(w, h) {
t.setStyles({width : w, height : h});
},

resizeBy : function(w, h) {
var s = t.getSize();

t.resizeTo(s.w + w, s.h + h);


},

update : function(k) {
var b;

if (tinymce.isIE6 && settings.blocker) {


k = k || '';

// Ignore getters
if (k.indexOf('get') === 0 || k.indexOf('has') === 0
|| k.indexOf('is') === 0)
return;

// Remove blocker on remove


if (k == 'remove') {
dom.remove(t.blocker);
return;
}

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, TreeWalker =
tinymce.dom.TreeWalker;

tinymce.create('tinymce.dom.Selection', {
Selection : function(dom, win, serializer, editor) {
var t = this;

t.dom = dom;
t.win = win;
t.serializer = serializer;
t.editor = editor;

// 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);

if (tinymce.isIE && dom.boxModel)


this._fixIESelection();

// Prevent leaks
tinymce.addUnload(t.destroy, t);
},

setCursorLocation: function(node, offset) {


var t = this; var r = t.dom.createRng();
r.setStart(node, offset);
r.setEnd(node, offset);
t.setRng(r);
t.collapse(false);
},
getContent : function(s) {
var t = this, r = t.getRng(), e = t.dom.create("body"), se =
t.getSel(), wb, wa, n;

s = s || {};
wb = wa = '';
s.get = true;
s.format = s.format || 'html';
s.forced_root_block = '';
t.onBeforeGetContent.dispatch(t, s);

if (s.format == 'text')
return t.isCollapsed() ? '' : (r.text || (se.toString ?
se.toString() : ''));

if (r.cloneContents) {
n = r.cloneContents();

if (n)
e.appendChild(n);
} else if (is(r.item) || is(r.htmlText)) {
// IE will produce invalid markup if elements are present
that
// it doesn't understand like custom elements or HTML5
elements.
// Adding a BR in front of the contents and then remoiving
it seems to fix it though.
e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML :
r.htmlText);
e.removeChild(e.firstChild);
} else
e.innerHTML = r.toString();

// Keep whitespace before and after


if (/^\s/.test(e.innerHTML))
wb = ' ';

if (/\s+$/.test(e.innerHTML))
wa = ' ';

s.getInner = true;

s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e,


s) + wa;
t.onGetContent.dispatch(t, s);

return s.content;
},

setContent : function(content, args) {


var self = this, rng = self.getRng(), caretNode, doc =
self.win.document, frag, temp;

args = args || {format : 'html'};


args.set = true;
content = args.content = content;

// Dispatch before set content event


if (!args.no_events)
self.onBeforeSetContent.dispatch(self, args);

content = args.content;

if (rng.insertNode) {
// Make caret marker since insertNode places the caret in
the beginning of text after insert
content += '<span id="__caret">_</span>';

// 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();

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');

frag.appendChild(temp);
temp.outerHTML = content;

rng.insertNode(frag);
}
}
}

// Move to caret marker


caretNode = self.dom.get('__caret');

// 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);

// Remove the caret position


self.dom.remove('__caret');

try {
self.setRng(rng);
} catch (ex) {
// Might fail on Opera for some odd reason
}
} else {
if (rng.item) {
// Delete content and get caret text selection
doc.execCommand('Delete', false, null);
rng = self.getRng();
}
// Explorer removes spaces from the beginning of pasted
contents
if (/^\s+/.test(content)) {
rng.pasteHTML('<span id="__mce_tmp">_</span>' +
content);
self.dom.remove('__mce_tmp');
} else
rng.pasteHTML(content);
}

// Dispatch set content event


if (!args.no_events)
self.onSetContent.dispatch(self, args);
},

getStart : function() {
var self = this, rng = self.getRng(), startElement,
parentElement, checkRng, node;

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();
if (startElement.ownerDocument !== self.dom.doc) {
startElement = self.dom.getRoot();
}

// 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 startElement;
} else {
startElement = rng.startContainer;

if (startElement.nodeType == 1 &&
startElement.hasChildNodes())
startElement =
startElement.childNodes[Math.min(startElement.childNodes.length - 1,
rng.startOffset)];

if (startElement && startElement.nodeType == 3)


return startElement.parentNode;

return startElement;
}
},

getEnd : function() {
var self = this, rng = self.getRng(), endElement, endOffset;

if (rng.duplicate || rng.item) {
if (rng.item)
return rng.item(0);

rng = rng.duplicate();
rng.collapse(0);
endElement = rng.parentElement();
if (endElement.ownerDocument !== self.dom.doc) {
endElement = self.dom.getRoot();
}

if (endElement && endElement.nodeName == 'BODY')


return endElement.lastChild || endElement;

return endElement;
} else {
endElement = rng.endContainer;
endOffset = rng.endOffset;

if (endElement.nodeType == 1 && endElement.hasChildNodes())


endElement = endElement.childNodes[endOffset > 0 ?
endOffset - 1 : endOffset];

if (endElement && endElement.nodeType == 3)


return endElement.parentNode;

return endElement;
}
},

getBookmark : function(type, normalized) {


var t = this, dom = t.dom, rng, rng2, id, collapsed, name,
element, index, chr = '\uFEFF', styles;

function findIndex(name, element) {


var index = 0;

each(dom.select(name), function(node, i) {
if (node == element)
index = i;
});

return index;
};

function normalizeTableCellSelection(rng) {
function moveEndPoint(start) {
var container, offset, childNodes, prefix = start ?
'start' : 'end';

container = rng[prefix + 'Container'];


offset = rng[prefix + 'Offset'];

if (container.nodeType == 1 && container.nodeName ==


"TR") {
childNodes = container.childNodes;
container = childNodes[Math.min(start ?
offset : offset - 1, childNodes.length - 1)];
if (container) {
offset = start ? 0 :
container.childNodes.length;
rng['set' + (start ? 'Start' : 'End')]
(container, offset);
}
}
};

moveEndPoint(true);
moveEndPoint();

return rng;
};

function getLocation() {
var rng = t.getRng(true), root = dom.getRoot(), bookmark =
{};

function getPoint(rng, start) {


var container = rng[start ? 'startContainer' :
'endContainer'],
offset = rng[start ? 'startOffset' :
'endOffset'], point = [], node, childNodes, after = 0;

if (container.nodeType == 3) {
if (normalized) {
for (node = container.previousSibling;
node && node.nodeType == 3; node = node.previousSibling)
offset += node.nodeValue.length;
}

point.push(offset);
} else {
childNodes = container.childNodes;

if (offset >= childNodes.length &&


childNodes.length) {
after = 1;
offset = Math.max(0, childNodes.length -
1);
}

point.push(t.dom.nodeIndex(childNodes[offset],
normalized) + after);
}

for (; container && container != root; container =


container.parentNode)
point.push(t.dom.nodeIndex(container,
normalized));

return point;
};
bookmark.start = getPoint(rng, true);

if (!t.isCollapsed())
bookmark.end = getPoint(rng);

return bookmark;
};

if (type == 2) {
if (t.tridentSel)
return t.tridentSel.getBookmark(type);

return getLocation();
}

// Handle simple range


if (type)
return {rng : t.getRng()};

rng = t.getRng();
id = dom.uniqueId();
collapsed = tinyMCE.activeEditor.selection.isCollapsed();
styles = 'overflow:hidden;line-height:0px';

// Explorer method
if (rng.duplicate || rng.item) {
// Text selection
if (!rng.item) {
rng2 = rng.duplicate();

try {
// Insert start marker
rng.collapse();
rng.pasteHTML('<span data-mce-type="bookmark"
id="' + id + '_start" style="' + styles + '">' + chr + '</span>');

// Insert end marker


if (!collapsed) {
rng2.collapse(false);

// Detect the empty space after block


elements in IE and move the end back one character <p></p>] becomes <p>]</p>

rng.moveToElementText(rng2.parentElement());
if (rng.compareEndPoints('StartToEnd',
rng2) === 0)
rng2.move('character', -1);

rng2.pasteHTML('<span data-mce-
type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
}
} catch (ex) {
// IE might throw unspecified error so lets
ignore it
return null;
}
} else {
// Control selection
element = rng.item(0);
name = element.nodeName;

return {name : name, index : findIndex(name,


element)};
}
} else {
element = t.getNode();
name = element.nodeName;
if (name == 'IMG')
return {name : name, index : findIndex(name,
element)};

// W3C method
rng2 = normalizeTableCellSelection(rng.cloneRange());

// Insert end marker


if (!collapsed) {
rng2.collapse(false);
rng2.insertNode(dom.create('span', {'data-mce-type' :
"bookmark", id : id + '_end', style : styles}, chr));
}

rng = normalizeTableCellSelection(rng);
rng.collapse(true);
rng.insertNode(dom.create('span', {'data-mce-type' :
"bookmark", id : id + '_start', style : styles}, chr));
}

t.moveToBookmark({id : id, keep : 1});

return {id : id};


},

moveToBookmark : function(bookmark) {
var t = this, dom = t.dom, marker1, marker2, rng, root,
startContainer, endContainer, startOffset, endOffset;

function setEndPoint(start) {
var point = bookmark[start ? 'start' : 'end'], i, node,
offset, children;

if (point) {
offset = point[0];

// Find container node


for (node = root, i = point.length - 1; i >= 1; i--)
{
children = node.childNodes;

if (point[i] > children.length - 1)


return;

node = children[point[i]];
}

// Move text offset to best suitable location


if (node.nodeType === 3)
offset = Math.min(point[0],
node.nodeValue.length);
// Move element offset to best suitable location
if (node.nodeType === 1)
offset = Math.min(point[0],
node.childNodes.length);

// Set offset within container node


if (start)
rng.setStart(node, offset);
else
rng.setEnd(node, offset);
}

return true;
};

function restoreEndPoint(suffix) {
var marker = dom.get(bookmark.id + '_' + suffix), node,
idx, next, prev, keep = bookmark.keep;

if (marker) {
node = marker.parentNode;

if (suffix == 'start') {
if (!keep) {
idx = dom.nodeIndex(marker);
} else {
node = marker.firstChild;
idx = 1;
}

startContainer = endContainer = node;


startOffset = endOffset = idx;
} else {
if (!keep) {
idx = dom.nodeIndex(marker);
} else {
node = marker.firstChild;
idx = 1;
}

endContainer = node;
endOffset = idx;
}

if (!keep) {
prev = marker.previousSibling;
next = marker.nextSibling;

// 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);

// If siblings are text nodes then merge them


unless it's Opera since it some how removes the node
// and we are sniffing since adding a lot of
detection code for a browser with 3% of the market isn't worth the effort. Sorry,
Opera but it's just a fact
if (prev && next && prev.nodeType ==
next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
idx = prev.nodeValue.length;
prev.appendData(next.nodeValue);
dom.remove(next);

if (suffix == 'start') {
startContainer = endContainer =
prev;
startOffset = endOffset = idx;
} else {
endContainer = prev;
endOffset = idx;
}
}
}
}
};

function addBogus(node) {
// Adds a bogus BR element for empty block elements
if (dom.isBlock(node) && !node.innerHTML && !isIE)
node.innerHTML = '<br data-mce-bogus="1" />';

return node;
};

if (bookmark) {
if (bookmark.start) {
rng = dom.createRng();
root = dom.getRoot();

if (t.tridentSel)
return t.tridentSel.moveToBookmark(bookmark);

if (setEndPoint(true) && setEndPoint()) {


t.setRng(rng);
}
} else if (bookmark.id) {
// Restore start/end points
restoreEndPoint('start');
restoreEndPoint('end');

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);
}
},

select : function(node, content) {


var t = this, dom = t.dom, rng = dom.createRng(), idx;

function setPoint(node, start) {


var walker = new 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);

return;
}

// BR element
if (node.nodeName == 'BR') {
if (start)
rng.setStartBefore(node);
else
rng.setEndBefore(node);

return;
}
} while (node = (start ? walker.next() : walker.prev()));
};

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) {
setPoint(node, 1);
setPoint(node);
}

t.setRng(rng);
}

return node;
},

isCollapsed : function() {
var t = this, r = t.getRng(), s = t.getSel();
if (!r || r.item)
return false;

if (r.compareEndPoints)
return r.compareEndPoints('StartToEnd', r) === 0;

return !s || r.collapsed;
},

collapse : function(to_start) {
var self = this, rng = self.getRng(), node;

// Control range on IE
if (rng.item) {
node = rng.item(0);
rng = self.win.document.body.createTextRange();
rng.moveToElementText(node);
}

rng.collapse(!!to_start);
self.setRng(rng);
},

getSel : function() {
var t = this, w = this.win;

return w.getSelection ? w.getSelection() : w.document.selection;


},

getRng : function(w3c) {
var self = this, selection, rng, elm, doc = self.win.document;

// Found tridentSel object then we need to use that one


if (w3c && self.tridentSel) {
return self.tridentSel.getRangeAt(0);
}

try {
if (selection = self.getSel()) {
rng = selection.rangeCount > 0 ?
selection.getRangeAt(0) : (selection.createRange ? selection.createRange() :
doc.createRange());
}
} catch (ex) {
// IE throws unspecified error here if TinyMCE is placed in
a frame/iframe
}

// We have W3C ranges and it's IE then fake control selection


since IE9 doesn't handle that correctly yet
if (tinymce.isIE && rng && rng.setStart &&
doc.selection.createRange().item) {
elm = doc.selection.createRange().item(0);
rng = doc.createRange();
rng.setStartBefore(elm);
rng.setEndAfter(elm);
}

// 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 (!rng) {
rng = doc.createRange ? doc.createRange() :
doc.body.createTextRange();
}

// If range is at start of document then move it to start of body


if (rng.setStart && rng.startContainer.nodeType === 9 &&
rng.collapsed) {
elm = self.dom.getRoot();
rng.setStart(elm, 0);
rng.setEnd(elm, 0);
}

if (self.selectedRange && self.explicitRange) {


if (rng.compareBoundaryPoints(rng.START_TO_START,
self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END,
self.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.
rng = self.explicitRange;
} else {
self.selectedRange = null;
self.explicitRange = null;
}
}

return rng;
},

setRng : function(r, forward) {


var s, t = this;

if (!t.tridentSel) {
s = t.getSel();

if (s) {
t.explicitRange = r;

try {
s.removeAllRanges();
} catch (ex) {
// IE9 might throw errors here don't know why
}

s.addRange(r);

// Forward is set to false and we have an extend


function
if (forward === false && s.extend) {
s.collapse(r.endContainer, r.endOffset);
s.extend(r.startContainer, r.startOffset);
}

// adding range isn't always successful so we need to


check range count otherwise an exception can occur
t.selectedRange = s.rangeCount > 0 ?
s.getRangeAt(0) : null;
}
} else {
// Is W3C Range
if (r.cloneRange) {
try {
t.tridentSel.addRange(r);
return;
} catch (ex) {
//IE9 throws an error here if called before
selection is placed in the editor
}
}

// Is IE specific range
try {
r.select();
} catch (ex) {
// Needed for some odd IE bug #1843306
}
}
},

setNode : function(n) {
var t = this;

t.setContent(t.dom.getOuterHTML(n));

return n;
},

getNode : function() {
var t = this, rng = t.getRng(), sel = t.getSel(), elm, start =
rng.startContainer, end = rng.endContainer;

function skipEmptyTextNodes(n, forwards) {


var orig = n;
while (n && n.nodeType === 3 && n.length === 0) {
n = forwards ? n.nextSibling : n.previousSibling;
}
return n || orig;
};

// Range maybe lost after the editor is made visible again


if (!rng)
return t.dom.getRoot();

if (rng.setStart) {
elm = rng.commonAncestorContainer;

// 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];
}
}

// 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) {
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;
}
}

if (elm && elm.nodeType == 3)


return elm.parentNode;

return elm;
}

return rng.item ? rng.item(0) : rng.parentElement();


},

getSelectedBlocks : function(st, en) {


var t = this, dom = t.dom, sb, eb, n, bl = [];

sb = dom.getParent(st || t.getStart(), dom.isBlock);


eb = dom.getParent(en || t.getEnd(), dom.isBlock);

if (sb)
bl.push(sb);

if (sb && eb && sb != eb) {


n = sb;

var walker = new TreeWalker(sb, dom.getRoot());


while ((n = walker.next()) && n != eb) {
if (dom.isBlock(n))
bl.push(n);
}
}

if (eb && sb != eb)


bl.push(eb);

return bl;
},

isForward: function(){
var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;

// No support for selection direction then always return true


if (!sel || sel.anchorNode == null || sel.focusNode == null) {
return true;
}

anchorRange = dom.createRng();
anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
anchorRange.collapse(true);

focusRange = dom.createRng();
focusRange.setStart(sel.focusNode, sel.focusOffset);
focusRange.collapse(true);

return
anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
},

normalize : function() {
var self = this, rng, normalized, collapsed, node, sibling;

function normalizeEndPoint(start) {
var container, offset, walker, dom = self.dom, body =
dom.getRoot(), node, nonEmptyElementsMap, nodeName;

function hasBrBeforeAfter(node, left) {


var walker = new TreeWalker(node,
dom.getParent(node.parentNode, dom.isBlock) || body);

while (node = walker[left ? 'prev' : 'next']()) {


if (node.nodeName === "BR") {
return true;
}
}
};

// Walks the dom left/right to find a suitable text node to


move the endpoint into
// It will only walk within the current parent block or
body and will stop if it hits a block or a BR/IMG
function findTextNodeRelative(left, startNode) {
var walker, lastInlineElement;

startNode = startNode || container;


walker = new TreeWalker(startNode,
dom.getParent(startNode.parentNode, dom.isBlock) || body);

// Walk left until we hit a text node we can move to


or a block/br/img
while (node = walker[left ? 'prev' : 'next']()) {
// Found text node that has a length
if (node.nodeType === 3 &&
node.nodeValue.length > 0) {
container = node;
offset = left ? node.nodeValue.length :
0;
normalized = true;
return;
}

// Break if we find a block or a BR/IMG/INPUT


etc
if (dom.isBlock(node) ||
nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
return;
}

lastInlineElement = node;
}

// Only fetch the last inline element when in caret


mode for now
if (collapsed && lastInlineElement) {
container = lastInlineElement;
normalized = true;
offset = 0;
}
};

container = rng[(start ? 'start' : 'end') + 'Container'];


offset = rng[(start ? 'start' : 'end') + 'Offset'];
nonEmptyElementsMap = dom.schema.getNonEmptyElements();

// If the container is a document move it to the body


element
if (container.nodeType === 9) {
container = dom.getRoot();
offset = 0;
}

// If the container is body try move it into the closest


text node or position
if (container === body) {
// If start is before/after a image, table etc
if (start) {
node = container.childNodes[offset > 0 ? offset
- 1 : 0];
if (node) {
nodeName = node.nodeName.toLowerCase();
if (nonEmptyElementsMap[node.nodeName] ||
node.nodeName == "TABLE") {
return;
}
}
}

// Resolve the index


if (container.hasChildNodes()) {
container = container.childNodes[Math.min(!
start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
offset = 0;

// Don't walk into elements that doesn't have


any child nodes like a IMG
if (container.hasChildNodes()
&& !/TABLE/.test(container.nodeName)) {
// Walk the DOM to find a text node to
place the caret at or a BR
node = container;
walker = new TreeWalker(container, body);

do {
// Found a text node use that
position
if (node.nodeType === 3 &&
node.nodeValue.length > 0) {
offset = start ? 0 :
node.nodeValue.length;
container = node;
normalized = true;
break;
}

// Found a BR/IMG element that we


can place the caret before
if
(nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
offset = dom.nodeIndex(node);
container = node.parentNode;

// Put caret after image when


moving the end point
if (node.nodeName == "IMG"
&& !start) {
offset++;
}

normalized = true;
break;
}
} while (node = (start ? walker.next() :
walker.prev()));
}
}
}

// Lean the caret to the left if possible


if (collapsed) {
// So this: <b>x</b><i>|x</i>
// Becomes: <b>x|</b><i>x</i>
// Seems that only gecko has issues with this
if (container.nodeType === 3 && offset === 0) {
findTextNodeRelative(true);
}

// Lean left into empty inline elements when the


caret is before a BR
// So this: <i><b></b><i>|<br></i>
// Becomes: <i><b>|</b><i><br></i>
// Seems that only gecko has issues with this
if (container.nodeType === 1) {
node = container.childNodes[offset];
if(node && node.nodeName === 'BR' && !
hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
findTextNodeRelative(true,
container.childNodes[offset]);
}
}
}

// Lean the start of the selection right if possible


// So this: x[<b>x]</b>
// Becomes: x<b>[x]</b>
if (start && !collapsed && container.nodeType === 3 &&
offset === container.nodeValue.length) {
findTextNodeRelative(false);
}

// Set endpoint if it was normalized


if (normalized)
rng['set' + (start ? 'Start' : 'End')](container,
offset);
};

// Normalize only on non IE browsers for now


if (tinymce.isIE)
return;

rng = self.getRng();
collapsed = rng.collapsed;

// Normalize the end points


normalizeEndPoint(true);

if (!collapsed)
normalizeEndPoint();

// Set the selection if it was normalized


if (normalized) {
// If it was collapsed then make sure it still is
if (collapsed) {
rng.collapse(true);
}

//console.log(self.dom.dumpRng(rng));
self.setRng(rng, self.isForward());
}
},

selectorChanged: function(selector, callback) {


var self = this, currentSelectors;

if (!self.selectorChangedData) {
self.selectorChangedData = {};
currentSelectors = {};
self.editor.onNodeChange.addToTop(function(ed, cm, node) {
var dom = self.dom, parents = dom.getParents(node,
null, dom.getRoot()), matchedSelectors = {};

// Check for new matching selectors


each(self.selectorChangedData, function(callbacks,
selector) {
each(parents, function(node) {
if (dom.is(node, selector)) {
if (!currentSelectors[selector]) {
// Execute callbacks
each(callbacks,
function(callback) {
callback(true, {node:
node, selector: selector, parents: parents});
});

currentSelectors[selector] =
callbacks;
}

matchedSelectors[selector] =
callbacks;
return false;
}
});
});

// Check if current selectors still match


each(currentSelectors, function(callbacks, selector)
{
if (!matchedSelectors[selector]) {
delete currentSelectors[selector];

each(callbacks, function(callback) {
callback(false, {node: node,
selector: selector, parents: parents});
});
}
});
});
}

// Add selector listeners


if (!self.selectorChangedData[selector]) {
self.selectorChangedData[selector] = [];
}

self.selectorChangedData[selector].push(callback);

return self;
},

scrollIntoView: function(elm) {
var y, viewPort, self = this, dom = self.dom;

viewPort = dom.getViewPort(self.editor.getWin());
y = dom.getPos(elm).y;
if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y -
viewPort.h + 25);
}
},

destroy : function(manual) {
var self = this;

self.win = null;

// Manual destroy then remove unload handler


if (!manual)
tinymce.removeUnload(self.destroy);
},

// 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;

// 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;
}

return rng;
};

// Fires while the selection is changing


function selectionChange(e) {
var pointRng;

// Check if the button is down or not


if (e.button) {
// Create range from mouse position
pointRng = rngFromPoint(e.x, e.y);

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);

pointRng.select();
}
} else
endSelection();
}

// Removes listeners
function endSelection() {
var rng = doc.selection.createRange();

// If the range is collapsed then use the last start range


if (startRng && !rng.item &&
rng.compareEndPoints('StartToEnd', rng) === 0)
startRng.select();

dom.unbind(doc, 'mouseup', endSelection);


dom.unbind(doc, 'mousemove', selectionChange);
startRng = started = 0;
};

// Make HTML element unselectable since we are going to handle


selection by hand
doc.documentElement.unselectable = true;

// Detect when user selects outside BODY


dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
if (e.target.nodeName === 'HTML') {
if (started)
endSelection();

// 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;

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);

dom.win.focus();
startRng.select();
}
}
});
}
});
})(tinymce);

(function(tinymce) {
tinymce.dom.Serializer = function(settings, dom, schema) {
var onPreProcess, onPostProcess, isIE = tinymce.isIE, each =
tinymce.each, htmlParser;

// Support the old apply_source_formatting option


if (!settings.apply_source_formatting)
settings.indent = false;
// 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';
settings.remove_trailing_brs = "remove_trailing_brs" in settings ?
settings.remove_trailing_brs : true;

onPreProcess = new tinymce.util.Dispatcher(self);

onPostProcess = new tinymce.util.Dispatcher(self);

htmlParser = new tinymce.html.DomParser(settings, schema);

// 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;

while (i--) {
node = nodes[i];

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 (name === "style")


value =
dom.serializeStyle(dom.parseStyle(value), node.name);
else if (urlConverter)
value = urlConverter.call(urlConverterScope,
value, name, node.name);

node.attr(name, value.length > 0 ? value : null);


}
}
});

// Remove internal classes mceItem<..> or mceSelected


htmlParser.addAttributeFilter('class', function(nodes, name) {
var i = nodes.length, node, value;

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);
}
});
// Remove bookmark elements
htmlParser.addAttributeFilter('data-mce-type', function(nodes, name,
args) {
var i = nodes.length, node;

while (i--) {
node = nodes[i];

if (node.attributes.map['data-mce-type'] === 'bookmark'


&& !args.cleanup)
node.remove();
}
});

// Remove expando attributes


htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name,
args) {
var i = nodes.length;

while (i--) {
nodes[i].attr(name, null);
}
});

htmlParser.addNodeFilter('noscript', function(nodes) {
var i = nodes.length, node;

while (i--) {
node = nodes[i].firstChild;

if (node) {
node.value =
tinymce.html.Entities.decode(node.value);
}
}
});

// 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;

function trim(value) {
return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
.replace(/^[\r\n]*|[\r\n]*$/g, '')
.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|
(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|
(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
.replace(/\s*(\/\*\s*\]\]>\s*\*\/(--
>)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g,
'');
};

while (i--) {
node = nodes[i];
value = node.firstChild ? node.firstChild.value : '';

if (name === "script") {


// Remove mce- prefix from script elements
node.attr('type', (node.attr('type') ||
'text/javascript').replace(/^mce\-/, ''));

if (value.length > 0)
node.firstChild.value = '// <![CDATA[\n' +
trim(value) + '\n// ]]>';
} else {
if (value.length > 0)
node.firstChild.value = '<!--\n' + trim(value)
+ '\n-->';
}
}
});

// Convert comments to cdata and handle protected comments


htmlParser.addNodeFilter('#comment', function(nodes, name) {
var i = nodes.length, node;

while (i--) {
node = nodes[i];

if (node.value.indexOf('[CDATA[') === 0) {
node.name = '#cdata';
node.type = 4;
node.value = node.value.replace(/^\[CDATA\[|\]\]$/g,
'');
} else if (node.value.indexOf('mce:protected ') === 0) {
node.name = "#text";
node.type = 3;
node.raw = true;
node.value = unescape(node.value).substr(14);
}
}
});

htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {


var i = nodes.length, node;

while (i--) {
node = nodes[i];
if (node.type === 7)
node.remove();
else if (node.type === 1) {
if (name === "input" && !("type" in
node.attributes.map))
node.attr('type', 'text');
}
}
});

// Fix list elements, TODO: Replace this later


if (settings.fix_list_elements) {
htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
var i = nodes.length, node, parentNode;

while (i--) {
node = nodes[i];
parentNode = node.parent;
if (parentNode.name === 'ul' || parentNode.name ===
'ol') {
if (node.prev && node.prev.name === 'li') {
node.prev.append(node);
}
}
}
});
}

// Remove internal data attributes


htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-
style', function(nodes, name) {
var i = nodes.length;

while (i--) {
nodes[i].attr(name, null);
}
});

// Return public methods


return {
schema : schema,

addNodeFilter : htmlParser.addNodeFilter,

addAttributeFilter : htmlParser.addAttributeFilter,

onPreProcess : onPreProcess,

onPostProcess : onPostProcess,

serialize : function(node, args) {


var impl, doc, oldDoc, htmlSerializer, content;

// Explorer won't clone contents of script and style and


the
// selected index of select elements are cleared on a clone
operation.
if (isIE && dom.select('script,style,select,map').length >
0) {
content = node.innerHTML;
node = node.cloneNode(false);
dom.setHTML(node, content);
} else
node = node.cloneNode(true);

// Nodes needs to be attached to something in WebKit/Opera


// Older builds of Opera crashes if you attach the node to
an document created dynamically
// and since we can't feature detect a crash we need to
sniff the acutal build number
// This fix will make DOM ranges and make Sizzle happy!
impl = node.ownerDocument.implementation;
if (impl.createHTMLDocument) {
// Create an empty HTML document
doc = impl.createHTMLDocument("");

// Add the element or it's children if it's a body


element to the new document
each(node.nodeName == 'BODY' ? node.childNodes :
[node], function(node) {
doc.body.appendChild(doc.importNode(node,
true));
});

// Grab first child or body element for serialization


if (node.nodeName != 'BODY')
node = doc.body.firstChild;
else
node = doc.body;

// set the new document in DOMUtils so createElement


etc works
oldDoc = dom.doc;
dom.doc = doc;
}

args = args || {};


args.format = args.format || 'html';

// Pre process
if (!args.no_events) {
args.node = node;
onPreProcess.dispatch(self, args);
}

// Setup serializer
htmlSerializer = new tinymce.html.Serializer(settings,
schema);

// Parse and serialize HTML


args.content = htmlSerializer.serialize(
htmlParser.parse(tinymce.trim(args.getInner ?
node.innerHTML : dom.getOuterHTML(node)), args)
);

// Replace all BOM characters for now until we can find a


better solution
if (!args.cleanup)
args.content = args.content.replace(/\uFEFF/g, '');

// Post process
if (!args.no_events)
onPostProcess.dispatch(self, args);

// Restore the old document if it was changed


if (oldDoc)
dom.doc = oldDoc;

args.node = null;

return args.content;
},

addRules : function(rules) {
schema.addValidElements(rules);
},
setRules : function(rules) {
schema.setValidElements(rules);
}
};
};
})(tinymce);
(function(tinymce) {
tinymce.dom.ScriptLoader = function(settings) {
var QUEUED = 0,
LOADING = 1,
LOADED = 2,
states = {},
queue = [],
scriptLoadedCallbacks = {},
queueLoadedCallbacks = [],
loading = 0,
undef;

function loadScript(url, callback) {


var t = this, dom = tinymce.DOM, elm, uri, loc, id;

// Execute callback when script is loaded


function done() {
dom.remove(id);

if (elm)
elm.onreadystatechange = elm.onload = elm = null;

callback();
};

function error() {
// Report the error so it's easier for people to spot
loading errors
if (typeof(console) !== "undefined" && console.log)
console.log("Failed to load: " + url);

// We can't mark it as done if there is a load error since


// A) We don't want to produce 404 errors on the server and
// B) the onerror event won't fire on all browsers.
// done();
};

id = dom.uniqueId();

if (tinymce.isIE6) {
uri = new tinymce.util.URI(url);
loc = location;

// If script is from same domain and we


// use IE 6 then use XHR since it's more reliable
if (uri.host == loc.hostname && uri.port == loc.port &&
(uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
tinymce.util.XHR.send({
url : tinymce._addVer(uri.getURI()),
success : function(content) {
// Create new temp script element
var script = dom.create('script', {
type : 'text/javascript'
});

// Evaluate script in global scope


script.text = content;
document.getElementsByTagName('head')
[0].appendChild(script);
dom.remove(script);

done();
},

error : error
});

return;
}
}

// Create new script element


elm = document.createElement('script');
elm.id = id;
elm.type = 'text/javascript';
elm.src = tinymce._addVer(url);

// Add onload listener for non IE browsers since IE9


// fires onload event before the script is parsed and executed
if (!tinymce.isIE)
elm.onload = done;

// Add onerror event will get fired on some browsers but not all
of them
elm.onerror = error;

// Opera 9.60 doesn't seem to fire the onreadystate event at


correctly
if (!tinymce.isOpera) {
elm.onreadystatechange = function() {
var state = elm.readyState;

// Loaded state is passed on IE 6 however there


// are known issues with this method but we can't use
// XHR in a cross domain loading
if (state == 'complete' || state == 'loaded')
done();
};
}

// Most browsers support this feature so we report errors


// for those at least to help users track their missing plugins
etc
// todo: Removed since it produced error if the document is
unloaded by navigating away, re-add it as an option
/*elm.onerror = function() {
alert('Failed to load: ' + url);
};*/

// Add script to document


(document.getElementsByTagName('head')[0] ||
document.body).appendChild(elm);
};

this.isDone = function(url) {
return states[url] == LOADED;
};

this.markDone = function(url) {
states[url] = LOADED;
};

this.add = this.load = function(url, callback, scope) {


var item, state = states[url];

// Add url to load queue


if (state == undef) {
queue.push(url);
states[url] = QUEUED;
}

if (callback) {
// Store away callback for later execution
if (!scriptLoadedCallbacks[url])
scriptLoadedCallbacks[url] = [];

scriptLoadedCallbacks[url].push({
func : callback,
scope : scope || this
});
}
};

this.loadQueue = function(callback, scope) {


this.loadScripts(queue, callback, scope);
};

this.loadScripts = function(scripts, callback, scope) {


var loadScripts;

function execScriptLoadedCallbacks(url) {
// Execute URL callback functions
tinymce.each(scriptLoadedCallbacks[url], function(callback)
{
callback.func.call(callback.scope);
});

scriptLoadedCallbacks[url] = undef;
};

queueLoadedCallbacks.push({
func : callback,
scope : scope || this
});

loadScripts = function() {
var loadingScripts = tinymce.grep(scripts);

// Current scripts has been handled


scripts.length = 0;
// Load scripts that needs to be loaded
tinymce.each(loadingScripts, function(url) {
// Script is already loaded then execute script
callbacks directly
if (states[url] == LOADED) {
execScriptLoadedCallbacks(url);
return;
}

// Is script not loading then start loading it


if (states[url] != LOADING) {
states[url] = LOADING;
loading++;

loadScript(url, function() {
states[url] = LOADED;
loading--;

execScriptLoadedCallbacks(url);

// Load more scripts if they where added


by the recently loaded script
loadScripts();
});
}
});

// No scripts are currently loading then execute all


pending queue loaded callbacks
if (!loading) {
tinymce.each(queueLoadedCallbacks, function(callback)
{
callback.func.call(callback.scope);
});

queueLoadedCallbacks.length = 0;
}
};

loadScripts();
};
};

// Global script loader


tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
})(tinymce);

(function(tinymce) {
tinymce.dom.RangeUtils = function(dom) {
var INVISIBLE_CHAR = '\uFEFF';

this.walk = function(rng, callback) {


var startContainer = rng.startContainer,
startOffset = rng.startOffset,
endContainer = rng.endContainer,
endOffset = rng.endOffset,
ancestor, startPoint,
endPoint, node, parent, siblings, nodes;
// Handle table cell selection the table plugin enables
// you to fake select table cells and perform formatting actions
on them
nodes = dom.select('td.mceSelected,th.mceSelected');
if (nodes.length > 0) {
tinymce.each(nodes, function(node) {
callback([node]);
});

return;
}

function exclude(nodes) {
var node;

// First node is excluded


node = nodes[0];
if (node.nodeType === 3 && node === startContainer &&
startOffset >= node.nodeValue.length) {
nodes.splice(0, 1);
}

// Last node is excluded


node = nodes[nodes.length - 1];
if (endOffset === 0 && nodes.length > 0 && node ===
endContainer && node.nodeType === 3) {
nodes.splice(nodes.length - 1, 1);
}

return nodes;
};

function collectSiblings(node, name, end_node) {


var siblings = [];

for (; node && node != end_node; node = node[name])


siblings.push(node);

return siblings;
};

function findEndPoint(node, root) {


do {
if (node.parentNode == root)
return node;

node = node.parentNode;
} while(node);
};

function walkBoundary(start_node, end_node, next) {


var siblingName = next ? 'nextSibling' : 'previousSibling';

for (node = start_node, parent = node.parentNode; node &&


node != end_node; node = parent) {
parent = node.parentNode;
siblings = collectSiblings(node == start_node ?
node : node[siblingName], siblingName);
if (siblings.length) {
if (!next)
siblings.reverse();

callback(exclude(siblings));
}
}
};

// If index based start position then resolve it


if (startContainer.nodeType == 1 &&
startContainer.hasChildNodes())
startContainer = startContainer.childNodes[startOffset];

// If index based end position then resolve it


if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
endContainer = endContainer.childNodes[Math.min(endOffset -
1, endContainer.childNodes.length - 1)];

// Same container
if (startContainer == endContainer)
return callback(exclude([startContainer]));

// Find common ancestor and end points


ancestor = dom.findCommonAncestor(startContainer, endContainer);

// Process left side


for (node = startContainer; node; node = node.parentNode) {
if (node === endContainer)
return walkBoundary(startContainer, ancestor, true);

if (node === ancestor)


break;
}

// Process right side


for (node = endContainer; node; node = node.parentNode) {
if (node === startContainer)
return walkBoundary(endContainer, ancestor);

if (node === ancestor)


break;
}

// Find start/end point


startPoint = findEndPoint(startContainer, ancestor) ||
startContainer;
endPoint = findEndPoint(endContainer, ancestor) || endContainer;

// Walk left leaf


walkBoundary(startContainer, startPoint, true);

// Walk the middle from start to end point


siblings = collectSiblings(
startPoint == startContainer ? startPoint :
startPoint.nextSibling,
'nextSibling',
endPoint == endContainer ? endPoint.nextSibling : endPoint
);

if (siblings.length)
callback(exclude(siblings));

// Walk right leaf


walkBoundary(endContainer, endPoint);
};

this.split = function(rng) {
var startContainer = rng.startContainer,
startOffset = rng.startOffset,
endContainer = rng.endContainer,
endOffset = rng.endOffset;

function splitText(node, offset) {


return node.splitText(offset);
};

// Handle single text node


if (startContainer == endContainer && startContainer.nodeType ==
3) {
if (startOffset > 0 && startOffset <
startContainer.nodeValue.length) {
endContainer = splitText(startContainer,
startOffset);
startContainer = endContainer.previousSibling;

if (endOffset > startOffset) {


endOffset = endOffset - startOffset;
startContainer = endContainer =
splitText(endContainer, endOffset).previousSibling;
endOffset = endContainer.nodeValue.length;
startOffset = 0;
} else {
endOffset = 0;
}
}
} else {
// Split startContainer text node if needed
if (startContainer.nodeType == 3 && startOffset > 0 &&
startOffset < startContainer.nodeValue.length) {
startContainer = splitText(startContainer,
startOffset);
startOffset = 0;
}

// Split endContainer text node if needed


if (endContainer.nodeType == 3 && endOffset > 0 &&
endOffset < endContainer.nodeValue.length) {
endContainer = splitText(endContainer,
endOffset).previousSibling;
endOffset = endContainer.nodeValue.length;
}
}

return {
startContainer : startContainer,
startOffset : startOffset,
endContainer : endContainer,
endOffset : endOffset
};
};

};

tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {


if (rng1 && rng2) {
// Compare native IE ranges
if (rng1.item || rng1.duplicate) {
// Both are control ranges and the selected element matches
if (rng1.item && rng2.item && rng1.item(0) ===
rng2.item(0))
return true;

// Both are text ranges and the range matches


if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
return true;
} else {
// Compare w3c ranges
return rng1.startContainer == rng2.startContainer &&
rng1.startOffset == rng2.startOffset;
}
}

return false;
};
})(tinymce);

(function(tinymce) {
var Event = tinymce.dom.Event, each = tinymce.each;

tinymce.create('tinymce.ui.KeyboardNavigation', {
KeyboardNavigation: function(settings, dom) {
var t = this, root = settings.root, items = settings.items,
enableUpDown = settings.enableUpDown, enableLeftRight
= settings.enableLeftRight || !settings.enableUpDown,
excludeFromTabOrder = settings.excludeFromTabOrder,
itemFocussed, itemBlurred, rootKeydown, rootFocussed,
focussedId;

dom = dom || tinymce.DOM;

itemFocussed = function(evt) {
focussedId = evt.target.id;
};

itemBlurred = function(evt) {
dom.setAttrib(evt.target.id, 'tabindex', '-1');
};

rootFocussed = function(evt) {
var item = dom.get(focussedId);
dom.setAttrib(item, 'tabindex', '0');
item.focus();
};

t.focus = function() {
dom.get(focussedId).focus();
};

t.destroy = function() {
each(items, function(item) {
var elm = dom.get(item.id);

dom.unbind(elm, 'focus', itemFocussed);


dom.unbind(elm, 'blur', itemBlurred);
});

var rootElm = dom.get(root);


dom.unbind(rootElm, 'focus', rootFocussed);
dom.unbind(rootElm, 'keydown', rootKeydown);

items = dom = root = t.focus = itemFocussed = itemBlurred =


rootKeydown = rootFocussed = null;
t.destroy = function() {};
};

t.moveFocus = function(dir, evt) {


var idx = -1, controls = t.controls, newFocus;

if (!focussedId)
return;

each(items, function(item, index) {


if (item.id === focussedId) {
idx = index;
return false;
}
});

idx += dir;
if (idx < 0) {
idx = items.length - 1;
} else if (idx >= items.length) {
idx = 0;
}

newFocus = items[idx];
dom.setAttrib(focussedId, 'tabindex', '-1');
dom.setAttrib(newFocus.id, 'tabindex', '0');
dom.get(newFocus.id).focus();

if (settings.actOnFocus) {
settings.onAction(newFocus.id);
}

if (evt)
Event.cancel(evt);
};

rootKeydown = function(evt) {
var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38,
DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13,
DOM_VK_SPACE = 32;

switch (evt.keyCode) {
case DOM_VK_LEFT:
if (enableLeftRight) t.moveFocus(-1);
break;

case DOM_VK_RIGHT:
if (enableLeftRight) t.moveFocus(1);
break;

case DOM_VK_UP:
if (enableUpDown) t.moveFocus(-1);
break;

case DOM_VK_DOWN:
if (enableUpDown) t.moveFocus(1);
break;

case DOM_VK_ESCAPE:
if (settings.onCancel) {
settings.onCancel();
Event.cancel(evt);
}
break;

case DOM_VK_ENTER:
case DOM_VK_RETURN:
case DOM_VK_SPACE:
if (settings.onAction) {
settings.onAction(focussedId);
Event.cancel(evt);
}
break;
}
};

// Set up state and listeners for each item.


each(items, function(item, idx) {
var tabindex, elm;

if (!item.id) {
item.id = dom.uniqueId('_mce_item_');
}

elm = dom.get(item.id);

if (excludeFromTabOrder) {
dom.bind(elm, 'blur', itemBlurred);
tabindex = '-1';
} else {
tabindex = (idx === 0 ? '0' : '-1');
}

elm.setAttribute('tabindex', tabindex);
dom.bind(elm, 'focus', itemFocussed);
});

// Setup initial state for root element.


if (items[0]){
focussedId = items[0].id;
}
dom.setAttrib(root, 'tabindex', '-1');

// Setup listeners for root element.


var rootElm = dom.get(root);
dom.bind(rootElm, 'focus', rootFocussed);
dom.bind(rootElm, 'keydown', rootKeydown);
}
});
})(tinymce);

(function(tinymce) {
// Shorten class names
var DOM = tinymce.DOM, is = tinymce.is;

tinymce.create('tinymce.ui.Control', {
Control : function(id, s, editor) {
this.id = id;
this.settings = s = s || {};
this.rendered = false;
this.onRender = new tinymce.util.Dispatcher(this);
this.classPrefix = '';
this.scope = s.scope || this;
this.disabled = 0;
this.active = 0;
this.editor = editor;
},

setAriaProperty : function(property, value) {


var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
if (element) {
DOM.setAttrib(element, 'aria-' + property, !!value);
}
},

focus : function() {
DOM.get(this.id).focus();
},

setDisabled : function(s) {
if (s != this.disabled) {
this.setAriaProperty('disabled', s);

this.setState('Disabled', s);
this.setState('Enabled', !s);
this.disabled = s;
}
},

isDisabled : function() {
return this.disabled;
},

setActive : function(s) {
if (s != this.active) {
this.setState('Active', s);
this.active = s;
this.setAriaProperty('pressed', s);
}
},

isActive : function() {
return this.active;
},

setState : function(c, s) {
var n = DOM.get(this.id);

c = this.classPrefix + c;

if (s)
DOM.addClass(n, c);
else
DOM.removeClass(n, c);
},

isRendered : function() {
return this.rendered;
},

renderHTML : function() {
},

renderTo : function(n) {
DOM.setHTML(n, this.renderHTML());
},

postRender : function() {
var t = this, b;

// Set pending states


if (is(t.disabled)) {
b = t.disabled;
t.disabled = -1;
t.setDisabled(b);
}

if (is(t.active)) {
b = t.active;
t.active = -1;
t.setActive(b);
}
},

remove : function() {
DOM.remove(this.id);
this.destroy();
},

destroy : function() {
tinymce.dom.Event.clear(this.id);
}
});
})(tinymce);
tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
Container : function(id, s, editor) {
this.parent(id, s, editor);
this.controls = [];

this.lookup = {};
},

add : function(c) {
this.lookup[c.id] = c;
this.controls.push(c);

return c;
},

get : function(n) {
return this.lookup[n];
}
});

tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
Separator : function(id, s) {
this.parent(id, s);
this.classPrefix = 'mceSeparator';
this.setDisabled(true);
},

renderHTML : function() {
return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role
: 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
}
});

(function(tinymce) {
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk =
tinymce.walk;

tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
MenuItem : function(id, s) {
this.parent(id, s);
this.classPrefix = 'mceMenuItem';
},

setSelected : function(s) {
this.setState('Selected', s);
this.setAriaProperty('checked', !!s);
this.selected = s;
},

isSelected : function() {
return this.selected;
},

postRender : function() {
var t = this;

t.parent();

// Set pending state


if (is(t.selected))
t.setSelected(t.selected);
}
});
})(tinymce);

(function(tinymce) {
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk =
tinymce.walk;

tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
Menu : function(id, s) {
var t = this;

t.parent(id, s);
t.items = {};
t.collapsed = false;
t.menuCount = 0;
t.onAddItem = new tinymce.util.Dispatcher(this);
},

expand : function(d) {
var t = this;

if (d) {
walk(t, function(o) {
if (o.expand)
o.expand();
}, 'items', t);
}

t.collapsed = false;
},

collapse : function(d) {
var t = this;

if (d) {
walk(t, function(o) {
if (o.collapse)
o.collapse();
}, 'items', t);
}

t.collapsed = true;
},

isCollapsed : function() {
return this.collapsed;
},

add : function(o) {
if (!o.settings)
o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);

this.onAddItem.dispatch(this, o);

return this.items[o.id] = o;
},

addSeparator : function() {
return this.add({separator : true});
},

addMenu : function(o) {
if (!o.collapse)
o = this.createMenu(o);

this.menuCount++;

return this.add(o);
},

hasMenus : function() {
return this.menuCount !== 0;
},

remove : function(o) {
delete this.items[o.id];
},

removeAll : function() {
var t = this;

walk(t, function(o) {
if (o.removeAll)
o.removeAll();
else
o.remove();

o.destroy();
}, 'items', t);

t.items = {};
},

createMenu : function(o) {
var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);

m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);

return m;
}
});
})(tinymce);
(function(tinymce) {
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event =
tinymce.dom.Event, Element = tinymce.dom.Element;

tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
DropMenu : function(id, s) {
s = s || {};
s.container = s.container || DOM.doc.body;
s.offset_x = s.offset_x || 0;
s.offset_y = s.offset_y || 0;
s.vp_offset_x = s.vp_offset_x || 0;
s.vp_offset_y = s.vp_offset_y || 0;

if (is(s.icons) && !s.icons)


s['class'] += ' mceNoIcons';
this.parent(id, s);
this.onShowMenu = new tinymce.util.Dispatcher(this);
this.onHideMenu = new tinymce.util.Dispatcher(this);
this.classPrefix = 'mceMenu';
},

createMenu : function(s) {
var t = this, cs = t.settings, m;

s.container = s.container || cs.container;


s.parent = t;
s.constrain = s.constrain || cs.constrain;
s['class'] = s['class'] || cs['class'];
s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
s.keyboard_focus = cs.keyboard_focus;
m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);

m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);

return m;
},

focus : function() {
var t = this;
if (t.keyboardNav) {
t.keyboardNav.focus();
}
},

update : function() {
var t = this, s = t.settings, tb = DOM.get('menu_' + t.id +
'_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;

tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) :


tb.offsetWidth;
th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) :
tb.offsetHeight;

if (!DOM.boxModel)
t.element.setStyles({width : tw + 2, height : th + 2});
else
t.element.setStyles({width : tw, height : th});

if (s.max_width)
DOM.setStyle(co, 'width', tw);

if (s.max_height) {
DOM.setStyle(co, 'height', th);

if (tb.clientHeight < s.max_height)


DOM.setStyle(co, 'overflow', 'hidden');
}
},

showMenu : function(x, y, px) {


var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h,
mx, my, ot = 2, dm, tb, cp = t.classPrefix;
t.collapse(1);

if (t.isMenuVisible)
return;

if (!t.rendered) {
co = DOM.add(t.settings.container, t.renderNode());

each(t.items, function(o) {
o.postRender();
});

t.element = new Element('menu_' + t.id, {blocker : 1,


container : s.container});
} else
co = DOM.get('menu_' + t.id);

// Move layer out of sight unless it's Opera since it scrolls to


top of page due to an bug
if (!tinymce.isOpera)
DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});

DOM.show(co);
t.update();

x += s.offset_x || 0;
y += s.offset_y || 0;
vp.w -= 4;
vp.h -= 4;

// Move inside viewport if not submenu


if (s.constrain) {
w = co.clientWidth - ot;
h = co.clientHeight - ot;
mx = vp.x + vp.w;
my = vp.y + vp.h;

if ((x + s.vp_offset_x + w) > mx)


x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) -
w);

if ((y + s.vp_offset_y + h) > my)


y = Math.max(0, (my - s.vp_offset_y) - h);
}

DOM.setStyles(co, {left : x , top : y});


t.element.update();

t.isMenuVisible = 1;
t.mouseClickFunc = Event.add(co, 'click', function(e) {
var m;

e = e.target;

if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e,


cp + 'ItemSub')) {
m = t.items[e.id];
if (m.isDisabled())
return;

dm = t;

while (dm) {
if (dm.hideMenu)
dm.hideMenu();

dm = dm.settings.parent;
}

if (m.settings.onclick)
m.settings.onclick(e);

return false; // Cancel to fix onbeforeunload problem


}
});

if (t.hasMenus()) {
t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
var m, r, mi;

e = e.target;
if (e && (e = DOM.getParent(e, 'tr'))) {
m = t.items[e.id];

if (t.lastMenu)
t.lastMenu.collapse(1);

if (m.isDisabled())
return;

if (e && DOM.hasClass(e, cp + 'ItemSub')) {


//p = DOM.getPos(s.container);
r = DOM.getRect(e);
m.showMenu((r.x + r.w - ot), r.y - ot,
r.x);
t.lastMenu = m;
DOM.addClass(DOM.get(m.id).firstChild, cp
+ 'ItemActive');
}
}
});
}

Event.add(co, 'keydown', t._keyHandler, t);

t.onShowMenu.dispatch(t);

if (s.keyboard_focus) {
t._setupKeyboardNav();
}
},

hideMenu : function(c) {
var t = this, co = DOM.get('menu_' + t.id), e;

if (!t.isMenuVisible)
return;

if (t.keyboardNav) t.keyboardNav.destroy();
Event.remove(co, 'mouseover', t.mouseOverFunc);
Event.remove(co, 'click', t.mouseClickFunc);
Event.remove(co, 'keydown', t._keyHandler);
DOM.hide(co);
t.isMenuVisible = 0;

if (!c)
t.collapse(1);

if (t.element)
t.element.hide();

if (e = DOM.get(t.id))
DOM.removeClass(e.firstChild, t.classPrefix +
'ItemActive');

t.onHideMenu.dispatch(t);
},

add : function(o) {
var t = this, co;

o = t.parent(o);

if (t.isRendered && (co = DOM.get('menu_' + t.id)))


t._add(DOM.select('tbody', co)[0], o);

return o;
},

collapse : function(d) {
this.parent(d);
this.hideMenu(1);
},

remove : function(o) {
DOM.remove(o.id);
this.destroy();

return this.parent(o);
},

destroy : function() {
var t = this, co = DOM.get('menu_' + t.id);

if (t.keyboardNav) t.keyboardNav.destroy();
Event.remove(co, 'mouseover', t.mouseOverFunc);
Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
Event.remove(co, 'click', t.mouseClickFunc);
Event.remove(co, 'keydown', t._keyHandler);

if (t.element)
t.element.remove();

DOM.remove(co);
},
renderNode : function() {
var t = this, s = t.settings, n, tb, co, w;

w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id,


'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-
index:200000;outline:0'});
if (t.settings.parent) {
DOM.setAttrib(w, 'aria-parent', 'menu_' +
t.settings.parent.id);
}
co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id
+ '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
t.element = new Element('menu_' + t.id, {blocker : 1, container :
s.container});

if (s.menu_line)
DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});

// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' :


'mceMenuContainer'});
n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' +
t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
tb = DOM.add(n, 'tbody');

each(t.items, function(o) {
t._add(tb, o);
});

t.rendered = true;

return w;
},

// Internal functions
_setupKeyboardNav : function(){
var contextMenu, menuItems, t=this;
contextMenu = DOM.get('menu_' + t.id);
menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
menuItems.splice(0,0,contextMenu);
t.keyboardNav = new tinymce.ui.KeyboardNavigation({
root: 'menu_' + t.id,
items: menuItems,
onCancel: function() {
t.hideMenu();
},
enableUpDown: true
});
contextMenu.focus();
},

_keyHandler : function(evt) {
var t = this, e;
switch (evt.keyCode) {
case 37: // Left
if (t.settings.parent) {
t.hideMenu();
t.settings.parent.focus();
Event.cancel(evt);
}
break;
case 39: // Right
if (t.mouseOverFunc)
t.mouseOverFunc(evt);
break;
}
},

_add : function(tb, o) {
var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;

if (s.separator) {
ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp +
'ItemSeparator'});
DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});

if (n = ro.previousSibling)
DOM.addClass(n, 'mceLast');

return;
}

n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' +


cp + 'ItemEnabled'});
n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ?
'presentation' : 'option', href : 'javascript:;', onclick : "return false;",
onmousedown : 'return false;'});

if (s.parent) {
DOM.setAttrib(a, 'aria-haspopup', 'true');
DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
}

DOM.addClass(it, s['class']);
// n = DOM.add(n, 'span', {'class' : 'item'});

ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_'


+ s.icon : '')});

if (s.icon_src)
DOM.add(ic, 'img', {src : s.icon_src});

n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title :


o.settings.title}, o.settings.title);

if (o.settings.style) {
if (typeof o.settings.style == "function")
o.settings.style = o.settings.style();

DOM.setAttrib(n, 'style', o.settings.style);


}

if (tb.childNodes.length == 1)
DOM.addClass(ro, 'mceFirst');

if ((n = ro.previousSibling) && DOM.hasClass(n, cp +


'ItemSeparator'))
DOM.addClass(ro, 'mceFirst');

if (o.collapse)
DOM.addClass(ro, cp + 'ItemSub');

if (n = ro.previousSibling)
DOM.removeClass(n, 'mceLast');

DOM.addClass(ro, 'mceLast');
}
});
})(tinymce);
(function(tinymce) {
var DOM = tinymce.DOM;

tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
Button : function(id, s, ed) {
this.parent(id, s, ed);
this.classPrefix = 'mceButton';
},

renderHTML : function() {
var cp = this.classPrefix, s = this.settings, h, l;

l = DOM.encode(s.label || '');
h = '<a role="button" id="' + this.id + '" href="javascript:;"
class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' :
'') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' +
this.id + '_voice" title="' + DOM.encode(s.title) + '">';
if (s.image && !(this.editor
&&this.editor.forcedHighContrastMode) )
h += '<span class="mceIcon ' + s['class'] + '"><img
class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>'
+ (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
else
h += '<span class="mceIcon ' + s['class'] + '"></span>' +
(l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');

h += '<span class="mceVoiceLabel mceIconOnly" style="display:


none;" id="' + this.id + '_voice">' + s.title + '</span>';
h += '</a>';
return h;
},

postRender : function() {
var t = this, s = t.settings, imgBookmark;

// In IE a large image that occupies the entire editor area will


be deselected when a button is clicked, so
// need to keep the selection in case the selection is lost
if (tinymce.isIE && t.editor) {
tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
var nodeName = t.editor.selection.getNode().nodeName;
imgBookmark = nodeName === 'IMG' ?
t.editor.selection.getBookmark() : null;
});
}
tinymce.dom.Event.add(t.id, 'click', function(e) {
if (!t.isDisabled()) {
// restore the selection in case the selection is
lost in IE
if (tinymce.isIE && t.editor && imgBookmark !== null)
{
t.editor.selection.moveToBookmark(imgBookmark);
}
return s.onclick.call(s.scope, e);
}
});
tinymce.dom.Event.add(t.id, 'keyup', function(e) {
if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
return s.onclick.call(s.scope, e);
});
}
});
})(tinymce);

(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each,
Dispatcher = tinymce.util.Dispatcher, undef;

tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
ListBox : function(id, s, ed) {
var t = this;

t.parent(id, s, ed);

t.items = [];

t.onChange = new Dispatcher(t);

t.onPostRender = new Dispatcher(t);

t.onAdd = new Dispatcher(t);

t.onRenderMenu = new tinymce.util.Dispatcher(this);

t.classPrefix = 'mceListBox';
t.marked = {};
},

select : function(va) {
var t = this, fv, f;

t.marked = {};

if (va == undef)
return t.selectByIndex(-1);

// Is string or number make function selector


if (va && typeof(va)=="function")
f = va;
else {
f = function(v) {
return v == va;
};
}

// Do we need to do something?
if (va != t.selectedValue) {
// Find item
each(t.items, function(o, i) {
if (f(o.value)) {
fv = 1;
t.selectByIndex(i);
return false;
}
});

if (!fv)
t.selectByIndex(-1);
}
},

selectByIndex : function(idx) {
var t = this, e, o, label;

t.marked = {};

if (idx != t.selectedIndex) {
e = DOM.get(t.id + '_text');
label = DOM.get(t.id + '_voiceDesc');
o = t.items[idx];

if (o) {
t.selectedValue = o.value;
t.selectedIndex = idx;
DOM.setHTML(e, DOM.encode(o.title));
DOM.setHTML(label, t.settings.title + " - " +
o.title);
DOM.removeClass(e, 'mceTitle');
DOM.setAttrib(t.id, 'aria-valuenow', o.title);
} else {
DOM.setHTML(e, DOM.encode(t.settings.title));
DOM.setHTML(label, DOM.encode(t.settings.title));
DOM.addClass(e, 'mceTitle');
t.selectedValue = t.selectedIndex = null;
DOM.setAttrib(t.id, 'aria-valuenow',
t.settings.title);
}
e = 0;
}
},

mark : function(value) {
this.marked[value] = true;
},

add : function(n, v, o) {
var t = this;

o = o || {};
o = tinymce.extend(o, {
title : n,
value : v
});

t.items.push(o);
t.onAdd.dispatch(t, o);
},

getLength : function() {
return this.items.length;
},

renderHTML : function() {
var h = '', t = this, s = t.settings, cp = t.classPrefix;

h = '<span role="listbox" aria-haspopup="true" aria-labelledby="'


+ t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table
role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0"
class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') +
'"><tbody><tr>';
h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc',
'class': 'voiceLabel', style:'display:none;'}, t.settings.title);
h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1,
href : 'javascript:;', 'class' : 'mceText', onclick : "return false;",
onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
h += '<td>' + DOM.createHTML('a', {id : t.id + '_open',
tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return
false;", onmousedown : 'return false;'}, '<span><span style="display:none;"
class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
h += '</tr></tbody></table></span>';

return h;
},

showMenu : function() {
var t = this, p2, e = DOM.get(this.id), m;

if (t.isDisabled() || t.items.length === 0)


return;

if (t.menu && t.menu.isMenuVisible)


return t.hideMenu();

if (!t.isMenuRendered) {
t.renderMenu();
t.isMenuRendered = true;
}

p2 = DOM.getPos(e);

m = t.menu;
m.settings.offset_x = p2.x;
m.settings.offset_y = p2.y;
m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy
when it comes to auto focus

// Select in menu
each(t.items, function(o) {
if (m.items[o.id]) {
m.items[o.id].setSelected(0);
}
});

each(t.items, function(o) {
if (m.items[o.id] && t.marked[o.value]) {
m.items[o.id].setSelected(1);
}

if (o.value === t.selectedValue) {


m.items[o.id].setSelected(1);
}
});

m.showMenu(0, e.clientHeight);

Event.add(DOM.doc, 'mousedown', t.hideMenu, t);


DOM.addClass(t.id, t.classPrefix + 'Selected');

//DOM.get(t.id + '_text').focus();
},

hideMenu : function(e) {
var t = this;

if (t.menu && t.menu.isMenuVisible) {


DOM.removeClass(t.id, t.classPrefix + 'Selected');

// Prevent double toogles by canceling the mouse click


event to the button
if (e && e.type == "mousedown" && (e.target.id == t.id +
'_text' || e.target.id == t.id + '_open'))
return;

if (!e || !DOM.getParent(e.target, '.mceMenu')) {


DOM.removeClass(t.id, t.classPrefix + 'Selected');
Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
t.menu.hideMenu();
}
}
},

renderMenu : function() {
var t = this, m;

m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
menu_line : 1,
'class' : t.classPrefix + 'Menu mceNoIcons',
max_width : 250,
max_height : 150
});

m.onHideMenu.add(function() {
t.hideMenu();
t.focus();
});

m.add({
title : t.settings.title,
'class' : 'mceMenuItemTitle',
onclick : function() {
if (t.settings.onselect('') !== false)
t.select(''); // Must be runned after
}
});

each(t.items, function(o) {
// No value then treat it as a title
if (o.value === undef) {
m.add({
title : o.title,
role : "option",
'class' : 'mceMenuItemTitle',
onclick : function() {
if (t.settings.onselect('') !== false)
t.select(''); // Must be runned
after
}
});
} else {
o.id = DOM.uniqueId();
o.role= "option";
o.onclick = function() {
if (t.settings.onselect(o.value) !== false)
t.select(o.value); // Must be runned
after
};

m.add(o);
}
});

t.onRenderMenu.dispatch(t, m);
t.menu = m;
},

postRender : function() {
var t = this, cp = t.classPrefix;

Event.add(t.id, 'click', t.showMenu, t);


Event.add(t.id, 'keydown', function(evt) {
if (evt.keyCode == 32) { // Space
t.showMenu(evt);
Event.cancel(evt);
}
});
Event.add(t.id, 'focus', function() {
if (!t._focused) {
t.keyDownHandler = Event.add(t.id, 'keydown',
function(e) {
if (e.keyCode == 40) {
t.showMenu();
Event.cancel(e);
}
});
t.keyPressHandler = Event.add(t.id, 'keypress',
function(e) {
var v;
if (e.keyCode == 13) {
// Fake select on enter
v = t.selectedValue;
t.selectedValue = null; // Needs to be
null to fake change
Event.cancel(e);
t.settings.onselect(v);
}
});
}

t._focused = 1;
});
Event.add(t.id, 'blur', function() {
Event.remove(t.id, 'keydown', t.keyDownHandler);
Event.remove(t.id, 'keypress', t.keyPressHandler);
t._focused = 0;
});

// Old IE doesn't have hover on all elements


if (tinymce.isIE6 || !DOM.boxModel) {
Event.add(t.id, 'mouseover', function() {
if (!DOM.hasClass(t.id, cp + 'Disabled'))
DOM.addClass(t.id, cp + 'Hover');
});

Event.add(t.id, 'mouseout', function() {


if (!DOM.hasClass(t.id, cp + 'Disabled'))
DOM.removeClass(t.id, cp + 'Hover');
});
}

t.onPostRender.dispatch(t, DOM.get(t.id));
},

destroy : function() {
this.parent();

Event.clear(this.id + '_text');
Event.clear(this.id + '_open');
}
});
})(tinymce);

(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each,
Dispatcher = tinymce.util.Dispatcher, undef;

tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
NativeListBox : function(id, s) {
this.parent(id, s);
this.classPrefix = 'mceNativeListBox';
},

setDisabled : function(s) {
DOM.get(this.id).disabled = s;
this.setAriaProperty('disabled', s);
},

isDisabled : function() {
return DOM.get(this.id).disabled;
},

select : function(va) {
var t = this, fv, f;

if (va == undef)
return t.selectByIndex(-1);

// Is string or number make function selector


if (va && typeof(va)=="function")
f = va;
else {
f = function(v) {
return v == va;
};
}

// Do we need to do something?
if (va != t.selectedValue) {
// Find item
each(t.items, function(o, i) {
if (f(o.value)) {
fv = 1;
t.selectByIndex(i);
return false;
}
});

if (!fv)
t.selectByIndex(-1);
}
},

selectByIndex : function(idx) {
DOM.get(this.id).selectedIndex = idx + 1;
this.selectedValue = this.items[idx] ? this.items[idx].value :
null;
},

add : function(n, v, a) {
var o, t = this;

a = a || {};
a.value = v;

if (t.isRendered())
DOM.add(DOM.get(this.id), 'option', a, n);

o = {
title : n,
value : v,
attribs : a
};

t.items.push(o);
t.onAdd.dispatch(t, o);
},

getLength : function() {
return this.items.length;
},
renderHTML : function() {
var h, t = this;

h = DOM.createHTML('option', {value : ''}, '-- ' +


t.settings.title + ' --');

each(t.items, function(it) {
h += DOM.createHTML('option', {value : it.value},
it.title);
});

h = DOM.createHTML('select', {id : t.id, 'class' :


'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
h += DOM.createHTML('span', {id : t.id + '_aria', 'style':
'display: none'}, t.settings.title);
return h;
},

postRender : function() {
var t = this, ch, changeListenerAdded = true;

t.rendered = true;

function onChange(e) {
var v = t.items[e.target.selectedIndex - 1];

if (v && (v = v.value)) {
t.onChange.dispatch(t, v);

if (t.settings.onselect)
t.settings.onselect(v);
}
};

Event.add(t.id, 'change', onChange);

// Accessibility keyhandler
Event.add(t.id, 'keydown', function(e) {
var bf;

Event.remove(t.id, 'change', ch);


changeListenerAdded = false;

bf = Event.add(t.id, 'blur', function() {


if (changeListenerAdded) return;
changeListenerAdded = true;
Event.add(t.id, 'change', onChange);
Event.remove(t.id, 'blur', bf);
});

//prevent default left and right keys on chrome - so that


the keyboard navigation is used.
if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
return Event.prevent(e);
}

if (e.keyCode == 13 || e.keyCode == 32) {


onChange(e);
return Event.cancel(e);
}
});

t.onPostRender.dispatch(t, DOM.get(t.id));
}
});
})(tinymce);

(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;

tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
MenuButton : function(id, s, ed) {
this.parent(id, s, ed);

this.onRenderMenu = new tinymce.util.Dispatcher(this);

s.menu_container = s.menu_container || DOM.doc.body;


},

showMenu : function() {
var t = this, p1, p2, e = DOM.get(t.id), m;

if (t.isDisabled())
return;

if (!t.isMenuRendered) {
t.renderMenu();
t.isMenuRendered = true;
}

if (t.isMenuVisible)
return t.hideMenu();

p1 = DOM.getPos(t.settings.menu_container);
p2 = DOM.getPos(e);

m = t.menu;
m.settings.offset_x = p2.x;
m.settings.offset_y = p2.y;
m.settings.vp_offset_x = p2.x;
m.settings.vp_offset_y = p2.y;
m.settings.keyboard_focus = t._focused;
m.showMenu(0, e.firstChild.clientHeight);

Event.add(DOM.doc, 'mousedown', t.hideMenu, t);


t.setState('Selected', 1);

t.isMenuVisible = 1;
},

renderMenu : function() {
var t = this, m;

m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
menu_line : 1,
'class' : this.classPrefix + 'Menu',
icons : t.settings.icons
});
m.onHideMenu.add(function() {
t.hideMenu();
t.focus();
});

t.onRenderMenu.dispatch(t, m);
t.menu = m;
},

hideMenu : function(e) {
var t = this;

// Prevent double toogles by canceling the mouse click event to


the button
if (e && e.type == "mousedown" && DOM.getParent(e.target,
function(e) {return e.id === t.id || e.id === t.id + '_open';}))
return;

if (!e || !DOM.getParent(e.target, '.mceMenu')) {


t.setState('Selected', 0);
Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
if (t.menu)
t.menu.hideMenu();
}

t.isMenuVisible = 0;
},

postRender : function() {
var t = this, s = t.settings;

Event.add(t.id, 'click', function() {


if (!t.isDisabled()) {
if (s.onclick)
s.onclick(t.value);

t.showMenu();
}
});
}
});
})(tinymce);

(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;

tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
SplitButton : function(id, s, ed) {
this.parent(id, s, ed);
this.classPrefix = 'mceSplitButton';
},

renderHTML : function() {
var h, t = this, s = t.settings, h1;

h = '<tbody><tr>';

if (s.image)
h1 = DOM.createHTML('img ', {src : s.image, role:
'presentation', 'class' : 'mceAction ' + s['class']});
else
h1 = DOM.createHTML('span', {'class' : 'mceAction ' +
s['class']}, '');

h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel


mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id +
'_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' +
s['class'], onclick : "return false;", onmousedown : 'return false;', title :
s.title}, h1) + '</td>';

h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']},


'<span style="display:none;" class="mceIconOnly" aria-
hidden="true">\u25BC</span>');
h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id +
'_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'],
onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) +
'</td>';

h += '</tr></tbody>';
h = DOM.createHTML('table', { role: 'presentation', 'class' :
'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0',
cellspacing : '0', title : s.title}, h);
return DOM.createHTML('div', {id : t.id, role: 'button',
tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
},

postRender : function() {
var t = this, s = t.settings, activate;

if (s.onclick) {
activate = function(evt) {
if (!t.isDisabled()) {
s.onclick(t.value);
Event.cancel(evt);
}
};
Event.add(t.id + '_action', 'click', activate);
Event.add(t.id, ['click', 'keydown'], function(evt) {
var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14,
DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
if ((evt.keyCode === 32 || evt.keyCode === 13 ||
evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
activate();
Event.cancel(evt);
} else if (evt.type === 'click' || evt.keyCode ===
DOM_VK_DOWN) {
t.showMenu();
Event.cancel(evt);
}
});
}

Event.add(t.id + '_open', 'click', function (evt) {


t.showMenu();
Event.cancel(evt);
});
Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused
= 1;});
Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused
= 0;});

// Old IE doesn't have hover on all elements


if (tinymce.isIE6 || !DOM.boxModel) {
Event.add(t.id, 'mouseover', function() {
if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
DOM.addClass(t.id, 'mceSplitButtonHover');
});

Event.add(t.id, 'mouseout', function() {


if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
DOM.removeClass(t.id, 'mceSplitButtonHover');
});
}
},

destroy : function() {
this.parent();

Event.clear(this.id + '_action');
Event.clear(this.id + '_open');
Event.clear(this.id);
}
});
})(tinymce);

(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each =
tinymce.each;

tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
ColorSplitButton : function(id, s, ed) {
var t = this;

t.parent(id, s, ed);

t.settings = s = tinymce.extend({
colors :
'000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,00800
0,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,9999
99,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCF
FCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
grid_width : 8,
default_color : '#888888'
}, t.settings);

t.onShowMenu = new tinymce.util.Dispatcher(t);

t.onHideMenu = new tinymce.util.Dispatcher(t);

t.value = s.default_color;
},

showMenu : function() {
var t = this, r, p, e, p2;
if (t.isDisabled())
return;

if (!t.isMenuRendered) {
t.renderMenu();
t.isMenuRendered = true;
}

if (t.isMenuVisible)
return t.hideMenu();

e = DOM.get(t.id);
DOM.show(t.id + '_menu');
DOM.addClass(e, 'mceSplitButtonSelected');
p2 = DOM.getPos(e);
DOM.setStyles(t.id + '_menu', {
left : p2.x,
top : p2.y + e.firstChild.clientHeight,
zIndex : 200000
});
e = 0;

Event.add(DOM.doc, 'mousedown', t.hideMenu, t);


t.onShowMenu.dispatch(t);

if (t._focused) {
t._keyHandler = Event.add(t.id + '_menu', 'keydown',
function(e) {
if (e.keyCode == 27)
t.hideMenu();
});

DOM.select('a', t.id + '_menu')[0].focus(); // Select first


link
}

t.keyboardNav = new tinymce.ui.KeyboardNavigation({


root: t.id + '_menu',
items: DOM.select('a', t.id + '_menu'),
onCancel: function() {
t.hideMenu();
t.focus();
}
});

t.keyboardNav.focus();
t.isMenuVisible = 1;
},

hideMenu : function(e) {
var t = this;

if (t.isMenuVisible) {
// Prevent double toogles by canceling the mouse click
event to the button
if (e && e.type == "mousedown" && DOM.getParent(e.target,
function(e) {return e.id === t.id + '_open';}))
return;
if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu'))
{
DOM.removeClass(t.id, 'mceSplitButtonSelected');
Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
Event.remove(t.id + '_menu', 'keydown',
t._keyHandler);
DOM.hide(t.id + '_menu');
}

t.isMenuVisible = 0;
t.onHideMenu.dispatch();
t.keyboardNav.destroy();
}
},

renderMenu : function() {
var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;

w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id


+ '_menu', 'class' : s.menu_class + ' ' + s['class'], style :
'position:absolute;left:0;top:-1000px;'});
m = DOM.add(w, 'div', {'class' : s['class'] + '
mceSplitButtonMenu'});
DOM.add(m, 'span', {'class' : 'mceMenuLine'});

n = DOM.add(m, 'table', {role: 'presentation', 'class' :


'mceColorSplitMenu'});
tb = DOM.add(n, 'tbody');

// Generate color grid


i = 0;
each(is(s.colors, 'array') ? s.colors : s.colors.split(','),
function(c) {
c = c.replace(/^#/, '');

if (!i--) {
tr = DOM.add(tb, 'tr');
i = s.grid_width - 1;
}

n = DOM.add(tr, 'td');
var settings = {
href : 'javascript:;',
style : {
backgroundColor : '#' + c
},
'title': t.editor.getLang('colors.' + c, c),
'data-mce-color' : '#' + c
};

// adding a proper ARIA role = button causes JAWS to read


things incorrectly on IE.
if (!tinymce.isIE ) {
settings.role = 'option';
}

n = DOM.add(n, 'a', settings);

if (t.editor.forcedHighContrastMode) {
n = DOM.add(n, 'canvas', { width: 16, height: 16,
'aria-hidden': 'true' });
if (n.getContext && (context = n.getContext("2d"))) {
context.fillStyle = '#' + c;
context.fillRect(0, 0, 16, 16);
} else {
// No point leaving a canvas element around if
it's not supported for drawing on anyway.
DOM.remove(n);
}
}
});

if (s.more_colors_func) {
n = DOM.add(tb, 'tr');
n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' :
'mceMoreColors'});
n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more',
href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'},
s.more_colors_title);

Event.add(n, 'click', function(e) {


s.more_colors_func.call(s.more_colors_scope || this);
return Event.cancel(e); // Cancel to fix
onbeforeunload problem
});
}

DOM.addClass(m, 'mceColorSplitMenu');

// Prevent IE from scrolling and hindering click to occur #4019


Event.add(t.id + '_menu', 'mousedown', function(e) {return
Event.cancel(e);});

Event.add(t.id + '_menu', 'click', function(e) {


var c;

e = DOM.getParent(e.target, 'a', tb);

if (e && e.nodeName.toLowerCase() == 'a' && (c =


e.getAttribute('data-mce-color')))
t.setColor(c);

return false; // Prevent IE auto save warning


});

return w;
},

setColor : function(c) {
this.displayColor(c);
this.hideMenu();
this.settings.onselect(c);
},

displayColor : function(c) {
var t = this;

DOM.setStyle(t.id + '_preview', 'backgroundColor', c);


t.value = c;
},

postRender : function() {
var t = this, id = t.id;

t.parent();
DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' :
'mceColorPreview'});
DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
},

destroy : function() {
var self = this;

self.parent();

Event.clear(self.id + '_menu');
Event.clear(self.id + '_more');
DOM.remove(self.id + '_menu');

if (self.keyboardNav) {
self.keyboardNav.destroy();
}
}
});
})(tinymce);

(function(tinymce) {
// Shorten class names
var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
renderHTML : function() {
var t = this, h = [], controls = t.controls, each = tinymce.each,
settings = t.settings;

h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id +


'_voice">');
//TODO: ACC test this out - adding a role = application for getting the
landmarks working well.
h.push("<span role='application'>");
h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel"
style="display:none;">' + dom.encode(settings.name) + '</span>');
each(controls, function(toolbar) {
h.push(toolbar.renderHTML());
});
h.push("</span>");
h.push('</div>');

return h.join('');
},

focus : function() {
var t = this;
dom.get(t.id).focus();
},

postRender : function() {
var t = this, items = [];

each(t.controls, function(toolbar) {
each (toolbar.controls, function(control) {
if (control.id) {
items.push(control);
}
});
});

t.keyNav = new tinymce.ui.KeyboardNavigation({


root: t.id,
items: items,
onCancel: function() {
//Move focus if webkit so that navigation back will read
the item.
if (tinymce.isWebKit) {
dom.get(t.editor.id+"_ifr").focus();
}
t.editor.focus();
},
excludeFromTabOrder: !t.settings.tab_focus_toolbar
});
},

destroy : function() {
var self = this;

self.parent();
self.keyNav.destroy();
Event.clear(self.id);
}
});
})(tinymce);

(function(tinymce) {
// Shorten class names
var dom = tinymce.DOM, each = tinymce.each;
tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
renderHTML : function() {
var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;

cl = t.controls;
for (i=0; i<cl.length; i++) {
// Get current control, prev control, next control and if the
control is a list box or not
co = cl[i];
pr = cl[i - 1];
nx = cl[i + 1];

// Add toolbar start


if (i === 0) {
c = 'mceToolbarStart';

if (co.Button)
c += ' mceToolbarStartButton';
else if (co.SplitButton)
c += ' mceToolbarStartSplitButton';
else if (co.ListBox)
c += ' mceToolbarStartListBox';

h += dom.createHTML('td', {'class' : c},


dom.createHTML('span', null, '<!-- IE -->'));
}

// Add toolbar end before list box and after the previous button
// This is to fix the o2k7 editor skins
if (pr && co.ListBox) {
if (pr.Button || pr.SplitButton)
h += dom.createHTML('td', {'class' :
'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
}

// Render control HTML

// IE 8 quick fix, needed to propertly generate a hit area for


anchors
if (dom.stdMode)
h += '<td style="position: relative">' + co.renderHTML() +
'</td>';
else
h += '<td>' + co.renderHTML() + '</td>';

// Add toolbar start after list box and before the next button
// This is to fix the o2k7 editor skins
if (nx && co.ListBox) {
if (nx.Button || nx.SplitButton)
h += dom.createHTML('td', {'class' :
'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
}
}

c = 'mceToolbarEnd';

if (co.Button)
c += ' mceToolbarEndButton';
else if (co.SplitButton)
c += ' mceToolbarEndSplitButton';
else if (co.ListBox)
c += ' mceToolbarEndListBox';

h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null,


'<!-- IE -->'));

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'}, '<tbody><tr>' + h +
'</tr></tbody>');
}
});
})(tinymce);

(function(tinymce) {
var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;

tinymce.create('tinymce.AddOnManager', {
AddOnManager : function() {
var self = this;
self.items = [];
self.urls = {};
self.lookup = {};
self.onAdd = new Dispatcher(self);
},

get : function(n) {
if (this.lookup[n]) {
return this.lookup[n].instance;
} else {
return undefined;
}
},

dependencies : function(n) {
var result;
if (this.lookup[n]) {
result = this.lookup[n].dependencies;
}
return result || [];
},

requireLangPack : function(n) {
var s = tinymce.settings;

if (s && s.language && s.language_load !== false)


tinymce.ScriptLoader.add(this.urls[n] + '/langs/' +
s.language + '.js');
},

add : function(id, o, dependencies) {


this.items.push(o);
this.lookup[id] = {instance:o, dependencies:dependencies};
this.onAdd.dispatch(this, id, o);

return o;
},
createUrl: function(baseUrl, dep) {
if (typeof dep === "object") {
return dep
} else {
return {prefix: baseUrl.prefix, resource: dep, suffix:
baseUrl.suffix};
}
},

addComponents: function(pluginName, scripts) {


var pluginUrl = this.urls[pluginName];
tinymce.each(scripts, function(script){
tinymce.ScriptLoader.add(pluginUrl+"/"+script);
});
},

load : function(n, u, cb, s) {


var t = this, url = u;

function loadDependencies() {
var dependencies = t.dependencies(n);
tinymce.each(dependencies, function(dep) {
var newUrl = t.createUrl(u, dep);
t.load(newUrl.resource, newUrl, undefined,
undefined);
});
if (cb) {
if (s) {
cb.call(s);
} else {
cb.call(tinymce.ScriptLoader);
}
}
}

if (t.urls[n])
return;
if (typeof u === "object")
url = u.prefix + u.resource + u.suffix;

if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)


url = tinymce.baseURL + '/' + url;

t.urls[n] = url.substring(0, url.lastIndexOf('/'));

if (t.lookup[n]) {
loadDependencies();
} else {
tinymce.ScriptLoader.add(url, loadDependencies, s);
}
}
});

// Create plugin and theme managers


tinymce.PluginManager = new tinymce.AddOnManager();
tinymce.ThemeManager = new tinymce.AddOnManager();
}(tinymce));

(function(tinymce) {
// Shorten names
var each = tinymce.each, extend = tinymce.extend,
DOM = tinymce.DOM, Event = tinymce.dom.Event,
ThemeManager = tinymce.ThemeManager, PluginManager =
tinymce.PluginManager,
explode = tinymce.explode,
Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;

// Setup some URLs where the editor API is located and where the document is
tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/,
'').replace(/[\/\\][^\/]+$/, '');
if (!/[\/\\]$/.test(tinymce.documentBaseURL))
tinymce.documentBaseURL += '/';

tinymce.baseURL = new
tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);

tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);

// Add before unload listener


// This was required since IE was leaking memory if you added and removed
beforeunload listeners
// with attachEvent/detatchEvent so this only adds one listener and instances
can the attach to the onBeforeUnload event
tinymce.onBeforeUnload = new Dispatcher(tinymce);

// Must be on window or IE will leak if the editor is placed in frame or


iframe
Event.add(window, 'beforeunload', function(e) {
tinymce.onBeforeUnload.dispatch(tinymce, e);
});

tinymce.onAddEditor = new Dispatcher(tinymce);

tinymce.onRemoveEditor = new Dispatcher(tinymce);

tinymce.EditorManager = extend(tinymce, {
editors : [],

i18n : {},

activeEditor : null,

init : function(s) {
var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;

function createId(elm) {
var id = elm.id;

// Use element id, or unique name or generate a unique id


if (!id) {
id = elm.name;

if (id && !DOM.get(id)) {


id = elm.name;
} else {
// Generate unique name
id = DOM.uniqueId();
}

elm.setAttribute('id', id);
}

return id;
};

function execCallback(se, n, s) {
var f = se[n];

if (!f)
return;

if (tinymce.is(f, 'string')) {
s = f.replace(/\.\w+$/, '');
s = s ? tinymce.resolve(s) : 0;
f = tinymce.resolve(f);
}

return f.apply(s || this,


Array.prototype.slice.call(arguments, 2));
};

function hasClass(n, c) {
return c.constructor === RegExp ? c.test(n.className) :
DOM.hasClass(n, c);
};

t.settings = s;

// Legacy call
Event.bind(window, 'ready', function() {
var l, co;

execCallback(s, 'onpageload');

switch (s.mode) {
case "exact":
l = s.elements || '';

if(l.length > 0) {
each(explode(l), function(v) {
if (DOM.get(v)) {
ed = new tinymce.Editor(v,
s);
el.push(ed);
ed.render(1);
} else {
each(document.forms,
function(f) {
each(f.elements,
function(e) {
if (e.name === v)
{
v =
'mce_editor_' + instanceCounter++;

DOM.setAttrib(e, 'id', v);

ed = new
tinymce.Editor(v, s);
el.push(ed);

ed.render(1);
}
});
});
}
});
}
break;

case "textareas":
case "specific_textareas":
each(DOM.select('textarea'), function(elm) {
if (s.editor_deselector && hasClass(elm,
s.editor_deselector))
return;

if (!s.editor_selector || hasClass(elm,
s.editor_selector)) {
ed = new
tinymce.Editor(createId(elm), s);
el.push(ed);
ed.render(1);
}
});
break;

default:
if (s.types) {
// Process type specific selector
each(s.types, function(type) {
each(DOM.select(type.selector),
function(elm) {
var editor = new
tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
el.push(editor);
editor.render(1);
});
});
} else if (s.selector) {
// Process global selector
each(DOM.select(s.selector),
function(elm) {
var editor = new
tinymce.Editor(createId(elm), s);
el.push(editor);
editor.render(1);
});
}
}

// Call onInit when all editors are initialized


if (s.oninit) {
l = co = 0;

each(el, function(ed) {
co++;

if (!ed.initialized) {
// Wait for it
ed.onInit.add(function() {
l++;

// All done
if (l == co)
execCallback(s, 'oninit');
});
} else
l++;

// All done
if (l == co)
execCallback(s, 'oninit');

});
}
});
},

get : function(id) {
if (id === undef)
return this.editors;

if (!this.editors.hasOwnProperty(id))
return undef;

return this.editors[id];
},

getInstanceById : function(id) {
return this.get(id);
},

add : function(editor) {
var self = this, editors = self.editors;

// Add named and index editor instance


editors[editor.id] = editor;
editors.push(editor);

self._setActive(editor);
self.onAddEditor.dispatch(self, editor);

return editor;
},

remove : function(editor) {
var t = this, i, editors = t.editors;

// Not in the collection


if (!editors[editor.id])
return null;

delete editors[editor.id];

for (i = 0; i < editors.length; i++) {


if (editors[i] == editor) {
editors.splice(i, 1);
break;
}
}

// Select another editor since the active one was removed


if (t.activeEditor == editor)
t._setActive(editors[0]);

editor.destroy();
t.onRemoveEditor.dispatch(t, editor);

return editor;
},

execCommand : function(c, u, v) {
var t = this, ed = t.get(v), w;
function clr() {
ed.destroy();
w.detachEvent('onunload', clr);
w = w.tinyMCE = w.tinymce = null; // IE leak
};

// Manager commands
switch (c) {
case "mceFocus":
ed.focus();
return true;

case "mceAddEditor":
case "mceAddControl":
if (!t.get(v))
new tinymce.Editor(v, t.settings).render();

return true;

case "mceAddFrameControl":
w = v.window;

// Add tinyMCE global instance and tinymce namespace


to specified window
w.tinyMCE = tinyMCE;
w.tinymce = tinymce;

tinymce.DOM.doc = w.document;
tinymce.DOM.win = w;

ed = new tinymce.Editor(v.element_id, v);


ed.render();

// Fix IE memory leaks


if (tinymce.isIE) {
w.attachEvent('onunload', clr);
}

v.page_window = null;

return true;

case "mceRemoveEditor":
case "mceRemoveControl":
if (ed)
ed.remove();

return true;

case 'mceToggleEditor':
if (!ed) {
t.execCommand('mceAddControl', 0, v);
return true;
}

if (ed.isHidden())
ed.show();
else
ed.hide();
return true;
}

// Run command on active editor


if (t.activeEditor)
return t.activeEditor.execCommand(c, u, v);

return false;
},

execInstanceCommand : function(id, c, u, v) {
var ed = this.get(id);

if (ed)
return ed.execCommand(c, u, v);

return false;
},

triggerSave : function() {
each(this.editors, function(e) {
e.save();
});
},

addI18n : function(p, o) {
var lo, i18n = this.i18n;

if (!tinymce.is(p, 'string')) {
each(p, function(o, lc) {
each(o, function(o, g) {
each(o, function(o, k) {
if (g === 'common')
i18n[lc + '.' + k] = o;
else
i18n[lc + '.' + g + '.' + k] = o;
});
});
});
} else {
each(o, function(o, k) {
i18n[p + '.' + k] = o;
});
}
},

// Private methods

_setActive : function(editor) {
this.selectedInstance = this.activeEditor = editor;
}
});
})(tinymce);

(function(tinymce) {
// Shorten these names
var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
each = tinymce.each, isGecko = tinymce.isGecko,
isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
ThemeManager = tinymce.ThemeManager, PluginManager =
tinymce.PluginManager,
explode = tinymce.explode;

tinymce.create('tinymce.Editor', {
Editor : function(id, settings) {
var self = this, TRUE = true;

self.settings = settings = extend({


id : id,
language : 'en',
theme : 'advanced',
skin : 'default',
delta_width : 0,
delta_height : 0,
popup_css : '',
plugins : '',
document_base_url : tinymce.documentBaseURL,
add_form_submit_trigger : TRUE,
submit_patch : TRUE,
add_unload_trigger : TRUE,
convert_urls : TRUE,
relative_urls : TRUE,
remove_script_host : TRUE,
table_inline_editing : false,
object_resizing : TRUE,
accessibility_focus : TRUE,
doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC
"-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE
6 to avoid horizontal scroll
visual : TRUE,
font_size_style_values : 'xx-small,x-
small,small,medium,large,x-large,xx-large',
font_size_legacy_values : 'xx-small,small,medium,large,x-
large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-
size
apply_source_formatting : TRUE,
directionality : 'ltr',
forced_root_block : 'p',
hidden_input : TRUE,
padd_empty_editor : TRUE,
render_ui : TRUE,
indentation : '30px',
fix_table_elements : TRUE,
inline_styles : TRUE,
convert_fonts_to_spans : TRUE,
indent : 'simple',
indent_before :
'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thea
d,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
indent_after :
'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thea
d,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
validate : TRUE,
entity_encoding : 'named',
url_converter : self.convertURL,
url_converter_scope : self,
ie7_compat : TRUE
}, settings);

self.id = self.editorId = id;

self.isNotDirty = false;

self.plugins = {};

self.documentBaseURI = new
tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
base_uri : tinyMCE.baseURI
});

self.baseURI = tinymce.baseURI;

self.contentCSS = [];

self.contentStyles = [];

// Creates all events like onClick, onSetContent etc see


Editor.Events.js for the actual logic
self.setupEvents();

// Internal command handler objects


self.execCommands = {};
self.queryStateCommands = {};
self.queryValueCommands = {};

// Call setup
self.execCallback('setup', self);
},

render : function(nst) {
var t = this, s = t.settings, id = t.id, sl =
tinymce.ScriptLoader;

// Page is not loaded yet, wait for it


if (!Event.domLoaded) {
Event.add(window, 'ready', function() {
t.render();
});
return;
}

tinyMCE.settings = s;

// Element not found, then skip initialization


if (!t.getElement())
return;

// Is a iPad/iPhone and not on iOS5, then skip initialization. We


need to sniff
// here since the browser says it has contentEditable support but
there is no visible caret.
if (tinymce.isIDevice && !tinymce.isIOS5)
return;

// Add hidden input for non input elements inside form elements
if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) &&
s.hidden_input && DOM.getParent(id, 'form'))
DOM.insertAfter(DOM.create('input', {type : 'hidden',
name : id}), id);

// Hide target element early to prevent content flashing


if (!s.content_editable) {
t.orgVisibility = t.getElement().style.visibility;
t.getElement().style.visibility = 'hidden';
}

if (tinymce.WindowManager)
t.windowManager = new tinymce.WindowManager(t);

if (s.encoding == 'xml') {
t.onGetContent.add(function(ed, o) {
if (o.save)
o.content = DOM.encode(o.content);
});
}

if (s.add_form_submit_trigger) {
t.onSubmit.addToTop(function() {
if (t.initialized) {
t.save();
t.isNotDirty = 1;
}
});
}

if (s.add_unload_trigger) {
t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
if (t.initialized && !t.destroyed && !t.isHidden())
t.save({format : 'raw', no_events : true});
});
}

tinymce.addUnload(t.destroy, t);

if (s.submit_patch) {
t.onBeforeRenderUI.add(function() {
var n = t.getElement().form;

if (!n)
return;

// Already patched
if (n._mceOldSubmit)
return;

// Check page uses id="submit" or name="submit" for


it's submit button
if (!n.submit.nodeType && !n.submit.length) {
t.formElement = n;
n._mceOldSubmit = n.submit;
n.submit = function() {
// Save all instances
tinymce.triggerSave();
t.isNotDirty = 1;
return
t.formElement._mceOldSubmit(t.formElement);
};
}

n = null;
});
}

// Load scripts
function loadScripts() {
if (s.language && s.language_load !== false)
sl.add(tinymce.baseURL + '/langs/' + s.language +
'.js');

if (s.theme && typeof s.theme != "function" &&


s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
ThemeManager.load(s.theme, 'themes/' + s.theme +
'/editor_template' + tinymce.suffix + '.js');

each(explode(s.plugins), function(p) {
if (p &&!PluginManager.urls[p]) {
if (p.charAt(0) == '-') {
p = p.substr(1, p.length);
var dependencies =
PluginManager.dependencies(p);
each(dependencies, function(dep) {
var defaultSettings =
{prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix +
'.js'};
dep =
PluginManager.createUrl(defaultSettings, dep);
PluginManager.load(dep.resource,
dep);
});
} else {
// Skip safari plugin, since it is
removed as of 3.3b1
if (p == 'safari') {
return;
}
PluginManager.load(p, {prefix:'plugins/',
resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
}
}
});

// Init when que is loaded


sl.loadQueue(function() {
if (!t.removed)
t.init();
});
};

loadScripts();
},

init : function() {
var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o,
ti, u, bi, bc, re, i, initializedPlugins = [];

tinymce.add(t);

s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label',


t.getLang('aria.rich_text_area'));

if (s.theme) {
if (typeof s.theme != "function") {
s.theme = s.theme.replace(/-/, '');
o = ThemeManager.get(s.theme);
t.theme = new o();

if (t.theme.init)
t.theme.init(t, ThemeManager.urls[s.theme] ||
tinymce.documentBaseURL.replace(/\/$/, ''));
} else {
t.theme = s.theme;
}
}

function initPlugin(p) {
var c = PluginManager.get(p), u = PluginManager.urls[p] ||
tinymce.documentBaseURL.replace(/\/$/, ''), po;
if (c && tinymce.inArray(initializedPlugins,p) === -1) {
each(PluginManager.dependencies(p), function(dep){
initPlugin(dep);
});
po = new c(t, u);

t.plugins[p] = po;

if (po.init) {
po.init(t, u);
initializedPlugins.push(p);
}
}
}

// Create all plugins


each(explode(s.plugins.replace(/\-/g, '')), initPlugin);

// Setup popup CSS path(s)


if (s.popup_css !== false) {
if (s.popup_css)
s.popup_css =
t.documentBaseURI.toAbsolute(s.popup_css);
else
s.popup_css = t.baseURI.toAbsolute("themes/" +
s.theme + "/skins/" + s.skin + "/dialog.css");
}

if (s.popup_css_add)
s.popup_css += ',' +
t.documentBaseURI.toAbsolute(s.popup_css_add);

t.controlManager = new tinymce.ControlManager(t);

// Enables users to override the control factory


t.onBeforeRenderUI.dispatch(t, t.controlManager);

// Measure box
if (s.render_ui && t.theme) {
t.orgDisplay = e.style.display;

if (typeof s.theme != "function") {


w = s.width || e.style.width || e.offsetWidth;
h = s.height || e.style.height || e.offsetHeight;
mh = s.min_height || 100;
re = /^[0-9\.]+(|px)$/i;

if (re.test('' + w))
w = Math.max(parseInt(w, 10) + (o.deltaWidth ||
0), 100);

if (re.test('' + h))
h = Math.max(parseInt(h, 10) + (o.deltaHeight
|| 0), mh);

// Render UI
o = t.theme.renderUI({
targetNode : e,
width : w,
height : h,
deltaWidth : s.delta_width,
deltaHeight : s.delta_height
});

// Resize editor
DOM.setStyles(o.sizeContainer || o.editorContainer, {
width : w,
height : h
});

h = (o.iframeHeight || h) + (typeof(h) == 'number' ?


(o.deltaHeight || 0) : '');
if (h < mh)
h = mh;
} else {
o = s.theme(t, e);

// Convert element type to id:s


if (o.editorContainer.nodeType) {
o.editorContainer = o.editorContainer.id =
o.editorContainer.id || t.id + "_parent";
}

// Convert element type to id:s


if (o.iframeContainer.nodeType) {
o.iframeContainer = o.iframeContainer.id =
o.iframeContainer.id || t.id + "_iframecontainer";
}

// Use specified iframe height or the targets


offsetHeight
h = o.iframeHeight || e.offsetHeight;

// Store away the selection when it's changed to it


can be restored later with a editor.focus() call
if (isIE) {
t.onInit.add(function(ed) {
ed.dom.bind(ed.getBody(),
'beforedeactivate keydown', function() {
ed.lastIERng =
ed.selection.getRng();
});
});
}
}

t.editorContainer = o.editorContainer;
}

// Load specified content CSS last


if (s.content_css) {
each(explode(s.content_css), function(u) {
t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
});
}

// Load specified content CSS last


if (s.content_style) {
t.contentStyles.push(s.content_style);
}

// Content editable mode ends here


if (s.content_editable) {
e = n = o = null; // Fix IE leak
return t.initContentBody();
}

// User specified a document.domain value


if (document.domain && location.hostname != document.domain)
tinymce.relaxedDomain = document.domain;

t.iframeHTML = s.doctype + '<html><head


xmlns="http://www.w3.org/1999/xhtml">';

// We only need to override paths if we have to


// IE has a bug where it remove site absolute urls to relative
ones if this is specified
if (s.document_base_url != tinymce.documentBaseURL)
t.iframeHTML += '<base href="' + t.documentBaseURI.getURI()
+ '" />';

// IE8 doesn't support carets behind images setting ie7_compat


would force IE8+ to run in IE7 compat mode.
if (tinymce.isIE8) {
if (s.ie7_compat)
t.iframeHTML += '<meta http-equiv="X-UA-Compatible"
content="IE=7" />';
else
t.iframeHTML += '<meta http-equiv="X-UA-Compatible"
content="IE=edge" />';
}

t.iframeHTML += '<meta http-equiv="Content-Type"


content="text/html; charset=UTF-8" />';

// Load the CSS by injecting them into the HTML this will reduce
"flicker"
for (i = 0; i < t.contentCSS.length; i++) {
t.iframeHTML += '<link type="text/css" rel="stylesheet"
href="' + t.contentCSS[i] + '" />';
}

t.contentCSS = [];

bi = s.body_id || 'tinymce';
if (bi.indexOf('=') != -1) {
bi = t.getParam('body_id', '', 'hash');
bi = bi[t.id] || bi;
}

bc = s.body_class || '';
if (bc.indexOf('=') != -1) {
bc = t.getParam('body_class', '', 'hash');
bc = bc[t.id] || '';
}

t.iframeHTML += '</head><body id="' + bi + '"


class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id +
'\').onLoad.dispatch();"><br></body></html>';

// Domain relaxing enabled, then set document domain


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
u = 'javascript:(function()
{document.open();document.domain="' + document.domain + '";var ed =
window.parent.tinyMCE.get("' + t.id +
'");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
}

// Create 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',
allowTransparency : "true",
title : s.aria_label,
style : {
width : '100%',
height : h,
display : 'block' // Important for Gecko to render
the iframe correctly
}
});

t.contentAreaContainer = o.iframeContainer;

if (o.editorContainer) {
DOM.get(o.editorContainer).style.display = t.orgDisplay;
}

// Restore visibility on target element


e.style.visibility = t.orgVisibility;

DOM.get(t.id).style.display = 'none';
DOM.setAttrib(t.id, 'aria-hidden', true);

if (!tinymce.relaxedDomain || !u)
t.initContentBody();

e = n = o = null; // Cleanup
},

initContentBody : function() {
var self = this, settings = self.settings, targetElm =
DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;

// Setup iframe body


if ((!isIE || !tinymce.relaxedDomain) && !
settings.content_editable) {
doc.open();
doc.write(self.iframeHTML);
doc.close();

if (tinymce.relaxedDomain)
doc.domain = tinymce.relaxedDomain;
}

if (settings.content_editable) {
DOM.addClass(targetElm, 'mceContentBody');
self.contentDocument = doc = settings.content_document ||
document;
self.contentWindow = settings.content_window || window;
self.bodyElement = targetElm;

// Prevent leak in IE
settings.content_document = settings.content_window = null;
}

// It will not steal focus while setting contentEditable


body = self.getBody();
body.disabled = true;

if (!settings.readonly)
body.contentEditable =
self.getParam('content_editable_state', true);

body.disabled = false;

self.schema = new tinymce.html.Schema(settings);

self.dom = new tinymce.dom.DOMUtils(doc, {


keep_values : true,
url_converter : self.convertURL,
url_converter_scope : self,
hex_colors : settings.force_hex_style_colors,
class_filter : settings.class_filter,
update_styles : true,
root_element : settings.content_editable ? self.id : null,
schema : self.schema
});

self.parser = new tinymce.html.DomParser(settings, self.schema);

// Convert src and href into data-mce-src, data-mce-href and


data-mce-style
self.parser.addAttributeFilter('src,href,style', function(nodes,
name) {
var i = nodes.length, node, dom = self.dom, value,
internalName;

while (i--) {
node = nodes[i];
value = node.attr(name);
internalName = 'data-mce-' + name;

// Add internal attribute if we need to we don't on a


refresh of the document
if (!node.attributes.map[internalName]) {
if (name === "style")
node.attr(internalName,
dom.serializeStyle(dom.parseStyle(value), node.name));
else
node.attr(internalName,
self.convertURL(value, name, node.name));
}
}
});

// Keep scripts from executing


self.parser.addNodeFilter('script', function(nodes, name) {
var i = nodes.length, node;

while (i--) {
node = nodes[i];
node.attr('type', 'mce-' + (node.attr('type') ||
'text/javascript'));
}
});

self.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 + ']]';
}
});

self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div',
function(nodes, name) {
var i = nodes.length, node, nonEmptyElements =
self.schema.getNonEmptyElements();

while (i--) {
node = nodes[i];

if (node.isEmpty(nonEmptyElements))
node.empty().append(new tinymce.html.Node('br',
1)).shortEnded = true;
}
});

self.serializer = new tinymce.dom.Serializer(settings, self.dom,


self.schema);

self.selection = new tinymce.dom.Selection(self.dom,


self.getWin(), self.serializer, self);

self.formatter = new tinymce.Formatter(self);

self.undoManager = new tinymce.UndoManager(self);

self.forceBlocks = new tinymce.ForceBlocks(self);


self.enterKey = new tinymce.EnterKey(self);
self.editorCommands = new tinymce.EditorCommands(self);

self.onExecCommand.add(function(editor, command) {
// Don't refresh the select lists until caret move
if (!/^(FontName|FontSize)$/.test(command))
self.nodeChanged();
});

// Pass through
self.serializer.onPreProcess.add(function(se, o) {
return self.onPreProcess.dispatch(self, o, se);
});

self.serializer.onPostProcess.add(function(se, o) {
return self.onPostProcess.dispatch(self, o, se);
});

self.onPreInit.dispatch(self);

if (!settings.browser_spellcheck && !settings.gecko_spellcheck)


doc.body.spellcheck = false;

if (!settings.readonly) {
self.bindNativeEvents();
}

self.controlManager.onPostRender.dispatch(self,
self.controlManager);
self.onPostRender.dispatch(self);

self.quirks = tinymce.util.Quirks(self);

if (settings.directionality)
body.dir = settings.directionality;

if (settings.nowrap)
body.style.whiteSpace = "nowrap";

if (settings.protect) {
self.onBeforeSetContent.add(function(ed, o) {
each(settings.protect, function(pattern) {
o.content = o.content.replace(pattern,
function(str) {
return '<!--mce:protected ' + escape(str)
+ '-->';
});
});
});
}

// Add visual aids when new contents is added


self.onSetContent.add(function() {
self.addVisual(self.getBody());
});

// Remove empty contents


if (settings.padd_empty_editor) {
self.onPostProcess.add(function(ed, o) {
o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|
&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
});
}

self.load({initial : true, format : 'html'});


self.startContent = self.getContent({format : 'raw'});

self.initialized = true;

self.onInit.dispatch(self);
self.execCallback('setupcontent_callback', self.id, body, doc);
self.execCallback('init_instance_callback', self);
self.focus(true);
self.nodeChanged({initial : true});

// Add editor specific CSS styles


if (self.contentStyles.length > 0) {
contentCssText = '';

each(self.contentStyles, function(style) {
contentCssText += style + "\r\n";
});

self.dom.addStyle(contentCssText);
}

// Load specified content CSS last


each(self.contentCSS, function(url) {
self.dom.loadCSS(url);
});

// Handle auto focus


if (settings.auto_focus) {
setTimeout(function () {
var ed = tinymce.get(settings.auto_focus);

ed.selection.select(ed.getBody(), 1);
ed.selection.collapse(1);
ed.getBody().focus();
ed.getWin().focus();
}, 100);
}

// Clean up references for IE


targetElm = doc = body = null;
},

focus : function(skip_focus) {
var oed, self = this, selection = self.selection, contentEditable
= self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;

if (!skip_focus) {
if (self.lastIERng) {
selection.setRng(self.lastIERng);
}

// Get selected control element


ieRng = selection.getRng();
if (ieRng.item) {
controlElm = ieRng.item(0);
}

self._refreshContentEditable();

// Focus the window iframe


if (!contentEditable) {
self.getWin().focus();
}

// Focus the body as well since it's contentEditable


if (tinymce.isGecko || contentEditable) {
body = self.getBody();

// Check for setActive since it doesn't scroll to the


element
if (body.setActive) {
// In IE9+, attempting to setActive can cause a js error when content
editable is not visible
// similar issue that appeared on another WYSIWYG:
https://github.com/xing/wysihtml5/issues/9
try{
body.setActive();
}
catch(e){
body.focus();
}
} else {
body.focus();
}

if (contentEditable) {
selection.normalize();
}
}

// Restore selected control element


// This is needed when for example an image is selected
within a
// layer a call to focus will then remove the control
selection
if (controlElm && controlElm.ownerDocument == doc) {
ieRng = doc.body.createControlRange();
ieRng.addElement(controlElm);
ieRng.select();
}
}

if (tinymce.activeEditor != self) {
if ((oed = tinymce.activeEditor) != null)
oed.onDeactivate.dispatch(oed, self);

self.onActivate.dispatch(self, oed);
}

tinymce._setActive(self);
},

execCallback : function(n) {
var t = this, f = t.settings[n], s;

if (!f)
return;

// Look through lookup


if (t.callbackLookup && (s = t.callbackLookup[n])) {
f = s.func;
s = s.scope;
}

if (is(f, 'string')) {
s = f.replace(/\.\w+$/, '');
s = s ? tinymce.resolve(s) : 0;
f = tinymce.resolve(f);
t.callbackLookup = t.callbackLookup || {};
t.callbackLookup[n] = {func : f, scope : s};
}

return f.apply(s || t, Array.prototype.slice.call(arguments, 1));


},

translate : function(s) {
var c = this.settings.language || 'en', i18n = tinymce.i18n;

if (!s)
return '';

return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g,


function(a, b) {
return i18n[c + '.' + b] || '{#' + b + '}';
});
},

getLang : function(n, dv) {


return tinymce.i18n[(this.settings.language || 'en') + '.' + n]
|| (is(dv) ? dv : '{#' + n + '}');
},
getParam : function(n, dv, ty) {
var tr = tinymce.trim, v = is(this.settings[n]) ?
this.settings[n] : dv, o;

if (ty === 'hash') {


o = {};

if (is(v, 'string')) {
each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:
[;,]|$))/) : v.split(','), function(v) {
v = v.split('=');

if (v.length > 1)
o[tr(v[0])] = tr(v[1]);
else
o[tr(v[0])] = tr(v);
});
} else
o = v;

return o;
}

return v;
},

nodeChanged : function(o) {
var self = this, selection = self.selection, node;

// Fix for bug #1896577 it seems that this can not be fired while
the editor is loading
if (self.initialized) {
o = o || {};

// Get start node


node = selection.getStart() || self.getBody();
node = isIE && node.ownerDocument != self.getDoc() ?
self.getBody() : node; // Fix for IE initial state

// Get parents and add them to object


o.parents = [];
self.dom.getParent(node, function(node) {
if (node.nodeName == 'BODY')
return true;

o.parents.push(node);
});

self.onNodeChange.dispatch(
self,
o ? o.controlManager || self.controlManager :
self.controlManager,
node,
selection.isCollapsed(),
o
);
}
},
addButton : function(name, settings) {
var self = this;

self.buttons = self.buttons || {};


self.buttons[name] = settings;
},

addCommand : function(name, callback, scope) {


this.execCommands[name] = {func : callback, scope : scope ||
this};
},

addQueryStateHandler : function(name, callback, scope) {


this.queryStateCommands[name] = {func : callback, scope : scope
|| this};
},

addQueryValueHandler : function(name, callback, scope) {


this.queryValueCommands[name] = {func : callback, scope : scope
|| this};
},

addShortcut : function(pa, desc, cmd_func, sc) {


var t = this, c;

if (t.settings.custom_shortcuts === false)


return false;

t.shortcuts = t.shortcuts || {};

if (is(cmd_func, 'string')) {
c = cmd_func;

cmd_func = function() {
t.execCommand(c, false, null);
};
}

if (is(cmd_func, 'object')) {
c = cmd_func;

cmd_func = function() {
t.execCommand(c[0], c[1], c[2]);
};
}

each(explode(pa), function(pa) {
var o = {
func : cmd_func,
scope : sc || this,
desc : t.translate(desc),
alt : false,
ctrl : false,
shift : false
};

each(explode(pa, '+'), function(v) {


switch (v) {
case 'alt':
case 'ctrl':
case 'shift':
o[v] = true;
break;

default:
o.charCode = v.charCodeAt(0);
o.keyCode =
v.toUpperCase().charCodeAt(0);
}
});

t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' :


'') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
});

return true;
},

execCommand : function(cmd, ui, val, a) {


var t = this, s = 0, o, st;

if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|
mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
t.focus();

a = extend({}, a);
t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
if (a.terminate)
return false;

// Command callback
if (t.execCallback('execcommand_callback', t.id,
t.selection.getNode(), cmd, ui, val)) {
t.onExecCommand.dispatch(t, cmd, ui, val, a);
return true;
}

// Registred commands
if (o = t.execCommands[cmd]) {
st = o.func.call(o.scope, ui, val);

// Fall through on true


if (st !== true) {
t.onExecCommand.dispatch(t, cmd, ui, val, a);
return st;
}
}

// Plugin commands
each(t.plugins, function(p) {
if (p.execCommand && p.execCommand(cmd, ui, val)) {
t.onExecCommand.dispatch(t, cmd, ui, val, a);
s = 1;
return false;
}
});

if (s)
return true;

// Theme commands
if (t.theme && t.theme.execCommand && t.theme.execCommand(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);
return true;
}

// Browser commands
t.getDoc().execCommand(cmd, ui, val);
t.onExecCommand.dispatch(t, cmd, ui, val, a);
},

queryCommandState : function(cmd) {
var t = this, o, s;

// Is hidden then return undefined


if (t._isHidden())
return;

// Registred commands
if (o = t.queryStateCommands[cmd]) {
s = o.func.call(o.scope);

// Fall though on true


if (s !== true)
return s;
}

// Registred commands
o = t.editorCommands.queryCommandState(cmd);
if (o !== -1)
return o;

// Browser commands
try {
return this.getDoc().queryCommandState(cmd);
} catch (ex) {
// Fails sometimes see bug: 1896577
}
},

queryCommandValue : function(c) {
var t = this, o, s;

// Is hidden then return undefined


if (t._isHidden())
return;

// Registred commands
if (o = t.queryValueCommands[c]) {
s = o.func.call(o.scope);
// Fall though on true
if (s !== true)
return s;
}

// Registred commands
o = t.editorCommands.queryCommandValue(c);
if (is(o))
return o;

// Browser commands
try {
return this.getDoc().queryCommandValue(c);
} catch (ex) {
// Fails sometimes see bug: 1896577
}
},

show : function() {
var self = this;

DOM.show(self.getContainer());
DOM.hide(self.id);
self.load();
},

hide : function() {
var self = this, doc = self.getDoc();

// Fixed bug where IE has a blinking cursor left from the editor
if (isIE && doc)
doc.execCommand('SelectAll');

// We must save before we hide so Safari doesn't crash


self.save();

// defer the call to hide to prevent an IE9 crash #4921


DOM.hide(self.getContainer());
DOM.setStyle(self.id, 'display', self.orgDisplay);
},

isHidden : function() {
return !DOM.isHidden(this.id);
},

setProgressState : function(b, ti, o) {


this.onSetProgressState.dispatch(this, b, ti, o);

return b;
},

load : function(o) {
var t = this, e = t.getElement(), h;

if (e) {
o = o || {};
o.load = true;
// Double encode existing entities in the value
h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
o.element = e;

if (!o.no_events)
t.onLoadContent.dispatch(t, o);

o.element = e = null;

return h;
}
},

save : function(o) {
var t = this, e = t.getElement(), h, f;

if (!e || !t.initialized)
return;

o = o || {};
o.save = true;

o.element = e;
h = o.content = t.getContent(o);

if (!o.no_events)
t.onSaveContent.dispatch(t, o);

h = o.content;

if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
e.innerHTML = h;

// Update hidden form element


if (f = DOM.getParent(t.id, 'form')) {
each(f.elements, function(e) {
if (e.name == t.id) {
e.value = h;
return false;
}
});
}
} else
e.value = h;

o.element = e = null;

return h;
},

setContent : function(content, args) {


var self = this, rootNode, body = self.getBody(),
forcedRootBlockName;

// Setup args object


args = args || {};
args.format = args.format || 'html';
args.set = true;
args.content = content;
// 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 && (content.length === 0 || /^\s+
$/.test(content))) {
forcedRootBlockName = self.settings.forced_root_block;
if (forcedRootBlockName)
content = '<' + forcedRootBlockName + '><br data-mce-
bogus="1"></' + forcedRootBlockName + '>';
else
content = '<br data-mce-bogus="1">';

body.innerHTML = content;
self.selection.select(body, true);
self.selection.collapse(true);
return;
}

// Parse and serialize the html


if (args.format !== 'raw') {
content = new tinymce.html.Serializer({},
self.schema).serialize(
self.parser.parse(content)
);
}

// 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);

// Don't normalize selection if the focused element isn't the


body in content editable mode since it will steal focus otherwise
if (!self.settings.content_editable || document.activeElement ===
self.getBody()) {
self.selection.normalize();
}

return args.content;
},

getContent : function(args) {
var self = this, content, body = self.getBody();

// Setup args object


args = args || {};
args.format = args.format || 'html';
args.get = true;
args.getInner = true;

// Do preprocessing
if (!args.no_events)
self.onBeforeGetContent.dispatch(self, args);

// Get raw contents or by default the cleaned contents


if (args.format == 'raw')
content = body.innerHTML;
else if (args.format == 'text')
content = body.innerText || body.textContent;
else
content = self.serializer.serialize(body, args);

// Trim whitespace in beginning/end of HTML


if (args.format != 'text') {
args.content = tinymce.trim(content);
} else {
args.content = content;
}

// Do post processing
if (!args.no_events)
self.onGetContent.dispatch(self, args);

return args.content;
},

isDirty : function() {
var self = this;

return tinymce.trim(self.startContent) !=
tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
},

getContainer : function() {
var self = this;

if (!self.container)
self.container = DOM.get(self.editorContainer || self.id +
'_parent');

return self.container;
},

getContentAreaContainer : function() {
return this.contentAreaContainer;
},

getElement : function() {
return DOM.get(this.settings.content_element || this.id);
},

getWin : function() {
var self = this, elm;

if (!self.contentWindow) {
elm = DOM.get(self.id + "_ifr");
if (elm)
self.contentWindow = elm.contentWindow;
}

return self.contentWindow;
},

getDoc : function() {
var self = this, win;

if (!self.contentDocument) {
win = self.getWin();

if (win)
self.contentDocument = win.document;
}

return self.contentDocument;
},

getBody : function() {
return this.bodyElement || this.getDoc().body;
},

convertURL : function(url, name, elm) {


var self = this, settings = self.settings;

// Use callback instead


if (settings.urlconverter_callback)
return self.execCallback('urlconverter_callback', url, elm,
true, name);

// Don't convert link href since thats the CSS files that gets
loaded into the editor also skip local file URLs
if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') ||
url.indexOf('file:') === 0)
return url;

// Convert to relative
if (settings.relative_urls)
return self.documentBaseURI.toRelative(url);

// Convert to absolute
url = self.documentBaseURI.toAbsolute(url,
settings.remove_script_host);

return url;
},

addVisual : function(elm) {
var self = this, settings = self.settings, dom = self.dom, cls;

elm = elm || self.getBody();

if (!is(self.hasVisual))
self.hasVisual = settings.visual;

each(dom.select('table,a', elm), function(elm) {


var value;
switch (elm.nodeName) {
case 'TABLE':
cls = settings.visual_table_class ||
'mceItemTable';
value = dom.getAttrib(elm, 'border');

if (!value || value == '0') {


if (self.hasVisual)
dom.addClass(elm, cls);
else
dom.removeClass(elm, cls);
}

return;

case 'A':
if (!dom.getAttrib(elm, 'href', false)) {
value = dom.getAttrib(elm, 'name') ||
elm.id;
cls = 'mceItemAnchor';

if (value) {
if (self.hasVisual)
dom.addClass(elm, cls);
else
dom.removeClass(elm, cls);
}
}

return;
}
});

self.onVisualAid.dispatch(self, elm, self.hasVisual);


},

remove : function() {
var self = this, elm = self.getContainer(), doc = self.getDoc();

if (!self.removed) {
self.removed = 1; // Cancels post remove event execution

// Fixed bug where IE has a blinking cursor left from the


editor
if (isIE && doc)
doc.execCommand('SelectAll');

// We must save before we hide so Safari doesn't crash


self.save();

DOM.setStyle(self.id, 'display', self.orgDisplay);

// Don't clear the window or document if content editable


// is enabled since other instances might still be present
if (!self.settings.content_editable) {
Event.unbind(self.getWin());
Event.unbind(self.getDoc());
}
Event.unbind(self.getBody());
Event.clear(elm);

self.execCallback('remove_instance_callback', self);
self.onRemove.dispatch(self);

// Clear all execCommand listeners this is required to


avoid errors if the editor was removed inside another command
self.onExecCommand.listeners = [];

tinymce.remove(self);
DOM.remove(elm);
}
},

destroy : function(s) {
var t = this;

// One time is enough


if (t.destroyed)
return;

// We must unbind on Gecko since it would otherwise produce the


pesky "attempt to run compile-and-go script on a cleared scope" message
if (isGecko) {
Event.unbind(t.getDoc());
Event.unbind(t.getWin());
Event.unbind(t.getBody());
}

if (!s) {
tinymce.removeUnload(t.destroy);
tinyMCE.onBeforeUnload.remove(t._beforeUnload);

// Manual destroy
if (t.theme && t.theme.destroy)
t.theme.destroy();

// Destroy controls, selection and dom


t.controlManager.destroy();
t.selection.destroy();
t.dom.destroy();
}

if (t.formElement) {
t.formElement.submit = t.formElement._mceOldSubmit;
t.formElement._mceOldSubmit = null;
}

t.contentAreaContainer = t.formElement = t.container =


t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow =
null;

if (t.selection)
t.selection = t.selection.win = t.selection.dom =
t.selection.dom.doc = null;

t.destroyed = 1;
},

// Internal functions

_refreshContentEditable : function() {
var self = this, body, parent;

// Check if the editor was hidden and the re-initalize


contentEditable mode by removing and adding the body again
if (self._isHidden()) {
body = self.getBody();
parent = body.parentNode;

parent.removeChild(body);
parent.appendChild(body);

body.focus();
}
},

_isHidden : function() {
var s;

if (!isGecko)
return 0;

// Weird, wheres that cursor selection?


s = this.selection.getSel();
return (!s || !s.rangeCount || s.rangeCount === 0);
}
});
})(tinymce);
(function(tinymce) {
var each = tinymce.each;

tinymce.Editor.prototype.setupEvents = function() {
var self = this, settings = self.settings;

// Add events to the editor


each([
'onPreInit',

'onBeforeRenderUI',

'onPostRender',

'onLoad',

'onInit',

'onRemove',

'onActivate',

'onDeactivate',

'onClick',

'onEvent',
'onMouseUp',

'onMouseDown',

'onDblClick',

'onKeyDown',

'onKeyUp',

'onKeyPress',

'onContextMenu',

'onSubmit',

'onReset',

'onPaste',

'onPreProcess',

'onPostProcess',

'onBeforeSetContent',

'onBeforeGetContent',

'onSetContent',

'onGetContent',

'onLoadContent',

'onSaveContent',

'onNodeChange',

'onChange',

'onBeforeExecCommand',

'onExecCommand',

'onUndo',

'onRedo',

'onVisualAid',

'onSetProgressState',

'onSetAttrib'
], function(name) {
self[name] = new tinymce.util.Dispatcher(self);
});

// Handle legacy cleanup_callback option


if (settings.cleanup_callback) {
self.onBeforeSetContent.add(function(ed, o) {
o.content = ed.execCallback('cleanup_callback',
'insert_to_editor', o.content, o);
});

self.onPreProcess.add(function(ed, o) {
if (o.set)
ed.execCallback('cleanup_callback',
'insert_to_editor_dom', o.node, o);

if (o.get)
ed.execCallback('cleanup_callback',
'get_from_editor_dom', o.node, o);
});

self.onPostProcess.add(function(ed, o) {
if (o.set)
o.content = ed.execCallback('cleanup_callback',
'insert_to_editor', o.content, o);

if (o.get)
o.content = ed.execCallback('cleanup_callback',
'get_from_editor', o.content, o);
});
}

// Handle legacy save_callback option


if (settings.save_callback) {
self.onGetContent.add(function(ed, o) {
if (o.save)
o.content = ed.execCallback('save_callback', ed.id,
o.content, ed.getBody());
});
}

// Handle legacy handle_event_callback option


if (settings.handle_event_callback) {
self.onEvent.add(function(ed, e, o) {
if (self.execCallback('handle_event_callback', e, ed, o)
=== false) {
e.preventDefault();
e.stopPropagation();
}
});
}

// Handle legacy handle_node_change_callback option


if (settings.handle_node_change_callback) {
self.onNodeChange.add(function(ed, cm, n) {
ed.execCallback('handle_node_change_callback', ed.id, n,
-1, -1, true, ed.selection.isCollapsed());
});
}

// Handle legacy save_callback option


if (settings.save_callback) {
self.onSaveContent.add(function(ed, o) {
var h = ed.execCallback('save_callback', ed.id, o.content,
ed.getBody());

if (h)
o.content = h;
});
}

// Handle legacy onchange_callback option


if (settings.onchange_callback) {
self.onChange.add(function(ed, l) {
ed.execCallback('onchange_callback', ed, l);
});
}
};

tinymce.Editor.prototype.bindNativeEvents = function() {
// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
var self = this, i, settings = self.settings, dom = self.dom,
nativeToDispatcherMap;

nativeToDispatcherMap = {
mouseup : 'onMouseUp',
mousedown : 'onMouseDown',
click : 'onClick',
keyup : 'onKeyUp',
keydown : 'onKeyDown',
keypress : 'onKeyPress',
submit : 'onSubmit',
reset : 'onReset',
contextmenu : 'onContextMenu',
dblclick : 'onDblClick',
paste : 'onPaste' // Doesn't work in all browsers yet
};

// Handler that takes a native event and sends it out to a dispatcher


like onKeyDown
function eventHandler(evt, args) {
var type = evt.type;

// Don't fire events when it's removed


if (self.removed)
return;

// Sends the native event out to a global dispatcher then to the


specific event dispatcher
if (self.onEvent.dispatch(self, evt, args) !== false) {
self[nativeToDispatcherMap[evt.fakeType ||
evt.type]].dispatch(self, evt, args);
}
};

// Opera doesn't support focus event for contentEditable elements so we


need to fake it
function doOperaFocus(e) {
self.focus(true);
};

function nodeChanged(ed, e) {
// Normalize selection for example <b>a</b><i>|a</i> becomes
<b>a|</b><i>a</i> except for Ctrl+A since it selects everything
if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
self.selection.normalize();
}

self.nodeChanged();
}

// Add DOM events


each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
var root = settings.content_editable ? self.getBody() :
self.getDoc();

switch (nativeName) {
case 'contextmenu':
dom.bind(root, nativeName, eventHandler);
break;

case 'paste':
dom.bind(self.getBody(), nativeName, eventHandler);
break;

case 'submit':
case 'reset':
dom.bind(self.getElement().form ||
tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
break;

default:
dom.bind(root, nativeName, eventHandler);
}
});

// Set the editor as active when focused


dom.bind(settings.content_editable ? self.getBody() :
(tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
self.focus(true);
});

if (settings.content_editable && tinymce.isOpera) {


dom.bind(self.getBody(), 'click', doOperaFocus);
dom.bind(self.getBody(), 'keydown', doOperaFocus);
}

// Add node change handler


self.onMouseUp.add(nodeChanged);

self.onKeyUp.add(function(ed, e) {
var keyCode = e.keyCode;

if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode
<= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 ||
(tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
nodeChanged(ed, e);
});

// Add reset handler


self.onReset.add(function() {
self.setContent(self.startContent, {format : 'raw'});
});

// Add shortcuts
function handleShortcut(e, execute) {
if (e.altKey || e.ctrlKey || e.metaKey) {
each(self.shortcuts, function(shortcut) {
var ctrlState = tinymce.isMac ? e.metaKey :
e.ctrlKey;

if (shortcut.ctrl != ctrlState || shortcut.alt !=


e.altKey || shortcut.shift != e.shiftKey)
return;

if (e.keyCode == shortcut.keyCode || (e.charCode &&


e.charCode == shortcut.charCode)) {
e.preventDefault();

if (execute) {
shortcut.func.call(shortcut.scope);
}

return true;
}
});
}
};

self.onKeyUp.add(function(ed, e) {
handleShortcut(e);
});

self.onKeyPress.add(function(ed, e) {
handleShortcut(e);
});

self.onKeyDown.add(function(ed, e) {
handleShortcut(e, true);
});

if (tinymce.isOpera) {
self.onClick.add(function(ed, e) {
e.preventDefault();
});
}
};
})(tinymce);
(function(tinymce) {
// Added for compression purposes
var each = tinymce.each, undef, TRUE = true, FALSE = false;

tinymce.EditorCommands = function(editor) {
var dom = editor.dom,
selection = editor.selection,
commands = {state: {}, exec : {}, value : {}},
settings = editor.settings,
formatter = editor.formatter,
bookmark;

function execCommand(command, ui, value) {


var func;

command = command.toLowerCase();
if (func = commands.exec[command]) {
func(command, ui, value);
return TRUE;
}

return FALSE;
};

function queryCommandState(command) {
var func;

command = command.toLowerCase();
if (func = commands.state[command])
return func(command);

return -1;
};

function queryCommandValue(command) {
var func;

command = command.toLowerCase();
if (func = commands.value[command])
return func(command);

return FALSE;
};

function addCommands(command_list, type) {


type = type || 'exec';

each(command_list, function(callback, command) {


each(command.toLowerCase().split(','), function(command) {
commands[type][command] = callback;
});
});
};

// Expose public methods


tinymce.extend(this, {
execCommand : execCommand,
queryCommandState : queryCommandState,
queryCommandValue : queryCommandValue,
addCommands : addCommands
});

// Private methods

function execNativeCommand(command, ui, value) {


if (ui === undef)
ui = FALSE;

if (value === undef)


value = null;

return editor.getDoc().execCommand(command, ui, value);


};

function isFormatMatch(name) {
return formatter.match(name);
};

function toggleFormat(name, value) {


formatter.toggle(name, value ? {value : value} : undef);
};

function storeSelection(type) {
bookmark = selection.getBookmark(type);
};

function restoreSelection() {
selection.moveToBookmark(bookmark);
};

// Add execCommand overrides


addCommands({
// Ignore these, added for compatibility
'mceResetDesignMode,mceBeginUndoLevel' : function() {},

// Add undo manager logic


'mceEndUndoLevel,mceAddUndoLevel' : function() {
editor.undoManager.add();
},

'Cut,Copy,Paste' : function(command) {
var doc = editor.getDoc(), failed;

// Try executing the native command


try {
execNativeCommand(command);
} catch (ex) {
// Command failed
failed = TRUE;
}

// Present alert message about clipboard access not being


available
if (failed || !doc.queryCommandSupported(command)) {
if (tinymce.isGecko) {

editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state)
{
if (state)

open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
});
} else

editor.windowManager.alert(editor.getLang('clipboard_no_support'));
}
},

// Override unlink command


unlink : function(command) {
if (selection.isCollapsed())
selection.select(selection.getNode());

execNativeCommand(command);
selection.collapse(FALSE);
},

// Override justify commands to use the text formatter engine


'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' :
function(command) {
var align = command.substring(7);

// Remove all other alignments first


each('left,center,right,full'.split(','), function(name) {
if (align != name)
formatter.remove('align' + name);
});

toggleFormat('align' + align);
execCommand('mceRepaint');
},

// Override list commands to fix WebKit bug


'InsertUnorderedList,InsertOrderedList' : function(command) {
var listElm, listParent;

execNativeCommand(command);

// WebKit produces lists within block elements so we need


to split them
// we will replace the native list creation logic to custom
logic later on
// TODO: Remove this when the list creation logic is
removed
listElm = dom.getParent(selection.getNode(), 'ol,ul');
if (listElm) {
listParent = listElm.parentNode;

// If list is within a text block then split that


block
if (/^(H[1-6]|P|ADDRESS|PRE)
$/.test(listParent.nodeName)) {
storeSelection();
dom.split(listParent, listElm);
restoreSelection();
}
}
},

// Override commands to use the text formatter engine


'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' :
function(command) {
toggleFormat(command);
},

// Override commands to use the text formatter engine


'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
toggleFormat(command, value);
},
FontSize : function(command, ui, value) {
var fontClasses, fontSizes;

// Convert font size 1-7 to styles


if (value >= 1 && value <= 7) {
fontSizes =
tinymce.explode(settings.font_size_style_values);
fontClasses =
tinymce.explode(settings.font_size_classes);

if (fontClasses)
value = fontClasses[value - 1] || value;
else
value = fontSizes[value - 1] || value;
}

toggleFormat(command, value);
},

RemoveFormat : function(command) {
formatter.remove(command);
},

mceBlockQuote : function(command) {
toggleFormat('blockquote');
},

FormatBlock : function(command, ui, value) {


return toggleFormat(value || 'p');
},

mceCleanup : function() {
var bookmark = selection.getBookmark();

editor.setContent(editor.getContent({cleanup : TRUE}),
{cleanup : TRUE});

selection.moveToBookmark(bookmark);
},

mceRemoveNode : function(command, ui, value) {


var node = value || selection.getNode();

// Make sure that the body node isn't removed


if (node != editor.getBody()) {
storeSelection();
editor.dom.remove(node, TRUE);
restoreSelection();
}
},

mceSelectNodeDepth : function(command, ui, value) {


var counter = 0;

dom.getParent(selection.getNode(), function(node) {
if (node.nodeType == 1 && counter++ == value) {
selection.select(node);
return FALSE;
}
}, editor.getBody());
},

mceSelectNode : function(command, ui, value) {


selection.select(value);
},

mceInsertContent : function(command, ui, value) {


var parser, serializer, parentNode, rootNode, fragment,
args,
marker, nodeRect, viewPortRect, rng, node, node2,
bookmarkHtml, viewportBodyElement;

//selection.normalize();

// Setup parser and serializer


parser = editor.parser;
serializer = new tinymce.html.Serializer({},
editor.schema);
bookmarkHtml = '<span id="mce_marker" data-mce-
type="bookmark">\uFEFF</span>';

// Run beforeSetContent handlers on the HTML to be inserted


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}';

// Replace the caret marker with a span bookmark element


value = value.replace(/\{\$caret\}/, bookmarkHtml);

// Insert node maker where we will insert the new HTML and
get it's parent
if (!selection.isCollapsed())
editor.getDoc().execCommand('Delete', false, null);

parentNode = selection.getNode();

// Parse the fragment within the context of the parent node


args = {context : parentNode.nodeName.toLowerCase()};
fragment = parser.parse(value, args);

// Move the caret to a more suitable location


node = fragment.lastChild;
if (node.attr('id') == 'mce_marker') {
marker = node;

for (node = node.prev; node; node = node.walk(true))


{
if (node.type == 3 || !dom.isBlock(node.name))
{
node.parent.insert(marker, node,
node.name === 'br');
break;
}
}
}

// If parser says valid we can insert the contents into


that parent
if (!args.invalid) {
value = serializer.serialize(fragment);

// Check if parent is empty or only has one BR


element then set the innerHTML of that parent
node = parentNode.firstChild;
node2 = parentNode.lastChild;
if (!node || (node === node2 && node.nodeName ===
'BR'))
dom.setHTML(parentNode, value);
else
selection.setContent(value);
} else {
// If the fragment was invalid within that context
then we need
// to parse and process the parent it's inserted into

// Insert bookmark node and get the parent


selection.setContent(bookmarkHtml);
parentNode = selection.getNode();
rootNode = editor.getBody();

// Opera will return the document node when selection


is in root
if (parentNode.nodeType == 9)
parentNode = node = rootNode;
else
node = parentNode;

// Find the ancestor just before the root element


while (node !== rootNode) {
parentNode = node;
node = node.parentNode;
}

// Get the outer/inner HTML depending on if we are in


the root and parser and serialize that
value = parentNode == rootNode ? rootNode.innerHTML :
dom.getOuterHTML(parentNode);
value = serializer.serialize(
parser.parse(
// Need to replace by using a function
since $ in the contents would otherwise be a problem
value.replace(/<span (id="mce_marker"|
id=mce_marker).+?<\/span>/i, function() {
return
serializer.serialize(fragment);
})
)
);

// Set the inner/outer HTML depending on if we are in


the root or not
if (parentNode == rootNode)
dom.setHTML(rootNode, value);
else
dom.setOuterHTML(parentNode, value);
}

marker = dom.get('mce_marker');

// Scroll range into view scrollIntoView on element can't


be used since it will scroll the main view port as well
nodeRect = dom.getRect(marker);
viewPortRect = dom.getViewPort(editor.getWin());

// Check if node is out side the viewport if it is then


scroll to it
if ((nodeRect.y + nodeRect.h > viewPortRect.y +
viewPortRect.h || nodeRect.y < viewPortRect.y) ||
(nodeRect.x > viewPortRect.x + viewPortRect.w ||
nodeRect.x < viewPortRect.x)) {
viewportBodyElement = tinymce.isIE ?
editor.getDoc().documentElement : editor.getBody();
viewportBodyElement.scrollLeft = nodeRect.x;
viewportBodyElement.scrollTop = nodeRect.y -
viewPortRect.h + 25;
}

// Move selection before marker and remove it


rng = dom.createRng();

// If previous sibling is a text node set the selection to


the end of that node
node = marker.previousSibling;
if (node && node.nodeType == 3) {
rng.setStart(node, node.nodeValue.length);
} else {
// If the previous sibling isn't a text node or
doesn't exist set the selection before the marker node
rng.setStartBefore(marker);
rng.setEndBefore(marker);
}

// Remove the marker node and set the new range


dom.remove(marker);
selection.setRng(rng);

// Dispatch after event and add any visual elements needed


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, function()
{ return value }));
},

mceToggleFormat : function(command, ui, value) {


toggleFormat(value);
},
mceSetContent : function(command, ui, value) {
editor.setContent(value);
},

'Indent,Outdent' : function(command) {
var intentValue, indentUnit, value;

// Setup indent level


intentValue = settings.indentation;
indentUnit = /[a-z%]+$/i.exec(intentValue);
intentValue = parseInt(intentValue);

if (!queryCommandState('InsertUnorderedList') && !
queryCommandState('InsertOrderedList')) {
// If forced_root_blocks is set to false we don't
have a block to indent so lets create a div
if (!settings.forced_root_block && !
dom.getParent(selection.getNode(), dom.isBlock)) {
formatter.apply('div');
}

each(selection.getSelectedBlocks(), function(element)
{
if (command == 'outdent') {
value = Math.max(0,
parseInt(element.style.paddingLeft || 0) - intentValue);
dom.setStyle(element, 'paddingLeft',
value ? value + indentUnit : '');
} else
dom.setStyle(element, 'paddingLeft',
(parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
});
} else
execNativeCommand(command);
},

mceRepaint : function() {
var bookmark;

if (tinymce.isGecko) {
try {
storeSelection(TRUE);

if (selection.getSel())

selection.getSel().selectAllChildren(editor.getBody());

selection.collapse(TRUE);
restoreSelection();
} catch (ex) {
// Ignore
}
}
},

mceToggleFormat : function(command, ui, value) {


formatter.toggle(value);
},
InsertHorizontalRule : function() {
editor.execCommand('mceInsertContent', false, '<hr />');
},

mceToggleVisualAid : function() {
editor.hasVisual = !editor.hasVisual;
editor.addVisual();
},

mceReplaceContent : function(command, ui, value) {


editor.execCommand('mceInsertContent', false,
value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
},

mceInsertLink : function(command, ui, value) {


var anchor;

if (typeof(value) == 'string')
value = {href : value};

anchor = dom.getParent(selection.getNode(), 'a');

// 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');

// Remove existing links if there could be child links or


that the href isn't specified
if (!anchor || !value.href) {
formatter.remove('link');
}

// Apply new link to selection


if (value.href) {
formatter.apply('link', value, anchor);
}
},

selectAll : function() {
var root = dom.getRoot(), rng = dom.createRng();

// Old IE does a better job with selectall than new


versions
if (selection.getRng().setStart) {
rng.setStart(root, 0);
rng.setEnd(root, root.childNodes.length);

selection.setRng(rng);
} else {
execNativeCommand('SelectAll');
}
}
});

// Add queryCommandState overrides


addCommands({
// Override justify commands
'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' :
function(command) {
var name = 'align' + command.substring(7);
var nodes = selection.isCollapsed() ?
[dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
var matches = tinymce.map(nodes, function(node) {
return !!formatter.matchNode(node, name);
});
return tinymce.inArray(matches, TRUE) !== -1;
},

'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' :
function(command) {
return isFormatMatch(command);
},

mceBlockQuote : function() {
return isFormatMatch('blockquote');
},

Outdent : function() {
var node;

if (settings.inline_styles) {
if ((node = dom.getParent(selection.getStart(),
dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
return TRUE;

if ((node = dom.getParent(selection.getEnd(),
dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
return TRUE;
}

return queryCommandState('InsertUnorderedList') ||
queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!
dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
},

'InsertUnorderedList,InsertOrderedList' : function(command) {
var list = dom.getParent(selection.getNode(), 'ul,ol');
return list &&
(command === 'insertunorderedlist' && list.tagName ===
'UL'
|| command === 'insertorderedlist' && list.tagName ===
'OL');
}
}, 'state');

// Add queryCommandValue overrides


addCommands({
'FontSize,FontName' : function(command) {
var value = 0, parent;

if (parent = dom.getParent(selection.getNode(), 'span')) {


if (command == 'fontsize')
value = parent.style.fontSize;
else
value = parent.style.fontFamily.replace(/, /g,
',').replace(/[\'\"]/g, '').toLowerCase();
}
return value;
}
}, 'value');

// Add undo manager logic


addCommands({
Undo : function() {
editor.undoManager.undo();
},

Redo : function() {
editor.undoManager.redo();
}
});
};
})(tinymce);

(function(tinymce) {
var Dispatcher = tinymce.util.Dispatcher;

tinymce.UndoManager = function(editor) {
var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;

function getContent() {
// Remove whitespace before/after and remove pure bogus nodes
return tinymce.trim(editor.getContent({format : 'raw',
no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g,
''));
};

function addNonTypingUndoLevel() {
self.typing = false;
self.add();
};

// Create event instances


onBeforeAdd = new Dispatcher(self);
onAdd = new Dispatcher(self);
onUndo = new Dispatcher(self);
onRedo = new Dispatcher(self);

// Pass though onAdd event from UndoManager to Editor as onChange


onAdd.add(function(undoman, level) {
if (undoman.hasUndo())
return editor.onChange.dispatch(editor, level, undoman);
});

// Pass though onUndo event from UndoManager to Editor


onUndo.add(function(undoman, level) {
return editor.onUndo.dispatch(editor, level, undoman);
});

// Pass though onRedo event from UndoManager to Editor


onRedo.add(function(undoman, level) {
return editor.onRedo.dispatch(editor, level, undoman);
});

// Add initial undo level when the editor is initialized


editor.onInit.add(function() {
self.add();
});

// Get position before an execCommand is processed


editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!
args || !args.skip_undo)) {
self.beforeChange();
}
});

// Add undo level after an execCommand call was made


editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!
args || !args.skip_undo)) {
self.add();
}
});

// Add undo level on save contents, drag end and blur/focusout


editor.onSaveContent.add(addNonTypingUndoLevel);
editor.dom.bind(editor.dom.getRoot(), 'dragend',
addNonTypingUndoLevel);
editor.dom.bind(editor.getBody(), 'focusout', function(e) {
if (!editor.removed && self.typing) {
addNonTypingUndoLevel();
}
});

editor.onKeyUp.add(function(editor, e) {
var keyCode = e.keyCode;

if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode
<= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
addNonTypingUndoLevel();
}
});

editor.onKeyDown.add(function(editor, e) {
var keyCode = e.keyCode;

// Is caracter positon keys


left,right,up,down,home,end,pgdown,pgup,enter
if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode
<= 40) || keyCode == 45) {
if (self.typing) {
addNonTypingUndoLevel();
}

return;
}

// If key isn't shift,ctrl,alt,capslock,metakey


if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode !
= 91 && !self.typing) {
self.beforeChange();
self.typing = true;
self.add();
}
});

editor.onMouseDown.add(function(editor, e) {
if (self.typing) {
addNonTypingUndoLevel();
}
});

// Add keyboard shortcuts for undo/redo keys


editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');

self = {
// Explose for debugging reasons
data : data,

typing : false,

onBeforeAdd: onBeforeAdd,

onAdd : onAdd,

onUndo : onUndo,

onRedo : onRedo,

beforeChange : function() {
beforeBookmark = editor.selection.getBookmark(2, true);
},

add : function(level) {
var i, settings = editor.settings, lastLevel;

level = level || {};


level.content = getContent();

self.onBeforeAdd.dispatch(self, level);

// Add undo level if needed


lastLevel = data[index];
if (lastLevel && lastLevel.content == level.content)
return null;

// Set before bookmark on previous level


if (data[index])
data[index].beforeBookmark = beforeBookmark;

// Time to compress
if (settings.custom_undo_redo_levels) {
if (data.length > settings.custom_undo_redo_levels) {
for (i = 0; i < data.length - 1; i++)
data[i] = data[i + 1];

data.length--;
index = data.length;
}
}

// Get a non intrusive normalized bookmark


level.bookmark = editor.selection.getBookmark(2, true);

// Crop array if needed


if (index < data.length - 1)
data.length = index + 1;

data.push(level);
index = data.length - 1;

self.onAdd.dispatch(self, level);
editor.isNotDirty = 0;

return level;
},

undo : function() {
var level, i;

if (self.typing) {
self.add();
self.typing = false;
}

if (index > 0) {
level = data[--index];

editor.setContent(level.content, {format : 'raw'});

editor.selection.moveToBookmark(level.beforeBookmark);

self.onUndo.dispatch(self, level);
}

return level;
},

redo : function() {
var level;

if (index < data.length - 1) {


level = data[++index];

editor.setContent(level.content, {format : 'raw'});


editor.selection.moveToBookmark(level.bookmark);

self.onRedo.dispatch(self, level);
}

return level;
},

clear : function() {
data = [];
index = 0;
self.typing = false;
},

hasUndo : function() {
return index > 0 || this.typing;
},

hasRedo : function() {
return index < data.length - 1 && !this.typing;
}
};

return self;
};
})(tinymce);

tinymce.ForceBlocks = function(editor) {
var settings = editor.settings, dom = editor.dom, selection =
editor.selection, blockElements = editor.schema.getBlockElements();

function addRootBlocks() {
var node = selection.getStart(), rootNode = editor.getBody(), rng,
startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode,
offset = -0xFFFFFF, wrapped, isInEditorDocument;

if (!node || node.nodeType !== 1 || !settings.forced_root_block)


return;

// Check if node is wrapped in block


while (node && node != rootNode) {
if (blockElements[node.nodeName])
return;

node = node.parentNode;
}

// Get current selection


rng = selection.getRng();
if (rng.setStart) {
startContainer = rng.startContainer;
startOffset = rng.startOffset;
endContainer = rng.endContainer;
endOffset = rng.endOffset;
} else {
// Force control range into text range
if (rng.item) {
node = rng.item(0);
rng = editor.getDoc().body.createTextRange();
rng.moveToElementText(node);
}

isInEditorDocument = rng.parentElement().ownerDocument ===


editor.getDoc();
tmpRng = rng.duplicate();
tmpRng.collapse(true);
startOffset = tmpRng.move('character', offset) * -1;

if (!tmpRng.collapsed) {
tmpRng = rng.duplicate();
tmpRng.collapse(false);
endOffset = (tmpRng.move('character', offset) * -1) -
startOffset;
}
}
// Wrap non block elements and text nodes
node = rootNode.firstChild;
while (node) {
if (node.nodeType === 3 || (node.nodeType == 1 && !
blockElements[node.nodeName])) {
// Remove empty text nodes
if (node.nodeType === 3 && node.nodeValue.length == 0) {
tempNode = node;
node = node.nextSibling;
dom.remove(tempNode);
continue;
}

if (!rootBlockNode) {
rootBlockNode =
dom.create(settings.forced_root_block);
node.parentNode.insertBefore(rootBlockNode, node);
wrapped = true;
}

tempNode = node;
node = node.nextSibling;
rootBlockNode.appendChild(tempNode);
} else {
rootBlockNode = null;
node = node.nextSibling;
}
}

if (wrapped) {
if (rng.setStart) {
rng.setStart(startContainer, startOffset);
rng.setEnd(endContainer, endOffset);
selection.setRng(rng);
} else {
// Only select if the previous selection was inside the
document to prevent auto focus in quirks mode
if (isInEditorDocument) {
try {
rng = editor.getDoc().body.createTextRange();
rng.moveToElementText(rootNode);
rng.collapse(true);
rng.moveStart('character', startOffset);

if (endOffset > 0)
rng.moveEnd('character', endOffset);

rng.select();
} catch (ex) {
// Ignore
}
}
}

editor.nodeChanged();
}
};
// Force root blocks
if (settings.forced_root_block) {
editor.onKeyUp.add(addRootBlocks);
editor.onNodeChange.add(addRootBlocks);
}
};

(function(tinymce) {
// Shorten names
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend
= tinymce.extend;

tinymce.create('tinymce.ControlManager', {
ControlManager : function(ed, s) {
var t = this, i;

s = s || {};
t.editor = ed;
t.controls = {};
t.onAdd = new tinymce.util.Dispatcher(t);
t.onPostRender = new tinymce.util.Dispatcher(t);
t.prefix = s.prefix || ed.id + '_';
t._cls = {};

t.onPostRender.add(function() {
each(t.controls, function(c) {
c.postRender();
});
});
},

get : function(id) {
return this.controls[this.prefix + id] || this.controls[id];
},

setActive : function(id, s) {
var c = null;

if (c = this.get(id))
c.setActive(s);

return c;
},

setDisabled : function(id, s) {
var c = null;

if (c = this.get(id))
c.setDisabled(s);

return c;
},

add : function(c) {
var t = this;

if (c) {
t.controls[c.id] = c;
t.onAdd.dispatch(c, t);
}

return c;
},

createControl : function(name) {
var ctrl, i, l, self = this, editor = self.editor, factories,
ctrlName;

// Build control factory cache


if (!self.controlFactories) {
self.controlFactories = [];
each(editor.plugins, function(plugin) {
if (plugin.createControl) {
self.controlFactories.push(plugin);
}
});
}

// Create controls by asking cached factories


factories = self.controlFactories;
for (i = 0, l = factories.length; i < l; i++) {
ctrl = factories[i].createControl(name, self);

if (ctrl) {
return self.add(ctrl);
}
}

// Create sepearator
if (name === "|" || name === "separator") {
return self.createSeparator();
}

// Create control from button collection


if (editor.buttons && (ctrl = editor.buttons[name])) {
return self.createButton(name, ctrl);
}

return self.add(ctrl);
},

createDropMenu : function(id, s, cc) {


var t = this, ed = t.editor, c, bm, v, cls;

s = extend({
'class' : 'mceDropDown',
constrain : ed.settings.constrain_menus
}, s);

s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';


if (v = ed.getParam('skin_variant'))
s['class'] += ' ' + ed.getParam('skin') + 'Skin' +
v.substring(0, 1).toUpperCase() + v.substring(1);

s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' :


'';

id = t.prefix + id;
cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
c = t.controls[id] = new cls(id, s);
c.onAddItem.add(function(c, o) {
var s = o.settings;

s.title = ed.getLang(s.title, s.title);

if (!s.onclick) {
s.onclick = function(v) {
if (s.cmd)
ed.execCommand(s.cmd, s.ui || false,
s.value);
};
}
});

ed.onRemove.add(function() {
c.destroy();
});

// Fix for bug #1897785, #1898007


if (tinymce.isIE) {
c.onShowMenu.add(function() {
// IE 8 needs focus in order to store away a range
with the current collapsed caret location
ed.focus();

bm = ed.selection.getBookmark(1);
});

c.onHideMenu.add(function() {
if (bm) {
ed.selection.moveToBookmark(bm);
bm = 0;
}
});
}

return t.add(c);
},

createListBox : function(id, s, cc) {


var t = this, ed = t.editor, cmd, c, cls;

if (t.get(id))
return null;

s.title = ed.translate(s.title);
s.scope = s.scope || ed;

if (!s.onselect) {
s.onselect = function(v) {
ed.execCommand(s.cmd, s.ui || false, v || s.value);
};
}

s = extend({
title : s.title,
'class' : 'mce_' + id,
scope : s.scope,
control_manager : t
}, s);

id = t.prefix + id;

function useNativeListForAccessibility(ed) {
return ed.settings.use_accessible_selects && !
tinymce.isGecko
}

if (ed.settings.use_native_selects ||
useNativeListForAccessibility(ed))
c = new tinymce.ui.NativeListBox(id, s);
else {
cls = cc || t._cls.listbox || tinymce.ui.ListBox;
c = new cls(id, s, ed);
}

t.controls[id] = c;

// Fix focus problem in Safari


if (tinymce.isWebKit) {
c.onPostRender.add(function(c, n) {
// Store bookmark on mousedown
Event.add(n, 'mousedown', function() {
ed.bookmark = ed.selection.getBookmark(1);
});

// Restore on focus, since it might be lost


Event.add(n, 'focus', function() {
ed.selection.moveToBookmark(ed.bookmark);
ed.bookmark = null;
});
});
}

if (c.hideMenu)
ed.onMouseDown.add(c.hideMenu, c);

return t.add(c);
},

createButton : function(id, s, cc) {


var t = this, ed = t.editor, o, c, cls;

if (t.get(id))
return null;

s.title = ed.translate(s.title);
s.label = ed.translate(s.label);
s.scope = s.scope || ed;

if (!s.onclick && !s.menu_button) {


s.onclick = function() {
ed.execCommand(s.cmd, s.ui || false, s.value);
};
}
s = extend({
title : s.title,
'class' : 'mce_' + id,
unavailable_prefix : ed.getLang('unavailable', ''),
scope : s.scope,
control_manager : t
}, s);

id = t.prefix + id;

if (s.menu_button) {
cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
c = new cls(id, s, ed);
ed.onMouseDown.add(c.hideMenu, c);
} else {
cls = t._cls.button || tinymce.ui.Button;
c = new cls(id, s, ed);
}

return t.add(c);
},

createMenuButton : function(id, s, cc) {


s = s || {};
s.menu_button = 1;

return this.createButton(id, s, cc);


},

createSplitButton : function(id, s, cc) {


var t = this, ed = t.editor, cmd, c, cls;

if (t.get(id))
return null;

s.title = ed.translate(s.title);
s.scope = s.scope || ed;

if (!s.onclick) {
s.onclick = function(v) {
ed.execCommand(s.cmd, s.ui || false, v || s.value);
};
}

if (!s.onselect) {
s.onselect = function(v) {
ed.execCommand(s.cmd, s.ui || false, v || s.value);
};
}

s = extend({
title : s.title,
'class' : 'mce_' + id,
scope : s.scope,
control_manager : t
}, s);

id = t.prefix + id;
cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
c = t.add(new cls(id, s, ed));
ed.onMouseDown.add(c.hideMenu, c);

return c;
},

createColorSplitButton : function(id, s, cc) {


var t = this, ed = t.editor, cmd, c, cls, bm;

if (t.get(id))
return null;

s.title = ed.translate(s.title);
s.scope = s.scope || ed;

if (!s.onclick) {
s.onclick = function(v) {
if (tinymce.isIE)
bm = ed.selection.getBookmark(1);

ed.execCommand(s.cmd, s.ui || false, v || s.value);


};
}

if (!s.onselect) {
s.onselect = function(v) {
ed.execCommand(s.cmd, s.ui || false, v || s.value);
};
}

s = extend({
title : s.title,
'class' : 'mce_' + id,
'menu_class' : ed.getParam('skin') + 'Skin',
scope : s.scope,
more_colors_title : ed.getLang('more_colors')
}, s);

id = t.prefix + id;
cls = cc || t._cls.colorsplitbutton ||
tinymce.ui.ColorSplitButton;
c = new cls(id, s, ed);
ed.onMouseDown.add(c.hideMenu, c);

// Remove the menu element when the editor is removed


ed.onRemove.add(function() {
c.destroy();
});

// Fix for bug #1897785, #1898007


if (tinymce.isIE) {
c.onShowMenu.add(function() {
// IE 8 needs focus in order to store away a range
with the current collapsed caret location
ed.focus();
bm = ed.selection.getBookmark(1);
});
c.onHideMenu.add(function() {
if (bm) {
ed.selection.moveToBookmark(bm);
bm = 0;
}
});
}

return t.add(c);
},

createToolbar : function(id, s, cc) {


var c, t = this, cls;

id = t.prefix + id;
cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
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;

return new cls();


},

setControlType : function(n, c) {
return this._cls[n.toLowerCase()] = c;
},

destroy : function() {
each(this.controls, function(c) {
c.destroy();
});

this.controls = null;
}
});
})(tinymce);

(function(tinymce) {
var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE =
tinymce.isIE, isOpera = tinymce.isOpera;
tinymce.create('tinymce.WindowManager', {
WindowManager : function(ed) {
var t = this;

t.editor = ed;
t.onOpen = new Dispatcher(t);
t.onClose = new Dispatcher(t);
t.params = {};
t.features = {};
},

open : function(s, p) {
var t = this, f = '', x, y, mo = t.editor.settings.dialog_type ==
'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;

// Default some options


s = s || {};
p = p || {};
sw = isOpera ? vp.w : screen.width; // Opera uses windows inside
the Opera window
sh = isOpera ? vp.h : screen.height;
s.name = s.name || 'mc_' + new Date().getTime();
s.width = parseInt(s.width || 320);
s.height = parseInt(s.height || 240);
s.resizable = true;
s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
p.inline = false;
p.mce_width = s.width;
p.mce_height = s.height;
p.mce_auto_focus = s.auto_focus;

if (mo) {
if (isIE) {
s.center = true;
s.help = false;
s.dialogWidth = s.width + 'px';
s.dialogHeight = s.height + 'px';
s.scroll = s.scrollbars || false;
}
}

// Build features string


each(s, function(v, k) {
if (tinymce.is(v, 'boolean'))
v = v ? 'yes' : 'no';

if (!/^(name|url)$/.test(k)) {
if (isIE && mo)
f += (f ? ';' : '') + k + ':' + v;
else
f += (f ? ',' : '') + k + '=' + v;
}
});

t.features = s;
t.params = p;
t.onOpen.dispatch(t, s, p);
u = s.url || s.file;
u = tinymce._addVer(u);

try {
if (isIE && mo) {
w = 1;
window.showModalDialog(u, window, f);
} else
w = window.open(u, s.name, f);
} catch (ex) {
// Ignore
}

if (!w)
alert(t.editor.getLang('popup_blocked'));
},

close : function(w) {
w.close();
this.onClose.dispatch(this);
},

createInstance : function(cl, a, b, c, d, e) {
var f = tinymce.resolve(cl);

return new f(a, b, c, d, e);


},

confirm : function(t, cb, s, w) {


w = w || window;

cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t,


t))));
},

alert : function(tx, cb, s, w) {


var t = this;

w = w || window;
w.alert(t._decode(t.editor.getLang(tx, tx)));

if (cb)
cb.call(s || t);
},

resizeBy : function(dw, dh, win) {


win.resizeBy(dw, dh);
},

// Internal functions

_decode : function(s) {
return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
}
});
}(tinymce));
(function(tinymce) {
tinymce.Formatter = function(ed) {
var formats = {},
each = tinymce.each,
dom = ed.dom,
selection = ed.selection,
TreeWalker = tinymce.dom.TreeWalker,
rangeUtils = new tinymce.dom.RangeUtils(dom),
isValid = ed.schema.isValidChild,
isArray = tinymce.isArray,
isBlock = dom.isBlock,
forcedRootBlock = ed.settings.forced_root_block,
nodeIndex = dom.nodeIndex,
INVISIBLE_CHAR = '\uFEFF',
MCE_ATTR_RE = /^(src|href|style)$/,
FALSE = false,
TRUE = true,
formatChangeData,
undef,
getContentEditable = dom.getContentEditable;

function isTextBlock(name) {
return !!ed.schema.getTextBlocks()[name.toLowerCase()];
}

function getParents(node, selector) {


return dom.getParents(node, selector, dom.getRoot());
};

function isCaretNode(node) {
return node.nodeType === 1 && node.id === '_mce_caret';
};

function defaultFormats() {
register({
alignleft : [
{selector :
'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'},
defaultBlock: 'div'},
{selector : 'img,table', collapsed : false, styles :
{'float' : 'left'}}
],

aligncenter : [
{selector :
'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'},
defaultBlock: 'div'},
{selector : 'img', collapsed : false, styles :
{display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
{selector : 'table', collapsed : false, styles :
{marginLeft : 'auto', marginRight : 'auto'}}
],

alignright : [
{selector :
'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'},
defaultBlock: 'div'},
{selector : 'img,table', collapsed : false, styles :
{'float' : 'right'}}
],
alignfull : [
{selector :
'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'},
defaultBlock: 'div'}
],

bold : [
{inline : 'strong', remove : 'all'},
{inline : 'span', styles : {fontWeight : 'bold'}},
{inline : 'b', remove : 'all'}
],

italic : [
{inline : 'em', remove : 'all'},
{inline : 'span', styles : {fontStyle : 'italic'}},
{inline : 'i', remove : 'all'}
],

underline : [
{inline : 'span', styles : {textDecoration :
'underline'}, exact : true},
{inline : 'u', remove : 'all'}
],

strikethrough : [
{inline : 'span', styles : {textDecoration : 'line-
through'}, exact : true},
{inline : 'strike', remove : 'all'}
],

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'},

link : {inline : 'a', selector : 'a', remove : 'all', split


: true, deep : true,
onmatch : function(node) {
return true;
},

onformat : function(elm, fmt, vars) {


each(vars, function(value, key) {
dom.setAttrib(elm, key, value);
});
}
},

removeformat : [
{selector : 'b,strong,em,i,font,u,strike', remove :
'all', split : true, expand : false, block_expand : true, deep : true},
{selector : 'span', attributes : ['style', 'class'],
remove : 'empty', split : true, expand : false, deep : true},
{selector : '*', attributes : ['style', 'class'],
split : false, expand : false, deep : true}
]
});

// Register default block formats


each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd
samp'.split(/\s/), function(name) {
register(name, {block : name, remove : 'all'});
});

// Register user defined formats


register(ed.settings.formats);
};

function addKeyboardShortcuts() {
// Add some inline shortcuts
ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');

// BlockFormat shortcuts keys


for (var i = 1; i <= 6; i++) {
ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h'
+ i]);
}

ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);


ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
};

// Public functions

function get(name) {
return name ? formats[name] : formats;
};

function register(name, format) {


if (name) {
if (typeof(name) !== 'string') {
each(name, function(format, name) {
register(name, format);
});
} else {
// Force format into array and add it to internal
collection
format = format.length ? format : [format];

each(format, function(format) {
// Set deep to false by default on selector
formats this to avoid removing
// alignment on images inside paragraphs when
alignment is changed on paragraphs
if (format.deep === undef)
format.deep = !format.selector;

// Default to true
if (format.split === undef)
format.split = !format.selector ||
format.inline;

// Default to true
if (format.remove === undef && format.selector
&& !format.inline)
format.remove = 'none';

// Mark format as a mixed format inline + block


level
if (format.selector && format.inline) {
format.mixed = true;
format.block_expand = true;
}

// Split classes if needed


if (typeof(format.classes) === 'string')
format.classes =
format.classes.split(/\s+/);
});

formats[name] = format;
}
}
};

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, isCollapsed = selection.isCollapsed();
function setElementFormat(elm, fmt) {
fmt = fmt || format;

if (elm) {
if (fmt.onformat) {
fmt.onformat(elm, fmt, vars, node);
}

each(fmt.styles, function(value, name) {


dom.setStyle(elm, name, replaceVars(value,
vars));
});

each(fmt.attributes, function(value, name) {


dom.setAttrib(elm, name, replaceVars(value,
vars));
});

each(fmt.classes, function(value) {
value = replaceVars(value, vars);

if (!dom.hasClass(elm, value))
dom.addClass(elm, value);
});
}
};
function adjustSelectionToVisibleSelection() {
function findSelectionEnd(start, end) {
var walker = new TreeWalker(end);
for (node = walker.current(); node; node =
walker.prev()) {
if (node.childNodes.length > 1 || node == start
|| node.tagName == 'BR') {
return node;
}
}
};

// Adjust selection so that a end container with a end


offset of zero is not included in the selection
// as this isn't visible to the user.
var rng = ed.selection.getRng();
var start = rng.startContainer;
var end = rng.endContainer;

if (start != end && rng.endOffset === 0) {


var newEnd = findSelectionEnd(start, end);
var endOffset = newEnd.nodeType == 3 ?
newEnd.length : newEnd.childNodes.length;

rng.setEnd(newEnd, endOffset);
}

return rng;
}

function applyStyleToList(node, bookmark, wrapElm, newWrappers,


process){
var nodes = [], listIndex = -1, list, startIndex = -1,
endIndex = -1, currentWrapElm;

// find the index of the first child list.


each(node.childNodes, function(n, index) {
if (n.nodeName === "UL" || n.nodeName === "OL") {
listIndex = index;
list = n;
return false;
}
});

// get the index of the bookmarks


each(node.childNodes, function(n, index) {
if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-
mce-type") == "bookmark") {
if (n.id == bookmark.id + "_start") {
startIndex = index;
} else if (n.id == bookmark.id + "_end") {
endIndex = index;
}
}
});

// if the selection spans across an embedded list, or there


isn't an embedded list - handle processing normally
if (listIndex <= 0 || (startIndex < listIndex && endIndex >
listIndex)) {
each(tinymce.grep(node.childNodes), process);
return 0;
} else {
currentWrapElm = dom.clone(wrapElm, FALSE);

// create a list of the nodes on the same side of the


list as the selection
each(tinymce.grep(node.childNodes), function(n,
index) {
if ((startIndex < listIndex && index <
listIndex) || (startIndex > listIndex && index > listIndex)) {
nodes.push(n);
n.parentNode.removeChild(n);
}
});

// insert the wrapping element either before or after


the list.
if (startIndex < listIndex) {
node.insertBefore(currentWrapElm, list);
} else if (startIndex > listIndex) {
node.insertBefore(currentWrapElm,
list.nextSibling);
}

// add the new nodes to the list.


newWrappers.push(currentWrapElm);

each(nodes, function(node) {
currentWrapElm.appendChild(node);
});
return currentWrapElm;
}
};

function applyRngStyle(rng, bookmark, node_specific) {


var newWrappers = [], wrapName, wrapElm, contentEditable =
true;

// Setup wrapper element


wrapName = format.inline || format.block;
wrapElm = dom.create(wrapName);
setElementFormat(wrapElm);

rangeUtils.walk(rng, function(nodes) {
var currentWrapElm;

function process(node) {
var nodeName, parentName, found,
hasContentEditableState, lastContentEditable;

lastContentEditable = contentEditable;
nodeName = node.nodeName.toLowerCase();
parentName =
node.parentNode.nodeName.toLowerCase();

// Node has a contentEditable value


if (node.nodeType === 1 &&
getContentEditable(node)) {
lastContentEditable = contentEditable;
contentEditable =
getContentEditable(node) === "true";
hasContentEditableState = true; // We
don't want to wrap the container only it's children
}

// Stop wrapping on br elements


if (isEq(nodeName, 'br')) {
currentWrapElm = 0;

// Remove any br elements when we wrap


things
if (format.block)
dom.remove(node);

return;
}

// If node is wrapper type


if (format.wrapper && matchNode(node, name,
vars)) {
currentWrapElm = 0;
return;
}

// Can we rename the block


if (contentEditable && !hasContentEditableState
&& format.block && !format.wrapper && isTextBlock(nodeName)) {
node = dom.rename(node, wrapName);
setElementFormat(node);
newWrappers.push(node);
currentWrapElm = 0;
return;
}

// Handle selector patterns


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;
}
});

// Continue processing if a selector


match wasn't found and a inline element is defined
if (!format.inline || found) {
currentWrapElm = 0;
return;
}
}

// Is it valid to wrap this item


if (contentEditable && !hasContentEditableState
&& isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
!(!node_specific && node.nodeType
=== 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)
&& !isCaretNode(node)) {
// Start wrapping
if (!currentWrapElm) {
// Wrap the node
currentWrapElm = dom.clone(wrapElm,
FALSE);

node.parentNode.insertBefore(currentWrapElm, node);
newWrappers.push(currentWrapElm);
}

currentWrapElm.appendChild(node);
} else if (nodeName == 'li' && bookmark) {
// Start wrapping - if we are in a list
node and have a bookmark, then we will always begin by wrapping in a new element.
currentWrapElm = applyStyleToList(node,
bookmark, wrapElm, newWrappers, process);
} else {
// Start a new wrapper for possible
children
currentWrapElm = 0;
each(tinymce.grep(node.childNodes),
process);

if (hasContentEditableState) {
contentEditable =
lastContentEditable; // Restore last contentEditable state from stack
}

// End the last wrapper


currentWrapElm = 0;
}
};

// Process siblings from range


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 = dom.clone(wrapElm,
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;

function getChildCount(node) {
var count = 0;

each(node.childNodes, function(node) {
if (!isWhiteSpaceNode(node) && !
isBookmarkNode(node))
count++;
});

return count;
};

function mergeStyles(node) {
var child, clone;

each(node.childNodes, function(node) {
if (node.nodeType == 1 && !
isBookmarkNode(node) && !isCaretNode(node)) {
child = node;
return FALSE; // break loop
}
});

// If child was found and of the same type as


the current node
if (child && matchName(child, format)) {
clone = dom.clone(child, FALSE);
setElementFormat(clone);

dom.replace(clone, node, TRUE);


dom.remove(child, 1);
}

return clone || node;


};

childCount = getChildCount(node);

// Remove empty nodes but only if there is multiple


wrappers and they are not block
// elements so never remove single <h1></h1> 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;
}

if (format.inline || format.wrapper) {
// Merges the current node with it's children
of similar type to reduce the number of elements
if (!format.exact && childCount === 1)
node = mergeStyles(node);

// Remove/merge children
each(formatList, function(format) {
// Merge all children of similar type
will move styles from child to parent
// this: <span style="color:red"><b><span
style="color:red; font-size:10px">text</span></b></span>
// will become: <span
style="color:red"><b><span style="font-size:10px">text</span></b></span>
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);
}

// don't merge the span tag of a bookmark node, which will


effectively delete the bookmark.
if(!isBookmarkNode(child)){
removeFormat(format, vars, child,
format.exact ? child : null);
}
});
});

// Remove child if direct parent is of same


type
if (matchNode(node.parentNode, name, vars)) {
dom.remove(node, 1);
node = 0;
return TRUE;
}

// Look for parent with similar style format


if (format.merge_with_parents) {
dom.getParent(node.parentNode,
function(parent) {
if (matchNode(parent, name, vars))
{
dom.remove(node, 1);
node = 0;
return TRUE;
}
});
}

// Merge next and previous siblings if they are


similar <b>text</b><b>text</b> becomes <b>texttext</b>
if (node && format.merge_siblings !== false) {
node =
mergeSiblings(getNonWhiteSpaceSibling(node), node);
node = mergeSiblings(node,
getNonWhiteSpaceSibling(node, TRUE));
}
}
});
};

if (format) {
if (node) {
if (node.nodeType) {
rng = dom.createRng();
rng.setStartBefore(node);
rng.setEndAfter(node);
applyRngStyle(expandRng(rng, formatList), null,
true);
} else {
applyRngStyle(node, null, true);
}
} else {
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();

// If the formats have a default block and we


can't find a parent block then start wrapping it with a DIV this is for
forced_root_blocks: false
// It's kind of a hack but people should be
using the default block type P since all desktop editors work that way
if (!forcedRootBlock &&
formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
apply(formatList[0].defaultBlock);
}

// Apply formatting to selection

ed.selection.setRng(adjustSelectionToVisibleSelection());
bookmark = selection.getBookmark();
applyRngStyle(expandRng(selection.getRng(TRUE),
formatList), bookmark);

// 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);
moveStart(selection.getRng(TRUE));
ed.nodeChanged();
} else
performCaretAction('apply', name, vars);
}
}
};

function remove(name, vars, node) {


var formatList = get(name), format = formatList[0], bookmark, i,
rng, contentEditable = true;

// Merges the styles for each node


function process(node) {
var children, i, l, localContentEditable,
lastContentEditable, hasContentEditableState;

// Skip on text nodes as they have neither format to remove


nor children
if (node.nodeType === 3) {
return;
}

// Node has a contentEditable value


if (node.nodeType === 1 && getContentEditable(node)) {
lastContentEditable = contentEditable;
contentEditable = getContentEditable(node) ===
"true";
hasContentEditableState = true; // We don't want to
wrap the container only it's children
}

// Grab the children first since the nodelist might be


changed
children = tinymce.grep(node.childNodes);

// Process current node


if (contentEditable && !hasContentEditableState) {
for (i = 0, l = formatList.length; i < l; i++) {
if (removeFormat(formatList[i], vars, node,
node))
break;
}
}

// Process the children


if (format.deep) {
if (children.length) {
for (i = 0, l = children.length; i < l; i++)
process(children[i]);

if (hasContentEditableState) {
contentEditable = lastContentEditable; //
Restore last contentEditable state from stack
}
}
}
};

function findFormatRoot(container) {
var formatRoot;

// Find format root


each(getParents(container.parentNode).reverse(),
function(parent) {
var format;

// Find format root element


if (!formatRoot && parent.id != '_start' && parent.id
!= '_end') {
// Is the node matching the format we are
looking for
format = matchNode(parent, name, vars);
if (format && format.split !== false)
formatRoot = parent;
}
});

return formatRoot;
};

function wrapAndSplit(format_root, container, target, split) {


var parent, clone, lastClone, firstClone, i,
formatRootParent;

// Format root found then clone formats and split it


if (format_root) {
formatRootParent = format_root.parentNode;

for (parent = container.parentNode; parent &&


parent != formatRootParent; parent = parent.parentNode) {
clone = dom.clone(parent, FALSE);

for (i = 0; i < formatList.length; i++) {


if (removeFormat(formatList[i], vars,
clone, clone)) {
clone = 0;
break;
}
}

// Build wrapper node


if (clone) {
if (lastClone)
clone.appendChild(lastClone);

if (!firstClone)
firstClone = clone;

lastClone = clone;
}
}

// Never split block elements if the format is mixed


if (split && (!format.mixed || !
isBlock(format_root)))
container = dom.split(format_root, container);

// Wrap container in cloned formats


if (lastClone) {
target.parentNode.insertBefore(lastClone,
target);
firstClone.appendChild(target);
}
}

return container;
};

function splitToFormatRoot(container) {
return wrapAndSplit(findFormatRoot(container), container,
container, true);
};
function unwrap(start) {
var node = dom.get(start ? '_start' : '_end'),
out = node[start ? 'firstChild' : 'lastChild'];

// If the end is placed within the start the result will be


removed
// So this checks if the out node is a bookmark node if it
is it
// checks for another more suitable node
if (isBookmarkNode(out))
out = out[start ? 'firstChild' : 'lastChild'];

dom.remove(node, true);

return out;
};

function removeRngStyle(rng) {
var startContainer, endContainer, node;

rng = expandRng(rng, formatList, TRUE);

if (format.split) {
startContainer = getContainer(rng, TRUE);
endContainer = getContainer(rng);

if (startContainer != endContainer) {
// WebKit will render the table incorrectly if
we wrap a TD in a SPAN so lets see if the can use the first child instead
// This will happen if you tripple click a
table cell and use remove formatting
if (/^(TR|TD)$/.test(startContainer.nodeName)
&& startContainer.firstChild) {
startContainer = (startContainer.nodeName
== "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) ||
startContainer;
}

// Wrap start/end nodes in span element since


these might be cloned/moved
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);
splitToFormatRoot(endContainer);

// Unwrap start/end to get real elements again


startContainer = unwrap(TRUE);
endContainer = unwrap();
} else
startContainer = endContainer =
splitToFormatRoot(startContainer);

// Update range positions since they might have


changed after the split operations
rng.startContainer = startContainer.parentNode;
rng.startOffset = nodeIndex(startContainer);
rng.endContainer = endContainer.parentNode;
rng.endOffset = nodeIndex(endContainer) + 1;
}

// Remove items between start/end


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);
}
});
});
};

// Handle node
if (node) {
if (node.nodeType) {
rng = dom.createRng();
rng.setStartBefore(node);
rng.setEndAfter(node);
removeRngStyle(rng);
} else {
removeRngStyle(node);
}

return;
}

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: "<b>text|</b>text" and need to move the start into the next text node
if (format.inline && match(name, vars,
selection.getStart())) {
moveStart(selection.getRng(true));
}

ed.nodeChanged();
} else
performCaretAction('remove', name, vars);
};

function toggle(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);
};

function matchNode(node, name, vars, similar) {


var formatList = get(name), format, i, classes;

function matchItems(node, format, item_name) {


var key, value, items = format[item_name], i;

// Custom match
if (format.onmatch) {
return format.onmatch(node, format, item_name);
}

// Check all items


if (items) {
// Non indexed object
if (items.length === undef) {
for (key in items) {
if (items.hasOwnProperty(key)) {
if (item_name === 'attributes')
value = dom.getAttrib(node,
key);
else
value = getStyle(node, key);

if (similar && !value && !


format.exact)
return;

if ((!similar || format.exact) && !


isEq(value, replaceVars(items[key], vars)))
return;
}
}
} else {
// Only one match needed for indexed arrays
for (i = 0; i < items.length; i++) {
if (item_name === 'attributes' ?
dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
return format;
}
}
}

return format;
};

if (formatList && node) {


// Check each format in list
for (i = 0; i < formatList.length; i++) {
format = formatList[i];

// Name name, attributes, styles and classes


if (matchName(node, format) && matchItems(node,
format, 'attributes') && matchItems(node, format, 'styles')) {
// Match classes
if (classes = format.classes) {
for (i = 0; i < classes.length; i++) {
if (!dom.hasClass(node,
classes[i]))
return;
}
}

return format;
}
}
}
};

function match(name, vars, node) {


var startNode;

function matchParents(node) {
// Find first node with similar format settings
node = dom.getParent(node, function(node) {
return !!matchNode(node, name, vars, true);
});

// Do an exact check on the similar format element


return matchNode(node, name, vars);
};

// Check specified node


if (node)
return matchParents(node);

// Check selected node


node = selection.getNode();
if (matchParents(node))
return TRUE;

// Check start node if it's different


startNode = selection.getStart();
if (startNode != node) {
if (matchParents(startNode))
return TRUE;
}

return FALSE;
};

function matchAll(names, vars) {


var startElement, matchedFormatNames = [], checkedMap = {}, i,
ni, name;

// Check start of selection for formats


startElement = selection.getStart();
dom.getParent(startElement, function(node) {
var i, name;

for (i = 0; i < names.length; i++) {


name = names[i];
if (!checkedMap[name] && matchNode(node, name, vars))
{
checkedMap[name] = true;
matchedFormatNames.push(name);
}
}
}, dom.getRoot());

return matchedFormatNames;
};

function canApply(name) {
var formatList = get(name), startNode, parents, i, x, selector;

if (formatList) {
startNode = selection.getStart();
parents = getParents(startNode);

for (x = formatList.length - 1; x >= 0; x--) {


selector = formatList[x].selector;

// Format is not selector based, then always return


TRUE
if (!selector)
return TRUE;

for (i = parents.length - 1; i >= 0; i--) {


if (dom.is(parents[i], selector))
return TRUE;
}
}
}

return FALSE;
};

function formatChanged(formats, callback, similar) {


var currentFormats;

// Setup format node change logic


if (!formatChangeData) {
formatChangeData = {};
currentFormats = {};

ed.onNodeChange.addToTop(function(ed, cm, node) {


var parents = getParents(node), matchedFormats = {};

// Check for new formats


each(formatChangeData, function(callbacks, format) {
each(parents, function(node) {
if (matchNode(node, format, {},
callbacks.similar)) {
if (!currentFormats[format]) {
// Execute callbacks
each(callbacks,
function(callback) {
callback(true, {node:
node, format: format, parents: parents});
});
currentFormats[format] =
callbacks;
}

matchedFormats[format] = callbacks;
return false;
}
});
});

// Check if current formats still match


each(currentFormats, function(callbacks, format) {
if (!matchedFormats[format]) {
delete currentFormats[format];

each(callbacks, function(callback) {
callback(false, {node: node,
format: format, parents: parents});
});
}
});
});
}

// Add format listeners


each(formats.split(','), function(format) {
if (!formatChangeData[format]) {
formatChangeData[format] = [];
formatChangeData[format].similar = similar;
}

formatChangeData[format].push(callback);
});

return this;
};

// Expose to public
tinymce.extend(this, {
get : get,
register : register,
apply : apply,
remove : remove,
toggle : toggle,
match : match,
matchAll : matchAll,
matchNode : matchNode,
canApply : canApply,
formatChanged: formatChanged
});

// Initialize
defaultFormats();
addKeyboardShortcuts();

// Private functions

function matchName(node, format) {


// Check for inline match
if (isEq(node, format.inline))
return TRUE;

// Check for block match


if (isEq(node, format.block))
return TRUE;

// Check for selector match


if (format.selector)
return dom.is(node, format.selector);
};

function isEq(str1, str2) {


str1 = str1 || '';
str2 = str2 || '';

str1 = '' + (str1.nodeName || str1);


str2 = '' + (str2.nodeName || str2);

return str1.toLowerCase() == str2.toLowerCase();


};

function getStyle(node, name) {


var styleVal = dom.getStyle(node, name);

// Force the format to hex


if (name == 'color' || name == 'backgroundColor')
styleVal = dom.toHex(styleVal);

// Opera will return bold as 700


if (name == 'fontWeight' && styleVal == 700)
styleVal = 'bold';

return '' + styleVal;


};

function replaceVars(value, vars) {


if (typeof(value) != "string")
value = value(vars);
else if (vars) {
value = value.replace(/%(\w+)/g, function(str, name) {
return vars[name] || str;
});
}

return value;
};

function isWhiteSpaceNode(node) {
return node && node.nodeType === 3 && /^([\t \r\n]+|)
$/.test(node.nodeValue);
};

function wrap(node, name, attrs) {


var wrapper = dom.create(name, attrs);

node.parentNode.insertBefore(wrapper, node);
wrapper.appendChild(node);
return wrapper;
};

function expandRng(rng, format, remove) {


var sibling, lastIdx, leaf, endPoint,
startContainer = rng.startContainer,
startOffset = rng.startOffset,
endContainer = rng.endContainer,
endOffset = rng.endOffset;

// This function walks up the tree if there is no siblings


before/after the node
function findParentContainer(start) {
var container, parent, child, sibling, siblingName, root;

container = parent = start ? startContainer : endContainer;


siblingName = start ? 'previousSibling' : 'nextSibling';
root = dom.getRoot();

function isBogusBr(node) {
return node.nodeName == "BR" &&
node.getAttribute('data-mce-bogus') && !node.nextSibling;
};

// If it's a text node and the offset is inside the text


if (container.nodeType == 3 && !
isWhiteSpaceNode(container)) {
if (start ? startOffset > 0 : endOffset <
container.nodeValue.length) {
return container;
}
}

for (;;) {
// Stop expanding on block elements
if (!format[0].block_expand && isBlock(parent))
return parent;

// Walk left/right
for (sibling = parent[siblingName]; sibling; sibling
= sibling[siblingName]) {
if (!isBookmarkNode(sibling) && !
isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
return parent;
}
}

// Check if we can move up are we at root level or


body level
if (parent.parentNode == root) {
container = parent;
break;
}

parent = parent.parentNode;
}

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 === undef)
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;
startContainer = startContainer.childNodes[startOffset >
lastIdx ? lastIdx : startOffset];

if (startContainer.nodeType == 3)
startOffset = 0;
}

// If index based end position then resolve it


if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
lastIdx = endContainer.childNodes.length - 1;
endContainer = endContainer.childNodes[endOffset >
lastIdx ? lastIdx : endOffset - 1];

if (endContainer.nodeType == 3)
endOffset = endContainer.nodeValue.length;
}

// Expands the node to the closes contentEditable false element


if it exists
function findParentContentEditable(node) {
var parent = node;

while (parent) {
if (parent.nodeType === 1 &&
getContentEditable(parent)) {
return getContentEditable(parent) === "false" ?
parent : node;
}

parent = parent.parentNode;
}

return node;
};

function findWordEndPoint(container, offset, start) {


var walker, node, pos, lastTextNode;

function findSpace(node, offset) {


var pos, pos2, str = node.nodeValue;

if (typeof(offset) == "undefined") {
offset = start ? str.length : 0;
}

if (start) {
pos = str.lastIndexOf(' ', offset);
pos2 = str.lastIndexOf('\u00a0', offset);
pos = pos > pos2 ? pos : pos2;

// Include the space on remove to avoid tag


soup
if (pos !== -1 && !remove) {
pos++;
}
} else {
pos = str.indexOf(' ', offset);
pos2 = str.indexOf('\u00a0', offset);
pos = pos !== -1 && (pos2 === -1 || pos < pos2)
? pos : pos2;
}

return pos;
};

if (container.nodeType === 3) {
pos = findSpace(container, offset);

if (pos !== -1) {


return {container : container, offset : pos};
}

lastTextNode = container;
}

// Walk the nodes inside the block


walker = new TreeWalker(container, dom.getParent(container,
isBlock) || ed.getBody());
while (node = walker[start ? 'prev' : 'next']()) {
if (node.nodeType === 3) {
lastTextNode = node;
pos = findSpace(node);

if (pos !== -1) {


return {container : node, offset : pos};
}
} else if (isBlock(node)) {
break;
}
}

if (lastTextNode) {
if (start) {
offset = 0;
} else {
offset = lastTextNode.length;
}

return {container: lastTextNode, offset: offset};


}
};

function findSelectorEndPoint(container, sibling_name) {


var parents, i, y, curFormat;

if (container.nodeType == 3 && container.nodeValue.length


=== 0 && container[sibling_name])
container = container[sibling_name];

parents = getParents(container);
for (i = 0; i < parents.length; i++) {
for (y = 0; y < format.length; y++) {
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];
}
}

return container;
};

function findBlockEndPoint(container, sibling_name,


sibling_name2) {
var node;

// Expand to block of similar type


if (!format[0].wrapper)
node = dom.getParent(container, format[0].block);

// Expand to first wrappable block element or any block


element
if (!node)
node = dom.getParent(container.nodeType == 3 ?
container.parentNode : container, isTextBlock);

// Exclude inner lists from wrapping


if (node && format[0].wrapper)
node = getParents(node, 'ul,ol').reverse()[0] ||
node;

// Didn't find a block element look for first/last


wrappable element
if (!node) {
node = container;

while (node[sibling_name] && !


isBlock(node[sibling_name])) {
node = node[sibling_name];

// Break on BR but include it will be removed


later on
// we can't remove it now since we need to
check if it can be wrapped
if (isEq(node, 'br'))
break;
}
}

return node || container;


};

// Expand to closest contentEditable element


startContainer = findParentContentEditable(startContainer);
endContainer = findParentContentEditable(endContainer);

// Exclude bookmark nodes if possible


if (isBookmarkNode(startContainer.parentNode) ||
isBookmarkNode(startContainer)) {
startContainer = isBookmarkNode(startContainer) ?
startContainer : startContainer.parentNode;
startContainer = startContainer.nextSibling ||
startContainer;

if (startContainer.nodeType == 3)
startOffset = 0;
}

if (isBookmarkNode(endContainer.parentNode) ||
isBookmarkNode(endContainer)) {
endContainer = isBookmarkNode(endContainer) ?
endContainer : endContainer.parentNode;
endContainer = endContainer.previousSibling ||
endContainer;

if (endContainer.nodeType == 3)
endOffset = endContainer.length;
}

if (format[0].inline) {
if (rng.collapsed) {
// Expand left to closest word boundery
endPoint = findWordEndPoint(startContainer,
startOffset, true);
if (endPoint) {
startContainer = endPoint.container;
startOffset = endPoint.offset;
}

// Expand right to closest word boundery


endPoint = findWordEndPoint(endContainer, endOffset);
if (endPoint) {
endContainer = endPoint.container;
endOffset = endPoint.offset;
}
}
// 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);
}
}
}
}

// Move start/end point up the tree if the leaves are sharp and
if we are in different containers
// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
// This will reduce the number of wrapper elements that needs to
be created
// Move start point up the tree
if (format[0].inline || format[0].block_expand) {
if (!format[0].inline || (startContainer.nodeType != 3 ||
startOffset === 0)) {
startContainer = findParentContainer(true);
}

if (!format[0].inline || (endContainer.nodeType != 3 ||
endOffset === endContainer.nodeValue.length)) {
endContainer = findParentContainer();
}
}

// Expand start/end container to matching selector


if (format[0].selector && format[0].expand !== FALSE && !
format[0].inline) {
// Find new startContainer/endContainer if there is better
one
startContainer = findSelectorEndPoint(startContainer,
'previousSibling');
endContainer = findSelectorEndPoint(endContainer,
'nextSibling');
}

// Expand start/end container to matching block element or text


node
if (format[0].block || format[0].selector) {
// Find new startContainer/endContainer if there is better
one
startContainer = findBlockEndPoint(startContainer,
'previousSibling');
endContainer = findBlockEndPoint(endContainer,
'nextSibling');
// Non block element then try to expand up the leaf
if (format[0].block) {
if (!isBlock(startContainer))
startContainer = findParentContainer(true);

if (!isBlock(endContainer))
endContainer = findParentContainer();
}
}

// Setup index for startContainer


if (startContainer.nodeType == 1) {
startOffset = nodeIndex(startContainer);
startContainer = startContainer.parentNode;
}

// Setup index for endContainer


if (endContainer.nodeType == 1) {
endOffset = nodeIndex(endContainer) + 1;
endContainer = endContainer.parentNode;
}

// Return new range like object


return {
startContainer : startContainer,
startOffset : startOffset,
endContainer : endContainer,
endOffset : endOffset
};
}

function removeFormat(format, vars, node, compare_node) {


var i, attrs, stylesModified;

// Check if node matches format


if (!matchName(node, format))
return FALSE;

// Should we compare with format attribs and styles


if (format.remove != 'all') {
// Remove styles
each(format.styles, function(value, name) {
value = replaceVars(value, vars);

// Indexed array
if (typeof(name) === 'number') {
name = value;
compare_node = 0;
}

if (!compare_node || isEq(getStyle(compare_node,
name), value))
dom.setStyle(node, name, '');

stylesModified = 1;
});

// Remove style attribute if it's empty


if (stylesModified && dom.getAttrib(node, 'style') == '') {
node.removeAttribute('style');
node.removeAttribute('data-mce-style');
}

// Remove attributes
each(format.attributes, function(value, name) {
var valueOut;

value = replaceVars(value, vars);

// Indexed array
if (typeof(name) === 'number') {
name = value;
compare_node = 0;
}

if (!compare_node || isEq(dom.getAttrib(compare_node,
name), value)) {
// Keep internal classes
if (name == 'class') {
value = dom.getAttrib(node, name);
if (value) {
// Build new class value where
everything is removed except the internal prefixed classes
valueOut = '';
each(value.split(/\s+/),
function(cls) {
if (/mce\w+/.test(cls))
valueOut += (valueOut ?
' ' : '') + cls;
});

// We got some internal classes


left
if (valueOut) {
dom.setAttrib(node, name,
valueOut);
return;
}
}
}

// IE6 has a bug where the attribute doesn't


get removed correctly
if (name == "class")
node.removeAttribute('className');

// Remove mce prefixed attributes


if (MCE_ATTR_RE.test(name))
node.removeAttribute('data-mce-' + name);

node.removeAttribute(name);
}
});

// Remove classes
each(format.classes, function(value) {
value = replaceVars(value, vars);
if (!compare_node || dom.hasClass(compare_node,
value))
dom.removeClass(node, value);
});

// Check for non internal attributes


attrs = dom.getAttribs(node);
for (i = 0; i < attrs.length; i++) {
if (attrs[i].nodeName.indexOf('_') !== 0)
return FALSE;
}
}

// Remove the inline child if it's empty for example <b> or


<span>
if (format.remove != 'none') {
removeNode(node, format);
return TRUE;
}
};

function removeNode(node, format) {


var parentNode = node.parentNode, rootBlockElm;

function find(node, next, inc) {


node = getNonWhiteSpaceSibling(node, next, inc);

return !node || (node.nodeName == 'BR' || isBlock(node));


};

if (format.block) {
if (!forcedRootBlock) {
// Append BR elements if needed before we remove the
block
if (isBlock(node) && !isBlock(parentNode)) {
if (!find(node, FALSE) && !
find(node.firstChild, TRUE, 1))
node.insertBefore(dom.create('br'),
node.firstChild);

if (!find(node, TRUE) && !find(node.lastChild,


FALSE, 1))
node.appendChild(dom.create('br'));
}
} else {
// Wrap the block in a forcedRootBlock if we are at
the root of document
if (parentNode == dom.getRoot()) {
if (!format.list_block || !isEq(node,
format.list_block)) {
each(tinymce.grep(node.childNodes),
function(node) {
if (isValid(forcedRootBlock,
node.nodeName.toLowerCase())) {
if (!rootBlockElm)
rootBlockElm =
wrap(node, forcedRootBlock);
else
rootBlockElm.appendChild(node);
} else
rootBlockElm = 0;
});
}
}
}
}

// Never remove nodes that isn't the specified inline element if


a selector is specified too
if (format.selector && format.inline && !isEq(format.inline,
node))
return;

dom.remove(node, 1);
};

function getNonWhiteSpaceSibling(node, next, inc) {


if (node) {
next = next ? 'nextSibling' : 'previousSibling';

for (node = inc ? node : node[next]; node; node =


node[next]) {
if (node.nodeType == 1 || !isWhiteSpaceNode(node))
return node;
}
}
};

function isBookmarkNode(node) {
return node && node.nodeType == 1 && node.getAttribute('data-mce-
type') == 'bookmark';
};

function mergeSiblings(prev, next) {


var marker, sibling, tmpSibling;

function compareElements(node1, node2) {


// Not the same name
if (node1.nodeName != node2.nodeName)
return FALSE;

function getAttribs(node) {
var attribs = {};

each(dom.getAttribs(node), function(attr) {
var name = attr.nodeName.toLowerCase();

// Don't compare internal attributes or style


if (name.indexOf('_') !== 0 && name !==
'style')
attribs[name] = dom.getAttrib(node,
name);
});

return attribs;
};
function compareObjects(obj1, obj2) {
var value, name;

for (name in obj1) {


// Obj1 has item obj2 doesn't have
if (obj1.hasOwnProperty(name)) {
value = obj2[name];

// Obj2 doesn't have obj1 item


if (value === undef)
return FALSE;

// Obj2 item has a different value


if (obj1[name] != value)
return FALSE;

// Delete similar value


delete obj2[name];
}
}

// Check if obj 2 has something obj 1 doesn't have


for (name in obj2) {
// Obj2 has item obj1 doesn't have
if (obj2.hasOwnProperty(name))
return FALSE;
}

return TRUE;
};

// Attribs are not the same


if (!compareObjects(getAttribs(node1), getAttribs(node2)))
return FALSE;

// Styles are not the same


if (!compareObjects(dom.parseStyle(dom.getAttrib(node1,
'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
return FALSE;

return TRUE;
};

function findElementSibling(node, sibling_name) {


for (sibling = node; sibling; sibling =
sibling[sibling_name]) {
if (sibling.nodeType == 3 && sibling.nodeValue.length
!== 0)
return node;

if (sibling.nodeType == 1 && !
isBookmarkNode(sibling))
return sibling;
}

return node;
};

// Check if next/prev exists and that they are elements


if (prev && next) {
// If previous sibling is empty then jump over it
prev = findElementSibling(prev, 'previousSibling');
next = findElementSibling(next, 'nextSibling');

// Compare next and previous nodes


if (compareElements(prev, next)) {
// Append nodes between
for (sibling = prev.nextSibling; sibling && sibling !
= next;) {
tmpSibling = sibling;
sibling = sibling.nextSibling;
prev.appendChild(tmpSibling);
}

// Remove next node


dom.remove(next);

// Move children into prev node


each(tinymce.grep(next.childNodes), function(node) {
prev.appendChild(node);
});

return prev;
}
}

return next;
};

function isTextBlock(name) {
return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
};

function getContainer(rng, start) {


var container, offset, lastIdx, walker;

container = rng[start ? 'startContainer' : 'endContainer'];


offset = rng[start ? 'startOffset' : 'endOffset'];

if (container.nodeType == 1) {
lastIdx = container.childNodes.length - 1;

if (!start && offset)


offset--;

container = container.childNodes[offset > lastIdx ? lastIdx


: offset];
}

// If start text node is excluded then walk to the next node


if (container.nodeType === 3 && start && offset >=
container.nodeValue.length) {
container = new TreeWalker(container, ed.getBody()).next()
|| container;
}

// If end text node is excluded then walk to the previous node


if (container.nodeType === 3 && !start && offset === 0) {
container = new TreeWalker(container, ed.getBody()).prev()
|| container;
}

return container;
};

function performCaretAction(type, name, vars) {


var caretContainerId = '_mce_caret', debug =
ed.settings.caret_debug;

// Creates a caret container bogus element


function createCaretContainer(fill) {
var caretContainer = dom.create('span', {id:
caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});

if (fill) {

caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
}

return caretContainer;
};

function isCaretContainerEmpty(node, nodes) {


while (node) {
if ((node.nodeType === 3 && node.nodeValue !==
INVISIBLE_CHAR) || node.childNodes.length > 1) {
return false;
}

// Collect nodes
if (nodes && node.nodeType === 1) {
nodes.push(node);
}

node = node.firstChild;
}

return true;
};

// Returns any parent caret container element


function getParentCaretContainer(node) {
while (node) {
if (node.id === caretContainerId) {
return node;
}

node = node.parentNode;
}
};

// Finds the first text node in the specified node


function findFirstTextNode(node) {
var walker;

if (node) {
walker = new TreeWalker(node, node);
for (node = walker.current(); node; node =
walker.next()) {
if (node.nodeType === 3) {
return node;
}
}
}
};

// Removes the caret container for the specified node or all on


the current document
function removeCaretContainer(node, move_caret) {
var child, rng;

if (!node) {
node = getParentCaretContainer(selection.getStart());

if (!node) {
while (node = dom.get(caretContainerId)) {
removeCaretContainer(node, false);
}
}
} else {
rng = selection.getRng(true);

if (isCaretContainerEmpty(node)) {
if (move_caret !== false) {
rng.setStartBefore(node);
rng.setEndBefore(node);
}

dom.remove(node);
} else {
child = findFirstTextNode(node);

if (child.nodeValue.charAt(0) ===
INVISIBLE_CHAR) {
child = child.deleteData(0, 1);
}

dom.remove(node, 1);
}

selection.setRng(rng);
}
};

// Applies formatting to the caret postion


function applyCaretFormat() {
var rng, caretContainer, textNode, offset, bookmark,
container, text;

rng = selection.getRng(true);
offset = rng.startOffset;
container = rng.startContainer;
text = container.nodeValue;

caretContainer =
getParentCaretContainer(selection.getStart());
if (caretContainer) {
textNode = findFirstTextNode(caretContainer);
}

// Expand to word is caret is in the middle of a text node


and the char before/after is a alpha numeric character
if (text && offset > 0 && offset < text.length &&
/\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
// Get bookmark of caret position
bookmark = selection.getBookmark();

// Collapse bookmark range (WebKit)


rng.collapse(true);

// Expand the range to the closest word and split it


at those points
rng = expandRng(rng, get(name));
rng = rangeUtils.split(rng);

// Apply the format to the range


apply(name, vars, rng);

// Move selection back to caret position


selection.moveToBookmark(bookmark);
} else {
if (!caretContainer || textNode.nodeValue !==
INVISIBLE_CHAR) {
caretContainer = createCaretContainer(true);
textNode = caretContainer.firstChild;

rng.insertNode(caretContainer);
offset = 1;

apply(name, vars, caretContainer);


} else {
apply(name, vars, caretContainer);
}

// Move selection to text node


selection.setCursorLocation(textNode, offset);
}
};

function removeCaretFormat() {
var rng = selection.getRng(true), container, offset,
bookmark,
hasContentAfter, node, formatNode, parents = [], i,
caretContainer;

container = rng.startContainer;
offset = rng.startOffset;
node = container;

if (container.nodeType == 3) {
if (offset != container.nodeValue.length ||
container.nodeValue === INVISIBLE_CHAR) {
hasContentAfter = true;
}
node = node.parentNode;
}

while (node) {
if (matchNode(node, name, vars)) {
formatNode = node;
break;
}

if (node.nextSibling) {
hasContentAfter = true;
}

parents.push(node);
node = node.parentNode;
}

// Node doesn't have the specified format


if (!formatNode) {
return;
}

// Is there contents after the caret then remove the format


on the element
if (hasContentAfter) {
// Get bookmark of caret position
bookmark = selection.getBookmark();

// Collapse bookmark range (WebKit)


rng.collapse(true);

// Expand the range to the closest word and split it


at those points
rng = expandRng(rng, get(name), true);
rng = rangeUtils.split(rng);

// Remove the format from the range


remove(name, vars, rng);

// Move selection back to caret position


selection.moveToBookmark(bookmark);
} else {
caretContainer = createCaretContainer();

node = caretContainer;
for (i = parents.length - 1; i >= 0; i--) {
node.appendChild(dom.clone(parents[i], false));
node = node.firstChild;
}

// Insert invisible character into inner most format


element

node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
node = node.firstChild;

// Insert caret container after the formated node


dom.insertAfter(caretContainer, formatNode);
// Move selection to text node
selection.setCursorLocation(node, 1);
}
};

// Checks if the parent caret container node isn't empty if that


is the case it
// will remove the bogus state on all children that isn't empty
function unmarkBogusCaretParents() {
var i, caretContainer, node;

caretContainer =
getParentCaretContainer(selection.getStart());
if (caretContainer && !dom.isEmpty(caretContainer)) {
tinymce.walk(caretContainer, function(node) {
if (node.nodeType == 1 && node.id !==
caretContainerId && !dom.isEmpty(node)) {
dom.setAttrib(node, 'data-mce-bogus',
null);
}
}, 'childNodes');
}
};

// Only bind the caret events once


if (!self._hasCaretEvents) {
// Mark current caret container elements as bogus when
getting the contents so we don't end up with empty elements
ed.onBeforeGetContent.addToTop(function() {
var nodes = [], i;

if
(isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
// Mark children
i = nodes.length;
while (i--) {
dom.setAttrib(nodes[i], 'data-mce-bogus',
'1');
}
}
});

// Remove caret container on mouse up and on key up


tinymce.each('onMouseUp onKeyUp'.split(' '), function(name)
{
ed[name].addToTop(function() {
removeCaretContainer();
unmarkBogusCaretParents();
});
});

// Remove caret container on keydown and it's a backspace,


enter or left/right arrow keys
ed.onKeyDown.addToTop(function(ed, e) {
var keyCode = e.keyCode;

if (keyCode == 8 || keyCode == 37 || keyCode == 39) {


removeCaretContainer(getParentCaretContainer(selection.getStart()));
}

unmarkBogusCaretParents();
});

// Remove bogus state if they got filled by contents using


editor.selection.setContent
selection.onSetContent.add(unmarkBogusCaretParents);

self._hasCaretEvents = true;
}

// Do apply or remove caret format


if (type == "apply") {
applyCaretFormat();
} else {
removeCaretFormat();
}
};

function moveStart(rng) {
var container = rng.startContainer,
offset = rng.startOffset, isAtEndOfText,
walker, node, nodes, tmpNode;

// Convert text node into index if possible


if (container.nodeType == 3 && offset >=
container.nodeValue.length) {
// Get the parent container location and walk from there
offset = nodeIndex(container);
container = container.parentNode;
isAtEndOfText = true;
}

// 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, dom.getParent(container,
dom.isBlock));

// If offset is at end of the parent node walk to the next


one
if (offset > nodes.length - 1 || isAtEndOfText)
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;
}
}
}
};
};
})(tinymce);

tinymce.onAddEditor.add(function(tinymce, ed) {
var filters, fontSizes, dom, settings = ed.settings;

function replaceWithSpan(node, styles) {


tinymce.each(styles, function(value, name) {
if (value)
dom.setStyle(node, name, value);
});

dom.rename(node, 'span');
};

function convert(editor, params) {


dom = editor.dom;

if (settings.convert_fonts_to_spans) {
tinymce.each(dom.select('font,u,strike', params.node),
function(node) {
filters[node.nodeName.toLowerCase()](ed.dom, node);
});
}
};

if (settings.inline_styles) {
fontSizes = tinymce.explode(settings.font_size_legacy_values);

filters = {
font : function(dom, node) {
replaceWithSpan(node, {
backgroundColor : node.style.backgroundColor,
color : node.color,
fontFamily : node.face,
fontSize : fontSizes[parseInt(node.size, 10) - 1]
});
},

u : function(dom, node) {
replaceWithSpan(node, {
textDecoration : 'underline'
});
},

strike : function(dom, node) {


replaceWithSpan(node, {
textDecoration : 'line-through'
});
}
};
ed.onPreProcess.add(convert);
ed.onSetContent.add(convert);

ed.onInit.add(function() {
ed.selection.onSetContent.add(convert);
});
}
});

(function(tinymce) {
var TreeWalker = tinymce.dom.TreeWalker;

tinymce.EnterKey = function(editor) {
var dom = editor.dom, selection = editor.selection, settings =
editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap =
editor.schema.getNonEmptyElements();

function handleEnterKey(evt) {
var rng = selection.getRng(true), tmpRng, editableRoot,
container, offset, parentBlock, documentMode, shiftKey,
newBlock, fragment, containerBlock, parentBlockName,
containerBlockName, newBlockName, isAfterLastNodeInContainer;

// Returns true if the block can be split into two blocks or not
function canSplitBlock(node) {
return node &&
dom.isBlock(node) &&
!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
!/^(fixed|absolute)/i.test(node.style.position) &&
dom.getContentEditable(node) !== "true";
};

// Renders empty block on IE


function renderBlockOnIE(block) {
var oldRng;

if (tinymce.isIE && dom.isBlock(block)) {


oldRng = selection.getRng();
block.appendChild(dom.create('span', null,
'\u00a0'));
selection.select(block);
block.lastChild.outerHTML = '';
selection.setRng(oldRng);
}
};

// Remove the first empty inline element of the block so this:


<p><b><em></em></b>x</p> becomes this: <p>x</p>
function trimInlineElementsOnLeftSideOfBlock(block) {
var node = block, firstChilds = [], i;

// Find inner most first child ex: <p><i><b>*</b></i></p>


while (node = node.firstChild) {
if (dom.isBlock(node)) {
return;
}

if (node.nodeType == 1 && !
nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
firstChilds.push(node);
}
}

i = firstChilds.length;
while (i--) {
node = firstChilds[i];
if (!node.hasChildNodes() || (node.firstChild ==
node.lastChild && node.firstChild.nodeValue === '')) {
dom.remove(node);
} else {
// Remove <a> </a> see #5381
if (node.nodeName == "A" && (node.innerText ||
node.textContent) === ' ') {
dom.remove(node);
}
}
}
};

// Moves the caret to a suitable position within the root for


example in the first non pure whitespace text node or before an image
function moveToCaretPosition(root) {
var walker, node, rng, y, viewPort, lastNode = root,
tempElm;

rng = dom.createRng();

if (root.hasChildNodes()) {
walker = new TreeWalker(root, root);

while (node = walker.current()) {


if (node.nodeType == 3) {
rng.setStart(node, 0);
rng.setEnd(node, 0);
break;
}

if
(nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
rng.setStartBefore(node);
rng.setEndBefore(node);
break;
}

lastNode = node;
node = walker.next();
}

if (!node) {
rng.setStart(lastNode, 0);
rng.setEnd(lastNode, 0);
}
} else {
if (root.nodeName == 'BR') {
if (root.nextSibling &&
dom.isBlock(root.nextSibling)) {
// Trick on older IE versions to render
the caret before the BR between two lists
if (!documentMode || documentMode < 9) {
tempElm = dom.create('br');

root.parentNode.insertBefore(tempElm, root);
}

rng.setStartBefore(root);
rng.setEndBefore(root);
} else {
rng.setStartAfter(root);
rng.setEndAfter(root);
}
} else {
rng.setStart(root, 0);
rng.setEnd(root, 0);
}
}

selection.setRng(rng);

// Remove tempElm created for old IE:s


dom.remove(tempElm);

viewPort = dom.getViewPort(editor.getWin());

// 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 = dom.getPos(root).y;
if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
editor.getWin().scrollTo(0, y < viewPort.y ? y : y -
viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge
text block is broken into two blocks
}
};

// Creates a new block element by cloning the current one or


creating a new one if the name is specified
// This function will also copy any text formatting from the
parent block and add it to the new one
function createNewBlock(name) {
var node = container, block, clonedNode, caretNode;

block = name || parentBlockName == "TABLE" ?


dom.create(name || newBlockName) : parentBlock.cloneNode(false);
caretNode = block;

// Clone any parent styles


if (settings.keep_styles !== false) {
do {
if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)
$/.test(node.nodeName)) {
// Never clone a caret containers
if (node.id == '_mce_caret') {
continue;
}

clonedNode = node.cloneNode(false);
dom.setAttrib(clonedNode, 'id', ''); //
Remove ID since it needs to be document unique

if (block.hasChildNodes()) {

clonedNode.appendChild(block.firstChild);
block.appendChild(clonedNode);
} else {
caretNode = clonedNode;
block.appendChild(clonedNode);
}
}
} while (node = node.parentNode);
}

// BR is needed in empty blocks on non IE browsers


if (!tinymce.isIE) {
caretNode.innerHTML = '<br data-mce-bogus="1">';
}

return block;
};

// Returns true/false if the caret is at the start/end of the


parent block element
function isCaretAtStartOrEndOfBlock(start) {
var walker, node, name;

// Caret is in the middle of a text node like "a|b"


if (container.nodeType == 3 && (start ? offset > 0 : offset
< container.nodeValue.length)) {
return false;
}

// If after the last element in block node edge case for


#5091
if (container.parentNode == parentBlock &&
isAfterLastNodeInContainer && !start) {
return true;
}

// If the caret if before the first element in parentBlock


if (start && container.nodeType == 1 && container ==
parentBlock.firstChild) {
return true;
}

// Caret can be before/after a table


if (container.nodeName === "TABLE" ||
(container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
return (isAfterLastNodeInContainer && !start) || (!
isAfterLastNodeInContainer && start);
}

// Walk the DOM and look for text nodes or non empty
elements
walker = new TreeWalker(container, parentBlock);

// If caret is in beginning or end of a text block then


jump to the next/previous node
if (container.nodeType == 3) {
if (start && offset == 0) {
walker.prev();
} else if (!start && offset ==
container.nodeValue.length) {
walker.next();
}
}

while (node = walker.current()) {


if (node.nodeType === 1) {
// Ignore bogus elements
if (!node.getAttribute('data-mce-bogus')) {
// Keep empty elements like <img />
<input /> but not trailing br:s like <p>text|<br></p>
name = node.nodeName.toLowerCase();
if (nonEmptyElementsMap[name] && name !==
'br') {
return false;
}
}
} else if (node.nodeType === 3 && !/^[
\t\r\n]*$/.test(node.nodeValue)) {
return false;
}

if (start) {
walker.prev();
} else {
walker.next();
}
}

return true;
};

// Wraps any text nodes or inline elements in the specified


forced root block name
function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
var newBlock, parentBlock, startNode, node, next, blockName
= newBlockName || 'P';

// Not in a block element or in a table cell or caption


parentBlock = dom.getParent(container, dom.isBlock);
if (!parentBlock || !canSplitBlock(parentBlock)) {
parentBlock = parentBlock || editableRoot;

if (!parentBlock.hasChildNodes()) {
newBlock = dom.create(blockName);
parentBlock.appendChild(newBlock);
rng.setStart(newBlock, 0);
rng.setEnd(newBlock, 0);
return newBlock;
}

// Find parent that is the first child of parentBlock


node = container;
while (node.parentNode != parentBlock) {
node = node.parentNode;
}

// Loop left to find start node start wrapping at


while (node && !dom.isBlock(node)) {
startNode = node;
node = node.previousSibling;
}

if (startNode) {
newBlock = dom.create(blockName);
startNode.parentNode.insertBefore(newBlock,
startNode);

// Start wrapping until we hit a block


node = startNode;
while (node && !dom.isBlock(node)) {
next = node.nextSibling;
newBlock.appendChild(node);
node = next;
}

// Restore range to it's past location


rng.setStart(container, offset);
rng.setEnd(container, offset);
}
}

return container;
};

// Inserts a block or br before/after or in the middle of a split


list of the LI is empty
function handleEmptyListItem() {
function isFirstOrLastLi(first) {
var node = containerBlock[first ? 'firstChild' :
'lastChild'];

// Find first/last element since there might be


whitespace there
while (node) {
if (node.nodeType == 1) {
break;
}

node = node[first ? 'nextSibling' :


'previousSibling'];
}

return node === parentBlock;


};

newBlock = newBlockName ? createNewBlock(newBlockName) :


dom.create('BR');

if (isFirstOrLastLi(true) && isFirstOrLastLi()) {


// Is first and last list item then replace the OL/UL
with a text block
dom.replace(newBlock, containerBlock);
} else if (isFirstOrLastLi(true)) {
// First LI in list then remove LI and add text block
before list
containerBlock.parentNode.insertBefore(newBlock,
containerBlock);
} else if (isFirstOrLastLi()) {
// Last LI in list then temove LI and add text block
after list
dom.insertAfter(newBlock, containerBlock);
renderBlockOnIE(newBlock);
} else {
// Middle LI in list the split the list and insert a
text block in the middle
// Extract after fragment and insert it after the
current block
tmpRng = rng.cloneRange();
tmpRng.setStartAfter(parentBlock);
tmpRng.setEndAfter(containerBlock);
fragment = tmpRng.extractContents();
dom.insertAfter(fragment, containerBlock);
dom.insertAfter(newBlock, containerBlock);
}

dom.remove(parentBlock);
moveToCaretPosition(newBlock);
undoManager.add();
};

// Walks the parent block to the right and look for BR elements
function hasRightSideBr() {
var walker = new TreeWalker(container, parentBlock), node;

while (node = walker.current()) {


if (node.nodeName == 'BR') {
return true;
}

node = walker.next();
}
}

// Inserts a BR element if the forced_root_block option is set to


false or empty string
function insertBr() {
var brElm, extraBr, marker;

if (container && container.nodeType == 3 && offset >=


container.nodeValue.length) {
// Insert extra BR element at the end block elements
if (!tinymce.isIE && !hasRightSideBr()) {
brElm = dom.create('br');
rng.insertNode(brElm);
rng.setStartAfter(brElm);
rng.setEndAfter(brElm);
extraBr = true;
}
}

brElm = dom.create('br');
rng.insertNode(brElm);
// Rendering modes below IE8 doesn't display BR elements in
PRE unless we have a \n before it
if (tinymce.isIE && parentBlockName == 'PRE' && (!
documentMode || documentMode < 8)) {

brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
}

// Insert temp marker and scroll to that


marker = dom.create('span', {}, '&nbsp;');
brElm.parentNode.insertBefore(marker, brElm);
selection.scrollIntoView(marker);
dom.remove(marker);

if (!extraBr) {
rng.setStartAfter(brElm);
rng.setEndAfter(brElm);
} else {
rng.setStartBefore(brElm);
rng.setEndBefore(brElm);
}

selection.setRng(rng);
undoManager.add();
};

// Trims any linebreaks at the beginning of node user for example


when pressing enter in a PRE element
function trimLeadingLineBreaks(node) {
do {
if (node.nodeType === 3) {
node.nodeValue =
node.nodeValue.replace(/^[\r\n]+/, '');
}

node = node.firstChild;
} while (node);
};

function getEditableRoot(node) {
var root = dom.getRoot(), parent, editableRoot;

// Get all parents until we hit a non editable parent or


the root
parent = node;
while (parent !== root && dom.getContentEditable(parent) !
== "false") {
if (dom.getContentEditable(parent) === "true") {
editableRoot = parent;
}

parent = parent.parentNode;
}

return parent !== root ? editableRoot : root;


};

// Adds a BR at the end of blocks that only contains an IMG or


INPUT since these might be floated and then they won't expand the block
function addBrToBlockIfNeeded(block) {
var lastChild;

// IE will render the blocks correctly other browsers needs


a BR
if (!tinymce.isIE) {
block.normalize(); // Remove empty text nodes that
got left behind by the extract

// Check if the block is empty or contains a floated


last child
lastChild = block.lastChild;
if (!lastChild || (/^(left|right)
$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
dom.add(block, 'br');
}
}
};

// Delete any selected contents


if (!rng.collapsed) {
editor.execCommand('Delete');
return;
}

// Event is blocked by some other handler for example the lists


plugin
if (evt.isDefaultPrevented()) {
return;
}

// Setup range items and newBlockName


container = rng.startContainer;
offset = rng.startOffset;
newBlockName = (settings.force_p_newlines ? 'p' : '') ||
settings.forced_root_block;
newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
documentMode = dom.doc.documentMode;
shiftKey = evt.shiftKey;

// Resolve node index


if (container.nodeType == 1 && container.hasChildNodes()) {
isAfterLastNodeInContainer = offset >
container.childNodes.length - 1;
container = container.childNodes[Math.min(offset,
container.childNodes.length - 1)] || container;
if (isAfterLastNodeInContainer && container.nodeType == 3)
{
offset = container.nodeValue.length;
} else {
offset = 0;
}
}

// Get editable root node normaly the body element but sometimes
a div or span
editableRoot = getEditableRoot(container);
// If there is no editable root then enter is done inside a
contentEditable false element
if (!editableRoot) {
return;
}

undoManager.beforeChange();

// If editable root isn't block nor the root of the editor


if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot())
{
if (!newBlockName || shiftKey) {
insertBr();
}

return;
}

// Wrap the current node and it's sibling in a default block if


it's needed.
// for example this <td>text|<b>text2</b></td> will become this
<td><p>text|<b>text2</p></b></td>
// This won't happen if root blocks are disabled or the shiftKey
is pressed
if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
container = wrapSelfAndSiblingsInDefaultBlock(container,
offset);
}

// Find parent block and setup empty block paddings


parentBlock = dom.getParent(container, dom.isBlock);
containerBlock = parentBlock ?
dom.getParent(parentBlock.parentNode, dom.isBlock) : null;

// Setup block names


parentBlockName = parentBlock ?
parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
containerBlockName = containerBlock ?
containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5

// Enter inside block contained within a LI then split or insert


before/after LI
if (containerBlockName == 'LI' && !evt.ctrlKey) {
parentBlock = containerBlock;
parentBlockName = containerBlockName;
}

// Handle enter in LI
if (parentBlockName == 'LI') {
if (!newBlockName && shiftKey) {
insertBr();
return;
}

// Handle enter inside an empty list item


if (dom.isEmpty(parentBlock)) {
// Let the list plugin or browser handle nested lists
for now
if (/^(UL|OL|LI)
$/.test(containerBlock.parentNode.nodeName)) {
return false;
}

handleEmptyListItem();
return;
}
}

// Don't split PRE tags but insert a BR instead easier when


writing code samples etc
if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
if (!shiftKey) {
insertBr();
return;
}
} else {
// If no root block is configured then insert a BR by
default or if the shiftKey is pressed
if ((!newBlockName && !shiftKey && parentBlockName != 'LI')
|| (newBlockName && shiftKey)) {
insertBr();
return;
}
}

// Default block name if it's not configured


newBlockName = newBlockName || 'P';

// Insert new block before/after the parent block depending on


caret location
if (isCaretAtStartOrEndOfBlock()) {
// If the caret is at the end of a header we produce a P
tag after it similar to Word unless we are in a hgroup
if (/^(H[1-6]|PRE)$/.test(parentBlockName) &&
containerBlockName != 'HGROUP') {
newBlock = createNewBlock(newBlockName);
} else {
newBlock = createNewBlock();
}

// Split the current container block element if enter is


pressed inside an empty inner block element
if (settings.end_container_on_empty_block &&
canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
// Split container block for example a BLOCKQUOTE at
the current blockParent location for example a P
newBlock = dom.split(containerBlock, parentBlock);
} else {
dom.insertAfter(newBlock, parentBlock);
}

moveToCaretPosition(newBlock);
} else if (isCaretAtStartOrEndOfBlock(true)) {
// Insert new block before
newBlock =
parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
renderBlockOnIE(newBlock);
} else {
// Extract after fragment and insert it after the current
block
tmpRng = rng.cloneRange();
tmpRng.setEndAfter(parentBlock);
fragment = tmpRng.extractContents();
trimLeadingLineBreaks(fragment);
newBlock = fragment.firstChild;
dom.insertAfter(fragment, parentBlock);
trimInlineElementsOnLeftSideOfBlock(newBlock);
addBrToBlockIfNeeded(parentBlock);
moveToCaretPosition(newBlock);
}

dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to


be document unique
undoManager.add();
}

editor.onKeyDown.add(function(ed, evt) {
if (evt.keyCode == 13) {
if (handleEnterKey(evt) !== false) {
evt.preventDefault();
}
}
});
};
})(tinymce);

;tinyMCE.addI18n({es:{common:{more_colors:"M\u00e1s colores",invalid_data:"Error:
Introdujo un valor no v\u00e1lido, est\u00e1n marcados en rojo.",popup_blocked:"Lo
sentimos, su bloqueo de ventanas emergentes ha deshabilitado una ventana que provee
funcionalidades a la aplicaci\u00f3n. Necesita deshabilitar este bloqueo en este
sitio para poder utilizar todas las funciones.",clipboard_no_support:"Su navegador
no soporta las funciones de cortapapeles, use los accesos por
teclado.",clipboard_msg:"Copiar/Cortar/Pegar no se encuentra disponible en Mozilla
y Firefox.\\n \u00bfDesea obtener m\u00e1s informaci\u00f3n acerca de este
tema?",not_set:"-- Ninguno
--",class_name:"Clase",browse:"Examinar",close:"Cerrar",cancel:"Cancelar",update:"A
ctualizar",insert:"Insertar",apply:"Aplicar",edit_confirm:" \u00bfDesea utilizar el
modo WYSIWYG para esta caja de texto?"},contextmenu:
{full:"Justificado",right:"Derecha",center:"Centrado",left:"Izquierda",align:"Aline
aci\u00f3n"},insertdatetime:
{day_short:"Dom,Lun,Mar,Mie,Jue,Vie,Sab,Dom",day_long:"Domingo,Lunes,Martes,Mi\u00e
9rcoles,Jueves,Viernes,S\u00e1bado,Domingo",months_short:"Ene,Feb,Mar,Abr,May,Jun,J
ul,Ago,Sep,Oct,Nov,Dic",months_long:"Enero,Febrero,Marzo,Abril,Mayo,Junio,Julio,Ago
sto,Septiembre,Octubre,Noviembre,Diciembre",inserttime_desc:"Insertar
hora",insertdate_desc:"Insertar fecha",time_fmt:"%H:%M:%S",date_fmt:"%d-%m-
%Y"},print:{print_desc:"Imprimir"},preview:{preview_desc:"Vista
previa"},directionality:{rtl_desc:"Direcci\u00f3n derecha a
izquierda",ltr_desc:"Direcci\u00f3n izquierda a derecha"},layer:{content:"Nueva
capa...",absolute_desc:"Cambiar a posici\u00f3n
absoluta",backward_desc:"Retroceder",forward_desc:"Avanzar",insertlayer_desc:"Inser
tar nueva capa"},save:{save_desc:"Guardar",cancel_desc:"Cancelar todos los
cambios"},nonbreaking:{nonbreaking_desc:"Insertar caracter de espacio \'non-
breaking\'"},iespell:{download:"No se detect\u00f3 \'ieSpell\'. \u00bfDesea
instalarlo ahora?",iespell_desc:"Corrector ortogr\u00e1fico"},advhr:
{advhr_desc:"Regla horizontal",delta_height:"",delta_width:""},emotions:
{emotions_desc:"Emoticones",delta_height:"",delta_width:""},searchreplace:
{replace_desc:"Buscar/Reemplazar",search_desc:"Buscar",delta_width:"",delta_height:
""},advimage:{image_desc:"Insertar/editar
imagen",delta_width:"",delta_height:""},advlink:{link_desc:"Insertar/editar
hiperv\u00ednculo",delta_height:"",delta_width:""},xhtmlxtras:
{attribs_desc:"Insertar/Editar
atributos",ins_desc:"Inserci\u00f3n",del_desc:"Borrado",acronym_desc:"Acr\u00f3nimo
",abbr_desc:"Abreviatura",cite_desc:"Cita",attribs_delta_height:"",attribs_delta_wi
dth:"",ins_delta_height:"",ins_delta_width:"",del_delta_height:"",del_delta_width:"
",acronym_delta_height:"",acronym_delta_width:"",abbr_delta_height:"",abbr_delta_wi
dth:"",cite_delta_height:"",cite_delta_width:""},style:{desc:"Editar Estilo
CSS",delta_height:"",delta_width:""},paste:{plaintext_mode:"Paste is now in plain
text mode. Click again to toggle back to regular paste
mode.",plaintext_mode_sticky:"Paste is now in plain text mode. Click again to
toggle back to regular paste mode. After you paste something you will be returned
to regular paste mode.",selectall_desc:"Elegir todo",paste_word_desc:"Pegar desde
Word",paste_text_desc:"Pegar como texto plano"},paste_dlg:{word_title:"Use CTRL+V
en su teclado para pegar el texto en la ventana.",text_linebreaks:"Keep
linebreaks",text_title:"Use CTRL+V en su teclado para pegar el texto en la
ventana."},table:{cell:"Celda",col:"Columna",row:"Fila",del:"Eliminar
tabla",copy_row_desc:"Copiar fila",cut_row_desc:"Cortar
fila",paste_row_after_desc:"Pegar filas
(despu\u00e9s)",paste_row_before_desc:"Pegar filas (antes)",props_desc:"Propiedades
de la tabla",cell_desc:"Propiedades de la celda",row_desc:"Propiedades de la
fila",merge_cells_desc:"Vincular celdas",split_cells_desc:"Dividir
celdas",delete_col_desc:"Suprimir columna",col_after_desc:"Insertar columna
(despu\u00e9s)",col_before_desc:"Insertar columna
(antes)",delete_row_desc:"Suprimir fila",row_after_desc:"Insertar fila
(despu\u00e9s)",row_before_desc:"Insertar fila (antes)",desc:"Inserta una nueva
tabla",merge_cells_delta_height:"",merge_cells_delta_width:"",table_delta_height:""
,table_delta_width:"",cellprops_delta_height:"",cellprops_delta_width:"",rowprops_d
elta_height:"",rowprops_delta_width:""},autosave:{warning_message:"Se reestablece
en contenido guardado, perder\u00e1 todo el contenido que est\u00e1 actualmente en
el editor.\\n\\nEst\u00e1 seguro de que quiere reestablecer el contenido
guardado.",restore_content:"Reestablecer contenido guardado
autom\u00e1ticamente",unload_msg:"Los cambios realizados se perder\u00e1n si sale
de esta p\u00e1gina."},fullscreen:{desc:"Cambiar a modo Pantalla Completa"},media:
{edit:"Editar medio embebido",desc:"Insertar/editar medio
embebido",delta_height:"",delta_width:""},fullpage:{desc:"Propiedades del
documento",delta_width:"Ancho",delta_height:"Alto"},template:{desc:"Insertar
contenido de plantilla predefinida"},visualchars:{desc:"Caracteres de control
ON/OFF."},spellchecker:{desc:"Cambiar a corrector
ortogr\u00e1fico",menu:"Configuraci\u00f3n de corrector
ortogr\u00e1fico",ignore_word:"Ignorar",ignore_words:"Ignorar
todo",langs:"Idiomas",wait:"Espere...",sug:"Sugerencias",no_sug:"Sin
sugerencias",no_mpell:"No se encontraron errores."},pagebreak:{desc:"Insertar fin
de p\u00e1gina"},advlist:{types:"Tipo",def:"Preestablecido",lower_alpha:"Menos
opaco",lower_greek:"Menos greek",lower_roman:"Menos roman",upper_alpha:"M\u00e1s
opaco",upper_roman:"M\u00e1s
roman",circle:"Circulo",disc:"Disc",square:"Cuadro"}}});;/**
* editor_template_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/

(function(tinymce) {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
each = tinymce.each, Cookie = tinymce.util.Cookie, lastExtID, explode =
tinymce.explode;

// Generates a preview for a format


function getPreviewCss(ed, fmt) {
var name, previewElm, dom = ed.dom, previewCss = '', parentFontSize,
previewStylesName;

previewStyles = ed.settings.preview_styles;

// No preview forced
if (previewStyles === false)
return '';

// Default preview
if (!previewStyles)
previewStyles = 'font-family font-size font-weight text-
decoration text-transform color background-color';

// Removes any variables since these can't be previewed


function removeVars(val) {
return val.replace(/%(\w+)/g, '');
};

// Create block/inline element to use for preview


name = fmt.block || fmt.inline || 'span';
previewElm = dom.create(name);

// Add format styles to preview element


each(fmt.styles, function(value, name) {
value = removeVars(value);

if (value)
dom.setStyle(previewElm, name, value);
});

// Add attributes to preview element


each(fmt.attributes, function(value, name) {
value = removeVars(value);

if (value)
dom.setAttrib(previewElm, name, value);
});

// Add classes to preview element


each(fmt.classes, function(value) {
value = removeVars(value);

if (!dom.hasClass(previewElm, value))
dom.addClass(previewElm, value);
});

// Add the previewElm outside the visual area


dom.setStyles(previewElm, {position: 'absolute', left: -0xFFFF});
ed.getBody().appendChild(previewElm);

// Get parent container font size so we can compute px values out of


em/% for older IE:s
parentFontSize = dom.getStyle(ed.getBody(), 'fontSize', true);
parentFontSize = /px$/.test(parentFontSize) ? parseInt(parentFontSize,
10) : 0;

each(previewStyles.split(' '), function(name) {


var value = dom.getStyle(previewElm, name, true);

// If background is transparent then check if the body has a


background color we can use
if (name == 'background-color' && /transparent|rgba\s*\([^)]
+,\s*0\)/.test(value)) {
value = dom.getStyle(ed.getBody(), name, true);

// Ignore white since it's the default color, not the


nicest fix
if (dom.toHex(value).toLowerCase() == '#ffffff') {
return;
}
}

// Old IE won't calculate the font size so we need to do that


manually
if (name == 'font-size') {
if (/em|%$/.test(value)) {
if (parentFontSize === 0) {
return;
}

// Convert font size from em/% to px


value = parseFloat(value, 10) / (/%$/.test(value) ?
100 : 1);
value = (value * parentFontSize) + 'px';
}
}

previewCss += name + ':' + value + ';';


});

dom.remove(previewElm);

return previewCss;
};

// Tell it to load theme specific language pack(s)


tinymce.ThemeManager.requireLangPack('advanced');

tinymce.create('tinymce.themes.AdvancedTheme', {
sizes : [8, 10, 12, 14, 18, 24, 36],

// Control name lookup, format: title, command


controls : {
bold : ['bold_desc', 'Bold'],
italic : ['italic_desc', 'Italic'],
underline : ['underline_desc', 'Underline'],
strikethrough : ['striketrough_desc', 'Strikethrough'],
justifyleft : ['justifyleft_desc', 'JustifyLeft'],
justifycenter : ['justifycenter_desc', 'JustifyCenter'],
justifyright : ['justifyright_desc', 'JustifyRight'],
justifyfull : ['justifyfull_desc', 'JustifyFull'],
bullist : ['bullist_desc', 'InsertUnorderedList'],
numlist : ['numlist_desc', 'InsertOrderedList'],
outdent : ['outdent_desc', 'Outdent'],
indent : ['indent_desc', 'Indent'],
cut : ['cut_desc', 'Cut'],
copy : ['copy_desc', 'Copy'],
paste : ['paste_desc', 'Paste'],
undo : ['undo_desc', 'Undo'],
redo : ['redo_desc', 'Redo'],
link : ['link_desc', 'mceLink'],
unlink : ['unlink_desc', 'unlink'],
image : ['image_desc', 'mceImage'],
cleanup : ['cleanup_desc', 'mceCleanup'],
help : ['help_desc', 'mceHelp'],
code : ['code_desc', 'mceCodeEditor'],
hr : ['hr_desc', 'InsertHorizontalRule'],
removeformat : ['removeformat_desc', 'RemoveFormat'],
sub : ['sub_desc', 'subscript'],
sup : ['sup_desc', 'superscript'],
forecolor : ['forecolor_desc', 'ForeColor'],
forecolorpicker : ['forecolor_desc', 'mceForeColor'],
backcolor : ['backcolor_desc', 'HiliteColor'],
backcolorpicker : ['backcolor_desc', 'mceBackColor'],
charmap : ['charmap_desc', 'mceCharMap'],
visualaid : ['visualaid_desc', 'mceToggleVisualAid'],
anchor : ['anchor_desc', 'mceInsertAnchor'],
newdocument : ['newdocument_desc', 'mceNewDocument'],
blockquote : ['blockquote_desc', 'mceBlockQuote']
},

stateControls : ['bold', 'italic', 'underline', 'strikethrough',


'bullist', 'numlist', 'justifyleft', 'justifycenter', 'justifyright',
'justifyfull', 'sub', 'sup', 'blockquote'],

init : function(ed, url) {


var t = this, s, v, o;

t.editor = ed;
t.url = url;
t.onResolveName = new tinymce.util.Dispatcher(this);
s = ed.settings;

ed.forcedHighContrastMode = ed.settings.detect_highcontrast &&


t._isHighContrast();
ed.settings.skin = ed.forcedHighContrastMode ? 'highcontrast' :
ed.settings.skin;

// Setup default buttons


if (!s.theme_advanced_buttons1) {
s = extend({
theme_advanced_buttons1 :
"bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justi
fyfull,|,styleselect,formatselect",
theme_advanced_buttons2 :
"bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,cleanup,he
lp,code",
theme_advanced_buttons3 :
"hr,removeformat,visualaid,|,sub,sup,|,charmap"
}, s);
}
// Default settings
t.settings = s = extend({
theme_advanced_path : true,
theme_advanced_toolbar_location : 'top',
theme_advanced_blockformats :
"p,h1,h2,h3,h4,pre,blockquote",
theme_advanced_toolbar_align : "left",
theme_advanced_statusbar_location : "bottom",
theme_advanced_fonts : "Andale Mono=andale
mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant
garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-
serif;Courier New=courier
new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symb
ol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times
New Roman=times new roman,times;Trebuchet MS=trebuchet
ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf
dingbats",
theme_advanced_more_colors : 1,
theme_advanced_row_height : 23,
theme_advanced_resize_horizontal : 1,
theme_advanced_resizing_use_cookie : 1,
theme_advanced_font_sizes : "1,2,3,4,5,6,7",
theme_advanced_font_selector : "span",
theme_advanced_show_current_color: 0,
readonly : ed.settings.readonly
}, s);

// Setup default font_size_style_values


if (!s.font_size_style_values)
s.font_size_style_values =
"8pt,10pt,12pt,14pt,18pt,24pt,36pt";

if (tinymce.is(s.theme_advanced_font_sizes, 'string')) {
s.font_size_style_values =
tinymce.explode(s.font_size_style_values);
s.font_size_classes = tinymce.explode(s.font_size_classes
|| '');

// Parse string value


o = {};
ed.settings.theme_advanced_font_sizes =
s.theme_advanced_font_sizes;
each(ed.getParam('theme_advanced_font_sizes', '', 'hash'),
function(v, k) {
var cl;

if (k == v && v >= 1 && v <= 7) {


k = v + ' (' + t.sizes[v - 1] + 'pt)';
cl = s.font_size_classes[v - 1];
v = s.font_size_style_values[v - 1] ||
(t.sizes[v - 1] + 'pt');
}

if (/^\s*\./.test(v))
cl = v.replace(/\./g, '');

o[k] = cl ? {'class' : cl} : {fontSize : v};


});
s.theme_advanced_font_sizes = o;
}

if ((v = s.theme_advanced_path_location) && v != 'none')


s.theme_advanced_statusbar_location =
s.theme_advanced_path_location;

if (s.theme_advanced_statusbar_location == 'none')
s.theme_advanced_statusbar_location = 0;

if (ed.settings.content_css !== false)


ed.contentCSS.push(ed.baseURI.toAbsolute(url + "/skins/" +
ed.settings.skin + "/content.css"));

// Init editor
ed.onInit.add(function() {
if (!ed.settings.readonly) {
ed.onNodeChange.add(t._nodeChanged, t);
ed.onKeyUp.add(t._updateUndoStatus, t);
ed.onMouseUp.add(t._updateUndoStatus, t);
ed.dom.bind(ed.dom.getRoot(), 'dragend', function() {
t._updateUndoStatus(ed);
});
}
});

ed.onSetProgressState.add(function(ed, b, ti) {
var co, id = ed.id, tb;

if (b) {
t.progressTimer = setTimeout(function() {
co = ed.getContainer();
co = co.insertBefore(DOM.create('DIV', {style :
'position:relative'}), co.firstChild);
tb = DOM.get(ed.id + '_tbl');

DOM.add(co, 'div', {id : id + '_blocker',


'class' : 'mceBlocker', style : {width : tb.clientWidth + 2, height :
tb.clientHeight + 2}});
DOM.add(co, 'div', {id : id + '_progress',
'class' : 'mceProgress', style : {left : tb.clientWidth / 2, top :
tb.clientHeight / 2}});
}, ti || 0);
} else {
DOM.remove(id + '_blocker');
DOM.remove(id + '_progress');
clearTimeout(t.progressTimer);
}
});

DOM.loadCSS(s.editor_css ?
ed.documentBaseURI.toAbsolute(s.editor_css) : url + "/skins/" + ed.settings.skin +
"/ui.css");

if (s.skin_variant)
DOM.loadCSS(url + "/skins/" + ed.settings.skin + "/ui_" +
s.skin_variant + ".css");
},
_isHighContrast : function() {
var actualColor, div = DOM.add(DOM.getRoot(), 'div', {'style':
'background-color: rgb(171,239,86);'});

actualColor = (DOM.getStyle(div, 'background-color', true) +


'').toLowerCase().replace(/ /g, '');
DOM.remove(div);

return actualColor != 'rgb(171,239,86)' && actualColor !=


'#abef56';
},

createControl : function(n, cf) {


var cd, c;

if (c = cf.createControl(n))
return c;

switch (n) {
case "styleselect":
return this._createStyleSelect();

case "formatselect":
return this._createBlockFormats();

case "fontselect":
return this._createFontSelect();

case "fontsizeselect":
return this._createFontSizeSelect();

case "forecolor":
return this._createForeColorMenu();

case "backcolor":
return this._createBackColorMenu();
}

if ((cd = this.controls[n]))
return cf.createButton(n, {title : "advanced." + cd[0], cmd
: cd[1], ui : cd[2], value : cd[3]});
},

execCommand : function(cmd, ui, val) {


var f = this['_' + cmd];

if (f) {
f.call(this, ui, val);
return true;
}

return false;
},

_importClasses : function(e) {
var ed = this.editor, ctrl =
ed.controlManager.get('styleselect');
if (ctrl.getLength() == 0) {
each(ed.dom.getClasses(), function(o, idx) {
var name = 'style_' + idx, fmt;

fmt = {
inline : 'span',
attributes : {'class' : o['class']},
selector : '*'
};

ed.formatter.register(name, fmt);

ctrl.add(o['class'], name, {
style: function() {
return getPreviewCss(ed, fmt);
}
});
});
}
},

_createStyleSelect : function(n) {
var t = this, ed = t.editor, ctrlMan = ed.controlManager, ctrl;

// Setup style select box


ctrl = ctrlMan.createListBox('styleselect', {
title : 'advanced.style_select',
onselect : function(name) {
var matches, formatNames = [], removedFormat;

each(ctrl.items, function(item) {
formatNames.push(item.value);
});

ed.focus();
ed.undoManager.add();

// Toggle off the current format(s)


matches = ed.formatter.matchAll(formatNames);
tinymce.each(matches, function(match) {
if (!name || match == name) {
if (match)
ed.formatter.remove(match);

removedFormat = true;
}
});

if (!removedFormat)
ed.formatter.apply(name);

ed.undoManager.add();
ed.nodeChanged();

return false; // No auto select


}
});

// Handle specified format


ed.onPreInit.add(function() {
var counter = 0, formats = ed.getParam('style_formats');

if (formats) {
each(formats, function(fmt) {
var name, keys = 0;

each(fmt, function() {keys++;});

if (keys > 1) {
name = fmt.name = fmt.name || 'style_' +
(counter++);
ed.formatter.register(name, fmt);
ctrl.add(fmt.title, name, {
style: function() {
return getPreviewCss(ed,
fmt);
}
});
} else
ctrl.add(fmt.title);
});
} else {
each(ed.getParam('theme_advanced_styles', '',
'hash'), function(val, key) {
var name, fmt;

if (val) {
name = 'style_' + (counter++);
fmt = {
inline : 'span',
classes : val,
selector : '*'
};

ed.formatter.register(name, fmt);
ctrl.add(t.editor.translate(key), name, {
style: function() {
return getPreviewCss(ed,
fmt);
}
});
}
});
}
});

// Auto import classes if the ctrl box is empty


if (ctrl.getLength() == 0) {
ctrl.onPostRender.add(function(ed, n) {
if (!ctrl.NativeListBox) {
Event.add(n.id + '_text', 'focus',
t._importClasses, t);
Event.add(n.id + '_text', 'mousedown',
t._importClasses, t);
Event.add(n.id + '_open', 'focus',
t._importClasses, t);
Event.add(n.id + '_open', 'mousedown',
t._importClasses, t);
} else
Event.add(n.id, 'focus', t._importClasses, t);
});
}

return ctrl;
},

_createFontSelect : function() {
var c, t = this, ed = t.editor;

c = ed.controlManager.createListBox('fontselect', {
title : 'advanced.fontdefault',
onselect : function(v) {
var cur = c.items[c.selectedIndex];

if (!v && cur) {


ed.execCommand('FontName', false, cur.value);
return;
}

ed.execCommand('FontName', false, v);

// Fake selection, execCommand will fire a nodeChange


and update the selection
c.select(function(sv) {
return v == sv;
});

if (cur && cur.value == v) {


c.select(null);
}

return false; // No auto select


}
});

if (c) {
each(ed.getParam('theme_advanced_fonts',
t.settings.theme_advanced_fonts, 'hash'), function(v, k) {
c.add(ed.translate(k), v, {style : v.indexOf('dings')
== -1 ? 'font-family:' + v : ''});
});
}

return c;
},

_createFontSizeSelect : function() {
var t = this, ed = t.editor, c, i = 0, cl = [];

c = ed.controlManager.createListBox('fontsizeselect', {title :
'advanced.font_size', onselect : function(v) {
var cur = c.items[c.selectedIndex];

if (!v && cur) {


cur = cur.value;

if (cur['class']) {
ed.formatter.toggle('fontsize_class', {value :
cur['class']});
ed.undoManager.add();
ed.nodeChanged();
} else {
ed.execCommand('FontSize', false,
cur.fontSize);
}

return;
}

if (v['class']) {
ed.focus();
ed.undoManager.add();
ed.formatter.toggle('fontsize_class', {value :
v['class']});
ed.undoManager.add();
ed.nodeChanged();
} else
ed.execCommand('FontSize', false, v.fontSize);

// Fake selection, execCommand will fire a nodeChange and


update the selection
c.select(function(sv) {
return v == sv;
});

if (cur && (cur.value.fontSize == v.fontSize ||


cur.value['class'] && cur.value['class'] == v['class'])) {
c.select(null);
}

return false; // No auto select


}});

if (c) {
each(t.settings.theme_advanced_font_sizes, function(v, k) {
var fz = v.fontSize;

if (fz >= 1 && fz <= 7)


fz = t.sizes[parseInt(fz) - 1] + 'pt';

c.add(k, v, {'style' : 'font-size:' + fz, 'class' :


'mceFontSize' + (i++) + (' ' + (v['class'] || ''))});
});
}

return c;
},

_createBlockFormats : function() {
var c, fmts = {
p : 'advanced.paragraph',
address : 'advanced.address',
pre : 'advanced.pre',
h1 : 'advanced.h1',
h2 : 'advanced.h2',
h3 : 'advanced.h3',
h4 : 'advanced.h4',
h5 : 'advanced.h5',
h6 : 'advanced.h6',
div : 'advanced.div',
blockquote : 'advanced.blockquote',
code : 'advanced.code',
dt : 'advanced.dt',
dd : 'advanced.dd',
samp : 'advanced.samp'
}, t = this;

c = t.editor.controlManager.createListBox('formatselect',
{title : 'advanced.block', onselect : function(v) {
t.editor.execCommand('FormatBlock', false, v);
return false;
}});

if (c) {
each(t.editor.getParam('theme_advanced_blockformats',
t.settings.theme_advanced_blockformats, 'hash'), function(v, k) {
c.add(t.editor.translate(k != v ? k : fmts[v]), v,
{'class' : 'mce_formatPreview mce_' + v, style: function() {
return getPreviewCss(t.editor, {block: v});
}});
});
}

return c;
},

_createForeColorMenu : function() {
var c, t = this, s = t.settings, o = {}, v;

if (s.theme_advanced_more_colors) {
o.more_colors_func = function() {
t._mceColorPicker(0, {
color : c.value,
func : function(co) {
c.setColor(co);
}
});
};
}

if (v = s.theme_advanced_text_colors)
o.colors = v;

if (s.theme_advanced_default_foreground_color)
o.default_color =
s.theme_advanced_default_foreground_color;

o.title = 'advanced.forecolor_desc';
o.cmd = 'ForeColor';
o.scope = this;

c = t.editor.controlManager.createColorSplitButton('forecolor',
o);

return c;
},

_createBackColorMenu : function() {
var c, t = this, s = t.settings, o = {}, v;

if (s.theme_advanced_more_colors) {
o.more_colors_func = function() {
t._mceColorPicker(0, {
color : c.value,
func : function(co) {
c.setColor(co);
}
});
};
}

if (v = s.theme_advanced_background_colors)
o.colors = v;

if (s.theme_advanced_default_background_color)
o.default_color =
s.theme_advanced_default_background_color;

o.title = 'advanced.backcolor_desc';
o.cmd = 'HiliteColor';
o.scope = this;

c = t.editor.controlManager.createColorSplitButton('backcolor',
o);

return c;
},

renderUI : function(o) {
var n, ic, tb, t = this, ed = t.editor, s = t.settings, sc, p,
nl;

if (ed.settings) {
ed.settings.aria_label = s.aria_label +
ed.getLang('advanced.help_shortcut');
}

// TODO: ACC Should have an aria-describedby attribute which is


user-configurable to describe what this field is actually for.
// Maybe actually inherit it from the original textara?
n = p = DOM.create('span', {role : 'application', 'aria-
labelledby' : ed.id + '_voice', id : ed.id + '_parent', 'class' : 'mceEditor ' +
ed.settings.skin + 'Skin' + (s.skin_variant ? ' ' + ed.settings.skin + 'Skin' +
t._ufirst(s.skin_variant) : '') + (ed.settings.directionality == "rtl" ? '
mceRtl' : '')});
DOM.add(n, 'span', {'class': 'mceVoiceLabel', 'style':
'display:none;', id: ed.id + '_voice'}, s.aria_label);

if (!DOM.boxModel)
n = DOM.add(n, 'div', {'class' : 'mceOldBoxModel'});

n = sc = DOM.add(n, 'table', {role : "presentation", id : ed.id +


'_tbl', 'class' : 'mceLayout', cellSpacing : 0, cellPadding : 0});
n = tb = DOM.add(n, 'tbody');
switch ((s.theme_advanced_layout_manager || '').toLowerCase()) {
case "rowlayout":
ic = t._rowLayout(s, tb, o);
break;

case "customlayout":
ic = ed.execCallback("theme_advanced_custom_layout",
s, tb, o, p);
break;

default:
ic = t._simpleLayout(s, tb, o, p);
}

n = o.targetNode;

// Add classes to first and last TRs


nl = sc.rows;
DOM.addClass(nl[0], 'mceFirst');
DOM.addClass(nl[nl.length - 1], 'mceLast');

// Add classes to first and last TDs


each(DOM.select('tr', tb), function(n) {
DOM.addClass(n.firstChild, 'mceFirst');
DOM.addClass(n.childNodes[n.childNodes.length - 1],
'mceLast');
});

if (DOM.get(s.theme_advanced_toolbar_container))
DOM.get(s.theme_advanced_toolbar_container).appendChild(p);
else
DOM.insertAfter(p, n);

Event.add(ed.id + '_path_row', 'click', function(e) {


e = e.target;

if (e.nodeName == 'A') {
t._sel(e.className.replace(/^.*mcePath_([0-9]+).*$/,
'$1'));
return false;
}
});
/*
if (DOM.get(ed.id + '_path_row')) {
Event.add(ed.id + '_tbl', 'mouseover', function(e) {
var re;

e = e.target;

if (e.nodeName == 'SPAN' &&


DOM.hasClass(e.parentNode, 'mceButton')) {
re = DOM.get(ed.id + '_path_row');
t.lastPath = re.innerHTML;
DOM.setHTML(re, e.parentNode.title);
}
});

Event.add(ed.id + '_tbl', 'mouseout', function(e) {


if (t.lastPath) {
DOM.setHTML(ed.id + '_path_row', t.lastPath);
t.lastPath = 0;
}
});
}
*/

if (!ed.getParam('accessibility_focus'))
Event.add(DOM.add(p, 'a', {href : '#'}, '<!-- IE -->'),
'focus', function() {tinyMCE.get(ed.id).focus();});

if (s.theme_advanced_toolbar_location == 'external')
o.deltaHeight = 0;

t.deltaHeight = o.deltaHeight;
o.targetNode = null;

ed.onKeyDown.add(function(ed, evt) {
var DOM_VK_F10 = 121, DOM_VK_F11 = 122;

if (evt.altKey) {
if (evt.keyCode === DOM_VK_F10) {
// Make sure focus is given to toolbar in
Safari.
// We can't do this in IE as it prevents giving
focus to toolbar when editor is in a frame
if (tinymce.isWebKit) {
window.focus();
}
t.toolbarGroup.focus();
return Event.cancel(evt);
} else if (evt.keyCode === DOM_VK_F11) {
DOM.get(ed.id + '_path_row').focus();
return Event.cancel(evt);
}
}
});

// alt+0 is the UK recommended shortcut for accessing the list of


access controls.
ed.addShortcut('alt+0', '', 'mceShortcuts', t);

return {
iframeContainer : ic,
editorContainer : ed.id + '_parent',
sizeContainer : sc,
deltaHeight : o.deltaHeight
};
},

getInfo : function() {
return {
longname : 'Advanced theme',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
version : tinymce.majorVersion + "." + tinymce.minorVersion
}
},
resizeBy : function(dw, dh) {
var e = DOM.get(this.editor.id + '_ifr');

this.resizeTo(e.clientWidth + dw, e.clientHeight + dh);


},

resizeTo : function(w, h, store) {


var ed = this.editor, s = this.settings, e = DOM.get(ed.id +
'_tbl'), ifr = DOM.get(ed.id + '_ifr');

// Boundery fix box


w = Math.max(s.theme_advanced_resizing_min_width || 100, w);
h = Math.max(s.theme_advanced_resizing_min_height || 100, h);
w = Math.min(s.theme_advanced_resizing_max_width || 0xFFFF, w);
h = Math.min(s.theme_advanced_resizing_max_height || 0xFFFF, h);

// Resize iframe and container


DOM.setStyle(e, 'height', '');
DOM.setStyle(ifr, 'height', h);

if (s.theme_advanced_resize_horizontal) {
DOM.setStyle(e, 'width', '');
DOM.setStyle(ifr, 'width', w);

// Make sure that the size is never smaller than the over
all ui
if (w < e.clientWidth) {
w = e.clientWidth;
DOM.setStyle(ifr, 'width', e.clientWidth);
}
}

// Store away the size


if (store && s.theme_advanced_resizing_use_cookie) {
Cookie.setHash("TinyMCE_" + ed.id + "_size", {
cw : w,
ch : h
});
}
},

destroy : function() {
var id = this.editor.id;

Event.clear(id + '_resize');
Event.clear(id + '_path_row');
Event.clear(id + '_external_close');
},

// Internal functions

_simpleLayout : function(s, tb, o, p) {


var t = this, ed = t.editor, lo =
s.theme_advanced_toolbar_location, sl = s.theme_advanced_statusbar_location, n, ic,
etb, c;

if (s.readonly) {
n = DOM.add(tb, 'tr');
n = ic = DOM.add(n, 'td', {'class' :
'mceIframeContainer'});
return ic;
}

// Create toolbar container at top


if (lo == 'top')
t._addToolbars(tb, o);

// Create external toolbar


if (lo == 'external') {
n = c = DOM.create('div', {style : 'position:relative'});
n = DOM.add(n, 'div', {id : ed.id + '_external', 'class' :
'mceExternalToolbar'});
DOM.add(n, 'a', {id : ed.id + '_external_close', href :
'javascript:;', 'class' : 'mceExternalClose'});
n = DOM.add(n, 'table', {id : ed.id + '_tblext',
cellSpacing : 0, cellPadding : 0});
etb = DOM.add(n, 'tbody');

if (p.firstChild.className == 'mceOldBoxModel')
p.firstChild.appendChild(c);
else
p.insertBefore(c, p.firstChild);

t._addToolbars(etb, o);

ed.onMouseUp.add(function() {
var e = DOM.get(ed.id + '_external');
DOM.show(e);

DOM.hide(lastExtID);

var f = Event.add(ed.id + '_external_close', 'click',


function() {
DOM.hide(ed.id + '_external');
Event.remove(ed.id + '_external_close',
'click', f);
return false;
});

DOM.show(e);
DOM.setStyle(e, 'top', 0 - DOM.getRect(ed.id +
'_tblext').h - 1);

// Fixes IE rendering bug


DOM.hide(e);
DOM.show(e);
e.style.filter = '';

lastExtID = ed.id + '_external';

e = null;
});
}

if (sl == 'top')
t._addStatusBar(tb, o);
// Create iframe container
if (!s.theme_advanced_toolbar_container) {
n = DOM.add(tb, 'tr');
n = ic = DOM.add(n, 'td', {'class' :
'mceIframeContainer'});
}

// Create toolbar container at bottom


if (lo == 'bottom')
t._addToolbars(tb, o);

if (sl == 'bottom')
t._addStatusBar(tb, o);

return ic;
},

_rowLayout : function(s, tb, o) {


var t = this, ed = t.editor, dc, da, cf = ed.controlManager, n,
ic, to, a;

dc = s.theme_advanced_containers_default_class || '';
da = s.theme_advanced_containers_default_align || 'center';

each(explode(s.theme_advanced_containers || ''), function(c, i) {


var v = s['theme_advanced_container_' + c] || '';

switch (c.toLowerCase()) {
case 'mceeditor':
n = DOM.add(tb, 'tr');
n = ic = DOM.add(n, 'td', {'class' :
'mceIframeContainer'});
break;

case 'mceelementpath':
t._addStatusBar(tb, o);
break;

default:
a = (s['theme_advanced_container_' + c +
'_align'] || da).toLowerCase();
a = 'mce' + t._ufirst(a);

n = DOM.add(DOM.add(tb, 'tr'), 'td', {


'class' : 'mceToolbar ' +
(s['theme_advanced_container_' + c + '_class'] || dc) + ' ' + a || da
});

to = cf.createToolbar("toolbar" + i);
t._addControls(v, to);
DOM.setHTML(n, to.renderHTML());
o.deltaHeight -= s.theme_advanced_row_height;
}
});

return ic;
},

_addControls : function(v, tb) {


var t = this, s = t.settings, di, cf = t.editor.controlManager;

if (s.theme_advanced_disable && !t._disabled) {


di = {};

each(explode(s.theme_advanced_disable), function(v) {
di[v] = 1;
});

t._disabled = di;
} else
di = t._disabled;

each(explode(v), function(n) {
var c;

if (di && di[n])


return;

// Compatiblity with 2.x


if (n == 'tablecontrols') {

each(["table","|","row_before","row_after","delete_row","|","col_before","col_after
","delete_col","|","split_cells","merge_cells"], function(n) {
n = t.createControl(n, cf);

if (n)
tb.add(n);
});

return;
}

c = t.createControl(n, cf);

if (c)
tb.add(c);
});
},

_addToolbars : function(c, o) {
var t = this, i, tb, ed = t.editor, s = t.settings, v, cf =
ed.controlManager, di, n, h = [], a, toolbarGroup, toolbarsExist = false;

toolbarGroup = cf.createToolbarGroup('toolbargroup', {
'name': ed.getLang('advanced.toolbar'),
'tab_focus_toolbar':ed.getParam('theme_advanced_tab_focus_t
oolbar')
});

t.toolbarGroup = toolbarGroup;

a = s.theme_advanced_toolbar_align.toLowerCase();
a = 'mce' + t._ufirst(a);

n = DOM.add(DOM.add(c, 'tr', {role: 'presentation'}), 'td',


{'class' : 'mceToolbar ' + a, "role":"toolbar"});
// Create toolbar and add the controls
for (i=1; (v = s['theme_advanced_buttons' + i]); i++) {
toolbarsExist = true;
tb = cf.createToolbar("toolbar" + i, {'class' :
'mceToolbarRow' + i});

if (s['theme_advanced_buttons' + i + '_add'])
v += ',' + s['theme_advanced_buttons' + i + '_add'];

if (s['theme_advanced_buttons' + i + '_add_before'])
v = s['theme_advanced_buttons' + i + '_add_before'] +
',' + v;

t._addControls(v, tb);
toolbarGroup.add(tb);

o.deltaHeight -= s.theme_advanced_row_height;
}
// Handle case when there are no toolbar buttons and ensure
editor height is adjusted accordingly
if (!toolbarsExist)
o.deltaHeight -= s.theme_advanced_row_height;
h.push(toolbarGroup.renderHTML());
h.push(DOM.createHTML('a', {href : '#', accesskey : 'z', title :
ed.getLang("advanced.toolbar_focus"), onfocus : 'tinyMCE.getInstanceById(\'' +
ed.id + '\').focus();'}, '<!-- IE -->'));
DOM.setHTML(n, h.join(''));
},

_addStatusBar : function(tb, o) {
var n, t = this, ed = t.editor, s = t.settings, r, mf, me, td;

n = DOM.add(tb, 'tr');
n = td = DOM.add(n, 'td', {'class' : 'mceStatusbar'});
n = DOM.add(n, 'div', {id : ed.id + '_path_row', 'role': 'group',
'aria-labelledby': ed.id + '_path_voice'});
if (s.theme_advanced_path) {
DOM.add(n, 'span', {id: ed.id + '_path_voice'},
ed.translate('advanced.path'));
DOM.add(n, 'span', {}, ': ');
} else {
DOM.add(n, 'span', {}, '&#160;');
}

if (s.theme_advanced_resizing) {
DOM.add(td, 'a', {id : ed.id + '_resize', href :
'javascript:;', onclick : "return false;", 'class' : 'mceResize', tabIndex:"-1"});

if (s.theme_advanced_resizing_use_cookie) {
ed.onPostRender.add(function() {
var o = Cookie.getHash("TinyMCE_" + ed.id +
"_size"), c = DOM.get(ed.id + '_tbl');

if (!o)
return;

t.resizeTo(o.cw, o.ch);
});
}

ed.onPostRender.add(function() {
Event.add(ed.id + '_resize', 'click', function(e) {
e.preventDefault();
});

Event.add(ed.id + '_resize', 'mousedown', function(e)


{
var mouseMoveHandler1, mouseMoveHandler2,
mouseUpHandler1, mouseUpHandler2,
startX, startY, startWidth, startHeight,
width, height, ifrElm;

function resizeOnMove(e) {
e.preventDefault();

width = startWidth + (e.screenX -


startX);
height = startHeight + (e.screenY -
startY);

t.resizeTo(width, height);
};

function endResize(e) {
// Stop listening
Event.remove(DOM.doc, 'mousemove',
mouseMoveHandler1);
Event.remove(ed.getDoc(), 'mousemove',
mouseMoveHandler2);
Event.remove(DOM.doc, 'mouseup',
mouseUpHandler1);
Event.remove(ed.getDoc(), 'mouseup',
mouseUpHandler2);

width = startWidth + (e.screenX -


startX);
height = startHeight + (e.screenY -
startY);
t.resizeTo(width, height, true);

ed.nodeChanged();
};

e.preventDefault();

// Get the current rect size


startX = e.screenX;
startY = e.screenY;
ifrElm = DOM.get(t.editor.id + '_ifr');
startWidth = width = ifrElm.clientWidth;
startHeight = height = ifrElm.clientHeight;

// Register envent handlers


mouseMoveHandler1 = Event.add(DOM.doc,
'mousemove', resizeOnMove);
mouseMoveHandler2 = Event.add(ed.getDoc(),
'mousemove', resizeOnMove);
mouseUpHandler1 = Event.add(DOM.doc, 'mouseup',
endResize);
mouseUpHandler2 = Event.add(ed.getDoc(),
'mouseup', endResize);
});
});
}

o.deltaHeight -= 21;
n = tb = null;
},

_updateUndoStatus : function(ed) {
var cm = ed.controlManager, um = ed.undoManager;

cm.setDisabled('undo', !um.hasUndo() && !um.typing);


cm.setDisabled('redo', !um.hasRedo());
},

_nodeChanged : function(ed, cm, n, co, ob) {


var t = this, p, de = 0, v, c, s = t.settings, cl, fz, fn, fc,
bc, formatNames, matches;

tinymce.each(t.stateControls, function(c) {
cm.setActive(c, ed.queryCommandState(t.controls[c][1]));
});

function getParent(name) {
var i, parents = ob.parents, func = name;

if (typeof(name) == 'string') {
func = function(node) {
return node.nodeName == name;
};
}

for (i = 0; i < parents.length; i++) {


if (func(parents[i]))
return parents[i];
}
};

cm.setActive('visualaid', ed.hasVisual);
t._updateUndoStatus(ed);
cm.setDisabled('outdent', !ed.queryCommandState('Outdent'));

p = getParent('A');
if (c = cm.get('link')) {
c.setDisabled((!p && co) || (p && !p.href));
c.setActive(!!p && (!p.name && !p.id));
}

if (c = cm.get('unlink')) {
c.setDisabled(!p && co);
c.setActive(!!p && !p.name && !p.id);
}

if (c = cm.get('anchor')) {
c.setActive(!co && !!p && (p.name || (p.id && !p.href)));
}

p = getParent('IMG');
if (c = cm.get('image'))
c.setActive(!co && !!p && n.className.indexOf('mceItem') ==
-1);

if (c = cm.get('styleselect')) {
t._importClasses();

formatNames = [];
each(c.items, function(item) {
formatNames.push(item.value);
});

matches = ed.formatter.matchAll(formatNames);
c.select(matches[0]);
tinymce.each(matches, function(match, index) {
if (index > 0) {
c.mark(match);
}
});
}

if (c = cm.get('formatselect')) {
p = getParent(ed.dom.isBlock);

if (p)
c.select(p.nodeName.toLowerCase());
}

// Find out current fontSize, fontFamily and fontClass


getParent(function(n) {
if (n.nodeName === 'SPAN') {
if (!cl && n.className)
cl = n.className;
}

if (ed.dom.is(n, s.theme_advanced_font_selector)) {
if (!fz && n.style.fontSize)
fz = n.style.fontSize;

if (!fn && n.style.fontFamily)


fn = n.style.fontFamily.replace(/[\"\']+/g,
'').replace(/^([^,]+).*/, '$1').toLowerCase();

if (!fc && n.style.color)


fc = n.style.color;

if (!bc && n.style.backgroundColor)


bc = n.style.backgroundColor;
}

return false;
});

if (c = cm.get('fontselect')) {
c.select(function(v) {
return v.replace(/^([^,]+).*/, '$1').toLowerCase() ==
fn;
});
}

// Select font size


if (c = cm.get('fontsizeselect')) {
// Use computed style
if (s.theme_advanced_runtime_fontsize && !fz && !cl)
fz = ed.dom.getStyle(n, 'fontSize', true);

c.select(function(v) {
if (v.fontSize && v.fontSize === fz)
return true;

if (v['class'] && v['class'] === cl)


return true;
});
}

if (s.theme_advanced_show_current_color) {
function updateColor(controlId, color) {
if (c = cm.get(controlId)) {
if (!color)
color = c.settings.default_color;
if (color !== c.value) {
c.displayColor(color);
}
}
}
updateColor('forecolor', fc);
updateColor('backcolor', bc);
}

if (s.theme_advanced_show_current_color) {
function updateColor(controlId, color) {
if (c = cm.get(controlId)) {
if (!color)
color = c.settings.default_color;
if (color !== c.value) {
c.displayColor(color);
}
}
};

updateColor('forecolor', fc);
updateColor('backcolor', bc);
}

if (s.theme_advanced_path && s.theme_advanced_statusbar_location)


{
p = DOM.get(ed.id + '_path') || DOM.add(ed.id +
'_path_row', 'span', {id : ed.id + '_path'});

if (t.statusKeyboardNavigation) {
t.statusKeyboardNavigation.destroy();
t.statusKeyboardNavigation = null;
}

DOM.setHTML(p, '');
getParent(function(n) {
var na = n.nodeName.toLowerCase(), u, pi, ti = '';

// Ignore non element and bogus/hidden elements


if (n.nodeType != 1 || na === 'br' ||
n.getAttribute('data-mce-bogus') || DOM.hasClass(n, 'mceItemHidden') ||
DOM.hasClass(n, 'mceItemRemoved'))
return;

// Handle prefix
if (tinymce.isIE && n.scopeName !== 'HTML' &&
n.scopeName)
na = n.scopeName + ':' + na;

// Remove internal prefix


na = na.replace(/mce\:/g, '');

// Handle node name


switch (na) {
case 'b':
na = 'strong';
break;

case 'i':
na = 'em';
break;

case 'img':
if (v = DOM.getAttrib(n, 'src'))
ti += 'src: ' + v + ' ';

break;

case 'a':
if (v = DOM.getAttrib(n, 'name')) {
ti += 'name: ' + v + ' ';
na += '#' + v;
}

if (v = DOM.getAttrib(n, 'href'))
ti += 'href: ' + v + ' ';

break;

case 'font':
if (v = DOM.getAttrib(n, 'face'))
ti += 'font: ' + v + ' ';

if (v = DOM.getAttrib(n, 'size'))
ti += 'size: ' + v + ' ';

if (v = DOM.getAttrib(n, 'color'))
ti += 'color: ' + v + ' ';

break;

case 'span':
if (v = DOM.getAttrib(n, 'style'))
ti += 'style: ' + v + ' ';

break;
}

if (v = DOM.getAttrib(n, 'id'))
ti += 'id: ' + v + ' ';

if (v = n.className) {
v = v.replace(/\b\s*(webkit|mce|
Apple-)\w+\s*\b/g, '');

if (v) {
ti += 'class: ' + v + ' ';

if (ed.dom.isBlock(n) || na == 'img' ||
na == 'span')
na += '.' + v;
}
}

na = na.replace(/(html:)/g, '');
na = {name : na, node : n, title : ti};
t.onResolveName.dispatch(t, na);
ti = na.title;
na = na.name;

//u = "javascript:tinymce.EditorManager.get('" +
ed.id + "').theme._sel('" + (de++) + "');";
pi = DOM.create('a', {'href' : "javascript:;", role:
'button', onmousedown : "return false;", title : ti, 'class' : 'mcePath_' + (de+
+)}, na);

if (p.hasChildNodes()) {
p.insertBefore(DOM.create('span', {'aria-
hidden': 'true'}, '\u00a0\u00bb '), p.firstChild);
p.insertBefore(pi, p.firstChild);
} else
p.appendChild(pi);
}, ed.getBody());

if (DOM.select('a', p).length > 0) {


t.statusKeyboardNavigation = new
tinymce.ui.KeyboardNavigation({
root: ed.id + "_path_row",
items: DOM.select('a', p),
excludeFromTabOrder: true,
onCancel: function() {
ed.focus();
}
}, DOM);
}
}
},

// Commands gets called by execCommand

_sel : function(v) {
this.editor.execCommand('mceSelectNodeDepth', false, v);
},

_makeUrl: function(filename){
var url = this.url + '/' + filename;
if(this.settings.cache_string){
url += (url.indexOf('?') >= 0 ? '&' : '?') + 'cache=' +
this.settings.cache_string;
}
return url;
},

_mceInsertAnchor : function(ui, v) {
var ed = this.editor;

ed.windowManager.open({
url : this._makeUrl('anchor.htm'),
width : 320 +
parseInt(ed.getLang('advanced.anchor_delta_width', 0)),
height : 90 +
parseInt(ed.getLang('advanced.anchor_delta_height', 0)),
inline : true
}, {
theme_url : this.url
});
},

_mceCharMap : function() {
var ed = this.editor;

ed.windowManager.open({
url : this._makeUrl('charmap.htm'),
width : 613 +
parseInt(ed.getLang('advanced.charmap_delta_width', 0)),
height : 320 +
parseInt(ed.getLang('advanced.charmap_delta_height', 0)),
inline : true
}, {
theme_url : this.url
});
},

_mceHelp : function() {
var ed = this.editor;

ed.windowManager.open({
url : this._makeUrl('about.htm'),
width : 480,
height : 380,
inline : true
}, {
theme_url : this.url
});
},

_mceShortcuts : function() {
var ed = this.editor;
ed.windowManager.open({
url : this._makeUrl('shortcuts.htm'),
width: 480,
height: 380,
inline: true
}, {
theme_url: this.url
});
},

_mceColorPicker : function(u, v) {
var ed = this.editor;

v = v || {};

ed.windowManager.open({
url : this._makeUrl('color_picker.htm'),
width : 360 +
parseInt(ed.getLang('advanced.colorpicker_delta_width', 0)),
height : 260 +
parseInt(ed.getLang('advanced.colorpicker_delta_height', 0)),
close_previous : false,
inline : true
}, {
input_color : v.color,
func : v.func,
theme_url : this.url
});
},

_mceCodeEditor : function(ui, val) {


var ed = this.editor;

ed.windowManager.open({
url : this._makeUrl('source_editor.htm'),
width :
parseInt(ed.getParam("theme_advanced_source_editor_width", 720)),
height :
parseInt(ed.getParam("theme_advanced_source_editor_height", 580)),
inline : true,
resizable : true,
maximizable : true
}, {
theme_url : this.url
});
},

_mceImage : function(ui, val) {


var ed = this.editor;

// Internal image object like a flash placeholder


if (ed.dom.getAttrib(ed.selection.getNode(), 'class',
'').indexOf('mceItem') != -1)
return;

ed.windowManager.open({
url : this._makeUrl('image.htm'),
width : 400 +
parseInt(ed.getLang('advanced.image_delta_width', 0)),
height : 250 +
parseInt(ed.getLang('advanced.image_delta_height', 0)),
inline : true
}, {
theme_url : this.url
});
},

_mceLink : function(ui, val) {


var ed = this.editor;

ed.windowManager.open({
url : this._makeUrl('link.htm'),
width : 400 +
parseInt(ed.getLang('advanced.link_delta_width', 0)),
height : 160 +
parseInt(ed.getLang('advanced.link_delta_height', 0)),
inline : true
}, {
theme_url : this.url
});
},

_mceNewDocument : function() {
var ed = this.editor;

ed.windowManager.confirm('advanced.newdocument', function(s) {
if (s)
ed.execCommand('mceSetContent', false, '');
});
},

_mceForeColor : function() {
var t = this;

this._mceColorPicker(0, {
color: t.fgColor,
func : function(co) {
t.fgColor = co;
t.editor.execCommand('ForeColor', false, co);
}
});
},

_mceBackColor : function() {
var t = this;

this._mceColorPicker(0, {
color: t.bgColor,
func : function(co) {
t.bgColor = co;
t.editor.execCommand('HiliteColor', false, co);
}
});
},

_ufirst : function(s) {
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
});

tinymce.ThemeManager.add('advanced', tinymce.themes.AdvancedTheme);
}(tinymce));
;tinyMCE.addI18n('es.advanced',{underline_desc:"Subrayado
(Ctrl+U)",italic_desc:"Cursiva (Ctrl+I)",bold_desc:"Negrita
(Ctrl+B)",dd:"Descripci\u00f3n de definici\u00f3n",dt:"T\u00e9rmino de
definici\u00f3n",samp:"Ejemplo de
c\u00f3digo",code:"C\u00f3digo",blockquote:"Cita",h6:"Encabezado 6",h5:"Encabezado
5",h4:"Encabezado 4",h3:"Encabezado 3",h2:"Encabezado 2",h1:"Encabezado
1",pre:"Preformateado",address:"Direcci\u00f3n",div:"Div",paragraph:"P\u00e1rrafo",
block:"P\u00e1rrafo",fontdefault:"Fuente",font_size:"12",style_select:"Estilos",mor
e_colors:"M\u00e1s colores",toolbar_focus:"Ir a los botones de herramientas -
Alt+Q, Ir al editor - Alt-Z, Ir a la ruta del elemento - Alt-X",newdocument:"
\u00bfSeguro que desea limpiar todo el
contenido?",path:"Ruta",clipboard_msg:"Copiar/Cortar/Pegar no se encuentra
disponible en Mozilla y Firefox.\\n \u00bfQuiere m\u00e1s informaci\u00f3n sobre
este tema?",blockquote_desc:"Cita",help_desc:"Ayuda",newdocument_desc:"Nuevo
documento",image_props_desc:"Propiedades de
imagen",paste_desc:"Pegar",copy_desc:"Copiar",cut_desc:"Cortar",anchor_desc:"Insert
ar/editar ancla",visualaid_desc:"Mostrar/ocultar l\u00ednea de gu\u00eda/elementos
invisibles",charmap_desc:"Insertar caracteres
personalizados",backcolor_desc:"Elegir color de fondo",forecolor_desc:"Elegir color
del texto",custom1_desc:"Su descripci\u00f3n personal
aqu\u00ed",removeformat_desc:"Limpiar formato",hr_desc:"Insertar regla
horizontal",sup_desc:"Super\u00edndice",sub_desc:"Sub\u00edndice",code_desc:"Editar
c\u00f3digo HTML",cleanup_desc:"Limpiar c\u00f3digo
basura",image_desc:"Insertar/editar imagen",unlink_desc:"Quitar
hiperv\u00ednculo",link_desc:"Insertar/editar hiperv\u00ednculo",redo_desc:"Rehacer
(Ctrl+Y)",undo_desc:"Deshacer (Ctrl+Z)",indent_desc:"Aumentar
sangr\u00eda",outdent_desc:"Reducir sangr\u00eda",numlist_desc:"Lista
ordenada",bullist_desc:"Lista
desordenada",justifyfull_desc:"Justificar",justifyright_desc:"Alinear a la
derecha",justifycenter_desc:"Alinear al centro",justifyleft_desc:"Alinear a la
izquierda",striketrough_desc:"Tachado",anchor_delta_height:"",anchor_delta_width:""
,charmap_delta_height:"",charmap_delta_width:"",colorpicker_delta_height:"",colorpi
cker_delta_width:"",link_delta_height:"",link_delta_width:"",image_delta_height:"",
image_delta_width:""});
;(function(){var
a=tinymce.util.JSONRequest,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plu
gins.SpellcheckerPlugin",{getInfo:function()
{return{longname:"Spellchecker",author:"Moxiecode Systems
AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/ind
ex.php/TinyMCE:Plugins/spellchecker",version:tinymce.majorVersion+"."+tinymce.minor
Version}},init:function(e,f){var
g=this,d;g.url=f;g.editor=e;g.rpcUrl=e.getParam("spellchecker_rpc_url","{backend}")
;if(g.rpcUrl=="{backend}"){if(tinymce.isIE)
{return}g.hasSupport=true;e.onContextMenu.addToTop(function(h,i){if(g.active)
{return false}})}e.addCommand("mceSpellCheck",function(){if(g.rpcUrl=="{backend}")
{g.editor.getBody().spellcheck=g.active=!g.active;return}if(!g.active)
{e.setProgressState(1);g._sendRPC("checkWords",
[g.selectedLang,g._getWords()],function(h){if(h.length>0)
{g.active=1;g._markWords(h);e.setProgressState(0);e.nodeChanged()}else{e.setProgres
sState(0);if(e.getParam("spellchecker_report_no_misspellings",true))
{e.windowManager.alert("spellchecker.no_mpell")}}})}else{g._done()}});if(e.settings
.content_css!==false)
{e.contentCSS.push(f+"/css/content.css")}e.onClick.add(g._showMenu,g);e.onContextMe
nu.add(g._showMenu,g);e.onBeforeGetContent.add(function(){if(g.active)
{g._removeWords()}});e.onNodeChange.add(function(i,h)
{h.setActive("spellchecker",g.active)});e.onSetContent.add(function()
{g._done()});e.onBeforeGetContent.add(function()
{g._done()});e.onBeforeExecCommand.add(function(h,i){if(i=="mceFullScreen")
{g._done()}});g.languages={};c(e.getParam("spellchecker_languages","+English=en,Dan
ish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,S
panish=es,Swedish=sv","hash"),function(i,h){if(h.indexOf("+")===0)
{h=h.substring(1);g.selectedLang=i}g.languages[h]=i})},createControl:function(h,d)
{var f=this,g,e=f.editor;if(h=="spellchecker"){if(f.rpcUrl=="{backend}")
{if(f.hasSupport){g=d.createButton(h,
{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f})}return
g}g=d.createSplitButton(h,
{title:"spellchecker.desc",cmd:"mceSpellCheck",scope:f});g.onRenderMenu.add(functio
n(j,i)
{i.add({title:"spellchecker.langs","class":"mceMenuItemTitle"}).setDisabled(1);c(f.
languages,function(n,m){var p={icon:1},l;p.onclick=function(){if(n==f.selectedLang)
{return}l.setSelected(1);f.selectedItem.setSelected(0);f.selectedItem=l;f.selectedL
ang=n};p.title=m;l=i.add(p);l.setSelected(n==f.selectedLang);if(n==f.selectedLang)
{f.selectedItem=l}})});return g}},_walk:function(i,g){var
h=this.editor.getDoc(),e;if(h.createTreeWalker)
{e=h.createTreeWalker(i,NodeFilter.SHOW_TEXT,null,false);while((i=e.nextNode())!
=null)
{g.call(this,i)}}else{tinymce.walk(i,g,"childNodes")}},_getSeparators:function()
{var e="",d,f=this.editor.getParam("spellchecker_word_separator_chars",'\\s!"#$
%&()*+,-./:;<=>?@[]^_{|}���������������\u201d\u201c');for(d=0;d<f.length;d++)
{e+="\\"+f.charAt(d)}return e},_getWords:function(){var
e=this.editor,g=[],d="",f={},h=[];this._walk(e.getBody(),function(i)
{if(i.nodeType==3){d+=i.nodeValue+"
"}});if(e.getParam("spellchecker_word_pattern"))
{h=d.match("("+e.getParam("spellchecker_word_pattern")
+")","gi")}else{d=d.replace(new RegExp("([0-9]|["+this._getSeparators()+"])","g"),"
");d=tinymce.trim(d.replace(/(\s+)/g," "));h=d.split(" ")}c(h,function(i){if(!f[i])
{g.push(i);f[i]=1}});return g},_removeWords:function(e){var
f=this.editor,h=f.dom,g=f.selection,d=g.getBookmark();c(h.select("span").reverse(),
function(i){if(i&&(h.hasClass(i,"mceItemHiddenSpellWord")||
h.hasClass(i,"mceItemHidden"))){if(!e||h.decode(i.innerHTML)==e)
{h.remove(i,1)}}});g.moveToBookmark(d)},_markWords:function(l){var
g=this.editor,f=g.dom,j=g.getDoc(),h=g.selection,i=h.getBookmark(),d=[],k=l.join("|
"),m=this._getSeparators(),e=new RegExp("(^|["+m+"])("+k+")(?=["+m+"]|
$)","g");this._walk(g.getBody(),function(o){if(o.nodeType==3)
{d.push(o)}});c(d,function(t){var r,q,o,s,p=t.nodeValue;if(e.test(p))
{p=f.encode(p);q=f.create("span",{"class":"mceItemHidden"});if(tinymce.isIE)
{p=p.replace(e,"$1<mcespell>$2</mcespell>");while((s=p.indexOf("<mcespell>"))!=-1)
{o=p.substring(0,s);if(o.length)
{r=j.createTextNode(f.decode(o));q.appendChild(r)}p=p.substring(s+10);s=p.indexOf("
</mcespell>");o=p.substring(0,s);p=p.substring(s+11);q.appendChild(f.create("span",
{"class":"mceItemHiddenSpellWord"},o))}if(p.length)
{r=j.createTextNode(f.decode(p));q.appendChild(r)}}else{q.innerHTML=p.replace(e,'$1
<span
class="mceItemHiddenSpellWord">$2</span>')}f.replace(q,t)}});h.moveToBookmark(i)},_
showMenu:function(h,j){var
i=this,h=i.editor,d=i._menu,l,k=h.dom,g=k.getViewPort(h.getWin()),f=j.target;j=0;if
(!d){d=h.controlManager.createDropMenu("spellcheckermenu",
{"class":"mceNoIcons"});i._menu=d}if(k.hasClass(f,"mceItemHiddenSpellWord"))
{d.removeAll();d.add({title:"spellchecker.wait","class":"mceMenuItemTitle"}).setDis
abled(1);i._sendRPC("getSuggestions",
[i.selectedLang,k.decode(f.innerHTML)],function(m){var
e;d.removeAll();if(m.length>0)
{d.add({title:"spellchecker.sug","class":"mceMenuItemTitle"}).setDisabled(1);c(m,fu
nction(n){d.add({title:n,onclick:function()
{k.replace(h.getDoc().createTextNode(n),f);i._checkDone()}})});d.addSeparator()}els
e{d.add({title:"spellchecker.no_sug","class":"mceMenuItemTitle"}).setDisabled(1)}e=
i.editor.getParam("spellchecker_enable_ignore_rpc","");d.add({title:"spellchecker.i
gnore_word",onclick:function(){var n=f.innerHTML;k.remove(f,1);i._checkDone();if(e)
{h.setProgressState(1);i._sendRPC("ignoreWord",[i.selectedLang,n],function(o)
{h.setProgressState(0)})}}});d.add({title:"spellchecker.ignore_words",onclick:funct
ion(){var n=f.innerHTML;i._removeWords(k.decode(n));i._checkDone();if(e)
{h.setProgressState(1);i._sendRPC("ignoreWords",[i.selectedLang,n],function(o)
{h.setProgressState(0)})}}});if(i.editor.getParam("spellchecker_enable_learn_rpc"))
{d.add({title:"spellchecker.learn_word",onclick:function(){var
n=f.innerHTML;k.remove(f,1);i._checkDone();h.setProgressState(1);i._sendRPC("learnW
ord",[i.selectedLang,n],function(o)
{h.setProgressState(0)})}})}d.update()});l=k.getPos(h.getContentAreaContainer());d.
settings.offset_x=l.x;d.settings.offset_y=l.y;h.selection.select(f);l=k.getPos(f);d
.showMenu(l.x,l.y+f.offsetHeight-g.y);return
tinymce.dom.Event.cancel(j)}else{d.hideMenu()}},_checkDone:function(){var
e=this,d=e.editor,g=d.dom,f;c(g.select("span"),function(h)
{if(h&&g.hasClass(h,"mceItemHiddenSpellWord")){f=true;return false}});if(!f)
{e._done()}},_done:function(){var d=this,e=d.active;if(d.active)
{d.active=0;d._removeWords();if(d._menu){d._menu.hideMenu()}if(e)
{d.editor.nodeChanged()}}},_sendRPC:function(e,g,d){var
f=this;a.sendRPC({url:f.rpcUrl,method:e,params:g,success:d,error:function(i,h)
{f.editor.setProgressState(0);f.editor.windowManager.alert(i.errstr||("Error
response:
"+h.responseText))}})}});tinymce.PluginManager.add("spellchecker",tinymce.plugins.S
pellcheckerPlugin)})();;(function(c){var d=c.each;function b(f,g){var
h=g.ownerDocument,e=h.createRange(),j;e.setStartBefore(g);e.setEnd(f.endContainer,f
.endOffset);j=h.createElement("body");j.appendChild(e.cloneContents());return
j.innerHTML.replace(/<(br|img|object|embed|input|textarea)
[^>]*>/gi,"-").replace(/<[^>]+>/g,"").length==0}function a(H,G,K){var
f,L,D,o;t();o=G.getParent(K.getStart(),"th,td");if(o)
{L=F(o);D=I();o=z(L.x,L.y)}function A(N,M)
{N=N.cloneNode(M);N.removeAttribute("id");return N}function t(){var
M=0;f=[];d(["thead","tbody","tfoot"],function(N){var O=G.select("> "+N+"
tr",H);d(O,function(P,Q){Q+=M;d(G.select("> td, > th",P),function(W,R){var
S,T,U,V;if(f[Q]){while(f[Q][R]){R+
+}}U=h(W,"rowspan");V=h(W,"colspan");for(T=Q;T<Q+U;T++){if(!f[T])
{f[T]=[]}for(S=R;S<R+V;S++){f[T]
[S]={part:N,real:T==Q&&S==R,elm:W,rowspan:U,colspan:V}}}})});M+=O.length})}function
z(M,O){var N;N=f[O];if(N){return N[M]}}function h(N,M){return
parseInt(N.getAttribute(M)||1)}function s(O,M,N){if(O){N=parseInt(N);if(N===1)
{O.removeAttribute(M,1)}else{O.setAttribute(M,N,1)}}}function j(M){return
M&&(G.hasClass(M.elm,"mceSelected")||M==o)}function k(){var
M=[];d(H.rows,function(N){d(N.cells,function(O){if(G.hasClass(O,"mceSelected")||
O==o.elm){M.push(N);return false}})});return M}function r(){var
M=G.createRng();M.setStartAfter(H);M.setEndAfter(H);K.setRng(M);G.remove(H)}functio
n e(M){var N;c.walk(M,function(P){var O;if(P.nodeType==3)
{d(G.getParents(P.parentNode,null,M).reverse(),function(Q){Q=A(Q,false);if(!N)
{N=O=Q}else{if(O){O.appendChild(Q)}}O=Q});if(O){O.innerHTML=c.isIE?"&nbsp;":'<br
data-mce-bogus="1" />'}return
false}},"childNodes");M=A(M,false);s(M,"rowSpan",1);s(M,"colSpan",1);if(N)
{M.appendChild(N)}else{if(!c.isIE){M.innerHTML='<br data-mce-bogus="1" />'}}return
M}function q(){var M=G.createRng();d(G.select("tr",H),function(N)
{if(N.cells.length==0){G.remove(N)}});if(G.select("tr",H).length==0)
{M.setStartAfter(H);M.setEndAfter(H);K.setRng(M);G.remove(H);return}d(G.select("the
ad,tbody,tfoot",H),function(N){if(N.rows.length==0)
{G.remove(N)}});t();row=f[Math.min(f.length-1,L.y)];if(row)
{K.select(row[Math.min(row.length-1,L.x)].elm,true);K.collapse(true)}}function
u(S,Q,U,R){var P,N,M,O,T;P=f[Q][S].elm.parentNode;for(M=1;M<=U;M++)
{P=G.getNext(P,"tr");if(P){for(N=S;N>=0;N--){T=f[Q+M][N].elm;if(T.parentNode==P)
{for(O=1;O<=R;O++){G.insertAfter(e(T),T)}break}}if(N==-1){for(O=1;O<=R;O++)
{P.insertBefore(e(P.cells[0]),P.cells[0])}}}}}function C(){d(f,function(M,N)
{d(M,function(P,O){var S,R,T,Q;if(j(P))
{P=P.elm;S=h(P,"colspan");R=h(P,"rowspan");if(S>1||R>1)
{s(P,"rowSpan",1);s(P,"colSpan",1);for(Q=0;Q<S-1;Q++)
{G.insertAfter(e(P),P)}u(O,N,R-1,S)}}})})}function p(V,S,Y){var
P,O,X,W,U,R,T,M,V,N,Q;if(V){pos=F(V);P=pos.x;O=pos.y;X=P+(S-1);W=O+(Y-
1)}else{P=L.x;O=L.y;X=D.x;W=D.y}T=z(P,O);M=z(X,W);if(T&&M&&T.part==M.part)
{C();t();T=z(P,O).elm;s(T,"colSpan",(X-P)+1);s(T,"rowSpan",(W-O)+1);for(R=O;R<=W;R+
+){for(U=P;U<=X;U++){if(!f[R]||!f[R][U]){continue}V=f[R][U].elm;if(V!=T)
{N=c.grep(V.childNodes);d(N,function(Z){T.appendChild(Z)});if(N.length)
{N=c.grep(T.childNodes);Q=0;d(N,function(Z)
{if(Z.nodeName=="BR"&&G.getAttrib(Z,"data-mce-bogus")
&&Q++<N.length-1){T.removeChild(Z)}})}G.remove(V)}}}q()}}function l(Q){var
M,S,P,R,T,U,N,V,O;d(f,function(W,X){d(W,function(Z,Y){if(j(Z))
{Z=Z.elm;T=Z.parentNode;U=A(T,false);M=X;if(Q){return false}}});if(Q){return !
M}});for(R=0;R<f[0].length;R++){if(!f[M][R]){continue}S=f[M][R].elm;if(S!=P){if(!Q)
{O=h(S,"rowspan");if(O>1){s(S,"rowSpan",O+1);continue}}else{if(M>0&&f[M-1][R])
{V=f[M-1][R].elm;O=h(V,"rowSpan");if(O>1)
{s(V,"rowSpan",O+1);continue}}}N=e(S);s(N,"colSpan",S.colSpan);U.appendChild(N);P=S
}}if(U.hasChildNodes()){if(!Q)
{G.insertAfter(U,T)}else{T.parentNode.insertBefore(U,T)}}}function g(N){var
O,M;d(f,function(P,Q){d(P,function(S,R){if(j(S)){O=R;if(N){return false}}});if(N)
{return !O}});d(f,function(S,T){var P,Q,R;if(!S[O]){return}P=S[O].elm;if(P!=M)
{R=h(P,"colspan");Q=h(P,"rowspan");if(R==1){if(!N){G.insertAfter(e(P),P);u(O,T,Q-
1,R)}else{P.parentNode.insertBefore(e(P),P);u(O,T,Q-
1,R)}}else{s(P,"colSpan",P.colSpan+1)}M=P}})}function n(){var
M=[];d(f,function(N,O){d(N,function(Q,P){if(j(Q)&&c.inArray(M,P)===-1)
{d(f,function(T){var R=T[P].elm,S;S=h(R,"colSpan");if(S>1){s(R,"colSpan",S-
1)}else{G.remove(R)}});M.push(P)}})});q()}function m(){var N;function M(Q){var
P,R,O;P=G.getNext(Q,"tr");d(Q.cells,function(S){var T=h(S,"rowSpan");if(T>1)
{s(S,"rowSpan",T-1);R=F(S);u(R.x,R.y,1,1)}});R=F(Q.cells[0]);d(f[R.y],function(S)
{var T;S=S.elm;if(S!=O){T=h(S,"rowSpan");if(T<=1){G.remove(S)}else{s(S,"rowSpan",T-
1)}O=S}})}N=k();d(N.reverse(),function(O){M(O)});q()}function E(){var
M=k();G.remove(M);q();return M}function J(){var M=k();d(M,function(O,N)
{M[N]=A(O,true)});return M}function B(O,N){var P=k(),M=P[N?0:P.length-
1],Q=M.cells.length;d(f,function(S){var R;Q=0;d(S,function(U,T){if(U.real)
{Q+=U.colspan}if(U.elm.parentNode==M){R=1}});if(R){return false}});if(!N)
{O.reverse()}d(O,function(T){var S=T.cells.length,R;for(i=0;i<S;i++)
{R=T.cells[i];s(R,"colSpan",1);s(R,"rowSpan",1)}for(i=S;i<Q;i++)
{T.appendChild(e(T.cells[S-1]))}for(i=Q;i<S;i++){G.remove(T.cells[i])}if(N)
{M.parentNode.insertBefore(T,M)}else{G.insertAfter(T,M)}})}function F(M){var
N;d(f,function(O,P){d(O,function(R,Q){if(R.elm==M){N={x:Q,y:P};return
false}});return !N});return N}function w(M){L=F(M)}function I(){var
O,N,M;N=M=0;d(f,function(P,Q){d(P,function(S,R){var U,T;if(j(S)){S=f[Q][R];if(R>N)
{N=R}if(Q>M){M=Q}if(S.real){U=S.colspan-1;T=S.rowspan-1;if(U){if(R+U>N)
{N=R+U}}if(T){if(Q+T>M){M=Q+T}}}}})});return{x:N,y:M}}function v(S){var
P,O,U,T,N,M,Q,R;D=F(S);if(L&&D)
{P=Math.min(L.x,D.x);O=Math.min(L.y,D.y);U=Math.max(L.x,D.x);T=Math.max(L.y,D.y);N=
U;M=T;for(y=O;y<=M;y++){S=f[y][P];if(!S.real){if(P-(S.colspan-1)<P){P-=S.colspan-
1}}}for(x=P;x<=N;x++){S=f[O][x];if(!S.real){if(O-(S.rowspan-1)<O){O-=S.rowspan-
1}}}for(y=O;y<=T;y++){for(x=P;x<=U;x++){S=f[y][x];if(S.real){Q=S.colspan-
1;R=S.rowspan-1;if(Q){if(x+Q>N){N=x+Q}}if(R){if(y+R>M)
{M=y+R}}}}}G.removeClass(G.select("td.mceSelected,th.mceSelected"),"mceSelected");f
or(y=O;y<=M;y++){for(x=P;x<=N;x++){if(f[y][x]){G.addClass(f[y]
[x].elm,"mceSelected")}}}}}c.extend(this,
{deleteTable:r,split:C,merge:p,insertRow:l,insertCol:g,deleteCols:n,deleteRows:m,cu
tRows:E,copyRows:J,pasteRows:B,getPos:F,setStartCell:w,setEndCell:v})}c.create("tin
ymce.plugins.TablePlugin",{init:function(f,g){var e,k;function j(n){var
m=f.selection,l=f.dom.getParent(n||m.getNode(),"table");if(l){return new
a(l,f.dom,m)}}function h()
{f.getBody().style.webkitUserSelect="";f.dom.removeClass(f.dom.select("td.mceSelect
ed,th.mceSelected"),"mceSelected")}d([["table","table.desc","mceInsertTable",true],
["delete_table","table.del","mceTableDelete"],
["delete_col","table.delete_col_desc","mceTableDeleteCol"],
["delete_row","table.delete_row_desc","mceTableDeleteRow"],
["col_after","table.col_after_desc","mceTableInsertColAfter"],
["col_before","table.col_before_desc","mceTableInsertColBefore"],
["row_after","table.row_after_desc","mceTableInsertRowAfter"],
["row_before","table.row_before_desc","mceTableInsertRowBefore"],
["row_props","table.row_desc","mceTableRowProps",true],
["cell_props","table.cell_desc","mceTableCellProps",true],
["split_cells","table.split_cells_desc","mceTableSplitCells",true],
["merge_cells","table.merge_cells_desc","mceTableMergeCells",true]],function(l)
{f.addButton(l[0],{title:l[1],cmd:l[2],ui:l[3]})});if(!c.isIE)
{f.onClick.add(function(l,m){m=m.target;if(m.nodeName==="TABLE")
{l.selection.select(m);l.nodeChanged()}})}f.onPreProcess.add(function(m,n){var
l,o,p,r=m.dom,q;l=r.select("table",n.node);o=l.length;while(o--)
{p=l[o];r.setAttrib(p,"data-mce-style","");if((q=r.getAttrib(p,"width")))
{r.setStyle(p,"width",q);r.setAttrib(p,"width","")}if((q=r.getAttrib(p,"height")))
{r.setStyle(p,"height",q);r.setAttrib(p,"height","")}}});f.onNodeChange.add(functio
n(m,l,q){var
o;q=m.selection.getStart();o=m.dom.getParent(q,"td,th,caption");l.setActive("table"
,q.nodeName==="TABLE"||!!o);if(o&&o.nodeName==="CAPTION")
{o=0}l.setDisabled("delete_table",!o);l.setDisabled("delete_col",!
o);l.setDisabled("delete_table",!o);l.setDisabled("delete_row",!
o);l.setDisabled("col_after",!o);l.setDisabled("col_before",!
o);l.setDisabled("row_after",!o);l.setDisabled("row_before",!
o);l.setDisabled("row_props",!o);l.setDisabled("cell_props",!
o);l.setDisabled("split_cells",!o);l.setDisabled("merge_cells",!
o)});f.onInit.add(function(m){var
l,p,q=m.dom,n;e=m.windowManager;m.onMouseDown.add(function(r,s){if(s.button!=2)
{h();p=q.getParent(s.target,"td,th");l=q.getParent(p,"table")}});q.bind(m.getDoc(),
"mouseover",function(v){var t,s,u=v.target;if(p&&(n||u!=p)&&(u.nodeName=="TD"||
u.nodeName=="TH")){s=q.getParent(u,"table");if(s==l){if(!n)
{n=j(s);n.setStartCell(p);m.getBody().style.webkitUserSelect="none"}n.setEndCell(u)
}t=m.selection.getSel();try{if(t.removeAllRanges)
{t.removeAllRanges()}else{t.empty()}}catch(r)
{}v.preventDefault()}});m.onMouseUp.add(function(A,B){var
s,u=A.selection,C,D=u.getSel(),r,v,t,z;if(p){if(n)
{A.getBody().style.webkitUserSelect=""}function w(E,G){var F=new
c.dom.TreeWalker(E,E);do{if(E.nodeType==3&&c.trim(E.nodeValue).length!=0){if(G)
{s.setStart(E,0)}else{s.setEnd(E,E.nodeValue.length)}return}if(E.nodeName=="BR")
{if(G){s.setStartBefore(E)}else{s.setEndBefore(E)}return}}while(E=(G?
F.next():F.prev()))}C=q.select("td.mceSelected,th.mceSelected");if(C.length>0)
{s=q.createRng();v=C[0];z=C[C.length-1];w(v,1);r=new
c.dom.TreeWalker(v,q.getParent(C[0],"table"));do{if(v.nodeName=="TD"||
v.nodeName=="TH"){if(!q.hasClass(v,"mceSelected"))
{break}t=v}}while(v=r.next());w(t);u.setRng(s)}A.nodeChanged();p=n=l=null}});m.onKe
yUp.add(function(r,s){h()});if(m&&m.plugins.contextmenu)
{m.plugins.contextmenu.onContextMenu.add(function(t,r,v){var
w,u=m.selection,s=u.getNode()||m.getBody();if(m.dom.getParent(v,"td")||
m.dom.getParent(v,"th")||m.dom.select("td.mceSelected,th.mceSelected").length)
{r.removeAll();if(s.nodeName=="A"&&!m.dom.getAttrib(s,"name"))
{r.add({title:"advanced.link_desc",icon:"link",cmd:m.plugins.advlink?"mceAdvLink":"
mceLink",ui:true});r.add({title:"advanced.unlink_desc",icon:"unlink",cmd:"UnLink"})
;r.addSeparator()}if(s.nodeName=="IMG"&&s.className.indexOf("mceItem")==-1)
{r.add({title:"advanced.image_desc",icon:"image",cmd:m.plugins.advimage?"mceAdvImag
e":"mceImage",ui:true});r.addSeparator()}r.add({title:"table.desc",icon:"table",cmd
:"mceInsertTable",value:
{action:"insert"}});r.add({title:"table.props_desc",icon:"table_props",cmd:"mceInse
rtTable"});r.add({title:"table.del",icon:"delete_table",cmd:"mceTableDelete"});r.ad
dSeparator();w=r.addMenu({title:"table.cell"});w.add({title:"table.cell_desc",icon:
"cell_props",cmd:"mceTableCellProps"});w.add({title:"table.split_cells_desc",icon:"
split_cells",cmd:"mceTableSplitCells"});w.add({title:"table.merge_cells_desc",icon:
"merge_cells",cmd:"mceTableMergeCells"});w=r.addMenu({title:"table.row"});w.add({ti
tle:"table.row_desc",icon:"row_props",cmd:"mceTableRowProps"});w.add({title:"table.
row_before_desc",icon:"row_before",cmd:"mceTableInsertRowBefore"});w.add({title:"ta
ble.row_after_desc",icon:"row_after",cmd:"mceTableInsertRowAfter"});w.add({title:"t
able.delete_row_desc",icon:"delete_row",cmd:"mceTableDeleteRow"});w.addSeparator();
w.add({title:"table.cut_row_desc",icon:"cut",cmd:"mceTableCutRow"});w.add({title:"t
able.copy_row_desc",icon:"copy",cmd:"mceTableCopyRow"});w.add({title:"table.paste_r
ow_before_desc",icon:"paste",cmd:"mceTablePasteRowBefore"}).setDisabled(!
k);w.add({title:"table.paste_row_after_desc",icon:"paste",cmd:"mceTablePasteRowAfte
r"}).setDisabled(!
k);w=r.addMenu({title:"table.col"});w.add({title:"table.col_before_desc",icon:"col_
before",cmd:"mceTableInsertColBefore"});w.add({title:"table.col_after_desc",icon:"c
ol_after",cmd:"mceTableInsertColAfter"});w.add({title:"table.delete_col_desc",icon:
"delete_col",cmd:"mceTableDeleteCol"})}else{r.add({title:"table.desc",icon:"table",
cmd:"mceInsertTable"})}})}if(!c.isIE){function o(){var
r;for(r=m.getBody().lastChild;r&&r.nodeType==3&&!
r.nodeValue.length;r=r.previousSibling){}if(r&&r.nodeName=="TABLE")
{m.dom.add(m.getBody(),"p",null,'<br mce_bogus="1" />')}}if(c.isGecko)
{m.onKeyDown.add(function(s,u){var r,t,v=s.dom;if(u.keyCode==37||u.keyCode==38)
{r=s.selection.getRng();t=v.getParent(r.startContainer,"table");if(t&&s.getBody().f
irstChild==t){if(b(r,t))
{r=v.createRng();r.setStartBefore(t);r.setEndBefore(t);s.selection.setRng(r);u.prev
entDefault()}}}})}m.onKeyUp.add(o);m.onSetContent.add(o);m.onVisualAid.add(o);m.onP
reProcess.add(function(r,t){var
s=t.node.lastChild;if(s&&s.childNodes.length==1&&s.firstChild.nodeName=="BR")
{r.dom.remove(s)}});o()}});d({mceTableSplitCells:function(l)
{l.split()},mceTableMergeCells:function(m){var
n,o,l;l=f.dom.getParent(f.selection.getNode(),"th,td");if(l)
{n=l.rowSpan;o=l.colSpan}if(!f.dom.select("td.mceSelected,th.mceSelected").length)
{/*e.open({url:g+"/merge_cells.htm",width:240+parseInt(f.getLang("table.merge_cells
_delta_width",0)),height:110+parseInt(f.getLang("table.merge_cells_delta_height",0)
),inline:1},{rows:n,cols:o,onaction:function(p)
{m.merge(l,p.cols,p.rows)},plugin_url:g})*/}else{m.merge()}},mceTableInsertRowBefor
e:function(l
){l.insertRow(true)},mceTableInsertRowAfter:function(l)
{l.insertRow()},mceTableInsertColBefore:function(l)
{l.insertCol(true)},mceTableInsertColAfter:function(l)
{l.insertCol()},mceTableDeleteCol:function(l)
{l.deleteCols()},mceTableDeleteRow:function(l)
{l.deleteRows()},mceTableCutRow:function(l)
{k=l.cutRows()},mceTableCopyRow:function(l)
{k=l.copyRows()},mceTablePasteRowBefore:function(l)
{l.pasteRows(k,true)},mceTablePasteRowAfter:function(l)
{l.pasteRows(k)},mceTableDelete:function(l){l.deleteTable()}},function(m,l)
{f.addCommand(l,function(){var n=j();if(n)
{m(n);f.execCommand("mceRepaint");h()}})});d({mceInsertTable:function(l)
{e.open({url:g+"/table.htm",width:400+parseInt(f.getLang("table.table_delta_width",
0)),height:320+parseInt(f.getLang("table.table_delta_height",0)),inline:1},
{plugin_url:g,action:l?l.action:0})},mceTableRowProps:function()
{e.open({url:g+"/row.htm",width:400+parseInt(f.getLang("table.rowprops_delta_width"
,0)),height:295+parseInt(f.getLang("table.rowprops_delta_height",0)),inline:1},
{plugin_url:g})},mceTableCellProps:function()
{e.open({url:g+"/cell.htm",width:400+parseInt(f.getLang("table.cellprops_delta_widt
h",0)),height:295+parseInt(f.getLang("table.cellprops_delta_height",0)),inline:1},
{plugin_url:g})}},function(m,l){f.addCommand(l,function(n,o)
{m(o)})})}});c.PluginManager.add("table",c.plugins.TablePlugin)})(tinymce);
;/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/

(function() {
var rootAttributes =
tinymce.explode('id,name,width,height,style,align,class,hspace,vspace,bgcolor,type'
), excludedAttrs = tinymce.makeMap(rootAttributes.join(',')), Node =
tinymce.html.Node,
mediaTypes, scriptRegExp, JSON = tinymce.util.JSON, mimeTypes;

// Media types supported by this plugin


mediaTypes = [
// Type, clsid:s, mime types, codebase
["Flash", "d27cdb6e-ae6d-11cf-96b8-444553540000", "application/x-
shockwave-flash",
"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40
,0"],
["ShockWave", "166b1bca-3f9c-11cf-8075-444553540000", "application/x-
director",
"http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0"
],
["WindowsMedia", "6bf52a52-394a-11d3-b153-00c04f79faa6,22d6f312-b0f6-
11d0-94ab-0080c74c7e95,05589fa1-c356-11ce-bf01-00aa0055595a", "application/x-
mplayer2",
"http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,
52,701"],
["QuickTime", "02bf25d5-8c17-4b23-bc80-d3488abddc6b",
"video/quicktime", "http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0"],
["RealMedia", "cfcdaa03-8be4-11cf-b84b-0020afbbccfa", "audio/x-pn-
realaudio-plugin",
"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40
,0"],
["Java", "8ad9c840-044e-11d1-b3e9-00805f499d93", "application/x-java-
applet", "http://java.sun.com/products/plugin/autodl/jinstall-1_5_0-windows-
i586.cab#Version=1,5,0,0"],
["Silverlight", "dfeaf541-f3e1-4c24-acac-99c30715084a", "application/x-
silverlight-2"],
["Iframe"],
["Video"]
];

function toArray(obj) {
var undef, out, i;

if (obj && !obj.splice) {


out = [];

for (i = 0; true; i++) {


if (obj[i])
out[i] = obj[i];
else
break;
}

return out;
}

return obj;
};

tinymce.create('tinymce.plugins.MediaPlugin', {
init : function(ed, url) {
var self = this, lookup = {}, i, y, item, name;

function isMediaImg(node) {
return node && node.nodeName === 'IMG' &&
ed.dom.hasClass(node, 'mceItemMedia');
};

self.editor = ed;
self.url = url;

// Parse media types into a lookup table


scriptRegExp = '';
for (i = 0; i < mediaTypes.length; i++) {
name = mediaTypes[i][0];

item = {
name : name,
clsids : tinymce.explode(mediaTypes[i][1] || ''),
mimes : tinymce.explode(mediaTypes[i][2] || ''),
codebase : mediaTypes[i][3]
};

for (y = 0; y < item.clsids.length; y++)


lookup['clsid:' + item.clsids[y]] = item;

for (y = 0; y < item.mimes.length; y++)


lookup[item.mimes[y]] = item;

lookup['mceItem' + name] = item;


lookup[name.toLowerCase()] = item;

scriptRegExp += (scriptRegExp ? '|' : '') + name;


}

// Handle the media_types setting


tinymce.each(ed.getParam("media_types",
"video=mp4,m4v,ogv,webm;" +
"silverlight=xap;" +
"flash=swf,flv;" +
"shockwave=dcr;" +
"quicktime=mov,qt,mpg,mp3,mpeg;" +
"shockwave=dcr;" +
"windowsmedia=avi,wmv,wm,asf,asx,wmx,wvx;" +
"realmedia=rm,ra,ram;" +
"java=jar"
).split(';'), function(item) {
var i, extensions, type;
item = item.split(/=/);
extensions = tinymce.explode(item[1].toLowerCase());
for (i = 0; i < extensions.length; i++) {
type = lookup[item[0].toLowerCase()];

if (type)
lookup[extensions[i]] = type;
}
});

scriptRegExp = new RegExp('write(' + scriptRegExp + ')\\(([^)]


+)\\)');
self.lookup = lookup;

ed.onPreInit.add(function() {
// Allow video elements
ed.schema.addValidElements('object[id|style|width|height|
classid|codebase|*],param[name|value],embed[id|style|width|height|type|src|
*],video[*],audio[*],source[*]');

// Convert video elements to image placeholder

ed.parser.addNodeFilter('object,embed,video,audio,script,iframe',
function(nodes) {
var i = nodes.length;

while (i--)
self.objectToImg(nodes[i]);
});

// Convert image placeholders to video elements


ed.serializer.addNodeFilter('img', function(nodes, name,
args) {
var i = nodes.length, node;

while (i--) {
node = nodes[i];
if ((node.attr('class') ||
'').indexOf('mceItemMedia') !== -1)
self.imgToObject(node, args);
}
});
});

ed.onInit.add(function() {
// Display "media" instead of "img" in element path
if (ed.theme && ed.theme.onResolveName) {
ed.theme.onResolveName.add(function(theme,
path_object) {
if (path_object.name === 'img' &&
ed.dom.hasClass(path_object.node, 'mceItemMedia'))
path_object.name = 'media';
});
}

// Add contect menu if it's loaded


if (ed && ed.plugins.contextmenu) {
ed.plugins.contextmenu.onContextMenu.add(function(plugin, menu, element) {
if (element.nodeName === 'IMG' &&
element.className.indexOf('mceItemMedia') !== -1)
menu.add({title : 'media.edit', icon :
'media', cmd : 'mceMedia'});
});
}
});

// Register commands
ed.addCommand('mceMedia', function() {
var data, img;

img = ed.selection.getNode();
if (isMediaImg(img)) {
data = JSON.parse(ed.dom.getAttrib(img, 'data-mce-
json'));

// Add some extra properties to the data object


tinymce.each(rootAttributes, function(name) {
var value = ed.dom.getAttrib(img, name);

if (value)
data[name] = value;
});

data.type =
self.getType(img.className).name.toLowerCase();
}

if (!data) {
data = {
type : 'flash',
video: {sources:[]},
params: {}
};
}

ed.windowManager.open({
file : url + '/media.htm',
width : 476 +
parseInt(ed.getLang('media.delta_width', 0)),
height : 541 +
parseInt(ed.getLang('media.delta_height', 0)),
inline : 1
}, {
plugin_url : url,
data : data
});
});

// Register buttons
ed.addButton('media', {title : 'media.desc', cmd : 'mceMedia'});

// Update media selection status


ed.onNodeChange.add(function(ed, cm, node) {
cm.setActive('media', isMediaImg(node));
});
},
convertUrl : function(url, force_absolute) {
var self = this, editor = self.editor, settings =
editor.settings,
urlConverter = settings.url_converter,
urlConverterScope = settings.url_converter_scope || self;

if (!url)
return url;

if (force_absolute)
return editor.documentBaseURI.toAbsolute(url);

return urlConverter.call(urlConverterScope, url, 'src',


'object');
},

getInfo : function() {
return {
longname : 'Media',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl :
'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/media',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
},

/**
* Converts the JSON data object to an img node.
*/
dataToImg : function(data, force_absolute) {
var self = this, editor = self.editor, baseUri =
editor.documentBaseURI, sources, attrs, img, i;

data.params.src = self.convertUrl(data.params.src,
force_absolute);

attrs = data.video.attrs;
if (attrs)
attrs.src = self.convertUrl(attrs.src, force_absolute);

if (attrs)
attrs.poster = self.convertUrl(attrs.poster,
force_absolute);

sources = toArray(data.video.sources);
if (sources) {
for (i = 0; i < sources.length; i++)
sources[i].src = self.convertUrl(sources[i].src,
force_absolute);
}

img = self.editor.dom.create('img', {
id : data.id,
style : data.style,
align : data.align,
src : self.editor.theme.url + '/img/trans.gif',
'class' : 'mceItemMedia mceItem' +
self.getType(data.type).name,
'data-mce-json' : JSON.serialize(data, "'")
});

img.width = data.width || "320";


img.height = data.height || "240";

return img;
},

/**
* Converts the JSON data object to a HTML string.
*/
dataToHtml : function(data, force_absolute) {
return this.editor.serializer.serialize(this.dataToImg(data,
force_absolute), {force_absolute : force_absolute});
},

/**
* Converts the JSON data object to a HTML string.
*/
htmlToData : function(html) {
var fragment, img, data;

data = {
type : 'flash',
video: {sources:[]},
params: {}
};

fragment = this.editor.parser.parse(html);
img = fragment.getAll('img')[0];

if (img) {
data = JSON.parse(img.attr('data-mce-json'));
data.type =
this.getType(img.attr('class')).name.toLowerCase();

// Add some extra properties to the data object


tinymce.each(rootAttributes, function(name) {
var value = img.attr(name);

if (value)
data[name] = value;
});
}

return data;
},

/**
* Get type item by extension, class, clsid or mime type.
*
* @method getType
* @param {String} value Value to get type item by.
* @return {Object} Type item object or undefined.
*/
getType : function(value) {
var i, values, typeItem;
// Find type by checking the classes
values = tinymce.explode(value, ' ');
for (i = 0; i < values.length; i++) {
typeItem = this.lookup[values[i]];

if (typeItem)
return typeItem;
}
},

/**
* Converts a tinymce.html.Node image element to video/object/embed.
*/
imgToObject : function(node, args) {
var self = this, editor = self.editor, video, object, embed,
iframe, name, value, data,
source, sources, params, param, typeItem, i, item,
mp4Source, replacement,
posterSrc, style;

// Adds the flash player


function addPlayer(video_src, poster_src) {
var baseUri, flashVars, flashVarsOutput, params,
flashPlayer;

flashPlayer = editor.getParam('flash_video_player_url',
self.convertUrl(self.url + '/moxieplayer.swf'));
if (flashPlayer) {
baseUri = editor.documentBaseURI;
data.params.src = flashPlayer;

// Convert the movie url to absolute urls


if (editor.getParam('flash_video_player_absvideourl',
true)) {
video_src = baseUri.toAbsolute(video_src || '',
true);
poster_src = baseUri.toAbsolute(poster_src ||
'', true);
}

// Generate flash vars


flashVarsOutput = '';
flashVars =
editor.getParam('flash_video_player_flashvars', {url : '$url', poster :
'$poster'});
tinymce.each(flashVars, function(value, name) {
// Replace $url and $poster variables in
flashvars value
value = value.replace(/\$url/, video_src ||
'');
value = value.replace(/\$poster/, poster_src ||
'');

if (value.length > 0)
flashVarsOutput += (flashVarsOutput ? '&'
: '') + name + '=' + escape(value);
});
if (flashVarsOutput.length)
data.params.flashvars = flashVarsOutput;

params = editor.getParam('flash_video_player_params',
{
allowfullscreen: true,
allowscriptaccess: true
});

tinymce.each(params, function(value, name) {


data.params[name] = "" + value;
});
}
};

data = JSON.parse(node.attr('data-mce-json'));
typeItem = this.getType(node.attr('class'));

style = node.attr('data-mce-style')
if (!style) {
style = node.attr('style');

if (style)
style =
editor.dom.serializeStyle(editor.dom.parseStyle(style, 'img'));
}

// Handle iframe
if (typeItem.name === 'Iframe') {
replacement = new Node('iframe', 1);

tinymce.each(rootAttributes, function(name) {
var value = node.attr(name);

if (name == 'class' && value)


value = value.replace(/mceItem.+ ?/g, '');

if (value && value.length > 0)


replacement.attr(name, value);
});

for (name in data.params)


replacement.attr(name, data.params[name]);

replacement.attr({
style: style,
src: data.params.src
});

node.replace(replacement);

return;
}

// Handle scripts
if (this.editor.settings.media_use_script) {
replacement = new Node('script', 1).attr('type',
'text/javascript');
value = new Node('#text', 3);
value.value = 'write' + typeItem.name + '(' +
JSON.serialize(tinymce.extend(data.params, {
width: node.attr('width'),
height: node.attr('height')
})) + ');';

replacement.append(value);
node.replace(replacement);

return;
}

// Add HTML5 video element


if (typeItem.name === 'Video' && data.video.sources[0]) {
// Create new object element
video = new Node('video', 1).attr(tinymce.extend({
id : node.attr('id'),
width: node.attr('width'),
height: node.attr('height'),
style : style
}, data.video.attrs));

// Get poster source and use that for flash fallback


if (data.video.attrs)
posterSrc = data.video.attrs.poster;

sources = data.video.sources = toArray(data.video.sources);


for (i = 0; i < sources.length; i++) {
if (/\.mp4$/.test(sources[i].src))
mp4Source = sources[i].src;
}

if (!sources[0].type) {
video.attr('src', sources[0].src);
sources.splice(0, 1);
}

for (i = 0; i < sources.length; i++) {


source = new Node('source', 1).attr(sources[i]);
source.shortEnded = true;
video.append(source);
}

// Create flash fallback for video if we have a mp4 source


if (mp4Source) {
addPlayer(mp4Source, posterSrc);
typeItem = self.getType('flash');
} else
data.params.src = '';
}

// Do we have a params src then we can generate object


if (data.params.src) {
// Is flv movie add player for it
if (/\.flv$/i.test(data.params.src))
addPlayer(data.params.src, '');

if (args && args.force_absolute)


data.params.src =
editor.documentBaseURI.toAbsolute(data.params.src);

// Create new object element


object = new Node('object', 1).attr({
id : node.attr('id'),
width: node.attr('width'),
height: node.attr('height'),
style : style
});

tinymce.each(rootAttributes, function(name) {
if (data[name] && name != 'type')
object.attr(name, data[name]);
});

// Add params
for (name in data.params) {
param = new Node('param', 1);
param.shortEnded = true;
value = data.params[name];

// Windows media needs to use url instead of src for


the media URL
if (name === 'src' && typeItem.name ===
'WindowsMedia')
name = 'url';

param.attr({name: name, value: value});


object.append(param);
}

// Setup add type and classid if strict is disabled


if (this.editor.getParam('media_strict', true)) {
object.attr({
data: data.params.src,
type: typeItem.mimes[0]
});
} else {
object.attr({
classid: "clsid:" + typeItem.clsids[0],
codebase: typeItem.codebase
});

embed = new Node('embed', 1);


embed.shortEnded = true;
embed.attr({
id: node.attr('id'),
width: node.attr('width'),
height: node.attr('height'),
style : style,
type: typeItem.mimes[0]
});

for (name in data.params)


embed.attr(name, data.params[name]);

tinymce.each(rootAttributes, function(name) {
if (data[name] && name != 'type')
embed.attr(name, data[name]);
});

object.append(embed);
}

// Insert raw HTML


if (data.object_html) {
value = new Node('#text', 3);
value.raw = true;
value.value = data.object_html;
object.append(value);
}

// Append object to video element if it exists


if (video)
video.append(object);
}

if (video) {
// Insert raw HTML
if (data.video_html) {
value = new Node('#text', 3);
value.raw = true;
value.value = data.video_html;
video.append(value);
}
}

if (video || object)
node.replace(video || object);
else
node.remove();
},

/**
* Converts a tinymce.html.Node video/object/embed to an img element.
*
* The video/object/embed will be converted into an image placeholder
with a JSON data attribute like this:
* <img class="mceItemMedia mceItemFlash" width="100" height="100"
data-mce-json="{..}" />
*
* The JSON structure will be like this:
* {'params':
{'flashvars':'something','quality':'high','src':'someurl'}, 'video':{'sources':
[{src: 'someurl', type: 'video/mp4'}]}}
*/
objectToImg : function(node) {
var object, embed, video, iframe, img, name, id, width, height,
style, i, html,
param, params, source, sources, data, type, lookup =
this.lookup,
matches, attrs, urlConverter =
this.editor.settings.url_converter,
urlConverterScope =
this.editor.settings.url_converter_scope;

function getInnerHTML(node) {
return new tinymce.html.Serializer({
inner: true,
validate: false
}).serialize(node);
};

// If node isn't in document


if (!node.parent)
return;

// Handle media scripts


if (node.name === 'script') {
if (node.firstChild)
matches = scriptRegExp.exec(node.firstChild.value);

if (!matches)
return;

type = matches[1];
data = {video : {}, params : JSON.parse(matches[2])};
width = data.params.width;
height = data.params.height;
}

// Setup data objects


data = data || {
video : {},
params : {}
};

// Setup new image object


img = new Node('img', 1);
img.attr({
src : this.editor.theme.url + '/img/trans.gif'
});

// Video element
name = node.name;
if (name === 'video') {
video = node;
object = node.getAll('object')[0];
embed = node.getAll('embed')[0];
width = video.attr('width');
height = video.attr('height');
id = video.attr('id');
data.video = {attrs : {}, sources : []};

// Get all video attributes


attrs = data.video.attrs;
for (name in video.attributes.map)
attrs[name] = video.attributes.map[name];

source = node.attr('src');
if (source)
data.video.sources.push({src :
urlConverter.call(urlConverterScope, source, 'src', 'video')});

// Get all sources


sources = video.getAll("source");
for (i = 0; i < sources.length; i++) {
source = sources[i].remove();

data.video.sources.push({
src: urlConverter.call(urlConverterScope,
source.attr('src'), 'src', 'source'),
type: source.attr('type'),
media: source.attr('media')
});
}

// Convert the poster URL


if (attrs.poster)
attrs.poster = urlConverter.call(urlConverterScope,
attrs.poster, 'poster', 'video');
}

// Object element
if (node.name === 'object') {
object = node;
embed = node.getAll('embed')[0];
}

// Embed element
if (node.name === 'embed')
embed = node;

// Iframe element
if (node.name === 'iframe') {
iframe = node;
type = 'Iframe';
}

if (object) {
// Get width/height
width = width || object.attr('width');
height = height || object.attr('height');
style = style || object.attr('style');
id = id || object.attr('id');

// Get all object params


params = object.getAll("param");
for (i = 0; i < params.length; i++) {
param = params[i];
name = param.remove().attr('name');

if (!excludedAttrs[name])
data.params[name] = param.attr('value');
}

data.params.src = data.params.src || object.attr('data');


}

if (embed) {
// Get width/height
width = width || embed.attr('width');
height = height || embed.attr('height');
style = style || embed.attr('style');
id = id || embed.attr('id');
// Get all embed attributes
for (name in embed.attributes.map) {
if (!excludedAttrs[name] && !data.params[name])
data.params[name] = embed.attributes.map[name];
}
}

if (iframe) {
// Get width/height
width = iframe.attr('width');
height = iframe.attr('height');
style = style || iframe.attr('style');
id = iframe.attr('id');

tinymce.each(rootAttributes, function(name) {
img.attr(name, iframe.attr(name));
});

// Get all iframe attributes


for (name in iframe.attributes.map) {
if (!excludedAttrs[name] && !data.params[name])
data.params[name] =
iframe.attributes.map[name];
}
}

// Use src not movie


if (data.params.movie) {
data.params.src = data.params.src || data.params.movie;
delete data.params.movie;
}

// Convert the URL to relative/absolute depending on


configuration
if (data.params.src)
data.params.src = urlConverter.call(urlConverterScope,
data.params.src, 'src', 'object');

if (video)
type = lookup.video.name;

if (object && !type)


type = (lookup[(object.attr('clsid') || '').toLowerCase()]
|| lookup[(object.attr('classid') || '').toLowerCase()] ||
lookup[(object.attr('type') || '').toLowerCase()] || {}).name;

if (embed && !type)


type = (lookup[(embed.attr('type') || '').toLowerCase()] ||
{}).name;

// Replace the video/object/embed element with a placeholder


image containing the data
node.replace(img);

// Remove embed
if (embed)
embed.remove();
// Serialize the inner HTML of the object element
if (object) {
html = getInnerHTML(object.remove());

if (html)
data.object_html = html;
}

// Serialize the inner HTML of the video element


if (video) {
html = getInnerHTML(video.remove());

if (html)
data.video_html = html;
}

// Set width/height of placeholder


img.attr({
id : id,
'class' : 'mceItemMedia mceItem' + (type || 'Flash'),
style : style,
width : width || "320",
height : height || "240",
"data-mce-json" : JSON.serialize(data, "'")
});
}
});

// Register plugin
tinymce.PluginManager.add('media', tinymce.plugins.MediaPlugin);
})();;/**
* editor_plugin_src.js
*
* Copyright 2009, Moxiecode Systems AB
* Released under LGPL License.
*
* License: http://tinymce.moxiecode.com/license
* Contributing: http://tinymce.moxiecode.com/contributing
*/

(function() {
var each = tinymce.each,
defs = {
paste_auto_cleanup_on_paste : true,
paste_enable_default_filters : true,
paste_block_drop : false,
paste_retain_style_properties : "none",
paste_strip_class_attributes : "mso",
paste_remove_spans : false,
paste_remove_styles : false,
paste_remove_styles_if_webkit : true,
paste_convert_middot_lists : true,
paste_convert_headers_to_strong : false,
paste_dialog_width : "450",
paste_dialog_height : "400",
paste_text_use_dialog : false,
paste_text_sticky : false,
paste_text_sticky_default : false,
paste_text_notifyalways : false,
paste_text_linebreaktype : "p",
paste_text_replacements : [
[/\u2026/g, "..."],
[/[\x93\x94\u201c\u201d]/g, '"'],
[/[\x60\x91\x92\u2018\u2019]/g, "'"]
]
};

function getParam(ed, name) {


return ed.getParam(name, defs[name]);
}

tinymce.create('tinymce.plugins.PastePlugin', {
init : function(ed, url) {
var t = this;

t.editor = ed;
t.url = url;

// Setup plugin events


t.onPreProcess = new tinymce.util.Dispatcher(t);
t.onPostProcess = new tinymce.util.Dispatcher(t);

// Register default handlers


t.onPreProcess.add(t._preProcess);
t.onPostProcess.add(t._postProcess);

// Register optional preprocess handler


t.onPreProcess.add(function(pl, o) {
ed.execCallback('paste_preprocess', pl, o);
});

// Register optional postprocess


t.onPostProcess.add(function(pl, o) {
ed.execCallback('paste_postprocess', pl, o);
});

ed.onKeyDown.addToTop(function(ed, e) {
// Block ctrl+v from adding an undo level since the default
logic in tinymce.Editor will add that
if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode
== 86) || (e.shiftKey && e.keyCode == 45))
return false; // Stop other listeners
});

// Initialize plain text flag


ed.pasteAsPlainText = getParam(ed, 'paste_text_sticky_default');

// This function executes the process handlers and inserts the


contents
// force_rich overrides plain text mode set by user, important
for pasting with execCommand
function process(o, force_rich) {
var dom = ed.dom, rng, nodes;

// Execute pre process handlers


t.onPreProcess.dispatch(t, o);

// Create DOM structure


o.node = dom.create('div', 0, o.content);

// If pasting inside the same element and the contents is


only one block
// remove the block and keep the text since Firefox will
copy parts of pre and h1-h6 as a pre element
if (tinymce.isGecko) {
rng = ed.selection.getRng(true);
if (rng.startContainer == rng.endContainer &&
rng.startContainer.nodeType == 3) {
nodes = dom.select('p,h1,h2,h3,h4,h5,h6,pre',
o.node);

// Is only one block node and it doesn't


contain word stuff
if (nodes.length == 1 &&
o.content.indexOf('__MCE_ITEM__') === -1)
dom.remove(nodes.reverse(), true);
}
}

// Execute post process handlers


t.onPostProcess.dispatch(t, o);

// Serialize content
o.content = ed.serializer.serialize(o.node, {
getInner : 1,
forced_root_block: '' // default behavior is to wrap content with a <p>
tag - not desirable when pasting text
});

// Plain text option active?


if ((!force_rich) && (ed.pasteAsPlainText)) {
t._insertPlainText(ed, dom, o.content);

if (!getParam(ed, "paste_text_sticky")) {
ed.pasteAsPlainText = false;
ed.controlManager.setActive("pastetext",
false);
}
} else {
t._insert(o.content);
}
}

// Add command for external usage


ed.addCommand('mceInsertClipboardContent', function(u, o) {
process(o, true);
});

if (!getParam(ed, "paste_text_use_dialog")) {
ed.addCommand('mcePasteText', function(u, v) {
var cookie = tinymce.util.Cookie;

ed.pasteAsPlainText = !ed.pasteAsPlainText;
ed.controlManager.setActive('pastetext',
ed.pasteAsPlainText);

if ((ed.pasteAsPlainText) && (!
cookie.get("tinymcePasteText"))) {
if (getParam(ed, "paste_text_sticky")) {

ed.windowManager.alert(ed.translate('paste.plaintext_mode_sticky'));
} else {

ed.windowManager.alert(ed.translate('paste.plaintext_mode_sticky'));
}

if (!getParam(ed, "paste_text_notifyalways")) {
cookie.set("tinymcePasteText", "1", new
Date(new Date().getFullYear() + 1, 12, 31))
}
}
});
}

ed.addButton('pastetext', {title: 'paste.paste_text_desc', cmd:


'mcePasteText'});
ed.addButton('selectall', {title: 'paste.selectall_desc', cmd:
'selectall'});

// This function grabs the contents from the clipboard by adding


a
// hidden div and placing the caret inside it and after the
browser paste
// is done it grabs that contents and processes that
function grabContent(e) {
var n, or, rng, oldRng, sel = ed.selection, dom = ed.dom,
body = ed.getBody(), posY, textContent;

// Check if browser supports direct plaintext access


if (e.clipboardData || dom.doc.dataTransfer) {
textContent = (e.clipboardData ||
dom.doc.dataTransfer).getData('Text');

if (ed.pasteAsPlainText) {
e.preventDefault();
process({content :
textContent.replace(/\r?\n/g, '<br />')});
return;
}
}

if (dom.get('_mcePaste'))
return;

// Create container to paste into


n = dom.add(body, 'div', {id : '_mcePaste', 'class' :
'mcePaste', 'data-mce-bogus' : '1'}, '\uFEFF\uFEFF');

// If contentEditable mode we need to find out the position


of the closest element
if (body != ed.getDoc().body)
posY = dom.getPos(ed.selection.getStart(), body).y;
else
posY = body.scrollTop + dom.getViewPort().y;

// Styles needs to be applied after the element is added to


the document since WebKit will otherwise remove all styles
dom.setStyles(n, {
position : 'absolute',
left : -10000,
top : posY,
width : 1,
height : 1,
overflow : 'hidden'
});

if (tinymce.isIE) {
// Store away the old range
oldRng = sel.getRng();

// Select the container


rng = dom.doc.body.createTextRange();
rng.moveToElementText(n);
rng.execCommand('Paste');

// Remove container
dom.remove(n);

// Check if the contents was changed, if it wasn't


then clipboard extraction failed probably due
// to IE security settings so we pass the junk though
better than nothing right
if (n.innerHTML === '\uFEFF\uFEFF') {
ed.execCommand('mcePasteWord');
e.preventDefault();
return;
}

// Restore the old range and clear the contents


before pasting
sel.setRng(oldRng);
sel.setContent('');

// For some odd reason we need to detach the the


mceInsertContent call from the paste event
// It's like IE has a reference to the parent element
that you paste in and the selection gets messed up
// when it tries to restore the selection
setTimeout(function() {
// Process contents
process({content : n.innerHTML});
}, 0);

// Block the real paste event


return tinymce.dom.Event.cancel(e);
} else {
function block(e) {
e.preventDefault();
};

// Block mousedown and click to prevent selection


change
dom.bind(ed.getDoc(), 'mousedown', block);
dom.bind(ed.getDoc(), 'keydown', block);
or = ed.selection.getRng();

// Move select contents inside DIV


n = n.firstChild;
rng = ed.getDoc().createRange();
rng.setStart(n, 0);
rng.setEnd(n, 2);
sel.setRng(rng);

// Wait a while and grab the pasted contents


window.setTimeout(function() {
var h = '', nl;

// Paste divs duplicated in paste divs seems to


happen when you paste plain text so lets first look for that broken behavior in
WebKit
if (!dom.select('div.mcePaste >
div.mcePaste').length) {
nl = dom.select('div.mcePaste');

// WebKit will split the div into


multiple ones so this will loop through then all and join them to get the whole
HTML string
each(nl, function(n) {
var child = n.firstChild;

// WebKit inserts a DIV container


with lots of odd styles
if (child && child.nodeName ==
'DIV' && child.style.marginTop && child.style.backgroundColor) {
dom.remove(child, 1);
}

// Remove apply style spans


each(dom.select('span.Apple-style-
span', n), function(n) {
dom.remove(n, 1);
});

// Remove bogus br elements


each(dom.select('br[data-mce-
bogus]', n), function(n) {
dom.remove(n);
});

// WebKit will make a copy of the


DIV for each line of plain text pasted and insert them into the DIV
if (n.parentNode.className !=
'mcePaste')
h += n.innerHTML;
});
} else {
// Found WebKit weirdness so force the
content into plain text mode
h = '<pre>' +
dom.encode(textContent).replace(/\r?\n/g, '<br />') + '</pre>';
}

// Remove the nodes


each(dom.select('div.mcePaste'), function(n) {
dom.remove(n);
});

// Restore the old selection


if (or)
sel.setRng(or);

process({content : h});

// Unblock events ones we got the contents


dom.unbind(ed.getDoc(), 'mousedown', block);
dom.unbind(ed.getDoc(), 'keydown', block);
}, 0);
}
}

// Check if we should use the new auto process method


if (getParam(ed, "paste_auto_cleanup_on_paste")) {
// Is it's Opera or older FF use key handler
if (tinymce.isOpera ||
/Firefox\/2/.test(navigator.userAgent)) {
ed.onKeyDown.addToTop(function(ed, e) {
if (((tinymce.isMac ? e.metaKey : e.ctrlKey) &&
e.keyCode == 86) || (e.shiftKey && e.keyCode == 45))
grabContent(e);
});
} else {
// Grab contents on paste event on Gecko and WebKit
ed.onPaste.addToTop(function(ed, e) {
return grabContent(e);
});
}
}

ed.onInit.add(function() {
ed.controlManager.setActive("pastetext",
ed.pasteAsPlainText);

// Block all drag/drop events


if (getParam(ed, "paste_block_drop")) {
ed.dom.bind(ed.getBody(), ['dragend', 'dragover',
'draggesture', 'dragdrop', 'drop', 'drag'], function(e) {
e.preventDefault();
e.stopPropagation();

return false;
});
}
});

// Add legacy support


t._legacySupport();
},

getInfo : function() {
return {
longname : 'Paste text/word',
author : 'Moxiecode Systems AB',
authorurl : 'http://tinymce.moxiecode.com',
infourl :
'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
},

_preProcess : function(pl, o) {
var ed = this.editor,
h = o.content,
grep = tinymce.grep,
explode = tinymce.explode,
trim = tinymce.trim,
len, stripClass;

//console.log('Before preprocess:' + o.content);

function process(items) {
each(items, function(v) {
// Remove or replace
if (v.constructor == RegExp)
h = h.replace(v, '');
else
h = h.replace(v[0], v[1]);
});
}

if (ed.settings.paste_enable_default_filters == false) {
return;
}

// IE9 adds BRs before/after block elements when contents is


pasted from word or for example another browser
if (tinymce.isIE && document.documentMode >= 9)
process([[/(?:<br>&nbsp;[\s\r\n]+|<br>)*(<\/?(h[1-6r]|p|
div|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|caption|blockquote|
center|dl|dt|dd|dir|fieldset)[^>]*>)(?:<br>&nbsp;[\s\r\n]+|<br>)*/g, '$1']]);

// Detect Word content and process it more aggressive


if (/class="?Mso|style="[^"]*\bmso-|w:WordDocument/i.test(h) ||
o.wordContent) {
o.wordContent = true; // Mark the pasted
contents as word specific content
//console.log('Word contents detected.');

// Process away some basic content


process([
/^\s*(&nbsp;)+/gi, // &nbsp;
entities at the start of contents
/(&nbsp;|<br[^>]*>)+\s*$/gi // &nbsp; entities
at the end of contents
]);

if (getParam(ed, "paste_convert_headers_to_strong")) {
h = h.replace(/<p [^>]*class="?MsoHeading"?
[^>]*>(.*?)<\/p>/gi, "<p><strong>$1</strong></p>");
}

if (getParam(ed, "paste_convert_middot_lists")) {
process([
[/<!--\[if !supportLists\]-->/gi,
'$&__MCE_ITEM__'], // Convert supportLists to a list
item marker
[/(<span[^>]+(?:mso-list:|:\s*symbol)[^>]
+>)/gi, '$1__MCE_ITEM__'], // Convert mso-list and symbol spans to item
markers
[/(<p[^>]+(?:MsoListParagraph)[^>]+>)/gi,
'$1__MCE_ITEM__'] // Convert mso-list and symbol paragraphs to
item markers (FF)
]);
}

process([
// Word comments like conditional comments etc
/<!--[\s\S]+?-->/gi,

// Remove comments, scripts (e.g., msoShowComment),


XML tag, VML content, MS Office namespaced tags, and a few other tags
/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?
xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,

// Convert <s> into <strike> for line-though


[/<(\/?)s>/gi, "<$1strike>"],

// Replace nsbp entites to char since it's easier to


handle
[/&nbsp;/gi, "\u00a0"]
]);

// Remove bad attributes, with or without quotes, ensuring


that attribute text is really inside a tag.
// If JavaScript had a RegExp look-behind, we could have
integrated this with the last process() array and got rid of the loop. But alas, it
does not, so we cannot.
do {
len = h.length;
h = h.replace(/(<[a-z][^>]*\s)(?:id|name|language|
type|on\w+|\w+:\w+)=(?:"[^"]*"|\w+)\s?/gi, "$1");
} while (len != h.length);

// Remove all spans if no styles is to be retained


if (getParam(ed,
"paste_retain_style_properties").replace(/^none$/i, "").length == 0) {
h = h.replace(/<\/?span[^>]*>/gi, "");
} else {
// We're keeping styles, so at least clean them up.
// CSS Reference: http://msdn.microsoft.com/en-
us/library/aa155477.aspx

process([
// Convert <span style="mso-
spacerun:yes">___</span> to string of alternating breaking/non-breaking spaces of
same length
[/<span\s+style\s*=\s*"\s*mso-
spacerun\s*:\s*yes\s*;?\s*"\s*>([\s\u00a0]*)<\/span>/gi,
function(str, spaces) {
return (spaces.length > 0)?
spaces.replace(/./, "
").slice(Math.floor(spaces.length/2)).split("").join("\u00a0") : "";
}
],

// Examine all styles: delete junk, transform


some, and keep the rest
[/(<[a-z][^>]*)\sstyle="([^"]*)"/gi,
function(str, tag, style) {
var n = [],
i = 0,
s =
explode(trim(style).replace(/&quot;/gi, "'"), ";");

// Examine each style definition


within the tag's style attribute
each(s, function(v) {
var name, value,
parts = explode(v, ":");

function ensureUnits(v) {
return v + ((v !== "0")
&& (/\d$/.test(v)))? "px" : "";
}

if (parts.length == 2) {
name =
parts[0].toLowerCase();
value =
parts[1].toLowerCase();

// Translate certain MS
Office styles into their CSS equivalents
switch (name) {
case "mso-padding-
alt":
case "mso-padding-
top-alt":
case "mso-padding-
right-alt":
case "mso-padding-
bottom-alt":
case "mso-padding-
left-alt":
case "mso-margin-
alt":
case "mso-margin-
top-alt":
case "mso-margin-
right-alt":
case "mso-margin-
bottom-alt":
case "mso-margin-
left-alt":
case "mso-table-
layout-alt":
case "mso-height":
case "mso-width":
case "mso-
vertical-align-alt":
n[i++] =
name.replace(/^mso-|-alt$/g, "") + ":" + ensureUnits(value);
return;

case "horiz-
align":
n[i++] =
"text-align:" + value;
return;

case "vert-align":
n[i++] =
"vertical-align:" + value;
return;

case "font-color":
case "mso-
foreground":
n[i++] =
"color:" + value;
return;

case "mso-
background":
case "mso-
highlight":
n[i++] =
"background:" + value;
return;

case "mso-default-
height":
n[i++] =
"min-height:" + ensureUnits(value);
return;

case "mso-default-
width":
n[i++] =
"min-width:" + ensureUnits(value);
return;

case "mso-padding-
between-alt":
n[i++] =
"border-collapse:separate;border-spacing:" + ensureUnits(value);
return;

case "text-line-
through":
if ((value
== "single") || (value == "double")) {
n[i++]
= "text-decoration:line-through";
}
return;

case "mso-zero-
height":
if (value ==
"yes") {
n[i++]
= "display:none";
}
return;
}

// Eliminate all MS
Office style definitions that have no CSS equivalent by examining the first
characters in the name
if (/^(mso|column|font-
emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|
table-border|text-(?!align|decor|indent|trans)|top-bar|version|vnd|word-
break)/.test(name)) {
return;
}

// If it reached this
point, it must be a valid CSS style
n[i++] = name + ":" +
parts[1]; // Lower-case name, but keep value case
}
});

// If style attribute contained any


valid styles the re-write it; otherwise delete style attribute.
if (i > 0) {
return tag + ' style="' +
n.join(';') + '"';
} else {
return tag;
}
}
]
]);
}
}

// Replace headers with <strong>


if (getParam(ed, "paste_convert_headers_to_strong")) {
process([
[/<h[1-6][^>]*>/gi, "<p><strong>"],
[/<\/h[1-6][^>]*>/gi, "</strong></p>"]
]);
}

process([
// Copy paste from Java like Open Office will produce this
junk on FF
[/Version:[\d.]
+\nStartHTML:\d+\nEndHTML:\d+\nStartFragment:\d+\nEndFragment:\d+/gi, '']
]);

// Class attribute options are: leave all as-is ("none"), remove


all ("all"), or remove only those starting with mso ("mso").
// Note:- paste_strip_class_attributes: "none",
verify_css_classes: true is also a good variation.
stripClass = getParam(ed, "paste_strip_class_attributes");
if (stripClass !== "none") {
function removeClasses(match, g1) {
if (stripClass === "all")
return '';

var cls = grep(explode(g1.replace(/^(["'])


(.*)\1$/, "$2"), " "),
function(v) {
return (/^(?!mso)/i.test(v));
}
);

return cls.length ? ' class="' + cls.join(" ")


+ '"' : '';
};

h = h.replace(/ class="([^"]+)"/gi, removeClasses);


h = h.replace(/ class=([\-\w]+)/gi, removeClasses);
}

// Remove spans option


if (getParam(ed, "paste_remove_spans")) {
h = h.replace(/<\/?span[^>]*>/gi, "");
}

//console.log('After preprocess:' + h);

o.content = h;
},

/**
* Various post process items.
*/
_postProcess : function(pl, o) {
var t = this, ed = t.editor, dom = ed.dom, styleProps;

if (ed.settings.paste_enable_default_filters == false) {
return;
}

if (o.wordContent) {
// Remove named anchors or TOC links
each(dom.select('a', o.node), function(a) {
if (!a.href || a.href.indexOf('#_Toc') != -1)
dom.remove(a, 1);
});

if (getParam(ed, "paste_convert_middot_lists")) {
t._convertLists(pl, o);
}

// Process styles
styleProps = getParam(ed, "paste_retain_style_properties");
// retained properties

// Process only if a string was specified and not equal to


"all" or "*"
if ((tinymce.is(styleProps, "string")) && (styleProps !==
"all") && (styleProps !== "*")) {
styleProps =
tinymce.explode(styleProps.replace(/^none$/i, ""));

// Retains some style properties


each(dom.select('*', o.node), function(el) {
var newStyle = {}, npc = 0, i, sp, sv;

// Store a subset of the existing styles


if (styleProps) {
for (i = 0; i < styleProps.length; i++) {
sp = styleProps[i];
sv = dom.getStyle(el, sp);

if (sv) {
newStyle[sp] = sv;
npc++;
}
}
}

// Remove all of the existing styles


dom.setAttrib(el, 'style', '');

if (styleProps && npc > 0)


dom.setStyles(el, newStyle); // Add back
the stored subset of styles
else // Remove empty span tags that do not have
class attributes
if (el.nodeName == 'SPAN' && !
el.className)
dom.remove(el, true);
});
}
}

// Remove all style information or only specifically on WebKit to


avoid the style bug on that browser
if (getParam(ed, "paste_remove_styles") || (getParam(ed,
"paste_remove_styles_if_webkit") && tinymce.isWebKit)) {
each(dom.select('*[style]', o.node), function(el) {
el.removeAttribute('style');
el.removeAttribute('data-mce-style');
});
} else {
if (tinymce.isWebKit) {
// We need to compress the styles on WebKit since if
you paste <img border="0" /> it will become <img border="0" style="... lots of junk
..." />
// Removing the mce_style that contains the real
value will force the Serializer engine to compress the styles
each(dom.select('*', o.node), function(el) {
el.removeAttribute('data-mce-style');
});
}
}
},

/**
* Converts the most common bullet and number formats in Office into a
real semantic UL/LI list.
*/
_convertLists : function(pl, o) {
var dom = pl.editor.dom, listElm, li, lastMargin = -1, margin,
levels = [], lastType, html;

// Convert middot lists into real semantic lists


each(dom.select('p', o.node), function(p) {
var sib, val = '', type, html, idx, parents;

// Get text node value at beginning of paragraph


for (sib = p.firstChild; sib && sib.nodeType == 3; sib =
sib.nextSibling)
val += sib.nodeValue;

val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi,
'').replace(/&nbsp;/g, '\u00a0');

// Detect unordered lists look for bullets


if (/^(__MCE_ITEM__)+
[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*\u00a0*/.test(val))
type = 'ul';

// Detect ordered lists 1., a. or ixv.


if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0+/.test(val))
type = 'ol';

// Check if node value matches the list pattern:


o&nbsp;&nbsp;
if (type) {
margin = parseFloat(p.style.marginLeft || 0);

if (margin > lastMargin)


levels.push(margin);

if (!listElm || type != lastType) {


listElm = dom.create(type);
dom.insertAfter(listElm, p);
} else {
// Nested list element
if (margin > lastMargin) {
listElm =
li.appendChild(dom.create(type));
} else if (margin < lastMargin) {
// Find parent level based on margin
value
idx = tinymce.inArray(levels, margin);
parents =
dom.getParents(listElm.parentNode, type);
listElm = parents[parents.length - 1 -
idx] || listElm;
}
}

// Remove middot or number spans if they exists


each(dom.select('span', p), function(span) {
var html = span.innerHTML.replace(/<\/?\w+
[^>]*>/gi, '');
// Remove span with the middot or the number
if (type == 'ul' &&
/^__MCE_ITEM__[\u2022\u00b7\u00a7\u00d8o\u25CF]/.test(html))
dom.remove(span);
else if (/^__MCE_ITEM__[\s\S]*\w+\.
(&nbsp;|\u00a0)*\s*/.test(html))
dom.remove(span);
});

html = p.innerHTML;

// Remove middot/list items


if (type == 'ul')
html = p.innerHTML.replace(/__MCE_ITEM__/g,
'').replace(/^[\u2022\u00b7\u00a7\u00d8o\u25CF]\s*(&nbsp;|\u00a0)+\s*/, '');
else
html = p.innerHTML.replace(/__MCE_ITEM__/g,
'').replace(/^\s*\w+\.(&nbsp;|\u00a0)+\s*/, '');

// Create li and add paragraph data into the new li


li = listElm.appendChild(dom.create('li', 0, html));
dom.remove(p);

lastMargin = margin;
lastType = type;
} else
listElm = lastMargin = 0; // End list element
});

// Remove any left over makers


html = o.node.innerHTML;
if (html.indexOf('__MCE_ITEM__') != -1)
o.node.innerHTML = html.replace(/__MCE_ITEM__/g, '');
},

/**
* Inserts the specified contents at the caret position.
*/
_insert : function(h, skip_undo) {
var ed = this.editor, r = ed.selection.getRng();

// First delete the contents seems to work better on WebKit when


the selection spans multiple list items or multiple table cells.
if (!ed.selection.isCollapsed() && r.startContainer !=
r.endContainer)
ed.getDoc().execCommand('Delete', false, null);

ed.execCommand('mceInsertContent', false, h, {skip_undo :


skip_undo});
},

/**
* Instead of the old plain text method which tried to re-create a
paste operation, the
* new approach adds a plain text mode toggle switch that changes the
behavior of paste.
* This function is passed the same input that the regular paste plugin
produces.
* It performs additional scrubbing and produces (and inserts) the
plain text.
* This approach leverages all of the great existing functionality in
the paste
* plugin, and requires minimal changes to add the new functionality.
* Speednet - June 2009
*/
_insertPlainText : function(ed, dom, h) {
var i, len, pos, rpos, node, breakElms, before, after,
w = ed.getWin(),
d = ed.getDoc(),
sel = ed.selection,
is = tinymce.is,
inArray = tinymce.inArray,
linebr = getParam(ed, "paste_text_linebreaktype"),
rl = getParam(ed, "paste_text_replacements");

function process(items) {
each(items, function(v) {
if (v.constructor == RegExp)
h = h.replace(v, "");
else
h = h.replace(v[0], v[1]);
});
};

if ((typeof(h) === "string") && (h.length > 0)) {


// If HTML content with line-breaking tags, then remove all
cr/lf chars because only tags will break a line
if (/<(?:p|br|h[1-6]|ul|ol|dl|table|t[rdh]|div|blockquote|
fieldset|pre|address|center)[^>]*>/i.test(h)) {
process([
/[\n\r]+/g
]);
} else {
// Otherwise just get rid of carriage returns (only
need linefeeds)
process([
/\r+/g
]);
}

process([
[/<\/(?:p|h[1-6]|ul|ol|dl|table|div|blockquote|
fieldset|pre|address|center)>/gi, "\n\n"], // Block tags get a blank
line after them
[/<br[^>]*>|<\/tr>/gi, "\n"], //
Single linebreak for <br /> tags and table rows
[/<\/t[dh]>\s*<t[dh][^>]*>/gi, "\t"], //
Table cells get tabs betweem them
/<[a-z!\/?][^>]*>/gi,
// Delete all remaining tags
[/&nbsp;/gi, " "],
// Convert non-break spaces to regular spaces (remember, *plain text*)
[/(?:(?!\n)\s)*(\n+)(?:(?!\n)\s)*/gi, "$1"], //
Cool little RegExp deletes whitespace around linebreak chars.
[/\n{3,}/g, "\n\n"],
// Max. 2 consecutive linebreaks
/^\s+|\s+$/g
// Trim the front & back
]);

h = dom.decode(tinymce.html.Entities.encodeRaw(h));

// Delete any highlighted text before pasting


if (!sel.isCollapsed()) {
d.execCommand("Delete", false, null);
}

// Perform default or custom replacements


if (is(rl, "array") || (is(rl, "array"))) {
process(rl);
}
else if (is(rl, "string")) {
process(new RegExp(rl, "gi"));
}

// Treat paragraphs as specified in the config


if (linebr == "none") {
process([
[/\n+/g, " "]
]);
}
else if (linebr == "br") {
process([
[/\n/g, "<br />"]
]);
}
else {
process([
/^\s+|\s+$/g,
[/\n\n/g, "</p><p>"],
[/\n/g, "<br />"]
]);
}

// This next piece of code handles the situation where


we're pasting more than one paragraph of plain
// text, and we are pasting the content into the middle of
a block node in the editor. The block
// node gets split at the selection point into "Para A" and
"Para B" (for the purposes of explaining).
// The first paragraph of the pasted text is appended to
"Para A", and the last paragraph of the
// pasted text is prepended to "Para B". Any other
paragraphs of pasted text are placed between
// "Para A" and "Para B". This code solves a host of
problems with the original plain text plugin and
// now handles styles correctly. (Pasting plain text into
a styled paragraph is supposed to make the
// plain text take the same style as the existing
paragraph.)
if ((pos = h.indexOf("</p><p>")) != -1) {
rpos = h.lastIndexOf("</p><p>");
node = sel.getNode();
breakElms = []; // Get list of elements to
break
do {
if (node.nodeType == 1) {
// Don't break tables and break at body
if (node.nodeName == "TD" ||
node.nodeName == "BODY") {
break;
}

breakElms[breakElms.length] = node;
}
} while (node = node.parentNode);

// Are we in the middle of a block node?


if (breakElms.length > 0) {
before = h.substring(0, pos);
after = "";

for (i=0, len=breakElms.length; i<len; i++) {


before += "</" +
breakElms[i].nodeName.toLowerCase() + ">";
after += "<" +
breakElms[breakElms.length-i-1].nodeName.toLowerCase() + ">";
}

if (pos == rpos) {
h = before + after + h.substring(pos+7);
}
else {
h = before + h.substring(pos+4, rpos+4) +
after + h.substring(rpos+7);
}
}
}

// Insert content at the caret, plus add a marker for


repositioning the caret
ed.execCommand("mceInsertRawHTML", false, h + '<span
id="_plain_text_marker">&nbsp;</span>');

// Reposition the caret to the marker, which was placed


immediately after the inserted content.
// Needs to be done asynchronously (in window.setTimeout)
or else it doesn't work in all browsers.
// The second part of the code scrolls the content up if
the caret is positioned off-screen.
// This is only necessary for WebKit browsers, but it
doesn't hurt to use for all.
window.setTimeout(function() {
var marker = dom.get('_plain_text_marker'),
elm, vp, y, elmHeight;

sel.select(marker, false);
d.execCommand("Delete", false, null);
marker = null;

// Get element, position and height


elm = sel.getStart();
vp = dom.getViewPort(w);
y = dom.getPos(elm).y;
elmHeight = elm.clientHeight;

// Is element within viewport if not then scroll it


into view
if ((y < vp.y) || (y + elmHeight > vp.y + vp.h)) {
d.body.scrollTop = y < vp.y ? y : y - vp.h +
25;
}
}, 0);
}
},

/**
* This method will open the old style paste dialogs. Some users might
want the old behavior but still use the new cleanup engine.
*/
_legacySupport : function() {
var t = this, ed = t.editor;

// Register command(s) for backwards compatibility


ed.addCommand("mcePasteWord", function() {
ed.windowManager.open({
file: t.url + "/pasteword.htm",
width: parseInt(getParam(ed, "paste_dialog_width")),
height: parseInt(getParam(ed,
"paste_dialog_height")),
inline: 1
});
});

if (getParam(ed, "paste_text_use_dialog")) {
ed.addCommand("mcePasteText", function() {
ed.windowManager.open({
file : t.url + "/pastetext.htm",
width: parseInt(getParam(ed,
"paste_dialog_width")),
height: parseInt(getParam(ed,
"paste_dialog_height")),
inline : 1
});
});
}

// Register button for backwards compatibility


ed.addButton("pasteword", {title : "paste.paste_word_desc", cmd :
"mcePasteWord"});
}
});

// Register plugin
tinymce.PluginManager.add("paste", tinymce.plugins.PastePlugin);
})();
;// Define variables needed by core/core.js.
var _wrs_int_conf_file = "integration/configurationjs.php";
var _wrs_int_conf_async = true;
var _wrs_baseURL;

// Stats editor (needed by core/editor.js).


var _wrs_conf_editor = "TinyMCE";
// Define _wrs_conf_path (path where configuration is found).
if (typeof _wrs_isMoodle24 == 'undefined') {
_wrs_baseURL = tinymce.baseURL;
_wrs_conf_path = _wrs_baseURL + '/plugins/tiny_mce_wiris/'; // TODO use the
same variable name always.
}else{
var base = tinymce.baseURL;
var search = 'lib/editor/tinymce';
var pos = base.indexOf(search);
_wrs_baseURL = tinymce.baseURL.substr(0, pos + search.length);
_wrs_conf_path = _wrs_baseURL + '/plugins/tiny_mce_wiris/tinymce/'; // TODO use
the same variable name always
_wrs_int_conf_async = false; // For sure!
}

var _wrs_int_path = wrs_intPath(_wrs_int_conf_file, _wrs_conf_path);

// Load configuration synchronously.


if (!_wrs_int_conf_async) {
var httpRequest = typeof XMLHttpRequest != 'undefined' ? new XMLHttpRequest() :
new ActiveXObject('Microsoft.XMLHTTP');
var configUrl = (_wrs_int_conf_file.indexOf('/') == 0 ||
_wrs_int_conf_file.indexOf('http') == 0) ? _wrs_int_conf_file : _wrs_conf_path +
'/' + _wrs_int_conf_file;
httpRequest.open('GET', configUrl, false);
httpRequest.send(null);
eval(httpRequest.responseText);
}

var _wrs_conf_pluginBasePath = _wrs_conf_path;

/* Vars */
var _wrs_int_editorIcon;
var _wrs_int_CASIcon;
var _wrs_int_temporalIframe;
var _wrs_int_temporalElementIsIframe;
var _wrs_int_window;
var _wrs_int_window_opened = false;
var _wrs_int_temporalImageResizing;
var _wrs_int_wirisProperties;
var _wrs_int_directionality;
// Custom Editors.
var _wrs_int_customEditors = {chemistry : {name: 'Chemistry', toolbar :
'chemistry', icon : 'chem.png', enabled : false, confVariable :
'_wrs_conf_chemEnabled'}}

// Variable to control first wrs_initParse call.


var _wrs_int_initParsed = false;

// Core added to queue.


var _wrs_addCoreQueue = typeof _wrs_addCoreQueue == 'undefined' ? false :
_wrs_addCoreQueue;

/* Plugin integration */
(function () {
tinymce.create('tinymce.plugins.tiny_mce_wiris', {
init: function (editor, url) {
// Including core.js
// First of all: recalculating _wrs_conf_path if WIRIS plugin has been
loaded as an external plugin.
// Cant access editor params since now.
if (typeof editor.getParam('external_plugins') != 'undefined' && typeof
editor.getParam('external_plugins')['tiny_mce_wiris'] != 'undefined') {
var external_url = editor.getParam('external_plugins')
['tiny_mce_wiris'];
_wrs_conf_path =
external_url.substring(0,external_url.lastIndexOf("/") + 1)
// New int path.
// Absolute URL path needed: integration files are in the same
external_url domain.
_wrs_int_conf_file = _wrs_conf_path.split('/')[0] + '//' +
_wrs_conf_path.split('/')[2] + _wrs_int_conf_file;
_wrs_int_path = wrs_intPath(_wrs_int_conf_file, _wrs_conf_path);
}
if (typeof _wrs_conf_hostPlatform != 'undefined' &&
_wrs_conf_hostPlatform == 'Moodle' && _wrs_conf_versionPlatform < 2013111800) {
_wrs_int_editorIcon = _wrs_conf_path +
'icons/tinymce3/formula.png';
_wrs_int_CASIcon = _wrs_conf_path + 'icons/tinymce3/cas.png';
} else {
_wrs_int_editorIcon = _wrs_conf_path + 'icons/formula.png';
_wrs_int_CASIcon = _wrs_conf_path + 'icons/cas.png';
}
var element;

// Fix a Moodle 2.4 bug. data-mathml was lost without this.


if (typeof _wrs_isMoodle24 !== 'undefined' && _wrs_isMoodle24){
editor.settings.extended_valid_elements += ',img[*]';

// Conflict between tinyMCE Moodle scriptLoader. Create a new one.


// When multiple editors are loaded (an essay for example)
// Moodle call scriptLoader multiple times. _wrs_addCoreQueue
global variable avoid core multiple loading.
if (!_wrs_addCoreQueue) {
var scriptLoader = new tinymce.dom.ScriptLoader();
scriptLoader.add(_wrs_conf_path + 'core/core.js');
scriptLoader.loadQueue();
_wrs_addCoreQueue = true;
}
} else {
if (!_wrs_addCoreQueue) {
tinymce.ScriptLoader.load(_wrs_conf_path + 'core/core.js');
tinymce.ScriptLoader.loadQueue();
_wrs_addCoreQueue = true;
}
}

// On inline mode, we can't recover unfiltered text


// mathml tags must be added to editor valid_elements.
if (editor.inline) {
editor.settings.extended_valid_elements +=
",math[*],menclose[*],merror[*],mfenced[*],mfrac[*],mglyph[*],mi[*],mlabeledtr[*],m
multiscripts[*],mn[*],mo[*],mover[*],mpadded[*],mphantom[*],mroot[*],mrow[*],ms[*],
mspace[*],msqrt[*],mstyle[*],msub[*],msubsup[*],msup[*],mtable[*],mtd[*],mtext[*],m
tr[*],munder[*],munderover[*],semantics[*],maction[*]";
editor.settings.extended_valid_elements += ",annotation[*]"; //
LaTeX parse.
}

var onInit = function (editor) {


var editorElement = editor.getElement();
var content = ('value' in editorElement) ? editorElement.value :
editorElement.innerHTML;

function whenDocReady() {
if (window.wrs_initParse && typeof _wrs_conf_plugin_loaded !=
'undefined') {
var language = editor.getParam('language');
_wrs_int_directionality =
editor.getParam('directionality');

if ('wiriseditorparameters' in editor.settings) {
_wrs_int_wirisProperties =
editor.settings['wiriseditorparameters'];
} else {
_wrs_int_wirisProperties = {
'bgColor': editor.settings['wirisimagebgcolor'],
'symbolColor':
editor.settings['wirisimagesymbolcolor'],
'transparency':
editor.settings['wiristransparency'],
'fontSize': editor.settings['wirisimagefontsize'],
'numberColor':
editor.settings['wirisimagenumbercolor'],
'identColor':
editor.settings['wirisimageidentcolor'],
'color' : editor.settings['wirisimagecolor'],
'dpi' : editor.settings['wirisdpi'],
'backgroundColor' :
editor.settings['wirisimagebackgroundcolor'],
'fontFamily' : editor.settings['wirisfontfamily']
};
}

if (editor.settings['wirisformulaeditorlang']) {
language = editor.settings['wirisformulaeditorlang'];
}

// Bug fix: In Moodle2.x when TinyMCE is set to full screen


// the content doesn't need to be filtered.
if (!editor.getParam('fullscreen_is_enabled')){
editor.setContent(wrs_initParse(content, language));
// Init parsing OK. If a setContent method is called
// wrs_initParse is called again.
// Now if source code is edited the returned code is
parsed.
_wrs_int_initParsed = true;
}

if (!editor.inline) {
element = editor.getContentAreaContainer().firstChild;
wrs_initParseImgToIframes(element.contentWindow);
wrs_addIframeEvents(element, function (iframe, element)
{
wrs_int_doubleClickHandler(editor, iframe, true,
element);
}, wrs_int_mousedownHandler, wrs_int_mouseupHandler);
// Attaching obsevers to wiris images.
if (typeof wrs_observer != 'undefined') {

Array.prototype.forEach.call(element.contentDocument.getElementsByClassName(_wrs_co
nf_imageClassName), function(wirisImages){
wrs_observer.observe(wirisImages,
wrs_observer_config);
});
}
} else { // Inline.
element = editorElement;
wrs_addElementEvents(element, function (div, element) {
wrs_int_doubleClickHandler(editor, div, false,
element);
}, wrs_int_mousedownHandler, wrs_int_mouseupHandler);
// Attaching obsevers to wiris images.

Array.prototype.forEach.call(document.getElementsByClassName(_wrs_conf_imageClassNa
me), function(wirisImages) {
wrs_observer.observe(wirisImages,
wrs_observer_config);
});
}

}
else {
setTimeout(whenDocReady, 50);
}
}

whenDocReady();
}

if ('onInit' in editor) {
editor.onInit.add(onInit);
}
else {
editor.on('init', function () {
onInit(editor);
});
}

var onSave = function (editor, params) {


if (typeof _wrs_conf_plugin_loaded !== 'undefined') {
var language = editor.getParam('language');
_wrs_int_directionality = editor.getParam('directionality');

if (editor.settings['wirisformulaeditorlang']) {
language = editor.settings['wirisformulaeditorlang'];
}

params.content = wrs_endParse(params.content,
_wrs_int_wirisProperties, language);
}
}

if ('onSaveContent' in editor) {
editor.onSaveContent.add(onSave);
}
else {
editor.on('saveContent', function (params) {
onSave(editor, params);
});
}

if ('onGetContent' in editor) {
editor.onGetContent.add(onSave);
} else {
editor.on('getContent', function(params) {
onSave(editor, params);
});
}

if ('onBeforeSetContent' in editor) {
editor.onBeforeSetContent.add(function(e,params) {
if (_wrs_int_initParsed) {
params.content = wrs_initParse(params.content,
editor.getParam('language'));
}
});
} else {
editor.on('beforeSetContent', function(params){
if (_wrs_int_initParsed) {
params.content = wrs_initParse(params.content,
editor.getParam('language'));
}
});
}

if (_wrs_int_conf_async || _wrs_conf_editorEnabled) {
editor.addCommand('tiny_mce_wiris_openFormulaEditor', function () {
if ('wiriseditorparameters' in editor.settings) {
_wrs_int_wirisProperties =
editor.settings['wiriseditorparameters'];
} else {
_wrs_int_wirisProperties = {
'bgColor': editor.settings['wirisimagebgcolor'],
'symbolColor':
editor.settings['wirisimagesymbolcolor'],
'transparency': editor.settings['wiristransparency'],
'fontSize': editor.settings['wirisimagefontsize'],
'numberColor':
editor.settings['wirisimagenumbercolor'],
'identColor': editor.settings['wirisimageidentcolor'],
'color' : editor.settings['wirisimagecolor'],
'dpi' : editor.settings['wirisdpi'],
'backgroundColor' :
editor.settings['wirisimagebackgroundcolor'],
'fontFamily' : editor.settings['wirisfontfamily']
};
}

var language = editor.getParam('language');


_wrs_int_directionality = editor.getParam('directionality');

if (editor.settings['wirisformulaeditorlang']) {
language = editor.settings['wirisformulaeditorlang'];
}

wrs_int_openNewFormulaEditor(element, language, editor.inline ?


false : true);
});

editor.addButton('tiny_mce_wiris_formulaEditor', {
title: 'Math editor',
cmd: 'tiny_mce_wiris_openFormulaEditor',
image: _wrs_int_editorIcon
});
}

if (_wrs_int_conf_async || _wrs_conf_CASEnabled) {
editor.addCommand('tiny_mce_wiris_openCAS', function () {
var language = editor.settings.language;

if (editor.settings['wirisformulaeditorlang']) {
language = editor.settings['wirisformulaeditorlang'];
}

wrs_int_openNewCAS(element, language, editor.inline ? false :


true);
});

editor.addButton('tiny_mce_wiris_CAS', {
title: 'Calculator',
cmd: 'tiny_mce_wiris_openCAS',
image: _wrs_int_CASIcon
});
}

// Dynamic customEditors buttons.


for (var key in _wrs_int_customEditors) {
if (_wrs_int_customEditors.hasOwnProperty(key)) {
if (_wrs_int_conf_async ||
window[_wrs_int_customEditors[key].confVariable]) {

var cmd = 'tiny_mce_wiris_openFormulaEditor' +


_wrs_int_customEditors[key].name;
editor.addCommand(cmd, function () {
if ('wiriseditorparameters' in editor.settings) {
_wrs_int_wirisProperties =
editor.settings['wiriseditorparameters'];
} else {
_wrs_int_wirisProperties = {
'bgColor':
editor.settings['wirisimagebgcolor'],
'symbolColor':
editor.settings['wirisimagesymbolcolor'],
'transparency':
editor.settings['wiristransparency'],
'fontSize':
editor.settings['wirisimagefontsize'],
'numberColor':
editor.settings['wirisimagenumbercolor'],
'identColor':
editor.settings['wirisimageidentcolor'],
'color' : editor.settings['wirisimagecolor'],
'dpi' : editor.settings['wirisdpi'],
'backgroundColor' :
editor.settings['wirisimagebackgroundcolor'],
'fontFamily' :
editor.settings['wirisfontfamily']
};
}

var language = editor.getParam('language');


_wrs_int_directionality =
editor.getParam('directionality');

if (editor.settings['wirisformulaeditorlang']) {
language =
editor.settings['wirisformulaeditorlang'];
}
wrs_int_enableCustomEditor(key);
wrs_int_openNewFormulaEditor(element, language,
editor.inline ? false : true);
});

var imagePath;
if (typeof _wrs_conf_hostPlatform != 'undefined' &&
_wrs_conf_hostPlatform == 'Moodle' && _wrs_conf_versionPlatform < 2013111800) {
imagePath = _wrs_conf_path + 'icons/tinymce3/' +
_wrs_int_customEditors[key].icon;
} else {
imagePath = _wrs_conf_path + 'icons/' +
_wrs_int_customEditors[key].icon;
}

editor.addButton('tiny_mce_wiris_formulaEditor' +
_wrs_int_customEditors[key].name, {
title: _wrs_int_customEditors[key].name + ' editor',
cmd: cmd,
image: imagePath
});

}
}
}
},

// All versions.
getInfo: function () {
return {
longname : 'tiny_mce_wiris',
author : 'Maths for More',
authorurl : 'http://www.wiris.com',
infourl : 'http://www.wiris.com',
version : '1.0'
};
}
});

tinymce.PluginManager.add('tiny_mce_wiris', tinymce.plugins.tiny_mce_wiris);
})();

function wrs_intPath(intFile, confPath) {


var intPath = intFile.split("/");
intPath.pop();
intPath = intPath.join("/");
intPath = intPath.indexOf("/") == 0 || intPath.indexOf("http") == 0 ? intPath :
confPath + intPath;
return intPath;
}

/**
* Opens formula editor.
* @param object element Target
* @param string language
* @param bool isIframe
*/
function wrs_int_openNewFormulaEditor(element, language, isIframe) {
if (_wrs_int_window_opened) {
_wrs_int_window.focus();
}
else {
_wrs_int_window_opened = true;
_wrs_isNewElement = true;
_wrs_int_temporalIframe = element;
_wrs_int_temporalElementIsIframe = isIframe;
_wrs_int_window = wrs_openEditorWindow(language, element, isIframe);
}
}

/**
* Opens CAS.
* @param object element Target
* @param string language
* @param bool isIframe
*/
function wrs_int_openNewCAS(element, language, isIframe) {
if (_wrs_int_window_opened) {
_wrs_int_window.focus();
}
else {
_wrs_int_window_opened = true;
_wrs_isNewElement = true;
_wrs_int_temporalIframe = element;
_wrs_int_temporalElementIsIframe = isIframe;
_wrs_int_window = wrs_openCASWindow(element, isIframe, language);
}
}

/**
* Handles a double click on the target.
* @param object editor tinymce active editor
* @param object target Target
* @param object element Element double clicked
* @param bool isIframe target is an iframe or not
*/
function wrs_int_doubleClickHandler(editor, target, isIframe, element) {
// This loop allows the double clicking on the formulas represented with
span's.

while (!wrs_containsClass(element, 'Wirisformula') && !


wrs_containsClass(element, 'Wiriscas') && element.parentNode) {
element = element.parentNode;
}

var elementName = element.nodeName.toLowerCase();

if (elementName == 'img' || elementName == 'iframe' || elementName == 'span') {


if (wrs_containsClass(element, 'Wirisformula')) {
if (customEditor = element.getAttribute('data-custom-editor')) {
if (window[_wrs_int_customEditors[customEditor].confVariable]) {
wrs_int_enableCustomEditor(customEditor);
}
}
if ('wiriseditorparameters' in editor.settings) {
_wrs_int_wirisProperties =
editor.settings['wiriseditorparameters'];
} else {
_wrs_int_wirisProperties = {
'bgColor': editor.settings['wirisimagebgcolor'],
'symbolColor': editor.settings['wirisimagesymbolcolor'],
'transparency': editor.settings['wiristransparency'],
'fontSize': editor.settings['wirisimagefontsize'],
'numberColor': editor.settings['wirisimagenumbercolor'],
'identColor': editor.settings['wirisimageidentcolor'],
'color' : editor.settings['wirisimagecolor'],
'dpi' : editor.settings['wirisdpi'],
'backgroundColor' :
editor.settings['wirisimagebackgroundcolor'],
'fontFamily' : editor.settings['wirisfontfamily']
};
}

if (!_wrs_int_window_opened) {
var language = editor.settings.language;

if (editor.settings['wirisformulaeditorlang']) {
language = editor.settings['wirisformulaeditorlang'];
}

_wrs_temporalImage = element;
wrs_int_openExistingFormulaEditor(target, isIframe, language);
}
else {
_wrs_int_window.focus();
}
}
else if (wrs_containsClass(element, 'Wiriscas')) {
if (!_wrs_int_window_opened) {
var language = editor.settings.language;

if (editor.settings['wirisformulaeditorlang']) {
language = editor.settings['wirisformulaeditorlang'];
}

_wrs_temporalImage = element;
wrs_int_openExistingCAS(target, isIframe, language);
}
else {
_wrs_int_window.focus();
}
}
}
}

/**
* Opens formula editor to edit an existing formula.
* @param object element Target
* @param bool isIframe
*/
function wrs_int_openExistingFormulaEditor(element, isIframe, language) {
_wrs_int_window_opened = true;
_wrs_isNewElement = false;
_wrs_int_temporalIframe = element;
_wrs_int_temporalElementIsIframe = isIframe;
_wrs_int_window = wrs_openEditorWindow(language, element, isIframe);
}

/**
* Opens CAS to edit an existing formula.
* @param object element Target
* @param bool isIframe
* @param string language
*/
function wrs_int_openExistingCAS(element, isIframe, language) {
_wrs_int_window_opened = true;
_wrs_isNewElement = false;
_wrs_int_temporalIframe = element;
_wrs_int_temporalElementIsIframe = isIframe;
_wrs_int_window = wrs_openCASWindow(element, isIframe, language);
}

/**
* Handles a mouse down event on the iframe.
* @param object iframe Target
* @param object element Element mouse downed
*/
function wrs_int_mousedownHandler(iframe, element) {
if (element.nodeName.toLowerCase() == 'img') {
if (wrs_containsClass(element, 'Wirisformula') ||
wrs_containsClass(element, 'Wiriscas')) {
_wrs_int_temporalImageResizing = element;
}
}
}

/**
* Handles a mouse up event on the iframe.
*/
function wrs_int_mouseupHandler() {
if (_wrs_int_temporalImageResizing) {
setTimeout(function () {
wrs_fixAfterResize(_wrs_int_temporalImageResizing);
}, 10);
}
}

/**
* Calls wrs_updateFormula with well params.
* @param string mathml
*/
function wrs_int_updateFormula(mathml, editMode, language) {
// Var _wrs_int_wirisProperties contains some js render params. Since mathml
can support render params, js params should be send only to editor, not to render.
if (_wrs_int_temporalElementIsIframe) {
wrs_updateFormula(_wrs_int_temporalIframe.contentWindow,
_wrs_int_temporalIframe.contentWindow, mathml, {}, editMode, language);
}
else {
wrs_updateFormula(_wrs_int_temporalIframe, window, mathml, {}, editMode,
language);
}

if (typeof tinymce.activeEditor.fire != 'undefined') {


tinymce.activeEditor.fire('change');
}
}

/**
* Calls wrs_updateCAS with well params.
* @param string appletCode
* @param string image
* @param int width
* @param int height
*/
function wrs_int_updateCAS(appletCode, image, width, height) {
if (_wrs_int_temporalElementIsIframe) {
wrs_updateCAS(_wrs_int_temporalIframe.contentWindow,
_wrs_int_temporalIframe.contentWindow, appletCode, image, width, height);
} else {
wrs_updateCAS(_wrs_int_temporalIframe, window, appletCode, image, width,
height);
}
}

/**
* Handles window closing.
*/
function wrs_int_notifyWindowClosed() {
_wrs_int_window_opened = false;
}
;var sTinymceOpts = {};

function sTinyMceIsIE10(){
// TRUE if user is using IE10
var t = tinymce, d = document, na = navigator, ua = na.userAgent;
return !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && /MSIE [10]/.test(ua)
&& (/Explorer/gi).test(na.appName);
}

Drupal.behaviors.s_tinymce = function(context) {
// no settings
if(!Drupal.settings.s_tinymce)
return;

var tinymce_settings = Drupal.settings.s_tinymce;


// no editors
if(!tinymce_settings.editors || typeof tinymce_settings.editors != 'object')
return;
// already loaded
if( tinyMCE.activeEditor ){
var activeEditorId = tinyMCE.activeEditor.editorId;
// active editor is in the list of currently configured editors, no need to
load it
if( activeEditorId && activeEditorId != 'edit-tme-textarea' ){
var needs_config = false;
$.each( tinymce_settings.editors , function( obj_id , editors ){
if( $.inArray( activeEditorId , editors ) == -1 )
needs_config = true;
});

if(!needs_config)
return;
}
}

//if the html button is active, don't reload this, we don't need it
if($('#tinymce-toggle-html', context).hasClass('tinymce-toggle-active'))
return;

// setup tinymce and load


var textFieldIds = [];
$.each( tinymce_settings.editors , function( form_id , editors ){
$.each( editors , function( index , field_id ){
var richTextField = $('#' + field_id);
if(!richTextField.hasClass('sTinymce-processed')){
textFieldIds.push(field_id);
richTextField.addClass('sTinymce-processed');
}
});
});

// if all of the potential rich text fields have been processed, then we don't
need/want to process them again
if(!textFieldIds.length){
return;
}

// attach the editor toggle to our custom buttons


var htmlToggleBtn = $(".tinymce-html-btn",context),
visualToggleBtn = $(".tinymce-visual-btn",context);
htmlToggleBtn.each(function(){
$(this).unbind('click').bind('click',function(){
if( tinyMCE.activeEditor == null )
return;

var editor_id = $(this).attr('editor_id');


var form = $(this).parents('form');
var editorTextarea = $("textarea#"+editor_id, form);

//tinyMCE.get(editor_id).save();
tinyMCE.execCommand('mceRemoveControl', false, editor_id);
$('#'+editor_id).focus();

$("#tinymce-toolbar-placeholder",form).css('display','block');
editorTextarea.css('visibility', 'visible').focus();
$(".tinymce-ext-buttons",form).hide();
$("#topic-content-select-wrapper",form).hide();

if(editorTextarea.closest('.s-tme-with-attachments')){
// if the rich text has an associated attachment row, add a filler div for
the HTML editor
var attachmentRow = editorTextarea.siblings('.s-tme-attachment-row');
if(!attachmentRow.length){
var attachmentRow = $('<div/>').addClass('s-tme-attachment-row');
attachmentRow.insertAfter(editorTextarea);
}
attachmentRow.show();
}

$(this).addClass('tinymce-toggle-active');
$(this).siblings(".tinymce-visual-btn").removeClass('tinymce-toggle-active');
sPopupsResizeCenter();
});
});

visualToggleBtn.each(function(){
$(this).unbind('click').bind('click',function() {

var editor_id = $(this).attr('editor_id');


var form = $(this).parents('form');

if( tinyMCE.activeEditor && tinyMCE.activeEditor.editorId == editor_id )


return;

var editorTextarea = $("textarea#"+editor_id, form);

tinyMCE.execCommand('mceAddControl', true , editor_id );

$("#tinymce-toolbar-placeholder",form).hide();
editorTextarea.css('visibility', 'hidden');
$(".tinymce-ext-buttons",form).show();
$("#topic-content-select-wrapper",form).show();

if(editorTextarea.closest('.s-tme-with-attachments')){
// if the rich text has an associated attachment row, add a filler div for
the HTML editor
editorTextarea.siblings('.s-tme-attachment-row').hide();
}

sTinymcePopup.updateEditorHeight();

$(this).addClass('tinymce-toggle-active');
$(this).siblings(".tinymce-html-btn").removeClass('tinymce-toggle-active');
sPopupsResizeCenter();
});
});

htmlToggleBtn.add(visualToggleBtn).tipsy({
gravity: 's',
title: function(){
return $(this).attr('original-title');
}
});

// bind to the custom content buttons


$(".scontent-image-insert",context).each(function(){
$(this)
.unbind('click')
.bind('click',function(){
tinyMCE.activeEditor.execCommand('sContentImageInsert');
});
});

$(".scontent-media-insert",context).each(function(){
$(this)
.unbind('click')
.bind('click',function(){
tinyMCE.activeEditor.execCommand('sContentMediaInsert');
});
});

$(".scontent-formula-insert",context).each(function(){
$(this)
.unbind('click')
.bind('click',function(){
tinyMCE.activeEditor.execCommand('sContentFormulaInsert');
});
});

$(".scontent-latex-insert",context).each(function(){
$(this)
.unbind('click')
.bind('click',function(){
tinyMCE.activeEditor.execCommand('sContentLatexInsert');
});
});

// remove the loader


$(".s-tinymce-loader",context).each(function(){
$(this).remove();
});

/// for popups


if($('#'+textFieldIds[0] , context ).parents('.popups-box').length > 0) {

$(document).data('sTinymcePopupActiveEditors',textFieldIds.join(","));

sTinymceInit({mode: 'none'});

$
(document).unbind('popups_open_path_done.s_tinymce').bind('popups_open_path_done.s_
tinymce',function() {
var active_editor_id = $(document).data('sTinymcePopupActiveEditors');
if(!active_editor_id)
return;

var popup = Popups.activePopup();


var tme_popup_parent = $('#'+active_editor_id).parents('.popups-box');

// active editor is not in this popup


if(popup && popup.id && popup.id != tme_popup_parent.attr('id')){
return;
}
tinyMCE.onAddEditor.add(function(tmce, ed){
if(ed.id == active_editor_id){
ed.onInit.add(function(){
sPopupsResizeCenter();
});
}
});

// make sure the in-page editor is disabled


tinyMCE.execCommand('mceRemoveControl',false, 'edit-tme-textarea');

// enable the active editor in the popup


tinyMCE.execCommand('mceAddControl', false , active_editor_id );

sTinymcePopup.initializeEditors(textFieldIds, context);
});

$(document).bind('popups_before_submit',function( event , formData, $form,


options ){

// get the active editor ids, match then with the name attributes
// and then inject editor content into the formData array

tinyMCE.triggerSave();

var editor_ids = new Array();


var editor_names = new Array();

$.each( tinyMCE.editors , function( index , val ){


editor_ids.push(val.editorId);
});

if(editor_ids.length==0)
return;

$('textarea',$form).each(function(){
var txt_id = $(this).attr('id');
if( $.inArray( txt_id , editor_ids ) != -1 ){
var txt_name = $(this).attr('name');
editor_names.push(txt_name);
}
});

if(editor_names.length==0)
return;

// this is singleton but works for now


// note that at this point all the nice tinyMCE 'get' functions don't work
var editor_content =
tinyMCE.get( tinyMCE.activeEditor.editorId ).getContent();

$.each( formData , function( index , form_field ){


if( $.inArray( form_field.name , editor_names ) != -1 )
formData[ index ].value = editor_content;
});

var active_editor_id = $(document).data('sTinymcePopupActiveEditors');


tinyMCE.execCommand('mceRemoveControl', true, active_editor_id );
});
$(document).bind('popups_form_success_notdone',function(e,p,d){
// In this flow the popup has been destroyed and recreated, so we need to
reintialize behaviors on editors within the popup
sTinymcePopup.initializeEditors(textFieldIds, context);

var active_editor_id = $(document).data('sTinymcePopupActiveEditors');


if(!active_editor_id)
return;

tinyMCE.execCommand('mceAddControl', true , active_editor_id );

});

$(document).bind('popups_before_remove',function(event,popup,nextActivePopup) {
// only destory if active editor is in currently closing popup
if( typeof popup =='object' && popup.id && typeof tinyMCE == 'object' &&
tinyMCE.activeEditor
&& tinyMCE.activeEditor.s_tinymce_popup == popup.id) {
var editor_id = tinyMCE.activeEditor.editorId;
tinyMCE.activeEditor.execCommand('sContentRemovePopups');
tinyMCE.execCommand('mceFocus', false, editor_id );
tinyMCE.execCommand('mceRemoveControl', false, editor_id );
$(document).data('sTinymcePopupActiveEditors',false);
Drupal.settings.s_tinymce = {};
}
});

} else {
// in-page loading

// remove existing text fields with the same ids.


// this needs to happen when partial-page redraw happens (creating page breaks,
deleting assessment components)
// and another textfield with the same id gets include leaving the old ones
dangling
$.each(textFieldIds, function(k, id){
var existing = tinyMCE.get(id);
if(typeof existing == 'object'){
tinyMCE.remove(existing);
}
});

sTinymceInit( { elements: textFieldIds.join(",") } );


}
}
//var oldVal = 0;
/*
* Note that the oldVal declaration above is intentionally commented out
* It is initiated to 0 in s_assessment_component_fitb.js. The value needs
* to be kept track of between Visual and HTML modes in the editor.
*/
// initialize the editor
var sTinymceInit = function(localOpts) {
var altOpts = typeof Drupal.settings.s_tinymce_alt_opts != 'undefined' ?
Drupal.settings.s_tinymce_alt_opts : {};
var tme_lang = typeof Drupal.settings.s_tinymce != 'undefined' && typeof
Drupal.settings.s_tinymce.language != 'undefined' ?
Drupal.settings.s_tinymce.language : 'en';
var toolbarStyle = 'full';
if(typeof altOpts.toolbar != 'undefined'){
toolbarStyle = altOpts.toolbar;
}
else if(typeof localOpts.toolbar != 'undefined'){
toolbarStyle = localOpts.toolbar;
}
if(typeof toolbarStyle == 'object'){
toolbarStyle = toolbarStyle[0];
}

function editorStripInlineImages(editor) {
editor.onPaste.add(function(ed) {
ed._itemPasted = true;
});

editor.onChange.add(function(ed) {
if (!ed._itemPasted) {
return;
}
ed._itemPasted = null;
var $div = $(document.createElement('div'));
$div.html(ed.getContent());
var inlineMatchStr = "data:image/";
//for some reason [src^="data:image/"] does not work in IE10
var $inlineImgs = $div.find('[src]').filter(function() {
return this.src.substring(0, inlineMatchStr.length).toLowerCase() ===
inlineMatchStr;
});
if (!$inlineImgs.length) {
return;
}
$inlineImgs.remove();
ed.setContent($div.html());
});
}

sTinymceOpts = {
mode: 'exact',
theme: 'advanced',
language: tme_lang,
cache_string: Drupal.settings.s_tinymce && Drupal.settings.s_tinymce.cache ?
Drupal.settings.s_tinymce.cache : '',
elements: '',
relative_urls : false,
remove_script_host : true,
paste_retain_style_properties : "all",
paste_remove_styles_if_webkit: false,
setup: function(ed){
editorStripInlineImages(ed);
ed.onInit.add(function(editor){
$('.mceButton', '#' + editor.id + '_tbl').tipsy({
gravity: 's',
title: function(){
return $(this).attr('original-title');
}
});
var tinymce_popup_parent = $('#'+ed.editorId).parents('.popups-box');
ed.s_tinymce_popup = tinymce_popup_parent.attr('id');
});
},

// toolbars
theme_advanced_layout_manager: "SimpleLayout",
theme_advanced_buttons1: "",
theme_advanced_buttons2: "",
theme_advanced_buttons3: "",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left",
theme_advanced_statusbar_location: "bottom",
theme_advanced_resizing: true,
theme_advanced_resize_horizontal: false,

// tinyMCE normally uses a cookie to remember the users' resized dimensions,


which we do not want
theme_advanced_resizing_use_cookie: false,

// plugins
plugins: "",

apply_source_formatting: true,
extended_valid_elements: 'label[id|class|style],span[id|class|style|cluetip],'
+
'object[classid|codebase|width|height|align],' +
'param[name|value],' +
'embed[quality|type|pluginspage|width|height|src|align],' +
'iframe[src|width|height|name|align|scrolling|frameborder|style|
allowfullscreen],' +
'img[src|alt|width|height|style|id|name|formula|class|align|title|border|
cluetip],' +
// custom td entry so cells don't inherit the paddEmpty option, which puts
&nbsp; inside empty cells when saving
'td[id|class|style|title|lang|dir|onclick|ondblclick|onmousedown|onmouseup|
onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup|abbr|axis|headers|
scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height]',

// for plugins that handle images,embeds,etc.


media_strict: false,
s_dropmenu_menus: {},

//SGY-12656 - Workaround for bizarre pasting of text in Webkit browsers


paste_postprocess: function(pl, o) {
if (tinyMCE.isWebKit) {
var lineHeightRegex = new RegExp('style="line-height: [0-9]*.[0-9]
{10,}px;"', 'ig');
o.node.innerHTML = o.content.replace(lineHeightRegex, '');
}
}
};

// SGY-12389 : If user is using IE10, enable browser spellcheck


// SGY-10594 : Also, if the user is using an iDevice (iPad|iPhone)
if (sTinyMceIsIE10() || tinymce.isIDevice){
sTinymceOpts.browser_spellcheck = true;
}
var fontSizes = false;
switch(toolbarStyle){
// used on updates, discussion posts
case 'discussion':
sTinymceOpts.theme_advanced_resizing_min_height = sTinymceOpts.min_height =
60;
sTinymceOpts.theme_advanced_buttons1 =
"bold,italic,underline,bullist,numlist,indent,outdent,spellchecker,fontsizeselect,i
nsert_content,removeformat";
sTinymceOpts.plugins = "-s_dropmenu,-s_content,spellchecker,-
paste,inlinepopups";
fontSizes = [11, 12, 13, 14];
sTinymceOpts.s_dropmenu_menus = {
insert_content: {
title: Drupal.t('Insert Content'),
items:
sTinymce.getDropMenu('s_insert_link,s_insert_symbol,s_insert_equation')
}
};

if ($('body').hasClass('s-enable-mathml')) {
sTinymceOpts.theme_advanced_buttons1 += ',tiny_mce_wiris_formulaEditor';
sTinymceOpts.plugins += ',tiny_mce_wiris';
}

break;
case 'basic_update':
sTinymceOpts.theme_advanced_buttons1 =
"bold,italic,underline,bullist,numlist,indent,outdent,spellchecker,fontsizeselect,i
nsert_content,removeformat";
sTinymceOpts.plugins = "-s_dropmenu,-s_content,spellchecker,-
paste,inlinepopups";
fontSizes = [11, 12, 13, 14];
sTinymceOpts.s_dropmenu_menus = {
insert_content: {
title: Drupal.t('Insert Content'),
items:
sTinymce.getDropMenu('s_insert_link,s_insert_symbol,s_insert_equation')
}
};
break;
case 'basic_comment':
sTinymceOpts.theme_advanced_resizing_min_height = sTinymceOpts.min_height =
60;
sTinymceOpts.theme_advanced_buttons1 =
"bold,italic,underline,bullist,numlist,indent,outdent,spellchecker";
sTinymceOpts.plugins = "spellchecker,paste";
sTinymceOpts.theme_advanced_buttons1 =
"bold,italic,underline,bullist,numlist,indent,outdent,spellchecker";
break;

// SGY-8174 basic folder description for rich-text enabled schools


case 'basic_folder_description':
sTinymceOpts.theme_advanced_buttons1 =
"bold,italic,underline,bullist,numlist,forecolor,formatting,indent,outdent,justifyl
eft,justifycenter,justifyright,insert_content,spellchecker";
sTinymceOpts.plugins = "-s_dropmenu,-s_content,-
s_image_hover,spellchecker,media,-paste,inlinepopups";
sTinymceOpts.s_dropmenu_menus = {
insert_content: {
title: Drupal.t('Insert Content'),
items:
sTinymce.getDropMenu('s_insert_image,s_insert_link,s_insert_symbol,s_insert_equatio
n,s_insert_latex')
},
formatting: {
title: Drupal.t('Formatting'),
items:
sTinymce.getDropMenu('strikethrough,sup,sub,separator,s_format_remove')
}
};

// prevent tinymce advanced theme from leaving off space in the bottom for a
status bar
sTinymceOpts.theme_advanced_resizing = false;
sTinymceOpts.theme_advanced_statusbar_location = '';
break;

default:
case 'full':
sTinymceOpts.plugins = "-s_dropmenu,-s_content,-s_image_hover,-s_cluetip,-
s_table,spellchecker,media,-paste,table,inlinepopups";
sTinymceOpts.theme_advanced_buttons1 =
"bold,italic,underline,bullist,numlist,forecolor,backcolor,formatting,indent,outden
t,justifyleft,justifycenter,justifyright,s_link,insert_content,s_table_button,spell
checker,formatselect,fontsizeselect";
sTinymceOpts.theme_advanced_buttons2 =
"s_table_border,row_before,row_after,delete_row,col_before,col_after,delete_col,mer
ge_cells,split_cells,delete_table";
sTinymceOpts.s_dropmenu_menus = {
insert_content: {
title: Drupal.t('Insert Content'),
items:
sTinymce.getDropMenu('s_insert_imagemedia,s_insert_link,s_insert_symbol,s_insert_eq
uation,s_insert_latex,s_cluetip_toggle_note')
},
formatting: {
title: Drupal.t('Formatting'),
items:
sTinymce.getDropMenu('strikethrough,sup,sub,separator,s_format_remove')
}
};

if ($('body').hasClass('s-enable-mathml')) {
sTinymceOpts.theme_advanced_buttons1 += ',tiny_mce_wiris_formulaEditor';
sTinymceOpts.plugins += ',tiny_mce_wiris';
}

break;
}

/*
Include resource content menu items if allowed
*/
var sContentInsertMenuItems = sCommonGetSetting('s_tinymce',
's_content_menu_items');
if(sContentInsertMenuItems && (toolbarStyle == 'basic_folder_description' ||
toolbarStyle == 'full')) {
sContentInsertMenuItems =
sTinymceOpts.s_dropmenu_menus.insert_content.items.concat(sContentInsertMenuItems);
sTinymceOpts.s_dropmenu_menus.insert_content.items = sContentInsertMenuItems;
sTinymceOpts.s_dropmenu_menus.insert_content.isMegaDropdown = true;
}

// generate custom font size declaration


sTinymceOpts.theme_advanced_font_sizes = {};
if(fontSizes === false){
fontSizes = [11, 12, 13, 14, 16, 18, 24];
}
$.each(fontSizes, function(k, size){
sTinymceOpts.theme_advanced_font_sizes[size] = {
fontSize: size + 'px'
};
});

sTinymceOpts = $.extend({}, sTinymceOpts, localOpts, altOpts);

tinyMCE.init(sTinymceOpts);
}

if(typeof sTinymce == 'undefined'){


/**
* Contains general utility methods to help deal with TinyMCE instances.
*/
var sTinymce = (function(){
var obj = {};

var dropMenuItems = {
s_insert_imagemedia: {
id: 's_insert_imagemedia',
title: Drupal.t('Image/Media'),
cmd: 'sContentMediaInsert'
},
s_insert_image: {
id: 's_insert_image',
title: Drupal.t('Image'),
cmd: 'sContentImageInsert'
},
s_insert_link: {
id: 's_insert_link',
title: Drupal.t('Link'),
cmd: 'mceLink'
},
s_insert_symbol: {
id: 's_insert_symbol',
title: Drupal.t('Symbol'),
cmd: 'mceCharMap'
},
s_insert_equation: {
id: 's_insert_equation',
title: Drupal.t('Equation'),
cmd: 'sContentFormulaInsert'
},
s_insert_latex: {
id: 's_insert_latex',
title: Drupal.t('LaTeX'),
cmd: 'sContentLatexInsert'
},
s_cluetip_toggle_note: {
id: 's_cluetip_toggle_note',
title: Drupal.t('Tooltip'),
cmd: 'sCluetipToggleNote',
disabled: true // disabled by default and enabled when highlighting texts
and such
},
strikethrough: {
id: 'strikethrough',
title: Drupal.t('Strikethrough'),
cmd: 'strikethrough'
},
sup: {
id: 'sup',
title: Drupal.t('Superscript'),
cmd: 'superscript'
},
sub: {
id: 'sub',
title: Drupal.t('Subscript'),
cmd: 'subscript'
},
separator: {
type: 'separator'
},
s_format_remove: {
id: 's_format_remove',
title: Drupal.t('Clear Formatting'),
cmd: 'removeformat'
}
};

/**
* Allow textareas to properly retrieve the rich text contents before a submit
event
*
* @param object context
*/
obj.updateRichtextContent = function(context){
var editorId = tinyMCE && tinyMCE.activeEditor ?
tinyMCE.activeEditor.editorId : null;
if(editorId){
var editorObj = $('#' + editorId, context);
if(editorObj.length){
// there is a rich text editor in the form, we need to pull its contents
out before submitting
var richTextContent =
tinyMCE.get( tinyMCE.activeEditor.editorId ).getContent();
editorObj.val(richTextContent);
}
}
};

/**
* Parse the menu list config and return a list of menu item objects
*
* @param string menuListConfig comma separated list of menu item ids
*/
obj.getDropMenu = function(menuListConfig){
var list = [];

$.each(menuListConfig.split(','), function(i, id){


if(typeof dropMenuItems[id] != 'undefined'){
list.push(dropMenuItems[id]);
}
});

return list;
};

/**
* Get dom element in editor content body
*
* @param {string} jquerySelector
* @returns {HTMLElement}
*/
obj.findContentBody = function(jquerySelector) {
var tinyMceBody = tinyMCE && tinyMCE.activeEditor &&
tinyMCE.activeEditor.getBody();
if (!tinyMceBody) {
return;
}
return $(jquerySelector, tinyMceBody);
};

/**
* Attach presubmit function to check for any inline base64-encoded images
*
* @param {HTMLElement} formContext
*/
obj.checkInlineImgBeforeSubmit = function(formContext) {
$('input[type=submit]:not(.sTinymce-imgCheck-processed)',
formContext).addClass('sTinymce-imgCheck-processed').each(function() {
var $el = $(this);
$el.data('beforeSubmitHandler', function (e) {
if (!e) {
return;
}
//Check if any element contains inline img
var inlineMatchStr = "data:image/";
var $inlineImgs = sTinymce.findContentBody('[src]').filter(function() {
return this.src.substring(0, inlineMatchStr.length).toLowerCase() ===
inlineMatchStr;
});
if (!($inlineImgs && $inlineImgs.length)) {
return;
}

//stop form submit


e.preventDefault();

$el.closest('form').prepend(
'<div class="messages error"><ul><li>' +
Drupal.t('You must delete images in your post and upload them as
attachments.') +
'</ul></li></div>'
);

}); //end submitHandler


}); //End each submit
};

return obj;
}());
}

if(typeof sTinymcePopup == 'undefined'){


/**
* Contains utility methods to help deal with TinyMCE instances within a popup.
*/
var sTinymcePopup = (function(){
var obj = {};

// defined heights for when toggling between different modes


// this cannot be done in CSS because tinymce has to mathematically determine
the size of iframes
// depending on whether certain menus are showing
obj.viewModeHeights = {
'edit-description-wrapper': { lite: 128, standard: 228 }, //
assignment description
'edit-post-wrapper': { lite: 128, standard: 228 }, //
discussion description
'edit-template-fields-body-wrapper': { lite: 128, standard: 228 } // resource
assignment & discussion description
};

/**
* Initializes behaviors on editors contained within the popup
* @param {Array} textFieldIds An array of strings that are the ids of each RTE
* @param {Element} context
*/
obj.initializeEditors = function(textFieldIds, context) {
//# Initialize the view mode toggle 'lite' | 'standard'
var richTextView = sUserGetUISettings('rte', 'view_mode') == 'standard' ?
'standard' : 'lite';
var supportViewModes = false;
$.each(textFieldIds, function(k, textFieldId){
var textField = $('#' + textFieldId, context);
if(textField.hasClass('s-tinymce-view-modes')){
supportViewModes = true;
var toggleBtn = $('<span class="s-tinymce-toggle-view" />'),
expandText = Drupal.t('Expand'),
minimizeText = Drupal.t('Minimize');
toggleBtn.appendTo(textField.parent());
toggleBtn.click(function(){
sTinymcePopup.setView(sTinymcePopup.getView() == 'standard' ? 'lite' :
'standard');
}).tipsy({
gravity: 's',
title: function(){
return sTinymcePopup.getView() == 'standard' ? minimizeText :
expandText;
}
});
}
});

// determine if the user should see the lite version of tinymce


if(supportViewModes){
sTinymcePopup.setView(richTextView, false);
}
}

/**
* Set the popup view to the specified mode.
*
* @param string mode possible values: standard (default), lite
* @param bool update_settings whether to update the user's preference
*/
obj.setView = function(mode, update_settings){
var possibleValues = {standard: true, lite: true},
popupContainer = this.getPopupContainer();
if(popupContainer){
$.each(possibleValues, function(m, setting){
var className = 's-tinymce-view-' + m;
if(mode == m){
popupContainer.addClass(className);
}
else{
popupContainer.removeClass(className);
}
});

if(typeof update_settings == 'undefined' || update_settings){


sUserSetUISettings('rte', 'view_mode', mode);
}

obj.updateEditorHeight(mode);

sPopupsResizeCenter();
}
};

/**
* Get the current popup view mode
*
* @param string mode
*/
obj.getView = function(){
var mode = null,
popupContainer = this.getPopupContainer();
if(popupContainer){
var match = /s-tinymce-view-([^\s]+)/.exec(popupContainer.attr('class'));
if(match.length > 1){
mode = match[1];
}
}

return mode;
};

/**
* Update the editor height with a preset value when the view mode changes.
*
* @param string mode possible values: standard (default), lite
*/
obj.updateEditorHeight = function(mode){
var ed = tinyMCE.activeEditor;
if(ed){
if(typeof mode == 'undefined'){
mode = obj.getCurrentViewMode();
}
var formItemId = $(ed.contentAreaContainer).closest('.form-
item').attr('id'),
newHeight = null;
if(typeof obj.viewModeHeights[formItemId] == 'object' && typeof
obj.viewModeHeights[formItemId][mode] != 'undefined'){
ed.theme.resizeTo(null, obj.viewModeHeights[formItemId][mode]);
}
}
};

/**
* Get the popup of the current container.
*
* @return object the jquery object of the popup element.
*/
obj.getPopupContainer = function(){
var active_editor_id = $(document).data('sTinymcePopupActiveEditors'),
ret = null;
if(active_editor_id) {
var popup = Popups.activePopup();
ret = $('#'+active_editor_id).parents('.popups-box');
}

return ret;
};

/**
* Get the view mode of the current popup container
*
* @return string
*/
obj.getCurrentViewMode = function(){
// if the mode is not provided, get the current mode
var popupContainer = this.getPopupContainer(),
matches = null,
mode = null;
if(popupContainer){
matches = /s-tinymce-view-(\w+)/.exec(popupContainer.attr('class'));
if(matches){
mode = matches[1];
}
}

return mode;
};

return obj;
}());
}

;/**
* $Id: editor_plugin_src.js 001 2010-07-01 $
*
* @author Schoology, Inc.
* @copyright Copyright 2010, Schoology, Inc., All rights reserved.
*/

(function() {

var Event = tinymce.dom.Event;


var _enabledImage = false;

tinymce.create('tinymce.plugins.SImageHoverPlugin', {

init : function(ed, url) {

var t = this;

ed.onInit.add(function(ed){
t._createBtns();

Event.add(ed.getWin(), 'scroll', function(e){


if(!tinyMCE.activeEditor){
Event.remove(ed, 'scroll');
return true;
}

tinyMCE.activeEditor.execCommand('sImageHover_removeModBtns',false,false,
{skip_focus: true});
});
});

ed.addCommand('sImageHover_removeModBtns' , this._removeBtns, this);


ed.addCommand('sImageHover_positionModBtns' , this._positionBtns, this);
ed.addCommand('sImageHover_getEnabledImage' , this._getEnabledImage , this);
ed.addCommand('sImageHover_setEnabledImage' , this._setEnabledImage , this);

$('body').bind('mousedown',function(e){
if(typeof tinyMCE == 'undefined' || typeof tinyMCE.activeEditor ==
'undefined'){
$(this).unbind(e);
return true;
}

var targetObj = $(e.target);


var targetId = String(targetObj.attr('id'));
if( targetId == 'undefined' )
targetId = targetObj.parent().attr('id');
if( targetId == 'undefined' || targetId == 'tme_imgModBtn' || targetId ==
'tme_imgDelBtn' )
return;
tinyMCE.activeEditor.execCommand('sImageHover_removeModBtns',false,false,
{skip_focus: true});
});

ed.onRemove.add(function(ed){
// the following would be nice, but causes mac safari to crash
//ed.execCommand('sImageHover_removeModBtns');
$('#tme_imgModEnabled').attr('id','');
$('#tme_modBtnContainer').hide();
});

ed.onClick.add( function(ed,e){
var enabledImage =
tinyMCE.activeEditor.execCommand('sImageHover_getEnabledImage');
if(typeof enabledImage == 'undefined' || (enabledImage!=false && $
(e.target)[0] == enabledImage[0]))
return;
tinyMCE.activeEditor.execCommand('sImageHover_removeModBtns');
});

ed.onMouseUp.add(function(ed,e) {
if( e.target.nodeName == 'IMG'){
var img = $(e.target);
var img_class = String(img.attr('class'));
var imgSpan = $("span",$("#tme_imgModBtn"));

var hover_type = img_class.match(/^.*(mceItemQuickTime|mceItemMedia|


mceItemFlash|mceItemWindowsMedia|mceItemRealMedia).*$/gi) ? "media" : false;

// tutortrove formula png?


if(!hover_type)
hover_type = img_class.match(/^.*tutortrove-formula.*$/gi) ? "tt-
formula" : false;

// latex formula png?


if(!hover_type)
hover_type = img_class.match(/^.*latex-formula.*$/gi) ? "latex-
formula" : false;

if(!hover_type)
hover_type = img_class.match(/^.*mathquill-formula.*$/gi) ?
'mathquill-formula': false;

switch( hover_type ){
case 'media':
imgSpan.css('background-
image',"url(/sites/all/libraries/tinymce/jscripts/tiny_mce/themes/advanced/img/icon
s.gif)");
imgSpan.css({'background-position':'-323px -24px','background-
repeat':'no-repeat'});
imgSpan.data('mceMethod','mceMedia');
break;

case 'tt-formula':
imgSpan.css('background-
image',"url(/sites/all/themes/schoology_theme/images/math-icon.png)");
imgSpan.css({'background-position':'1px 1px','background-
repeat':'no-repeat'});
imgSpan.data('mceMethod','sContentFormulaInsert');
imgSpan.data('mceParams', img );
break;

case 'latex-formula':
imgSpan.css('background-
image',"url(/sites/all/themes/schoology_theme/images/math-icon.png)");
imgSpan.css({'background-position':'-1px -16px','background-
repeat':'no-repeat'});
imgSpan.data('mceMethod','sContentLatexInsert');
imgSpan.data('mceParams', img );
break;

case 'mathquill-formula':
imgSpan.css('background-
image',"url(/sites/all/themes/schoology_theme/images/math-icon.png)");
imgSpan.css({'background-position':'1px 1px','background-
repeat':'no-repeat'});
imgSpan.data('mceMethod','sContentFormulaInsert');
imgSpan.data('mceParams', img );
break;

default:
imgSpan.css('background-
image',"url(/sites/all/libraries/tinymce/jscripts/tiny_mce/themes/advanced/img/icon
s.gif)");
imgSpan.css({'background-position':'-384px -4px','background-
repeat':'no-repeat'});
imgSpan.data('mceMethod','mceImage');
break;
}

ed.execCommand('sImageHover_setEnabledImage',e.target);
ed.execCommand('sImageHover_positionModBtns');
}
});
},

_getEnabledImage: function(){
return this._enabledImage;
},

_setEnabledImage: function(img){
this._enabledImage = $(img);
this._enabledImage.attr('id','tme_imgModEnabled');
},

_createBtns: function(){
if ($('#tme_modBtnContainer').length) return;

var modBtnContainer = $("<div></div>");


modBtnContainer.attr('id','tme_modBtnContainer');

var modBtn = $("<span></span>");


modBtn.attr('id','tme_imgModBtn');
modBtn.append($("<span></span>"));

modBtn.bind('click',function(){
var ed = tinyMCE.activeEditor;
// otherwise the click deselects the image in ie
if(jQuery.browser.msie)
ed.selection.select(ed.dom.select('img#tme_imgModEnabled')[0]);
var img = $("span",$(this));
ed.execCommand('sImageHover_removeModBtns');
ed.execCommand( img.data('mceMethod') , img.data("mceParams") );
});

var delBtn = $("<span></span>");


delBtn.attr('id','tme_imgDelBtn');
delBtn.append($("<span></span>"));

delBtn.bind('click',function(){
tinyMCE.activeEditor.execCommand('sImageHover_removeModBtns',true);
});

modBtnContainer.append(modBtn);
modBtnContainer.append(delBtn);
modBtnContainer.css('display','none');

$('body').append(modBtnContainer);

// for safari
modBtnContainer.hide();
},

_removeBtns: function(delImage){
if(typeof delImage != 'boolean') delImage = false;
$(this._enabledImage).attr('id','');

if(delImage) {
tinyMCE.activeEditor.dom.remove(this._enabledImage);
// would be nice but webkit no bueno
//tinyMCE.activeEditor.execCommand('mceCleanup');
tinyMCE.activeEditor.execCommand('sContentSaveLastNode');
}

if(tinyMCE.activeEditor==null) return;
$('#tme_modBtnContainer').hide();

this._enabledImage = false;
},

_positionBtns: function(showBtns){
if( this._enabledImage == false ) return;
if(typeof showBtns != 'boolean') showBtns = true;
var iPos = $(this._enabledImage).position();
var ifr = $('iframe',$(tinyMCE.activeEditor.contentAreaContainer));
var ifPos = ifr.offset();

var scrollOffset = tinyMCE.activeEditor.contentWindow.pageYOffset;

if( typeof scrollOffset == 'undefined' ){


scrollOffset = tinymce.DOM.getViewPort(tinymce.activeEditor.getWin()).y;
}

var mbc_top = ifPos.top+iPos.top+5-scrollOffset;


if( ifPos.top > mbc_top ) mbc_top = ifPos.top + 10;

var mbc = $('#tme_modBtnContainer');


mbc.css('left',String(ifPos.left+iPos.left+5)+"px");
mbc.css('top',String(mbc_top)+"px");

if( showBtns ) mbc.show();


},

getInfo : function() {
return {
longname : 'S_Image_Hover',
author : 'Schoology, Inc.',
authorurl : 'http://' + sCommonGetDefaultDomain() + '.schoology.com',
infourl : 'http://' + sCommonGetDefaultDomain() + '.schoology.com',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
}
});

// Register plugin
tinymce.PluginManager.add('s_image_hover', tinymce.plugins.SImageHoverPlugin );
})();;/**
* $Id: editor_plugin_src.js 001 2010-10-12 $
*
* @author Schoology, Inc.
* @copyright Copyright 2010, Schoology, Inc., All rights reserved.
*/

(function() {
var Event = tinymce.dom.Event;

tinymce.create('tinymce.plugins.SContentPlugin', {

init : function(ed, url) {


var t = this;

ed.addCommand('sContentMediaInsert', function(){
t._mediaInsert();
});
ed.addCommand('sContentImageInsert', function(){
t._mediaInsert({type: 'image'});
});
ed.addCommand('sContentFormulaInsert', this._formulaInsert);
ed.addCommand('sContentLatexInsert', this._latexInsert);
ed.addCommand('sContentInsert', this._insertContent);
ed.addCommand('sContentSaveLastNode', this._saveLastNode);
ed.addCommand('sContentToggleSubmitBtns', this._toggleSubmitButtons);
ed.addCommand('sContentGetRealmFromUrl', this._getRealmFromUrl);
ed.addCommand('sContentSetWmodeOpaque', this._setWmodeOpaque);
ed.addCommand('sContentResourcesInsert', this._insertResourceContent);
ed.addCommand('sContentResourcesInsertPopup',
this._insertResourcesContentPopup);
ed.addCommand('sContentRemovePopups', this._removePopups);

// constant bookmarking
ed.onEvent.add(function(ed,e){
if( e.type == 'submit' )
return;
tinyMCE.activeEditor.execCommand('sContentSaveLastNode');
});

// Register buttons
ed.addButton('sContentFormula', {
title : Drupal.t('Equation'),
cmd : 'sContentFormulaInsert'
});

ed.addButton('sContentImage', {
title : Drupal.t('Image/Media'),
cmd : 'sContentMediaInsert'
});
},

createControl: function(n, cm){


// button that doesn't get disabled when not selecting a link
// can be used to insert new links
if(n == 's_link'){
return cm.createButton('s_link', {
title: 'advlink.link_desc',
cmd: 'mceLink'
});
}
},

//Clean up s_content import popups


_removePopups: function() {
var popups = Popups.popupStack;
for (var i = 0; i < popups.length; i++) {
var popup = popups[i];
var cssClass = popup.extraClass;
if (cssClass && cssClass.indexOf('tinymce') >= 0) {
popup.close();
}
}
},

_saveLastNode : function( ) {

var tmeNode = tinyMCE.activeEditor.selection.getNode();

// the above selector sometimes targets the document, which cannot be


appended to via jquery, so switch to the body
if( typeof tmeNode == 'object' && tmeNode.nodeName == '#document' )
tmeNode = tmeNode.body;

$(document).data('tinymceLastNode', tmeNode );
},

_mediaInsert : function(opts) {
if(typeof opts == 'undefined'){
opts = {};
}
if(typeof opts.type == 'undefined'){
opts.type = 'both';
}

sAfu.onUpload = function(file){
if(!file.url || file.url == '' || !file.type)
return;

if(file.type == 'image'){
var img = '<img src="' + file.url + '" alt="" title="" />';
tinyMCE.activeEditor.execCommand( 'sContentInsert' , img );

var popup = Popups.activePopup();


Popups.close( popup );
tinyMCE.activeEditor.focus();
}
else{
var url = file.url;
var fid = file.fid ? file.fid : '';

if(sCommonIsEmbed(url)){
Popups.close();
// ensure that wmode=opage is set for all embeds
url = tinyMCE.activeEditor.execCommand('sContentSetWmodeOpaque',url);
tinyMCE.activeEditor.execCommand('sContentInsert' , url);
return;
}

$.ajax({
url: '/media/richtext/embedcode',
data: { 'fid': fid , 'url': url },
type: 'POST',
dataType: 'json',
success: function (data, status){
var popup = Popups.activePopup();
Popups.close( popup );
tinyMCE.activeEditor.execCommand( 'sContentInsert' ,
data.embedCode );
},
error: function (data, status, e){
alert(Drupal.t("There was an internal error. Please try again in a
few moments."));
}
});
}
};

var realm = tinyMCE.activeEditor.execCommand('sContentGetRealmFromUrl');


var url = '/tinymceinsertmedia?r='+String(realm[0])+'&id='+String(realm[1]);
if(opts.type == 'image'){
url += '&type=image';
}

var popupOptions = {
ajaxForm: false,
extraClass: 'tinymce-insert-popup',
updateMethod: 'none',
href: url,
hijackDestination: false,
disableCursorMod: true,
disableAttachBehaviors: false,
formOnSubmit: function(){
// using upload file form and there are no uploads
if($('#mediaInsertTabURL').hasClass('active') && $('#edit-submit-
upload').hasClass('disabled'))
return false;

var urlObj = $("#edit-media-url"),


typeObj = $('.edit-media-type.active'),
embedUrl = urlObj.val();

if(embedUrl == ''){
// using URL form and no URL specified
if($('#mediaInsertTabUpload').hasClass('active')){
alert(Drupal.t('Please enter either a URL or embed code.'));
urlObj.focus();
tinyMCE.activeEditor.execCommand('sContentToggleSubmitBtns');
return false;
}

// file upload
var sAfu_opts = {
success: function (data, status){
if( data.status != 0 ) {
tinyMCE.activeEditor.execCommand('sContentToggleSubmitBtns');
alert(Drupal.t("There was an internal error. Please try again in
a few moments."));
return;
}

sAfu.onUpload(data.file);
},
error: function (data, status, e){
tinyMCE.activeEditor.execCommand('sContentToggleSubmitBtns');
alert(Drupal.t("There was an internal error. Please try again in a
few moments."));
},
url: '/tinymceinsertmedia'
};

sAfu.uploadFormAjax({},sAfu_opts);

}
else {
sAfu.onUpload({
type: typeObj.hasClass('edit-media-type-image') ? 'image' : 'media',
url: embedUrl
});
tinyMCE.activeEditor.focus();
}

return false;
}
};

// make sure the active editor is the one where the picutre will be inserted
tinyMCE.activeEditor.execCommand('sContentSaveLastNode');

Popups.openPath(this, popupOptions, window);


},

_formulaInsert : function(img) {
var realm = tinyMCE.activeEditor.execCommand('sContentGetRealmFromUrl');
var formula = img ? $(img).attr('formula') : '';
var use_mathquill = true;

// formulas are base64 encoded to prevent TinyMCE from mangling them during
html encoding/decoding
// because base64 encoding for formula attributes of tutortrove image tags
was implemented later, some old image tags may not have base64 encoded formulas
if( formula && Base64.is_encoded( formula ))
formula = Base64.decode( formula );

// save the image object so we can remove it later when updating an existing
formula
if(img){
$(document).data('s_content_saved_formula_image',img);

// only not use mathquill when it is an existing formula created with the
old editor
use_mathquill = img.hasClass('mathquill-formula');
}

if(use_mathquill){
var href = '/tinymceinsertmathquill';
var extraClass = ' tinymce-mathquill-formula-popup';
}
else{
var href = '/tinymceinsertformula';
var extraClass = ' tinymce-formula-popup';
}
var po = {
ajaxForm: false,
extraClass: 'tinymce-insert-popup popups-large' + extraClass,
width: 550,
updateMethod: 'none',
href: href + '?r='+String(realm[0])+'&id='+String(realm[1])+'&formula=' +
encodeURIComponent(formula),
hijackDestination: false,
disableCursorMod: true,
disableAttachBehaviors: false,
formOnSubmit: function(){
}
};

// make sure the active editor is the one where the picutre will be inserted
tinyMCE.activeEditor.execCommand('sContentSaveLastNode');

Popups.openPath(this, po, window);


},

_latexInsert : function(img) {
var realm = tinyMCE.activeEditor.execCommand('sContentGetRealmFromUrl');
var formula = img ? $(img).attr('formula') : '';

// save the image object so we can remove it later when updating an existing
formula
if(img) $(document).data('s_content_saved_latex_image',img);

var po = {
ajaxForm: false,
extraClass: 'tinymce-insert-popup tinymce-latex-popup popups-large',
width: 550,
updateMethod: 'none',
href: '/tinymceinsertlatex?r='+String(realm[0])+'&id='+String(realm[1])
+'&formula=' + encodeURIComponent(formula),
hijackDestination: false,
disableCursorMod: true,
disableAttachBehaviors: false,
formOnSubmit: function(){
}
};
// make sure the active editor is the one where the picutre will be inserted
tinyMCE.activeEditor.execCommand('sContentSaveLastNode');

Popups.openPath(this, po, window);


},

/**
* A wrapper for TinyMCE's method of injecting new content into the editor DOM
at the point of the cursor.
* Optionally uses its own bookmark system (tinymceLastNode) if available and
makes sense.
*
* @param string/object content html string or a jquery object
*/
_insertContent : function(content, opts) {
var defaults = {
use_native_insert: false // whether to use the tinyMCE native
mceInsertContent to insert the content
};
if(typeof opts == 'undefined'){
opts = {};
}
opts = $.extend({}, defaults, opts);

var cHtml = typeof content == 'string' ? content : $('<div


/>').append(content.clone()).remove().html();
var lastNode = $(document).data('tinymceLastNode');
var lastNodeObj = $(lastNode);
var ed = tinyMCE.activeEditor;

try{
if(!opts.use_native_insert && lastNodeObj.length > 0){
// use JQuery to inject the new element into the DOM after our own
bookmark
var nodeName = lastNode.nodeName.toLowerCase();

cHtml = new tinyMCE.html.Serializer().serialize(ed.parser.parse(cHtml));


// SGY-25009 Make root of date-mce-json src URL /media if there is
nothing before it except occurrences of "../"
cHtml = cHtml.replace(/(['"])[\.\/]+media\/ifr/, "$1/media/ifr");

if (!ed.settings.relative_urls) {
// If ed.settings.relative_urls is FALSE, convert the value of data-
mce-src to be absolute by removing any "../"
cHtml = cHtml.replace(/data-mce-src="(\.+\/)+/, 'data-mce-src="/');
}

// the body tag and table cells cannot have other tags placed directly
after them in the DOM hierarchy, use JQuery append instead of after
if(nodeName == 'td' || nodeName == 'body' ){
lastNodeObj.append(cHtml);
}
else {
lastNodeObj.after(cHtml);
}
}
else{
// our own bookmark is not available or valid, use the standard TinyMCE
method
ed.execCommand('mceInsertContent' , false , '' ); // sgy-1761
ed.execCommand('mceInsertContent' , false , cHtml );
}
}
catch(e){}
},

_toggleSubmitButtons : function(){
var submitBtn = $('#edit-submit-url');
if(submitBtn.attr('disabled')==true){
submitBtn.attr('disabled',false);
submitBtn.parent().removeClass('disabled');
}
else{
submitBtn.attr('disabled',true);
submitBtn.parent().addClass('disabled');
}

},

_getRealmFromUrl : function (){


/// which piece is the realm? the number after that is the realm id
var path_parts = String(window.location.pathname).split("/");
var valid_realms =
['user','school','district','course','group','page','topic','assignment','event','m
essage','scorm','resources'];
var path_part = '',
upload_realm = null,
upload_realmId = null;

// template assessments
var resourcesMatch =
String(window.location.pathname).match(/^\/template\/([0-9]+)\/assessment\/
(questions|settings|preview)/);
if( resourcesMatch )
path_parts = ['resources',resourcesMatch[1]];

for(var i=0;i<path_parts.length;i++) {
path_part = path_parts[i];
if( $.inArray( path_part , valid_realms ) != -1) {
upload_realm = path_part;
upload_realmId = path_parts[i+1];
break;
}
}

return [ upload_realm , upload_realmId ];


},

_setWmodeOpaque: function( html ){


var domObj = $(html);
// this is an iframe embed, append wmode=opaque to the iframe src
if(html.match(/^<iframe.+/i)) {
var iframe_src = domObj.attr('src');
// only operate on youtu.be, youtube.com or vimeo.com links
if( iframe_src.match(/^http(s?):\/\/.*((youtube\.com)|(youtu\.be)|
(vimeo\.com)).+/gi) ) {
// wmode already in querystring? replace
if( iframe_src.match(/^.+(\&|\?).*wmode=.+$/i) ){
iframe_src = iframe_src.replace(/^(.+(\&|\?).*wmode=)([^\&]+)(.*)
$/i,'$1opaque$4');
domObj.attr('src',iframe_src);
} else {
var append_str = iframe_src.indexOf('?') == -1 ? '?' : '&';
domObj.attr('src',iframe_src + append_str + 'wmode=opaque');
}
}
}

// this in an object tag, set the param tag and the attribute
if(html.match(/^<(object|embed).+/i)){
$('param[name=wmode]' , domObj).remove();
domObj.append('<param name="wmode" value="opaque"/>');
domObj.attr('wmode','opaque');
}

return $('<div />').append( domObj ).html();


},

_insertResourceContent: function(data) {
var html, width, height;

$.each(data.content, function(k, content){


html = '';
if(content.type == 'link') {
html = '<a href="' + content.href + '" class="s-content-insert" />' +
content.title + '</a>';
}
else if(content.type == 'image') {
html = '<img src="' + content.src + '" alt="' + content.title + '"
title="' + content.title + '" />';
}
else if(content.type == 'iframe') {
html = '<iframe src="' + content.src + '" width="' + content.width + '"
height="' + content.height + '" frameborder="0" allowfullscreen></iframe>';
html = tinyMCE.activeEditor.execCommand('sContentSetWmodeOpaque', html);
}
tinyMCE.activeEditor.execCommand('sContentInsert' , html);
});

var popup = Popups.activePopup();


Popups.close( popup );
},

_insertResourcesContentPopup: function(ui, params) {


var realm = tinyMCE.activeEditor.execCommand('sContentGetRealmFromUrl');
var url = '/tinymceinsertresourcesapp?r='+String(realm[0])
+'&id='+String(realm[1])+'&a='+String(params.nid);

Popups.saveSettings();

// The parent of the new popup is the currently active popup.


var parentPopup = Popups.activePopup();

var popupOptions = Popups.options({


ajaxForm: false,
extraClass: 'popups-extra-large popups-insert-library tinymce-resource-
popup',
updateMethod: 'none',
href: url,
hijackDestination: false,
disableCursorMod: true,
disableAttachBehaviors: false
});

// make sure the active editor is the one where the picutre will be inserted
tinyMCE.activeEditor.execCommand('sContentSaveLastNode');

// Launch the cookie preload popup first, then launch app


var cookiePreloadUrl = sCommonGetSetting('s_app', 'cookie_preload_urls',
params.nid);
if(cookiePreloadUrl) {
sAppMenuCookiePreloadRun(params.nid, cookiePreloadUrl, function(){
// clear cached launch data since we store cookie preload attempts in
session
sAppLauncherClearCache(params.nid);
sAppMenuCookiePreloadDelete(params.nid);
Popups.openPath(this, popupOptions, parentPopup);
});
}
// launch app popup
else {
Popups.openPath(this, popupOptions, parentPopup);
}
},

getInfo : function() {
return {
longname : 'S_Content',
author : 'Schoology, Inc.',
authorurl : 'http://' + sCommonGetDefaultDomain() + '.schoology.com',
infourl : 'http://' + sCommonGetDefaultDomain() + '.schoology.com',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
}
});

// Register plugin
tinymce.PluginManager.add('s_content', tinymce.plugins.SContentPlugin );
})();;/**
* @note
* Workarounds:
* - An issue with the click handler not firing in IE. Used mousedown instead.
* There was most likely another click event registered in tinyMCE that was
taking precedence and causing the popup to close
* before our custom event fired.
* - Using Firefox (tried in 16.0.x) in multiple choice rich text popup for
choices:
* A removeClass call or a css call to alter 'display' on the popupObj in
showPopup() was causing the selection from being deselected.
* A nodeChange event will fire soon after from mceToggleFormat, which will then
hide the popup since the selection is no longer
* on the annotated text. Workaround by using an incredibly negative top offset
(S_CLUETIP_HIDDEN).
*/
(function() {
var highlightClass = 's_cluetip_highlight';
var highlightSelector = '.' + highlightClass;
var highlightTmpClass = highlightClass + '_tmp';
var highlightTmpSelector = '.' + highlightTmpClass;
var lastNode = null;

var S_CLUETIP_HIDDEN = '-10000px';

function popupIsShowing(){
var ed = tinymce.activeEditor,
editorContainerObj = $('#' + ed.editorContainer),
popupObj = $('.add-note-popup', editorContainerObj);
return (popupObj.css('top') != S_CLUETIP_HIDDEN);
}

/**
* Adjust the popup position to the elements
*
* The popup box will stay within the boundary on the left and right side.
*/
function repositionPopup(elementObjs){
var newY = 0,
newX,
newXLeft = null,
newXRight = null,
xAnchor = null,
ed = tinymce.activeEditor,
editorContainerObj = $('#' + ed.editorContainer),
contentContainerObj = $(ed.contentAreaContainer),
menuHeight = contentContainerObj.offset().top -
editorContainerObj.offset().top,
popupObj = $('.add-note-popup', editorContainerObj),
arrowObj = $('.arrow-n', popupObj),
popupWidth = popupObj.outerWidth(),
popupHeight = popupObj.outerHeight(),
boundaryPadding = 5;

elementObjs.each(function(i, el){
var elementObj = $(el);
var offset = elementObj.offset();
if(!newXLeft || offset.left < newXLeft){
newXLeft = offset.left;
}
if(!newXRight || (offset.left + elementObj.outerWidth()) > newXRight){
newXRight = offset.left + elementObj.outerWidth();
}
if(offset.top + elementObj.outerHeight() > newY){
newY = offset.top + elementObj.outerHeight();
}
});

xAnchor = (newXLeft + newXRight) / 2;


newX = xAnchor - (popupWidth / 2);
if(newX < 0){
// going out of boundary to the left
newX = boundaryPadding;
arrowObj.css('margin-left', (xAnchor - (arrowObj.innerWidth() / 2) -
boundaryPadding) + 'px');
}
else if(newX + popupWidth > contentContainerObj.outerWidth()){
// going out of boundary to the right
newX = contentContainerObj.outerWidth() - popupWidth - boundaryPadding;
arrowObj.css('margin-left', (xAnchor - newX - (arrowObj.innerWidth() / 2)) +
'px');
}
else {
arrowObj.css('margin-left', '47%');
}
// the offset cannot be higher than the viewport (an image that extends below
the viewport)
var maxY = editorContainerObj.height() - popupHeight - arrowObj.outerHeight();
newY = Math.min(newY - $(ed.contentDocument).scrollTop() + menuHeight, maxY);

popupObj.css({
left: newX + 'px',
top: newY + 'px'
});
}

/**
* Shows the "add note" popup below the specified element
*/
function showPopup(elementObjs){
var ed = tinymce.activeEditor,
editorContainerObj = $('#' + ed.editorContainer),
popupObj = $('.add-note-popup', editorContainerObj);

var default_value = elementObjs.attr('cluetip');


if(Base64.is_encoded(default_value)){
default_value = Base64.decode(default_value);
}
$('.cluetip_text', popupObj).val(default_value);

// issue in IE where the click event wasn't triggering.


$('.check-mark-icon', popupObj).unbind('mousedown').bind('mousedown',
function(e){
e.stopPropagation();
var newNoteText = $('.cluetip_text', popupObj).val();
if(newNoteText.length > 0){
newNoteText = Base64.encode(newNoteText);
elementObjs.each(function(i, el){
$(el).attr('cluetip', newNoteText).removeClass(highlightTmpClass);
});
}
lastNode = null;
hidePopup();
});

if(default_value && default_value.length > 0){


$('.delete-icon', popupObj).unbind('mousedown').bind('mousedown', function(e)
{
e.stopPropagation();
unHighlightElements(elementObjs);
lastNode = null;
hidePopup();
}).show();
}
else{
$('.delete-icon', popupObj).unbind('mousedown').hide();
}

repositionPopup(elementObjs);
}

function unHighlightElements(elements){
var formatter = tinymce.activeEditor.formatter;
elements.each(function(i, el){
// the tinymce formatter can get screwy and apply the format to a descendent
because of element boundaries
// we need to check if there is a descendent that's also highlighted to avoid
having it removed from the DOM via the replaceWith
var elObj = $(el),
childrenObj = elObj.children(),
childHighlights = elObj.find(highlightTmpSelector);
if(childrenObj.length && !childHighlights.length){
elObj.replaceWith(elObj.html());
}
else{
formatter.toggle('highlight', null, elObj.get(0));
}
});
}

/**
* Hides the "add note" popup and reset the text.
*/
function hidePopup(){
var ed = tinymce.activeEditor,
popupObj = $('.add-note-popup', $('#' + ed.editorContainer));

popupObj.css('top', S_CLUETIP_HIDDEN);
popupObj.find('.cluetip_text').val('');

// unhighlight any untagged notes (the user highlighted something but didn't
fill it out)
unHighlightElements($(highlightTmpSelector, $(ed.contentDocument)));
}

/**
* Save the contents of the annotation.
*/
function saveOpenedPopup(){
var ed = tinymce.activeEditor,
popupObj = $('.add-note-popup', $('#' + ed.editorContainer)),
cluetipText = popupObj.find('.cluetip_text').val();
if(cluetipText && cluetipText.length > 0){
$('.check-mark-icon', popupObj).trigger('mousedown');
}
}

tinymce.create('tinymce.plugins.SCluetipPlugin', {
init : function(ed, url) {
ed.addCommand('sCluetipToggleNote', this._toggleNote);

ed.onNodeChange.add(this._nodeChanged, this);

ed.onBeforeGetContent.add(this._beforeGetContent, this);
ed.onInit.add(function(ed){
ed.formatter.register({
highlight: {
inline: 'span',
classes: [highlightClass, highlightTmpClass],
attributes: {'cluetip': ''},
onmatch: function(node, format, item_name){
// the default matchItems() check will fail because the value of
cluetip being an empty string
// fails the !value part of the if(similar && !value && !
format.exact) conditional
var nodeObj = $(node);
return (nodeObj.hasClass(highlightClass) &&
nodeObj.hasClass(highlightTmpClass));
}
}
});

var editorContainerObj = $('#' + ed.editorContainer);


var popupBox = $('<div></div>').css('position', 'absolute').addClass('add-
note-popup').prependTo(editorContainerObj);
$('<div></div>').addClass('arrow-n').appendTo(popupBox);
var popupContent = $('<div></div>').addClass('add-note-popup-
content').appendTo(popupBox);
$('<input type="text"/>').addClass('cluetip_text').appendTo(popupContent);
$('<span></span>').addClass('inline-icon mini check-mark-
icon').appendTo(popupContent);
$('<span></span>').addClass('inline-icon mini delete-
icon').appendTo(popupContent);

var popupParent = editorContainerObj.closest('.popups-box');


if(popupParent.length > 0){
popupParent.addClass('allow-overflow');
}

hidePopup();
});
},

/**
* Remove the annotation when the caret is in a highlighted node. Otherwise,
add the annotation using the tinymce formatter
*/
_toggleNote: function() {
var ed = tinymce.activeEditor,
nodeObj = $(ed.selection.getNode()),
parentHighlighted = nodeObj.parents(highlightSelector),
highlightedElements = null;

if(nodeObj.hasClass(highlightClass)){
// unhighlight selected element
unHighlightElements(nodeObj);
}
else if(parentHighlighted.length > 0){
// unhighlight the highlighted parent element
parentHighlighted.eq(-1);
unHighlightElements(parentHighlighted);
}
else{
// apply the highlighting
ed.execCommand('mceToggleFormat', false, 'highlight');
if(!nodeObj.is(':visible')){
// mceToggleFormat removed our previously selected node from the DOM, so
we need to reselect the new selection
nodeObj = $(ed.selection.getNode());
}
highlightedElements = nodeObj.find(highlightTmpSelector);
if(!highlightedElements.length){
highlightedElements =
nodeObj.addClass(highlightTmpClass).addClass(highlightClass);
}
showPopup(highlightedElements);
}
},

_nodeChanged: function(ed, cm, n, co, ob) {


var nodeObj = $(n);
var nodeHighlighted = nodeObj.hasClass(highlightClass);
var parentHighlighted = nodeObj.parents(highlightSelector);
var isHighlighted = nodeHighlighted || parentHighlighted.length > 0;

if(c = cm.get('s_cluetip_toggle_note')){
// disable the button if it's not a highlighted and the selection is
collapsed
c.setDisabled(!isHighlighted && co);
c.setActive(isHighlighted);
}

if(isHighlighted){
// show the popup for the highlighted elemented
var highlightedElements = nodeHighlighted ? nodeObj : parentHighlighted;
if(lastNode != n){
saveOpenedPopup();
showPopup(highlightedElements);
}
}
else{
if(popupIsShowing()){
saveOpenedPopup();
hidePopup();
}
}
lastNode = n;
},

/**
* Clean up any empty annotations before getting the content to be saved.
*/
_beforeGetContent: function(ed){
var popupObj = $('.add-note-popup', $('#' + ed.editorContainer));
if(popupObj.length > 0 && popupIsShowing()){
saveOpenedPopup();
hidePopup();
}
},

getInfo : function() {
return {
longname : 'S_Cluetip',
author : 'Schoology, Inc.',
authorurl : 'http://' + sCommonGetDefaultDomain() + '.schoology.com',
infourl : 'http://' + sCommonGetDefaultDomain() + '.schoology.com',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
}
});

// Register plugin
tinymce.PluginManager.add('s_cluetip', tinymce.plugins.SCluetipPlugin);
})();;/**
* Create a way of creating buttons that show/hide DropMenus
*/
(function() {
var menuConfig = {};

tinymce.create('tinymce.plugins.SDropMenu', {
init : function(ed, url) {
var t = this;
menuConfig = typeof ed.settings.s_dropmenu_menus == 'object' ?
ed.settings.s_dropmenu_menus : {};
},

/**
* Create Control hook for when TinyMCE need to determine what to create
*
* @param string n
* @param object cm
*/
createControl: function(n, cm){
var t = this;
if(typeof menuConfig[n] != 'undefined'){
var config = menuConfig[n], button = null, ccMenuButton = null, ccDropMenu
= null;

// use optional control class to modify default menu ui


if(config.isMegaDropdown === true) {
ccMenuButton = tinymce.ui.SMenuButton;
ccDropMenu = tinymce.ui.SMegaDropMenu;
}

button = cm.createMenuButton(n, {
title : config.title,
label: config.label,
icons : false,
ccDropMenu : ccDropMenu
}, ccMenuButton);

button.onRenderMenu.add(function(c, m){
t._createSubMenu(c.editor, m, config.items);
});

button.renderMenu();

return button;
}
},
/**
* Helper method in recursively populating a menu list.
*
* @param object ed
* @param object menu
* @param object items
*/
_createSubMenu: function(ed, menu, items){
var t = this;
$.each(items, function(k, item){
if(typeof item.items != 'undefined'){
var submenu = menu.addMenu({
title: item.title
});
t._createSubMenu(ed, submenu, item.items);
}
else if(typeof item.type != 'undefined'){
if(item.type == 'separator'){
menu.addSeparator();
}
}
else{
var newItem = {title: item.title},
menuItem = null;
if(typeof item.onclick == 'function'){
// execute a custom onclick handler
newItem.onclick = function(e){
item.onclick(e, ed);
};
}
else if(typeof item.cmd == 'string'){
// execute a tinymce command
newItem.cmd = item.cmd;
}

if(typeof item.id != 'undefined'){


newItem.id = item.id;
}

if(typeof item.value != 'undefined'){


newItem.value = item.value;
}

if(typeof item.icon_src != 'undefined'){


newItem.icon_src = item.icon_src;
}

if(typeof item['class'] != 'undefined'){


newItem['class'] = item['class'];
}

menuItem = menu.add(newItem);

if(typeof item.disabled != 'undefined' && item.disabled){


menuItem.setDisabled(true);
}

// by registering with the control mananger, the item will be like a


button that's accessible without digging into the menu buttons
ed.controlManager.add(menuItem);
}
});
},

getInfo : function() {
return {
longname : 'S_DropMenu',
author : 'Schoology, Inc.',
authorurl : 'http://' + sCommonGetDefaultDomain() + '.schoology.com',
infourl : 'http://' + sCommonGetDefaultDomain() + '.schoology.com',
version : tinymce.majorVersion + "." + tinymce.minorVersion
};
}
});

// Register plugin
tinymce.PluginManager.add('s_dropmenu', tinymce.plugins.SDropMenu);
})();

/**
* Optional control class for tinymce.ui.MenuButton
*/
(function() {
var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;

tinymce.create('tinymce.ui.SMenuButton:tinymce.ui.MenuButton', {
SMenuButton : function(id, s, ed) {
this.parent(id, s, ed);

this.onRenderMenu = new tinymce.util.Dispatcher(this);

s.menu_container = s.menu_container || DOM.doc.body;


},

/**
* Use settings object to pass in option control class for DropMenu ui
*/
renderMenu : function() {
var t = this, m;

m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
menu_line : 1,
'class' : this.classPrefix + 'Menu',
icons : t.settings.icons
}, t.settings.ccDropMenu);

m.onHideMenu.add(function() {
t.hideMenu();
t.focus();
});

t.onRenderMenu.dispatch(t, m);
t.menu = m;
}
});
})();
/**
* Optional control class for tinymce.ui.DropMenu
*/
(function() {
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event =
tinymce.dom.Event, Element = tinymce.dom.Element;

tinymce.create('tinymce.ui.SMegaDropMenu:tinymce.ui.DropMenu', {
SMegaDropMenu : function(id, s) {
s = s || {};
s.container = s.container || DOM.doc.body;
s.offset_x = s.offset_x || 0;
s.offset_y = s.offset_y || 0;
s.vp_offset_x = s.vp_offset_x || 0;
s.vp_offset_y = s.vp_offset_y || 0;

if (is(s.icons) && !s.icons)


s['class'] += ' mceNoIcons';

this.parent(id, s);
this.onShowMenu = new tinymce.util.Dispatcher(this);
this.onHideMenu = new tinymce.util.Dispatcher(this);
this.classPrefix = 'mceMenu';
},

createMenu : function(s) {
var t = this, cs = t.settings, m;

s.container = s.container || cs.container;


s.parent = t;
s.constrain = s.constrain || cs.constrain;
s['class'] = s['class'] || cs['class'];
s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
s.keyboard_focus = cs.keyboard_focus;
m = new tinymce.ui.SMegaDropMenu(s.id || DOM.uniqueId(), s);

m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);

return m;
},

/**
* Dropmen showmenu callback
*/
showMenu : function(x, y, px) {
var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot =
2, dm, tb, cp = t.classPrefix;

t.collapse(1);

if (t.isMenuVisible)
return;

if (!t.rendered) {
co = DOM.add(t.settings.container, t.renderNode());
each(t.items, function(o) {
o.postRender();
});

t.element = new Element('menu_' + t.id, {blocker : 1, container :


s.container});
} else
co = DOM.get('menu_' + t.id);

// Move layer out of sight unless it's Opera since it scrolls to top of page
due to an bug
if (!tinymce.isOpera)
DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});

DOM.show(co);
t.update();

x += s.offset_x || 0;
y += s.offset_y || 0;
vp.w -= 4;
vp.h -= 4;

// Move inside viewport if not submenu


if (s.constrain) {
w = co.clientWidth - ot;
h = co.clientHeight - ot;
mx = vp.x + vp.w;
my = vp.y + vp.h;

if ((x + s.vp_offset_x + w) > mx)


x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);

if ((y + s.vp_offset_y + h) > my)


y = Math.max(0, (my - s.vp_offset_y) - h);
}

DOM.setStyles(co, {left : x , top : y});


t.element.update();

t.isMenuVisible = 1;
t.mouseClickFunc = Event.add(co, 'click', function(e) {
var m;

e = e.target;

if (e && (e = DOM.getParent(e, 'td')) && !DOM.hasClass(e, cp + 'ItemSub')


&& !DOM.hasClass(e, 's-js-resource-items-empty')) {
m = t.items[e.id];

if (m.isDisabled())
return;

dm = t;

while (dm) {
if (dm.hideMenu)
dm.hideMenu();
dm = dm.settings.parent;
}

if (m.settings.onclick)
m.settings.onclick(e);

return false; // Cancel to fix onbeforeunload problem


}
});

if (t.hasMenus()) {
t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
var m, r, mi;

e = e.target;
if (e && (e = DOM.getParent(e, 'td'))) {
m = t.items[e.id];

if (t.lastMenu)
t.lastMenu.collapse(1);

if (m.isDisabled())
return;

if (e && DOM.hasClass(e, cp + 'ItemSub')) {


//p = DOM.getPos(s.container);
r = DOM.getRect(e);
m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
t.lastMenu = m;
DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
}
}
});
}

Event.add(co, 'keydown', t._keyHandler, t);

t.onShowMenu.dispatch(t);

if (s.keyboard_focus) {
t._setupKeyboardNav();
}
},

/**
* Render dropmenu
*
* @returns {div}
*/
renderNode : function() {
var t = this, s = t.settings, n, tb, co, w;

w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' :


s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
if (t.settings.parent) {
DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
}
co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co',
'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
t.element = new Element('menu_' + t.id, {blocker : 1, container :
s.container});

var mWrapper = DOM.add(co, 'div', {'class' : 's-content-resources-menu'});

n = DOM.add(mWrapper, 'table', {role: 'presentation', id : 'menu_' + t.id +


'_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
tb = DOM.add(n, 'tbody');

// we need a 2-column layout for dropmenu


var mChunked = [[], []], i;
each(t.items, function(o) {
i = o.settings['class'] &&
o.settings['class'].search(/s\-js\-resources\-insert\-item/) != -1
? 1 : 0;
mChunked[i].push(o);
});

// use extra class to toggle 2-col styles


DOM.addClass(mWrapper, 's-content-resources-menu-col-' +
(mChunked[1].length ? 2 : 1));

// render our 2-column drop menu


var rItems, mRowLen = mChunked[0].length, mColLen = mChunked.length, r, j;

if(mChunked[1].length > mRowLen) {


mRowLen = mChunked[1].length;
}

for(r = 0; r < mRowLen; r++) {


rItems = [];
for(j = 0; j < mColLen; j++) {
rItems.push( (mChunked[j][r] != undefined ? mChunked[j][r] : null) );
}
t._add(tb, rItems);
}

t.rendered = true;

return w;
},

/**
* Render menu rows
*
* @param tb table body node
* @param ri an array of menu items
*/
_add : function(tb, ri) {
var n, s, a, ro, it, cp = this.classPrefix, ic, is;

ro = DOM.add(tb, 'tr');

each(ri, function(o){
if(o != null) {
s = o.settings;
it = DOM.add(ro, 'td', {id: o.id, 'class': s['class'] + ' ' + cp + 'Item
' + cp + 'ItemEnabled'});
a = DOM.add(it, 'a', {id: o.id + '_aria', role: s.titleItem ?
'presentation' : 'option', href : 'javascript:;', onclick : "return false;",
onmousedown : 'return false;'});
if(s.icon_src) {
is = DOM.add(a, 'span', {'class' : 'mceIcon'});
DOM.add(is, 'img', {'src' : s.icon_src});
}
DOM.add(a, 'span', {'class' : 'mceText', title : o.settings.title},
o.settings.title);
}
else {
it = DOM.add(ro, 'td', {'class' : 's-js-resource-items-empty'});
}
});

if (tb.childNodes.length == 1) {
DOM.addClass(ro, 'mceFirst');
}

if (n = ro.previousSibling) {
DOM.removeClass(n, 'mceLast');
}

DOM.addClass(ro, 'mceLast');
}

});
})();
;/*
*
* Table Dropdown Plugin for TinyMCE
*
* Copyright 2011 Cory LaViska for A Beautiful Site, LLC. — www.abeautifulsite.net
*
* Licensed under the MIT License: http://www.opensource.org/licenses/mit-
license.php
*
* Modified to include table creation widget.
*
*/
(function() {
var menuObjCache = {};

function openTablePicker(createTableMenu){
var menuObj = null,
minRows = 4,
minCols = 4,
maxRows = 15,
maxCols = 10,
cellWidth = 25,
cellHeight = 17;
if(typeof menuObjCache[createTableMenu.id] == 'object' &&
menuObjCache[createTableMenu.id].closest('body').length){
// the menu object has already been created and is still in the DOM
menuObj = menuObjCache[createTableMenu.id];
}
else{
menuObj = menuObjCache[createTableMenu.id] = $('#menu_' + createTableMenu.id
+ '_co');
}
if(!menuObj.children('.table-picker-wrapper').length){
var tablePicker = '<div class="table-picker-wrapper">'
+ '<div class="table-picker-cells">'
+ '<div class="picker table-picker-selected-cell"></div>'
+ '</div>'
+ '<p class="table-picker-desc"></p>'
+ '</div>';
menuObj.append(tablePicker);
var selectedCell = menuObj.find('.table-picker-selected-cell'),
cellGrid = menuObj.find('.table-picker-cells');

menuObj.resetSelection = function(){
menuObj.updateSelection(1, 1);
};

menuObj.getColsRows = function(mouseX, mouseY){


var offset = cellGrid.offset(),
x = mouseX - offset.left,
y = mouseY - offset.top,
numCols = Math.min(Math.max(Math.ceil(x / cellWidth), 1), maxCols),
numRows = Math.min(Math.max(Math.ceil(y / cellHeight), 1), maxRows);
return [numCols, numRows];
};

menuObj.updateSelection = function(numCols, numRows){


var gridCols = Math.max(numCols, minCols),
gridRows = Math.max(numRows, minRows);
selectedCell.css({
width: numCols * cellWidth + 1,
height: numRows * cellHeight + 1
});

cellGrid.css({
width: gridCols * cellWidth + 1,
height: gridRows * cellHeight + 1
});

menuObj.find('.table-picker-desc').text(numCols + ' x ' + numRows);


};

cellGrid.bind('mousemove.tablepicker', function(e){
e.stopPropagation();
var colsRows = menuObj.getColsRows(e.pageX, e.pageY);
menuObj.updateSelection(colsRows[0], colsRows[1]);
}).bind('mouseleave.tablepicker', function(e){
$('body').bind('mousemove.tablepicker', function(e2){
if(menuObj.is(':visible')){
var colsRows = menuObj.getColsRows(e2.pageX, e2.pageY);
menuObj.updateSelection(colsRows[0], colsRows[1]);
}
});
}).bind('mouseenter.tablepicker', function(e){
$('body').unbind('mousemove.tablepicker');
}).bind('click.tablepicker', function(e){
var colsRows = menuObj.getColsRows(e.pageX, e.pageY),
i = 0,
html = '<table class="s_table s_table_border">';
for(i = 0; i < colsRows[1]; i++){
html += '<tr>';
for(j = 0; j < colsRows[0]; j++){
html += '<td><br/></td>';
}
html += '</tr>';
}
html += '</table>';
tinyMCE.activeEditor.execCommand('mceInsertContent', false, html);
createTableMenu.hideMenu();
});
}

if(typeof menuObj.resetSelection == 'function'){


menuObj.resetSelection();
}
}

tinymce.create('tinymce.plugins.STablePlugin', {
init: function(ed, url){
var t = this,
createTableMenu = null,
createTableButton = null,
editTableToolbarMenu = null;

ed.addCommand('mceTablePicker', function(){
if(createTableButton){
var cm = tinyMCE.activeEditor.controlManager;
button = $('#' + createTableButton.id),
offset = button.offset(),
x = offset.left,
y = offset.top + button.height();
if(createTableMenu.isMenuVisible){
createTableMenu.hideMenu();
}
else{
createTableMenu.showMenu(x, y);
openTablePicker(createTableMenu);
}
}
});

// toggle border by toggling the s_table_border class on the table


ed.addCommand('mceToggleTableBorder', this.toggleTableBorder, this);

ed.onInit.add(function(){
var cm = ed.controlManager;
createTableButton = cm.get(cm.prefix + 's_table_button');
createTableMenu = cm.createDropMenu('createtable');
createTableMenu.onHideMenu.add(function(){
$('body').unbind('mousemove.tablepicker');
tinyMCE.dom.Event.unbind(tinyMCE.DOM.doc, 'mousedown');
});
createTableMenu.onShowMenu.add(function(){
tinyMCE.dom.Event.bind(tinyMCE.DOM.doc, 'mousedown', function(e){
// close when clicking anywhere but the button and the menu
if(!$(e.target).closest('#' + createTableButton.id + ', #menu_' +
createTableMenu.id).length){
createTableMenu.hideMenu();
}
});
});

editTableToolbarMenu = $('#' + ed.editorId +


'_delete_table').closest('.mceToolbar');
if(editTableToolbarMenu){
t.isVisible = false;
t._resizeIframe(ed, 31);
sPopupsResizeCenter();
}
});

ed.onNodeChange.add(function(ed, cm, n, co, o) {


var tableNode = ed.dom.getParent(ed.selection.getStart(), 'table');

if(createTableButton){
createTableButton.setActive(tableNode);
}
if(editTableToolbarMenu){
// decide whether or not to show the edit table context menu
if(tableNode){
if(!t.isVisible){
t.isVisible = true;
editTableToolbarMenu.show();
t._resizeIframe(ed, -31);
}
}
else{
if(t.isVisible){
t.isVisible = false;
editTableToolbarMenu.hide();
t._resizeIframe(ed, 31);
}
}
}

// check if the table has class s_table_border to determine if the button


should be active
if(c = cm.get('s_table_border')){
var tableObj = $(tableNode);
c.setActive(tableObj.hasClass('s_table_border'));
c.setDisabled(!tableNode);
}
});

ed.onClick.add(function(){
if(createTableMenu){
createTableMenu.hideMenu();
}
});

t.isVisible = false;
},

toggleTableBorder: function(){
var ed = tinymce.activeEditor,
tableObj = $(ed.dom.getParent(ed.selection.getStart(), 'table'));
tableObj.toggleClass('s_table_border');
},
createControl: function(n, cm) {
switch( n ) {
case 's_table_button':
var createTableButton = cm.createButton(n, {
title : 'table.desc',
'class': 'mce_table',
onclick: function() {
tinymce.activeEditor.execCommand('mceTablePicker');
}
});

return createTableButton;

case 's_table_border':
var tableBorderButton = cm.createButton(n, {
title : Drupal.t('Border'),
'class': 'mce_table_border',
cmd: 'mceToggleTableBorder'
});

return tableBorderButton;
}

return null;

},

/**
* Taken from PDW plugin's PDW toggle mechanism
*
* @param object ed
* @param int diff
*/
_resizeIframe: function(ed, diff){
var frameObj = $(ed.getContentAreaContainer().firstChild);
frameObj.height(frameObj.height() + diff);

ed.theme.deltaHeight += diff;
}
});

// Register plugin
tinymce.PluginManager.add('s_table', tinymce.plugins.STablePlugin);

})();;

You might also like