2 var whiteSpaceRe = /^\s*|\s*$/g,
\r
3 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
\r
8 minorVersion : '4.3.1',
\r
10 releaseDate : '2011-06-16',
\r
12 _init : function() {
\r
13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
\r
15 t.isOpera = win.opera && opera.buildNumber;
\r
17 t.isWebKit = /WebKit/.test(ua);
\r
19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
\r
21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
\r
23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
\r
25 t.isMac = ua.indexOf('Mac') != -1;
\r
27 t.isAir = /adobeair/i.test(ua);
\r
29 t.isIDevice = /(iPad|iPhone)/.test(ua);
\r
31 t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
\r
33 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
\r
34 if (win.tinyMCEPreInit) {
\r
35 t.suffix = tinyMCEPreInit.suffix;
\r
36 t.baseURL = tinyMCEPreInit.base;
\r
37 t.query = tinyMCEPreInit.query;
\r
41 // Get suffix and base
\r
44 // If base element found, add that infront of baseURL
\r
45 nl = d.getElementsByTagName('base');
\r
46 for (i=0; i<nl.length; i++) {
\r
47 if (v = nl[i].href) {
\r
48 // Host only value like http://site.com or http://site.com:8008
\r
49 if (/^https?:\/\/[^\/]+$/.test(v))
\r
52 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
\r
56 function getBase(n) {
\r
57 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
\r
58 if (/_(src|dev)\.js/g.test(n.src))
\r
61 if ((p = n.src.indexOf('?')) != -1)
\r
62 t.query = n.src.substring(p + 1);
\r
64 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
\r
66 // If path to script is relative and a base href was found add that one infront
\r
67 // the src property will always be an absolute one on non IE browsers and IE 8
\r
68 // so this logic will basically only be executed on older IE versions
\r
69 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
\r
70 t.baseURL = base + t.baseURL;
\r
79 nl = d.getElementsByTagName('script');
\r
80 for (i=0; i<nl.length; i++) {
\r
86 n = d.getElementsByTagName('head')[0];
\r
88 nl = n.getElementsByTagName('script');
\r
89 for (i=0; i<nl.length; i++) {
\r
98 is : function(o, t) {
\r
100 return o !== undefined;
\r
102 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
\r
105 return typeof(o) == t;
\r
108 makeMap : function(items, delim, map) {
\r
111 items = items || [];
\r
112 delim = delim || ',';
\r
114 if (typeof(items) == "string")
\r
115 items = items.split(delim);
\r
121 map[items[i]] = {};
\r
126 each : function(o, cb, s) {
\r
134 if (o.length !== undefined) {
\r
135 // Indexed arrays, needed for Safari
\r
136 for (n=0, l = o.length; n < l; n++) {
\r
137 if (cb.call(s, o[n], n, o) === false)
\r
143 if (o.hasOwnProperty(n)) {
\r
144 if (cb.call(s, o[n], n, o) === false)
\r
154 map : function(a, f) {
\r
157 tinymce.each(a, function(v) {
\r
164 grep : function(a, f) {
\r
167 tinymce.each(a, function(v) {
\r
175 inArray : function(a, v) {
\r
179 for (i = 0, l = a.length; i < l; i++) {
\r
188 extend : function(o, e) {
\r
189 var i, l, a = arguments;
\r
191 for (i = 1, l = a.length; i < l; i++) {
\r
194 tinymce.each(e, function(v, n) {
\r
195 if (v !== undefined)
\r
204 trim : function(s) {
\r
205 return (s ? '' + s : '').replace(whiteSpaceRe, '');
\r
208 create : function(s, p, root) {
\r
209 var t = this, sp, ns, cn, scn, c, de = 0;
\r
211 // Parse : <prefix> <class>:<super class>
\r
212 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
\r
213 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
\r
215 // Create namespace for new class
\r
216 ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
\r
218 // Class already exists
\r
222 // Make pure static class
\r
223 if (s[2] == 'static') {
\r
227 this.onCreate(s[2], s[3], ns[cn]);
\r
232 // Create default constructor
\r
234 p[cn] = function() {};
\r
238 // Add constructor and methods
\r
240 t.extend(ns[cn].prototype, p);
\r
244 sp = t.resolve(s[5]).prototype;
\r
245 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
\r
247 // Extend constructor
\r
250 // Add passthrough constructor
\r
251 ns[cn] = function() {
\r
252 return sp[scn].apply(this, arguments);
\r
255 // Add inherit constructor
\r
256 ns[cn] = function() {
\r
257 this.parent = sp[scn];
\r
258 return c.apply(this, arguments);
\r
261 ns[cn].prototype[cn] = ns[cn];
\r
263 // Add super methods
\r
264 t.each(sp, function(f, n) {
\r
265 ns[cn].prototype[n] = sp[n];
\r
268 // Add overridden methods
\r
269 t.each(p, function(f, n) {
\r
270 // Extend methods if needed
\r
272 ns[cn].prototype[n] = function() {
\r
273 this.parent = sp[n];
\r
274 return f.apply(this, arguments);
\r
278 ns[cn].prototype[n] = f;
\r
283 // Add static methods
\r
284 t.each(p['static'], function(f, n) {
\r
289 this.onCreate(s[2], s[3], ns[cn].prototype);
\r
292 walk : function(o, f, n, s) {
\r
299 tinymce.each(o, function(o, i) {
\r
300 if (f.call(s, o, i, n) === false)
\r
303 tinymce.walk(o, f, n, s);
\r
308 createNS : function(n, o) {
\r
314 for (i=0; i<n.length; i++) {
\r
326 resolve : function(n, o) {
\r
332 for (i = 0, l = n.length; i < l; i++) {
\r
342 addUnload : function(f, s) {
\r
345 f = {func : f, scope : s || this};
\r
348 function unload() {
\r
349 var li = t.unloads, o, n;
\r
352 // Call unload handlers
\r
357 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
\r
360 // Detach unload function
\r
361 if (win.detachEvent) {
\r
362 win.detachEvent('onbeforeunload', fakeUnload);
\r
363 win.detachEvent('onunload', unload);
\r
364 } else if (win.removeEventListener)
\r
365 win.removeEventListener('unload', unload, false);
\r
367 // Destroy references
\r
368 t.unloads = o = li = w = unload = 0;
\r
370 // Run garbarge collector on IE
\r
371 if (win.CollectGarbage)
\r
376 function fakeUnload() {
\r
379 // Is there things still loading, then do some magic
\r
380 if (d.readyState == 'interactive') {
\r
382 // Prevent memory leak
\r
383 d.detachEvent('onstop', stop);
\r
385 // Call unload handler
\r
392 // Fire unload when the currently loading page is stopped
\r
394 d.attachEvent('onstop', stop);
\r
396 // Remove onstop listener after a while to prevent the unload function
\r
397 // to execute if the user presses cancel in an onbeforeunload
\r
398 // confirm dialog and then presses the browser stop button
\r
399 win.setTimeout(function() {
\r
401 d.detachEvent('onstop', stop);
\r
406 // Attach unload handler
\r
407 if (win.attachEvent) {
\r
408 win.attachEvent('onunload', unload);
\r
409 win.attachEvent('onbeforeunload', fakeUnload);
\r
410 } else if (win.addEventListener)
\r
411 win.addEventListener('unload', unload, false);
\r
413 // Setup initial unload handler array
\r
421 removeUnload : function(f) {
\r
422 var u = this.unloads, r = null;
\r
424 tinymce.each(u, function(o, i) {
\r
425 if (o && o.func == f) {
\r
435 explode : function(s, d) {
\r
436 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
\r
439 _addVer : function(u) {
\r
445 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
\r
447 if (u.indexOf('#') == -1)
\r
450 return u.replace('#', v + '#');
\r
453 // Fix function for IE 9 where regexps isn't working correctly
\r
454 // Todo: remove me once MS fixes the bug
\r
455 _replace : function(find, replace, str) {
\r
456 // On IE9 we have to fake $x replacement
\r
457 if (isRegExpBroken) {
\r
458 return str.replace(find, function() {
\r
459 var val = replace, args = arguments, i;
\r
461 for (i = 0; i < args.length - 2; i++) {
\r
462 if (args[i] === undefined) {
\r
463 val = val.replace(new RegExp('\\$' + i, 'g'), '');
\r
465 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
\r
473 return str.replace(find, replace);
\r
478 // Initialize the API
\r
481 // Expose tinymce namespace to the global namespace (window)
\r
482 win.tinymce = win.tinyMCE = tinymce;
\r
484 // Describe the different namespaces
\r
490 tinymce.create('tinymce.util.Dispatcher', {
\r
494 Dispatcher : function(s) {
\r
495 this.scope = s || this;
\r
496 this.listeners = [];
\r
499 add : function(cb, s) {
\r
500 this.listeners.push({cb : cb, scope : s || this.scope});
\r
505 addToTop : function(cb, s) {
\r
506 this.listeners.unshift({cb : cb, scope : s || this.scope});
\r
511 remove : function(cb) {
\r
512 var l = this.listeners, o = null;
\r
514 tinymce.each(l, function(c, i) {
\r
525 dispatch : function() {
\r
526 var s, a = arguments, i, li = this.listeners, c;
\r
528 // Needs to be a real loop since the listener count might change while looping
\r
529 // And this is also more efficient
\r
530 for (i = 0; i<li.length; i++) {
\r
532 s = c.cb.apply(c.scope, a);
\r
544 var each = tinymce.each;
\r
546 tinymce.create('tinymce.util.URI', {
\r
547 URI : function(u, s) {
\r
548 var t = this, o, a, b, base_url;
\r
551 u = tinymce.trim(u);
\r
553 // Default settings
\r
554 s = t.settings = s || {};
\r
556 // Strange app protocol or local anchor
\r
557 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
\r
562 // Absolute path with no host, fake host and protocol
\r
563 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
\r
564 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
\r
566 // Relative path http:// or protocol relative //path
\r
567 if (!/^[\w-]*:?\/\//.test(u)) {
\r
568 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
\r
569 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
\r
572 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
\r
573 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
\r
574 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
\r
575 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
\r
578 // Zope 3 workaround, they use @@something
\r
580 s = s.replace(/\(mce_at\)/g, '@@');
\r
585 if (b = s.base_uri) {
\r
587 t.protocol = b.protocol;
\r
590 t.userInfo = b.userInfo;
\r
592 if (!t.port && t.host == 'mce_host')
\r
595 if (!t.host || t.host == 'mce_host')
\r
601 //t.path = t.path || '/';
\r
604 setPath : function(p) {
\r
607 p = /^(.*?)\/?(\w+)?$/.exec(p);
\r
609 // Update path parts
\r
611 t.directory = p[1];
\r
619 toRelative : function(u) {
\r
625 u = new tinymce.util.URI(u, {base_uri : t});
\r
627 // Not on same domain/port or protocol
\r
628 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
\r
631 o = t.toRelPath(t.path, u.path);
\r
635 o += '?' + u.query;
\r
639 o += '#' + u.anchor;
\r
644 toAbsolute : function(u, nh) {
\r
645 var u = new tinymce.util.URI(u, {base_uri : this});
\r
647 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
\r
650 toRelPath : function(base, path) {
\r
651 var items, bp = 0, out = '', i, l;
\r
654 base = base.substring(0, base.lastIndexOf('/'));
\r
655 base = base.split('/');
\r
656 items = path.split('/');
\r
658 if (base.length >= items.length) {
\r
659 for (i = 0, l = base.length; i < l; i++) {
\r
660 if (i >= items.length || base[i] != items[i]) {
\r
667 if (base.length < items.length) {
\r
668 for (i = 0, l = items.length; i < l; i++) {
\r
669 if (i >= base.length || base[i] != items[i]) {
\r
679 for (i = 0, l = base.length - (bp - 1); i < l; i++)
\r
682 for (i = bp - 1, l = items.length; i < l; i++) {
\r
684 out += "/" + items[i];
\r
692 toAbsPath : function(base, path) {
\r
693 var i, nb = 0, o = [], tr, outPath;
\r
696 tr = /\/$/.test(path) ? '/' : '';
\r
697 base = base.split('/');
\r
698 path = path.split('/');
\r
700 // Remove empty chunks
\r
701 each(base, function(k) {
\r
708 // Merge relURLParts chunks
\r
709 for (i = path.length - 1, o = []; i >= 0; i--) {
\r
710 // Ignore empty or .
\r
711 if (path[i].length == 0 || path[i] == ".")
\r
715 if (path[i] == '..') {
\r
729 i = base.length - nb;
\r
733 outPath = o.reverse().join('/');
\r
735 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
\r
737 // Add front / if it's needed
\r
738 if (outPath.indexOf('/') !== 0)
\r
739 outPath = '/' + outPath;
\r
741 // Add traling / if it's needed
\r
742 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
\r
748 getURI : function(nh) {
\r
752 if (!t.source || nh) {
\r
757 s += t.protocol + '://';
\r
760 s += t.userInfo + '@';
\r
773 s += '?' + t.query;
\r
776 s += '#' + t.anchor;
\r
787 var each = tinymce.each;
\r
789 tinymce.create('static tinymce.util.Cookie', {
\r
790 getHash : function(n) {
\r
791 var v = this.get(n), h;
\r
794 each(v.split('&'), function(v) {
\r
797 h[unescape(v[0])] = unescape(v[1]);
\r
804 setHash : function(n, v, e, p, d, s) {
\r
807 each(v, function(v, k) {
\r
808 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
\r
811 this.set(n, o, e, p, d, s);
\r
814 get : function(n) {
\r
815 var c = document.cookie, e, p = n + "=", b;
\r
821 b = c.indexOf("; " + p);
\r
831 e = c.indexOf(";", b);
\r
836 return unescape(c.substring(b + p.length, e));
\r
839 set : function(n, v, e, p, d, s) {
\r
840 document.cookie = n + "=" + escape(v) +
\r
841 ((e) ? "; expires=" + e.toGMTString() : "") +
\r
842 ((p) ? "; path=" + escape(p) : "") +
\r
843 ((d) ? "; domain=" + d : "") +
\r
844 ((s) ? "; secure" : "");
\r
847 remove : function(n, p) {
\r
848 var d = new Date();
\r
850 d.setTime(d.getTime() - 1000);
\r
852 this.set(n, '', d, p, d);
\r
858 function serialize(o, quote) {
\r
861 quote = quote || '"';
\r
868 if (t == 'string') {
\r
869 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
\r
871 return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
\r
872 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
\r
873 if (quote === '"' && a === "'")
\r
879 return '\\' + v.charAt(i + 1);
\r
881 a = b.charCodeAt().toString(16);
\r
883 return '\\u' + '0000'.substring(a.length) + a;
\r
887 if (t == 'object') {
\r
888 if (o.hasOwnProperty && o instanceof Array) {
\r
889 for (i=0, v = '['; i<o.length; i++)
\r
890 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
\r
898 v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
\r
906 tinymce.util.JSON = {
\r
907 serialize: serialize,
\r
909 parse: function(s) {
\r
911 return eval('(' + s + ')');
\r
919 tinymce.create('static tinymce.util.XHR', {
\r
920 send : function(o) {
\r
921 var x, t, w = window, c = 0;
\r
923 // Default settings
\r
924 o.scope = o.scope || this;
\r
925 o.success_scope = o.success_scope || o.scope;
\r
926 o.error_scope = o.error_scope || o.scope;
\r
927 o.async = o.async === false ? false : true;
\r
928 o.data = o.data || '';
\r
934 x = new ActiveXObject(s);
\r
941 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
\r
944 if (x.overrideMimeType)
\r
945 x.overrideMimeType(o.content_type);
\r
947 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
\r
949 if (o.content_type)
\r
950 x.setRequestHeader('Content-Type', o.content_type);
\r
952 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
\r
957 if (!o.async || x.readyState == 4 || c++ > 10000) {
\r
958 if (o.success && c < 10000 && x.status == 200)
\r
959 o.success.call(o.success_scope, '' + x.responseText, x, o);
\r
961 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
\r
965 w.setTimeout(ready, 10);
\r
968 // Syncronous request
\r
972 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
\r
973 t = w.setTimeout(ready, 10);
\r
979 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
\r
981 tinymce.create('tinymce.util.JSONRequest', {
\r
982 JSONRequest : function(s) {
\r
983 this.settings = extend({
\r
988 send : function(o) {
\r
989 var ecb = o.error, scb = o.success;
\r
991 o = extend(this.settings, o);
\r
993 o.success = function(c, x) {
\r
996 if (typeof(c) == 'undefined') {
\r
998 error : 'JSON Parse error.'
\r
1003 ecb.call(o.error_scope || o.scope, c.error, x);
\r
1005 scb.call(o.success_scope || o.scope, c.result);
\r
1008 o.error = function(ty, x) {
\r
1010 ecb.call(o.error_scope || o.scope, ty, x);
\r
1013 o.data = JSON.serialize({
\r
1014 id : o.id || 'c' + (this.count++),
\r
1015 method : o.method,
\r
1019 // JSON content type for Ruby on rails. Bug: #1883287
\r
1020 o.content_type = 'application/json';
\r
1026 sendRPC : function(o) {
\r
1027 return new tinymce.util.JSONRequest().send(o);
\r
1032 (function(tinymce) {
\r
1033 var namedEntities, baseEntities, reverseEntities,
\r
1034 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
\r
1035 textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
\r
1036 rawCharsRegExp = /[<>&\"\']/g,
\r
1037 entityRegExp = /&(#x|#)?([\w]+);/g,
\r
1039 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
\r
1040 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
\r
1041 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
\r
1042 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
\r
1043 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
\r
1055 // Reverse lookup table for raw entities
\r
1056 reverseEntities = {
\r
1064 // Decodes text by using the browser
\r
1065 function nativeDecode(text) {
\r
1068 elm = document.createElement("div");
\r
1069 elm.innerHTML = text;
\r
1071 return elm.textContent || elm.innerText || text;
\r
1074 // Build a two way lookup table for the entities
\r
1075 function buildEntitiesLookup(items, radix) {
\r
1076 var i, chr, entity, lookup = {};
\r
1079 items = items.split(',');
\r
1080 radix = radix || 10;
\r
1082 // Build entities lookup table
\r
1083 for (i = 0; i < items.length; i += 2) {
\r
1084 chr = String.fromCharCode(parseInt(items[i], radix));
\r
1086 // Only add non base entities
\r
1087 if (!baseEntities[chr]) {
\r
1088 entity = '&' + items[i + 1] + ';';
\r
1089 lookup[chr] = entity;
\r
1090 lookup[entity] = chr;
\r
1098 // Unpack entities lookup where the numbers are in radix 32 to reduce the size
\r
1099 namedEntities = buildEntitiesLookup(
\r
1100 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
\r
1101 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
\r
1102 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
\r
1103 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
\r
1104 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
\r
1105 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
\r
1106 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
\r
1107 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
\r
1108 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
\r
1109 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
\r
1110 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
\r
1111 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
\r
1112 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
\r
1113 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
\r
1114 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
\r
1115 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
\r
1116 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
\r
1117 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
\r
1118 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
\r
1119 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
\r
1120 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
\r
1121 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
\r
1122 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
\r
1123 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
\r
1124 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
\r
1127 tinymce.html = tinymce.html || {};
\r
1129 tinymce.html.Entities = {
\r
1130 encodeRaw : function(text, attr) {
\r
1131 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
\r
1132 return baseEntities[chr] || chr;
\r
1136 encodeAllRaw : function(text) {
\r
1137 return ('' + text).replace(rawCharsRegExp, function(chr) {
\r
1138 return baseEntities[chr] || chr;
\r
1142 encodeNumeric : function(text, attr) {
\r
1143 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
\r
1144 // Multi byte sequence convert it to a single entity
\r
1145 if (chr.length > 1)
\r
1146 return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
\r
1148 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
\r
1152 encodeNamed : function(text, attr, entities) {
\r
1153 entities = entities || namedEntities;
\r
1155 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
\r
1156 return baseEntities[chr] || entities[chr] || chr;
\r
1160 getEncodeFunc : function(name, entities) {
\r
1161 var Entities = tinymce.html.Entities;
\r
1163 entities = buildEntitiesLookup(entities) || namedEntities;
\r
1165 function encodeNamedAndNumeric(text, attr) {
\r
1166 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
\r
1167 return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
\r
1171 function encodeCustomNamed(text, attr) {
\r
1172 return Entities.encodeNamed(text, attr, entities);
\r
1175 // Replace + with , to be compatible with previous TinyMCE versions
\r
1176 name = tinymce.makeMap(name.replace(/\+/g, ','));
\r
1178 // Named and numeric encoder
\r
1179 if (name.named && name.numeric)
\r
1180 return encodeNamedAndNumeric;
\r
1186 return encodeCustomNamed;
\r
1188 return Entities.encodeNamed;
\r
1193 return Entities.encodeNumeric;
\r
1196 return Entities.encodeRaw;
\r
1199 decode : function(text) {
\r
1200 return text.replace(entityRegExp, function(all, numeric, value) {
\r
1202 value = parseInt(value, numeric.length === 2 ? 16 : 10);
\r
1204 // Support upper UTF
\r
1205 if (value > 0xFFFF) {
\r
1208 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
\r
1210 return asciiMap[value] || String.fromCharCode(value);
\r
1213 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
\r
1219 tinymce.html.Styles = function(settings, schema) {
\r
1220 var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
\r
1221 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
\r
1222 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
\r
1223 trimRightRegExp = /\s+$/,
\r
1224 urlColorRegExp = /rgb/,
\r
1225 undef, i, encodingLookup = {}, encodingItems;
\r
1227 settings = settings || {};
\r
1229 encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
\r
1230 for (i = 0; i < encodingItems.length; i++) {
\r
1231 encodingLookup[encodingItems[i]] = '\uFEFF' + i;
\r
1232 encodingLookup['\uFEFF' + i] = encodingItems[i];
\r
1235 function toHex(match, r, g, b) {
\r
1236 function hex(val) {
\r
1237 val = parseInt(val).toString(16);
\r
1239 return val.length > 1 ? val : '0' + val; // 0 -> 00
\r
1242 return '#' + hex(r) + hex(g) + hex(b);
\r
1246 toHex : function(color) {
\r
1247 return color.replace(rgbRegExp, toHex);
\r
1250 parse : function(css) {
\r
1251 var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
\r
1253 function compress(prefix, suffix) {
\r
1254 var top, right, bottom, left;
\r
1256 // Get values and check it it needs compressing
\r
1257 top = styles[prefix + '-top' + suffix];
\r
1261 right = styles[prefix + '-right' + suffix];
\r
1265 bottom = styles[prefix + '-bottom' + suffix];
\r
1266 if (right != bottom)
\r
1269 left = styles[prefix + '-left' + suffix];
\r
1270 if (bottom != left)
\r
1274 styles[prefix + suffix] = left;
\r
1275 delete styles[prefix + '-top' + suffix];
\r
1276 delete styles[prefix + '-right' + suffix];
\r
1277 delete styles[prefix + '-bottom' + suffix];
\r
1278 delete styles[prefix + '-left' + suffix];
\r
1281 function canCompress(key) {
\r
1282 var value = styles[key], i;
\r
1284 if (!value || value.indexOf(' ') < 0)
\r
1287 value = value.split(' ');
\r
1290 if (value[i] !== value[0])
\r
1294 styles[key] = value[0];
\r
1299 function compress2(target, a, b, c) {
\r
1300 if (!canCompress(a))
\r
1303 if (!canCompress(b))
\r
1306 if (!canCompress(c))
\r
1310 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
\r
1316 // Encodes the specified string by replacing all \" \' ; : with _<num>
\r
1317 function encode(str) {
\r
1320 return encodingLookup[str];
\r
1323 // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
\r
1324 // It will also decode the \" \' if keep_slashes is set to fale or omitted
\r
1325 function decode(str, keep_slashes) {
\r
1327 str = str.replace(/\uFEFF[0-9]/g, function(str) {
\r
1328 return encodingLookup[str];
\r
1332 if (!keep_slashes)
\r
1333 str = str.replace(/\\([\'\";:])/g, "$1");
\r
1339 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
\r
1340 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
\r
1341 return str.replace(/[;:]/g, encode);
\r
1345 while (matches = styleRegExp.exec(css)) {
\r
1346 name = matches[1].replace(trimRightRegExp, '').toLowerCase();
\r
1347 value = matches[2].replace(trimRightRegExp, '');
\r
1349 if (name && value.length > 0) {
\r
1350 // Opera will produce 700 instead of bold in their style values
\r
1351 if (name === 'font-weight' && value === '700')
\r
1353 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
\r
1354 value = value.toLowerCase();
\r
1356 // Convert RGB colors to HEX
\r
1357 value = value.replace(rgbRegExp, toHex);
\r
1359 // Convert URLs and force them into url('value') format
\r
1360 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
\r
1361 str = str || str2;
\r
1364 str = decode(str);
\r
1366 // Force strings into single quote format
\r
1367 return "'" + str.replace(/\'/g, "\\'") + "'";
\r
1370 url = decode(url || url2 || url3);
\r
1372 // Convert the URL to relative/absolute depending on config
\r
1374 url = urlConverter.call(urlConverterScope, url, 'style');
\r
1376 // Output new URL format
\r
1377 return "url('" + url.replace(/\'/g, "\\'") + "')";
\r
1380 styles[name] = isEncoded ? decode(value, true) : value;
\r
1383 styleRegExp.lastIndex = matches.index + matches[0].length;
\r
1386 // Compress the styles to reduce it's size for example IE will expand styles
\r
1387 compress("border", "");
\r
1388 compress("border", "-width");
\r
1389 compress("border", "-color");
\r
1390 compress("border", "-style");
\r
1391 compress("padding", "");
\r
1392 compress("margin", "");
\r
1393 compress2('border', 'border-width', 'border-style', 'border-color');
\r
1395 // Remove pointless border, IE produces these
\r
1396 if (styles.border === 'medium none')
\r
1397 delete styles.border;
\r
1403 serialize : function(styles, element_name) {
\r
1404 var css = '', name, value;
\r
1406 function serializeStyles(name) {
\r
1407 var styleList, i, l, value;
\r
1409 styleList = schema.styles[name];
\r
1411 for (i = 0, l = styleList.length; i < l; i++) {
\r
1412 name = styleList[i];
\r
1413 value = styles[name];
\r
1415 if (value !== undef && value.length > 0)
\r
1416 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
\r
1421 // Serialize styles according to schema
\r
1422 if (element_name && schema && schema.styles) {
\r
1423 // Serialize global styles and element specific styles
\r
1424 serializeStyles('*');
\r
1425 serializeStyles(element_name);
\r
1427 // Output the styles in the order they are inside the object
\r
1428 for (name in styles) {
\r
1429 value = styles[name];
\r
1431 if (value !== undef && value.length > 0)
\r
1432 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
\r
1441 (function(tinymce) {
\r
1442 var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {},
\r
1443 whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
\r
1445 function split(str, delim) {
\r
1446 return str.split(delim || ',');
\r
1449 function unpack(lookup, data) {
\r
1450 var key, elements = {};
\r
1452 function replace(value) {
\r
1453 return value.replace(/[A-Z]+/g, function(key) {
\r
1454 return replace(lookup[key]);
\r
1459 for (key in lookup) {
\r
1460 if (lookup.hasOwnProperty(key))
\r
1461 lookup[key] = replace(lookup[key]);
\r
1464 // Unpack and parse data into object map
\r
1465 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
\r
1466 attributes = split(attributes, '|');
\r
1468 elements[name] = {
\r
1469 attributes : makeMap(attributes),
\r
1470 attributesOrder : attributes,
\r
1471 children : makeMap(children, '|', {'#comment' : {}})
\r
1478 // Build a lookup table for block elements both lowercase and uppercase
\r
1479 blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' +
\r
1480 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' +
\r
1481 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
\r
1482 blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
\r
1484 // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
\r
1485 transitional = unpack({
\r
1488 ZG : 'E|span|width|align|char|charoff|valign',
\r
1489 X : 'p|T|div|U|W|isindex|fieldset|table',
\r
1490 ZF : 'E|align|char|charoff|valign',
\r
1491 W : 'pre|hr|blockquote|address|center|noframes',
\r
1492 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
\r
1494 U : 'ul|ol|dl|menu|dir',
\r
1495 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
\r
1496 T : 'h1|h2|h3|h4|h5|h6',
\r
1499 ZA : 'a|G|J|M|O|P',
\r
1502 P : 'ins|del|script',
\r
1503 O : 'input|select|textarea|label|button',
\r
1505 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
\r
1508 J : 'tt|i|b|u|s|strike',
\r
1509 I : 'big|small|font|basefont',
\r
1511 G : 'br|span|bdo',
\r
1512 F : 'object|applet|img|map|iframe',
\r
1514 D : 'accesskey|tabindex|onfocus|onblur',
\r
1515 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
\r
1516 B : 'lang|xml:lang|dir',
\r
1517 A : 'id|class|style|title'
\r
1518 }, 'script[id|charset|type|language|src|defer|xml:space][]' +
\r
1519 'style[B|id|type|media|title|xml:space][]' +
\r
1520 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' +
\r
1521 'param[id|name|value|valuetype|type][]' +
\r
1522 'p[E|align][#|S]' +
\r
1523 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' +
\r
1524 'br[A|clear][]' +
\r
1526 'bdo[A|C|B][#|S]' +
\r
1527 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' +
\r
1528 'h1[E|align][#|S]' +
\r
1529 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' +
\r
1530 'map[B|C|A|name][X|form|Q|area]' +
\r
1531 'h2[E|align][#|S]' +
\r
1532 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' +
\r
1533 'h3[E|align][#|S]' +
\r
1539 'strike[E][#|S]' +
\r
1541 'small[E][#|S]' +
\r
1542 'font[A|B|size|color|face][#|S]' +
\r
1543 'basefont[id|size|color|face][]' +
\r
1545 'strong[E][#|S]' +
\r
1548 'q[E|cite][#|S]' +
\r
1554 'acronym[E][#|S]' +
\r
1557 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' +
\r
1558 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' +
\r
1559 'optgroup[E|disabled|label][option]' +
\r
1560 'option[E|selected|disabled|label|value][]' +
\r
1561 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' +
\r
1562 'label[E|for|accesskey|onfocus|onblur][#|S]' +
\r
1563 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
\r
1564 'h4[E|align][#|S]' +
\r
1565 'ins[E|cite|datetime][#|Y]' +
\r
1566 'h5[E|align][#|S]' +
\r
1567 'del[E|cite|datetime][#|Y]' +
\r
1568 'h6[E|align][#|S]' +
\r
1569 'div[E|align][#|Y]' +
\r
1570 'ul[E|type|compact][li]' +
\r
1571 'li[E|type|value][#|Y]' +
\r
1572 'ol[E|type|compact|start][li]' +
\r
1573 'dl[E|compact][dt|dd]' +
\r
1576 'menu[E|compact][li]' +
\r
1577 'dir[E|compact][li]' +
\r
1578 'pre[E|width|xml:space][#|ZA]' +
\r
1579 'hr[E|align|noshade|size|width][]' +
\r
1580 'blockquote[E|cite][#|Y]' +
\r
1581 'address[E][#|S|p]' +
\r
1582 'center[E][#|Y]' +
\r
1583 'noframes[E][#|Y]' +
\r
1584 'isindex[A|B|prompt][]' +
\r
1585 'fieldset[E][#|legend|Y]' +
\r
1586 'legend[E|accesskey|align][#|S]' +
\r
1587 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' +
\r
1588 'caption[E|align][#|S]' +
\r
1590 'colgroup[ZG][col]' +
\r
1591 'thead[ZF][tr]' +
\r
1592 'tr[ZF|bgcolor][th|td]' +
\r
1593 'th[E|ZE][#|Y]' +
\r
1594 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' +
\r
1595 'noscript[E][#|Y]' +
\r
1596 'td[E|ZE][#|Y]' +
\r
1597 'tfoot[ZF][tr]' +
\r
1598 'tbody[ZF][tr]' +
\r
1599 'area[E|D|shape|coords|href|nohref|alt|target][]' +
\r
1600 'base[id|href|target][]' +
\r
1601 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
\r
1604 boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,autoplay,loop,controls');
\r
1605 shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
\r
1606 nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,object'), shortEndedElementsMap);
\r
1607 whiteSpaceElementsMap = makeMap('pre,script,style');
\r
1608 selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
\r
1610 tinymce.html.Schema = function(settings) {
\r
1611 var self = this, elements = {}, children = {}, patternElements = [], validStyles;
\r
1613 settings = settings || {};
\r
1615 // Allow all elements and attributes if verify_html is set to false
\r
1616 if (settings.verify_html === false)
\r
1617 settings.valid_elements = '*[*]';
\r
1619 // Build styles list
\r
1620 if (settings.valid_styles) {
\r
1623 // Convert styles into a rule list
\r
1624 each(settings.valid_styles, function(value, key) {
\r
1625 validStyles[key] = tinymce.explode(value);
\r
1629 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
\r
1630 function patternToRegExp(str) {
\r
1631 return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
\r
1634 // Parses the specified valid_elements string and adds to the current rules
\r
1635 // This function is a bit hard to read since it's heavily optimized for speed
\r
1636 function addValidElements(valid_elements) {
\r
1637 var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
\r
1638 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
\r
1639 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
\r
1640 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
\r
1641 hasPatternsRegExp = /[*?+]/;
\r
1643 if (valid_elements) {
\r
1644 // Split valid elements into an array with rules
\r
1645 valid_elements = split(valid_elements);
\r
1647 if (elements['@']) {
\r
1648 globalAttributes = elements['@'].attributes;
\r
1649 globalAttributesOrder = elements['@'].attributesOrder;
\r
1653 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
\r
1654 // Parse element rule
\r
1655 matches = elementRuleRegExp.exec(valid_elements[ei]);
\r
1657 // Setup local names for matches
\r
1658 prefix = matches[1];
\r
1659 elementName = matches[2];
\r
1660 outputName = matches[3];
\r
1661 attrData = matches[4];
\r
1663 // Create new attributes and attributesOrder
\r
1665 attributesOrder = [];
\r
1667 // Create the new element
\r
1669 attributes : attributes,
\r
1670 attributesOrder : attributesOrder
\r
1673 // Padd empty elements prefix
\r
1674 if (prefix === '#')
\r
1675 element.paddEmpty = true;
\r
1677 // Remove empty elements prefix
\r
1678 if (prefix === '-')
\r
1679 element.removeEmpty = true;
\r
1681 // Copy attributes from global rule into current rule
\r
1682 if (globalAttributes) {
\r
1683 for (key in globalAttributes)
\r
1684 attributes[key] = globalAttributes[key];
\r
1686 attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
\r
1689 // Attributes defined
\r
1691 attrData = split(attrData, '|');
\r
1692 for (ai = 0, al = attrData.length; ai < al; ai++) {
\r
1693 matches = attrRuleRegExp.exec(attrData[ai]);
\r
1696 attrType = matches[1];
\r
1697 attrName = matches[2].replace(/::/g, ':');
\r
1698 prefix = matches[3];
\r
1699 value = matches[4];
\r
1702 if (attrType === '!') {
\r
1703 element.attributesRequired = element.attributesRequired || [];
\r
1704 element.attributesRequired.push(attrName);
\r
1705 attr.required = true;
\r
1708 // Denied from global
\r
1709 if (attrType === '-') {
\r
1710 delete attributes[attrName];
\r
1711 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
\r
1718 if (prefix === '=') {
\r
1719 element.attributesDefault = element.attributesDefault || [];
\r
1720 element.attributesDefault.push({name: attrName, value: value});
\r
1721 attr.defaultValue = value;
\r
1725 if (prefix === ':') {
\r
1726 element.attributesForced = element.attributesForced || [];
\r
1727 element.attributesForced.push({name: attrName, value: value});
\r
1728 attr.forcedValue = value;
\r
1731 // Required values
\r
1732 if (prefix === '<')
\r
1733 attr.validValues = makeMap(value, '?');
\r
1736 // Check for attribute patterns
\r
1737 if (hasPatternsRegExp.test(attrName)) {
\r
1738 element.attributePatterns = element.attributePatterns || [];
\r
1739 attr.pattern = patternToRegExp(attrName);
\r
1740 element.attributePatterns.push(attr);
\r
1742 // Add attribute to order list if it doesn't already exist
\r
1743 if (!attributes[attrName])
\r
1744 attributesOrder.push(attrName);
\r
1746 attributes[attrName] = attr;
\r
1752 // Global rule, store away these for later usage
\r
1753 if (!globalAttributes && elementName == '@') {
\r
1754 globalAttributes = attributes;
\r
1755 globalAttributesOrder = attributesOrder;
\r
1758 // Handle substitute elements such as b/strong
\r
1760 element.outputName = elementName;
\r
1761 elements[outputName] = element;
\r
1764 // Add pattern or exact element
\r
1765 if (hasPatternsRegExp.test(elementName)) {
\r
1766 element.pattern = patternToRegExp(elementName);
\r
1767 patternElements.push(element);
\r
1769 elements[elementName] = element;
\r
1775 function setValidElements(valid_elements) {
\r
1777 patternElements = [];
\r
1779 addValidElements(valid_elements);
\r
1781 each(transitional, function(element, name) {
\r
1782 children[name] = element.children;
\r
1786 // Adds custom non HTML elements to the schema
\r
1787 function addCustomElements(custom_elements) {
\r
1788 var customElementRegExp = /^(~)?(.+)$/;
\r
1790 if (custom_elements) {
\r
1791 each(split(custom_elements), function(rule) {
\r
1792 var matches = customElementRegExp.exec(rule),
\r
1793 inline = matches[1] === '~',
\r
1794 cloneName = inline ? 'span' : 'div',
\r
1795 name = matches[2];
\r
1797 children[name] = children[cloneName];
\r
1798 customElementsMap[name] = cloneName;
\r
1800 // If it's not marked as inline then add it to valid block elements
\r
1802 blockElementsMap[name] = {};
\r
1804 // Add custom elements at span/div positions
\r
1805 each(children, function(element, child) {
\r
1806 if (element[cloneName])
\r
1807 element[name] = element[cloneName];
\r
1813 // Adds valid children to the schema object
\r
1814 function addValidChildren(valid_children) {
\r
1815 var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
\r
1817 if (valid_children) {
\r
1818 each(split(valid_children), function(rule) {
\r
1819 var matches = childRuleRegExp.exec(rule), parent, prefix;
\r
1822 prefix = matches[1];
\r
1824 // Add/remove items from default
\r
1826 parent = children[matches[2]];
\r
1828 parent = children[matches[2]] = {'#comment' : {}};
\r
1830 parent = children[matches[2]];
\r
1832 each(split(matches[3], '|'), function(child) {
\r
1833 if (prefix === '-')
\r
1834 delete parent[child];
\r
1836 parent[child] = {};
\r
1843 if (!settings.valid_elements) {
\r
1844 // No valid elements defined then clone the elements from the transitional spec
\r
1845 each(transitional, function(element, name) {
\r
1846 elements[name] = {
\r
1847 attributes : element.attributes,
\r
1848 attributesOrder : element.attributesOrder
\r
1851 children[name] = element.children;
\r
1855 each(split('strong/b,em/i'), function(item) {
\r
1856 item = split(item, '/');
\r
1857 elements[item[1]].outputName = item[0];
\r
1860 // Add default alt attribute for images
\r
1861 elements.img.attributesDefault = [{name: 'alt', value: ''}];
\r
1863 // Remove these if they are empty by default
\r
1864 each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) {
\r
1865 elements[name].removeEmpty = true;
\r
1868 // Padd these by default
\r
1869 each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
\r
1870 elements[name].paddEmpty = true;
\r
1873 setValidElements(settings.valid_elements);
\r
1875 addCustomElements(settings.custom_elements);
\r
1876 addValidChildren(settings.valid_children);
\r
1877 addValidElements(settings.extended_valid_elements);
\r
1879 // Todo: Remove this when we fix list handling to be valid
\r
1880 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
\r
1882 // Delete invalid elements
\r
1883 if (settings.invalid_elements) {
\r
1884 tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
\r
1885 if (elements[item])
\r
1886 delete elements[item];
\r
1890 self.children = children;
\r
1892 self.styles = validStyles;
\r
1894 self.getBoolAttrs = function() {
\r
1895 return boolAttrMap;
\r
1898 self.getBlockElements = function() {
\r
1899 return blockElementsMap;
\r
1902 self.getShortEndedElements = function() {
\r
1903 return shortEndedElementsMap;
\r
1906 self.getSelfClosingElements = function() {
\r
1907 return selfClosingElementsMap;
\r
1910 self.getNonEmptyElements = function() {
\r
1911 return nonEmptyElementsMap;
\r
1914 self.getWhiteSpaceElements = function() {
\r
1915 return whiteSpaceElementsMap;
\r
1918 self.isValidChild = function(name, child) {
\r
1919 var parent = children[name];
\r
1921 return !!(parent && parent[child]);
\r
1924 self.getElementRule = function(name) {
\r
1925 var element = elements[name], i;
\r
1927 // Exact match found
\r
1931 // No exact match then try the patterns
\r
1932 i = patternElements.length;
\r
1934 element = patternElements[i];
\r
1936 if (element.pattern.test(name))
\r
1941 self.getCustomElements = function() {
\r
1942 return customElementsMap;
\r
1945 self.addValidElements = addValidElements;
\r
1947 self.setValidElements = setValidElements;
\r
1949 self.addCustomElements = addCustomElements;
\r
1951 self.addValidChildren = addValidChildren;
\r
1954 // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
\r
1955 tinymce.html.Schema.boolAttrMap = boolAttrMap;
\r
1956 tinymce.html.Schema.blockElementsMap = blockElementsMap;
\r
1959 (function(tinymce) {
\r
1960 tinymce.html.SaxParser = function(settings, schema) {
\r
1961 var self = this, noop = function() {};
\r
1963 settings = settings || {};
\r
1964 self.schema = schema = schema || new tinymce.html.Schema();
\r
1966 if (settings.fix_self_closing !== false)
\r
1967 settings.fix_self_closing = true;
\r
1969 // Add handler functions from settings and setup default handlers
\r
1970 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
\r
1972 self[name] = settings[name] || noop;
\r
1975 self.parse = function(html) {
\r
1976 var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name,
\r
1977 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
\r
1978 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
\r
1979 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
\r
1981 function processEndTag(name) {
\r
1984 // Find position of parent of the same type
\r
1985 pos = stack.length;
\r
1987 if (stack[pos].name === name)
\r
1993 // Close all the open elements
\r
1994 for (i = stack.length - 1; i >= pos; i--) {
\r
1998 self.end(name.name);
\r
2001 // Remove the open elements from the stack
\r
2002 stack.length = pos;
\r
2006 // Precompile RegExps and map objects
\r
2007 tokenRegExp = new RegExp('<(?:' +
\r
2008 '(?:!--([\\w\\W]*?)-->)|' + // Comment
\r
2009 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
\r
2010 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
\r
2011 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
\r
2012 '(?:\\/([^>]+)>)|' + // End element
\r
2013 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
\r
2016 attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
\r
2017 specialElements = {
\r
2018 'script' : /<\/script[^>]*>/gi,
\r
2019 'style' : /<\/style[^>]*>/gi,
\r
2020 'noscript' : /<\/noscript[^>]*>/gi
\r
2023 // Setup lookup tables for empty elements and boolean attributes
\r
2024 shortEndedElements = schema.getShortEndedElements();
\r
2025 selfClosing = schema.getSelfClosingElements();
\r
2026 fillAttrsMap = schema.getBoolAttrs();
\r
2027 validate = settings.validate;
\r
2028 fixSelfClosing = settings.fix_self_closing;
\r
2030 while (matches = tokenRegExp.exec(html)) {
\r
2032 if (index < matches.index)
\r
2033 self.text(decode(html.substr(index, matches.index - index)));
\r
2035 if (value = matches[6]) { // End element
\r
2036 processEndTag(value.toLowerCase());
\r
2037 } else if (value = matches[7]) { // Start element
\r
2038 value = value.toLowerCase();
\r
2039 isShortEnded = value in shortEndedElements;
\r
2041 // Is self closing tag for example an <li> after an open <li>
\r
2042 if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
\r
2043 processEndTag(value);
\r
2045 // Validate element
\r
2046 if (!validate || (elementRule = schema.getElementRule(value))) {
\r
2047 isValidElement = true;
\r
2049 // Grab attributes map and patters when validation is enabled
\r
2051 validAttributesMap = elementRule.attributes;
\r
2052 validAttributePatterns = elementRule.attributePatterns;
\r
2055 // Parse attributes
\r
2056 if (attribsValue = matches[8]) {
\r
2058 attrList.map = {};
\r
2060 attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
\r
2063 name = name.toLowerCase();
\r
2064 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
\r
2066 // Validate name and value
\r
2067 if (validate && name.indexOf('data-') !== 0) {
\r
2068 attrRule = validAttributesMap[name];
\r
2070 // Find rule by pattern matching
\r
2071 if (!attrRule && validAttributePatterns) {
\r
2072 i = validAttributePatterns.length;
\r
2074 attrRule = validAttributePatterns[i];
\r
2075 if (attrRule.pattern.test(name))
\r
2079 // No rule matched
\r
2084 // No attribute rule found
\r
2089 if (attrRule.validValues && !(value in attrRule.validValues))
\r
2093 // Add attribute to list and map
\r
2094 attrList.map[name] = value;
\r
2102 attrList.map = {};
\r
2105 // Process attributes if validation is enabled
\r
2107 attributesRequired = elementRule.attributesRequired;
\r
2108 attributesDefault = elementRule.attributesDefault;
\r
2109 attributesForced = elementRule.attributesForced;
\r
2111 // Handle forced attributes
\r
2112 if (attributesForced) {
\r
2113 i = attributesForced.length;
\r
2115 attr = attributesForced[i];
\r
2117 attrValue = attr.value;
\r
2119 if (attrValue === '{$uid}')
\r
2120 attrValue = 'mce_' + idCount++;
\r
2122 attrList.map[name] = attrValue;
\r
2123 attrList.push({name: name, value: attrValue});
\r
2127 // Handle default attributes
\r
2128 if (attributesDefault) {
\r
2129 i = attributesDefault.length;
\r
2131 attr = attributesDefault[i];
\r
2134 if (!(name in attrList.map)) {
\r
2135 attrValue = attr.value;
\r
2137 if (attrValue === '{$uid}')
\r
2138 attrValue = 'mce_' + idCount++;
\r
2140 attrList.map[name] = attrValue;
\r
2141 attrList.push({name: name, value: attrValue});
\r
2146 // Handle required attributes
\r
2147 if (attributesRequired) {
\r
2148 i = attributesRequired.length;
\r
2150 if (attributesRequired[i] in attrList.map)
\r
2154 // None of the required attributes where found
\r
2156 isValidElement = false;
\r
2159 // Invalidate element if it's marked as bogus
\r
2160 if (attrList.map['data-mce-bogus'])
\r
2161 isValidElement = false;
\r
2164 if (isValidElement)
\r
2165 self.start(value, attrList, isShortEnded);
\r
2167 isValidElement = false;
\r
2169 // Treat script, noscript and style a bit different since they may include code that looks like elements
\r
2170 if (endRegExp = specialElements[value]) {
\r
2171 endRegExp.lastIndex = index = matches.index + matches[0].length;
\r
2173 if (matches = endRegExp.exec(html)) {
\r
2174 if (isValidElement)
\r
2175 text = html.substr(index, matches.index - index);
\r
2177 index = matches.index + matches[0].length;
\r
2179 text = html.substr(index);
\r
2180 index = html.length;
\r
2183 if (isValidElement && text.length > 0)
\r
2184 self.text(text, true);
\r
2186 if (isValidElement)
\r
2189 tokenRegExp.lastIndex = index;
\r
2193 // Push value on to stack
\r
2194 if (!isShortEnded) {
\r
2195 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
\r
2196 stack.push({name: value, valid: isValidElement});
\r
2197 else if (isValidElement)
\r
2200 } else if (value = matches[1]) { // Comment
\r
2201 self.comment(value);
\r
2202 } else if (value = matches[2]) { // CDATA
\r
2203 self.cdata(value);
\r
2204 } else if (value = matches[3]) { // DOCTYPE
\r
2205 self.doctype(value);
\r
2206 } else if (value = matches[4]) { // PI
\r
2207 self.pi(value, matches[5]);
\r
2210 index = matches.index + matches[0].length;
\r
2214 if (index < html.length)
\r
2215 self.text(decode(html.substr(index)));
\r
2217 // Close any open elements
\r
2218 for (i = stack.length - 1; i >= 0; i--) {
\r
2222 self.end(value.name);
\r
2228 (function(tinymce) {
\r
2229 var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
\r
2235 '#document-fragment' : 11
\r
2238 // Walks the tree left/right
\r
2239 function walk(node, root_node, prev) {
\r
2240 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
\r
2242 // Walk into nodes if it has a start
\r
2243 if (node[startName])
\r
2244 return node[startName];
\r
2246 // Return the sibling if it has one
\r
2247 if (node !== root_node) {
\r
2248 sibling = node[siblingName];
\r
2253 // Walk up the parents to look for siblings
\r
2254 for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
\r
2255 sibling = parent[siblingName];
\r
2263 function Node(name, type) {
\r
2268 this.attributes = [];
\r
2269 this.attributes.map = {};
\r
2273 tinymce.extend(Node.prototype, {
\r
2274 replace : function(node) {
\r
2280 self.insert(node, self);
\r
2286 attr : function(name, value) {
\r
2287 var self = this, attrs, i, undef;
\r
2289 if (typeof name !== "string") {
\r
2291 self.attr(i, name[i]);
\r
2296 if (attrs = self.attributes) {
\r
2297 if (value !== undef) {
\r
2298 // Remove attribute
\r
2299 if (value === null) {
\r
2300 if (name in attrs.map) {
\r
2301 delete attrs.map[name];
\r
2305 if (attrs[i].name === name) {
\r
2306 attrs = attrs.splice(i, 1);
\r
2316 if (name in attrs.map) {
\r
2320 if (attrs[i].name === name) {
\r
2321 attrs[i].value = value;
\r
2326 attrs.push({name: name, value: value});
\r
2328 attrs.map[name] = value;
\r
2332 return attrs.map[name];
\r
2337 clone : function() {
\r
2338 var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
\r
2340 // Clone element attributes
\r
2341 if (selfAttrs = self.attributes) {
\r
2343 cloneAttrs.map = {};
\r
2345 for (i = 0, l = selfAttrs.length; i < l; i++) {
\r
2346 selfAttr = selfAttrs[i];
\r
2348 // Clone everything except id
\r
2349 if (selfAttr.name !== 'id') {
\r
2350 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
\r
2351 cloneAttrs.map[selfAttr.name] = selfAttr.value;
\r
2355 clone.attributes = cloneAttrs;
\r
2358 clone.value = self.value;
\r
2359 clone.shortEnded = self.shortEnded;
\r
2364 wrap : function(wrapper) {
\r
2367 self.parent.insert(wrapper, self);
\r
2368 wrapper.append(self);
\r
2373 unwrap : function() {
\r
2374 var self = this, node, next;
\r
2376 for (node = self.firstChild; node; ) {
\r
2378 self.insert(node, self, true);
\r
2385 remove : function() {
\r
2386 var self = this, parent = self.parent, next = self.next, prev = self.prev;
\r
2389 if (parent.firstChild === self) {
\r
2390 parent.firstChild = next;
\r
2398 if (parent.lastChild === self) {
\r
2399 parent.lastChild = prev;
\r
2407 self.parent = self.next = self.prev = null;
\r
2413 append : function(node) {
\r
2414 var self = this, last;
\r
2419 last = self.lastChild;
\r
2423 self.lastChild = node;
\r
2425 self.lastChild = self.firstChild = node;
\r
2427 node.parent = self;
\r
2432 insert : function(node, ref_node, before) {
\r
2438 parent = ref_node.parent || this;
\r
2441 if (ref_node === parent.firstChild)
\r
2442 parent.firstChild = node;
\r
2444 ref_node.prev.next = node;
\r
2446 node.prev = ref_node.prev;
\r
2447 node.next = ref_node;
\r
2448 ref_node.prev = node;
\r
2450 if (ref_node === parent.lastChild)
\r
2451 parent.lastChild = node;
\r
2453 ref_node.next.prev = node;
\r
2455 node.next = ref_node.next;
\r
2456 node.prev = ref_node;
\r
2457 ref_node.next = node;
\r
2460 node.parent = parent;
\r
2465 getAll : function(name) {
\r
2466 var self = this, node, collection = [];
\r
2468 for (node = self.firstChild; node; node = walk(node, self)) {
\r
2469 if (node.name === name)
\r
2470 collection.push(node);
\r
2473 return collection;
\r
2476 empty : function() {
\r
2477 var self = this, nodes, i, node;
\r
2479 // Remove all children
\r
2480 if (self.firstChild) {
\r
2483 // Collect the children
\r
2484 for (node = self.firstChild; node; node = walk(node, self))
\r
2487 // Remove the children
\r
2491 node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
\r
2495 self.firstChild = self.lastChild = null;
\r
2500 isEmpty : function(elements) {
\r
2501 var self = this, node = self.firstChild, i, name;
\r
2505 if (node.type === 1) {
\r
2506 // Ignore bogus elements
\r
2507 if (node.attributes.map['data-mce-bogus'])
\r
2510 // Keep empty elements like <img />
\r
2511 if (elements[node.name])
\r
2514 // Keep elements with data attributes or name attribute like <a name="1"></a>
\r
2515 i = node.attributes.length;
\r
2517 name = node.attributes[i].name;
\r
2518 if (name === "name" || name.indexOf('data-') === 0)
\r
2523 // Keep non whitespace text nodes
\r
2524 if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
\r
2526 } while (node = walk(node, self));
\r
2532 walk : function(prev) {
\r
2533 return walk(this, null, prev);
\r
2537 tinymce.extend(Node, {
\r
2538 create : function(name, attrs) {
\r
2539 var node, attrName;
\r
2542 node = new Node(name, typeLookup[name] || 1);
\r
2544 // Add attributes if needed
\r
2546 for (attrName in attrs)
\r
2547 node.attr(attrName, attrs[attrName]);
\r
2554 tinymce.html.Node = Node;
\r
2557 (function(tinymce) {
\r
2558 var Node = tinymce.html.Node;
\r
2560 tinymce.html.DomParser = function(settings, schema) {
\r
2561 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
\r
2563 settings = settings || {};
\r
2564 settings.validate = "validate" in settings ? settings.validate : true;
\r
2565 settings.root_name = settings.root_name || 'body';
\r
2566 self.schema = schema = schema || new tinymce.html.Schema();
\r
2568 function fixInvalidChildren(nodes) {
\r
2569 var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
\r
2570 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
\r
2572 nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
\r
2573 nonEmptyElements = schema.getNonEmptyElements();
\r
2575 for (ni = 0; ni < nodes.length; ni++) {
\r
2578 // Already removed
\r
2582 // Get list of all parent nodes until we find a valid parent to stick the child into
\r
2584 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
\r
2585 parents.push(parent);
\r
2587 // Found a suitable parent
\r
2588 if (parent && parents.length > 1) {
\r
2589 // Reverse the array since it makes looping easier
\r
2590 parents.reverse();
\r
2592 // Clone the related parent and insert that after the moved node
\r
2593 newParent = currentNode = self.filterNode(parents[0].clone());
\r
2595 // Start cloning and moving children on the left side of the target node
\r
2596 for (i = 0; i < parents.length - 1; i++) {
\r
2597 if (schema.isValidChild(currentNode.name, parents[i].name)) {
\r
2598 tempNode = self.filterNode(parents[i].clone());
\r
2599 currentNode.append(tempNode);
\r
2601 tempNode = currentNode;
\r
2603 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
\r
2604 nextNode = childNode.next;
\r
2605 tempNode.append(childNode);
\r
2606 childNode = nextNode;
\r
2609 currentNode = tempNode;
\r
2612 if (!newParent.isEmpty(nonEmptyElements)) {
\r
2613 parent.insert(newParent, parents[0], true);
\r
2614 parent.insert(node, newParent);
\r
2616 parent.insert(node, parents[0], true);
\r
2619 // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
\r
2620 parent = parents[0];
\r
2621 if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
\r
2622 parent.empty().remove();
\r
2624 } else if (node.parent) {
\r
2625 // If it's an LI try to find a UL/OL for it or wrap it
\r
2626 if (node.name === 'li') {
\r
2627 sibling = node.prev;
\r
2628 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
\r
2629 sibling.append(node);
\r
2633 sibling = node.next;
\r
2634 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
\r
2635 sibling.insert(node, sibling.firstChild, true);
\r
2639 node.wrap(self.filterNode(new Node('ul', 1)));
\r
2643 // Try wrapping the element in a DIV
\r
2644 if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
\r
2645 node.wrap(self.filterNode(new Node('div', 1)));
\r
2647 // We failed wrapping it, then remove or unwrap it
\r
2648 if (node.name === 'style' || node.name === 'script')
\r
2649 node.empty().remove();
\r
2657 self.filterNode = function(node) {
\r
2658 var i, name, list;
\r
2660 // Run element filters
\r
2661 if (name in nodeFilters) {
\r
2662 list = matchedNodes[name];
\r
2667 matchedNodes[name] = [node];
\r
2670 // Run attribute filters
\r
2671 i = attributeFilters.length;
\r
2673 name = attributeFilters[i].name;
\r
2675 if (name in node.attributes.map) {
\r
2676 list = matchedAttributes[name];
\r
2681 matchedAttributes[name] = [node];
\r
2688 self.addNodeFilter = function(name, callback) {
\r
2689 tinymce.each(tinymce.explode(name), function(name) {
\r
2690 var list = nodeFilters[name];
\r
2693 nodeFilters[name] = list = [];
\r
2695 list.push(callback);
\r
2699 self.addAttributeFilter = function(name, callback) {
\r
2700 tinymce.each(tinymce.explode(name), function(name) {
\r
2703 for (i = 0; i < attributeFilters.length; i++) {
\r
2704 if (attributeFilters[i].name === name) {
\r
2705 attributeFilters[i].callbacks.push(callback);
\r
2710 attributeFilters.push({name: name, callbacks: [callback]});
\r
2714 self.parse = function(html, args) {
\r
2715 var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
\r
2716 blockElements, startWhiteSpaceRegExp, invalidChildren = [],
\r
2717 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
\r
2719 args = args || {};
\r
2720 matchedNodes = {};
\r
2721 matchedAttributes = {};
\r
2722 blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
\r
2723 nonEmptyElements = schema.getNonEmptyElements();
\r
2724 children = schema.children;
\r
2725 validate = settings.validate;
\r
2726 rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
\r
2728 whiteSpaceElements = schema.getWhiteSpaceElements();
\r
2729 startWhiteSpaceRegExp = /^[ \t\r\n]+/;
\r
2730 endWhiteSpaceRegExp = /[ \t\r\n]+$/;
\r
2731 allWhiteSpaceRegExp = /[ \t\r\n]+/g;
\r
2733 function addRootBlocks() {
\r
2734 var node = rootNode.firstChild, next, rootBlockNode;
\r
2739 if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
\r
2740 if (!rootBlockNode) {
\r
2741 // Create a new root block element
\r
2742 rootBlockNode = createNode(rootBlockName, 1);
\r
2743 rootNode.insert(rootBlockNode, node);
\r
2744 rootBlockNode.append(node);
\r
2746 rootBlockNode.append(node);
\r
2748 rootBlockNode = null;
\r
2755 function createNode(name, type) {
\r
2756 var node = new Node(name, type), list;
\r
2758 if (name in nodeFilters) {
\r
2759 list = matchedNodes[name];
\r
2764 matchedNodes[name] = [node];
\r
2770 function removeWhitespaceBefore(node) {
\r
2771 var textNode, textVal, sibling;
\r
2773 for (textNode = node.prev; textNode && textNode.type === 3; ) {
\r
2774 textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
\r
2776 if (textVal.length > 0) {
\r
2777 textNode.value = textVal;
\r
2778 textNode = textNode.prev;
\r
2780 sibling = textNode.prev;
\r
2781 textNode.remove();
\r
2782 textNode = sibling;
\r
2787 parser = new tinymce.html.SaxParser({
\r
2788 validate : validate,
\r
2789 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
\r
2791 cdata: function(text) {
\r
2792 node.append(createNode('#cdata', 4)).value = text;
\r
2795 text: function(text, raw) {
\r
2798 // Trim all redundant whitespace on non white space elements
\r
2799 if (!whiteSpaceElements[node.name]) {
\r
2800 text = text.replace(allWhiteSpaceRegExp, ' ');
\r
2802 if (node.lastChild && blockElements[node.lastChild.name])
\r
2803 text = text.replace(startWhiteSpaceRegExp, '');
\r
2806 // Do we need to create the node
\r
2807 if (text.length !== 0) {
\r
2808 textNode = createNode('#text', 3);
\r
2809 textNode.raw = !!raw;
\r
2810 node.append(textNode).value = text;
\r
2814 comment: function(text) {
\r
2815 node.append(createNode('#comment', 8)).value = text;
\r
2818 pi: function(name, text) {
\r
2819 node.append(createNode(name, 7)).value = text;
\r
2820 removeWhitespaceBefore(node);
\r
2823 doctype: function(text) {
\r
2826 newNode = node.append(createNode('#doctype', 10));
\r
2827 newNode.value = text;
\r
2828 removeWhitespaceBefore(node);
\r
2831 start: function(name, attrs, empty) {
\r
2832 var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
\r
2834 elementRule = validate ? schema.getElementRule(name) : {};
\r
2835 if (elementRule) {
\r
2836 newNode = createNode(elementRule.outputName || name, 1);
\r
2837 newNode.attributes = attrs;
\r
2838 newNode.shortEnded = empty;
\r
2840 node.append(newNode);
\r
2842 // Check if node is valid child of the parent node is the child is
\r
2843 // unknown we don't collect it since it's probably a custom element
\r
2844 parent = children[node.name];
\r
2845 if (parent && children[newNode.name] && !parent[newNode.name])
\r
2846 invalidChildren.push(newNode);
\r
2848 attrFiltersLen = attributeFilters.length;
\r
2849 while (attrFiltersLen--) {
\r
2850 attrName = attributeFilters[attrFiltersLen].name;
\r
2852 if (attrName in attrs.map) {
\r
2853 list = matchedAttributes[attrName];
\r
2856 list.push(newNode);
\r
2858 matchedAttributes[attrName] = [newNode];
\r
2862 // Trim whitespace before block
\r
2863 if (blockElements[name])
\r
2864 removeWhitespaceBefore(newNode);
\r
2866 // Change current node if the element wasn't empty i.e not <br /> or <img />
\r
2872 end: function(name) {
\r
2873 var textNode, elementRule, text, sibling, tempNode;
\r
2875 elementRule = validate ? schema.getElementRule(name) : {};
\r
2876 if (elementRule) {
\r
2877 if (blockElements[name]) {
\r
2878 if (!whiteSpaceElements[node.name]) {
\r
2879 // Trim whitespace at beginning of block
\r
2880 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
\r
2881 text = textNode.value.replace(startWhiteSpaceRegExp, '');
\r
2883 if (text.length > 0) {
\r
2884 textNode.value = text;
\r
2885 textNode = textNode.next;
\r
2887 sibling = textNode.next;
\r
2888 textNode.remove();
\r
2889 textNode = sibling;
\r
2893 // Trim whitespace at end of block
\r
2894 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
\r
2895 text = textNode.value.replace(endWhiteSpaceRegExp, '');
\r
2897 if (text.length > 0) {
\r
2898 textNode.value = text;
\r
2899 textNode = textNode.prev;
\r
2901 sibling = textNode.prev;
\r
2902 textNode.remove();
\r
2903 textNode = sibling;
\r
2908 // Trim start white space
\r
2909 textNode = node.prev;
\r
2910 if (textNode && textNode.type === 3) {
\r
2911 text = textNode.value.replace(startWhiteSpaceRegExp, '');
\r
2913 if (text.length > 0)
\r
2914 textNode.value = text;
\r
2916 textNode.remove();
\r
2920 // Handle empty nodes
\r
2921 if (elementRule.removeEmpty || elementRule.paddEmpty) {
\r
2922 if (node.isEmpty(nonEmptyElements)) {
\r
2923 if (elementRule.paddEmpty)
\r
2924 node.empty().append(new Node('#text', '3')).value = '\u00a0';
\r
2926 // Leave nodes that have a name like <a name="name">
\r
2927 if (!node.attributes.map.name) {
\r
2928 tempNode = node.parent;
\r
2929 node.empty().remove();
\r
2937 node = node.parent;
\r
2942 rootNode = node = new Node(args.context || settings.root_name, 11);
\r
2944 parser.parse(html);
\r
2946 // Fix invalid children or report invalid children in a contextual parsing
\r
2947 if (validate && invalidChildren.length) {
\r
2948 if (!args.context)
\r
2949 fixInvalidChildren(invalidChildren);
\r
2951 args.invalid = true;
\r
2954 // Wrap nodes in the root into block elements if the root is body
\r
2955 if (rootBlockName && rootNode.name == 'body')
\r
2958 // Run filters only when the contents is valid
\r
2959 if (!args.invalid) {
\r
2960 // Run node filters
\r
2961 for (name in matchedNodes) {
\r
2962 list = nodeFilters[name];
\r
2963 nodes = matchedNodes[name];
\r
2965 // Remove already removed children
\r
2966 fi = nodes.length;
\r
2968 if (!nodes[fi].parent)
\r
2969 nodes.splice(fi, 1);
\r
2972 for (i = 0, l = list.length; i < l; i++)
\r
2973 list[i](nodes, name, args);
\r
2976 // Run attribute filters
\r
2977 for (i = 0, l = attributeFilters.length; i < l; i++) {
\r
2978 list = attributeFilters[i];
\r
2980 if (list.name in matchedAttributes) {
\r
2981 nodes = matchedAttributes[list.name];
\r
2983 // Remove already removed children
\r
2984 fi = nodes.length;
\r
2986 if (!nodes[fi].parent)
\r
2987 nodes.splice(fi, 1);
\r
2990 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
\r
2991 list.callbacks[fi](nodes, list.name, args);
\r
2999 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
\r
3000 // make it possible to place the caret inside empty blocks. This logic tries to remove
\r
3001 // these elements and keep br elements that where intended to be there intact
\r
3002 if (settings.remove_trailing_brs) {
\r
3003 self.addNodeFilter('br', function(nodes, name) {
\r
3004 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
\r
3005 nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
\r
3007 // Remove brs from body element as well
\r
3008 blockElements.body = 1;
\r
3010 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
\r
3011 for (i = 0; i < l; i++) {
\r
3013 parent = node.parent;
\r
3015 if (blockElements[node.parent.name] && node === parent.lastChild) {
\r
3016 // Loop all nodes to the right of the current node and check for other BR elements
\r
3017 // excluding bookmarks since they are invisible
\r
3020 prevName = prev.name;
\r
3022 // Ignore bookmarks
\r
3023 if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
\r
3024 // Found a non BR element
\r
3025 if (prevName !== "br")
\r
3028 // Found another br it's a <br><br> structure then don't remove anything
\r
3029 if (prevName === 'br') {
\r
3041 // Is the parent to be considered empty after we removed the BR
\r
3042 if (parent.isEmpty(nonEmptyElements)) {
\r
3043 elementRule = schema.getElementRule(parent.name);
\r
3045 // Remove or padd the element depending on schema rule
\r
3046 if (elementRule.removeEmpty)
\r
3048 else if (elementRule.paddEmpty)
\r
3049 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
\r
3059 tinymce.html.Writer = function(settings) {
\r
3060 var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
\r
3062 settings = settings || {};
\r
3063 indent = settings.indent;
\r
3064 indentBefore = tinymce.makeMap(settings.indent_before || '');
\r
3065 indentAfter = tinymce.makeMap(settings.indent_after || '');
\r
3066 encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
\r
3067 htmlOutput = settings.element_format == "html";
\r
3070 start: function(name, attrs, empty) {
\r
3071 var i, l, attr, value;
\r
3073 if (indent && indentBefore[name] && html.length > 0) {
\r
3074 value = html[html.length - 1];
\r
3076 if (value.length > 0 && value !== '\n')
\r
3080 html.push('<', name);
\r
3083 for (i = 0, l = attrs.length; i < l; i++) {
\r
3085 html.push(' ', attr.name, '="', encode(attr.value, true), '"');
\r
3089 if (!empty || htmlOutput)
\r
3090 html[html.length] = '>';
\r
3092 html[html.length] = ' />';
\r
3094 if (empty && indent && indentAfter[name] && html.length > 0) {
\r
3095 value = html[html.length - 1];
\r
3097 if (value.length > 0 && value !== '\n')
\r
3102 end: function(name) {
\r
3105 /*if (indent && indentBefore[name] && html.length > 0) {
\r
3106 value = html[html.length - 1];
\r
3108 if (value.length > 0 && value !== '\n')
\r
3112 html.push('</', name, '>');
\r
3114 if (indent && indentAfter[name] && html.length > 0) {
\r
3115 value = html[html.length - 1];
\r
3117 if (value.length > 0 && value !== '\n')
\r
3122 text: function(text, raw) {
\r
3123 if (text.length > 0)
\r
3124 html[html.length] = raw ? text : encode(text);
\r
3127 cdata: function(text) {
\r
3128 html.push('<![CDATA[', text, ']]>');
\r
3131 comment: function(text) {
\r
3132 html.push('<!--', text, '-->');
\r
3135 pi: function(name, text) {
\r
3137 html.push('<?', name, ' ', text, '?>');
\r
3139 html.push('<?', name, '?>');
\r
3145 doctype: function(text) {
\r
3146 html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
\r
3149 reset: function() {
\r
3153 getContent: function() {
\r
3154 return html.join('').replace(/\n$/, '');
\r
3159 (function(tinymce) {
\r
3160 tinymce.html.Serializer = function(settings, schema) {
\r
3161 var self = this, writer = new tinymce.html.Writer(settings);
\r
3163 settings = settings || {};
\r
3164 settings.validate = "validate" in settings ? settings.validate : true;
\r
3166 self.schema = schema = schema || new tinymce.html.Schema();
\r
3167 self.writer = writer;
\r
3169 self.serialize = function(node) {
\r
3170 var handlers, validate;
\r
3172 validate = settings.validate;
\r
3176 3: function(node, raw) {
\r
3177 writer.text(node.value, node.raw);
\r
3181 8: function(node) {
\r
3182 writer.comment(node.value);
\r
3185 // Processing instruction
\r
3186 7: function(node) {
\r
3187 writer.pi(node.name, node.value);
\r
3191 10: function(node) {
\r
3192 writer.doctype(node.value);
\r
3196 4: function(node) {
\r
3197 writer.cdata(node.value);
\r
3200 // Document fragment
\r
3201 11: function(node) {
\r
3202 if ((node = node.firstChild)) {
\r
3205 } while (node = node.next);
\r
3212 function walk(node) {
\r
3213 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
\r
3217 isEmpty = node.shortEnded;
\r
3218 attrs = node.attributes;
\r
3220 // Sort attributes
\r
3221 if (validate && attrs && attrs.length > 1) {
\r
3223 sortedAttrs.map = {};
\r
3225 elementRule = schema.getElementRule(node.name);
\r
3226 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
\r
3227 attrName = elementRule.attributesOrder[i];
\r
3229 if (attrName in attrs.map) {
\r
3230 attrValue = attrs.map[attrName];
\r
3231 sortedAttrs.map[attrName] = attrValue;
\r
3232 sortedAttrs.push({name: attrName, value: attrValue});
\r
3236 for (i = 0, l = attrs.length; i < l; i++) {
\r
3237 attrName = attrs[i].name;
\r
3239 if (!(attrName in sortedAttrs.map)) {
\r
3240 attrValue = attrs.map[attrName];
\r
3241 sortedAttrs.map[attrName] = attrValue;
\r
3242 sortedAttrs.push({name: attrName, value: attrValue});
\r
3246 attrs = sortedAttrs;
\r
3249 writer.start(node.name, attrs, isEmpty);
\r
3252 if ((node = node.firstChild)) {
\r
3255 } while (node = node.next);
\r
3264 // Serialize element and treat all non elements as fragments
\r
3265 if (node.type == 1 && !settings.inner)
\r
3268 handlers[11](node);
\r
3270 return writer.getContent();
\r
3275 (function(tinymce) {
\r
3277 var each = tinymce.each,
\r
3279 isWebKit = tinymce.isWebKit,
\r
3280 isIE = tinymce.isIE,
\r
3281 Entities = tinymce.html.Entities,
\r
3282 simpleSelectorRe = /^([a-z0-9],?)+$/i,
\r
3283 blockElementsMap = tinymce.html.Schema.blockElementsMap,
\r
3284 whiteSpaceRegExp = /^[ \t\r\n]*$/;
\r
3286 tinymce.create('tinymce.dom.DOMUtils', {
\r
3290 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
\r
3292 "for" : "htmlFor",
\r
3293 "class" : "className",
\r
3294 className : "className",
\r
3295 checked : "checked",
\r
3296 disabled : "disabled",
\r
3297 maxlength : "maxLength",
\r
3298 readonly : "readOnly",
\r
3299 selected : "selected",
\r
3306 DOMUtils : function(d, s) {
\r
3307 var t = this, globalStyle, name;
\r
3312 t.cssFlicker = false;
\r
3314 t.stdMode = !tinymce.isIE || d.documentMode >= 8;
\r
3315 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
\r
3316 t.hasOuterHTML = "outerHTML" in d.createElement("a");
\r
3318 t.settings = s = tinymce.extend({
\r
3319 keep_values : false,
\r
3323 t.schema = s.schema;
\r
3324 t.styles = new tinymce.html.Styles({
\r
3325 url_converter : s.url_converter,
\r
3326 url_converter_scope : s.url_converter_scope
\r
3329 // Fix IE6SP2 flicker and check it failed for pre SP2
\r
3330 if (tinymce.isIE6) {
\r
3332 d.execCommand('BackgroundImageCache', false, true);
\r
3334 t.cssFlicker = true;
\r
3338 if (isIE && s.schema) {
\r
3339 // Add missing HTML 4/5 elements to IE
\r
3340 ('abbr article aside audio canvas ' +
\r
3341 'details figcaption figure footer ' +
\r
3342 'header hgroup mark menu meter nav ' +
\r
3343 'output progress section summary ' +
\r
3344 'time video').replace(/\w+/g, function(name) {
\r
3345 d.createElement(name);
\r
3348 // Create all custom elements
\r
3349 for (name in s.schema.getCustomElements()) {
\r
3350 d.createElement(name);
\r
3354 tinymce.addUnload(t.destroy, t);
\r
3357 getRoot : function() {
\r
3358 var t = this, s = t.settings;
\r
3360 return (s && t.get(s.root_element)) || t.doc.body;
\r
3363 getViewPort : function(w) {
\r
3366 w = !w ? this.win : w;
\r
3368 b = this.boxModel ? d.documentElement : d.body;
\r
3370 // Returns viewport size excluding scrollbars
\r
3372 x : w.pageXOffset || b.scrollLeft,
\r
3373 y : w.pageYOffset || b.scrollTop,
\r
3374 w : w.innerWidth || b.clientWidth,
\r
3375 h : w.innerHeight || b.clientHeight
\r
3379 getRect : function(e) {
\r
3380 var p, t = this, sr;
\r
3384 sr = t.getSize(e);
\r
3394 getSize : function(e) {
\r
3395 var t = this, w, h;
\r
3398 w = t.getStyle(e, 'width');
\r
3399 h = t.getStyle(e, 'height');
\r
3401 // Non pixel value, then force offset/clientWidth
\r
3402 if (w.indexOf('px') === -1)
\r
3405 // Non pixel value, then force offset/clientWidth
\r
3406 if (h.indexOf('px') === -1)
\r
3410 w : parseInt(w) || e.offsetWidth || e.clientWidth,
\r
3411 h : parseInt(h) || e.offsetHeight || e.clientHeight
\r
3415 getParent : function(n, f, r) {
\r
3416 return this.getParents(n, f, r, false);
\r
3419 getParents : function(n, f, r, c) {
\r
3420 var t = this, na, se = t.settings, o = [];
\r
3423 c = c === undefined;
\r
3425 if (se.strict_root)
\r
3426 r = r || t.getRoot();
\r
3428 // Wrap node name as func
\r
3429 if (is(f, 'string')) {
\r
3433 f = function(n) {return n.nodeType == 1;};
\r
3436 return t.is(n, na);
\r
3442 if (n == r || !n.nodeType || n.nodeType === 9)
\r
3455 return c ? o : null;
\r
3458 get : function(e) {
\r
3461 if (e && this.doc && typeof(e) == 'string') {
\r
3463 e = this.doc.getElementById(e);
\r
3465 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
\r
3466 if (e && e.id !== n)
\r
3467 return this.doc.getElementsByName(n)[1];
\r
3473 getNext : function(node, selector) {
\r
3474 return this._findSib(node, selector, 'nextSibling');
\r
3477 getPrev : function(node, selector) {
\r
3478 return this._findSib(node, selector, 'previousSibling');
\r
3482 select : function(pa, s) {
\r
3485 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
\r
3488 is : function(n, selector) {
\r
3491 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
\r
3492 if (n.length === undefined) {
\r
3493 // Simple all selector
\r
3494 if (selector === '*')
\r
3495 return n.nodeType == 1;
\r
3497 // Simple selector just elements
\r
3498 if (simpleSelectorRe.test(selector)) {
\r
3499 selector = selector.toLowerCase().split(/,/);
\r
3500 n = n.nodeName.toLowerCase();
\r
3502 for (i = selector.length - 1; i >= 0; i--) {
\r
3503 if (selector[i] == n)
\r
3511 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
\r
3515 add : function(p, n, a, h, c) {
\r
3518 return this.run(p, function(p) {
\r
3521 e = is(n, 'string') ? t.doc.createElement(n) : n;
\r
3522 t.setAttribs(e, a);
\r
3531 return !c ? p.appendChild(e) : e;
\r
3535 create : function(n, a, h) {
\r
3536 return this.add(this.doc.createElement(n), n, a, h, 1);
\r
3539 createHTML : function(n, a, h) {
\r
3540 var o = '', t = this, k;
\r
3545 if (a.hasOwnProperty(k))
\r
3546 o += ' ' + k + '="' + t.encode(a[k]) + '"';
\r
3549 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
\r
3550 if (typeof(h) != "undefined")
\r
3551 return o + '>' + h + '</' + n + '>';
\r
3556 remove : function(node, keep_children) {
\r
3557 return this.run(node, function(node) {
\r
3558 var child, parent = node.parentNode;
\r
3563 if (keep_children) {
\r
3564 while (child = node.firstChild) {
\r
3565 // IE 8 will crash if you don't remove completely empty text nodes
\r
3566 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
\r
3567 parent.insertBefore(child, node);
\r
3569 node.removeChild(child);
\r
3573 return parent.removeChild(node);
\r
3577 setStyle : function(n, na, v) {
\r
3580 return t.run(n, function(e) {
\r
3585 // Camelcase it, if needed
\r
3586 na = na.replace(/-(\D)/g, function(a, b){
\r
3587 return b.toUpperCase();
\r
3590 // Default px suffix on these
\r
3591 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
\r
3596 // IE specific opacity
\r
3598 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
\r
3600 if (!n.currentStyle || !n.currentStyle.hasLayout)
\r
3601 s.display = 'inline-block';
\r
3604 // Fix for older browsers
\r
3605 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
\r
3609 isIE ? s.styleFloat = v : s.cssFloat = v;
\r
3616 // Force update of the style data
\r
3617 if (t.settings.update_styles)
\r
3618 t.setAttrib(e, 'data-mce-style');
\r
3622 getStyle : function(n, na, c) {
\r
3629 if (this.doc.defaultView && c) {
\r
3630 // Remove camelcase
\r
3631 na = na.replace(/[A-Z]/g, function(a){
\r
3636 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
\r
3638 // Old safari might fail
\r
3643 // Camelcase it, if needed
\r
3644 na = na.replace(/-(\D)/g, function(a, b){
\r
3645 return b.toUpperCase();
\r
3648 if (na == 'float')
\r
3649 na = isIE ? 'styleFloat' : 'cssFloat';
\r
3652 if (n.currentStyle && c)
\r
3653 return n.currentStyle[na];
\r
3655 return n.style ? n.style[na] : undefined;
\r
3658 setStyles : function(e, o) {
\r
3659 var t = this, s = t.settings, ol;
\r
3661 ol = s.update_styles;
\r
3662 s.update_styles = 0;
\r
3664 each(o, function(v, n) {
\r
3665 t.setStyle(e, n, v);
\r
3668 // Update style info
\r
3669 s.update_styles = ol;
\r
3670 if (s.update_styles)
\r
3671 t.setAttrib(e, s.cssText);
\r
3674 removeAllAttribs: function(e) {
\r
3675 return this.run(e, function(e) {
\r
3676 var i, attrs = e.attributes;
\r
3677 for (i = attrs.length - 1; i >= 0; i--) {
\r
3678 e.removeAttributeNode(attrs.item(i));
\r
3683 setAttrib : function(e, n, v) {
\r
3686 // Whats the point
\r
3690 // Strict XML mode
\r
3691 if (t.settings.strict)
\r
3692 n = n.toLowerCase();
\r
3694 return this.run(e, function(e) {
\r
3695 var s = t.settings;
\r
3699 if (!is(v, 'string')) {
\r
3700 each(v, function(v, n) {
\r
3701 t.setStyle(e, n, v);
\r
3707 // No mce_style for elements with these since they might get resized by the user
\r
3708 if (s.keep_values) {
\r
3709 if (v && !t._isRes(v))
\r
3710 e.setAttribute('data-mce-style', v, 2);
\r
3712 e.removeAttribute('data-mce-style', 2);
\r
3715 e.style.cssText = v;
\r
3719 e.className = v || ''; // Fix IE null bug
\r
3724 if (s.keep_values) {
\r
3725 if (s.url_converter)
\r
3726 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
\r
3728 t.setAttrib(e, 'data-mce-' + n, v, 2);
\r
3734 e.setAttribute('data-mce-style', v);
\r
3738 if (is(v) && v !== null && v.length !== 0)
\r
3739 e.setAttribute(n, '' + v, 2);
\r
3741 e.removeAttribute(n, 2);
\r
3745 setAttribs : function(e, o) {
\r
3748 return this.run(e, function(e) {
\r
3749 each(o, function(v, n) {
\r
3750 t.setAttrib(e, n, v);
\r
3755 getAttrib : function(e, n, dv) {
\r
3760 if (!e || e.nodeType !== 1)
\r
3766 // Try the mce variant for these
\r
3767 if (/^(src|href|style|coords|shape)$/.test(n)) {
\r
3768 v = e.getAttribute("data-mce-" + n);
\r
3774 if (isIE && t.props[n]) {
\r
3775 v = e[t.props[n]];
\r
3776 v = v && v.nodeValue ? v.nodeValue : v;
\r
3780 v = e.getAttribute(n, 2);
\r
3782 // Check boolean attribs
\r
3783 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
\r
3784 if (e[t.props[n]] === true && v === '')
\r
3787 return v ? n : '';
\r
3790 // Inner input elements will override attributes on form elements
\r
3791 if (e.nodeName === "FORM" && e.getAttributeNode(n))
\r
3792 return e.getAttributeNode(n).nodeValue;
\r
3794 if (n === 'style') {
\r
3795 v = v || e.style.cssText;
\r
3798 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
\r
3800 if (t.settings.keep_values && !t._isRes(v))
\r
3801 e.setAttribute('data-mce-style', v);
\r
3805 // Remove Apple and WebKit stuff
\r
3806 if (isWebKit && n === "class" && v)
\r
3807 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
\r
3809 // Handle IE issues
\r
3814 // IE returns 1 as default value
\r
3821 // IE returns +0 as default value for size
\r
3822 if (v === '+0' || v === 20 || v === 0)
\r
3839 // IE returns -1 as default value
\r
3847 // IE returns default value
\r
3848 if (v === 32768 || v === 2147483647 || v === '32768')
\r
3863 v = v.toLowerCase();
\r
3867 // IE has odd anonymous function for event attributes
\r
3868 if (n.indexOf('on') === 0 && v)
\r
3869 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
\r
3873 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
\r
3876 getPos : function(n, ro) {
\r
3877 var t = this, x = 0, y = 0, e, d = t.doc, r;
\r
3880 ro = ro || d.body;
\r
3883 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
\r
3884 if (n.getBoundingClientRect) {
\r
3885 n = n.getBoundingClientRect();
\r
3886 e = t.boxModel ? d.documentElement : d.body;
\r
3888 // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
\r
3889 // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
\r
3890 x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
\r
3891 y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
\r
3893 return {x : x, y : y};
\r
3897 while (r && r != ro && r.nodeType) {
\r
3898 x += r.offsetLeft || 0;
\r
3899 y += r.offsetTop || 0;
\r
3900 r = r.offsetParent;
\r
3904 while (r && r != ro && r.nodeType) {
\r
3905 x -= r.scrollLeft || 0;
\r
3906 y -= r.scrollTop || 0;
\r
3911 return {x : x, y : y};
\r
3914 parseStyle : function(st) {
\r
3915 return this.styles.parse(st);
\r
3918 serializeStyle : function(o, name) {
\r
3919 return this.styles.serialize(o, name);
\r
3922 loadCSS : function(u) {
\r
3923 var t = this, d = t.doc, head;
\r
3928 head = t.select('head')[0];
\r
3930 each(u.split(','), function(u) {
\r
3936 t.files[u] = true;
\r
3937 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
\r
3939 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
\r
3940 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
\r
3941 // It's ugly but it seems to work fine.
\r
3942 if (isIE && d.documentMode && d.recalc) {
\r
3943 link.onload = function() {
\r
3947 link.onload = null;
\r
3951 head.appendChild(link);
\r
3955 addClass : function(e, c) {
\r
3956 return this.run(e, function(e) {
\r
3962 if (this.hasClass(e, c))
\r
3963 return e.className;
\r
3965 o = this.removeClass(e, c);
\r
3967 return e.className = (o != '' ? (o + ' ') : '') + c;
\r
3971 removeClass : function(e, c) {
\r
3974 return t.run(e, function(e) {
\r
3977 if (t.hasClass(e, c)) {
\r
3979 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
\r
3981 v = e.className.replace(re, ' ');
\r
3982 v = tinymce.trim(v != ' ' ? v : '');
\r
3986 // Empty class attr
\r
3988 e.removeAttribute('class');
\r
3989 e.removeAttribute('className');
\r
3995 return e.className;
\r
3999 hasClass : function(n, c) {
\r
4005 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
\r
4008 show : function(e) {
\r
4009 return this.setStyle(e, 'display', 'block');
\r
4012 hide : function(e) {
\r
4013 return this.setStyle(e, 'display', 'none');
\r
4016 isHidden : function(e) {
\r
4019 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
\r
4022 uniqueId : function(p) {
\r
4023 return (!p ? 'mce_' : p) + (this.counter++);
\r
4026 setHTML : function(element, html) {
\r
4029 return self.run(element, function(element) {
\r
4031 // Remove all child nodes, IE keeps empty text nodes in DOM
\r
4032 while (element.firstChild)
\r
4033 element.removeChild(element.firstChild);
\r
4036 // IE will remove comments from the beginning
\r
4037 // unless you padd the contents with something
\r
4038 element.innerHTML = '<br />' + html;
\r
4039 element.removeChild(element.firstChild);
\r
4041 // 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
\r
4042 // This seems to fix this problem
\r
4044 // Create new div with HTML contents and a BR infront to keep comments
\r
4045 element = self.create('div');
\r
4046 element.innerHTML = '<br />' + html;
\r
4048 // Add all children from div to target
\r
4049 each (element.childNodes, function(node, i) {
\r
4050 // Skip br element
\r
4052 element.appendChild(node);
\r
4056 element.innerHTML = html;
\r
4062 getOuterHTML : function(elm) {
\r
4063 var doc, self = this;
\r
4065 elm = self.get(elm);
\r
4070 if (elm.nodeType === 1 && self.hasOuterHTML)
\r
4071 return elm.outerHTML;
\r
4073 doc = (elm.ownerDocument || self.doc).createElement("body");
\r
4074 doc.appendChild(elm.cloneNode(true));
\r
4076 return doc.innerHTML;
\r
4079 setOuterHTML : function(e, h, d) {
\r
4082 function setHTML(e, h, d) {
\r
4085 tp = d.createElement("body");
\r
4090 t.insertAfter(n.cloneNode(true), e);
\r
4091 n = n.previousSibling;
\r
4097 return this.run(e, function(e) {
\r
4100 // Only set HTML on elements
\r
4101 if (e.nodeType == 1) {
\r
4102 d = d || e.ownerDocument || t.doc;
\r
4106 // Try outerHTML for IE it sometimes produces an unknown runtime error
\r
4107 if (isIE && e.nodeType == 1)
\r
4112 // Fix for unknown runtime error
\r
4121 decode : Entities.decode,
\r
4123 encode : Entities.encodeAllRaw,
\r
4125 insertAfter : function(node, reference_node) {
\r
4126 reference_node = this.get(reference_node);
\r
4128 return this.run(node, function(node) {
\r
4129 var parent, nextSibling;
\r
4131 parent = reference_node.parentNode;
\r
4132 nextSibling = reference_node.nextSibling;
\r
4135 parent.insertBefore(node, nextSibling);
\r
4137 parent.appendChild(node);
\r
4143 isBlock : function(node) {
\r
4144 var type = node.nodeType;
\r
4146 // If it's a node then check the type and use the nodeName
\r
4148 return !!(type === 1 && blockElementsMap[node.nodeName]);
\r
4150 return !!blockElementsMap[node];
\r
4153 replace : function(n, o, k) {
\r
4156 if (is(o, 'array'))
\r
4157 n = n.cloneNode(true);
\r
4159 return t.run(o, function(o) {
\r
4161 each(tinymce.grep(o.childNodes), function(c) {
\r
4166 return o.parentNode.replaceChild(n, o);
\r
4170 rename : function(elm, name) {
\r
4171 var t = this, newElm;
\r
4173 if (elm.nodeName != name.toUpperCase()) {
\r
4174 // Rename block element
\r
4175 newElm = t.create(name);
\r
4177 // Copy attribs to new block
\r
4178 each(t.getAttribs(elm), function(attr_node) {
\r
4179 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
\r
4183 t.replace(newElm, elm, 1);
\r
4186 return newElm || elm;
\r
4189 findCommonAncestor : function(a, b) {
\r
4195 while (pe && ps != pe)
\r
4196 pe = pe.parentNode;
\r
4201 ps = ps.parentNode;
\r
4204 if (!ps && a.ownerDocument)
\r
4205 return a.ownerDocument.documentElement;
\r
4210 toHex : function(s) {
\r
4211 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
\r
4214 s = parseInt(s).toString(16);
\r
4216 return s.length > 1 ? s : '0' + s; // 0 -> 00
\r
4220 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
\r
4228 getClasses : function() {
\r
4229 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
\r
4234 function addClasses(s) {
\r
4235 // IE style imports
\r
4236 each(s.imports, function(r) {
\r
4240 each(s.cssRules || s.rules, function(r) {
\r
4241 // Real type or fake it on IE
\r
4242 switch (r.type || 1) {
\r
4245 if (r.selectorText) {
\r
4246 each(r.selectorText.split(','), function(v) {
\r
4247 v = v.replace(/^\s*|\s*$|^\s\./g, "");
\r
4249 // Is internal or it doesn't contain a class
\r
4250 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
\r
4253 // Remove everything but class name
\r
4255 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
\r
4258 if (f && !(v = f(v, ov)))
\r
4262 cl.push({'class' : v});
\r
4271 addClasses(r.styleSheet);
\r
4278 each(t.doc.styleSheets, addClasses);
\r
4283 if (cl.length > 0)
\r
4289 run : function(e, f, s) {
\r
4292 if (t.doc && typeof(e) === 'string')
\r
4299 if (!e.nodeType && (e.length || e.length === 0)) {
\r
4302 each(e, function(e, i) {
\r
4304 if (typeof(e) == 'string')
\r
4305 e = t.doc.getElementById(e);
\r
4307 o.push(f.call(s, e, i));
\r
4314 return f.call(s, e);
\r
4317 getAttribs : function(n) {
\r
4328 // Object will throw exception in IE
\r
4329 if (n.nodeName == 'OBJECT')
\r
4330 return n.attributes;
\r
4332 // IE doesn't keep the selected attribute if you clone option elements
\r
4333 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
\r
4334 o.push({specified : 1, nodeName : 'selected'});
\r
4336 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
\r
4337 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
\r
4338 o.push({specified : 1, nodeName : a});
\r
4344 return n.attributes;
\r
4347 isEmpty : function(node, elements) {
\r
4348 var self = this, i, attributes, type, walker, name;
\r
4350 node = node.firstChild;
\r
4352 walker = new tinymce.dom.TreeWalker(node);
\r
4353 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
\r
4356 type = node.nodeType;
\r
4359 // Ignore bogus elements
\r
4360 if (node.getAttribute('data-mce-bogus'))
\r
4363 // Keep empty elements like <img />
\r
4364 if (elements && elements[node.nodeName.toLowerCase()])
\r
4367 // Keep elements with data attributes or name attribute like <a name="1"></a>
\r
4368 attributes = self.getAttribs(node);
\r
4369 i = node.attributes.length;
\r
4371 name = node.attributes[i].nodeName;
\r
4372 if (name === "name" || name.indexOf('data-') === 0)
\r
4377 // Keep non whitespace text nodes
\r
4378 if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
\r
4380 } while (node = walker.next());
\r
4386 destroy : function(s) {
\r
4390 t.events.destroy();
\r
4392 t.win = t.doc = t.root = t.events = null;
\r
4394 // Manual destroy then remove unload handler
\r
4396 tinymce.removeUnload(t.destroy);
\r
4399 createRng : function() {
\r
4402 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
\r
4405 nodeIndex : function(node, normalized) {
\r
4406 var idx = 0, lastNodeType, lastNode, nodeType;
\r
4409 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
\r
4410 nodeType = node.nodeType;
\r
4412 // Normalize text nodes
\r
4413 if (normalized && nodeType == 3) {
\r
4414 if (nodeType == lastNodeType || !node.nodeValue.length)
\r
4418 lastNodeType = nodeType;
\r
4425 split : function(pe, e, re) {
\r
4426 var t = this, r = t.createRng(), bef, aft, pa;
\r
4428 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
\r
4429 // but we don't want that in our code since it serves no purpose for the end user
\r
4430 // For example if this is chopped:
\r
4431 // <p>text 1<span><b>CHOP</b></span>text 2</p>
\r
4433 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
\r
4434 // this function will then trim of empty edges and produce:
\r
4435 // <p>text 1</p><b>CHOP</b><p>text 2</p>
\r
4436 function trim(node) {
\r
4437 var i, children = node.childNodes, type = node.nodeType;
\r
4439 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
\r
4442 for (i = children.length - 1; i >= 0; i--)
\r
4443 trim(children[i]);
\r
4446 // Keep non whitespace text nodes
\r
4447 if (type == 3 && node.nodeValue.length > 0) {
\r
4448 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
\r
4449 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
\r
4451 } else if (type == 1) {
\r
4452 // If the only child is a bookmark then move it up
\r
4453 children = node.childNodes;
\r
4454 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
\r
4455 node.parentNode.insertBefore(children[0], node);
\r
4457 // Keep non empty elements or img, hr etc
\r
4458 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
\r
4469 // Get before chunk
\r
4470 r.setStart(pe.parentNode, t.nodeIndex(pe));
\r
4471 r.setEnd(e.parentNode, t.nodeIndex(e));
\r
4472 bef = r.extractContents();
\r
4474 // Get after chunk
\r
4475 r = t.createRng();
\r
4476 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
\r
4477 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
\r
4478 aft = r.extractContents();
\r
4480 // Insert before chunk
\r
4481 pa = pe.parentNode;
\r
4482 pa.insertBefore(trim(bef), pe);
\r
4484 // Insert middle chunk
\r
4486 pa.replaceChild(re, e);
\r
4488 pa.insertBefore(e, pe);
\r
4490 // Insert after chunk
\r
4491 pa.insertBefore(trim(aft), pe);
\r
4498 bind : function(target, name, func, scope) {
\r
4502 t.events = new tinymce.dom.EventUtils();
\r
4504 return t.events.add(target, name, func, scope || this);
\r
4507 unbind : function(target, name, func) {
\r
4511 t.events = new tinymce.dom.EventUtils();
\r
4513 return t.events.remove(target, name, func);
\r
4517 _findSib : function(node, selector, name) {
\r
4518 var t = this, f = selector;
\r
4521 // If expression make a function of it using is
\r
4522 if (is(f, 'string')) {
\r
4523 f = function(node) {
\r
4524 return t.is(node, selector);
\r
4528 // Loop all siblings
\r
4529 for (node = node[name]; node; node = node[name]) {
\r
4538 _isRes : function(c) {
\r
4539 // Is live resizble element
\r
4540 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
\r
4544 walk : function(n, f, s) {
\r
4545 var d = this.doc, w;
\r
4547 if (d.createTreeWalker) {
\r
4548 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
\r
4550 while ((n = w.nextNode()) != null)
\r
4551 f.call(s || this, n);
\r
4553 tinymce.walk(n, f, 'childNodes', s);
\r
4558 toRGB : function(s) {
\r
4559 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
\r
4562 // #FFF -> #FFFFFF
\r
4564 c[3] = c[2] = c[1];
\r
4566 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
\r
4574 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
\r
4578 // Range constructor
\r
4579 function Range(dom) {
\r
4587 START_OFFSET = 'startOffset',
\r
4588 START_CONTAINER = 'startContainer',
\r
4589 END_CONTAINER = 'endContainer',
\r
4590 END_OFFSET = 'endOffset',
\r
4591 extend = tinymce.extend,
\r
4592 nodeIndex = dom.nodeIndex;
\r
4596 startContainer : doc,
\r
4598 endContainer : doc,
\r
4601 commonAncestorContainer : doc,
\r
4603 // Range constants
\r
4604 START_TO_START : 0,
\r
4610 setStart : setStart,
\r
4612 setStartBefore : setStartBefore,
\r
4613 setStartAfter : setStartAfter,
\r
4614 setEndBefore : setEndBefore,
\r
4615 setEndAfter : setEndAfter,
\r
4616 collapse : collapse,
\r
4617 selectNode : selectNode,
\r
4618 selectNodeContents : selectNodeContents,
\r
4619 compareBoundaryPoints : compareBoundaryPoints,
\r
4620 deleteContents : deleteContents,
\r
4621 extractContents : extractContents,
\r
4622 cloneContents : cloneContents,
\r
4623 insertNode : insertNode,
\r
4624 surroundContents : surroundContents,
\r
4625 cloneRange : cloneRange
\r
4628 function setStart(n, o) {
\r
4629 _setEndPoint(TRUE, n, o);
\r
4632 function setEnd(n, o) {
\r
4633 _setEndPoint(FALSE, n, o);
\r
4636 function setStartBefore(n) {
\r
4637 setStart(n.parentNode, nodeIndex(n));
\r
4640 function setStartAfter(n) {
\r
4641 setStart(n.parentNode, nodeIndex(n) + 1);
\r
4644 function setEndBefore(n) {
\r
4645 setEnd(n.parentNode, nodeIndex(n));
\r
4648 function setEndAfter(n) {
\r
4649 setEnd(n.parentNode, nodeIndex(n) + 1);
\r
4652 function collapse(ts) {
\r
4654 t[END_CONTAINER] = t[START_CONTAINER];
\r
4655 t[END_OFFSET] = t[START_OFFSET];
\r
4657 t[START_CONTAINER] = t[END_CONTAINER];
\r
4658 t[START_OFFSET] = t[END_OFFSET];
\r
4661 t.collapsed = TRUE;
\r
4664 function selectNode(n) {
\r
4665 setStartBefore(n);
\r
4669 function selectNodeContents(n) {
\r
4671 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
\r
4674 function compareBoundaryPoints(h, r) {
\r
4675 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
\r
4676 rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
\r
4678 // Check START_TO_START
\r
4680 return _compareBoundaryPoints(sc, so, rsc, rso);
\r
4682 // Check START_TO_END
\r
4684 return _compareBoundaryPoints(ec, eo, rsc, rso);
\r
4686 // Check END_TO_END
\r
4688 return _compareBoundaryPoints(ec, eo, rec, reo);
\r
4690 // Check END_TO_START
\r
4692 return _compareBoundaryPoints(sc, so, rec, reo);
\r
4695 function deleteContents() {
\r
4696 _traverse(DELETE);
\r
4699 function extractContents() {
\r
4700 return _traverse(EXTRACT);
\r
4703 function cloneContents() {
\r
4704 return _traverse(CLONE);
\r
4707 function insertNode(n) {
\r
4708 var startContainer = this[START_CONTAINER],
\r
4709 startOffset = this[START_OFFSET], nn, o;
\r
4711 // Node is TEXT_NODE or CDATA
\r
4712 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
\r
4713 if (!startOffset) {
\r
4714 // At the start of text
\r
4715 startContainer.parentNode.insertBefore(n, startContainer);
\r
4716 } else if (startOffset >= startContainer.nodeValue.length) {
\r
4717 // At the end of text
\r
4718 dom.insertAfter(n, startContainer);
\r
4720 // Middle, need to split
\r
4721 nn = startContainer.splitText(startOffset);
\r
4722 startContainer.parentNode.insertBefore(n, nn);
\r
4725 // Insert element node
\r
4726 if (startContainer.childNodes.length > 0)
\r
4727 o = startContainer.childNodes[startOffset];
\r
4730 startContainer.insertBefore(n, o);
\r
4732 startContainer.appendChild(n);
\r
4736 function surroundContents(n) {
\r
4737 var f = t.extractContents();
\r
4744 function cloneRange() {
\r
4745 return extend(new Range(dom), {
\r
4746 startContainer : t[START_CONTAINER],
\r
4747 startOffset : t[START_OFFSET],
\r
4748 endContainer : t[END_CONTAINER],
\r
4749 endOffset : t[END_OFFSET],
\r
4750 collapsed : t.collapsed,
\r
4751 commonAncestorContainer : t.commonAncestorContainer
\r
4755 // Private methods
\r
4757 function _getSelectedNode(container, offset) {
\r
4760 if (container.nodeType == 3 /* TEXT_NODE */)
\r
4766 child = container.firstChild;
\r
4767 while (child && offset > 0) {
\r
4769 child = child.nextSibling;
\r
4778 function _isCollapsed() {
\r
4779 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
\r
4782 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
\r
4783 var c, offsetC, n, cmnRoot, childA, childB;
\r
4785 // In the first case the boundary-points have the same container. A is before B
\r
4786 // if its offset is less than the offset of B, A is equal to B if its offset is
\r
4787 // equal to the offset of B, and A is after B if its offset is greater than the
\r
4789 if (containerA == containerB) {
\r
4790 if (offsetA == offsetB)
\r
4791 return 0; // equal
\r
4793 if (offsetA < offsetB)
\r
4794 return -1; // before
\r
4796 return 1; // after
\r
4799 // In the second case a child node C of the container of A is an ancestor
\r
4800 // container of B. In this case, A is before B if the offset of A is less than or
\r
4801 // equal to the index of the child node C and A is after B otherwise.
\r
4803 while (c && c.parentNode != containerA)
\r
4808 n = containerA.firstChild;
\r
4810 while (n != c && offsetC < offsetA) {
\r
4812 n = n.nextSibling;
\r
4815 if (offsetA <= offsetC)
\r
4816 return -1; // before
\r
4818 return 1; // after
\r
4821 // In the third case a child node C of the container of B is an ancestor container
\r
4822 // of A. In this case, A is before B if the index of the child node C is less than
\r
4823 // the offset of B and A is after B otherwise.
\r
4825 while (c && c.parentNode != containerB) {
\r
4831 n = containerB.firstChild;
\r
4833 while (n != c && offsetC < offsetB) {
\r
4835 n = n.nextSibling;
\r
4838 if (offsetC < offsetB)
\r
4839 return -1; // before
\r
4841 return 1; // after
\r
4844 // In the fourth case, none of three other cases hold: the containers of A and B
\r
4845 // are siblings or descendants of sibling nodes. In this case, A is before B if
\r
4846 // the container of A is before the container of B in a pre-order traversal of the
\r
4847 // Ranges' context tree and A is after B otherwise.
\r
4848 cmnRoot = dom.findCommonAncestor(containerA, containerB);
\r
4849 childA = containerA;
\r
4851 while (childA && childA.parentNode != cmnRoot)
\r
4852 childA = childA.parentNode;
\r
4857 childB = containerB;
\r
4858 while (childB && childB.parentNode != cmnRoot)
\r
4859 childB = childB.parentNode;
\r
4864 if (childA == childB)
\r
4865 return 0; // equal
\r
4867 n = cmnRoot.firstChild;
\r
4870 return -1; // before
\r
4873 return 1; // after
\r
4875 n = n.nextSibling;
\r
4879 function _setEndPoint(st, n, o) {
\r
4883 t[START_CONTAINER] = n;
\r
4884 t[START_OFFSET] = o;
\r
4886 t[END_CONTAINER] = n;
\r
4887 t[END_OFFSET] = o;
\r
4890 // If one boundary-point of a Range is set to have a root container
\r
4891 // other than the current one for the Range, the Range is collapsed to
\r
4892 // the new position. This enforces the restriction that both boundary-
\r
4893 // points of a Range must have the same root container.
\r
4894 ec = t[END_CONTAINER];
\r
4895 while (ec.parentNode)
\r
4896 ec = ec.parentNode;
\r
4898 sc = t[START_CONTAINER];
\r
4899 while (sc.parentNode)
\r
4900 sc = sc.parentNode;
\r
4903 // The start position of a Range is guaranteed to never be after the
\r
4904 // end position. To enforce this restriction, if the start is set to
\r
4905 // be at a position after the end, the Range is collapsed to that
\r
4907 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
\r
4912 t.collapsed = _isCollapsed();
\r
4913 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
\r
4916 function _traverse(how) {
\r
4917 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
\r
4919 if (t[START_CONTAINER] == t[END_CONTAINER])
\r
4920 return _traverseSameContainer(how);
\r
4922 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
4923 if (p == t[START_CONTAINER])
\r
4924 return _traverseCommonStartContainer(c, how);
\r
4926 ++endContainerDepth;
\r
4929 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
4930 if (p == t[END_CONTAINER])
\r
4931 return _traverseCommonEndContainer(c, how);
\r
4933 ++startContainerDepth;
\r
4936 depthDiff = startContainerDepth - endContainerDepth;
\r
4938 startNode = t[START_CONTAINER];
\r
4939 while (depthDiff > 0) {
\r
4940 startNode = startNode.parentNode;
\r
4944 endNode = t[END_CONTAINER];
\r
4945 while (depthDiff < 0) {
\r
4946 endNode = endNode.parentNode;
\r
4950 // ascend the ancestor hierarchy until we have a common parent.
\r
4951 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
\r
4956 return _traverseCommonAncestors(startNode, endNode, how);
\r
4959 function _traverseSameContainer(how) {
\r
4960 var frag, s, sub, n, cnt, sibling, xferNode;
\r
4962 if (how != DELETE)
\r
4963 frag = doc.createDocumentFragment();
\r
4965 // If selection is empty, just return the fragment
\r
4966 if (t[START_OFFSET] == t[END_OFFSET])
\r
4969 // Text node needs special case handling
\r
4970 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
\r
4971 // get the substring
\r
4972 s = t[START_CONTAINER].nodeValue;
\r
4973 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
\r
4975 // set the original text node to its new value
\r
4976 if (how != CLONE) {
\r
4977 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
\r
4979 // Nothing is partially selected, so collapse to start point
\r
4983 if (how == DELETE)
\r
4986 frag.appendChild(doc.createTextNode(sub));
\r
4990 // Copy nodes between the start/end offsets.
\r
4991 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
\r
4992 cnt = t[END_OFFSET] - t[START_OFFSET];
\r
4995 sibling = n.nextSibling;
\r
4996 xferNode = _traverseFullySelected(n, how);
\r
4999 frag.appendChild( xferNode );
\r
5005 // Nothing is partially selected, so collapse to start point
\r
5012 function _traverseCommonStartContainer(endAncestor, how) {
\r
5013 var frag, n, endIdx, cnt, sibling, xferNode;
\r
5015 if (how != DELETE)
\r
5016 frag = doc.createDocumentFragment();
\r
5018 n = _traverseRightBoundary(endAncestor, how);
\r
5021 frag.appendChild(n);
\r
5023 endIdx = nodeIndex(endAncestor);
\r
5024 cnt = endIdx - t[START_OFFSET];
\r
5027 // Collapse to just before the endAncestor, which
\r
5028 // is partially selected.
\r
5029 if (how != CLONE) {
\r
5030 t.setEndBefore(endAncestor);
\r
5031 t.collapse(FALSE);
\r
5037 n = endAncestor.previousSibling;
\r
5039 sibling = n.previousSibling;
\r
5040 xferNode = _traverseFullySelected(n, how);
\r
5043 frag.insertBefore(xferNode, frag.firstChild);
\r
5049 // Collapse to just before the endAncestor, which
\r
5050 // is partially selected.
\r
5051 if (how != CLONE) {
\r
5052 t.setEndBefore(endAncestor);
\r
5053 t.collapse(FALSE);
\r
5059 function _traverseCommonEndContainer(startAncestor, how) {
\r
5060 var frag, startIdx, n, cnt, sibling, xferNode;
\r
5062 if (how != DELETE)
\r
5063 frag = doc.createDocumentFragment();
\r
5065 n = _traverseLeftBoundary(startAncestor, how);
\r
5067 frag.appendChild(n);
\r
5069 startIdx = nodeIndex(startAncestor);
\r
5070 ++startIdx; // Because we already traversed it
\r
5072 cnt = t[END_OFFSET] - startIdx;
\r
5073 n = startAncestor.nextSibling;
\r
5075 sibling = n.nextSibling;
\r
5076 xferNode = _traverseFullySelected(n, how);
\r
5079 frag.appendChild(xferNode);
\r
5085 if (how != CLONE) {
\r
5086 t.setStartAfter(startAncestor);
\r
5093 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
\r
5094 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
\r
5096 if (how != DELETE)
\r
5097 frag = doc.createDocumentFragment();
\r
5099 n = _traverseLeftBoundary(startAncestor, how);
\r
5101 frag.appendChild(n);
\r
5103 commonParent = startAncestor.parentNode;
\r
5104 startOffset = nodeIndex(startAncestor);
\r
5105 endOffset = nodeIndex(endAncestor);
\r
5108 cnt = endOffset - startOffset;
\r
5109 sibling = startAncestor.nextSibling;
\r
5112 nextSibling = sibling.nextSibling;
\r
5113 n = _traverseFullySelected(sibling, how);
\r
5116 frag.appendChild(n);
\r
5118 sibling = nextSibling;
\r
5122 n = _traverseRightBoundary(endAncestor, how);
\r
5125 frag.appendChild(n);
\r
5127 if (how != CLONE) {
\r
5128 t.setStartAfter(startAncestor);
\r
5135 function _traverseRightBoundary(root, how) {
\r
5136 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
\r
5139 return _traverseNode(next, isFullySelected, FALSE, how);
\r
5141 parent = next.parentNode;
\r
5142 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
\r
5146 prevSibling = next.previousSibling;
\r
5147 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
\r
5149 if (how != DELETE)
\r
5150 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
\r
5152 isFullySelected = TRUE;
\r
5153 next = prevSibling;
\r
5156 if (parent == root)
\r
5157 return clonedParent;
\r
5159 next = parent.previousSibling;
\r
5160 parent = parent.parentNode;
\r
5162 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
\r
5164 if (how != DELETE)
\r
5165 clonedGrandParent.appendChild(clonedParent);
\r
5167 clonedParent = clonedGrandParent;
\r
5171 function _traverseLeftBoundary(root, how) {
\r
5172 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
\r
5175 return _traverseNode(next, isFullySelected, TRUE, how);
\r
5177 parent = next.parentNode;
\r
5178 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
\r
5182 nextSibling = next.nextSibling;
\r
5183 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
\r
5185 if (how != DELETE)
\r
5186 clonedParent.appendChild(clonedChild);
\r
5188 isFullySelected = TRUE;
\r
5189 next = nextSibling;
\r
5192 if (parent == root)
\r
5193 return clonedParent;
\r
5195 next = parent.nextSibling;
\r
5196 parent = parent.parentNode;
\r
5198 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
\r
5200 if (how != DELETE)
\r
5201 clonedGrandParent.appendChild(clonedParent);
\r
5203 clonedParent = clonedGrandParent;
\r
5207 function _traverseNode(n, isFullySelected, isLeft, how) {
\r
5208 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
\r
5210 if (isFullySelected)
\r
5211 return _traverseFullySelected(n, how);
\r
5213 if (n.nodeType == 3 /* TEXT_NODE */) {
\r
5214 txtValue = n.nodeValue;
\r
5217 offset = t[START_OFFSET];
\r
5218 newNodeValue = txtValue.substring(offset);
\r
5219 oldNodeValue = txtValue.substring(0, offset);
\r
5221 offset = t[END_OFFSET];
\r
5222 newNodeValue = txtValue.substring(0, offset);
\r
5223 oldNodeValue = txtValue.substring(offset);
\r
5227 n.nodeValue = oldNodeValue;
\r
5229 if (how == DELETE)
\r
5232 newNode = n.cloneNode(FALSE);
\r
5233 newNode.nodeValue = newNodeValue;
\r
5238 if (how == DELETE)
\r
5241 return n.cloneNode(FALSE);
\r
5244 function _traverseFullySelected(n, how) {
\r
5245 if (how != DELETE)
\r
5246 return how == CLONE ? n.cloneNode(TRUE) : n;
\r
5248 n.parentNode.removeChild(n);
\r
5256 function Selection(selection) {
\r
5257 var self = this, dom = selection.dom, TRUE = true, FALSE = false;
\r
5259 function getPosition(rng, start) {
\r
5260 var checkRng, startIndex = 0, endIndex, inside,
\r
5261 children, child, offset, index, position = -1, parent;
\r
5263 // Setup test range, collapse it and get the parent
\r
5264 checkRng = rng.duplicate();
\r
5265 checkRng.collapse(start);
\r
5266 parent = checkRng.parentElement();
\r
5268 // Check if the selection is within the right document
\r
5269 if (parent.ownerDocument !== selection.dom.doc)
\r
5272 // IE will report non editable elements as it's parent so look for an editable one
\r
5273 while (parent.contentEditable === "false") {
\r
5274 parent = parent.parentNode;
\r
5277 // If parent doesn't have any children then return that we are inside the element
\r
5278 if (!parent.hasChildNodes()) {
\r
5279 return {node : parent, inside : 1};
\r
5282 // Setup node list and endIndex
\r
5283 children = parent.children;
\r
5284 endIndex = children.length - 1;
\r
5286 // Perform a binary search for the position
\r
5287 while (startIndex <= endIndex) {
\r
5288 index = Math.floor((startIndex + endIndex) / 2);
\r
5290 // Move selection to node and compare the ranges
\r
5291 child = children[index];
\r
5292 checkRng.moveToElementText(child);
\r
5293 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
\r
5295 // Before/after or an exact match
\r
5296 if (position > 0) {
\r
5297 endIndex = index - 1;
\r
5298 } else if (position < 0) {
\r
5299 startIndex = index + 1;
\r
5301 return {node : child};
\r
5305 // Check if child position is before or we didn't find a position
\r
5306 if (position < 0) {
\r
5307 // No element child was found use the parent element and the offset inside that
\r
5309 checkRng.moveToElementText(parent);
\r
5310 checkRng.collapse(true);
\r
5314 checkRng.collapse(false);
\r
5316 checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng);
\r
5318 // Fix for edge case: <div style="width: 100px; height:100px;"><table>..</table>ab|c</div>
\r
5319 if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) {
\r
5320 checkRng = rng.duplicate();
\r
5321 checkRng.collapse(start);
\r
5324 while (parent == checkRng.parentElement()) {
\r
5325 if (checkRng.move('character', -1) == 0)
\r
5332 offset = offset || checkRng.text.replace('\r\n', ' ').length;
\r
5334 // Child position is after the selection endpoint
\r
5335 checkRng.collapse(true);
\r
5336 checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng);
\r
5338 // Get the length of the text to find where the endpoint is relative to it's container
\r
5339 offset = checkRng.text.replace('\r\n', ' ').length;
\r
5342 return {node : child, position : position, offset : offset, inside : inside};
\r
5345 // Returns a W3C DOM compatible range object by using the IE Range API
\r
5346 function getRange() {
\r
5347 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
\r
5349 // If selection is outside the current document just return an empty range
\r
5350 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
\r
5351 if (element.ownerDocument != dom.doc)
\r
5354 collapsed = selection.isCollapsed();
\r
5356 // Handle control selection
\r
5357 if (ieRange.item) {
\r
5358 domRange.setStart(element.parentNode, dom.nodeIndex(element));
\r
5359 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
\r
5364 function findEndPoint(start) {
\r
5365 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
\r
5367 container = endPoint.node;
\r
5368 offset = endPoint.offset;
\r
5370 if (endPoint.inside && !container.hasChildNodes()) {
\r
5371 domRange[start ? 'setStart' : 'setEnd'](container, 0);
\r
5375 if (offset === undef) {
\r
5376 domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
\r
5380 if (endPoint.position < 0) {
\r
5381 sibling = endPoint.inside ? container.firstChild : container.nextSibling;
\r
5384 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
\r
5389 if (sibling.nodeType == 3)
\r
5390 domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
\r
5392 domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
\r
5397 // Find the text node and offset
\r
5399 nodeValue = sibling.nodeValue;
\r
5400 textNodeOffset += nodeValue.length;
\r
5402 // We are at or passed the position we where looking for
\r
5403 if (textNodeOffset >= offset) {
\r
5404 container = sibling;
\r
5405 textNodeOffset -= offset;
\r
5406 textNodeOffset = nodeValue.length - textNodeOffset;
\r
5410 sibling = sibling.nextSibling;
\r
5413 // Find the text node and offset
\r
5414 sibling = container.previousSibling;
\r
5417 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
\r
5419 // If there isn't any text to loop then use the first position
\r
5421 if (container.nodeType == 3)
\r
5422 domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
\r
5424 domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
\r
5430 textNodeOffset += sibling.nodeValue.length;
\r
5432 // We are at or passed the position we where looking for
\r
5433 if (textNodeOffset >= offset) {
\r
5434 container = sibling;
\r
5435 textNodeOffset -= offset;
\r
5439 sibling = sibling.previousSibling;
\r
5443 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
\r
5447 // Find start point
\r
5448 findEndPoint(true);
\r
5450 // Find end point if needed
\r
5454 // IE has a nasty bug where text nodes might throw "invalid argument" when you
\r
5455 // access the nodeValue or other properties of text nodes. This seems to happend when
\r
5456 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
\r
5457 if (ex.number == -2147024809) {
\r
5458 // Get the current selection
\r
5459 bookmark = self.getBookmark(2);
\r
5461 // Get start element
\r
5462 tmpRange = ieRange.duplicate();
\r
5463 tmpRange.collapse(true);
\r
5464 element = tmpRange.parentElement();
\r
5466 // Get end element
\r
5468 tmpRange = ieRange.duplicate();
\r
5469 tmpRange.collapse(false);
\r
5470 element2 = tmpRange.parentElement();
\r
5471 element2.innerHTML = element2.innerHTML;
\r
5474 // Remove the broken elements
\r
5475 element.innerHTML = element.innerHTML;
\r
5477 // Restore the selection
\r
5478 self.moveToBookmark(bookmark);
\r
5480 // Since the range has moved we need to re-get it
\r
5481 ieRange = selection.getRng();
\r
5483 // Find start point
\r
5484 findEndPoint(true);
\r
5486 // Find end point if needed
\r
5490 throw ex; // Throw other errors
\r
5496 this.getBookmark = function(type) {
\r
5497 var rng = selection.getRng(), start, end, bookmark = {};
\r
5499 function getIndexes(node) {
\r
5500 var node, parent, root, children, i, indexes = [];
\r
5502 parent = node.parentNode;
\r
5503 root = dom.getRoot().parentNode;
\r
5505 while (parent != root) {
\r
5506 children = parent.children;
\r
5508 i = children.length;
\r
5510 if (node === children[i]) {
\r
5517 parent = parent.parentNode;
\r
5523 function getBookmarkEndPoint(start) {
\r
5526 position = getPosition(rng, start);
\r
5529 position : position.position,
\r
5530 offset : position.offset,
\r
5531 indexes : getIndexes(position.node),
\r
5532 inside : position.inside
\r
5537 // Non ubstructive bookmark
\r
5539 // Handle text selection
\r
5541 bookmark.start = getBookmarkEndPoint(true);
\r
5543 if (!selection.isCollapsed())
\r
5544 bookmark.end = getBookmarkEndPoint();
\r
5546 bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
\r
5552 this.moveToBookmark = function(bookmark) {
\r
5553 var rng, body = dom.doc.body;
\r
5555 function resolveIndexes(indexes) {
\r
5556 var node, i, idx, children;
\r
5558 node = dom.getRoot();
\r
5559 for (i = indexes.length - 1; i >= 0; i--) {
\r
5560 children = node.children;
\r
5563 if (idx <= children.length - 1) {
\r
5564 node = children[idx];
\r
5571 function setBookmarkEndPoint(start) {
\r
5572 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
\r
5575 moveLeft = endPoint.position > 0;
\r
5577 moveRng = body.createTextRange();
\r
5578 moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
\r
5580 offset = endPoint.offset;
\r
5581 if (offset !== undef) {
\r
5582 moveRng.collapse(endPoint.inside || moveLeft);
\r
5583 moveRng.moveStart('character', moveLeft ? -offset : offset);
\r
5585 moveRng.collapse(start);
\r
5587 rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
\r
5590 rng.collapse(true);
\r
5594 if (bookmark.start) {
\r
5595 if (bookmark.start.ctrl) {
\r
5596 rng = body.createControlRange();
\r
5597 rng.addElement(resolveIndexes(bookmark.start.indexes));
\r
5600 rng = body.createTextRange();
\r
5601 setBookmarkEndPoint(true);
\r
5602 setBookmarkEndPoint();
\r
5608 this.addRange = function(rng) {
\r
5609 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
\r
5611 function setEndPoint(start) {
\r
5612 var container, offset, marker, tmpRng, nodes;
\r
5614 marker = dom.create('a');
\r
5615 container = start ? startContainer : endContainer;
\r
5616 offset = start ? startOffset : endOffset;
\r
5617 tmpRng = ieRng.duplicate();
\r
5619 if (container == doc || container == doc.documentElement) {
\r
5624 if (container.nodeType == 3) {
\r
5625 container.parentNode.insertBefore(marker, container);
\r
5626 tmpRng.moveToElementText(marker);
\r
5627 tmpRng.moveStart('character', offset);
\r
5628 dom.remove(marker);
\r
5629 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
\r
5631 nodes = container.childNodes;
\r
5633 if (nodes.length) {
\r
5634 if (offset >= nodes.length) {
\r
5635 dom.insertAfter(marker, nodes[nodes.length - 1]);
\r
5637 container.insertBefore(marker, nodes[offset]);
\r
5640 tmpRng.moveToElementText(marker);
\r
5642 // Empty node selection for example <div>|</div>
\r
5643 marker = doc.createTextNode('\uFEFF');
\r
5644 container.appendChild(marker);
\r
5645 tmpRng.moveToElementText(marker.parentNode);
\r
5646 tmpRng.collapse(TRUE);
\r
5649 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
\r
5650 dom.remove(marker);
\r
5654 // Setup some shorter versions
\r
5655 startContainer = rng.startContainer;
\r
5656 startOffset = rng.startOffset;
\r
5657 endContainer = rng.endContainer;
\r
5658 endOffset = rng.endOffset;
\r
5659 ieRng = body.createTextRange();
\r
5661 // If single element selection then try making a control selection out of it
\r
5662 if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
\r
5663 if (startOffset == endOffset - 1) {
\r
5665 ctrlRng = body.createControlRange();
\r
5666 ctrlRng.addElement(startContainer.childNodes[startOffset]);
\r
5675 // Set start/end point of selection
\r
5676 setEndPoint(true);
\r
5679 // Select the new range and scroll it into view
\r
5683 // Expose range method
\r
5684 this.getRangeAt = getRange;
\r
5687 // Expose the selection object
\r
5688 tinymce.dom.TridentSelection = Selection;
\r
5693 * Sizzle CSS Selector Engine - v1.0
\r
5694 * Copyright 2009, The Dojo Foundation
\r
5695 * Released under the MIT, BSD, and GPL Licenses.
\r
5696 * More information: http://sizzlejs.com/
\r
5700 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
\r
5702 toString = Object.prototype.toString,
\r
5703 hasDuplicate = false,
\r
5704 baseHasDuplicate = true;
\r
5706 // Here we check if the JavaScript engine is using some sort of
\r
5707 // optimization where it does not always call our comparision
\r
5708 // function. If that is the case, discard the hasDuplicate value.
\r
5709 // Thus far that includes Google Chrome.
\r
5710 [0, 0].sort(function(){
\r
5711 baseHasDuplicate = false;
\r
5715 var Sizzle = function(selector, context, results, seed) {
\r
5716 results = results || [];
\r
5717 context = context || document;
\r
5719 var origContext = context;
\r
5721 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
\r
5725 if ( !selector || typeof selector !== "string" ) {
\r
5729 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
\r
5730 soFar = selector, ret, cur, pop, i;
\r
5732 // Reset the position of the chunker regexp (start from head)
\r
5735 m = chunker.exec(soFar);
\r
5740 parts.push( m[1] );
\r
5749 if ( parts.length > 1 && origPOS.exec( selector ) ) {
\r
5750 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
\r
5751 set = posProcess( parts[0] + parts[1], context );
\r
5753 set = Expr.relative[ parts[0] ] ?
\r
5755 Sizzle( parts.shift(), context );
\r
5757 while ( parts.length ) {
\r
5758 selector = parts.shift();
\r
5760 if ( Expr.relative[ selector ] ) {
\r
5761 selector += parts.shift();
\r
5764 set = posProcess( selector, set );
\r
5768 // Take a shortcut and set the context if the root selector is an ID
\r
5769 // (but not if it'll be faster if the inner selector is an ID)
\r
5770 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
\r
5771 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
\r
5772 ret = Sizzle.find( parts.shift(), context, contextXML );
\r
5773 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
\r
5778 { expr: parts.pop(), set: makeArray(seed) } :
\r
5779 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
\r
5780 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
\r
5782 if ( parts.length > 0 ) {
\r
5783 checkSet = makeArray(set);
\r
5788 while ( parts.length ) {
\r
5789 cur = parts.pop();
\r
5792 if ( !Expr.relative[ cur ] ) {
\r
5795 pop = parts.pop();
\r
5798 if ( pop == null ) {
\r
5802 Expr.relative[ cur ]( checkSet, pop, contextXML );
\r
5805 checkSet = parts = [];
\r
5809 if ( !checkSet ) {
\r
5813 if ( !checkSet ) {
\r
5814 Sizzle.error( cur || selector );
\r
5817 if ( toString.call(checkSet) === "[object Array]" ) {
\r
5819 results.push.apply( results, checkSet );
\r
5820 } else if ( context && context.nodeType === 1 ) {
\r
5821 for ( i = 0; checkSet[i] != null; i++ ) {
\r
5822 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
\r
5823 results.push( set[i] );
\r
5827 for ( i = 0; checkSet[i] != null; i++ ) {
\r
5828 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
\r
5829 results.push( set[i] );
\r
5834 makeArray( checkSet, results );
\r
5838 Sizzle( extra, origContext, results, seed );
\r
5839 Sizzle.uniqueSort( results );
\r
5845 Sizzle.uniqueSort = function(results){
\r
5846 if ( sortOrder ) {
\r
5847 hasDuplicate = baseHasDuplicate;
\r
5848 results.sort(sortOrder);
\r
5850 if ( hasDuplicate ) {
\r
5851 for ( var i = 1; i < results.length; i++ ) {
\r
5852 if ( results[i] === results[i-1] ) {
\r
5853 results.splice(i--, 1);
\r
5862 Sizzle.matches = function(expr, set){
\r
5863 return Sizzle(expr, null, null, set);
\r
5866 Sizzle.find = function(expr, context, isXML){
\r
5873 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
\r
5874 var type = Expr.order[i], match;
\r
5876 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
\r
5877 var left = match[1];
\r
5878 match.splice(1,1);
\r
5880 if ( left.substr( left.length - 1 ) !== "\\" ) {
\r
5881 match[1] = (match[1] || "").replace(/\\/g, "");
\r
5882 set = Expr.find[ type ]( match, context, isXML );
\r
5883 if ( set != null ) {
\r
5884 expr = expr.replace( Expr.match[ type ], "" );
\r
5892 set = context.getElementsByTagName("*");
\r
5895 return {set: set, expr: expr};
\r
5898 Sizzle.filter = function(expr, set, inplace, not){
\r
5899 var old = expr, result = [], curLoop = set, match, anyFound,
\r
5900 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
\r
5902 while ( expr && set.length ) {
\r
5903 for ( var type in Expr.filter ) {
\r
5904 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
\r
5905 var filter = Expr.filter[ type ], found, item, left = match[1];
\r
5908 match.splice(1,1);
\r
5910 if ( left.substr( left.length - 1 ) === "\\" ) {
\r
5914 if ( curLoop === result ) {
\r
5918 if ( Expr.preFilter[ type ] ) {
\r
5919 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
\r
5922 anyFound = found = true;
\r
5923 } else if ( match === true ) {
\r
5929 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
\r
5931 found = filter( item, match, i, curLoop );
\r
5932 var pass = not ^ !!found;
\r
5934 if ( inplace && found != null ) {
\r
5938 curLoop[i] = false;
\r
5940 } else if ( pass ) {
\r
5941 result.push( item );
\r
5948 if ( found !== undefined ) {
\r
5953 expr = expr.replace( Expr.match[ type ], "" );
\r
5955 if ( !anyFound ) {
\r
5964 // Improper expression
\r
5965 if ( expr === old ) {
\r
5966 if ( anyFound == null ) {
\r
5967 Sizzle.error( expr );
\r
5979 Sizzle.error = function( msg ) {
\r
5980 throw "Syntax error, unrecognized expression: " + msg;
\r
5983 var Expr = Sizzle.selectors = {
\r
5984 order: [ "ID", "NAME", "TAG" ],
\r
5986 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
\r
5987 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
\r
5988 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
\r
5989 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
\r
5990 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
\r
5991 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
\r
5992 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
\r
5993 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
\r
5997 "class": "className",
\r
6001 href: function(elem){
\r
6002 return elem.getAttribute("href");
\r
6006 "+": function(checkSet, part){
\r
6007 var isPartStr = typeof part === "string",
\r
6008 isTag = isPartStr && !/\W/.test(part),
\r
6009 isPartStrNotTag = isPartStr && !isTag;
\r
6012 part = part.toLowerCase();
\r
6015 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
\r
6016 if ( (elem = checkSet[i]) ) {
\r
6017 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
\r
6019 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
\r
6025 if ( isPartStrNotTag ) {
\r
6026 Sizzle.filter( part, checkSet, true );
\r
6029 ">": function(checkSet, part){
\r
6030 var isPartStr = typeof part === "string",
\r
6031 elem, i = 0, l = checkSet.length;
\r
6033 if ( isPartStr && !/\W/.test(part) ) {
\r
6034 part = part.toLowerCase();
\r
6036 for ( ; i < l; i++ ) {
\r
6037 elem = checkSet[i];
\r
6039 var parent = elem.parentNode;
\r
6040 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
\r
6044 for ( ; i < l; i++ ) {
\r
6045 elem = checkSet[i];
\r
6047 checkSet[i] = isPartStr ?
\r
6049 elem.parentNode === part;
\r
6053 if ( isPartStr ) {
\r
6054 Sizzle.filter( part, checkSet, true );
\r
6058 "": function(checkSet, part, isXML){
\r
6059 var doneName = done++, checkFn = dirCheck, nodeCheck;
\r
6061 if ( typeof part === "string" && !/\W/.test(part) ) {
\r
6062 part = part.toLowerCase();
\r
6064 checkFn = dirNodeCheck;
\r
6067 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
\r
6069 "~": function(checkSet, part, isXML){
\r
6070 var doneName = done++, checkFn = dirCheck, nodeCheck;
\r
6072 if ( typeof part === "string" && !/\W/.test(part) ) {
\r
6073 part = part.toLowerCase();
\r
6075 checkFn = dirNodeCheck;
\r
6078 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
\r
6082 ID: function(match, context, isXML){
\r
6083 if ( typeof context.getElementById !== "undefined" && !isXML ) {
\r
6084 var m = context.getElementById(match[1]);
\r
6085 return m ? [m] : [];
\r
6088 NAME: function(match, context){
\r
6089 if ( typeof context.getElementsByName !== "undefined" ) {
\r
6090 var ret = [], results = context.getElementsByName(match[1]);
\r
6092 for ( var i = 0, l = results.length; i < l; i++ ) {
\r
6093 if ( results[i].getAttribute("name") === match[1] ) {
\r
6094 ret.push( results[i] );
\r
6098 return ret.length === 0 ? null : ret;
\r
6101 TAG: function(match, context){
\r
6102 return context.getElementsByTagName(match[1]);
\r
6106 CLASS: function(match, curLoop, inplace, result, not, isXML){
\r
6107 match = " " + match[1].replace(/\\/g, "") + " ";
\r
6113 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
\r
6115 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
\r
6117 result.push( elem );
\r
6119 } else if ( inplace ) {
\r
6120 curLoop[i] = false;
\r
6127 ID: function(match){
\r
6128 return match[1].replace(/\\/g, "");
\r
6130 TAG: function(match, curLoop){
\r
6131 return match[1].toLowerCase();
\r
6133 CHILD: function(match){
\r
6134 if ( match[1] === "nth" ) {
\r
6135 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
\r
6136 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
\r
6137 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
\r
6138 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
\r
6140 // calculate the numbers (first)n+(last) including if they are negative
\r
6141 match[2] = (test[1] + (test[2] || 1)) - 0;
\r
6142 match[3] = test[3] - 0;
\r
6145 // TODO: Move to normal caching system
\r
6146 match[0] = done++;
\r
6150 ATTR: function(match, curLoop, inplace, result, not, isXML){
\r
6151 var name = match[1].replace(/\\/g, "");
\r
6153 if ( !isXML && Expr.attrMap[name] ) {
\r
6154 match[1] = Expr.attrMap[name];
\r
6157 if ( match[2] === "~=" ) {
\r
6158 match[4] = " " + match[4] + " ";
\r
6163 PSEUDO: function(match, curLoop, inplace, result, not){
\r
6164 if ( match[1] === "not" ) {
\r
6165 // If we're dealing with a complex expression, or a simple one
\r
6166 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
\r
6167 match[3] = Sizzle(match[3], null, null, curLoop);
\r
6169 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
\r
6171 result.push.apply( result, ret );
\r
6175 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
\r
6181 POS: function(match){
\r
6182 match.unshift( true );
\r
6187 enabled: function(elem){
\r
6188 return elem.disabled === false && elem.type !== "hidden";
\r
6190 disabled: function(elem){
\r
6191 return elem.disabled === true;
\r
6193 checked: function(elem){
\r
6194 return elem.checked === true;
\r
6196 selected: function(elem){
\r
6197 // Accessing this property makes selected-by-default
\r
6198 // options in Safari work properly
\r
6199 elem.parentNode.selectedIndex;
\r
6200 return elem.selected === true;
\r
6202 parent: function(elem){
\r
6203 return !!elem.firstChild;
\r
6205 empty: function(elem){
\r
6206 return !elem.firstChild;
\r
6208 has: function(elem, i, match){
\r
6209 return !!Sizzle( match[3], elem ).length;
\r
6211 header: function(elem){
\r
6212 return (/h\d/i).test( elem.nodeName );
\r
6214 text: function(elem){
\r
6215 return "text" === elem.type;
\r
6217 radio: function(elem){
\r
6218 return "radio" === elem.type;
\r
6220 checkbox: function(elem){
\r
6221 return "checkbox" === elem.type;
\r
6223 file: function(elem){
\r
6224 return "file" === elem.type;
\r
6226 password: function(elem){
\r
6227 return "password" === elem.type;
\r
6229 submit: function(elem){
\r
6230 return "submit" === elem.type;
\r
6232 image: function(elem){
\r
6233 return "image" === elem.type;
\r
6235 reset: function(elem){
\r
6236 return "reset" === elem.type;
\r
6238 button: function(elem){
\r
6239 return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
\r
6241 input: function(elem){
\r
6242 return (/input|select|textarea|button/i).test(elem.nodeName);
\r
6246 first: function(elem, i){
\r
6249 last: function(elem, i, match, array){
\r
6250 return i === array.length - 1;
\r
6252 even: function(elem, i){
\r
6253 return i % 2 === 0;
\r
6255 odd: function(elem, i){
\r
6256 return i % 2 === 1;
\r
6258 lt: function(elem, i, match){
\r
6259 return i < match[3] - 0;
\r
6261 gt: function(elem, i, match){
\r
6262 return i > match[3] - 0;
\r
6264 nth: function(elem, i, match){
\r
6265 return match[3] - 0 === i;
\r
6267 eq: function(elem, i, match){
\r
6268 return match[3] - 0 === i;
\r
6272 PSEUDO: function(elem, match, i, array){
\r
6273 var name = match[1], filter = Expr.filters[ name ];
\r
6276 return filter( elem, i, match, array );
\r
6277 } else if ( name === "contains" ) {
\r
6278 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
\r
6279 } else if ( name === "not" ) {
\r
6280 var not = match[3];
\r
6282 for ( var j = 0, l = not.length; j < l; j++ ) {
\r
6283 if ( not[j] === elem ) {
\r
6290 Sizzle.error( "Syntax error, unrecognized expression: " + name );
\r
6293 CHILD: function(elem, match){
\r
6294 var type = match[1], node = elem;
\r
6298 while ( (node = node.previousSibling) ) {
\r
6299 if ( node.nodeType === 1 ) {
\r
6303 if ( type === "first" ) {
\r
6308 while ( (node = node.nextSibling) ) {
\r
6309 if ( node.nodeType === 1 ) {
\r
6315 var first = match[2], last = match[3];
\r
6317 if ( first === 1 && last === 0 ) {
\r
6321 var doneName = match[0],
\r
6322 parent = elem.parentNode;
\r
6324 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
\r
6326 for ( node = parent.firstChild; node; node = node.nextSibling ) {
\r
6327 if ( node.nodeType === 1 ) {
\r
6328 node.nodeIndex = ++count;
\r
6331 parent.sizcache = doneName;
\r
6334 var diff = elem.nodeIndex - last;
\r
6335 if ( first === 0 ) {
\r
6336 return diff === 0;
\r
6338 return ( diff % first === 0 && diff / first >= 0 );
\r
6342 ID: function(elem, match){
\r
6343 return elem.nodeType === 1 && elem.getAttribute("id") === match;
\r
6345 TAG: function(elem, match){
\r
6346 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
\r
6348 CLASS: function(elem, match){
\r
6349 return (" " + (elem.className || elem.getAttribute("class")) + " ")
\r
6350 .indexOf( match ) > -1;
\r
6352 ATTR: function(elem, match){
\r
6353 var name = match[1],
\r
6354 result = Expr.attrHandle[ name ] ?
\r
6355 Expr.attrHandle[ name ]( elem ) :
\r
6356 elem[ name ] != null ?
\r
6358 elem.getAttribute( name ),
\r
6359 value = result + "",
\r
6363 return result == null ?
\r
6368 value.indexOf(check) >= 0 :
\r
6370 (" " + value + " ").indexOf(check) >= 0 :
\r
6372 value && result !== false :
\r
6376 value.indexOf(check) === 0 :
\r
6378 value.substr(value.length - check.length) === check :
\r
6380 value === check || value.substr(0, check.length + 1) === check + "-" :
\r
6383 POS: function(elem, match, i, array){
\r
6384 var name = match[2], filter = Expr.setFilters[ name ];
\r
6387 return filter( elem, i, match, array );
\r
6393 var origPOS = Expr.match.POS,
\r
6394 fescape = function(all, num){
\r
6395 return "\\" + (num - 0 + 1);
\r
6398 for ( var type in Expr.match ) {
\r
6399 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
\r
6400 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
\r
6403 var makeArray = function(array, results) {
\r
6404 array = Array.prototype.slice.call( array, 0 );
\r
6407 results.push.apply( results, array );
\r
6414 // Perform a simple check to determine if the browser is capable of
\r
6415 // converting a NodeList to an array using builtin methods.
\r
6416 // Also verifies that the returned array holds DOM nodes
\r
6417 // (which is not the case in the Blackberry browser)
\r
6419 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
\r
6421 // Provide a fallback method if it does not work
\r
6423 makeArray = function(array, results) {
\r
6424 var ret = results || [], i = 0;
\r
6426 if ( toString.call(array) === "[object Array]" ) {
\r
6427 Array.prototype.push.apply( ret, array );
\r
6429 if ( typeof array.length === "number" ) {
\r
6430 for ( var l = array.length; i < l; i++ ) {
\r
6431 ret.push( array[i] );
\r
6434 for ( ; array[i]; i++ ) {
\r
6435 ret.push( array[i] );
\r
6446 if ( document.documentElement.compareDocumentPosition ) {
\r
6447 sortOrder = function( a, b ) {
\r
6448 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
\r
6450 hasDuplicate = true;
\r
6452 return a.compareDocumentPosition ? -1 : 1;
\r
6455 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
\r
6456 if ( ret === 0 ) {
\r
6457 hasDuplicate = true;
\r
6461 } else if ( "sourceIndex" in document.documentElement ) {
\r
6462 sortOrder = function( a, b ) {
\r
6463 if ( !a.sourceIndex || !b.sourceIndex ) {
\r
6465 hasDuplicate = true;
\r
6467 return a.sourceIndex ? -1 : 1;
\r
6470 var ret = a.sourceIndex - b.sourceIndex;
\r
6471 if ( ret === 0 ) {
\r
6472 hasDuplicate = true;
\r
6476 } else if ( document.createRange ) {
\r
6477 sortOrder = function( a, b ) {
\r
6478 if ( !a.ownerDocument || !b.ownerDocument ) {
\r
6480 hasDuplicate = true;
\r
6482 return a.ownerDocument ? -1 : 1;
\r
6485 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
\r
6486 aRange.setStart(a, 0);
\r
6487 aRange.setEnd(a, 0);
\r
6488 bRange.setStart(b, 0);
\r
6489 bRange.setEnd(b, 0);
\r
6490 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
\r
6491 if ( ret === 0 ) {
\r
6492 hasDuplicate = true;
\r
6498 // Utility function for retreiving the text value of an array of DOM nodes
\r
6499 Sizzle.getText = function( elems ) {
\r
6500 var ret = "", elem;
\r
6502 for ( var i = 0; elems[i]; i++ ) {
\r
6505 // Get the text from text nodes and CDATA nodes
\r
6506 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
\r
6507 ret += elem.nodeValue;
\r
6509 // Traverse everything else, except comment nodes
\r
6510 } else if ( elem.nodeType !== 8 ) {
\r
6511 ret += Sizzle.getText( elem.childNodes );
\r
6518 // Check to see if the browser returns elements by name when
\r
6519 // querying by getElementById (and provide a workaround)
\r
6521 // We're going to inject a fake input element with a specified name
\r
6522 var form = document.createElement("div"),
\r
6523 id = "script" + (new Date()).getTime();
\r
6524 form.innerHTML = "<a name='" + id + "'/>";
\r
6526 // Inject it into the root element, check its status, and remove it quickly
\r
6527 var root = document.documentElement;
\r
6528 root.insertBefore( form, root.firstChild );
\r
6530 // The workaround has to do additional checks after a getElementById
\r
6531 // Which slows things down for other browsers (hence the branching)
\r
6532 if ( document.getElementById( id ) ) {
\r
6533 Expr.find.ID = function(match, context, isXML){
\r
6534 if ( typeof context.getElementById !== "undefined" && !isXML ) {
\r
6535 var m = context.getElementById(match[1]);
\r
6536 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
\r
6540 Expr.filter.ID = function(elem, match){
\r
6541 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
\r
6542 return elem.nodeType === 1 && node && node.nodeValue === match;
\r
6546 root.removeChild( form );
\r
6547 root = form = null; // release memory in IE
\r
6551 // Check to see if the browser returns only elements
\r
6552 // when doing getElementsByTagName("*")
\r
6554 // Create a fake element
\r
6555 var div = document.createElement("div");
\r
6556 div.appendChild( document.createComment("") );
\r
6558 // Make sure no comments are found
\r
6559 if ( div.getElementsByTagName("*").length > 0 ) {
\r
6560 Expr.find.TAG = function(match, context){
\r
6561 var results = context.getElementsByTagName(match[1]);
\r
6563 // Filter out possible comments
\r
6564 if ( match[1] === "*" ) {
\r
6567 for ( var i = 0; results[i]; i++ ) {
\r
6568 if ( results[i].nodeType === 1 ) {
\r
6569 tmp.push( results[i] );
\r
6580 // Check to see if an attribute returns normalized href attributes
\r
6581 div.innerHTML = "<a href='#'></a>";
\r
6582 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
\r
6583 div.firstChild.getAttribute("href") !== "#" ) {
\r
6584 Expr.attrHandle.href = function(elem){
\r
6585 return elem.getAttribute("href", 2);
\r
6589 div = null; // release memory in IE
\r
6592 if ( document.querySelectorAll ) {
\r
6594 var oldSizzle = Sizzle, div = document.createElement("div");
\r
6595 div.innerHTML = "<p class='TEST'></p>";
\r
6597 // Safari can't handle uppercase or unicode characters when
\r
6598 // in quirks mode.
\r
6599 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
\r
6603 Sizzle = function(query, context, extra, seed){
\r
6604 context = context || document;
\r
6606 // Only use querySelectorAll on non-XML documents
\r
6607 // (ID selectors don't work in non-HTML documents)
\r
6608 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
\r
6610 return makeArray( context.querySelectorAll(query), extra );
\r
6614 return oldSizzle(query, context, extra, seed);
\r
6617 for ( var prop in oldSizzle ) {
\r
6618 Sizzle[ prop ] = oldSizzle[ prop ];
\r
6621 div = null; // release memory in IE
\r
6626 var div = document.createElement("div");
\r
6628 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
\r
6630 // Opera can't find a second classname (in 9.6)
\r
6631 // Also, make sure that getElementsByClassName actually exists
\r
6632 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
\r
6636 // Safari caches class attributes, doesn't catch changes (in 3.2)
\r
6637 div.lastChild.className = "e";
\r
6639 if ( div.getElementsByClassName("e").length === 1 ) {
\r
6643 Expr.order.splice(1, 0, "CLASS");
\r
6644 Expr.find.CLASS = function(match, context, isXML) {
\r
6645 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
\r
6646 return context.getElementsByClassName(match[1]);
\r
6650 div = null; // release memory in IE
\r
6653 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
\r
6654 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
6655 var elem = checkSet[i];
\r
6658 var match = false;
\r
6661 if ( elem.sizcache === doneName ) {
\r
6662 match = checkSet[elem.sizset];
\r
6666 if ( elem.nodeType === 1 && !isXML ){
\r
6667 elem.sizcache = doneName;
\r
6671 if ( elem.nodeName.toLowerCase() === cur ) {
\r
6679 checkSet[i] = match;
\r
6684 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
\r
6685 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
6686 var elem = checkSet[i];
\r
6689 var match = false;
\r
6692 if ( elem.sizcache === doneName ) {
\r
6693 match = checkSet[elem.sizset];
\r
6697 if ( elem.nodeType === 1 ) {
\r
6699 elem.sizcache = doneName;
\r
6702 if ( typeof cur !== "string" ) {
\r
6703 if ( elem === cur ) {
\r
6708 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
\r
6717 checkSet[i] = match;
\r
6722 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
\r
6723 return !!(a.compareDocumentPosition(b) & 16);
\r
6724 } : function(a, b){
\r
6725 return a !== b && (a.contains ? a.contains(b) : true);
\r
6728 Sizzle.isXML = function(elem){
\r
6729 // documentElement is verified for cases where it doesn't yet exist
\r
6730 // (such as loading iframes in IE - #4833)
\r
6731 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
\r
6732 return documentElement ? documentElement.nodeName !== "HTML" : false;
\r
6735 var posProcess = function(selector, context){
\r
6736 var tmpSet = [], later = "", match,
\r
6737 root = context.nodeType ? [context] : context;
\r
6739 // Position selectors must be done after the filter
\r
6740 // And so must :not(positional) so we move all PSEUDOs to the end
\r
6741 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
\r
6742 later += match[0];
\r
6743 selector = selector.replace( Expr.match.PSEUDO, "" );
\r
6746 selector = Expr.relative[selector] ? selector + "*" : selector;
\r
6748 for ( var i = 0, l = root.length; i < l; i++ ) {
\r
6749 Sizzle( selector, root[i], tmpSet );
\r
6752 return Sizzle.filter( later, tmpSet );
\r
6757 window.tinymce.dom.Sizzle = Sizzle;
\r
6762 (function(tinymce) {
\r
6764 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
\r
6766 tinymce.create('tinymce.dom.EventUtils', {
\r
6767 EventUtils : function() {
\r
6772 add : function(o, n, f, s) {
\r
6773 var cb, t = this, el = t.events, r;
\r
6775 if (n instanceof Array) {
\r
6778 each(n, function(n) {
\r
6779 r.push(t.add(o, n, f, s));
\r
6786 if (o && o.hasOwnProperty && o instanceof Array) {
\r
6789 each(o, function(o) {
\r
6791 r.push(t.add(o, n, f, s));
\r
6802 // Setup event callback
\r
6803 cb = function(e) {
\r
6804 // Is all events disabled
\r
6808 e = e || window.event;
\r
6810 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
\r
6813 e.target = e.srcElement;
\r
6815 // Patch in preventDefault, stopPropagation methods for W3C compatibility
\r
6816 tinymce.extend(e, t._stoppers);
\r
6822 return f.call(s, e);
\r
6825 if (n == 'unload') {
\r
6826 tinymce.unloads.unshift({func : cb});
\r
6830 if (n == 'init') {
\r
6839 // Store away listener reference
\r
6853 remove : function(o, n, f) {
\r
6854 var t = this, a = t.events, s = false, r;
\r
6857 if (o && o.hasOwnProperty && o instanceof Array) {
\r
6860 each(o, function(o) {
\r
6862 r.push(t.remove(o, n, f));
\r
6870 each(a, function(e, i) {
\r
6871 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
\r
6873 t._remove(o, n, e.cfunc);
\r
6882 clear : function(o) {
\r
6883 var t = this, a = t.events, i, e;
\r
6888 for (i = a.length - 1; i >= 0; i--) {
\r
6891 if (e.obj === o) {
\r
6892 t._remove(e.obj, e.name, e.cfunc);
\r
6893 e.obj = e.cfunc = null;
\r
6900 cancel : function(e) {
\r
6906 return this.prevent(e);
\r
6909 stop : function(e) {
\r
6910 if (e.stopPropagation)
\r
6911 e.stopPropagation();
\r
6913 e.cancelBubble = true;
\r
6918 prevent : function(e) {
\r
6919 if (e.preventDefault)
\r
6920 e.preventDefault();
\r
6922 e.returnValue = false;
\r
6927 destroy : function() {
\r
6930 each(t.events, function(e, i) {
\r
6931 t._remove(e.obj, e.name, e.cfunc);
\r
6932 e.obj = e.cfunc = null;
\r
6939 _add : function(o, n, f) {
\r
6940 if (o.attachEvent)
\r
6941 o.attachEvent('on' + n, f);
\r
6942 else if (o.addEventListener)
\r
6943 o.addEventListener(n, f, false);
\r
6948 _remove : function(o, n, f) {
\r
6951 if (o.detachEvent)
\r
6952 o.detachEvent('on' + n, f);
\r
6953 else if (o.removeEventListener)
\r
6954 o.removeEventListener(n, f, false);
\r
6956 o['on' + n] = null;
\r
6958 // Might fail with permission denined on IE so we just ignore that
\r
6963 _pageInit : function(win) {
\r
6966 // Keep it from running more than once
\r
6970 t.domLoaded = true;
\r
6972 each(t.inits, function(c) {
\r
6979 _wait : function(win) {
\r
6980 var t = this, doc = win.document;
\r
6982 // No need since the document is already loaded
\r
6983 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
\r
6989 if (doc.attachEvent) {
\r
6990 doc.attachEvent("onreadystatechange", function() {
\r
6991 if (doc.readyState === "complete") {
\r
6992 doc.detachEvent("onreadystatechange", arguments.callee);
\r
6997 if (doc.documentElement.doScroll && win == win.top) {
\r
7003 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
\r
7004 // http://javascript.nwbox.com/IEContentLoaded/
\r
7005 doc.documentElement.doScroll("left");
\r
7007 setTimeout(arguments.callee, 0);
\r
7014 } else if (doc.addEventListener) {
\r
7015 t._add(win, 'DOMContentLoaded', function() {
\r
7020 t._add(win, 'load', function() {
\r
7026 preventDefault : function() {
\r
7027 this.returnValue = false;
\r
7030 stopPropagation : function() {
\r
7031 this.cancelBubble = true;
\r
7036 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
\r
7038 // Dispatch DOM content loaded event for IE and Safari
\r
7039 Event._wait(window);
\r
7041 tinymce.addUnload(function() {
\r
7046 (function(tinymce) {
\r
7047 tinymce.dom.Element = function(id, settings) {
\r
7048 var t = this, dom, el;
\r
7050 t.settings = settings = settings || {};
\r
7052 t.dom = dom = settings.dom || tinymce.DOM;
\r
7054 // Only IE leaks DOM references, this is a lot faster
\r
7055 if (!tinymce.isIE)
\r
7056 el = dom.get(t.id);
\r
7059 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
\r
7060 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
\r
7061 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
\r
7062 'isHidden,setHTML,get').split(/,/)
\r
7064 t[k] = function() {
\r
7067 for (i = 0; i < arguments.length; i++)
\r
7068 a.push(arguments[i]);
\r
7070 a = dom[k].apply(dom, a);
\r
7077 tinymce.extend(t, {
\r
7078 on : function(n, f, s) {
\r
7079 return tinymce.dom.Event.add(t.id, n, f, s);
\r
7082 getXY : function() {
\r
7084 x : parseInt(t.getStyle('left')),
\r
7085 y : parseInt(t.getStyle('top'))
\r
7089 getSize : function() {
\r
7090 var n = dom.get(t.id);
\r
7093 w : parseInt(t.getStyle('width') || n.clientWidth),
\r
7094 h : parseInt(t.getStyle('height') || n.clientHeight)
\r
7098 moveTo : function(x, y) {
\r
7099 t.setStyles({left : x, top : y});
\r
7102 moveBy : function(x, y) {
\r
7103 var p = t.getXY();
\r
7105 t.moveTo(p.x + x, p.y + y);
\r
7108 resizeTo : function(w, h) {
\r
7109 t.setStyles({width : w, height : h});
\r
7112 resizeBy : function(w, h) {
\r
7113 var s = t.getSize();
\r
7115 t.resizeTo(s.w + w, s.h + h);
\r
7118 update : function(k) {
\r
7121 if (tinymce.isIE6 && settings.blocker) {
\r
7125 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
\r
7128 // Remove blocker on remove
\r
7129 if (k == 'remove') {
\r
7130 dom.remove(t.blocker);
\r
7135 t.blocker = dom.uniqueId();
\r
7136 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
\r
7137 dom.setStyle(b, 'opacity', 0);
\r
7139 b = dom.get(t.blocker);
\r
7141 dom.setStyles(b, {
\r
7142 left : t.getStyle('left', 1),
\r
7143 top : t.getStyle('top', 1),
\r
7144 width : t.getStyle('width', 1),
\r
7145 height : t.getStyle('height', 1),
\r
7146 display : t.getStyle('display', 1),
\r
7147 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
\r
7155 (function(tinymce) {
\r
7156 function trimNl(s) {
\r
7157 return s.replace(/[\n\r]+/g, '');
\r
7161 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
\r
7163 tinymce.create('tinymce.dom.Selection', {
\r
7164 Selection : function(dom, win, serializer) {
\r
7169 t.serializer = serializer;
\r
7173 'onBeforeSetContent',
\r
7175 'onBeforeGetContent',
\r
7181 t[e] = new tinymce.util.Dispatcher(t);
\r
7184 // No W3C Range support
\r
7185 if (!t.win.getSelection)
\r
7186 t.tridentSel = new tinymce.dom.TridentSelection(t);
\r
7188 if (tinymce.isIE && dom.boxModel)
\r
7189 this._fixIESelection();
\r
7192 tinymce.addUnload(t.destroy, t);
\r
7195 getContent : function(s) {
\r
7196 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
\r
7201 s.format = s.format || 'html';
\r
7202 s.forced_root_block = '';
\r
7203 t.onBeforeGetContent.dispatch(t, s);
\r
7205 if (s.format == 'text')
\r
7206 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
\r
7208 if (r.cloneContents) {
\r
7209 n = r.cloneContents();
\r
7213 } else if (is(r.item) || is(r.htmlText))
\r
7214 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
\r
7216 e.innerHTML = r.toString();
\r
7218 // Keep whitespace before and after
\r
7219 if (/^\s/.test(e.innerHTML))
\r
7222 if (/\s+$/.test(e.innerHTML))
\r
7225 s.getInner = true;
\r
7227 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
\r
7228 t.onGetContent.dispatch(t, s);
\r
7233 setContent : function(content, args) {
\r
7234 var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
\r
7236 args = args || {format : 'html'};
\r
7238 content = args.content = content;
\r
7240 // Dispatch before set content event
\r
7241 if (!args.no_events)
\r
7242 self.onBeforeSetContent.dispatch(self, args);
\r
7244 content = args.content;
\r
7246 if (rng.insertNode) {
\r
7247 // Make caret marker since insertNode places the caret in the beginning of text after insert
\r
7248 content += '<span id="__caret">_</span>';
\r
7250 // Delete and insert new node
\r
7251 if (rng.startContainer == doc && rng.endContainer == doc) {
\r
7252 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
\r
7253 doc.body.innerHTML = content;
\r
7255 rng.deleteContents();
\r
7257 if (doc.body.childNodes.length == 0) {
\r
7258 doc.body.innerHTML = content;
\r
7260 // createContextualFragment doesn't exists in IE 9 DOMRanges
\r
7261 if (rng.createContextualFragment) {
\r
7262 rng.insertNode(rng.createContextualFragment(content));
\r
7264 // Fake createContextualFragment call in IE 9
\r
7265 frag = doc.createDocumentFragment();
\r
7266 temp = doc.createElement('div');
\r
7268 frag.appendChild(temp);
\r
7269 temp.outerHTML = content;
\r
7271 rng.insertNode(frag);
\r
7276 // Move to caret marker
\r
7277 caretNode = self.dom.get('__caret');
\r
7279 // Make sure we wrap it compleatly, Opera fails with a simple select call
\r
7280 rng = doc.createRange();
\r
7281 rng.setStartBefore(caretNode);
\r
7282 rng.setEndBefore(caretNode);
\r
7285 // Remove the caret position
\r
7286 self.dom.remove('__caret');
\r
7291 // Might fail on Opera for some odd reason
\r
7295 // Delete content and get caret text selection
\r
7296 doc.execCommand('Delete', false, null);
\r
7297 rng = self.getRng();
\r
7300 rng.pasteHTML(content);
\r
7303 // Dispatch set content event
\r
7304 if (!args.no_events)
\r
7305 self.onSetContent.dispatch(self, args);
\r
7308 getStart : function() {
\r
7309 var rng = this.getRng(), startElement, parentElement, checkRng, node;
\r
7311 if (rng.duplicate || rng.item) {
\r
7312 // Control selection, return first item
\r
7314 return rng.item(0);
\r
7316 // Get start element
\r
7317 checkRng = rng.duplicate();
\r
7318 checkRng.collapse(1);
\r
7319 startElement = checkRng.parentElement();
\r
7321 // Check if range parent is inside the start element, then return the inner parent element
\r
7322 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
\r
7323 parentElement = node = rng.parentElement();
\r
7324 while (node = node.parentNode) {
\r
7325 if (node == startElement) {
\r
7326 startElement = parentElement;
\r
7331 return startElement;
\r
7333 startElement = rng.startContainer;
\r
7335 if (startElement.nodeType == 1 && startElement.hasChildNodes())
\r
7336 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
\r
7338 if (startElement && startElement.nodeType == 3)
\r
7339 return startElement.parentNode;
\r
7341 return startElement;
\r
7345 getEnd : function() {
\r
7346 var t = this, r = t.getRng(), e, eo;
\r
7348 if (r.duplicate || r.item) {
\r
7352 r = r.duplicate();
\r
7354 e = r.parentElement();
\r
7356 if (e && e.nodeName == 'BODY')
\r
7357 return e.lastChild || e;
\r
7361 e = r.endContainer;
\r
7364 if (e.nodeType == 1 && e.hasChildNodes())
\r
7365 e = e.childNodes[eo > 0 ? eo - 1 : eo];
\r
7367 if (e && e.nodeType == 3)
\r
7368 return e.parentNode;
\r
7374 getBookmark : function(type, normalized) {
\r
7375 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
\r
7377 function findIndex(name, element) {
\r
7380 each(dom.select(name), function(node, i) {
\r
7381 if (node == element)
\r
7389 function getLocation() {
\r
7390 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
\r
7392 function getPoint(rng, start) {
\r
7393 var container = rng[start ? 'startContainer' : 'endContainer'],
\r
7394 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
\r
7396 if (container.nodeType == 3) {
\r
7398 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
\r
7399 offset += node.nodeValue.length;
\r
7402 point.push(offset);
\r
7404 childNodes = container.childNodes;
\r
7406 if (offset >= childNodes.length && childNodes.length) {
\r
7408 offset = Math.max(0, childNodes.length - 1);
\r
7411 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
\r
7414 for (; container && container != root; container = container.parentNode)
\r
7415 point.push(t.dom.nodeIndex(container, normalized));
\r
7420 bookmark.start = getPoint(rng, true);
\r
7422 if (!t.isCollapsed())
\r
7423 bookmark.end = getPoint(rng);
\r
7429 return t.tridentSel.getBookmark(type);
\r
7431 return getLocation();
\r
7434 // Handle simple range
\r
7436 return {rng : t.getRng()};
\r
7439 id = dom.uniqueId();
\r
7440 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
\r
7441 styles = 'overflow:hidden;line-height:0px';
\r
7443 // Explorer method
\r
7444 if (rng.duplicate || rng.item) {
\r
7447 rng2 = rng.duplicate();
\r
7450 // Insert start marker
\r
7452 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
\r
7454 // Insert end marker
\r
7456 rng2.collapse(false);
\r
7458 // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
\r
7459 rng.moveToElementText(rng2.parentElement());
\r
7460 if (rng.compareEndPoints('StartToEnd', rng2) == 0)
\r
7461 rng2.move('character', -1);
\r
7463 rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
\r
7466 // IE might throw unspecified error so lets ignore it
\r
7470 // Control selection
\r
7471 element = rng.item(0);
\r
7472 name = element.nodeName;
\r
7474 return {name : name, index : findIndex(name, element)};
\r
7477 element = t.getNode();
\r
7478 name = element.nodeName;
\r
7479 if (name == 'IMG')
\r
7480 return {name : name, index : findIndex(name, element)};
\r
7483 rng2 = rng.cloneRange();
\r
7485 // Insert end marker
\r
7487 rng2.collapse(false);
\r
7488 rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
\r
7491 rng.collapse(true);
\r
7492 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
\r
7495 t.moveToBookmark({id : id, keep : 1});
\r
7500 moveToBookmark : function(bookmark) {
\r
7501 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
\r
7504 if (bookmark.start) {
\r
7505 rng = dom.createRng();
\r
7506 root = dom.getRoot();
\r
7508 function setEndPoint(start) {
\r
7509 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
\r
7512 offset = point[0];
\r
7514 // Find container node
\r
7515 for (node = root, i = point.length - 1; i >= 1; i--) {
\r
7516 children = node.childNodes;
\r
7518 if (point[i] > children.length - 1)
\r
7521 node = children[point[i]];
\r
7524 // Move text offset to best suitable location
\r
7525 if (node.nodeType === 3)
\r
7526 offset = Math.min(point[0], node.nodeValue.length);
\r
7528 // Move element offset to best suitable location
\r
7529 if (node.nodeType === 1)
\r
7530 offset = Math.min(point[0], node.childNodes.length);
\r
7532 // Set offset within container node
\r
7534 rng.setStart(node, offset);
\r
7536 rng.setEnd(node, offset);
\r
7543 return t.tridentSel.moveToBookmark(bookmark);
\r
7545 if (setEndPoint(true) && setEndPoint()) {
\r
7548 } else if (bookmark.id) {
\r
7549 function restoreEndPoint(suffix) {
\r
7550 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
\r
7553 node = marker.parentNode;
\r
7555 if (suffix == 'start') {
\r
7557 idx = dom.nodeIndex(marker);
\r
7559 node = marker.firstChild;
\r
7563 startContainer = endContainer = node;
\r
7564 startOffset = endOffset = idx;
\r
7567 idx = dom.nodeIndex(marker);
\r
7569 node = marker.firstChild;
\r
7573 endContainer = node;
\r
7578 prev = marker.previousSibling;
\r
7579 next = marker.nextSibling;
\r
7581 // Remove all marker text nodes
\r
7582 each(tinymce.grep(marker.childNodes), function(node) {
\r
7583 if (node.nodeType == 3)
\r
7584 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
\r
7587 // Remove marker but keep children if for example contents where inserted into the marker
\r
7588 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
\r
7589 while (marker = dom.get(bookmark.id + '_' + suffix))
\r
7590 dom.remove(marker, 1);
\r
7592 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
\r
7593 // 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
\r
7594 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
\r
7595 idx = prev.nodeValue.length;
\r
7596 prev.appendData(next.nodeValue);
\r
7599 if (suffix == 'start') {
\r
7600 startContainer = endContainer = prev;
\r
7601 startOffset = endOffset = idx;
\r
7603 endContainer = prev;
\r
7611 function addBogus(node) {
\r
7612 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
\r
7613 if (dom.isBlock(node) && !node.innerHTML)
\r
7614 node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
\r
7619 // Restore start/end points
\r
7620 restoreEndPoint('start');
\r
7621 restoreEndPoint('end');
\r
7623 if (startContainer) {
\r
7624 rng = dom.createRng();
\r
7625 rng.setStart(addBogus(startContainer), startOffset);
\r
7626 rng.setEnd(addBogus(endContainer), endOffset);
\r
7629 } else if (bookmark.name) {
\r
7630 t.select(dom.select(bookmark.name)[bookmark.index]);
\r
7631 } else if (bookmark.rng)
\r
7632 t.setRng(bookmark.rng);
\r
7636 select : function(node, content) {
\r
7637 var t = this, dom = t.dom, rng = dom.createRng(), idx;
\r
7640 idx = dom.nodeIndex(node);
\r
7641 rng.setStart(node.parentNode, idx);
\r
7642 rng.setEnd(node.parentNode, idx + 1);
\r
7644 // Find first/last text node or BR element
\r
7646 function setPoint(node, start) {
\r
7647 var walker = new tinymce.dom.TreeWalker(node, node);
\r
7651 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
\r
7653 rng.setStart(node, 0);
\r
7655 rng.setEnd(node, node.nodeValue.length);
\r
7661 if (node.nodeName == 'BR') {
\r
7663 rng.setStartBefore(node);
\r
7665 rng.setEndBefore(node);
\r
7669 } while (node = (start ? walker.next() : walker.prev()));
\r
7672 setPoint(node, 1);
\r
7682 isCollapsed : function() {
\r
7683 var t = this, r = t.getRng(), s = t.getSel();
\r
7688 if (r.compareEndPoints)
\r
7689 return r.compareEndPoints('StartToEnd', r) === 0;
\r
7691 return !s || r.collapsed;
\r
7694 collapse : function(to_start) {
\r
7695 var self = this, rng = self.getRng(), node;
\r
7697 // Control range on IE
\r
7699 node = rng.item(0);
\r
7700 rng = self.win.document.body.createTextRange();
\r
7701 rng.moveToElementText(node);
\r
7704 rng.collapse(!!to_start);
\r
7708 getSel : function() {
\r
7709 var t = this, w = this.win;
\r
7711 return w.getSelection ? w.getSelection() : w.document.selection;
\r
7714 getRng : function(w3c) {
\r
7715 var t = this, s, r, elm, doc = t.win.document;
\r
7717 // Found tridentSel object then we need to use that one
\r
7718 if (w3c && t.tridentSel)
\r
7719 return t.tridentSel.getRangeAt(0);
\r
7722 if (s = t.getSel())
\r
7723 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
\r
7725 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
\r
7728 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
\r
7729 if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
\r
7730 elm = doc.selection.createRange().item(0);
\r
7731 r = doc.createRange();
\r
7732 r.setStartBefore(elm);
\r
7733 r.setEndAfter(elm);
\r
7736 // No range found then create an empty one
\r
7737 // This can occur when the editor is placed in a hidden container element on Gecko
\r
7738 // Or on IE when there was an exception
\r
7740 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
\r
7742 if (t.selectedRange && t.explicitRange) {
\r
7743 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
\r
7744 // Safari, Opera and Chrome only ever select text which causes the range to change.
\r
7745 // This lets us use the originally set range if the selection hasn't been changed by the user.
\r
7746 r = t.explicitRange;
\r
7748 t.selectedRange = null;
\r
7749 t.explicitRange = null;
\r
7756 setRng : function(r) {
\r
7759 if (!t.tridentSel) {
\r
7763 t.explicitRange = r;
\r
7766 s.removeAllRanges();
\r
7768 // IE9 might throw errors here don't know why
\r
7772 t.selectedRange = s.getRangeAt(0);
\r
7776 if (r.cloneRange) {
\r
7777 t.tridentSel.addRange(r);
\r
7781 // Is IE specific range
\r
7785 // Needed for some odd IE bug #1843306
\r
7790 setNode : function(n) {
\r
7793 t.setContent(t.dom.getOuterHTML(n));
\r
7798 getNode : function() {
\r
7799 var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
\r
7801 // Range maybe lost after the editor is made visible again
\r
7803 return t.dom.getRoot();
\r
7805 if (rng.setStart) {
\r
7806 elm = rng.commonAncestorContainer;
\r
7808 // Handle selection a image or other control like element such as anchors
\r
7809 if (!rng.collapsed) {
\r
7810 if (rng.startContainer == rng.endContainer) {
\r
7811 if (rng.endOffset - rng.startOffset < 2) {
\r
7812 if (rng.startContainer.hasChildNodes())
\r
7813 elm = rng.startContainer.childNodes[rng.startOffset];
\r
7817 // If the anchor node is a element instead of a text node then return this element
\r
7818 //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
\r
7819 // return sel.anchorNode.childNodes[sel.anchorOffset];
\r
7821 // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
\r
7822 // This happens when you double click an underlined word in FireFox.
\r
7823 if (start.nodeType === 3 && end.nodeType === 3) {
\r
7824 function skipEmptyTextNodes(n, forwards) {
\r
7826 while (n && n.nodeType === 3 && n.length === 0) {
\r
7827 n = forwards ? n.nextSibling : n.previousSibling;
\r
7831 if (start.length === rng.startOffset) {
\r
7832 start = skipEmptyTextNodes(start.nextSibling, true);
\r
7834 start = start.parentNode;
\r
7836 if (rng.endOffset === 0) {
\r
7837 end = skipEmptyTextNodes(end.previousSibling, false);
\r
7839 end = end.parentNode;
\r
7842 if (start && start === end)
\r
7847 if (elm && elm.nodeType == 3)
\r
7848 return elm.parentNode;
\r
7853 return rng.item ? rng.item(0) : rng.parentElement();
\r
7856 getSelectedBlocks : function(st, en) {
\r
7857 var t = this, dom = t.dom, sb, eb, n, bl = [];
\r
7859 sb = dom.getParent(st || t.getStart(), dom.isBlock);
\r
7860 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
\r
7865 if (sb && eb && sb != eb) {
\r
7868 while ((n = n.nextSibling) && n != eb) {
\r
7869 if (dom.isBlock(n))
\r
7874 if (eb && sb != eb)
\r
7880 destroy : function(s) {
\r
7885 // Manual destroy then remove unload handler
\r
7887 tinymce.removeUnload(t.destroy);
\r
7890 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
\r
7891 _fixIESelection : function() {
\r
7892 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
\r
7894 // Make HTML element unselectable since we are going to handle selection by hand
\r
7895 doc.documentElement.unselectable = true;
\r
7897 // Return range from point or null if it failed
\r
7898 function rngFromPoint(x, y) {
\r
7899 var rng = body.createTextRange();
\r
7902 rng.moveToPoint(x, y);
\r
7904 // IE sometimes throws and exception, so lets just ignore it
\r
7911 // Fires while the selection is changing
\r
7912 function selectionChange(e) {
\r
7915 // Check if the button is down or not
\r
7917 // Create range from mouse position
\r
7918 pointRng = rngFromPoint(e.x, e.y);
\r
7921 // Check if pointRange is before/after selection then change the endPoint
\r
7922 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
\r
7923 pointRng.setEndPoint('StartToStart', startRng);
\r
7925 pointRng.setEndPoint('EndToEnd', startRng);
\r
7927 pointRng.select();
\r
7933 // Removes listeners
\r
7934 function endSelection() {
\r
7935 var rng = doc.selection.createRange();
\r
7937 // If the range is collapsed then use the last start range
\r
7938 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
\r
7939 startRng.select();
\r
7941 dom.unbind(doc, 'mouseup', endSelection);
\r
7942 dom.unbind(doc, 'mousemove', selectionChange);
\r
7943 startRng = started = 0;
\r
7946 // Detect when user selects outside BODY
\r
7947 dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
\r
7948 if (e.target.nodeName === 'HTML') {
\r
7952 // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
\r
7953 htmlElm = doc.documentElement;
\r
7954 if (htmlElm.scrollHeight > htmlElm.clientHeight)
\r
7958 // Setup start position
\r
7959 startRng = rngFromPoint(e.x, e.y);
\r
7961 // Listen for selection change events
\r
7962 dom.bind(doc, 'mouseup', endSelection);
\r
7963 dom.bind(doc, 'mousemove', selectionChange);
\r
7966 startRng.select();
\r
7974 (function(tinymce) {
\r
7975 tinymce.dom.Serializer = function(settings, dom, schema) {
\r
7976 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
\r
7978 // Support the old apply_source_formatting option
\r
7979 if (!settings.apply_source_formatting)
\r
7980 settings.indent = false;
\r
7982 settings.remove_trailing_brs = true;
\r
7984 // Default DOM and Schema if they are undefined
\r
7985 dom = dom || tinymce.DOM;
\r
7986 schema = schema || new tinymce.html.Schema(settings);
\r
7987 settings.entity_encoding = settings.entity_encoding || 'named';
\r
7989 onPreProcess = new tinymce.util.Dispatcher(self);
\r
7991 onPostProcess = new tinymce.util.Dispatcher(self);
\r
7993 htmlParser = new tinymce.html.DomParser(settings, schema);
\r
7995 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
\r
7996 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
\r
7997 var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
\r
8002 value = node.attributes.map[internalName];
\r
8003 if (value !== undef) {
\r
8004 // Set external name to internal value and remove internal
\r
8005 node.attr(name, value.length > 0 ? value : null);
\r
8006 node.attr(internalName, null);
\r
8008 // No internal attribute found then convert the value we have in the DOM
\r
8009 value = node.attributes.map[name];
\r
8011 if (name === "style")
\r
8012 value = dom.serializeStyle(dom.parseStyle(value), node.name);
\r
8013 else if (urlConverter)
\r
8014 value = urlConverter.call(urlConverterScope, value, name, node.name);
\r
8016 node.attr(name, value.length > 0 ? value : null);
\r
8021 // Remove internal classes mceItem<..>
\r
8022 htmlParser.addAttributeFilter('class', function(nodes, name) {
\r
8023 var i = nodes.length, node, value;
\r
8027 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
\r
8028 node.attr('class', value.length > 0 ? value : null);
\r
8032 // Remove bookmark elements
\r
8033 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
\r
8034 var i = nodes.length, node;
\r
8039 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
\r
8044 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
\r
8045 htmlParser.addNodeFilter('script,style', function(nodes, name) {
\r
8046 var i = nodes.length, node, value;
\r
8048 function trim(value) {
\r
8049 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
\r
8050 .replace(/^[\r\n]*|[\r\n]*$/g, '')
\r
8051 .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
\r
8052 .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
\r
8057 value = node.firstChild ? node.firstChild.value : '';
\r
8059 if (name === "script") {
\r
8060 // Remove mce- prefix from script elements
\r
8061 node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
\r
8063 if (value.length > 0)
\r
8064 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
\r
8066 if (value.length > 0)
\r
8067 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
\r
8072 // Convert comments to cdata and handle protected comments
\r
8073 htmlParser.addNodeFilter('#comment', function(nodes, name) {
\r
8074 var i = nodes.length, node;
\r
8079 if (node.value.indexOf('[CDATA[') === 0) {
\r
8080 node.name = '#cdata';
\r
8082 node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
\r
8083 } else if (node.value.indexOf('mce:protected ') === 0) {
\r
8084 node.name = "#text";
\r
8087 node.value = unescape(node.value).substr(14);
\r
8092 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
\r
8093 var i = nodes.length, node;
\r
8097 if (node.type === 7)
\r
8099 else if (node.type === 1) {
\r
8100 if (name === "input" && !("type" in node.attributes.map))
\r
8101 node.attr('type', 'text');
\r
8106 // Fix list elements, TODO: Replace this later
\r
8107 if (settings.fix_list_elements) {
\r
8108 htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
\r
8109 var i = nodes.length, node, parentNode;
\r
8113 parentNode = node.parent;
\r
8115 if (parentNode.name === 'ul' || parentNode.name === 'ol') {
\r
8116 if (node.prev && node.prev.name === 'li') {
\r
8117 node.prev.append(node);
\r
8124 // Remove internal data attributes
\r
8125 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
\r
8126 var i = nodes.length;
\r
8129 nodes[i].attr(name, null);
\r
8133 // Return public methods
\r
8137 addNodeFilter : htmlParser.addNodeFilter,
\r
8139 addAttributeFilter : htmlParser.addAttributeFilter,
\r
8141 onPreProcess : onPreProcess,
\r
8143 onPostProcess : onPostProcess,
\r
8145 serialize : function(node, args) {
\r
8146 var impl, doc, oldDoc, htmlSerializer, content;
\r
8148 // Explorer won't clone contents of script and style and the
\r
8149 // selected index of select elements are cleared on a clone operation.
\r
8150 if (isIE && dom.select('script,style,select').length > 0) {
\r
8151 content = node.innerHTML;
\r
8152 node = node.cloneNode(false);
\r
8153 dom.setHTML(node, content);
\r
8155 node = node.cloneNode(true);
\r
8157 // Nodes needs to be attached to something in WebKit/Opera
\r
8158 // Older builds of Opera crashes if you attach the node to an document created dynamically
\r
8159 // and since we can't feature detect a crash we need to sniff the acutal build number
\r
8160 // This fix will make DOM ranges and make Sizzle happy!
\r
8161 impl = node.ownerDocument.implementation;
\r
8162 if (impl.createHTMLDocument) {
\r
8163 // Create an empty HTML document
\r
8164 doc = impl.createHTMLDocument("");
\r
8166 // Add the element or it's children if it's a body element to the new document
\r
8167 each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
\r
8168 doc.body.appendChild(doc.importNode(node, true));
\r
8171 // Grab first child or body element for serialization
\r
8172 if (node.nodeName != 'BODY')
\r
8173 node = doc.body.firstChild;
\r
8177 // set the new document in DOMUtils so createElement etc works
\r
8182 args = args || {};
\r
8183 args.format = args.format || 'html';
\r
8186 if (!args.no_events) {
\r
8188 onPreProcess.dispatch(self, args);
\r
8191 // Setup serializer
\r
8192 htmlSerializer = new tinymce.html.Serializer(settings, schema);
\r
8194 // Parse and serialize HTML
\r
8195 args.content = htmlSerializer.serialize(
\r
8196 htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
\r
8199 // Replace all BOM characters for now until we can find a better solution
\r
8200 if (!args.cleanup)
\r
8201 args.content = args.content.replace(/\uFEFF/g, '');
\r
8204 if (!args.no_events)
\r
8205 onPostProcess.dispatch(self, args);
\r
8207 // Restore the old document if it was changed
\r
8213 return args.content;
\r
8216 addRules : function(rules) {
\r
8217 schema.addValidElements(rules);
\r
8220 setRules : function(rules) {
\r
8221 schema.setValidElements(rules);
\r
8226 (function(tinymce) {
\r
8227 tinymce.dom.ScriptLoader = function(settings) {
\r
8233 scriptLoadedCallbacks = {},
\r
8234 queueLoadedCallbacks = [],
\r
8238 function loadScript(url, callback) {
\r
8239 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
\r
8241 // Execute callback when script is loaded
\r
8246 elm.onreadystatechange = elm.onload = elm = null;
\r
8251 function error() {
\r
8252 // Report the error so it's easier for people to spot loading errors
\r
8253 if (typeof(console) !== "undefined" && console.log)
\r
8254 console.log("Failed to load: " + url);
\r
8256 // We can't mark it as done if there is a load error since
\r
8257 // A) We don't want to produce 404 errors on the server and
\r
8258 // B) the onerror event won't fire on all browsers.
\r
8262 id = dom.uniqueId();
\r
8264 if (tinymce.isIE6) {
\r
8265 uri = new tinymce.util.URI(url);
\r
8268 // If script is from same domain and we
\r
8269 // use IE 6 then use XHR since it's more reliable
\r
8270 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
\r
8271 tinymce.util.XHR.send({
\r
8272 url : tinymce._addVer(uri.getURI()),
\r
8273 success : function(content) {
\r
8274 // Create new temp script element
\r
8275 var script = dom.create('script', {
\r
8276 type : 'text/javascript'
\r
8279 // Evaluate script in global scope
\r
8280 script.text = content;
\r
8281 document.getElementsByTagName('head')[0].appendChild(script);
\r
8282 dom.remove(script);
\r
8294 // Create new script element
\r
8295 elm = dom.create('script', {
\r
8297 type : 'text/javascript',
\r
8298 src : tinymce._addVer(url)
\r
8301 // Add onload listener for non IE browsers since IE9
\r
8302 // fires onload event before the script is parsed and executed
\r
8303 if (!tinymce.isIE)
\r
8304 elm.onload = done;
\r
8306 // Add onerror event will get fired on some browsers but not all of them
\r
8307 elm.onerror = error;
\r
8309 // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
\r
8310 if (!tinymce.isOpera) {
\r
8311 elm.onreadystatechange = function() {
\r
8312 var state = elm.readyState;
\r
8314 // Loaded state is passed on IE 6 however there
\r
8315 // are known issues with this method but we can't use
\r
8316 // XHR in a cross domain loading
\r
8317 if (state == 'complete' || state == 'loaded')
\r
8322 // Most browsers support this feature so we report errors
\r
8323 // for those at least to help users track their missing plugins etc
\r
8324 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
\r
8325 /*elm.onerror = function() {
\r
8326 alert('Failed to load: ' + url);
\r
8329 // Add script to document
\r
8330 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
\r
8333 this.isDone = function(url) {
\r
8334 return states[url] == LOADED;
\r
8337 this.markDone = function(url) {
\r
8338 states[url] = LOADED;
\r
8341 this.add = this.load = function(url, callback, scope) {
\r
8342 var item, state = states[url];
\r
8344 // Add url to load queue
\r
8345 if (state == undefined) {
\r
8347 states[url] = QUEUED;
\r
8351 // Store away callback for later execution
\r
8352 if (!scriptLoadedCallbacks[url])
\r
8353 scriptLoadedCallbacks[url] = [];
\r
8355 scriptLoadedCallbacks[url].push({
\r
8357 scope : scope || this
\r
8362 this.loadQueue = function(callback, scope) {
\r
8363 this.loadScripts(queue, callback, scope);
\r
8366 this.loadScripts = function(scripts, callback, scope) {
\r
8369 function execScriptLoadedCallbacks(url) {
\r
8370 // Execute URL callback functions
\r
8371 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
\r
8372 callback.func.call(callback.scope);
\r
8375 scriptLoadedCallbacks[url] = undefined;
\r
8378 queueLoadedCallbacks.push({
\r
8380 scope : scope || this
\r
8383 loadScripts = function() {
\r
8384 var loadingScripts = tinymce.grep(scripts);
\r
8386 // Current scripts has been handled
\r
8387 scripts.length = 0;
\r
8389 // Load scripts that needs to be loaded
\r
8390 tinymce.each(loadingScripts, function(url) {
\r
8391 // Script is already loaded then execute script callbacks directly
\r
8392 if (states[url] == LOADED) {
\r
8393 execScriptLoadedCallbacks(url);
\r
8397 // Is script not loading then start loading it
\r
8398 if (states[url] != LOADING) {
\r
8399 states[url] = LOADING;
\r
8402 loadScript(url, function() {
\r
8403 states[url] = LOADED;
\r
8406 execScriptLoadedCallbacks(url);
\r
8408 // Load more scripts if they where added by the recently loaded script
\r
8414 // No scripts are currently loading then execute all pending queue loaded callbacks
\r
8416 tinymce.each(queueLoadedCallbacks, function(callback) {
\r
8417 callback.func.call(callback.scope);
\r
8420 queueLoadedCallbacks.length = 0;
\r
8428 // Global script loader
\r
8429 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
\r
8432 tinymce.dom.TreeWalker = function(start_node, root_node) {
\r
8433 var node = start_node;
\r
8435 function findSibling(node, start_name, sibling_name, shallow) {
\r
8436 var sibling, parent;
\r
8439 // Walk into nodes if it has a start
\r
8440 if (!shallow && node[start_name])
\r
8441 return node[start_name];
\r
8443 // Return the sibling if it has one
\r
8444 if (node != root_node) {
\r
8445 sibling = node[sibling_name];
\r
8449 // Walk up the parents to look for siblings
\r
8450 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
\r
8451 sibling = parent[sibling_name];
\r
8459 this.current = function() {
\r
8463 this.next = function(shallow) {
\r
8464 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
\r
8467 this.prev = function(shallow) {
\r
8468 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
\r
8472 (function(tinymce) {
\r
8473 tinymce.dom.RangeUtils = function(dom) {
\r
8474 var INVISIBLE_CHAR = '\uFEFF';
\r
8476 this.walk = function(rng, callback) {
\r
8477 var startContainer = rng.startContainer,
\r
8478 startOffset = rng.startOffset,
\r
8479 endContainer = rng.endContainer,
\r
8480 endOffset = rng.endOffset,
\r
8481 ancestor, startPoint,
\r
8482 endPoint, node, parent, siblings, nodes;
\r
8484 // Handle table cell selection the table plugin enables
\r
8485 // you to fake select table cells and perform formatting actions on them
\r
8486 nodes = dom.select('td.mceSelected,th.mceSelected');
\r
8487 if (nodes.length > 0) {
\r
8488 tinymce.each(nodes, function(node) {
\r
8495 function collectSiblings(node, name, end_node) {
\r
8496 var siblings = [];
\r
8498 for (; node && node != end_node; node = node[name])
\r
8499 siblings.push(node);
\r
8504 function findEndPoint(node, root) {
\r
8506 if (node.parentNode == root)
\r
8509 node = node.parentNode;
\r
8513 function walkBoundary(start_node, end_node, next) {
\r
8514 var siblingName = next ? 'nextSibling' : 'previousSibling';
\r
8516 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
\r
8517 parent = node.parentNode;
\r
8518 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
\r
8520 if (siblings.length) {
\r
8522 siblings.reverse();
\r
8524 callback(siblings);
\r
8529 // If index based start position then resolve it
\r
8530 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
\r
8531 startContainer = startContainer.childNodes[startOffset];
\r
8533 // If index based end position then resolve it
\r
8534 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
\r
8535 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
\r
8537 // Find common ancestor and end points
\r
8538 ancestor = dom.findCommonAncestor(startContainer, endContainer);
\r
8541 if (startContainer == endContainer)
\r
8542 return callback([startContainer]);
\r
8544 // Process left side
\r
8545 for (node = startContainer; node; node = node.parentNode) {
\r
8546 if (node == endContainer)
\r
8547 return walkBoundary(startContainer, ancestor, true);
\r
8549 if (node == ancestor)
\r
8553 // Process right side
\r
8554 for (node = endContainer; node; node = node.parentNode) {
\r
8555 if (node == startContainer)
\r
8556 return walkBoundary(endContainer, ancestor);
\r
8558 if (node == ancestor)
\r
8562 // Find start/end point
\r
8563 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
\r
8564 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
\r
8567 walkBoundary(startContainer, startPoint, true);
\r
8569 // Walk the middle from start to end point
\r
8570 siblings = collectSiblings(
\r
8571 startPoint == startContainer ? startPoint : startPoint.nextSibling,
\r
8573 endPoint == endContainer ? endPoint.nextSibling : endPoint
\r
8576 if (siblings.length)
\r
8577 callback(siblings);
\r
8579 // Walk right leaf
\r
8580 walkBoundary(endContainer, endPoint);
\r
8583 /* this.split = function(rng) {
\r
8584 var startContainer = rng.startContainer,
\r
8585 startOffset = rng.startOffset,
\r
8586 endContainer = rng.endContainer,
\r
8587 endOffset = rng.endOffset;
\r
8589 function splitText(node, offset) {
\r
8590 if (offset == node.nodeValue.length)
\r
8591 node.appendData(INVISIBLE_CHAR);
\r
8593 node = node.splitText(offset);
\r
8595 if (node.nodeValue === INVISIBLE_CHAR)
\r
8596 node.nodeValue = '';
\r
8601 // Handle single text node
\r
8602 if (startContainer == endContainer) {
\r
8603 if (startContainer.nodeType == 3) {
\r
8604 if (startOffset != 0)
\r
8605 startContainer = endContainer = splitText(startContainer, startOffset);
\r
8607 if (endOffset - startOffset != startContainer.nodeValue.length)
\r
8608 splitText(startContainer, endOffset - startOffset);
\r
8611 // Split startContainer text node if needed
\r
8612 if (startContainer.nodeType == 3 && startOffset != 0) {
\r
8613 startContainer = splitText(startContainer, startOffset);
\r
8617 // Split endContainer text node if needed
\r
8618 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
\r
8619 endContainer = splitText(endContainer, endOffset).previousSibling;
\r
8620 endOffset = endContainer.nodeValue.length;
\r
8625 startContainer : startContainer,
\r
8626 startOffset : startOffset,
\r
8627 endContainer : endContainer,
\r
8628 endOffset : endOffset
\r
8634 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
\r
8635 if (rng1 && rng2) {
\r
8636 // Compare native IE ranges
\r
8637 if (rng1.item || rng1.duplicate) {
\r
8638 // Both are control ranges and the selected element matches
\r
8639 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
\r
8642 // Both are text ranges and the range matches
\r
8643 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
\r
8646 // Compare w3c ranges
\r
8647 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
\r
8655 (function(tinymce) {
\r
8656 var Event = tinymce.dom.Event, each = tinymce.each;
\r
8658 tinymce.create('tinymce.ui.KeyboardNavigation', {
\r
8659 KeyboardNavigation: function(settings, dom) {
\r
8660 var t = this, root = settings.root, items = settings.items,
\r
8661 enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
\r
8662 excludeFromTabOrder = settings.excludeFromTabOrder,
\r
8663 itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
\r
8665 dom = dom || tinymce.DOM;
\r
8667 itemFocussed = function(evt) {
\r
8668 focussedId = evt.target.id;
\r
8671 itemBlurred = function(evt) {
\r
8672 dom.setAttrib(evt.target.id, 'tabindex', '-1');
\r
8675 rootFocussed = function(evt) {
\r
8676 var item = dom.get(focussedId);
\r
8677 dom.setAttrib(item, 'tabindex', '0');
\r
8681 t.focus = function() {
\r
8682 dom.get(focussedId).focus();
\r
8685 t.destroy = function() {
\r
8686 each(items, function(item) {
\r
8687 dom.unbind(dom.get(item.id), 'focus', itemFocussed);
\r
8688 dom.unbind(dom.get(item.id), 'blur', itemBlurred);
\r
8691 dom.unbind(dom.get(root), 'focus', rootFocussed);
\r
8692 dom.unbind(dom.get(root), 'keydown', rootKeydown);
\r
8694 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
\r
8695 t.destroy = function() {};
\r
8698 t.moveFocus = function(dir, evt) {
\r
8699 var idx = -1, controls = t.controls, newFocus;
\r
8704 each(items, function(item, index) {
\r
8705 if (item.id === focussedId) {
\r
8713 idx = items.length - 1;
\r
8714 } else if (idx >= items.length) {
\r
8718 newFocus = items[idx];
\r
8719 dom.setAttrib(focussedId, 'tabindex', '-1');
\r
8720 dom.setAttrib(newFocus.id, 'tabindex', '0');
\r
8721 dom.get(newFocus.id).focus();
\r
8723 if (settings.actOnFocus) {
\r
8724 settings.onAction(newFocus.id);
\r
8728 Event.cancel(evt);
\r
8731 rootKeydown = function(evt) {
\r
8732 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;
\r
8734 switch (evt.keyCode) {
\r
8736 if (enableLeftRight) t.moveFocus(-1);
\r
8739 case DOM_VK_RIGHT:
\r
8740 if (enableLeftRight) t.moveFocus(1);
\r
8744 if (enableUpDown) t.moveFocus(-1);
\r
8748 if (enableUpDown) t.moveFocus(1);
\r
8751 case DOM_VK_ESCAPE:
\r
8752 if (settings.onCancel) {
\r
8753 settings.onCancel();
\r
8754 Event.cancel(evt);
\r
8758 case DOM_VK_ENTER:
\r
8759 case DOM_VK_RETURN:
\r
8760 case DOM_VK_SPACE:
\r
8761 if (settings.onAction) {
\r
8762 settings.onAction(focussedId);
\r
8763 Event.cancel(evt);
\r
8769 // Set up state and listeners for each item.
\r
8770 each(items, function(item, idx) {
\r
8774 item.id = dom.uniqueId('_mce_item_');
\r
8777 if (excludeFromTabOrder) {
\r
8778 dom.bind(item.id, 'blur', itemBlurred);
\r
8781 tabindex = (idx === 0 ? '0' : '-1');
\r
8784 dom.setAttrib(item.id, 'tabindex', tabindex);
\r
8785 dom.bind(dom.get(item.id), 'focus', itemFocussed);
\r
8788 // Setup initial state for root element.
\r
8790 focussedId = items[0].id;
\r
8793 dom.setAttrib(root, 'tabindex', '-1');
\r
8795 // Setup listeners for root element.
\r
8796 dom.bind(dom.get(root), 'focus', rootFocussed);
\r
8797 dom.bind(dom.get(root), 'keydown', rootKeydown);
\r
8801 (function(tinymce) {
\r
8802 // Shorten class names
\r
8803 var DOM = tinymce.DOM, is = tinymce.is;
\r
8805 tinymce.create('tinymce.ui.Control', {
\r
8806 Control : function(id, s, editor) {
\r
8808 this.settings = s = s || {};
\r
8809 this.rendered = false;
\r
8810 this.onRender = new tinymce.util.Dispatcher(this);
\r
8811 this.classPrefix = '';
\r
8812 this.scope = s.scope || this;
\r
8813 this.disabled = 0;
\r
8815 this.editor = editor;
\r
8818 setAriaProperty : function(property, value) {
\r
8819 var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
\r
8821 DOM.setAttrib(element, 'aria-' + property, !!value);
\r
8825 focus : function() {
\r
8826 DOM.get(this.id).focus();
\r
8829 setDisabled : function(s) {
\r
8830 if (s != this.disabled) {
\r
8831 this.setAriaProperty('disabled', s);
\r
8833 this.setState('Disabled', s);
\r
8834 this.setState('Enabled', !s);
\r
8835 this.disabled = s;
\r
8839 isDisabled : function() {
\r
8840 return this.disabled;
\r
8843 setActive : function(s) {
\r
8844 if (s != this.active) {
\r
8845 this.setState('Active', s);
\r
8847 this.setAriaProperty('pressed', s);
\r
8851 isActive : function() {
\r
8852 return this.active;
\r
8855 setState : function(c, s) {
\r
8856 var n = DOM.get(this.id);
\r
8858 c = this.classPrefix + c;
\r
8861 DOM.addClass(n, c);
\r
8863 DOM.removeClass(n, c);
\r
8866 isRendered : function() {
\r
8867 return this.rendered;
\r
8870 renderHTML : function() {
\r
8873 renderTo : function(n) {
\r
8874 DOM.setHTML(n, this.renderHTML());
\r
8877 postRender : function() {
\r
8880 // Set pending states
\r
8881 if (is(t.disabled)) {
\r
8887 if (is(t.active)) {
\r
8894 remove : function() {
\r
8895 DOM.remove(this.id);
\r
8899 destroy : function() {
\r
8900 tinymce.dom.Event.clear(this.id);
\r
8904 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
\r
8905 Container : function(id, s, editor) {
\r
8906 this.parent(id, s, editor);
\r
8908 this.controls = [];
\r
8913 add : function(c) {
\r
8914 this.lookup[c.id] = c;
\r
8915 this.controls.push(c);
\r
8920 get : function(n) {
\r
8921 return this.lookup[n];
\r
8926 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
\r
8927 Separator : function(id, s) {
\r
8928 this.parent(id, s);
\r
8929 this.classPrefix = 'mceSeparator';
\r
8930 this.setDisabled(true);
\r
8933 renderHTML : function() {
\r
8934 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
\r
8938 (function(tinymce) {
\r
8939 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
8941 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
\r
8942 MenuItem : function(id, s) {
\r
8943 this.parent(id, s);
\r
8944 this.classPrefix = 'mceMenuItem';
\r
8947 setSelected : function(s) {
\r
8948 this.setState('Selected', s);
\r
8949 this.setAriaProperty('checked', !!s);
\r
8950 this.selected = s;
\r
8953 isSelected : function() {
\r
8954 return this.selected;
\r
8957 postRender : function() {
\r
8962 // Set pending state
\r
8963 if (is(t.selected))
\r
8964 t.setSelected(t.selected);
\r
8969 (function(tinymce) {
\r
8970 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
8972 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
\r
8973 Menu : function(id, s) {
\r
8978 t.collapsed = false;
\r
8980 t.onAddItem = new tinymce.util.Dispatcher(this);
\r
8983 expand : function(d) {
\r
8987 walk(t, function(o) {
\r
8993 t.collapsed = false;
\r
8996 collapse : function(d) {
\r
9000 walk(t, function(o) {
\r
9006 t.collapsed = true;
\r
9009 isCollapsed : function() {
\r
9010 return this.collapsed;
\r
9013 add : function(o) {
\r
9015 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
\r
9017 this.onAddItem.dispatch(this, o);
\r
9019 return this.items[o.id] = o;
\r
9022 addSeparator : function() {
\r
9023 return this.add({separator : true});
\r
9026 addMenu : function(o) {
\r
9028 o = this.createMenu(o);
\r
9032 return this.add(o);
\r
9035 hasMenus : function() {
\r
9036 return this.menuCount !== 0;
\r
9039 remove : function(o) {
\r
9040 delete this.items[o.id];
\r
9043 removeAll : function() {
\r
9046 walk(t, function(o) {
\r
9058 createMenu : function(o) {
\r
9059 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
\r
9061 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
\r
9067 (function(tinymce) {
\r
9068 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
\r
9070 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
\r
9071 DropMenu : function(id, s) {
\r
9073 s.container = s.container || DOM.doc.body;
\r
9074 s.offset_x = s.offset_x || 0;
\r
9075 s.offset_y = s.offset_y || 0;
\r
9076 s.vp_offset_x = s.vp_offset_x || 0;
\r
9077 s.vp_offset_y = s.vp_offset_y || 0;
\r
9079 if (is(s.icons) && !s.icons)
\r
9080 s['class'] += ' mceNoIcons';
\r
9082 this.parent(id, s);
\r
9083 this.onShowMenu = new tinymce.util.Dispatcher(this);
\r
9084 this.onHideMenu = new tinymce.util.Dispatcher(this);
\r
9085 this.classPrefix = 'mceMenu';
\r
9088 createMenu : function(s) {
\r
9089 var t = this, cs = t.settings, m;
\r
9091 s.container = s.container || cs.container;
\r
9093 s.constrain = s.constrain || cs.constrain;
\r
9094 s['class'] = s['class'] || cs['class'];
\r
9095 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
\r
9096 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
\r
9097 s.keyboard_focus = cs.keyboard_focus;
\r
9098 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
\r
9100 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
\r
9105 focus : function() {
\r
9107 if (t.keyboardNav) {
\r
9108 t.keyboardNav.focus();
\r
9112 update : function() {
\r
9113 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
\r
9115 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
\r
9116 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
\r
9118 if (!DOM.boxModel)
\r
9119 t.element.setStyles({width : tw + 2, height : th + 2});
\r
9121 t.element.setStyles({width : tw, height : th});
\r
9124 DOM.setStyle(co, 'width', tw);
\r
9126 if (s.max_height) {
\r
9127 DOM.setStyle(co, 'height', th);
\r
9129 if (tb.clientHeight < s.max_height)
\r
9130 DOM.setStyle(co, 'overflow', 'hidden');
\r
9134 showMenu : function(x, y, px) {
\r
9135 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
\r
9139 if (t.isMenuVisible)
\r
9142 if (!t.rendered) {
\r
9143 co = DOM.add(t.settings.container, t.renderNode());
\r
9145 each(t.items, function(o) {
\r
9149 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
9151 co = DOM.get('menu_' + t.id);
\r
9153 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
\r
9154 if (!tinymce.isOpera)
\r
9155 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
\r
9160 x += s.offset_x || 0;
\r
9161 y += s.offset_y || 0;
\r
9165 // Move inside viewport if not submenu
\r
9166 if (s.constrain) {
\r
9167 w = co.clientWidth - ot;
\r
9168 h = co.clientHeight - ot;
\r
9172 if ((x + s.vp_offset_x + w) > mx)
\r
9173 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
\r
9175 if ((y + s.vp_offset_y + h) > my)
\r
9176 y = Math.max(0, (my - s.vp_offset_y) - h);
\r
9179 DOM.setStyles(co, {left : x , top : y});
\r
9180 t.element.update();
\r
9182 t.isMenuVisible = 1;
\r
9183 t.mouseClickFunc = Event.add(co, 'click', function(e) {
\r
9188 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
\r
9189 m = t.items[e.id];
\r
9191 if (m.isDisabled())
\r
9200 dm = dm.settings.parent;
\r
9203 if (m.settings.onclick)
\r
9204 m.settings.onclick(e);
\r
9206 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
9210 if (t.hasMenus()) {
\r
9211 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
\r
9215 if (e && (e = DOM.getParent(e, 'tr'))) {
\r
9216 m = t.items[e.id];
\r
9219 t.lastMenu.collapse(1);
\r
9221 if (m.isDisabled())
\r
9224 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
\r
9225 //p = DOM.getPos(s.container);
\r
9226 r = DOM.getRect(e);
\r
9227 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
\r
9229 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
\r
9235 Event.add(co, 'keydown', t._keyHandler, t);
\r
9237 t.onShowMenu.dispatch(t);
\r
9239 if (s.keyboard_focus) {
\r
9240 t._setupKeyboardNav();
\r
9244 hideMenu : function(c) {
\r
9245 var t = this, co = DOM.get('menu_' + t.id), e;
\r
9247 if (!t.isMenuVisible)
\r
9250 if (t.keyboardNav) t.keyboardNav.destroy();
\r
9251 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
9252 Event.remove(co, 'click', t.mouseClickFunc);
\r
9253 Event.remove(co, 'keydown', t._keyHandler);
\r
9255 t.isMenuVisible = 0;
\r
9263 if (e = DOM.get(t.id))
\r
9264 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
\r
9266 t.onHideMenu.dispatch(t);
\r
9269 add : function(o) {
\r
9274 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
\r
9275 t._add(DOM.select('tbody', co)[0], o);
\r
9280 collapse : function(d) {
\r
9285 remove : function(o) {
\r
9289 return this.parent(o);
\r
9292 destroy : function() {
\r
9293 var t = this, co = DOM.get('menu_' + t.id);
\r
9295 if (t.keyboardNav) t.keyboardNav.destroy();
\r
9296 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
9297 Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
\r
9298 Event.remove(co, 'click', t.mouseClickFunc);
\r
9299 Event.remove(co, 'keydown', t._keyHandler);
\r
9302 t.element.remove();
\r
9307 renderNode : function() {
\r
9308 var t = this, s = t.settings, n, tb, co, w;
\r
9310 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'});
\r
9311 if (t.settings.parent) {
\r
9312 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
\r
9314 co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
\r
9315 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
9318 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
\r
9320 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
\r
9321 n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
\r
9322 tb = DOM.add(n, 'tbody');
\r
9324 each(t.items, function(o) {
\r
9328 t.rendered = true;
\r
9333 // Internal functions
\r
9334 _setupKeyboardNav : function(){
\r
9335 var contextMenu, menuItems, t=this;
\r
9336 contextMenu = DOM.select('#menu_' + t.id)[0];
\r
9337 menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
\r
9338 menuItems.splice(0,0,contextMenu);
\r
9339 t.keyboardNav = new tinymce.ui.KeyboardNavigation({
\r
9340 root: 'menu_' + t.id,
\r
9342 onCancel: function() {
\r
9345 enableUpDown: true
\r
9347 contextMenu.focus();
\r
9350 _keyHandler : function(evt) {
\r
9352 switch (evt.keyCode) {
\r
9354 if (t.settings.parent) {
\r
9356 t.settings.parent.focus();
\r
9357 Event.cancel(evt);
\r
9361 if (t.mouseOverFunc)
\r
9362 t.mouseOverFunc(evt);
\r
9367 _add : function(tb, o) {
\r
9368 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
\r
9370 if (s.separator) {
\r
9371 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
\r
9372 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
\r
9374 if (n = ro.previousSibling)
\r
9375 DOM.addClass(n, 'mceLast');
\r
9380 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
\r
9381 n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
\r
9382 n = a = DOM.add(n, 'a', {id: o.id + '_aria', role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
\r
9385 DOM.setAttrib(a, 'aria-haspopup', 'true');
\r
9386 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
\r
9389 DOM.addClass(it, s['class']);
\r
9390 // n = DOM.add(n, 'span', {'class' : 'item'});
\r
9392 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
\r
9395 DOM.add(ic, 'img', {src : s.icon_src});
\r
9397 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
\r
9399 if (o.settings.style)
\r
9400 DOM.setAttrib(n, 'style', o.settings.style);
\r
9402 if (tb.childNodes.length == 1)
\r
9403 DOM.addClass(ro, 'mceFirst');
\r
9405 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
\r
9406 DOM.addClass(ro, 'mceFirst');
\r
9409 DOM.addClass(ro, cp + 'ItemSub');
\r
9411 if (n = ro.previousSibling)
\r
9412 DOM.removeClass(n, 'mceLast');
\r
9414 DOM.addClass(ro, 'mceLast');
\r
9418 (function(tinymce) {
\r
9419 var DOM = tinymce.DOM;
\r
9421 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
\r
9422 Button : function(id, s, ed) {
\r
9423 this.parent(id, s, ed);
\r
9424 this.classPrefix = 'mceButton';
\r
9427 renderHTML : function() {
\r
9428 var cp = this.classPrefix, s = this.settings, h, l;
\r
9430 l = DOM.encode(s.label || '');
\r
9431 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) + '">';
\r
9432 if (s.image && !(this.editor &&this.editor.forcedHighContrastMode) )
\r
9433 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
\r
9435 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
\r
9437 h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>';
\r
9442 postRender : function() {
\r
9443 var t = this, s = t.settings;
\r
9445 tinymce.dom.Event.add(t.id, 'click', function(e) {
\r
9446 if (!t.isDisabled())
\r
9447 return s.onclick.call(s.scope, e);
\r
9453 (function(tinymce) {
\r
9454 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
9456 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
\r
9457 ListBox : function(id, s, ed) {
\r
9460 t.parent(id, s, ed);
\r
9464 t.onChange = new Dispatcher(t);
\r
9466 t.onPostRender = new Dispatcher(t);
\r
9468 t.onAdd = new Dispatcher(t);
\r
9470 t.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
9472 t.classPrefix = 'mceListBox';
\r
9475 select : function(va) {
\r
9476 var t = this, fv, f;
\r
9478 if (va == undefined)
\r
9479 return t.selectByIndex(-1);
\r
9481 // Is string or number make function selector
\r
9482 if (va && va.call)
\r
9490 // Do we need to do something?
\r
9491 if (va != t.selectedValue) {
\r
9493 each(t.items, function(o, i) {
\r
9496 t.selectByIndex(i);
\r
9502 t.selectByIndex(-1);
\r
9506 selectByIndex : function(idx) {
\r
9507 var t = this, e, o;
\r
9509 if (idx != t.selectedIndex) {
\r
9510 e = DOM.get(t.id + '_text');
\r
9514 t.selectedValue = o.value;
\r
9515 t.selectedIndex = idx;
\r
9516 DOM.setHTML(e, DOM.encode(o.title));
\r
9517 DOM.removeClass(e, 'mceTitle');
\r
9518 DOM.setAttrib(t.id, 'aria-valuenow', o.title);
\r
9520 DOM.setHTML(e, DOM.encode(t.settings.title));
\r
9521 DOM.addClass(e, 'mceTitle');
\r
9522 t.selectedValue = t.selectedIndex = null;
\r
9523 DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
\r
9529 add : function(n, v, o) {
\r
9533 o = tinymce.extend(o, {
\r
9539 t.onAdd.dispatch(t, o);
\r
9542 getLength : function() {
\r
9543 return this.items.length;
\r
9546 renderHTML : function() {
\r
9547 var h = '', t = this, s = t.settings, cp = t.classPrefix;
\r
9549 h = '<span role="button" aria-haspopup="true" aria-labelledby="' + t.id +'_text" 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>';
\r
9550 h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title);
\r
9551 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>';
\r
9552 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>';
\r
9553 h += '</tr></tbody></table></span>';
\r
9558 showMenu : function() {
\r
9559 var t = this, p2, e = DOM.get(this.id), m;
\r
9561 if (t.isDisabled() || t.items.length == 0)
\r
9564 if (t.menu && t.menu.isMenuVisible)
\r
9565 return t.hideMenu();
\r
9567 if (!t.isMenuRendered) {
\r
9569 t.isMenuRendered = true;
\r
9572 p2 = DOM.getPos(e);
\r
9575 m.settings.offset_x = p2.x;
\r
9576 m.settings.offset_y = p2.y;
\r
9577 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
\r
9581 m.items[t.oldID].setSelected(0);
\r
9583 each(t.items, function(o) {
\r
9584 if (o.value === t.selectedValue) {
\r
9585 m.items[o.id].setSelected(1);
\r
9590 m.showMenu(0, e.clientHeight);
\r
9592 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
9593 DOM.addClass(t.id, t.classPrefix + 'Selected');
\r
9595 //DOM.get(t.id + '_text').focus();
\r
9598 hideMenu : function(e) {
\r
9601 if (t.menu && t.menu.isMenuVisible) {
\r
9602 DOM.removeClass(t.id, t.classPrefix + 'Selected');
\r
9604 // Prevent double toogles by canceling the mouse click event to the button
\r
9605 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
\r
9608 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
9609 DOM.removeClass(t.id, t.classPrefix + 'Selected');
\r
9610 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
9611 t.menu.hideMenu();
\r
9616 renderMenu : function() {
\r
9619 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
9621 'class' : t.classPrefix + 'Menu mceNoIcons',
\r
9626 m.onHideMenu.add(function() {
\r
9632 title : t.settings.title,
\r
9633 'class' : 'mceMenuItemTitle',
\r
9634 onclick : function() {
\r
9635 if (t.settings.onselect('') !== false)
\r
9636 t.select(''); // Must be runned after
\r
9640 each(t.items, function(o) {
\r
9641 // No value then treat it as a title
\r
9642 if (o.value === undefined) {
\r
9645 'class' : 'mceMenuItemTitle',
\r
9646 onclick : function() {
\r
9647 if (t.settings.onselect('') !== false)
\r
9648 t.select(''); // Must be runned after
\r
9652 o.id = DOM.uniqueId();
\r
9653 o.onclick = function() {
\r
9654 if (t.settings.onselect(o.value) !== false)
\r
9655 t.select(o.value); // Must be runned after
\r
9662 t.onRenderMenu.dispatch(t, m);
\r
9666 postRender : function() {
\r
9667 var t = this, cp = t.classPrefix;
\r
9669 Event.add(t.id, 'click', t.showMenu, t);
\r
9670 Event.add(t.id, 'keydown', function(evt) {
\r
9671 if (evt.keyCode == 32) { // Space
\r
9673 Event.cancel(evt);
\r
9676 Event.add(t.id, 'focus', function() {
\r
9677 if (!t._focused) {
\r
9678 t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
\r
9679 if (e.keyCode == 40) {
\r
9684 t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
\r
9686 if (e.keyCode == 13) {
\r
9687 // Fake select on enter
\r
9688 v = t.selectedValue;
\r
9689 t.selectedValue = null; // Needs to be null to fake change
\r
9691 t.settings.onselect(v);
\r
9698 Event.add(t.id, 'blur', function() {
\r
9699 Event.remove(t.id, 'keydown', t.keyDownHandler);
\r
9700 Event.remove(t.id, 'keypress', t.keyPressHandler);
\r
9704 // Old IE doesn't have hover on all elements
\r
9705 if (tinymce.isIE6 || !DOM.boxModel) {
\r
9706 Event.add(t.id, 'mouseover', function() {
\r
9707 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
9708 DOM.addClass(t.id, cp + 'Hover');
\r
9711 Event.add(t.id, 'mouseout', function() {
\r
9712 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
9713 DOM.removeClass(t.id, cp + 'Hover');
\r
9717 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
9720 destroy : function() {
\r
9723 Event.clear(this.id + '_text');
\r
9724 Event.clear(this.id + '_open');
\r
9728 (function(tinymce) {
\r
9729 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
9731 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
\r
9732 NativeListBox : function(id, s) {
\r
9733 this.parent(id, s);
\r
9734 this.classPrefix = 'mceNativeListBox';
\r
9737 setDisabled : function(s) {
\r
9738 DOM.get(this.id).disabled = s;
\r
9739 this.setAriaProperty('disabled', s);
\r
9742 isDisabled : function() {
\r
9743 return DOM.get(this.id).disabled;
\r
9746 select : function(va) {
\r
9747 var t = this, fv, f;
\r
9749 if (va == undefined)
\r
9750 return t.selectByIndex(-1);
\r
9752 // Is string or number make function selector
\r
9753 if (va && va.call)
\r
9761 // Do we need to do something?
\r
9762 if (va != t.selectedValue) {
\r
9764 each(t.items, function(o, i) {
\r
9767 t.selectByIndex(i);
\r
9773 t.selectByIndex(-1);
\r
9777 selectByIndex : function(idx) {
\r
9778 DOM.get(this.id).selectedIndex = idx + 1;
\r
9779 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
\r
9782 add : function(n, v, a) {
\r
9788 if (t.isRendered())
\r
9789 DOM.add(DOM.get(this.id), 'option', a, n);
\r
9798 t.onAdd.dispatch(t, o);
\r
9801 getLength : function() {
\r
9802 return this.items.length;
\r
9805 renderHTML : function() {
\r
9808 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
\r
9810 each(t.items, function(it) {
\r
9811 h += DOM.createHTML('option', {value : it.value}, it.title);
\r
9814 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
\r
9815 h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
\r
9819 postRender : function() {
\r
9820 var t = this, ch, changeListenerAdded = true;
\r
9822 t.rendered = true;
\r
9824 function onChange(e) {
\r
9825 var v = t.items[e.target.selectedIndex - 1];
\r
9827 if (v && (v = v.value)) {
\r
9828 t.onChange.dispatch(t, v);
\r
9830 if (t.settings.onselect)
\r
9831 t.settings.onselect(v);
\r
9835 Event.add(t.id, 'change', onChange);
\r
9837 // Accessibility keyhandler
\r
9838 Event.add(t.id, 'keydown', function(e) {
\r
9841 Event.remove(t.id, 'change', ch);
\r
9842 changeListenerAdded = false;
\r
9844 bf = Event.add(t.id, 'blur', function() {
\r
9845 if (changeListenerAdded) return;
\r
9846 changeListenerAdded = true;
\r
9847 Event.add(t.id, 'change', onChange);
\r
9848 Event.remove(t.id, 'blur', bf);
\r
9851 if (e.keyCode == 13 || e.keyCode == 32) {
\r
9853 return Event.cancel(e);
\r
9857 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
9861 (function(tinymce) {
\r
9862 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
9864 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
\r
9865 MenuButton : function(id, s, ed) {
\r
9866 this.parent(id, s, ed);
\r
9868 this.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
9870 s.menu_container = s.menu_container || DOM.doc.body;
\r
9873 showMenu : function() {
\r
9874 var t = this, p1, p2, e = DOM.get(t.id), m;
\r
9876 if (t.isDisabled())
\r
9879 if (!t.isMenuRendered) {
\r
9881 t.isMenuRendered = true;
\r
9884 if (t.isMenuVisible)
\r
9885 return t.hideMenu();
\r
9887 p1 = DOM.getPos(t.settings.menu_container);
\r
9888 p2 = DOM.getPos(e);
\r
9891 m.settings.offset_x = p2.x;
\r
9892 m.settings.offset_y = p2.y;
\r
9893 m.settings.vp_offset_x = p2.x;
\r
9894 m.settings.vp_offset_y = p2.y;
\r
9895 m.settings.keyboard_focus = t._focused;
\r
9896 m.showMenu(0, e.clientHeight);
\r
9898 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
9899 t.setState('Selected', 1);
\r
9901 t.isMenuVisible = 1;
\r
9904 renderMenu : function() {
\r
9907 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
9909 'class' : this.classPrefix + 'Menu',
\r
9910 icons : t.settings.icons
\r
9913 m.onHideMenu.add(function() {
\r
9918 t.onRenderMenu.dispatch(t, m);
\r
9922 hideMenu : function(e) {
\r
9925 // Prevent double toogles by canceling the mouse click event to the button
\r
9926 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
\r
9929 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
9930 t.setState('Selected', 0);
\r
9931 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
9933 t.menu.hideMenu();
\r
9936 t.isMenuVisible = 0;
\r
9939 postRender : function() {
\r
9940 var t = this, s = t.settings;
\r
9942 Event.add(t.id, 'click', function() {
\r
9943 if (!t.isDisabled()) {
\r
9945 s.onclick(t.value);
\r
9954 (function(tinymce) {
\r
9955 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
9957 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
\r
9958 SplitButton : function(id, s, ed) {
\r
9959 this.parent(id, s, ed);
\r
9960 this.classPrefix = 'mceSplitButton';
\r
9963 renderHTML : function() {
\r
9964 var h, t = this, s = t.settings, h1;
\r
9966 h = '<tbody><tr>';
\r
9969 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
\r
9971 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
\r
9973 h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
\r
9974 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>';
\r
9976 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
\r
9977 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>';
\r
9979 h += '</tr></tbody>';
\r
9980 h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0', 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
\r
9981 return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
\r
9984 postRender : function() {
\r
9985 var t = this, s = t.settings, activate;
\r
9988 activate = function(evt) {
\r
9989 if (!t.isDisabled()) {
\r
9990 s.onclick(t.value);
\r
9991 Event.cancel(evt);
\r
9994 Event.add(t.id + '_action', 'click', activate);
\r
9995 Event.add(t.id, ['click', 'keydown'], function(evt) {
\r
9996 var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
\r
9997 if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
\r
9999 Event.cancel(evt);
\r
10000 } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
\r
10002 Event.cancel(evt);
\r
10007 Event.add(t.id + '_open', 'click', function (evt) {
\r
10009 Event.cancel(evt);
\r
10011 Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
\r
10012 Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
\r
10014 // Old IE doesn't have hover on all elements
\r
10015 if (tinymce.isIE6 || !DOM.boxModel) {
\r
10016 Event.add(t.id, 'mouseover', function() {
\r
10017 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
10018 DOM.addClass(t.id, 'mceSplitButtonHover');
\r
10021 Event.add(t.id, 'mouseout', function() {
\r
10022 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
10023 DOM.removeClass(t.id, 'mceSplitButtonHover');
\r
10028 destroy : function() {
\r
10031 Event.clear(this.id + '_action');
\r
10032 Event.clear(this.id + '_open');
\r
10033 Event.clear(this.id);
\r
10038 (function(tinymce) {
\r
10039 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
\r
10041 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
\r
10042 ColorSplitButton : function(id, s, ed) {
\r
10045 t.parent(id, s, ed);
\r
10047 t.settings = s = tinymce.extend({
\r
10048 colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
\r
10050 default_color : '#888888'
\r
10053 t.onShowMenu = new tinymce.util.Dispatcher(t);
\r
10055 t.onHideMenu = new tinymce.util.Dispatcher(t);
\r
10057 t.value = s.default_color;
\r
10060 showMenu : function() {
\r
10061 var t = this, r, p, e, p2;
\r
10063 if (t.isDisabled())
\r
10066 if (!t.isMenuRendered) {
\r
10068 t.isMenuRendered = true;
\r
10071 if (t.isMenuVisible)
\r
10072 return t.hideMenu();
\r
10074 e = DOM.get(t.id);
\r
10075 DOM.show(t.id + '_menu');
\r
10076 DOM.addClass(e, 'mceSplitButtonSelected');
\r
10077 p2 = DOM.getPos(e);
\r
10078 DOM.setStyles(t.id + '_menu', {
\r
10080 top : p2.y + e.clientHeight,
\r
10085 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
10086 t.onShowMenu.dispatch(t);
\r
10088 if (t._focused) {
\r
10089 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
\r
10090 if (e.keyCode == 27)
\r
10094 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
\r
10097 t.isMenuVisible = 1;
\r
10100 hideMenu : function(e) {
\r
10103 if (t.isMenuVisible) {
\r
10104 // Prevent double toogles by canceling the mouse click event to the button
\r
10105 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
\r
10108 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
\r
10109 DOM.removeClass(t.id, 'mceSplitButtonSelected');
\r
10110 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
10111 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
\r
10112 DOM.hide(t.id + '_menu');
\r
10115 t.isMenuVisible = 0;
\r
10119 renderMenu : function() {
\r
10120 var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
\r
10122 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;'});
\r
10123 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
\r
10124 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
\r
10126 n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
\r
10127 tb = DOM.add(n, 'tbody');
\r
10129 // Generate color grid
\r
10131 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
\r
10132 c = c.replace(/^#/, '');
\r
10135 tr = DOM.add(tb, 'tr');
\r
10136 i = s.grid_width - 1;
\r
10139 n = DOM.add(tr, 'td');
\r
10140 n = DOM.add(n, 'a', {
\r
10142 href : 'javascript:;',
\r
10144 backgroundColor : '#' + c
\r
10146 'title': t.editor.getLang('colors.' + c, c),
\r
10147 'data-mce-color' : '#' + c
\r
10150 if (t.editor.forcedHighContrastMode) {
\r
10151 n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
\r
10152 if (n.getContext && (context = n.getContext("2d"))) {
\r
10153 context.fillStyle = '#' + c;
\r
10154 context.fillRect(0, 0, 16, 16);
\r
10156 // No point leaving a canvas element around if it's not supported for drawing on anyway.
\r
10162 if (s.more_colors_func) {
\r
10163 n = DOM.add(tb, 'tr');
\r
10164 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
\r
10165 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
\r
10167 Event.add(n, 'click', function(e) {
\r
10168 s.more_colors_func.call(s.more_colors_scope || this);
\r
10169 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
10173 DOM.addClass(m, 'mceColorSplitMenu');
\r
10175 new tinymce.ui.KeyboardNavigation({
\r
10176 root: t.id + '_menu',
\r
10177 items: DOM.select('a', t.id + '_menu'),
\r
10178 onCancel: function() {
\r
10184 // Prevent IE from scrolling and hindering click to occur #4019
\r
10185 Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
\r
10187 Event.add(t.id + '_menu', 'click', function(e) {
\r
10190 e = DOM.getParent(e.target, 'a', tb);
\r
10192 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
\r
10195 return Event.cancel(e); // Prevent IE auto save warning
\r
10201 setColor : function(c) {
\r
10202 this.displayColor(c);
\r
10204 this.settings.onselect(c);
\r
10207 displayColor : function(c) {
\r
10210 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
\r
10215 postRender : function() {
\r
10216 var t = this, id = t.id;
\r
10219 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
\r
10220 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
\r
10223 destroy : function() {
\r
10226 Event.clear(this.id + '_menu');
\r
10227 Event.clear(this.id + '_more');
\r
10228 DOM.remove(this.id + '_menu');
\r
10233 (function(tinymce) {
\r
10234 // Shorten class names
\r
10235 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
\r
10236 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
\r
10237 renderHTML : function() {
\r
10238 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
\r
10240 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
\r
10241 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
\r
10242 h.push("<span role='application'>");
\r
10243 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
\r
10244 each(controls, function(toolbar) {
\r
10245 h.push(toolbar.renderHTML());
\r
10247 h.push("</span>");
\r
10248 h.push('</div>');
\r
10250 return h.join('');
\r
10253 focus : function() {
\r
10254 this.keyNav.focus();
\r
10257 postRender : function() {
\r
10258 var t = this, items = [];
\r
10260 each(t.controls, function(toolbar) {
\r
10261 each (toolbar.controls, function(control) {
\r
10262 if (control.id) {
\r
10263 items.push(control);
\r
10268 t.keyNav = new tinymce.ui.KeyboardNavigation({
\r
10271 onCancel: function() {
\r
10272 t.editor.focus();
\r
10274 excludeFromTabOrder: !t.settings.tab_focus_toolbar
\r
10278 destroy : function() {
\r
10282 self.keyNav.destroy();
\r
10283 Event.clear(self.id);
\r
10288 (function(tinymce) {
\r
10289 // Shorten class names
\r
10290 var dom = tinymce.DOM, each = tinymce.each;
\r
10291 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
\r
10292 renderHTML : function() {
\r
10293 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
\r
10296 for (i=0; i<cl.length; i++) {
\r
10297 // Get current control, prev control, next control and if the control is a list box or not
\r
10302 // Add toolbar start
\r
10304 c = 'mceToolbarStart';
\r
10307 c += ' mceToolbarStartButton';
\r
10308 else if (co.SplitButton)
\r
10309 c += ' mceToolbarStartSplitButton';
\r
10310 else if (co.ListBox)
\r
10311 c += ' mceToolbarStartListBox';
\r
10313 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
10316 // Add toolbar end before list box and after the previous button
\r
10317 // This is to fix the o2k7 editor skins
\r
10318 if (pr && co.ListBox) {
\r
10319 if (pr.Button || pr.SplitButton)
\r
10320 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
10323 // Render control HTML
\r
10325 // IE 8 quick fix, needed to propertly generate a hit area for anchors
\r
10327 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
\r
10329 h += '<td>' + co.renderHTML() + '</td>';
\r
10331 // Add toolbar start after list box and before the next button
\r
10332 // This is to fix the o2k7 editor skins
\r
10333 if (nx && co.ListBox) {
\r
10334 if (nx.Button || nx.SplitButton)
\r
10335 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
10339 c = 'mceToolbarEnd';
\r
10342 c += ' mceToolbarEndButton';
\r
10343 else if (co.SplitButton)
\r
10344 c += ' mceToolbarEndSplitButton';
\r
10345 else if (co.ListBox)
\r
10346 c += ' mceToolbarEndListBox';
\r
10348 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
10350 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>');
\r
10355 (function(tinymce) {
\r
10356 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
\r
10358 tinymce.create('tinymce.AddOnManager', {
\r
10359 AddOnManager : function() {
\r
10364 self.lookup = {};
\r
10365 self.onAdd = new Dispatcher(self);
\r
10368 get : function(n) {
\r
10369 if (this.lookup[n]) {
\r
10370 return this.lookup[n].instance;
\r
10372 return undefined;
\r
10376 dependencies : function(n) {
\r
10378 if (this.lookup[n]) {
\r
10379 result = this.lookup[n].dependencies;
\r
10381 return result || [];
\r
10384 requireLangPack : function(n) {
\r
10385 var s = tinymce.settings;
\r
10387 if (s && s.language && s.language_load !== false)
\r
10388 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
\r
10391 add : function(id, o, dependencies) {
\r
10392 this.items.push(o);
\r
10393 this.lookup[id] = {instance:o, dependencies:dependencies};
\r
10394 this.onAdd.dispatch(this, id, o);
\r
10398 createUrl: function(baseUrl, dep) {
\r
10399 if (typeof dep === "object") {
\r
10402 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
\r
10406 addComponents: function(pluginName, scripts) {
\r
10407 var pluginUrl = this.urls[pluginName];
\r
10408 tinymce.each(scripts, function(script){
\r
10409 tinymce.ScriptLoader.add(pluginUrl+"/"+script);
\r
10413 load : function(n, u, cb, s) {
\r
10414 var t = this, url = u;
\r
10416 function loadDependencies() {
\r
10417 var dependencies = t.dependencies(n);
\r
10418 tinymce.each(dependencies, function(dep) {
\r
10419 var newUrl = t.createUrl(u, dep);
\r
10420 t.load(newUrl.resource, newUrl, undefined, undefined);
\r
10426 cb.call(tinymce.ScriptLoader);
\r
10433 if (typeof u === "object")
\r
10434 url = u.prefix + u.resource + u.suffix;
\r
10436 if (url.indexOf('/') != 0 && url.indexOf('://') == -1)
\r
10437 url = tinymce.baseURL + '/' + url;
\r
10439 t.urls[n] = url.substring(0, url.lastIndexOf('/'));
\r
10441 if (t.lookup[n]) {
\r
10442 loadDependencies();
\r
10444 tinymce.ScriptLoader.add(url, loadDependencies, s);
\r
10449 // Create plugin and theme managers
\r
10450 tinymce.PluginManager = new tinymce.AddOnManager();
\r
10451 tinymce.ThemeManager = new tinymce.AddOnManager();
\r
10454 (function(tinymce) {
\r
10456 var each = tinymce.each, extend = tinymce.extend,
\r
10457 DOM = tinymce.DOM, Event = tinymce.dom.Event,
\r
10458 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
10459 explode = tinymce.explode,
\r
10460 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
\r
10462 // Setup some URLs where the editor API is located and where the document is
\r
10463 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
\r
10464 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
\r
10465 tinymce.documentBaseURL += '/';
\r
10467 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
\r
10469 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
\r
10471 // Add before unload listener
\r
10472 // This was required since IE was leaking memory if you added and removed beforeunload listeners
\r
10473 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
\r
10474 tinymce.onBeforeUnload = new Dispatcher(tinymce);
\r
10476 // Must be on window or IE will leak if the editor is placed in frame or iframe
\r
10477 Event.add(window, 'beforeunload', function(e) {
\r
10478 tinymce.onBeforeUnload.dispatch(tinymce, e);
\r
10481 tinymce.onAddEditor = new Dispatcher(tinymce);
\r
10483 tinymce.onRemoveEditor = new Dispatcher(tinymce);
\r
10485 tinymce.EditorManager = extend(tinymce, {
\r
10490 activeEditor : null,
\r
10492 init : function(s) {
\r
10493 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
\r
10495 function execCallback(se, n, s) {
\r
10501 if (tinymce.is(f, 'string')) {
\r
10502 s = f.replace(/\.\w+$/, '');
\r
10503 s = s ? tinymce.resolve(s) : 0;
\r
10504 f = tinymce.resolve(f);
\r
10507 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
\r
10511 theme : "simple",
\r
10518 Event.add(document, 'init', function() {
\r
10521 execCallback(s, 'onpageload');
\r
10523 switch (s.mode) {
\r
10525 l = s.elements || '';
\r
10527 if(l.length > 0) {
\r
10528 each(explode(l), function(v) {
\r
10529 if (DOM.get(v)) {
\r
10530 ed = new tinymce.Editor(v, s);
\r
10534 each(document.forms, function(f) {
\r
10535 each(f.elements, function(e) {
\r
10536 if (e.name === v) {
\r
10537 v = 'mce_editor_' + instanceCounter++;
\r
10538 DOM.setAttrib(e, 'id', v);
\r
10540 ed = new tinymce.Editor(v, s);
\r
10551 case "textareas":
\r
10552 case "specific_textareas":
\r
10553 function hasClass(n, c) {
\r
10554 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
\r
10557 each(DOM.select('textarea'), function(v) {
\r
10558 if (s.editor_deselector && hasClass(v, s.editor_deselector))
\r
10561 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
\r
10562 // Can we use the name
\r
10563 e = DOM.get(v.name);
\r
10567 // Generate unique name if missing or already exists
\r
10568 if (!v.id || t.get(v.id))
\r
10569 v.id = DOM.uniqueId();
\r
10571 ed = new tinymce.Editor(v.id, s);
\r
10579 // Call onInit when all editors are initialized
\r
10583 each(el, function(ed) {
\r
10586 if (!ed.initialized) {
\r
10588 ed.onInit.add(function() {
\r
10593 execCallback(s, 'oninit');
\r
10600 execCallback(s, 'oninit');
\r
10606 get : function(id) {
\r
10607 if (id === undefined)
\r
10608 return this.editors;
\r
10610 return this.editors[id];
\r
10613 getInstanceById : function(id) {
\r
10614 return this.get(id);
\r
10617 add : function(editor) {
\r
10618 var self = this, editors = self.editors;
\r
10620 // Add named and index editor instance
\r
10621 editors[editor.id] = editor;
\r
10622 editors.push(editor);
\r
10624 self._setActive(editor);
\r
10625 self.onAddEditor.dispatch(self, editor);
\r
10631 remove : function(editor) {
\r
10632 var t = this, i, editors = t.editors;
\r
10634 // Not in the collection
\r
10635 if (!editors[editor.id])
\r
10638 delete editors[editor.id];
\r
10640 for (i = 0; i < editors.length; i++) {
\r
10641 if (editors[i] == editor) {
\r
10642 editors.splice(i, 1);
\r
10647 // Select another editor since the active one was removed
\r
10648 if (t.activeEditor == editor)
\r
10649 t._setActive(editors[0]);
\r
10651 editor.destroy();
\r
10652 t.onRemoveEditor.dispatch(t, editor);
\r
10657 execCommand : function(c, u, v) {
\r
10658 var t = this, ed = t.get(v), w;
\r
10660 // Manager commands
\r
10666 case "mceAddEditor":
\r
10667 case "mceAddControl":
\r
10669 new tinymce.Editor(v, t.settings).render();
\r
10673 case "mceAddFrameControl":
\r
10676 // Add tinyMCE global instance and tinymce namespace to specified window
\r
10677 w.tinyMCE = tinyMCE;
\r
10678 w.tinymce = tinymce;
\r
10680 tinymce.DOM.doc = w.document;
\r
10681 tinymce.DOM.win = w;
\r
10683 ed = new tinymce.Editor(v.element_id, v);
\r
10686 // Fix IE memory leaks
\r
10687 if (tinymce.isIE) {
\r
10690 w.detachEvent('onunload', clr);
\r
10691 w = w.tinyMCE = w.tinymce = null; // IE leak
\r
10694 w.attachEvent('onunload', clr);
\r
10697 v.page_window = null;
\r
10701 case "mceRemoveEditor":
\r
10702 case "mceRemoveControl":
\r
10708 case 'mceToggleEditor':
\r
10710 t.execCommand('mceAddControl', 0, v);
\r
10714 if (ed.isHidden())
\r
10722 // Run command on active editor
\r
10723 if (t.activeEditor)
\r
10724 return t.activeEditor.execCommand(c, u, v);
\r
10729 execInstanceCommand : function(id, c, u, v) {
\r
10730 var ed = this.get(id);
\r
10733 return ed.execCommand(c, u, v);
\r
10738 triggerSave : function() {
\r
10739 each(this.editors, function(e) {
\r
10744 addI18n : function(p, o) {
\r
10745 var lo, i18n = this.i18n;
\r
10747 if (!tinymce.is(p, 'string')) {
\r
10748 each(p, function(o, lc) {
\r
10749 each(o, function(o, g) {
\r
10750 each(o, function(o, k) {
\r
10751 if (g === 'common')
\r
10752 i18n[lc + '.' + k] = o;
\r
10754 i18n[lc + '.' + g + '.' + k] = o;
\r
10759 each(o, function(o, k) {
\r
10760 i18n[p + '.' + k] = o;
\r
10765 // Private methods
\r
10767 _setActive : function(editor) {
\r
10768 this.selectedInstance = this.activeEditor = editor;
\r
10773 (function(tinymce) {
\r
10774 // Shorten these names
\r
10775 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
\r
10776 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
\r
10777 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
\r
10778 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
10779 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
\r
10781 tinymce.create('tinymce.Editor', {
\r
10782 Editor : function(id, s) {
\r
10785 t.id = t.editorId = id;
\r
10787 t.execCommands = {};
\r
10788 t.queryStateCommands = {};
\r
10789 t.queryValueCommands = {};
\r
10791 t.isNotDirty = false;
\r
10795 // Add events to the editor
\r
10799 'onBeforeRenderUI',
\r
10839 'onBeforeSetContent',
\r
10841 'onBeforeGetContent',
\r
10855 'onBeforeExecCommand',
\r
10865 'onSetProgressState'
\r
10867 t[e] = new Dispatcher(t);
\r
10870 t.settings = s = extend({
\r
10873 docs_language : 'en',
\r
10874 theme : 'simple',
\r
10875 skin : 'default',
\r
10877 delta_height : 0,
\r
10880 document_base_url : tinymce.documentBaseURL,
\r
10881 add_form_submit_trigger : 1,
\r
10882 submit_patch : 1,
\r
10883 add_unload_trigger : 1,
\r
10884 convert_urls : 1,
\r
10885 relative_urls : 1,
\r
10886 remove_script_host : 1,
\r
10887 table_inline_editing : 0,
\r
10888 object_resizing : 1,
\r
10890 accessibility_focus : 1,
\r
10891 custom_shortcuts : 1,
\r
10892 custom_undo_redo_keyboard_shortcuts : 1,
\r
10893 custom_undo_redo_restore_selection : 1,
\r
10894 custom_undo_redo : 1,
\r
10895 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
\r
10896 visual_table_class : 'mceItemTable',
\r
10898 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
\r
10899 apply_source_formatting : 1,
\r
10900 directionality : 'ltr',
\r
10901 forced_root_block : 'p',
\r
10902 hidden_input : 1,
\r
10903 padd_empty_editor : 1,
\r
10906 force_p_newlines : 1,
\r
10907 indentation : '30px',
\r
10909 fix_table_elements : 1,
\r
10910 inline_styles : 1,
\r
10911 convert_fonts_to_spans : true,
\r
10912 indent : 'simple',
\r
10913 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
\r
10914 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
\r
10916 entity_encoding : 'named',
\r
10917 url_converter : t.convertURL,
\r
10918 url_converter_scope : t,
\r
10919 ie7_compat : true
\r
10922 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
\r
10923 base_uri : tinyMCE.baseURI
\r
10926 t.baseURI = tinymce.baseURI;
\r
10928 t.contentCSS = [];
\r
10931 t.execCallback('setup', t);
\r
10934 render : function(nst) {
\r
10935 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
\r
10937 // Page is not loaded yet, wait for it
\r
10938 if (!Event.domLoaded) {
\r
10939 Event.add(document, 'init', function() {
\r
10945 tinyMCE.settings = s;
\r
10947 // Element not found, then skip initialization
\r
10948 if (!t.getElement())
\r
10951 // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff
\r
10952 // here since the browser says it has contentEditable support but there is no visible
\r
10953 // caret We will remove this check ones Apple implements full contentEditable support
\r
10954 if (tinymce.isIDevice && !tinymce.isIOS5)
\r
10957 // Add hidden input for non input elements inside form elements
\r
10958 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
\r
10959 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
\r
10961 if (tinymce.WindowManager)
\r
10962 t.windowManager = new tinymce.WindowManager(t);
\r
10964 if (s.encoding == 'xml') {
\r
10965 t.onGetContent.add(function(ed, o) {
\r
10967 o.content = DOM.encode(o.content);
\r
10971 if (s.add_form_submit_trigger) {
\r
10972 t.onSubmit.addToTop(function() {
\r
10973 if (t.initialized) {
\r
10975 t.isNotDirty = 1;
\r
10980 if (s.add_unload_trigger) {
\r
10981 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
\r
10982 if (t.initialized && !t.destroyed && !t.isHidden())
\r
10983 t.save({format : 'raw', no_events : true});
\r
10987 tinymce.addUnload(t.destroy, t);
\r
10989 if (s.submit_patch) {
\r
10990 t.onBeforeRenderUI.add(function() {
\r
10991 var n = t.getElement().form;
\r
10996 // Already patched
\r
10997 if (n._mceOldSubmit)
\r
11000 // Check page uses id="submit" or name="submit" for it's submit button
\r
11001 if (!n.submit.nodeType && !n.submit.length) {
\r
11002 t.formElement = n;
\r
11003 n._mceOldSubmit = n.submit;
\r
11004 n.submit = function() {
\r
11005 // Save all instances
\r
11006 tinymce.triggerSave();
\r
11007 t.isNotDirty = 1;
\r
11009 return t.formElement._mceOldSubmit(t.formElement);
\r
11018 function loadScripts() {
\r
11019 if (s.language && s.language_load !== false)
\r
11020 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
\r
11022 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
\r
11023 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
\r
11025 each(explode(s.plugins), function(p) {
\r
11026 if (p &&!PluginManager.urls[p]) {
\r
11027 if (p.charAt(0) == '-') {
\r
11028 p = p.substr(1, p.length);
\r
11029 var dependencies = PluginManager.dependencies(p);
\r
11030 each(dependencies, function(dep) {
\r
11031 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
\r
11032 var dep = PluginManager.createUrl(defaultSettings, dep);
\r
11033 PluginManager.load(dep.resource, dep);
\r
11037 // Skip safari plugin, since it is removed as of 3.3b1
\r
11038 if (p == 'safari') {
\r
11041 PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
\r
11046 // Init when que is loaded
\r
11047 sl.loadQueue(function() {
\r
11056 init : function() {
\r
11057 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
\r
11061 s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
\r
11064 s.theme = s.theme.replace(/-/, '');
\r
11065 o = ThemeManager.get(s.theme);
\r
11066 t.theme = new o();
\r
11068 if (t.theme.init && s.init_theme)
\r
11069 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
\r
11071 function initPlugin(p) {
\r
11072 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
\r
11073 if (c && tinymce.inArray(initializedPlugins,p) === -1) {
\r
11074 each(PluginManager.dependencies(p), function(dep){
\r
11077 po = new c(t, u);
\r
11079 t.plugins[p] = po;
\r
11083 initializedPlugins.push(p);
\r
11088 // Create all plugins
\r
11089 each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
\r
11091 // Setup popup CSS path(s)
\r
11092 if (s.popup_css !== false) {
\r
11094 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
\r
11096 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
\r
11099 if (s.popup_css_add)
\r
11100 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
\r
11102 t.controlManager = new tinymce.ControlManager(t);
\r
11104 if (s.custom_undo_redo) {
\r
11105 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
\r
11106 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
\r
11107 t.undoManager.beforeChange();
\r
11110 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
\r
11111 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
\r
11112 t.undoManager.add();
\r
11116 t.onExecCommand.add(function(ed, c) {
\r
11117 // Don't refresh the select lists until caret move
\r
11118 if (!/^(FontName|FontSize)$/.test(c))
\r
11122 // Remove ghost selections on images and tables in Gecko
\r
11124 function repaint(a, o) {
\r
11125 if (!o || !o.initial)
\r
11126 t.execCommand('mceRepaint');
\r
11129 t.onUndo.add(repaint);
\r
11130 t.onRedo.add(repaint);
\r
11131 t.onSetContent.add(repaint);
\r
11134 // Enables users to override the control factory
\r
11135 t.onBeforeRenderUI.dispatch(t, t.controlManager);
\r
11138 if (s.render_ui) {
\r
11139 w = s.width || e.style.width || e.offsetWidth;
\r
11140 h = s.height || e.style.height || e.offsetHeight;
\r
11141 t.orgDisplay = e.style.display;
\r
11142 re = /^[0-9\.]+(|px)$/i;
\r
11144 if (re.test('' + w))
\r
11145 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
\r
11147 if (re.test('' + h))
\r
11148 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
\r
11151 o = t.theme.renderUI({
\r
11155 deltaWidth : s.delta_width,
\r
11156 deltaHeight : s.delta_height
\r
11159 t.editorContainer = o.editorContainer;
\r
11163 // User specified a document.domain value
\r
11164 if (document.domain && location.hostname != document.domain)
\r
11165 tinymce.relaxedDomain = document.domain;
\r
11168 DOM.setStyles(o.sizeContainer || o.editorContainer, {
\r
11173 // Load specified content CSS last
\r
11174 if (s.content_css) {
\r
11175 tinymce.each(explode(s.content_css), function(u) {
\r
11176 t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
\r
11180 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
\r
11184 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
\r
11186 // We only need to override paths if we have to
\r
11187 // IE has a bug where it remove site absolute urls to relative ones if this is specified
\r
11188 if (s.document_base_url != tinymce.documentBaseURL)
\r
11189 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
\r
11191 // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
\r
11192 if (s.ie7_compat)
\r
11193 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
\r
11195 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
\r
11197 t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
\r
11199 // Firefox 2 doesn't load stylesheets correctly this way
\r
11200 if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
\r
11201 for (i = 0; i < t.contentCSS.length; i++)
\r
11202 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
\r
11204 t.contentCSS = [];
\r
11207 bi = s.body_id || 'tinymce';
\r
11208 if (bi.indexOf('=') != -1) {
\r
11209 bi = t.getParam('body_id', '', 'hash');
\r
11210 bi = bi[t.id] || bi;
\r
11213 bc = s.body_class || '';
\r
11214 if (bc.indexOf('=') != -1) {
\r
11215 bc = t.getParam('body_class', '', 'hash');
\r
11216 bc = bc[t.id] || '';
\r
11219 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
\r
11221 // Domain relaxing enabled, then set document domain
\r
11222 if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
\r
11223 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
\r
11224 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
\r
11228 // TODO: ACC add the appropriate description on this.
\r
11229 n = DOM.add(o.iframeContainer, 'iframe', {
\r
11230 id : t.id + "_ifr",
\r
11231 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
\r
11232 frameBorder : '0',
\r
11233 allowTransparency : "true",
\r
11234 title : s.aria_label,
\r
11241 t.contentAreaContainer = o.iframeContainer;
\r
11242 DOM.get(o.editorContainer).style.display = t.orgDisplay;
\r
11243 DOM.get(t.id).style.display = 'none';
\r
11244 DOM.setAttrib(t.id, 'aria-hidden', true);
\r
11246 if (!tinymce.relaxedDomain || !u)
\r
11249 e = n = o = null; // Cleanup
\r
11252 setupIframe : function(filled) {
\r
11253 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
\r
11255 // Setup iframe body
\r
11256 if ((!isIE || !tinymce.relaxedDomain) && !filled) {
\r
11257 // We need to wait for the load event on Gecko
\r
11258 if (isGecko && !s.readonly) {
\r
11259 t.getWin().onload = function() {
\r
11260 window.setTimeout(function() {
\r
11261 var b = t.getBody(), undef;
\r
11263 // Editable element needs to have some contents or backspace/delete won't work properly for some odd reason on FF 3.6 or older
\r
11264 b.innerHTML = '<br>';
\r
11266 // Check if Gecko supports contentEditable mode FF2 doesn't
\r
11267 if (b.contentEditable !== undef) {
\r
11268 // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
\r
11269 b.contentEditable = false;
\r
11270 b.contentEditable = true;
\r
11272 // Caret doesn't get rendered when you mousedown on the HTML element on FF 3.x
\r
11273 t.onMouseDown.add(function(ed, e) {
\r
11274 if (e.target.nodeName === "HTML") {
\r
11275 d.designMode = 'on'; // Render the caret
\r
11277 // Remove design mode again after a while so it has some time to execute
\r
11278 window.setTimeout(function() {
\r
11279 d.designMode = 'off';
\r
11280 t.getBody().focus();
\r
11285 d.designMode = 'on';
\r
11287 // Call setup frame once the contentEditable/designMode has been initialized
\r
11288 // since the caret won't be rendered some times otherwise.
\r
11289 t.setupIframe(true);
\r
11295 d.write(t.iframeHTML);
\r
11298 if (tinymce.relaxedDomain)
\r
11299 d.domain = tinymce.relaxedDomain;
\r
11301 // Wait for iframe onload event on Gecko
\r
11302 if (isGecko && !s.readonly)
\r
11306 // It will not steal focus while setting contentEditable
\r
11308 b.disabled = true;
\r
11310 if (!isGecko && !s.readonly)
\r
11311 b.contentEditable = true;
\r
11313 b.disabled = false;
\r
11315 t.schema = new tinymce.html.Schema(s);
\r
11317 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
\r
11318 keep_values : true,
\r
11319 url_converter : t.convertURL,
\r
11320 url_converter_scope : t,
\r
11321 hex_colors : s.force_hex_style_colors,
\r
11322 class_filter : s.class_filter,
\r
11323 update_styles : 1,
\r
11324 fix_ie_paragraphs : 1,
\r
11325 schema : t.schema
\r
11328 t.parser = new tinymce.html.DomParser(s, t.schema);
\r
11330 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
\r
11331 if (!t.settings.allow_html_in_named_anchor) {
\r
11332 t.parser.addAttributeFilter('name', function(nodes, name) {
\r
11333 var i = nodes.length, sibling, prevSibling, parent, node;
\r
11337 if (node.name === 'a' && node.firstChild) {
\r
11338 parent = node.parent;
\r
11340 // Move children after current node
\r
11341 sibling = node.lastChild;
\r
11343 prevSibling = sibling.prev;
\r
11344 parent.insert(sibling, node);
\r
11345 sibling = prevSibling;
\r
11346 } while (sibling);
\r
11352 // Convert src and href into data-mce-src, data-mce-href and data-mce-style
\r
11353 t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
\r
11354 var i = nodes.length, node, dom = t.dom, value, internalName;
\r
11358 value = node.attr(name);
\r
11359 internalName = 'data-mce-' + name;
\r
11361 // Add internal attribute if we need to we don't on a refresh of the document
\r
11362 if (!node.attributes.map[internalName]) {
\r
11363 if (name === "style")
\r
11364 node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
\r
11366 node.attr(internalName, t.convertURL(value, name, node.name));
\r
11371 // Keep scripts from executing
\r
11372 t.parser.addNodeFilter('script', function(nodes, name) {
\r
11373 var i = nodes.length;
\r
11376 nodes[i].attr('type', 'mce-text/javascript');
\r
11379 t.parser.addNodeFilter('#cdata', function(nodes, name) {
\r
11380 var i = nodes.length, node;
\r
11385 node.name = '#comment';
\r
11386 node.value = '[CDATA[' + node.value + ']]';
\r
11390 t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
\r
11391 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
\r
11396 if (node.isEmpty(nonEmptyElements))
\r
11397 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
\r
11401 t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
\r
11403 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
\r
11405 t.formatter = new tinymce.Formatter(this);
\r
11407 // Register default formats
\r
11408 t.formatter.register({
\r
11410 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
\r
11411 {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
\r
11415 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
\r
11416 {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
\r
11417 {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
\r
11421 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
\r
11422 {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
\r
11426 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
\r
11430 {inline : 'strong', remove : 'all'},
\r
11431 {inline : 'span', styles : {fontWeight : 'bold'}},
\r
11432 {inline : 'b', remove : 'all'}
\r
11436 {inline : 'em', remove : 'all'},
\r
11437 {inline : 'span', styles : {fontStyle : 'italic'}},
\r
11438 {inline : 'i', remove : 'all'}
\r
11442 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
\r
11443 {inline : 'u', remove : 'all'}
\r
11446 strikethrough : [
\r
11447 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
\r
11448 {inline : 'strike', remove : 'all'}
\r
11451 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
\r
11452 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
\r
11453 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
\r
11454 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
\r
11455 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
\r
11456 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
\r
11457 subscript : {inline : 'sub'},
\r
11458 superscript : {inline : 'sup'},
\r
11461 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
\r
11462 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
\r
11463 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
\r
11467 // Register default block formats
\r
11468 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
\r
11469 t.formatter.register(name, {block : name, remove : 'all'});
\r
11472 // Register user defined formats
\r
11473 t.formatter.register(t.settings.formats);
\r
11475 t.undoManager = new tinymce.UndoManager(t);
\r
11478 t.undoManager.onAdd.add(function(um, l) {
\r
11479 if (um.hasUndo())
\r
11480 return t.onChange.dispatch(t, l, um);
\r
11483 t.undoManager.onUndo.add(function(um, l) {
\r
11484 return t.onUndo.dispatch(t, l, um);
\r
11487 t.undoManager.onRedo.add(function(um, l) {
\r
11488 return t.onRedo.dispatch(t, l, um);
\r
11491 t.forceBlocks = new tinymce.ForceBlocks(t, {
\r
11492 forced_root_block : s.forced_root_block
\r
11495 t.editorCommands = new tinymce.EditorCommands(t);
\r
11498 t.serializer.onPreProcess.add(function(se, o) {
\r
11499 return t.onPreProcess.dispatch(t, o, se);
\r
11502 t.serializer.onPostProcess.add(function(se, o) {
\r
11503 return t.onPostProcess.dispatch(t, o, se);
\r
11506 t.onPreInit.dispatch(t);
\r
11508 if (!s.gecko_spellcheck)
\r
11509 t.getBody().spellcheck = 0;
\r
11514 t.controlManager.onPostRender.dispatch(t, t.controlManager);
\r
11515 t.onPostRender.dispatch(t);
\r
11517 if (s.directionality)
\r
11518 t.getBody().dir = s.directionality;
\r
11521 t.getBody().style.whiteSpace = "nowrap";
\r
11523 if (s.handle_node_change_callback) {
\r
11524 t.onNodeChange.add(function(ed, cm, n) {
\r
11525 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
\r
11529 if (s.save_callback) {
\r
11530 t.onSaveContent.add(function(ed, o) {
\r
11531 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
11538 if (s.onchange_callback) {
\r
11539 t.onChange.add(function(ed, l) {
\r
11540 t.execCallback('onchange_callback', t, l);
\r
11545 t.onBeforeSetContent.add(function(ed, o) {
\r
11547 each(s.protect, function(pattern) {
\r
11548 o.content = o.content.replace(pattern, function(str) {
\r
11549 return '<!--mce:protected ' + escape(str) + '-->';
\r
11556 if (s.convert_newlines_to_brs) {
\r
11557 t.onBeforeSetContent.add(function(ed, o) {
\r
11559 o.content = o.content.replace(/\r?\n/g, '<br />');
\r
11563 if (s.preformatted) {
\r
11564 t.onPostProcess.add(function(ed, o) {
\r
11565 o.content = o.content.replace(/^\s*<pre.*?>/, '');
\r
11566 o.content = o.content.replace(/<\/pre>\s*$/, '');
\r
11569 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
\r
11573 if (s.verify_css_classes) {
\r
11574 t.serializer.attribValueFilter = function(n, v) {
\r
11577 if (n == 'class') {
\r
11578 // Build regexp for classes
\r
11579 if (!t.classesRE) {
\r
11580 cl = t.dom.getClasses();
\r
11582 if (cl.length > 0) {
\r
11585 each (cl, function(o) {
\r
11586 s += (s ? '|' : '') + o['class'];
\r
11589 t.classesRE = new RegExp('(' + s + ')', 'gi');
\r
11593 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
\r
11600 if (s.cleanup_callback) {
\r
11601 t.onBeforeSetContent.add(function(ed, o) {
\r
11602 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
11605 t.onPreProcess.add(function(ed, o) {
\r
11607 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
\r
11610 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
\r
11613 t.onPostProcess.add(function(ed, o) {
\r
11615 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
11618 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
\r
11622 if (s.save_callback) {
\r
11623 t.onGetContent.add(function(ed, o) {
\r
11625 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
11629 if (s.handle_event_callback) {
\r
11630 t.onEvent.add(function(ed, e, o) {
\r
11631 if (t.execCallback('handle_event_callback', e, ed, o) === false)
\r
11636 // Add visual aids when new contents is added
\r
11637 t.onSetContent.add(function() {
\r
11638 t.addVisual(t.getBody());
\r
11641 // Remove empty contents
\r
11642 if (s.padd_empty_editor) {
\r
11643 t.onPostProcess.add(function(ed, o) {
\r
11644 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
\r
11649 // Fix gecko link bug, when a link is placed at the end of block elements there is
\r
11650 // no way to move the caret behind the link. This fix adds a bogus br element after the link
\r
11651 function fixLinks(ed, o) {
\r
11652 each(ed.dom.select('a'), function(n) {
\r
11653 var pn = n.parentNode;
\r
11655 if (ed.dom.isBlock(pn) && pn.lastChild === n)
\r
11656 ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
\r
11660 t.onExecCommand.add(function(ed, cmd) {
\r
11661 if (cmd === 'CreateLink')
\r
11665 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
\r
11668 t.load({initial : true, format : 'html'});
\r
11669 t.startContent = t.getContent({format : 'raw'});
\r
11670 t.undoManager.add();
\r
11671 t.initialized = true;
\r
11673 t.onInit.dispatch(t);
\r
11674 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
\r
11675 t.execCallback('init_instance_callback', t);
\r
11677 t.nodeChanged({initial : 1});
\r
11679 // Load specified content CSS last
\r
11680 each(t.contentCSS, function(u) {
\r
11681 t.dom.loadCSS(u);
\r
11684 // Handle auto focus
\r
11685 if (s.auto_focus) {
\r
11686 setTimeout(function () {
\r
11687 var ed = tinymce.get(s.auto_focus);
\r
11689 ed.selection.select(ed.getBody(), 1);
\r
11690 ed.selection.collapse(1);
\r
11691 ed.getBody().focus();
\r
11692 ed.getWin().focus();
\r
11700 focus : function(sf) {
\r
11701 var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
\r
11704 // Get selected control element
\r
11705 ieRng = t.selection.getRng();
\r
11706 if (ieRng.item) {
\r
11707 controlElm = ieRng.item(0);
\r
11710 // Is not content editable
\r
11712 t.getWin().focus();
\r
11714 // Restore selected control element
\r
11715 // This is needed when for example an image is selected within a
\r
11716 // layer a call to focus will then remove the control selection
\r
11717 if (controlElm && controlElm.ownerDocument == doc) {
\r
11718 ieRng = doc.body.createControlRange();
\r
11719 ieRng.addElement(controlElm);
\r
11725 if (tinymce.activeEditor != t) {
\r
11726 if ((oed = tinymce.activeEditor) != null)
\r
11727 oed.onDeactivate.dispatch(oed, t);
\r
11729 t.onActivate.dispatch(t, oed);
\r
11732 tinymce._setActive(t);
\r
11735 execCallback : function(n) {
\r
11736 var t = this, f = t.settings[n], s;
\r
11741 // Look through lookup
\r
11742 if (t.callbackLookup && (s = t.callbackLookup[n])) {
\r
11747 if (is(f, 'string')) {
\r
11748 s = f.replace(/\.\w+$/, '');
\r
11749 s = s ? tinymce.resolve(s) : 0;
\r
11750 f = tinymce.resolve(f);
\r
11751 t.callbackLookup = t.callbackLookup || {};
\r
11752 t.callbackLookup[n] = {func : f, scope : s};
\r
11755 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
\r
11758 translate : function(s) {
\r
11759 var c = this.settings.language || 'en', i18n = tinymce.i18n;
\r
11764 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
\r
11765 return i18n[c + '.' + b] || '{#' + b + '}';
\r
11769 getLang : function(n, dv) {
\r
11770 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
\r
11773 getParam : function(n, dv, ty) {
\r
11774 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
\r
11776 if (ty === 'hash') {
\r
11779 if (is(v, 'string')) {
\r
11780 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
\r
11781 v = v.split('=');
\r
11783 if (v.length > 1)
\r
11784 o[tr(v[0])] = tr(v[1]);
\r
11786 o[tr(v[0])] = tr(v);
\r
11797 nodeChanged : function(o) {
\r
11798 var t = this, s = t.selection, n = s.getStart() || t.getBody();
\r
11800 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
\r
11801 if (t.initialized) {
\r
11803 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
\r
11805 // Get parents and add them to object
\r
11807 t.dom.getParent(n, function(node) {
\r
11808 if (node.nodeName == 'BODY')
\r
11811 o.parents.push(node);
\r
11814 t.onNodeChange.dispatch(
\r
11816 o ? o.controlManager || t.controlManager : t.controlManager,
\r
11824 addButton : function(n, s) {
\r
11827 t.buttons = t.buttons || {};
\r
11828 t.buttons[n] = s;
\r
11831 addCommand : function(name, callback, scope) {
\r
11832 this.execCommands[name] = {func : callback, scope : scope || this};
\r
11835 addQueryStateHandler : function(name, callback, scope) {
\r
11836 this.queryStateCommands[name] = {func : callback, scope : scope || this};
\r
11839 addQueryValueHandler : function(name, callback, scope) {
\r
11840 this.queryValueCommands[name] = {func : callback, scope : scope || this};
\r
11843 addShortcut : function(pa, desc, cmd_func, sc) {
\r
11846 if (!t.settings.custom_shortcuts)
\r
11849 t.shortcuts = t.shortcuts || {};
\r
11851 if (is(cmd_func, 'string')) {
\r
11854 cmd_func = function() {
\r
11855 t.execCommand(c, false, null);
\r
11859 if (is(cmd_func, 'object')) {
\r
11862 cmd_func = function() {
\r
11863 t.execCommand(c[0], c[1], c[2]);
\r
11867 each(explode(pa), function(pa) {
\r
11870 scope : sc || this,
\r
11877 each(explode(pa, '+'), function(v) {
\r
11886 o.charCode = v.charCodeAt(0);
\r
11887 o.keyCode = v.toUpperCase().charCodeAt(0);
\r
11891 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
\r
11897 execCommand : function(cmd, ui, val, a) {
\r
11898 var t = this, s = 0, o, st;
\r
11900 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
\r
11904 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
\r
11908 // Command callback
\r
11909 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
\r
11910 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
11914 // Registred commands
\r
11915 if (o = t.execCommands[cmd]) {
\r
11916 st = o.func.call(o.scope, ui, val);
\r
11918 // Fall through on true
\r
11919 if (st !== true) {
\r
11920 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
11925 // Plugin commands
\r
11926 each(t.plugins, function(p) {
\r
11927 if (p.execCommand && p.execCommand(cmd, ui, val)) {
\r
11928 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
11937 // Theme commands
\r
11938 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
\r
11939 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
11943 // Editor commands
\r
11944 if (t.editorCommands.execCommand(cmd, ui, val)) {
\r
11945 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
11949 // Browser commands
\r
11950 t.getDoc().execCommand(cmd, ui, val);
\r
11951 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
11954 queryCommandState : function(cmd) {
\r
11955 var t = this, o, s;
\r
11957 // Is hidden then return undefined
\r
11958 if (t._isHidden())
\r
11961 // Registred commands
\r
11962 if (o = t.queryStateCommands[cmd]) {
\r
11963 s = o.func.call(o.scope);
\r
11965 // Fall though on true
\r
11970 // Registred commands
\r
11971 o = t.editorCommands.queryCommandState(cmd);
\r
11975 // Browser commands
\r
11977 return this.getDoc().queryCommandState(cmd);
\r
11979 // Fails sometimes see bug: 1896577
\r
11983 queryCommandValue : function(c) {
\r
11984 var t = this, o, s;
\r
11986 // Is hidden then return undefined
\r
11987 if (t._isHidden())
\r
11990 // Registred commands
\r
11991 if (o = t.queryValueCommands[c]) {
\r
11992 s = o.func.call(o.scope);
\r
11994 // Fall though on true
\r
11999 // Registred commands
\r
12000 o = t.editorCommands.queryCommandValue(c);
\r
12004 // Browser commands
\r
12006 return this.getDoc().queryCommandValue(c);
\r
12008 // Fails sometimes see bug: 1896577
\r
12012 show : function() {
\r
12015 DOM.show(t.getContainer());
\r
12020 hide : function() {
\r
12021 var t = this, d = t.getDoc();
\r
12023 // Fixed bug where IE has a blinking cursor left from the editor
\r
12025 d.execCommand('SelectAll');
\r
12027 // We must save before we hide so Safari doesn't crash
\r
12029 DOM.hide(t.getContainer());
\r
12030 DOM.setStyle(t.id, 'display', t.orgDisplay);
\r
12033 isHidden : function() {
\r
12034 return !DOM.isHidden(this.id);
\r
12037 setProgressState : function(b, ti, o) {
\r
12038 this.onSetProgressState.dispatch(this, b, ti, o);
\r
12043 load : function(o) {
\r
12044 var t = this, e = t.getElement(), h;
\r
12050 // Double encode existing entities in the value
\r
12051 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
\r
12054 if (!o.no_events)
\r
12055 t.onLoadContent.dispatch(t, o);
\r
12057 o.element = e = null;
\r
12063 save : function(o) {
\r
12064 var t = this, e = t.getElement(), h, f;
\r
12066 if (!e || !t.initialized)
\r
12072 // Add undo level will trigger onchange event
\r
12073 if (!o.no_events) {
\r
12074 t.undoManager.typing = false;
\r
12075 t.undoManager.add();
\r
12079 h = o.content = t.getContent(o);
\r
12081 if (!o.no_events)
\r
12082 t.onSaveContent.dispatch(t, o);
\r
12086 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
\r
12089 // Update hidden form element
\r
12090 if (f = DOM.getParent(t.id, 'form')) {
\r
12091 each(f.elements, function(e) {
\r
12092 if (e.name == t.id) {
\r
12101 o.element = e = null;
\r
12106 setContent : function(content, args) {
\r
12107 var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
\r
12109 // Setup args object
\r
12110 args = args || {};
\r
12111 args.format = args.format || 'html';
\r
12113 args.content = content;
\r
12115 // Do preprocessing
\r
12116 if (!args.no_events)
\r
12117 self.onBeforeSetContent.dispatch(self, args);
\r
12119 content = args.content;
\r
12121 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
\r
12122 // It will also be impossible to place the caret in the editor unless there is a BR element present
\r
12123 if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
\r
12124 forcedRootBlockName = self.settings.forced_root_block;
\r
12125 if (forcedRootBlockName)
\r
12126 content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
\r
12128 content = '<br data-mce-bogus="1">';
\r
12130 body.innerHTML = content;
\r
12131 self.selection.select(body, true);
\r
12132 self.selection.collapse(true);
\r
12136 // Parse and serialize the html
\r
12137 if (args.format !== 'raw') {
\r
12138 content = new tinymce.html.Serializer({}, self.schema).serialize(
\r
12139 self.parser.parse(content)
\r
12143 // Set the new cleaned contents to the editor
\r
12144 args.content = tinymce.trim(content);
\r
12145 self.dom.setHTML(body, args.content);
\r
12147 // Do post processing
\r
12148 if (!args.no_events)
\r
12149 self.onSetContent.dispatch(self, args);
\r
12151 return args.content;
\r
12154 getContent : function(args) {
\r
12155 var self = this, content;
\r
12157 // Setup args object
\r
12158 args = args || {};
\r
12159 args.format = args.format || 'html';
\r
12162 // Do preprocessing
\r
12163 if (!args.no_events)
\r
12164 self.onBeforeGetContent.dispatch(self, args);
\r
12166 // Get raw contents or by default the cleaned contents
\r
12167 if (args.format == 'raw')
\r
12168 content = self.getBody().innerHTML;
\r
12170 content = self.serializer.serialize(self.getBody(), args);
\r
12172 args.content = tinymce.trim(content);
\r
12174 // Do post processing
\r
12175 if (!args.no_events)
\r
12176 self.onGetContent.dispatch(self, args);
\r
12178 return args.content;
\r
12181 isDirty : function() {
\r
12184 return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
\r
12187 getContainer : function() {
\r
12190 if (!t.container)
\r
12191 t.container = DOM.get(t.editorContainer || t.id + '_parent');
\r
12193 return t.container;
\r
12196 getContentAreaContainer : function() {
\r
12197 return this.contentAreaContainer;
\r
12200 getElement : function() {
\r
12201 return DOM.get(this.settings.content_element || this.id);
\r
12204 getWin : function() {
\r
12207 if (!t.contentWindow) {
\r
12208 e = DOM.get(t.id + "_ifr");
\r
12211 t.contentWindow = e.contentWindow;
\r
12214 return t.contentWindow;
\r
12217 getDoc : function() {
\r
12220 if (!t.contentDocument) {
\r
12224 t.contentDocument = w.document;
\r
12227 return t.contentDocument;
\r
12230 getBody : function() {
\r
12231 return this.bodyElement || this.getDoc().body;
\r
12234 convertURL : function(u, n, e) {
\r
12235 var t = this, s = t.settings;
\r
12237 // Use callback instead
\r
12238 if (s.urlconverter_callback)
\r
12239 return t.execCallback('urlconverter_callback', u, e, true, n);
\r
12241 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
\r
12242 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
\r
12245 // Convert to relative
\r
12246 if (s.relative_urls)
\r
12247 return t.documentBaseURI.toRelative(u);
\r
12249 // Convert to absolute
\r
12250 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
\r
12255 addVisual : function(e) {
\r
12256 var t = this, s = t.settings;
\r
12258 e = e || t.getBody();
\r
12260 if (!is(t.hasVisual))
\r
12261 t.hasVisual = s.visual;
\r
12263 each(t.dom.select('table,a', e), function(e) {
\r
12266 switch (e.nodeName) {
\r
12268 v = t.dom.getAttrib(e, 'border');
\r
12270 if (!v || v == '0') {
\r
12272 t.dom.addClass(e, s.visual_table_class);
\r
12274 t.dom.removeClass(e, s.visual_table_class);
\r
12280 v = t.dom.getAttrib(e, 'name');
\r
12284 t.dom.addClass(e, 'mceItemAnchor');
\r
12286 t.dom.removeClass(e, 'mceItemAnchor');
\r
12293 t.onVisualAid.dispatch(t, e, t.hasVisual);
\r
12296 remove : function() {
\r
12297 var t = this, e = t.getContainer();
\r
12299 t.removed = 1; // Cancels post remove event execution
\r
12302 t.execCallback('remove_instance_callback', t);
\r
12303 t.onRemove.dispatch(t);
\r
12305 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
\r
12306 t.onExecCommand.listeners = [];
\r
12308 tinymce.remove(t);
\r
12312 destroy : function(s) {
\r
12315 // One time is enough
\r
12320 tinymce.removeUnload(t.destroy);
\r
12321 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
\r
12323 // Manual destroy
\r
12324 if (t.theme && t.theme.destroy)
\r
12325 t.theme.destroy();
\r
12327 // Destroy controls, selection and dom
\r
12328 t.controlManager.destroy();
\r
12329 t.selection.destroy();
\r
12332 // Remove all events
\r
12334 // Don't clear the window or document if content editable
\r
12335 // is enabled since other instances might still be present
\r
12336 if (!t.settings.content_editable) {
\r
12337 Event.clear(t.getWin());
\r
12338 Event.clear(t.getDoc());
\r
12341 Event.clear(t.getBody());
\r
12342 Event.clear(t.formElement);
\r
12345 if (t.formElement) {
\r
12346 t.formElement.submit = t.formElement._mceOldSubmit;
\r
12347 t.formElement._mceOldSubmit = null;
\r
12350 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
\r
12353 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
\r
12358 // Internal functions
\r
12360 _addEvents : function() {
\r
12361 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
\r
12362 var t = this, i, s = t.settings, dom = t.dom, lo = {
\r
12363 mouseup : 'onMouseUp',
\r
12364 mousedown : 'onMouseDown',
\r
12365 click : 'onClick',
\r
12366 keyup : 'onKeyUp',
\r
12367 keydown : 'onKeyDown',
\r
12368 keypress : 'onKeyPress',
\r
12369 submit : 'onSubmit',
\r
12370 reset : 'onReset',
\r
12371 contextmenu : 'onContextMenu',
\r
12372 dblclick : 'onDblClick',
\r
12373 paste : 'onPaste' // Doesn't work in all browsers yet
\r
12376 function eventHandler(e, o) {
\r
12379 // Don't fire events when it's removed
\r
12383 // Generic event handler
\r
12384 if (t.onEvent.dispatch(t, e, o) !== false) {
\r
12385 // Specific event handler
\r
12386 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
\r
12390 // Add DOM events
\r
12391 each(lo, function(v, k) {
\r
12393 case 'contextmenu':
\r
12394 dom.bind(t.getDoc(), k, eventHandler);
\r
12398 dom.bind(t.getBody(), k, function(e) {
\r
12405 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
\r
12409 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
\r
12413 dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
\r
12418 // Fixes bug where a specified document_base_uri could result in broken images
\r
12419 // This will also fix drag drop of images in Gecko
\r
12420 if (tinymce.isGecko) {
\r
12421 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
\r
12426 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
\r
12427 e.src = t.documentBaseURI.toAbsolute(v);
\r
12431 // Set various midas options in Gecko
\r
12433 function setOpts() {
\r
12434 var t = this, d = t.getDoc(), s = t.settings;
\r
12436 if (isGecko && !s.readonly) {
\r
12437 if (t._isHidden()) {
\r
12439 if (!s.content_editable) {
\r
12440 d.body.contentEditable = false;
\r
12441 d.body.contentEditable = true;
\r
12444 // Fails if it's hidden
\r
12449 // Try new Gecko method
\r
12450 d.execCommand("styleWithCSS", 0, false);
\r
12452 // Use old method
\r
12453 if (!t._isHidden())
\r
12454 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
\r
12457 if (!s.table_inline_editing)
\r
12458 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
\r
12460 if (!s.object_resizing)
\r
12461 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
\r
12465 t.onBeforeExecCommand.add(setOpts);
\r
12466 t.onMouseDown.add(setOpts);
\r
12469 t.onClick.add(function(ed, e) {
\r
12472 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
\r
12473 // WebKit can't even do simple things like selecting an image
\r
12474 // Needs tobe the setBaseAndExtend or it will fail to select floated images
\r
12475 if (tinymce.isWebKit && e.nodeName == 'IMG')
\r
12476 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
\r
12478 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))
\r
12479 t.selection.select(e);
\r
12484 // Add node change handlers
\r
12485 t.onMouseUp.add(t.nodeChanged);
\r
12486 //t.onClick.add(t.nodeChanged);
\r
12487 t.onKeyUp.add(function(ed, e) {
\r
12488 var c = e.keyCode;
\r
12490 if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
\r
12494 // Add reset handler
\r
12495 t.onReset.add(function() {
\r
12496 t.setContent(t.startContent, {format : 'raw'});
\r
12500 if (s.custom_shortcuts) {
\r
12501 if (s.custom_undo_redo_keyboard_shortcuts) {
\r
12502 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
\r
12503 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
\r
12506 // Add default shortcuts for gecko
\r
12507 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
\r
12508 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
\r
12509 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
\r
12511 // BlockFormat shortcuts keys
\r
12512 for (i=1; i<=6; i++)
\r
12513 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
\r
12515 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
\r
12516 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
\r
12517 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
\r
12519 function find(e) {
\r
12522 if (!e.altKey && !e.ctrlKey && !e.metaKey)
\r
12525 each(t.shortcuts, function(o) {
\r
12526 if (tinymce.isMac && o.ctrl != e.metaKey)
\r
12528 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
\r
12531 if (o.alt != e.altKey)
\r
12534 if (o.shift != e.shiftKey)
\r
12537 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
\r
12546 t.onKeyUp.add(function(ed, e) {
\r
12550 return Event.cancel(e);
\r
12553 t.onKeyPress.add(function(ed, e) {
\r
12557 return Event.cancel(e);
\r
12560 t.onKeyDown.add(function(ed, e) {
\r
12564 o.func.call(o.scope);
\r
12565 return Event.cancel(e);
\r
12570 if (tinymce.isIE) {
\r
12571 // Fix so resize will only update the width and height attributes not the styles of an image
\r
12572 // It will also block mceItemNoResize items
\r
12573 dom.bind(t.getDoc(), 'controlselect', function(e) {
\r
12574 var re = t.resizeInfo, cb;
\r
12578 // Don't do this action for non image elements
\r
12579 if (e.nodeName !== 'IMG')
\r
12583 dom.unbind(re.node, re.ev, re.cb);
\r
12585 if (!dom.hasClass(e, 'mceItemNoResize')) {
\r
12586 ev = 'resizeend';
\r
12587 cb = dom.bind(e, ev, function(e) {
\r
12592 if (v = dom.getStyle(e, 'width')) {
\r
12593 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
\r
12594 dom.setStyle(e, 'width', '');
\r
12597 if (v = dom.getStyle(e, 'height')) {
\r
12598 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
\r
12599 dom.setStyle(e, 'height', '');
\r
12603 ev = 'resizestart';
\r
12604 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
\r
12607 re = t.resizeInfo = {
\r
12615 if (tinymce.isOpera) {
\r
12616 t.onClick.add(function(ed, e) {
\r
12617 Event.prevent(e);
\r
12621 // Add custom undo/redo handlers
\r
12622 if (s.custom_undo_redo) {
\r
12623 function addUndo() {
\r
12624 t.undoManager.typing = false;
\r
12625 t.undoManager.add();
\r
12628 dom.bind(t.getDoc(), 'focusout', function(e) {
\r
12629 if (!t.removed && t.undoManager.typing)
\r
12633 // Add undo level when contents is drag/dropped within the editor
\r
12634 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
\r
12638 t.onKeyUp.add(function(ed, e) {
\r
12639 var keyCode = e.keyCode;
\r
12641 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)
\r
12645 t.onKeyDown.add(function(ed, e) {
\r
12646 var keyCode = e.keyCode, sel;
\r
12648 if (keyCode == 8) {
\r
12649 sel = t.getDoc().selection;
\r
12651 // Fix IE control + backspace browser bug
\r
12652 if (sel && sel.createRange && sel.createRange().item) {
\r
12653 t.undoManager.beforeChange();
\r
12654 ed.dom.remove(sel.createRange().item(0));
\r
12657 return Event.cancel(e);
\r
12661 // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
\r
12662 if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
\r
12663 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
\r
12664 // Todo: Remove this once we normalize enter behavior on IE
\r
12665 if (tinymce.isIE && keyCode == 13)
\r
12666 t.undoManager.beforeChange();
\r
12668 if (t.undoManager.typing)
\r
12674 // If key isn't shift,ctrl,alt,capslock,metakey
\r
12675 if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
\r
12676 t.undoManager.beforeChange();
\r
12677 t.undoManager.typing = true;
\r
12678 t.undoManager.add();
\r
12682 t.onMouseDown.add(function() {
\r
12683 if (t.undoManager.typing)
\r
12688 // Bug fix for FireFox keeping styles from end of selection instead of start.
\r
12689 if (tinymce.isGecko) {
\r
12690 function getAttributeApplyFunction() {
\r
12691 var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
\r
12693 return function() {
\r
12694 var target = t.selection.getStart();
\r
12695 t.dom.removeAllAttribs(target);
\r
12696 each(template, function(attr) {
\r
12697 target.setAttributeNode(attr.cloneNode(true));
\r
12702 function isSelectionAcrossElements() {
\r
12703 var s = t.selection;
\r
12705 return !s.isCollapsed() && s.getStart() != s.getEnd();
\r
12708 t.onKeyPress.add(function(ed, e) {
\r
12709 var applyAttributes;
\r
12711 if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
\r
12712 applyAttributes = getAttributeApplyFunction();
\r
12713 t.getDoc().execCommand('delete', false, null);
\r
12714 applyAttributes();
\r
12716 return Event.cancel(e);
\r
12720 t.dom.bind(t.getDoc(), 'cut', function(e) {
\r
12721 var applyAttributes;
\r
12723 if (isSelectionAcrossElements()) {
\r
12724 applyAttributes = getAttributeApplyFunction();
\r
12725 t.onKeyUp.addToTop(Event.cancel, Event);
\r
12727 setTimeout(function() {
\r
12728 applyAttributes();
\r
12729 t.onKeyUp.remove(Event.cancel, Event);
\r
12736 _isHidden : function() {
\r
12742 // Weird, wheres that cursor selection?
\r
12743 s = this.selection.getSel();
\r
12744 return (!s || !s.rangeCount || s.rangeCount == 0);
\r
12749 (function(tinymce) {
\r
12750 // Added for compression purposes
\r
12751 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
\r
12753 tinymce.EditorCommands = function(editor) {
\r
12754 var dom = editor.dom,
\r
12755 selection = editor.selection,
\r
12756 commands = {state: {}, exec : {}, value : {}},
\r
12757 settings = editor.settings,
\r
12760 function execCommand(command, ui, value) {
\r
12763 command = command.toLowerCase();
\r
12764 if (func = commands.exec[command]) {
\r
12765 func(command, ui, value);
\r
12772 function queryCommandState(command) {
\r
12775 command = command.toLowerCase();
\r
12776 if (func = commands.state[command])
\r
12777 return func(command);
\r
12782 function queryCommandValue(command) {
\r
12785 command = command.toLowerCase();
\r
12786 if (func = commands.value[command])
\r
12787 return func(command);
\r
12792 function addCommands(command_list, type) {
\r
12793 type = type || 'exec';
\r
12795 each(command_list, function(callback, command) {
\r
12796 each(command.toLowerCase().split(','), function(command) {
\r
12797 commands[type][command] = callback;
\r
12802 // Expose public methods
\r
12803 tinymce.extend(this, {
\r
12804 execCommand : execCommand,
\r
12805 queryCommandState : queryCommandState,
\r
12806 queryCommandValue : queryCommandValue,
\r
12807 addCommands : addCommands
\r
12810 // Private methods
\r
12812 function execNativeCommand(command, ui, value) {
\r
12813 if (ui === undefined)
\r
12816 if (value === undefined)
\r
12819 return editor.getDoc().execCommand(command, ui, value);
\r
12822 function isFormatMatch(name) {
\r
12823 return editor.formatter.match(name);
\r
12826 function toggleFormat(name, value) {
\r
12827 editor.formatter.toggle(name, value ? {value : value} : undefined);
\r
12830 function storeSelection(type) {
\r
12831 bookmark = selection.getBookmark(type);
\r
12834 function restoreSelection() {
\r
12835 selection.moveToBookmark(bookmark);
\r
12838 // Add execCommand overrides
\r
12840 // Ignore these, added for compatibility
\r
12841 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
\r
12843 // Add undo manager logic
\r
12844 'mceEndUndoLevel,mceAddUndoLevel' : function() {
\r
12845 editor.undoManager.add();
\r
12848 'Cut,Copy,Paste' : function(command) {
\r
12849 var doc = editor.getDoc(), failed;
\r
12851 // Try executing the native command
\r
12853 execNativeCommand(command);
\r
12855 // Command failed
\r
12859 // Present alert message about clipboard access not being available
\r
12860 if (failed || !doc.queryCommandSupported(command)) {
\r
12861 if (tinymce.isGecko) {
\r
12862 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
\r
12864 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
\r
12867 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
\r
12871 // Override unlink command
\r
12872 unlink : function(command) {
\r
12873 if (selection.isCollapsed())
\r
12874 selection.select(selection.getNode());
\r
12876 execNativeCommand(command);
\r
12877 selection.collapse(FALSE);
\r
12880 // Override justify commands to use the text formatter engine
\r
12881 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
12882 var align = command.substring(7);
\r
12884 // Remove all other alignments first
\r
12885 each('left,center,right,full'.split(','), function(name) {
\r
12886 if (align != name)
\r
12887 editor.formatter.remove('align' + name);
\r
12890 toggleFormat('align' + align);
\r
12891 execCommand('mceRepaint');
\r
12894 // Override list commands to fix WebKit bug
\r
12895 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
12896 var listElm, listParent;
\r
12898 execNativeCommand(command);
\r
12900 // WebKit produces lists within block elements so we need to split them
\r
12901 // we will replace the native list creation logic to custom logic later on
\r
12902 // TODO: Remove this when the list creation logic is removed
\r
12903 listElm = dom.getParent(selection.getNode(), 'ol,ul');
\r
12905 listParent = listElm.parentNode;
\r
12907 // If list is within a text block then split that block
\r
12908 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
\r
12909 storeSelection();
\r
12910 dom.split(listParent, listElm);
\r
12911 restoreSelection();
\r
12916 // Override commands to use the text formatter engine
\r
12917 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
\r
12918 toggleFormat(command);
\r
12921 // Override commands to use the text formatter engine
\r
12922 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
\r
12923 toggleFormat(command, value);
\r
12926 FontSize : function(command, ui, value) {
\r
12927 var fontClasses, fontSizes;
\r
12929 // Convert font size 1-7 to styles
\r
12930 if (value >= 1 && value <= 7) {
\r
12931 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
12932 fontClasses = tinymce.explode(settings.font_size_classes);
\r
12935 value = fontClasses[value - 1] || value;
\r
12937 value = fontSizes[value - 1] || value;
\r
12940 toggleFormat(command, value);
\r
12943 RemoveFormat : function(command) {
\r
12944 editor.formatter.remove(command);
\r
12947 mceBlockQuote : function(command) {
\r
12948 toggleFormat('blockquote');
\r
12951 FormatBlock : function(command, ui, value) {
\r
12952 return toggleFormat(value || 'p');
\r
12955 mceCleanup : function() {
\r
12956 var bookmark = selection.getBookmark();
\r
12958 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
\r
12960 selection.moveToBookmark(bookmark);
\r
12963 mceRemoveNode : function(command, ui, value) {
\r
12964 var node = value || selection.getNode();
\r
12966 // Make sure that the body node isn't removed
\r
12967 if (node != editor.getBody()) {
\r
12968 storeSelection();
\r
12969 editor.dom.remove(node, TRUE);
\r
12970 restoreSelection();
\r
12974 mceSelectNodeDepth : function(command, ui, value) {
\r
12977 dom.getParent(selection.getNode(), function(node) {
\r
12978 if (node.nodeType == 1 && counter++ == value) {
\r
12979 selection.select(node);
\r
12982 }, editor.getBody());
\r
12985 mceSelectNode : function(command, ui, value) {
\r
12986 selection.select(value);
\r
12989 mceInsertContent : function(command, ui, value) {
\r
12990 var parser, serializer, parentNode, rootNode, fragment, args,
\r
12991 marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
\r
12993 // Setup parser and serializer
\r
12994 parser = editor.parser;
\r
12995 serializer = new tinymce.html.Serializer({}, editor.schema);
\r
12996 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
\r
12998 // Run beforeSetContent handlers on the HTML to be inserted
\r
12999 args = {content: value, format: 'html'};
\r
13000 selection.onBeforeSetContent.dispatch(selection, args);
\r
13001 value = args.content;
\r
13003 // Add caret at end of contents if it's missing
\r
13004 if (value.indexOf('{$caret}') == -1)
\r
13005 value += '{$caret}';
\r
13007 // Replace the caret marker with a span bookmark element
\r
13008 value = value.replace(/\{\$caret\}/, bookmarkHtml);
\r
13010 // Insert node maker where we will insert the new HTML and get it's parent
\r
13011 if (!selection.isCollapsed())
\r
13012 editor.getDoc().execCommand('Delete', false, null);
\r
13014 parentNode = selection.getNode();
\r
13016 // Parse the fragment within the context of the parent node
\r
13017 args = {context : parentNode.nodeName.toLowerCase()};
\r
13018 fragment = parser.parse(value, args);
\r
13020 // Move the caret to a more suitable location
\r
13021 node = fragment.lastChild;
\r
13022 if (node.attr('id') == 'mce_marker') {
\r
13025 for (node = node.prev; node; node = node.walk(true)) {
\r
13026 if (node.type == 3 || !dom.isBlock(node.name)) {
\r
13027 node.parent.insert(marker, node, node.name === 'br');
\r
13033 // If parser says valid we can insert the contents into that parent
\r
13034 if (!args.invalid) {
\r
13035 value = serializer.serialize(fragment);
\r
13037 // Check if parent is empty or only has one BR element then set the innerHTML of that parent
\r
13038 node = parentNode.firstChild;
\r
13039 node2 = parentNode.lastChild;
\r
13040 if (!node || (node === node2 && node.nodeName === 'BR'))
\r
13041 dom.setHTML(parentNode, value);
\r
13043 selection.setContent(value);
\r
13045 // If the fragment was invalid within that context then we need
\r
13046 // to parse and process the parent it's inserted into
\r
13048 // Insert bookmark node and get the parent
\r
13049 selection.setContent(bookmarkHtml);
\r
13050 parentNode = editor.selection.getNode();
\r
13051 rootNode = editor.getBody();
\r
13053 // Opera will return the document node when selection is in root
\r
13054 if (parentNode.nodeType == 9)
\r
13055 parentNode = node = rootNode;
\r
13057 node = parentNode;
\r
13059 // Find the ancestor just before the root element
\r
13060 while (node !== rootNode) {
\r
13061 parentNode = node;
\r
13062 node = node.parentNode;
\r
13065 // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
\r
13066 value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
\r
13067 value = serializer.serialize(
\r
13069 // Need to replace by using a function since $ in the contents would otherwise be a problem
\r
13070 value.replace(/<span (id="mce_marker"|id=mce_marker).+<\/span>/i, function() {
\r
13071 return serializer.serialize(fragment);
\r
13076 // Set the inner/outer HTML depending on if we are in the root or not
\r
13077 if (parentNode == rootNode)
\r
13078 dom.setHTML(rootNode, value);
\r
13080 dom.setOuterHTML(parentNode, value);
\r
13083 marker = dom.get('mce_marker');
\r
13085 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
\r
13086 nodeRect = dom.getRect(marker);
\r
13087 viewPortRect = dom.getViewPort(editor.getWin());
\r
13089 // Check if node is out side the viewport if it is then scroll to it
\r
13090 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
\r
13091 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
\r
13092 viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
\r
13093 viewportBodyElement.scrollLeft = nodeRect.x;
\r
13094 viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
\r
13097 // Move selection before marker and remove it
\r
13098 rng = dom.createRng();
\r
13100 // If previous sibling is a text node set the selection to the end of that node
\r
13101 node = marker.previousSibling;
\r
13102 if (node && node.nodeType == 3) {
\r
13103 rng.setStart(node, node.nodeValue.length);
\r
13105 // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
\r
13106 rng.setStartBefore(marker);
\r
13107 rng.setEndBefore(marker);
\r
13110 // Remove the marker node and set the new range
\r
13111 dom.remove(marker);
\r
13112 selection.setRng(rng);
\r
13114 // Dispatch after event and add any visual elements needed
\r
13115 selection.onSetContent.dispatch(selection, args);
\r
13116 editor.addVisual();
\r
13119 mceInsertRawHTML : function(command, ui, value) {
\r
13120 selection.setContent('tiny_mce_marker');
\r
13121 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
\r
13124 mceSetContent : function(command, ui, value) {
\r
13125 editor.setContent(value);
\r
13128 'Indent,Outdent' : function(command) {
\r
13129 var intentValue, indentUnit, value;
\r
13131 // Setup indent level
\r
13132 intentValue = settings.indentation;
\r
13133 indentUnit = /[a-z%]+$/i.exec(intentValue);
\r
13134 intentValue = parseInt(intentValue);
\r
13136 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
\r
13137 each(selection.getSelectedBlocks(), function(element) {
\r
13138 if (command == 'outdent') {
\r
13139 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
\r
13140 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
\r
13142 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
\r
13145 execNativeCommand(command);
\r
13148 mceRepaint : function() {
\r
13151 if (tinymce.isGecko) {
\r
13153 storeSelection(TRUE);
\r
13155 if (selection.getSel())
\r
13156 selection.getSel().selectAllChildren(editor.getBody());
\r
13158 selection.collapse(TRUE);
\r
13159 restoreSelection();
\r
13166 mceToggleFormat : function(command, ui, value) {
\r
13167 editor.formatter.toggle(value);
\r
13170 InsertHorizontalRule : function() {
\r
13171 editor.execCommand('mceInsertContent', false, '<hr />');
\r
13174 mceToggleVisualAid : function() {
\r
13175 editor.hasVisual = !editor.hasVisual;
\r
13176 editor.addVisual();
\r
13179 mceReplaceContent : function(command, ui, value) {
\r
13180 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
\r
13183 mceInsertLink : function(command, ui, value) {
\r
13184 var link = dom.getParent(selection.getNode(), 'a'), img, style, cls;
\r
13186 if (tinymce.is(value, 'string'))
\r
13187 value = {href : value};
\r
13189 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
\r
13190 value.href = value.href.replace(' ', '%20');
\r
13193 // WebKit can't create links on floated images for some odd reason
\r
13194 // So, just remove styles and restore it later
\r
13195 if (tinymce.isWebKit) {
\r
13196 img = dom.getParent(selection.getNode(), 'img');
\r
13199 style = img.style.cssText;
\r
13200 cls = img.className;
\r
13201 img.style.cssText = null;
\r
13202 img.className = null;
\r
13206 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
\r
13208 // Restore styles
\r
13210 img.style.cssText = style;
\r
13212 img.className = cls;
\r
13214 each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
\r
13215 dom.setAttribs(link, value);
\r
13219 dom.setAttribs(link, value);
\r
13221 editor.dom.remove(link, TRUE);
\r
13225 selectAll : function() {
\r
13226 var root = dom.getRoot(), rng = dom.createRng();
\r
13228 rng.setStart(root, 0);
\r
13229 rng.setEnd(root, root.childNodes.length);
\r
13231 editor.selection.setRng(rng);
\r
13235 // Add queryCommandState overrides
\r
13237 // Override justify commands
\r
13238 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
13239 return isFormatMatch('align' + command.substring(7));
\r
13242 'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
\r
13243 return isFormatMatch(command);
\r
13246 mceBlockQuote : function() {
\r
13247 return isFormatMatch('blockquote');
\r
13250 Outdent : function() {
\r
13253 if (settings.inline_styles) {
\r
13254 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
13257 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
13261 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
\r
13264 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
13265 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
\r
13269 // Add queryCommandValue overrides
\r
13271 'FontSize,FontName' : function(command) {
\r
13272 var value = 0, parent;
\r
13274 if (parent = dom.getParent(selection.getNode(), 'span')) {
\r
13275 if (command == 'fontsize')
\r
13276 value = parent.style.fontSize;
\r
13278 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
\r
13285 // Add undo manager logic
\r
13286 if (settings.custom_undo_redo) {
\r
13288 Undo : function() {
\r
13289 editor.undoManager.undo();
\r
13292 Redo : function() {
\r
13293 editor.undoManager.redo();
\r
13300 (function(tinymce) {
\r
13301 var Dispatcher = tinymce.util.Dispatcher;
\r
13303 tinymce.UndoManager = function(editor) {
\r
13304 var self, index = 0, data = [], beforeBookmark;
\r
13306 function getContent() {
\r
13307 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
\r
13313 onAdd : new Dispatcher(self),
\r
13315 onUndo : new Dispatcher(self),
\r
13317 onRedo : new Dispatcher(self),
\r
13319 beforeChange : function() {
\r
13320 beforeBookmark = editor.selection.getBookmark(2, true);
\r
13323 add : function(level) {
\r
13324 var i, settings = editor.settings, lastLevel;
\r
13326 level = level || {};
\r
13327 level.content = getContent();
\r
13329 // Add undo level if needed
\r
13330 lastLevel = data[index];
\r
13331 if (lastLevel && lastLevel.content == level.content)
\r
13334 // Set before bookmark on previous level
\r
13336 data[index].beforeBookmark = beforeBookmark;
\r
13338 // Time to compress
\r
13339 if (settings.custom_undo_redo_levels) {
\r
13340 if (data.length > settings.custom_undo_redo_levels) {
\r
13341 for (i = 0; i < data.length - 1; i++)
\r
13342 data[i] = data[i + 1];
\r
13345 index = data.length;
\r
13349 // Get a non intrusive normalized bookmark
\r
13350 level.bookmark = editor.selection.getBookmark(2, true);
\r
13352 // Crop array if needed
\r
13353 if (index < data.length - 1)
\r
13354 data.length = index + 1;
\r
13356 data.push(level);
\r
13357 index = data.length - 1;
\r
13359 self.onAdd.dispatch(self, level);
\r
13360 editor.isNotDirty = 0;
\r
13365 undo : function() {
\r
13368 if (self.typing) {
\r
13370 self.typing = false;
\r
13374 level = data[--index];
\r
13376 editor.setContent(level.content, {format : 'raw'});
\r
13377 editor.selection.moveToBookmark(level.beforeBookmark);
\r
13379 self.onUndo.dispatch(self, level);
\r
13385 redo : function() {
\r
13388 if (index < data.length - 1) {
\r
13389 level = data[++index];
\r
13391 editor.setContent(level.content, {format : 'raw'});
\r
13392 editor.selection.moveToBookmark(level.bookmark);
\r
13394 self.onRedo.dispatch(self, level);
\r
13400 clear : function() {
\r
13403 self.typing = false;
\r
13406 hasUndo : function() {
\r
13407 return index > 0 || this.typing;
\r
13410 hasRedo : function() {
\r
13411 return index < data.length - 1 && !this.typing;
\r
13417 (function(tinymce) {
\r
13419 var Event = tinymce.dom.Event,
\r
13420 isIE = tinymce.isIE,
\r
13421 isGecko = tinymce.isGecko,
\r
13422 isOpera = tinymce.isOpera,
\r
13423 each = tinymce.each,
\r
13424 extend = tinymce.extend,
\r
13428 function cloneFormats(node) {
\r
13429 var clone, temp, inner;
\r
13432 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
\r
13434 temp = node.cloneNode(false);
\r
13435 temp.appendChild(clone);
\r
13438 clone = inner = node.cloneNode(false);
\r
13441 clone.removeAttribute('id');
\r
13443 } while (node = node.parentNode);
\r
13446 return {wrapper : clone, inner : inner};
\r
13449 // Checks if the selection/caret is at the end of the specified block element
\r
13450 function isAtEnd(rng, par) {
\r
13451 var rng2 = par.ownerDocument.createRange();
\r
13453 rng2.setStart(rng.endContainer, rng.endOffset);
\r
13454 rng2.setEndAfter(par);
\r
13456 // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
\r
13457 return rng2.cloneContents().textContent.length == 0;
\r
13460 function splitList(selection, dom, li) {
\r
13461 var listBlock, block;
\r
13463 if (dom.isEmpty(li)) {
\r
13464 listBlock = dom.getParent(li, 'ul,ol');
\r
13466 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
\r
13467 dom.split(listBlock, li);
\r
13468 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
\r
13469 dom.replace(block, li);
\r
13470 selection.select(block, 1);
\r
13479 tinymce.create('tinymce.ForceBlocks', {
\r
13480 ForceBlocks : function(ed) {
\r
13481 var t = this, s = ed.settings, elm;
\r
13485 elm = (s.forced_root_block || 'p').toLowerCase();
\r
13486 s.element = elm.toUpperCase();
\r
13488 ed.onPreInit.add(t.setup, t);
\r
13491 setup : function() {
\r
13492 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();
\r
13494 // Force root blocks
\r
13495 if (s.forced_root_block) {
\r
13496 function addRootBlocks() {
\r
13497 var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;
\r
13499 if (!node || node.nodeType !== 1)
\r
13502 // Check if node is wrapped in block
\r
13503 while (node != rootNode) {
\r
13504 if (blockElements[node.nodeName])
\r
13507 node = node.parentNode;
\r
13510 // Get current selection
\r
13511 rng = selection.getRng();
\r
13512 if (rng.setStart) {
\r
13513 startContainer = rng.startContainer;
\r
13514 startOffset = rng.startOffset;
\r
13515 endContainer = rng.endContainer;
\r
13516 endOffset = rng.endOffset;
\r
13518 // Force control range into text range
\r
13520 rng = ed.getDoc().body.createTextRange();
\r
13521 rng.moveToElementText(rng.item(0));
\r
13524 tmpRng = rng.duplicate();
\r
13525 tmpRng.collapse(true);
\r
13526 startOffset = tmpRng.move('character', offset) * -1;
\r
13528 if (!tmpRng.collapsed) {
\r
13529 tmpRng = rng.duplicate();
\r
13530 tmpRng.collapse(false);
\r
13531 endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
\r
13535 // Wrap non block elements and text nodes
\r
13536 for (node = rootNode.firstChild; node; node) {
\r
13537 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
\r
13538 if (!rootBlockNode) {
\r
13539 rootBlockNode = dom.create(s.forced_root_block);
\r
13540 node.parentNode.insertBefore(rootBlockNode, node);
\r
13544 node = node.nextSibling;
\r
13545 rootBlockNode.appendChild(tempNode);
\r
13547 rootBlockNode = null;
\r
13548 node = node.nextSibling;
\r
13552 if (rng.setStart) {
\r
13553 rng.setStart(startContainer, startOffset);
\r
13554 rng.setEnd(endContainer, endOffset);
\r
13555 selection.setRng(rng);
\r
13558 rng = ed.getDoc().body.createTextRange();
\r
13559 rng.moveToElementText(rootNode);
\r
13560 rng.collapse(true);
\r
13561 rng.moveStart('character', startOffset);
\r
13563 if (endOffset > 0)
\r
13564 rng.moveEnd('character', endOffset);
\r
13572 ed.nodeChanged();
\r
13575 ed.onKeyUp.add(addRootBlocks);
\r
13576 ed.onClick.add(addRootBlocks);
\r
13579 if (s.force_br_newlines) {
\r
13580 // Force IE to produce BRs on enter
\r
13582 ed.onKeyPress.add(function(ed, e) {
\r
13585 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
\r
13586 selection.setContent('<br id="__" /> ', {format : 'raw'});
\r
13587 n = dom.get('__');
\r
13588 n.removeAttribute('id');
\r
13589 selection.select(n);
\r
13590 selection.collapse();
\r
13591 return Event.cancel(e);
\r
13597 if (s.force_p_newlines) {
\r
13599 ed.onKeyPress.add(function(ed, e) {
\r
13600 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
\r
13604 // Ungly hack to for IE to preserve the formatting when you press
\r
13605 // enter at the end of a block element with formatted contents
\r
13606 // This logic overrides the browsers default logic with
\r
13607 // custom logic that enables us to control the output
\r
13608 tinymce.addUnload(function() {
\r
13609 t._previousFormats = 0; // Fix IE leak
\r
13612 ed.onKeyPress.add(function(ed, e) {
\r
13613 t._previousFormats = 0;
\r
13615 // Clone the current formats, this will later be applied to the new block contents
\r
13616 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
\r
13617 t._previousFormats = cloneFormats(ed.selection.getStart());
\r
13620 ed.onKeyUp.add(function(ed, e) {
\r
13621 // Let IE break the element and the wrap the new caret location in the previous formats
\r
13622 if (e.keyCode == 13 && !e.shiftKey) {
\r
13623 var parent = ed.selection.getStart(), fmt = t._previousFormats;
\r
13625 // Parent is an empty block
\r
13626 if (!parent.hasChildNodes() && fmt) {
\r
13627 parent = dom.getParent(parent, dom.isBlock);
\r
13629 if (parent && parent.nodeName != 'LI') {
\r
13630 parent.innerHTML = '';
\r
13632 if (t._previousFormats) {
\r
13633 parent.appendChild(fmt.wrapper);
\r
13634 fmt.inner.innerHTML = '\uFEFF';
\r
13636 parent.innerHTML = '\uFEFF';
\r
13638 selection.select(parent, 1);
\r
13639 selection.collapse(true);
\r
13640 ed.getDoc().execCommand('Delete', false, null);
\r
13641 t._previousFormats = 0;
\r
13649 ed.onKeyDown.add(function(ed, e) {
\r
13650 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
\r
13651 t.backspaceDelete(e, e.keyCode == 8);
\r
13656 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
\r
13657 if (tinymce.isWebKit) {
\r
13658 function insertBr(ed) {
\r
13659 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
\r
13661 // Insert BR element
\r
13662 rng.insertNode(br = dom.create('br'));
\r
13664 // Place caret after BR
\r
13665 rng.setStartAfter(br);
\r
13666 rng.setEndAfter(br);
\r
13667 selection.setRng(rng);
\r
13669 // Could not place caret after BR then insert an nbsp entity and move the caret
\r
13670 if (selection.getSel().focusNode == br.previousSibling) {
\r
13671 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
\r
13672 selection.collapse(TRUE);
\r
13675 // Create a temporary DIV after the BR and get the position as it
\r
13676 // seems like getPos() returns 0 for text nodes and BR elements.
\r
13677 dom.insertAfter(div, br);
\r
13678 divYPos = dom.getPos(div).y;
\r
13681 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
\r
13682 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
\r
13683 ed.getWin().scrollTo(0, divYPos);
\r
13686 ed.onKeyPress.add(function(ed, e) {
\r
13687 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
\r
13694 // IE specific fixes
\r
13696 // Replaces IE:s auto generated paragraphs with the specified element name
\r
13697 if (s.element != 'P') {
\r
13698 ed.onKeyPress.add(function(ed, e) {
\r
13699 t.lastElm = selection.getNode().nodeName;
\r
13702 ed.onKeyUp.add(function(ed, e) {
\r
13703 var bl, n = selection.getNode(), b = ed.getBody();
\r
13705 if (b.childNodes.length === 1 && n.nodeName == 'P') {
\r
13706 n = dom.rename(n, s.element);
\r
13707 selection.select(n);
\r
13708 selection.collapse();
\r
13709 ed.nodeChanged();
\r
13710 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
\r
13711 bl = dom.getParent(n, 'p');
\r
13714 dom.rename(bl, s.element);
\r
13715 ed.nodeChanged();
\r
13723 getParentBlock : function(n) {
\r
13724 var d = this.dom;
\r
13726 return d.getParent(n, d.isBlock);
\r
13729 insertPara : function(e) {
\r
13730 var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
\r
13731 var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
\r
13733 ed.undoManager.beforeChange();
\r
13735 // If root blocks are forced then use Operas default behavior since it's really good
\r
13736 // Removed due to bug: #1853816
\r
13737 // if (se.forced_root_block && isOpera)
\r
13740 // Setup before range
\r
13741 rb = d.createRange();
\r
13743 // If is before the first block element and in body, then move it into first block element
\r
13744 rb.setStart(s.anchorNode, s.anchorOffset);
\r
13745 rb.collapse(TRUE);
\r
13747 // Setup after range
\r
13748 ra = d.createRange();
\r
13750 // If is before the first block element and in body, then move it into first block element
\r
13751 ra.setStart(s.focusNode, s.focusOffset);
\r
13752 ra.collapse(TRUE);
\r
13754 // Setup start/end points
\r
13755 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
\r
13756 sn = dir ? s.anchorNode : s.focusNode;
\r
13757 so = dir ? s.anchorOffset : s.focusOffset;
\r
13758 en = dir ? s.focusNode : s.anchorNode;
\r
13759 eo = dir ? s.focusOffset : s.anchorOffset;
\r
13761 // If selection is in empty table cell
\r
13762 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
\r
13763 if (sn.firstChild.nodeName == 'BR')
\r
13764 dom.remove(sn.firstChild); // Remove BR
\r
13766 // Create two new block elements
\r
13767 if (sn.childNodes.length == 0) {
\r
13768 ed.dom.add(sn, se.element, null, '<br />');
\r
13769 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
13771 n = sn.innerHTML;
\r
13772 sn.innerHTML = '';
\r
13773 ed.dom.add(sn, se.element, null, n);
\r
13774 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
13777 // Move caret into the last one
\r
13778 r = d.createRange();
\r
13779 r.selectNodeContents(aft);
\r
13781 ed.selection.setRng(r);
\r
13786 // If the caret is in an invalid location in FF we need to move it into the first block
\r
13787 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
\r
13788 sn = en = sn.firstChild;
\r
13790 rb = d.createRange();
\r
13791 rb.setStart(sn, 0);
\r
13792 ra = d.createRange();
\r
13793 ra.setStart(en, 0);
\r
13796 // Never use body as start or end node
\r
13797 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
13798 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
\r
13799 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
13800 en = en.nodeName == "BODY" ? en.firstChild : en;
\r
13802 // Get start and end blocks
\r
13803 sb = t.getParentBlock(sn);
\r
13804 eb = t.getParentBlock(en);
\r
13805 bn = sb ? sb.nodeName : se.element; // Get block name to create
\r
13807 // Return inside list use default browser behavior
\r
13808 if (n = t.dom.getParent(sb, 'li,pre')) {
\r
13809 if (n.nodeName == 'LI')
\r
13810 return splitList(ed.selection, t.dom, n);
\r
13815 // If caption or absolute layers then always generate new blocks within
\r
13816 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
13821 // If caption or absolute layers then always generate new blocks within
\r
13822 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
13828 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
\r
13833 // Setup new before and after blocks
\r
13834 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
\r
13835 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
\r
13837 // Remove id from after clone
\r
13838 aft.removeAttribute('id');
\r
13840 // Is header and cursor is at the end, then force paragraph under
\r
13841 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
\r
13842 aft = ed.dom.create(se.element);
\r
13844 // Find start chop node
\r
13847 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
13851 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
\r
13853 // Find end chop node
\r
13856 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
13860 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
\r
13862 // Place first chop part into before block element
\r
13863 if (sc.nodeName == bn)
\r
13864 rb.setStart(sc, 0);
\r
13866 rb.setStartBefore(sc);
\r
13868 rb.setEnd(sn, so);
\r
13869 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
13871 // Place secnd chop part within new block element
\r
13873 ra.setEndAfter(ec);
\r
13875 //console.debug(s.focusNode, s.focusOffset);
\r
13878 ra.setStart(en, eo);
\r
13879 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
13881 // Create range around everything
\r
13882 r = d.createRange();
\r
13883 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
\r
13884 r.setStartBefore(sc.parentNode);
\r
13886 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
\r
13887 r.setStartBefore(rb.startContainer);
\r
13889 r.setStart(rb.startContainer, rb.startOffset);
\r
13892 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
\r
13893 r.setEndAfter(ec.parentNode);
\r
13895 r.setEnd(ra.endContainer, ra.endOffset);
\r
13897 // Delete and replace it with new block elements
\r
13898 r.deleteContents();
\r
13901 ed.getWin().scrollTo(0, vp.y);
\r
13903 // Never wrap blocks in blocks
\r
13904 if (bef.firstChild && bef.firstChild.nodeName == bn)
\r
13905 bef.innerHTML = bef.firstChild.innerHTML;
\r
13907 if (aft.firstChild && aft.firstChild.nodeName == bn)
\r
13908 aft.innerHTML = aft.firstChild.innerHTML;
\r
13910 function appendStyles(e, en) {
\r
13911 var nl = [], nn, n, i;
\r
13913 e.innerHTML = '';
\r
13915 // Make clones of style elements
\r
13916 if (se.keep_styles) {
\r
13919 // We only want style specific elements
\r
13920 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
\r
13921 nn = n.cloneNode(FALSE);
\r
13922 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
\r
13925 } while (n = n.parentNode);
\r
13928 // Append style elements to aft
\r
13929 if (nl.length > 0) {
\r
13930 for (i = nl.length - 1, nn = e; i >= 0; i--)
\r
13931 nn = nn.appendChild(nl[i]);
\r
13933 // Padd most inner style element
\r
13934 nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
\r
13935 return nl[0]; // Move caret to most inner element
\r
13937 e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
\r
13940 // Padd empty blocks
\r
13941 if (dom.isEmpty(bef))
\r
13942 appendStyles(bef, sn);
\r
13944 // Fill empty afterblook with current style
\r
13945 if (dom.isEmpty(aft))
\r
13946 car = appendStyles(aft, en);
\r
13948 // Opera needs this one backwards for older versions
\r
13949 if (isOpera && parseFloat(opera.version()) < 9.5) {
\r
13950 r.insertNode(bef);
\r
13951 r.insertNode(aft);
\r
13953 r.insertNode(aft);
\r
13954 r.insertNode(bef);
\r
13961 // Move cursor and scroll into view
\r
13962 ed.selection.select(aft, true);
\r
13963 ed.selection.collapse(true);
\r
13965 // 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
\r
13966 y = ed.dom.getPos(aft).y;
\r
13967 //ch = aft.clientHeight;
\r
13969 // Is element within viewport
\r
13970 if (y < vp.y || y + 25 > vp.y + vp.h) {
\r
13971 ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
\r
13974 'Element: y=' + y + ', h=' + ch + ', ' +
\r
13975 'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
\r
13979 ed.undoManager.add();
\r
13984 backspaceDelete : function(e, bs) {
\r
13985 var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;
\r
13987 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
\r
13988 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
\r
13989 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
\r
13991 // Walk the dom backwards until we find a text node
\r
13992 for (n = sc.lastChild; n; n = walker.prev()) {
\r
13993 if (n.nodeType == 3) {
\r
13994 r.setStart(n, n.nodeValue.length);
\r
13995 r.collapse(true);
\r
14002 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
\r
14003 // This workaround removes the element by hand and moves the caret to the previous element
\r
14004 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
\r
14005 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
\r
14006 // Find previous block element
\r
14008 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
\r
14011 if (sc != b.firstChild) {
\r
14012 // Find last text node
\r
14013 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
\r
14014 while (tn = w.nextNode())
\r
14017 // Place caret at the end of last text node
\r
14018 r = ed.getDoc().createRange();
\r
14019 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
\r
14020 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
\r
14023 // Remove the target container
\r
14024 ed.dom.remove(sc);
\r
14027 return Event.cancel(e);
\r
14035 (function(tinymce) {
\r
14037 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
\r
14039 tinymce.create('tinymce.ControlManager', {
\r
14040 ControlManager : function(ed, s) {
\r
14046 t.onAdd = new tinymce.util.Dispatcher(t);
\r
14047 t.onPostRender = new tinymce.util.Dispatcher(t);
\r
14048 t.prefix = s.prefix || ed.id + '_';
\r
14051 t.onPostRender.add(function() {
\r
14052 each(t.controls, function(c) {
\r
14058 get : function(id) {
\r
14059 return this.controls[this.prefix + id] || this.controls[id];
\r
14062 setActive : function(id, s) {
\r
14065 if (c = this.get(id))
\r
14071 setDisabled : function(id, s) {
\r
14074 if (c = this.get(id))
\r
14075 c.setDisabled(s);
\r
14080 add : function(c) {
\r
14084 t.controls[c.id] = c;
\r
14085 t.onAdd.dispatch(c, t);
\r
14091 createControl : function(n) {
\r
14092 var c, t = this, ed = t.editor;
\r
14094 each(ed.plugins, function(p) {
\r
14095 if (p.createControl) {
\r
14096 c = p.createControl(n, t);
\r
14105 case "separator":
\r
14106 return t.createSeparator();
\r
14109 if (!c && ed.buttons && (c = ed.buttons[n]))
\r
14110 return t.createButton(n, c);
\r
14115 createDropMenu : function(id, s, cc) {
\r
14116 var t = this, ed = t.editor, c, bm, v, cls;
\r
14119 'class' : 'mceDropDown',
\r
14120 constrain : ed.settings.constrain_menus
\r
14123 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
\r
14124 if (v = ed.getParam('skin_variant'))
\r
14125 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
\r
14127 id = t.prefix + id;
\r
14128 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
\r
14129 c = t.controls[id] = new cls(id, s);
\r
14130 c.onAddItem.add(function(c, o) {
\r
14131 var s = o.settings;
\r
14133 s.title = ed.getLang(s.title, s.title);
\r
14135 if (!s.onclick) {
\r
14136 s.onclick = function(v) {
\r
14138 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
14143 ed.onRemove.add(function() {
\r
14147 // Fix for bug #1897785, #1898007
\r
14148 if (tinymce.isIE) {
\r
14149 c.onShowMenu.add(function() {
\r
14150 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
14153 bm = ed.selection.getBookmark(1);
\r
14156 c.onHideMenu.add(function() {
\r
14158 ed.selection.moveToBookmark(bm);
\r
14167 createListBox : function(id, s, cc) {
\r
14168 var t = this, ed = t.editor, cmd, c, cls;
\r
14173 s.title = ed.translate(s.title);
\r
14174 s.scope = s.scope || ed;
\r
14176 if (!s.onselect) {
\r
14177 s.onselect = function(v) {
\r
14178 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
14184 'class' : 'mce_' + id,
\r
14186 control_manager : t
\r
14189 id = t.prefix + id;
\r
14191 if (ed.settings.use_native_selects)
\r
14192 c = new tinymce.ui.NativeListBox(id, s);
\r
14194 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
\r
14195 c = new cls(id, s, ed);
\r
14198 t.controls[id] = c;
\r
14200 // Fix focus problem in Safari
\r
14201 if (tinymce.isWebKit) {
\r
14202 c.onPostRender.add(function(c, n) {
\r
14203 // Store bookmark on mousedown
\r
14204 Event.add(n, 'mousedown', function() {
\r
14205 ed.bookmark = ed.selection.getBookmark(1);
\r
14208 // Restore on focus, since it might be lost
\r
14209 Event.add(n, 'focus', function() {
\r
14210 ed.selection.moveToBookmark(ed.bookmark);
\r
14211 ed.bookmark = null;
\r
14217 ed.onMouseDown.add(c.hideMenu, c);
\r
14222 createButton : function(id, s, cc) {
\r
14223 var t = this, ed = t.editor, o, c, cls;
\r
14228 s.title = ed.translate(s.title);
\r
14229 s.label = ed.translate(s.label);
\r
14230 s.scope = s.scope || ed;
\r
14232 if (!s.onclick && !s.menu_button) {
\r
14233 s.onclick = function() {
\r
14234 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
14240 'class' : 'mce_' + id,
\r
14241 unavailable_prefix : ed.getLang('unavailable', ''),
\r
14243 control_manager : t
\r
14246 id = t.prefix + id;
\r
14248 if (s.menu_button) {
\r
14249 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
\r
14250 c = new cls(id, s, ed);
\r
14251 ed.onMouseDown.add(c.hideMenu, c);
\r
14253 cls = t._cls.button || tinymce.ui.Button;
\r
14254 c = new cls(id, s, ed);
\r
14260 createMenuButton : function(id, s, cc) {
\r
14262 s.menu_button = 1;
\r
14264 return this.createButton(id, s, cc);
\r
14267 createSplitButton : function(id, s, cc) {
\r
14268 var t = this, ed = t.editor, cmd, c, cls;
\r
14273 s.title = ed.translate(s.title);
\r
14274 s.scope = s.scope || ed;
\r
14276 if (!s.onclick) {
\r
14277 s.onclick = function(v) {
\r
14278 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
14282 if (!s.onselect) {
\r
14283 s.onselect = function(v) {
\r
14284 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
14290 'class' : 'mce_' + id,
\r
14292 control_manager : t
\r
14295 id = t.prefix + id;
\r
14296 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
\r
14297 c = t.add(new cls(id, s, ed));
\r
14298 ed.onMouseDown.add(c.hideMenu, c);
\r
14303 createColorSplitButton : function(id, s, cc) {
\r
14304 var t = this, ed = t.editor, cmd, c, cls, bm;
\r
14309 s.title = ed.translate(s.title);
\r
14310 s.scope = s.scope || ed;
\r
14312 if (!s.onclick) {
\r
14313 s.onclick = function(v) {
\r
14314 if (tinymce.isIE)
\r
14315 bm = ed.selection.getBookmark(1);
\r
14317 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
14321 if (!s.onselect) {
\r
14322 s.onselect = function(v) {
\r
14323 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
14329 'class' : 'mce_' + id,
\r
14330 'menu_class' : ed.getParam('skin') + 'Skin',
\r
14332 more_colors_title : ed.getLang('more_colors')
\r
14335 id = t.prefix + id;
\r
14336 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
\r
14337 c = new cls(id, s, ed);
\r
14338 ed.onMouseDown.add(c.hideMenu, c);
\r
14340 // Remove the menu element when the editor is removed
\r
14341 ed.onRemove.add(function() {
\r
14345 // Fix for bug #1897785, #1898007
\r
14346 if (tinymce.isIE) {
\r
14347 c.onShowMenu.add(function() {
\r
14348 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
14350 bm = ed.selection.getBookmark(1);
\r
14353 c.onHideMenu.add(function() {
\r
14355 ed.selection.moveToBookmark(bm);
\r
14364 createToolbar : function(id, s, cc) {
\r
14365 var c, t = this, cls;
\r
14367 id = t.prefix + id;
\r
14368 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
\r
14369 c = new cls(id, s, t.editor);
\r
14377 createToolbarGroup : function(id, s, cc) {
\r
14378 var c, t = this, cls;
\r
14379 id = t.prefix + id;
\r
14380 cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
\r
14381 c = new cls(id, s, t.editor);
\r
14389 createSeparator : function(cc) {
\r
14390 var cls = cc || this._cls.separator || tinymce.ui.Separator;
\r
14392 return new cls();
\r
14395 setControlType : function(n, c) {
\r
14396 return this._cls[n.toLowerCase()] = c;
\r
14399 destroy : function() {
\r
14400 each(this.controls, function(c) {
\r
14404 this.controls = null;
\r
14409 (function(tinymce) {
\r
14410 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
\r
14412 tinymce.create('tinymce.WindowManager', {
\r
14413 WindowManager : function(ed) {
\r
14417 t.onOpen = new Dispatcher(t);
\r
14418 t.onClose = new Dispatcher(t);
\r
14423 open : function(s, p) {
\r
14424 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
\r
14426 // Default some options
\r
14429 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
\r
14430 sh = isOpera ? vp.h : screen.height;
\r
14431 s.name = s.name || 'mc_' + new Date().getTime();
\r
14432 s.width = parseInt(s.width || 320);
\r
14433 s.height = parseInt(s.height || 240);
\r
14434 s.resizable = true;
\r
14435 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
\r
14436 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
\r
14437 p.inline = false;
\r
14438 p.mce_width = s.width;
\r
14439 p.mce_height = s.height;
\r
14440 p.mce_auto_focus = s.auto_focus;
\r
14446 s.dialogWidth = s.width + 'px';
\r
14447 s.dialogHeight = s.height + 'px';
\r
14448 s.scroll = s.scrollbars || false;
\r
14452 // Build features string
\r
14453 each(s, function(v, k) {
\r
14454 if (tinymce.is(v, 'boolean'))
\r
14455 v = v ? 'yes' : 'no';
\r
14457 if (!/^(name|url)$/.test(k)) {
\r
14459 f += (f ? ';' : '') + k + ':' + v;
\r
14461 f += (f ? ',' : '') + k + '=' + v;
\r
14467 t.onOpen.dispatch(t, s, p);
\r
14469 u = s.url || s.file;
\r
14470 u = tinymce._addVer(u);
\r
14473 if (isIE && mo) {
\r
14475 window.showModalDialog(u, window, f);
\r
14477 w = window.open(u, s.name, f);
\r
14483 alert(t.editor.getLang('popup_blocked'));
\r
14486 close : function(w) {
\r
14488 this.onClose.dispatch(this);
\r
14491 createInstance : function(cl, a, b, c, d, e) {
\r
14492 var f = tinymce.resolve(cl);
\r
14494 return new f(a, b, c, d, e);
\r
14497 confirm : function(t, cb, s, w) {
\r
14500 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
\r
14503 alert : function(tx, cb, s, w) {
\r
14507 w.alert(t._decode(t.editor.getLang(tx, tx)));
\r
14513 resizeBy : function(dw, dh, win) {
\r
14514 win.resizeBy(dw, dh);
\r
14517 // Internal functions
\r
14519 _decode : function(s) {
\r
14520 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
\r
14524 (function(tinymce) {
\r
14525 tinymce.Formatter = function(ed) {
\r
14526 var formats = {},
\r
14527 each = tinymce.each,
\r
14529 selection = ed.selection,
\r
14530 TreeWalker = tinymce.dom.TreeWalker,
\r
14531 rangeUtils = new tinymce.dom.RangeUtils(dom),
\r
14532 isValid = ed.schema.isValidChild,
\r
14533 isBlock = dom.isBlock,
\r
14534 forcedRootBlock = ed.settings.forced_root_block,
\r
14535 nodeIndex = dom.nodeIndex,
\r
14536 INVISIBLE_CHAR = '\uFEFF',
\r
14537 MCE_ATTR_RE = /^(src|href|style)$/,
\r
14541 pendingFormats = {apply : [], remove : []};
\r
14543 function isArray(obj) {
\r
14544 return obj instanceof Array;
\r
14547 function getParents(node, selector) {
\r
14548 return dom.getParents(node, selector, dom.getRoot());
\r
14551 function isCaretNode(node) {
\r
14552 return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
\r
14555 // Public functions
\r
14557 function get(name) {
\r
14558 return name ? formats[name] : formats;
\r
14561 function register(name, format) {
\r
14563 if (typeof(name) !== 'string') {
\r
14564 each(name, function(format, name) {
\r
14565 register(name, format);
\r
14568 // Force format into array and add it to internal collection
\r
14569 format = format.length ? format : [format];
\r
14571 each(format, function(format) {
\r
14572 // Set deep to false by default on selector formats this to avoid removing
\r
14573 // alignment on images inside paragraphs when alignment is changed on paragraphs
\r
14574 if (format.deep === undefined)
\r
14575 format.deep = !format.selector;
\r
14577 // Default to true
\r
14578 if (format.split === undefined)
\r
14579 format.split = !format.selector || format.inline;
\r
14581 // Default to true
\r
14582 if (format.remove === undefined && format.selector && !format.inline)
\r
14583 format.remove = 'none';
\r
14585 // Mark format as a mixed format inline + block level
\r
14586 if (format.selector && format.inline) {
\r
14587 format.mixed = true;
\r
14588 format.block_expand = true;
\r
14591 // Split classes if needed
\r
14592 if (typeof(format.classes) === 'string')
\r
14593 format.classes = format.classes.split(/\s+/);
\r
14596 formats[name] = format;
\r
14601 var getTextDecoration = function(node) {
\r
14604 ed.dom.getParent(node, function(n) {
\r
14605 decoration = ed.dom.getStyle(n, 'text-decoration');
\r
14606 return decoration && decoration !== 'none';
\r
14609 return decoration;
\r
14612 var processUnderlineAndColor = function(node) {
\r
14613 var textDecoration;
\r
14614 if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
\r
14615 textDecoration = getTextDecoration(node.parentNode);
\r
14616 if (ed.dom.getStyle(node, 'color') && textDecoration) {
\r
14617 ed.dom.setStyle(node, 'text-decoration', textDecoration);
\r
14618 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
\r
14619 ed.dom.setStyle(node, 'text-decoration', null);
\r
14624 function apply(name, vars, node) {
\r
14625 var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
\r
14627 function moveStart(rng) {
\r
14628 var container = rng.startContainer,
\r
14629 offset = rng.startOffset,
\r
14632 // Move startContainer/startOffset in to a suitable node
\r
14633 if (container.nodeType == 1 || container.nodeValue === "") {
\r
14634 container = container.nodeType == 1 ? container.childNodes[offset] : container;
\r
14636 // Might fail if the offset is behind the last element in it's container
\r
14638 walker = new TreeWalker(container, container.parentNode);
\r
14639 for (node = walker.current(); node; node = walker.next()) {
\r
14640 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
\r
14641 rng.setStart(node, 0);
\r
14651 function setElementFormat(elm, fmt) {
\r
14652 fmt = fmt || format;
\r
14655 each(fmt.styles, function(value, name) {
\r
14656 dom.setStyle(elm, name, replaceVars(value, vars));
\r
14659 each(fmt.attributes, function(value, name) {
\r
14660 dom.setAttrib(elm, name, replaceVars(value, vars));
\r
14663 each(fmt.classes, function(value) {
\r
14664 value = replaceVars(value, vars);
\r
14666 if (!dom.hasClass(elm, value))
\r
14667 dom.addClass(elm, value);
\r
14672 function applyRngStyle(rng) {
\r
14673 var newWrappers = [], wrapName, wrapElm;
\r
14675 // Setup wrapper element
\r
14676 wrapName = format.inline || format.block;
\r
14677 wrapElm = dom.create(wrapName);
\r
14678 setElementFormat(wrapElm);
\r
14680 rangeUtils.walk(rng, function(nodes) {
\r
14681 var currentWrapElm;
\r
14683 function process(node) {
\r
14684 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
\r
14686 // Stop wrapping on br elements
\r
14687 if (isEq(nodeName, 'br')) {
\r
14688 currentWrapElm = 0;
\r
14690 // Remove any br elements when we wrap things
\r
14691 if (format.block)
\r
14692 dom.remove(node);
\r
14697 // If node is wrapper type
\r
14698 if (format.wrapper && matchNode(node, name, vars)) {
\r
14699 currentWrapElm = 0;
\r
14703 // Can we rename the block
\r
14704 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
\r
14705 node = dom.rename(node, wrapName);
\r
14706 setElementFormat(node);
\r
14707 newWrappers.push(node);
\r
14708 currentWrapElm = 0;
\r
14712 // Handle selector patterns
\r
14713 if (format.selector) {
\r
14714 // Look for matching formats
\r
14715 each(formatList, function(format) {
\r
14716 // Check collapsed state if it exists
\r
14717 if ('collapsed' in format && format.collapsed !== isCollapsed) {
\r
14721 if (dom.is(node, format.selector) && !isCaretNode(node)) {
\r
14722 setElementFormat(node, format);
\r
14727 // Continue processing if a selector match wasn't found and a inline element is defined
\r
14728 if (!format.inline || found) {
\r
14729 currentWrapElm = 0;
\r
14734 // Is it valid to wrap this item
\r
14735 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
\r
14736 !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
\r
14737 // Start wrapping
\r
14738 if (!currentWrapElm) {
\r
14740 currentWrapElm = wrapElm.cloneNode(FALSE);
\r
14741 node.parentNode.insertBefore(currentWrapElm, node);
\r
14742 newWrappers.push(currentWrapElm);
\r
14745 currentWrapElm.appendChild(node);
\r
14746 } else if (nodeName == 'li') {
\r
14747 // Start wrapping
\r
14748 if (!currentWrapElm) {
\r
14750 liTextNode = node.ownerDocument.createTextNode('');
\r
14751 each(tinymce.grep(node.childNodes), function(n) { if (n.nodeType == 3) { liTextNode.nodeValue += n.nodeValue; n.parentNode.removeChild(n); } });
\r
14752 currentWrapElm = wrapElm.cloneNode(FALSE);
\r
14753 node.insertBefore(currentWrapElm, node.firstChild);
\r
14754 newWrappers.push(currentWrapElm);
\r
14757 currentWrapElm.appendChild(liTextNode);
\r
14760 // Start a new wrapper for possible children
\r
14761 currentWrapElm = 0;
\r
14763 each(tinymce.grep(node.childNodes), process);
\r
14765 // End the last wrapper
\r
14766 currentWrapElm = 0;
\r
14770 // Process siblings from range
\r
14771 each(nodes, process);
\r
14774 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
\r
14775 if (format.wrap_links === false) {
\r
14776 each(newWrappers, function(node) {
\r
14777 function process(node) {
\r
14778 var i, currentWrapElm, children;
\r
14780 if (node.nodeName === 'A') {
\r
14781 currentWrapElm = wrapElm.cloneNode(FALSE);
\r
14782 newWrappers.push(currentWrapElm);
\r
14784 children = tinymce.grep(node.childNodes);
\r
14785 for (i = 0; i < children.length; i++)
\r
14786 currentWrapElm.appendChild(children[i]);
\r
14788 node.appendChild(currentWrapElm);
\r
14791 each(tinymce.grep(node.childNodes), process);
\r
14799 each(newWrappers, function(node) {
\r
14802 function getChildCount(node) {
\r
14805 each(node.childNodes, function(node) {
\r
14806 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
\r
14813 function mergeStyles(node) {
\r
14814 var child, clone;
\r
14816 each(node.childNodes, function(node) {
\r
14817 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
\r
14819 return FALSE; // break loop
\r
14823 // If child was found and of the same type as the current node
\r
14824 if (child && matchName(child, format)) {
\r
14825 clone = child.cloneNode(FALSE);
\r
14826 setElementFormat(clone);
\r
14828 dom.replace(clone, node, TRUE);
\r
14829 dom.remove(child, 1);
\r
14832 return clone || node;
\r
14835 childCount = getChildCount(node);
\r
14837 // Remove empty nodes but only if there is multiple wrappers and they are not block
\r
14838 // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
\r
14839 if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
\r
14840 dom.remove(node, 1);
\r
14844 if (format.inline || format.wrapper) {
\r
14845 // Merges the current node with it's children of similar type to reduce the number of elements
\r
14846 if (!format.exact && childCount === 1)
\r
14847 node = mergeStyles(node);
\r
14849 // Remove/merge children
\r
14850 each(formatList, function(format) {
\r
14851 // Merge all children of similar type will move styles from child to parent
\r
14852 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
\r
14853 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
\r
14854 each(dom.select(format.inline, node), function(child) {
\r
14857 // When wrap_links is set to false we don't want
\r
14858 // to remove the format on children within links
\r
14859 if (format.wrap_links === false) {
\r
14860 parent = child.parentNode;
\r
14863 if (parent.nodeName === 'A')
\r
14865 } while (parent = parent.parentNode);
\r
14868 removeFormat(format, vars, child, format.exact ? child : null);
\r
14872 // Remove child if direct parent is of same type
\r
14873 if (matchNode(node.parentNode, name, vars)) {
\r
14874 dom.remove(node, 1);
\r
14879 // Look for parent with similar style format
\r
14880 if (format.merge_with_parents) {
\r
14881 dom.getParent(node.parentNode, function(parent) {
\r
14882 if (matchNode(parent, name, vars)) {
\r
14883 dom.remove(node, 1);
\r
14890 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
\r
14892 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
\r
14893 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
\r
14901 rng = dom.createRng();
\r
14903 rng.setStartBefore(node);
\r
14904 rng.setEndAfter(node);
\r
14906 applyRngStyle(expandRng(rng, formatList));
\r
14908 if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
\r
14909 // Obtain selection node before selection is unselected by applyRngStyle()
\r
14910 var curSelNode = ed.selection.getNode();
\r
14912 // Apply formatting to selection
\r
14913 bookmark = selection.getBookmark();
\r
14914 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
\r
14916 // Colored nodes should be underlined so that the color of the underline matches the text color.
\r
14917 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
\r
14918 tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
\r
14919 processUnderlineAndColor(curSelNode);
\r
14922 selection.moveToBookmark(bookmark);
\r
14923 selection.setRng(moveStart(selection.getRng(TRUE)));
\r
14924 ed.nodeChanged();
\r
14926 performCaretAction('apply', name, vars);
\r
14931 function remove(name, vars, node) {
\r
14932 var formatList = get(name), format = formatList[0], bookmark, i, rng;
\r
14934 function moveStart(rng) {
\r
14935 var container = rng.startContainer,
\r
14936 offset = rng.startOffset,
\r
14937 walker, node, nodes, tmpNode;
\r
14939 // Convert text node into index if possible
\r
14940 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
\r
14941 container = container.parentNode;
\r
14942 offset = nodeIndex(container) + 1;
\r
14945 // Move startContainer/startOffset in to a suitable node
\r
14946 if (container.nodeType == 1) {
\r
14947 nodes = container.childNodes;
\r
14948 container = nodes[Math.min(offset, nodes.length - 1)];
\r
14949 walker = new TreeWalker(container);
\r
14951 // If offset is at end of the parent node walk to the next one
\r
14952 if (offset > nodes.length - 1)
\r
14955 for (node = walker.current(); node; node = walker.next()) {
\r
14956 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
\r
14957 // IE has a "neat" feature where it moves the start node into the closest element
\r
14958 // we can avoid this by inserting an element before it and then remove it after we set the selection
\r
14959 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
\r
14960 node.parentNode.insertBefore(tmpNode, node);
\r
14962 // Set selection and remove tmpNode
\r
14963 rng.setStart(node, 0);
\r
14964 selection.setRng(rng);
\r
14965 dom.remove(tmpNode);
\r
14973 // Merges the styles for each node
\r
14974 function process(node) {
\r
14975 var children, i, l;
\r
14977 // Grab the children first since the nodelist might be changed
\r
14978 children = tinymce.grep(node.childNodes);
\r
14980 // Process current node
\r
14981 for (i = 0, l = formatList.length; i < l; i++) {
\r
14982 if (removeFormat(formatList[i], vars, node, node))
\r
14986 // Process the children
\r
14987 if (format.deep) {
\r
14988 for (i = 0, l = children.length; i < l; i++)
\r
14989 process(children[i]);
\r
14993 function findFormatRoot(container) {
\r
14996 // Find format root
\r
14997 each(getParents(container.parentNode).reverse(), function(parent) {
\r
15000 // Find format root element
\r
15001 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
\r
15002 // Is the node matching the format we are looking for
\r
15003 format = matchNode(parent, name, vars);
\r
15004 if (format && format.split !== false)
\r
15005 formatRoot = parent;
\r
15009 return formatRoot;
\r
15012 function wrapAndSplit(format_root, container, target, split) {
\r
15013 var parent, clone, lastClone, firstClone, i, formatRootParent;
\r
15015 // Format root found then clone formats and split it
\r
15016 if (format_root) {
\r
15017 formatRootParent = format_root.parentNode;
\r
15019 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
\r
15020 clone = parent.cloneNode(FALSE);
\r
15022 for (i = 0; i < formatList.length; i++) {
\r
15023 if (removeFormat(formatList[i], vars, clone, clone)) {
\r
15029 // Build wrapper node
\r
15032 clone.appendChild(lastClone);
\r
15035 firstClone = clone;
\r
15037 lastClone = clone;
\r
15041 // Never split block elements if the format is mixed
\r
15042 if (split && (!format.mixed || !isBlock(format_root)))
\r
15043 container = dom.split(format_root, container);
\r
15045 // Wrap container in cloned formats
\r
15047 target.parentNode.insertBefore(lastClone, target);
\r
15048 firstClone.appendChild(target);
\r
15052 return container;
\r
15055 function splitToFormatRoot(container) {
\r
15056 return wrapAndSplit(findFormatRoot(container), container, container, true);
\r
15059 function unwrap(start) {
\r
15060 var node = dom.get(start ? '_start' : '_end'),
\r
15061 out = node[start ? 'firstChild' : 'lastChild'];
\r
15063 // If the end is placed within the start the result will be removed
\r
15064 // So this checks if the out node is a bookmark node if it is it
\r
15065 // checks for another more suitable node
\r
15066 if (isBookmarkNode(out))
\r
15067 out = out[start ? 'firstChild' : 'lastChild'];
\r
15069 dom.remove(node, true);
\r
15074 function removeRngStyle(rng) {
\r
15075 var startContainer, endContainer;
\r
15077 rng = expandRng(rng, formatList, TRUE);
\r
15079 if (format.split) {
\r
15080 startContainer = getContainer(rng, TRUE);
\r
15081 endContainer = getContainer(rng);
\r
15083 if (startContainer != endContainer) {
\r
15084 // Wrap start/end nodes in span element since these might be cloned/moved
\r
15085 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
\r
15086 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
\r
15088 // Split start/end
\r
15089 splitToFormatRoot(startContainer);
\r
15090 splitToFormatRoot(endContainer);
\r
15092 // Unwrap start/end to get real elements again
\r
15093 startContainer = unwrap(TRUE);
\r
15094 endContainer = unwrap();
\r
15096 startContainer = endContainer = splitToFormatRoot(startContainer);
\r
15098 // Update range positions since they might have changed after the split operations
\r
15099 rng.startContainer = startContainer.parentNode;
\r
15100 rng.startOffset = nodeIndex(startContainer);
\r
15101 rng.endContainer = endContainer.parentNode;
\r
15102 rng.endOffset = nodeIndex(endContainer) + 1;
\r
15105 // Remove items between start/end
\r
15106 rangeUtils.walk(rng, function(nodes) {
\r
15107 each(nodes, function(node) {
\r
15110 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
\r
15111 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
\r
15112 removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
\r
15120 rng = dom.createRng();
\r
15121 rng.setStartBefore(node);
\r
15122 rng.setEndAfter(node);
\r
15123 removeRngStyle(rng);
\r
15127 if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
\r
15128 bookmark = selection.getBookmark();
\r
15129 removeRngStyle(selection.getRng(TRUE));
\r
15130 selection.moveToBookmark(bookmark);
\r
15132 // 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
\r
15133 if (match(name, vars, selection.getStart())) {
\r
15134 moveStart(selection.getRng(true));
\r
15137 ed.nodeChanged();
\r
15139 performCaretAction('remove', name, vars);
\r
15142 function toggle(name, vars, node) {
\r
15143 var fmt = get(name);
\r
15145 if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
\r
15146 remove(name, vars, node);
\r
15148 apply(name, vars, node);
\r
15151 function matchNode(node, name, vars, similar) {
\r
15152 var formatList = get(name), format, i, classes;
\r
15154 function matchItems(node, format, item_name) {
\r
15155 var key, value, items = format[item_name], i;
\r
15157 // Check all items
\r
15159 // Non indexed object
\r
15160 if (items.length === undefined) {
\r
15161 for (key in items) {
\r
15162 if (items.hasOwnProperty(key)) {
\r
15163 if (item_name === 'attributes')
\r
15164 value = dom.getAttrib(node, key);
\r
15166 value = getStyle(node, key);
\r
15168 if (similar && !value && !format.exact)
\r
15171 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
\r
15176 // Only one match needed for indexed arrays
\r
15177 for (i = 0; i < items.length; i++) {
\r
15178 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
\r
15187 if (formatList && node) {
\r
15188 // Check each format in list
\r
15189 for (i = 0; i < formatList.length; i++) {
\r
15190 format = formatList[i];
\r
15192 // Name name, attributes, styles and classes
\r
15193 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
\r
15195 if (classes = format.classes) {
\r
15196 for (i = 0; i < classes.length; i++) {
\r
15197 if (!dom.hasClass(node, classes[i]))
\r
15208 function match(name, vars, node) {
\r
15209 var startNode, i;
\r
15211 function matchParents(node) {
\r
15212 // Find first node with similar format settings
\r
15213 node = dom.getParent(node, function(node) {
\r
15214 return !!matchNode(node, name, vars, true);
\r
15217 // Do an exact check on the similar format element
\r
15218 return matchNode(node, name, vars);
\r
15221 // Check specified node
\r
15223 return matchParents(node);
\r
15225 // Check pending formats
\r
15226 if (selection.isCollapsed()) {
\r
15227 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
15228 if (pendingFormats.apply[i].name == name)
\r
15232 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
15233 if (pendingFormats.remove[i].name == name)
\r
15237 return matchParents(selection.getNode());
\r
15240 // Check selected node
\r
15241 node = selection.getNode();
\r
15242 if (matchParents(node))
\r
15245 // Check start node if it's different
\r
15246 startNode = selection.getStart();
\r
15247 if (startNode != node) {
\r
15248 if (matchParents(startNode))
\r
15255 function matchAll(names, vars) {
\r
15256 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
\r
15258 // If the selection is collapsed then check pending formats
\r
15259 if (selection.isCollapsed()) {
\r
15260 for (ni = 0; ni < names.length; ni++) {
\r
15261 // If the name is to be removed, then stop it from being added
\r
15262 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
15263 name = names[ni];
\r
15265 if (pendingFormats.remove[i].name == name) {
\r
15266 checkedMap[name] = true;
\r
15272 // If the format is to be applied
\r
15273 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
15274 for (ni = 0; ni < names.length; ni++) {
\r
15275 name = names[ni];
\r
15277 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
\r
15278 checkedMap[name] = true;
\r
15279 matchedFormatNames.push(name);
\r
15285 // Check start of selection for formats
\r
15286 startElement = selection.getStart();
\r
15287 dom.getParent(startElement, function(node) {
\r
15290 for (i = 0; i < names.length; i++) {
\r
15293 if (!checkedMap[name] && matchNode(node, name, vars)) {
\r
15294 checkedMap[name] = true;
\r
15295 matchedFormatNames.push(name);
\r
15300 return matchedFormatNames;
\r
15303 function canApply(name) {
\r
15304 var formatList = get(name), startNode, parents, i, x, selector;
\r
15306 if (formatList) {
\r
15307 startNode = selection.getStart();
\r
15308 parents = getParents(startNode);
\r
15310 for (x = formatList.length - 1; x >= 0; x--) {
\r
15311 selector = formatList[x].selector;
\r
15313 // Format is not selector based, then always return TRUE
\r
15317 for (i = parents.length - 1; i >= 0; i--) {
\r
15318 if (dom.is(parents[i], selector))
\r
15327 // Expose to public
\r
15328 tinymce.extend(this, {
\r
15330 register : register,
\r
15335 matchAll : matchAll,
\r
15336 matchNode : matchNode,
\r
15337 canApply : canApply
\r
15340 // Private functions
\r
15342 function matchName(node, format) {
\r
15343 // Check for inline match
\r
15344 if (isEq(node, format.inline))
\r
15347 // Check for block match
\r
15348 if (isEq(node, format.block))
\r
15351 // Check for selector match
\r
15352 if (format.selector)
\r
15353 return dom.is(node, format.selector);
\r
15356 function isEq(str1, str2) {
\r
15357 str1 = str1 || '';
\r
15358 str2 = str2 || '';
\r
15360 str1 = '' + (str1.nodeName || str1);
\r
15361 str2 = '' + (str2.nodeName || str2);
\r
15363 return str1.toLowerCase() == str2.toLowerCase();
\r
15366 function getStyle(node, name) {
\r
15367 var styleVal = dom.getStyle(node, name);
\r
15369 // Force the format to hex
\r
15370 if (name == 'color' || name == 'backgroundColor')
\r
15371 styleVal = dom.toHex(styleVal);
\r
15373 // Opera will return bold as 700
\r
15374 if (name == 'fontWeight' && styleVal == 700)
\r
15375 styleVal = 'bold';
\r
15377 return '' + styleVal;
\r
15380 function replaceVars(value, vars) {
\r
15381 if (typeof(value) != "string")
\r
15382 value = value(vars);
\r
15384 value = value.replace(/%(\w+)/g, function(str, name) {
\r
15385 return vars[name] || str;
\r
15392 function isWhiteSpaceNode(node) {
\r
15393 return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
\r
15396 function wrap(node, name, attrs) {
\r
15397 var wrapper = dom.create(name, attrs);
\r
15399 node.parentNode.insertBefore(wrapper, node);
\r
15400 wrapper.appendChild(node);
\r
15405 function expandRng(rng, format, remove) {
\r
15406 var startContainer = rng.startContainer,
\r
15407 startOffset = rng.startOffset,
\r
15408 endContainer = rng.endContainer,
\r
15409 endOffset = rng.endOffset, sibling, lastIdx, leaf;
\r
15411 // This function walks up the tree if there is no siblings before/after the node
\r
15412 function findParentContainer(container, child_name, sibling_name, root) {
\r
15413 var parent, child;
\r
15415 root = root || dom.getRoot();
\r
15418 // Check if we can move up are we at root level or body level
\r
15419 parent = container.parentNode;
\r
15421 // Stop expanding on block elements or root depending on format
\r
15422 if (parent == root || (!format[0].block_expand && isBlock(parent)))
\r
15423 return container;
\r
15425 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
\r
15426 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
15427 return container;
\r
15429 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
15430 return container;
\r
15433 container = container.parentNode;
\r
15436 return container;
\r
15439 // This function walks down the tree to find the leaf at the selection.
\r
15440 // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
\r
15441 function findLeaf(node, offset) {
\r
15442 if (offset === undefined)
\r
15443 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
\r
15444 while (node && node.hasChildNodes()) {
\r
15445 node = node.childNodes[offset];
\r
15447 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
\r
15449 return { node: node, offset: offset };
\r
15452 // If index based start position then resolve it
\r
15453 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
\r
15454 lastIdx = startContainer.childNodes.length - 1;
\r
15455 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
\r
15457 if (startContainer.nodeType == 3)
\r
15461 // If index based end position then resolve it
\r
15462 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
\r
15463 lastIdx = endContainer.childNodes.length - 1;
\r
15464 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
\r
15466 if (endContainer.nodeType == 3)
\r
15467 endOffset = endContainer.nodeValue.length;
\r
15470 // Exclude bookmark nodes if possible
\r
15471 if (isBookmarkNode(startContainer.parentNode))
\r
15472 startContainer = startContainer.parentNode;
\r
15474 if (isBookmarkNode(startContainer))
\r
15475 startContainer = startContainer.nextSibling || startContainer;
\r
15477 if (isBookmarkNode(endContainer.parentNode)) {
\r
15478 endOffset = dom.nodeIndex(endContainer);
\r
15479 endContainer = endContainer.parentNode;
\r
15482 if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
\r
15483 endContainer = endContainer.previousSibling;
\r
15484 endOffset = endContainer.length;
\r
15487 if (format[0].inline) {
\r
15488 // Avoid applying formatting to a trailing space.
\r
15489 leaf = findLeaf(endContainer, endOffset);
\r
15491 while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
\r
15492 leaf = findLeaf(leaf.node.previousSibling);
\r
15494 if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
\r
15495 leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
\r
15497 if (leaf.offset > 1) {
\r
15498 endContainer = leaf.node;
\r
15499 endContainer.splitText(leaf.offset - 1);
\r
15500 } else if (leaf.node.previousSibling) {
\r
15501 endContainer = leaf.node.previousSibling;
\r
15507 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
\r
15508 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
\r
15509 // This will reduce the number of wrapper elements that needs to be created
\r
15510 // Move start point up the tree
\r
15511 if (format[0].inline || format[0].block_expand) {
\r
15512 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
15513 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
15516 // Expand start/end container to matching selector
\r
15517 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
\r
15518 function findSelectorEndPoint(container, sibling_name) {
\r
15519 var parents, i, y, curFormat;
\r
15521 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
\r
15522 container = container[sibling_name];
\r
15524 parents = getParents(container);
\r
15525 for (i = 0; i < parents.length; i++) {
\r
15526 for (y = 0; y < format.length; y++) {
\r
15527 curFormat = format[y];
\r
15529 // If collapsed state is set then skip formats that doesn't match that
\r
15530 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
\r
15533 if (dom.is(parents[i], curFormat.selector))
\r
15534 return parents[i];
\r
15538 return container;
\r
15541 // Find new startContainer/endContainer if there is better one
\r
15542 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
\r
15543 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
\r
15546 // Expand start/end container to matching block element or text node
\r
15547 if (format[0].block || format[0].selector) {
\r
15548 function findBlockEndPoint(container, sibling_name, sibling_name2) {
\r
15551 // Expand to block of similar type
\r
15552 if (!format[0].wrapper)
\r
15553 node = dom.getParent(container, format[0].block);
\r
15555 // Expand to first wrappable block element or any block element
\r
15557 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
\r
15559 // Exclude inner lists from wrapping
\r
15560 if (node && format[0].wrapper)
\r
15561 node = getParents(node, 'ul,ol').reverse()[0] || node;
\r
15563 // Didn't find a block element look for first/last wrappable element
\r
15565 node = container;
\r
15567 while (node[sibling_name] && !isBlock(node[sibling_name])) {
\r
15568 node = node[sibling_name];
\r
15570 // Break on BR but include it will be removed later on
\r
15571 // we can't remove it now since we need to check if it can be wrapped
\r
15572 if (isEq(node, 'br'))
\r
15577 return node || container;
\r
15580 // Find new startContainer/endContainer if there is better one
\r
15581 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
\r
15582 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
\r
15584 // Non block element then try to expand up the leaf
\r
15585 if (format[0].block) {
\r
15586 if (!isBlock(startContainer))
\r
15587 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
15589 if (!isBlock(endContainer))
\r
15590 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
15594 // Setup index for startContainer
\r
15595 if (startContainer.nodeType == 1) {
\r
15596 startOffset = nodeIndex(startContainer);
\r
15597 startContainer = startContainer.parentNode;
\r
15600 // Setup index for endContainer
\r
15601 if (endContainer.nodeType == 1) {
\r
15602 endOffset = nodeIndex(endContainer) + 1;
\r
15603 endContainer = endContainer.parentNode;
\r
15606 // Return new range like object
\r
15608 startContainer : startContainer,
\r
15609 startOffset : startOffset,
\r
15610 endContainer : endContainer,
\r
15611 endOffset : endOffset
\r
15615 function removeFormat(format, vars, node, compare_node) {
\r
15616 var i, attrs, stylesModified;
\r
15618 // Check if node matches format
\r
15619 if (!matchName(node, format))
\r
15622 // Should we compare with format attribs and styles
\r
15623 if (format.remove != 'all') {
\r
15625 each(format.styles, function(value, name) {
\r
15626 value = replaceVars(value, vars);
\r
15629 if (typeof(name) === 'number') {
\r
15631 compare_node = 0;
\r
15634 if (!compare_node || isEq(getStyle(compare_node, name), value))
\r
15635 dom.setStyle(node, name, '');
\r
15637 stylesModified = 1;
\r
15640 // Remove style attribute if it's empty
\r
15641 if (stylesModified && dom.getAttrib(node, 'style') == '') {
\r
15642 node.removeAttribute('style');
\r
15643 node.removeAttribute('data-mce-style');
\r
15646 // Remove attributes
\r
15647 each(format.attributes, function(value, name) {
\r
15650 value = replaceVars(value, vars);
\r
15653 if (typeof(name) === 'number') {
\r
15655 compare_node = 0;
\r
15658 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
\r
15659 // Keep internal classes
\r
15660 if (name == 'class') {
\r
15661 value = dom.getAttrib(node, name);
\r
15663 // Build new class value where everything is removed except the internal prefixed classes
\r
15665 each(value.split(/\s+/), function(cls) {
\r
15666 if (/mce\w+/.test(cls))
\r
15667 valueOut += (valueOut ? ' ' : '') + cls;
\r
15670 // We got some internal classes left
\r
15672 dom.setAttrib(node, name, valueOut);
\r
15678 // IE6 has a bug where the attribute doesn't get removed correctly
\r
15679 if (name == "class")
\r
15680 node.removeAttribute('className');
\r
15682 // Remove mce prefixed attributes
\r
15683 if (MCE_ATTR_RE.test(name))
\r
15684 node.removeAttribute('data-mce-' + name);
\r
15686 node.removeAttribute(name);
\r
15690 // Remove classes
\r
15691 each(format.classes, function(value) {
\r
15692 value = replaceVars(value, vars);
\r
15694 if (!compare_node || dom.hasClass(compare_node, value))
\r
15695 dom.removeClass(node, value);
\r
15698 // Check for non internal attributes
\r
15699 attrs = dom.getAttribs(node);
\r
15700 for (i = 0; i < attrs.length; i++) {
\r
15701 if (attrs[i].nodeName.indexOf('_') !== 0)
\r
15706 // Remove the inline child if it's empty for example <b> or <span>
\r
15707 if (format.remove != 'none') {
\r
15708 removeNode(node, format);
\r
15713 function removeNode(node, format) {
\r
15714 var parentNode = node.parentNode, rootBlockElm;
\r
15716 if (format.block) {
\r
15717 if (!forcedRootBlock) {
\r
15718 function find(node, next, inc) {
\r
15719 node = getNonWhiteSpaceSibling(node, next, inc);
\r
15721 return !node || (node.nodeName == 'BR' || isBlock(node));
\r
15724 // Append BR elements if needed before we remove the block
\r
15725 if (isBlock(node) && !isBlock(parentNode)) {
\r
15726 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
\r
15727 node.insertBefore(dom.create('br'), node.firstChild);
\r
15729 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
\r
15730 node.appendChild(dom.create('br'));
\r
15733 // Wrap the block in a forcedRootBlock if we are at the root of document
\r
15734 if (parentNode == dom.getRoot()) {
\r
15735 if (!format.list_block || !isEq(node, format.list_block)) {
\r
15736 each(tinymce.grep(node.childNodes), function(node) {
\r
15737 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
\r
15738 if (!rootBlockElm)
\r
15739 rootBlockElm = wrap(node, forcedRootBlock);
\r
15741 rootBlockElm.appendChild(node);
\r
15743 rootBlockElm = 0;
\r
15750 // Never remove nodes that isn't the specified inline element if a selector is specified too
\r
15751 if (format.selector && format.inline && !isEq(format.inline, node))
\r
15754 dom.remove(node, 1);
\r
15757 function getNonWhiteSpaceSibling(node, next, inc) {
\r
15759 next = next ? 'nextSibling' : 'previousSibling';
\r
15761 for (node = inc ? node : node[next]; node; node = node[next]) {
\r
15762 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
\r
15768 function isBookmarkNode(node) {
\r
15769 return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
\r
15772 function mergeSiblings(prev, next) {
\r
15773 var marker, sibling, tmpSibling;
\r
15775 function compareElements(node1, node2) {
\r
15776 // Not the same name
\r
15777 if (node1.nodeName != node2.nodeName)
\r
15780 function getAttribs(node) {
\r
15781 var attribs = {};
\r
15783 each(dom.getAttribs(node), function(attr) {
\r
15784 var name = attr.nodeName.toLowerCase();
\r
15786 // Don't compare internal attributes or style
\r
15787 if (name.indexOf('_') !== 0 && name !== 'style')
\r
15788 attribs[name] = dom.getAttrib(node, name);
\r
15794 function compareObjects(obj1, obj2) {
\r
15797 for (name in obj1) {
\r
15798 // Obj1 has item obj2 doesn't have
\r
15799 if (obj1.hasOwnProperty(name)) {
\r
15800 value = obj2[name];
\r
15802 // Obj2 doesn't have obj1 item
\r
15803 if (value === undefined)
\r
15806 // Obj2 item has a different value
\r
15807 if (obj1[name] != value)
\r
15810 // Delete similar value
\r
15811 delete obj2[name];
\r
15815 // Check if obj 2 has something obj 1 doesn't have
\r
15816 for (name in obj2) {
\r
15817 // Obj2 has item obj1 doesn't have
\r
15818 if (obj2.hasOwnProperty(name))
\r
15825 // Attribs are not the same
\r
15826 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
\r
15829 // Styles are not the same
\r
15830 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
\r
15836 // Check if next/prev exists and that they are elements
\r
15837 if (prev && next) {
\r
15838 function findElementSibling(node, sibling_name) {
\r
15839 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
\r
15840 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
\r
15843 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
15850 // If previous sibling is empty then jump over it
\r
15851 prev = findElementSibling(prev, 'previousSibling');
\r
15852 next = findElementSibling(next, 'nextSibling');
\r
15854 // Compare next and previous nodes
\r
15855 if (compareElements(prev, next)) {
\r
15856 // Append nodes between
\r
15857 for (sibling = prev.nextSibling; sibling && sibling != next;) {
\r
15858 tmpSibling = sibling;
\r
15859 sibling = sibling.nextSibling;
\r
15860 prev.appendChild(tmpSibling);
\r
15863 // Remove next node
\r
15864 dom.remove(next);
\r
15866 // Move children into prev node
\r
15867 each(tinymce.grep(next.childNodes), function(node) {
\r
15868 prev.appendChild(node);
\r
15878 function isTextBlock(name) {
\r
15879 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
\r
15882 function getContainer(rng, start) {
\r
15883 var container, offset, lastIdx;
\r
15885 container = rng[start ? 'startContainer' : 'endContainer'];
\r
15886 offset = rng[start ? 'startOffset' : 'endOffset'];
\r
15888 if (container.nodeType == 1) {
\r
15889 lastIdx = container.childNodes.length - 1;
\r
15891 if (!start && offset)
\r
15894 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
\r
15897 return container;
\r
15900 function performCaretAction(type, name, vars) {
\r
15901 var i, currentPendingFormats = pendingFormats[type],
\r
15902 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
\r
15904 function hasPending() {
\r
15905 return pendingFormats.apply.length || pendingFormats.remove.length;
\r
15908 function resetPending() {
\r
15909 pendingFormats.apply = [];
\r
15910 pendingFormats.remove = [];
\r
15913 function perform(caret_node) {
\r
15914 // Apply pending formats
\r
15915 each(pendingFormats.apply.reverse(), function(item) {
\r
15916 apply(item.name, item.vars, caret_node);
\r
15918 // Colored nodes should be underlined so that the color of the underline matches the text color.
\r
15919 if (item.name === 'forecolor' && item.vars.value)
\r
15920 processUnderlineAndColor(caret_node.parentNode);
\r
15923 // Remove pending formats
\r
15924 each(pendingFormats.remove.reverse(), function(item) {
\r
15925 remove(item.name, item.vars, caret_node);
\r
15928 dom.remove(caret_node, 1);
\r
15932 // Check if it already exists then ignore it
\r
15933 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
\r
15934 if (currentPendingFormats[i].name == name)
\r
15938 currentPendingFormats.push({name : name, vars : vars});
\r
15940 // Check if it's in the other type, then remove it
\r
15941 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
\r
15942 if (otherPendingFormats[i].name == name)
\r
15943 otherPendingFormats.splice(i, 1);
\r
15946 // Pending apply or remove formats
\r
15947 if (hasPending()) {
\r
15948 ed.getDoc().execCommand('FontName', false, 'mceinline');
\r
15949 pendingFormats.lastRng = selection.getRng();
\r
15951 // IE will convert the current word
\r
15952 each(dom.select('font,span'), function(node) {
\r
15955 if (isCaretNode(node)) {
\r
15956 bookmark = selection.getBookmark();
\r
15958 selection.moveToBookmark(bookmark);
\r
15959 ed.nodeChanged();
\r
15963 // Only register listeners once if we need to
\r
15964 if (!pendingFormats.isListening && hasPending()) {
\r
15965 pendingFormats.isListening = true;
\r
15967 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
\r
15968 ed[event].addToTop(function(ed, e) {
\r
15969 // Do we have pending formats and is the selection moved has moved
\r
15970 if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
\r
15971 each(dom.select('font,span'), function(node) {
\r
15972 var textNode, rng;
\r
15974 // Look for marker
\r
15975 if (isCaretNode(node)) {
\r
15976 textNode = node.firstChild;
\r
15978 // Find the first text node within node
\r
15979 while (textNode && textNode.nodeType != 3)
\r
15980 textNode = textNode.firstChild;
\r
15985 rng = dom.createRng();
\r
15986 rng.setStart(textNode, textNode.nodeValue.length);
\r
15987 rng.setEnd(textNode, textNode.nodeValue.length);
\r
15988 selection.setRng(rng);
\r
15989 ed.nodeChanged();
\r
15991 dom.remove(node);
\r
15995 // Always unbind and clear pending styles on keyup
\r
15996 if (e.type == 'keyup' || e.type == 'mouseup')
\r
16007 tinymce.onAddEditor.add(function(tinymce, ed) {
\r
16008 var filters, fontSizes, dom, settings = ed.settings;
\r
16010 if (settings.inline_styles) {
\r
16011 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
16013 function replaceWithSpan(node, styles) {
\r
16014 tinymce.each(styles, function(value, name) {
\r
16016 dom.setStyle(node, name, value);
\r
16019 dom.rename(node, 'span');
\r
16023 font : function(dom, node) {
\r
16024 replaceWithSpan(node, {
\r
16025 backgroundColor : node.style.backgroundColor,
\r
16026 color : node.color,
\r
16027 fontFamily : node.face,
\r
16028 fontSize : fontSizes[parseInt(node.size) - 1]
\r
16032 u : function(dom, node) {
\r
16033 replaceWithSpan(node, {
\r
16034 textDecoration : 'underline'
\r
16038 strike : function(dom, node) {
\r
16039 replaceWithSpan(node, {
\r
16040 textDecoration : 'line-through'
\r
16045 function convert(editor, params) {
\r
16046 dom = editor.dom;
\r
16048 if (settings.convert_fonts_to_spans) {
\r
16049 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
\r
16050 filters[node.nodeName.toLowerCase()](ed.dom, node);
\r
16055 ed.onPreProcess.add(convert);
\r
16056 ed.onSetContent.add(convert);
\r
16058 ed.onInit.add(function() {
\r
16059 ed.selection.onSetContent.add(convert);
\r