2 var whiteSpaceRe = /^\s*|\s*$/g,
\r
3 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
\r
8 minorVersion : '3.9.2',
\r
10 releaseDate : '2010-09-29',
\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 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
\r
32 if (win.tinyMCEPreInit) {
\r
33 t.suffix = tinyMCEPreInit.suffix;
\r
34 t.baseURL = tinyMCEPreInit.base;
\r
35 t.query = tinyMCEPreInit.query;
\r
39 // Get suffix and base
\r
42 // If base element found, add that infront of baseURL
\r
43 nl = d.getElementsByTagName('base');
\r
44 for (i=0; i<nl.length; i++) {
\r
45 if (v = nl[i].href) {
\r
46 // Host only value like http://site.com or http://site.com:8008
\r
47 if (/^https?:\/\/[^\/]+$/.test(v))
\r
50 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
\r
54 function getBase(n) {
\r
55 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
\r
56 if (/_(src|dev)\.js/g.test(n.src))
\r
59 if ((p = n.src.indexOf('?')) != -1)
\r
60 t.query = n.src.substring(p + 1);
\r
62 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
\r
64 // If path to script is relative and a base href was found add that one infront
\r
65 // the src property will always be an absolute one on non IE browsers and IE 8
\r
66 // so this logic will basically only be executed on older IE versions
\r
67 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
\r
68 t.baseURL = base + t.baseURL;
\r
77 nl = d.getElementsByTagName('script');
\r
78 for (i=0; i<nl.length; i++) {
\r
84 n = d.getElementsByTagName('head')[0];
\r
86 nl = n.getElementsByTagName('script');
\r
87 for (i=0; i<nl.length; i++) {
\r
96 is : function(o, t) {
\r
98 return o !== undefined;
\r
100 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
\r
103 return typeof(o) == t;
\r
106 each : function(o, cb, s) {
\r
114 if (o.length !== undefined) {
\r
115 // Indexed arrays, needed for Safari
\r
116 for (n=0, l = o.length; n < l; n++) {
\r
117 if (cb.call(s, o[n], n, o) === false)
\r
123 if (o.hasOwnProperty(n)) {
\r
124 if (cb.call(s, o[n], n, o) === false)
\r
134 map : function(a, f) {
\r
137 tinymce.each(a, function(v) {
\r
144 grep : function(a, f) {
\r
147 tinymce.each(a, function(v) {
\r
155 inArray : function(a, v) {
\r
159 for (i = 0, l = a.length; i < l; i++) {
\r
168 extend : function(o, e) {
\r
169 var i, l, a = arguments;
\r
171 for (i = 1, l = a.length; i < l; i++) {
\r
174 tinymce.each(e, function(v, n) {
\r
175 if (v !== undefined)
\r
184 trim : function(s) {
\r
185 return (s ? '' + s : '').replace(whiteSpaceRe, '');
\r
188 create : function(s, p) {
\r
189 var t = this, sp, ns, cn, scn, c, de = 0;
\r
191 // Parse : <prefix> <class>:<super class>
\r
192 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
\r
193 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
\r
195 // Create namespace for new class
\r
196 ns = t.createNS(s[3].replace(/\.\w+$/, ''));
\r
198 // Class already exists
\r
202 // Make pure static class
\r
203 if (s[2] == 'static') {
\r
207 this.onCreate(s[2], s[3], ns[cn]);
\r
212 // Create default constructor
\r
214 p[cn] = function() {};
\r
218 // Add constructor and methods
\r
220 t.extend(ns[cn].prototype, p);
\r
224 sp = t.resolve(s[5]).prototype;
\r
225 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
\r
227 // Extend constructor
\r
230 // Add passthrough constructor
\r
231 ns[cn] = function() {
\r
232 return sp[scn].apply(this, arguments);
\r
235 // Add inherit constructor
\r
236 ns[cn] = function() {
\r
237 this.parent = sp[scn];
\r
238 return c.apply(this, arguments);
\r
241 ns[cn].prototype[cn] = ns[cn];
\r
243 // Add super methods
\r
244 t.each(sp, function(f, n) {
\r
245 ns[cn].prototype[n] = sp[n];
\r
248 // Add overridden methods
\r
249 t.each(p, function(f, n) {
\r
250 // Extend methods if needed
\r
252 ns[cn].prototype[n] = function() {
\r
253 this.parent = sp[n];
\r
254 return f.apply(this, arguments);
\r
258 ns[cn].prototype[n] = f;
\r
263 // Add static methods
\r
264 t.each(p['static'], function(f, n) {
\r
269 this.onCreate(s[2], s[3], ns[cn].prototype);
\r
272 walk : function(o, f, n, s) {
\r
279 tinymce.each(o, function(o, i) {
\r
280 if (f.call(s, o, i, n) === false)
\r
283 tinymce.walk(o, f, n, s);
\r
288 createNS : function(n, o) {
\r
294 for (i=0; i<n.length; i++) {
\r
306 resolve : function(n, o) {
\r
312 for (i = 0, l = n.length; i < l; i++) {
\r
322 addUnload : function(f, s) {
\r
325 f = {func : f, scope : s || this};
\r
328 function unload() {
\r
329 var li = t.unloads, o, n;
\r
332 // Call unload handlers
\r
337 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
\r
340 // Detach unload function
\r
341 if (win.detachEvent) {
\r
342 win.detachEvent('onbeforeunload', fakeUnload);
\r
343 win.detachEvent('onunload', unload);
\r
344 } else if (win.removeEventListener)
\r
345 win.removeEventListener('unload', unload, false);
\r
347 // Destroy references
\r
348 t.unloads = o = li = w = unload = 0;
\r
350 // Run garbarge collector on IE
\r
351 if (win.CollectGarbage)
\r
356 function fakeUnload() {
\r
359 // Is there things still loading, then do some magic
\r
360 if (d.readyState == 'interactive') {
\r
362 // Prevent memory leak
\r
363 d.detachEvent('onstop', stop);
\r
365 // Call unload handler
\r
372 // Fire unload when the currently loading page is stopped
\r
374 d.attachEvent('onstop', stop);
\r
376 // Remove onstop listener after a while to prevent the unload function
\r
377 // to execute if the user presses cancel in an onbeforeunload
\r
378 // confirm dialog and then presses the browser stop button
\r
379 win.setTimeout(function() {
\r
381 d.detachEvent('onstop', stop);
\r
386 // Attach unload handler
\r
387 if (win.attachEvent) {
\r
388 win.attachEvent('onunload', unload);
\r
389 win.attachEvent('onbeforeunload', fakeUnload);
\r
390 } else if (win.addEventListener)
\r
391 win.addEventListener('unload', unload, false);
\r
393 // Setup initial unload handler array
\r
401 removeUnload : function(f) {
\r
402 var u = this.unloads, r = null;
\r
404 tinymce.each(u, function(o, i) {
\r
405 if (o && o.func == f) {
\r
415 explode : function(s, d) {
\r
416 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
\r
419 _addVer : function(u) {
\r
425 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
\r
427 if (u.indexOf('#') == -1)
\r
430 return u.replace('#', v + '#');
\r
433 // Fix function for IE 9 where regexps isn't working correctly
\r
434 // Todo: remove me once MS fixes the bug
\r
435 _replace : function(find, replace, str) {
\r
436 // On IE9 we have to fake $x replacement
\r
437 if (isRegExpBroken) {
\r
438 return str.replace(find, function() {
\r
439 var val = replace, args = arguments, i;
\r
441 for (i = 0; i < args.length - 2; i++) {
\r
442 if (args[i] === undefined) {
\r
443 val = val.replace(new RegExp('\\$' + i, 'g'), '');
\r
445 val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
\r
453 return str.replace(find, replace);
\r
458 // Initialize the API
\r
461 // Expose tinymce namespace to the global namespace (window)
\r
462 win.tinymce = win.tinyMCE = tinymce;
\r
466 tinymce.create('tinymce.util.Dispatcher', {
\r
470 Dispatcher : function(s) {
\r
471 this.scope = s || this;
\r
472 this.listeners = [];
\r
475 add : function(cb, s) {
\r
476 this.listeners.push({cb : cb, scope : s || this.scope});
\r
481 addToTop : function(cb, s) {
\r
482 this.listeners.unshift({cb : cb, scope : s || this.scope});
\r
487 remove : function(cb) {
\r
488 var l = this.listeners, o = null;
\r
490 tinymce.each(l, function(c, i) {
\r
501 dispatch : function() {
\r
502 var s, a = arguments, i, li = this.listeners, c;
\r
504 // Needs to be a real loop since the listener count might change while looping
\r
505 // And this is also more efficient
\r
506 for (i = 0; i<li.length; i++) {
\r
508 s = c.cb.apply(c.scope, a);
\r
520 var each = tinymce.each;
\r
522 tinymce.create('tinymce.util.URI', {
\r
523 URI : function(u, s) {
\r
524 var t = this, o, a, b;
\r
527 u = tinymce.trim(u);
\r
529 // Default settings
\r
530 s = t.settings = s || {};
\r
532 // Strange app protocol or local anchor
\r
533 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
\r
538 // Absolute path with no host, fake host and protocol
\r
539 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
\r
540 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
\r
542 // Relative path http:// or protocol relative //path
\r
543 if (!/^\w*:?\/\//.test(u))
\r
544 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
\r
546 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
\r
547 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
\r
548 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
\r
549 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
\r
552 // Zope 3 workaround, they use @@something
\r
554 s = s.replace(/\(mce_at\)/g, '@@');
\r
559 if (b = s.base_uri) {
\r
561 t.protocol = b.protocol;
\r
564 t.userInfo = b.userInfo;
\r
566 if (!t.port && t.host == 'mce_host')
\r
569 if (!t.host || t.host == 'mce_host')
\r
575 //t.path = t.path || '/';
\r
578 setPath : function(p) {
\r
581 p = /^(.*?)\/?(\w+)?$/.exec(p);
\r
583 // Update path parts
\r
585 t.directory = p[1];
\r
593 toRelative : function(u) {
\r
599 u = new tinymce.util.URI(u, {base_uri : t});
\r
601 // Not on same domain/port or protocol
\r
602 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
\r
605 o = t.toRelPath(t.path, u.path);
\r
609 o += '?' + u.query;
\r
613 o += '#' + u.anchor;
\r
618 toAbsolute : function(u, nh) {
\r
619 var u = new tinymce.util.URI(u, {base_uri : this});
\r
621 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
\r
624 toRelPath : function(base, path) {
\r
625 var items, bp = 0, out = '', i, l;
\r
628 base = base.substring(0, base.lastIndexOf('/'));
\r
629 base = base.split('/');
\r
630 items = path.split('/');
\r
632 if (base.length >= items.length) {
\r
633 for (i = 0, l = base.length; i < l; i++) {
\r
634 if (i >= items.length || base[i] != items[i]) {
\r
641 if (base.length < items.length) {
\r
642 for (i = 0, l = items.length; i < l; i++) {
\r
643 if (i >= base.length || base[i] != items[i]) {
\r
653 for (i = 0, l = base.length - (bp - 1); i < l; i++)
\r
656 for (i = bp - 1, l = items.length; i < l; i++) {
\r
658 out += "/" + items[i];
\r
666 toAbsPath : function(base, path) {
\r
667 var i, nb = 0, o = [], tr, outPath;
\r
670 tr = /\/$/.test(path) ? '/' : '';
\r
671 base = base.split('/');
\r
672 path = path.split('/');
\r
674 // Remove empty chunks
\r
675 each(base, function(k) {
\r
682 // Merge relURLParts chunks
\r
683 for (i = path.length - 1, o = []; i >= 0; i--) {
\r
684 // Ignore empty or .
\r
685 if (path[i].length == 0 || path[i] == ".")
\r
689 if (path[i] == '..') {
\r
703 i = base.length - nb;
\r
707 outPath = o.reverse().join('/');
\r
709 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
\r
711 // Add front / if it's needed
\r
712 if (outPath.indexOf('/') !== 0)
\r
713 outPath = '/' + outPath;
\r
715 // Add traling / if it's needed
\r
716 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
\r
722 getURI : function(nh) {
\r
726 if (!t.source || nh) {
\r
731 s += t.protocol + '://';
\r
734 s += t.userInfo + '@';
\r
747 s += '?' + t.query;
\r
750 s += '#' + t.anchor;
\r
761 var each = tinymce.each;
\r
763 tinymce.create('static tinymce.util.Cookie', {
\r
764 getHash : function(n) {
\r
765 var v = this.get(n), h;
\r
768 each(v.split('&'), function(v) {
\r
771 h[unescape(v[0])] = unescape(v[1]);
\r
778 setHash : function(n, v, e, p, d, s) {
\r
781 each(v, function(v, k) {
\r
782 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
\r
785 this.set(n, o, e, p, d, s);
\r
788 get : function(n) {
\r
789 var c = document.cookie, e, p = n + "=", b;
\r
795 b = c.indexOf("; " + p);
\r
805 e = c.indexOf(";", b);
\r
810 return unescape(c.substring(b + p.length, e));
\r
813 set : function(n, v, e, p, d, s) {
\r
814 document.cookie = n + "=" + escape(v) +
\r
815 ((e) ? "; expires=" + e.toGMTString() : "") +
\r
816 ((p) ? "; path=" + escape(p) : "") +
\r
817 ((d) ? "; domain=" + d : "") +
\r
818 ((s) ? "; secure" : "");
\r
821 remove : function(n, p) {
\r
822 var d = new Date();
\r
824 d.setTime(d.getTime() - 1000);
\r
826 this.set(n, '', d, p, d);
\r
831 tinymce.create('static tinymce.util.JSON', {
\r
832 serialize : function(o) {
\r
833 var i, v, s = tinymce.util.JSON.serialize, t;
\r
840 if (t == 'string') {
\r
841 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
\r
843 return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
\r
847 return '\\' + v.charAt(i + 1);
\r
849 a = b.charCodeAt().toString(16);
\r
851 return '\\u' + '0000'.substring(a.length) + a;
\r
855 if (t == 'object') {
\r
856 if (o.hasOwnProperty && o instanceof Array) {
\r
857 for (i=0, v = '['; i<o.length; i++)
\r
858 v += (i > 0 ? ',' : '') + s(o[i]);
\r
866 v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
\r
874 parse : function(s) {
\r
876 return eval('(' + s + ')');
\r
884 tinymce.create('static tinymce.util.XHR', {
\r
885 send : function(o) {
\r
886 var x, t, w = window, c = 0;
\r
888 // Default settings
\r
889 o.scope = o.scope || this;
\r
890 o.success_scope = o.success_scope || o.scope;
\r
891 o.error_scope = o.error_scope || o.scope;
\r
892 o.async = o.async === false ? false : true;
\r
893 o.data = o.data || '';
\r
899 x = new ActiveXObject(s);
\r
906 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
\r
909 if (x.overrideMimeType)
\r
910 x.overrideMimeType(o.content_type);
\r
912 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
\r
914 if (o.content_type)
\r
915 x.setRequestHeader('Content-Type', o.content_type);
\r
917 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
\r
922 if (!o.async || x.readyState == 4 || c++ > 10000) {
\r
923 if (o.success && c < 10000 && x.status == 200)
\r
924 o.success.call(o.success_scope, '' + x.responseText, x, o);
\r
926 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
\r
930 w.setTimeout(ready, 10);
\r
933 // Syncronous request
\r
937 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
\r
938 t = w.setTimeout(ready, 10);
\r
944 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
\r
946 tinymce.create('tinymce.util.JSONRequest', {
\r
947 JSONRequest : function(s) {
\r
948 this.settings = extend({
\r
953 send : function(o) {
\r
954 var ecb = o.error, scb = o.success;
\r
956 o = extend(this.settings, o);
\r
958 o.success = function(c, x) {
\r
961 if (typeof(c) == 'undefined') {
\r
963 error : 'JSON Parse error.'
\r
968 ecb.call(o.error_scope || o.scope, c.error, x);
\r
970 scb.call(o.success_scope || o.scope, c.result);
\r
973 o.error = function(ty, x) {
\r
974 ecb.call(o.error_scope || o.scope, ty, x);
\r
977 o.data = JSON.serialize({
\r
978 id : o.id || 'c' + (this.count++),
\r
983 // JSON content type for Ruby on rails. Bug: #1883287
\r
984 o.content_type = 'application/json';
\r
990 sendRPC : function(o) {
\r
991 return new tinymce.util.JSONRequest().send(o);
\r
996 (function(tinymce) {
\r
998 var each = tinymce.each,
\r
1000 isWebKit = tinymce.isWebKit,
\r
1001 isIE = tinymce.isIE,
\r
1002 blockRe = /^(H[1-6R]|P|DIV|ADDRESS|PRE|FORM|T(ABLE|BODY|HEAD|FOOT|H|R|D)|LI|OL|UL|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|MENU|ISINDEX|SAMP)$/,
\r
1003 boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
\r
1004 mceAttribs = makeMap('src,href,style,coords,shape'),
\r
1005 encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'},
\r
1006 encodeCharsRe = /[<>&\"]/g,
\r
1007 simpleSelectorRe = /^([a-z0-9],?)+$/i,
\r
1008 tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
\r
1009 attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
\r
1011 function makeMap(str) {
\r
1014 str = str.split(',');
\r
1015 for (i = str.length; i >= 0; i--)
\r
1021 tinymce.create('tinymce.dom.DOMUtils', {
\r
1025 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
\r
1027 "for" : "htmlFor",
\r
1028 "class" : "className",
\r
1029 className : "className",
\r
1030 checked : "checked",
\r
1031 disabled : "disabled",
\r
1032 maxlength : "maxLength",
\r
1033 readonly : "readOnly",
\r
1034 selected : "selected",
\r
1041 DOMUtils : function(d, s) {
\r
1042 var t = this, globalStyle;
\r
1047 t.cssFlicker = false;
\r
1049 t.stdMode = d.documentMode >= 8;
\r
1050 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
\r
1052 t.settings = s = tinymce.extend({
\r
1053 keep_values : false,
\r
1058 // Fix IE6SP2 flicker and check it failed for pre SP2
\r
1059 if (tinymce.isIE6) {
\r
1061 d.execCommand('BackgroundImageCache', false, true);
\r
1063 t.cssFlicker = true;
\r
1067 // Build styles list
\r
1068 if (s.valid_styles) {
\r
1071 // Convert styles into a rule list
\r
1072 each(s.valid_styles, function(value, key) {
\r
1073 t._styles[key] = tinymce.explode(value);
\r
1077 tinymce.addUnload(t.destroy, t);
\r
1080 getRoot : function() {
\r
1081 var t = this, s = t.settings;
\r
1083 return (s && t.get(s.root_element)) || t.doc.body;
\r
1086 getViewPort : function(w) {
\r
1089 w = !w ? this.win : w;
\r
1091 b = this.boxModel ? d.documentElement : d.body;
\r
1093 // Returns viewport size excluding scrollbars
\r
1095 x : w.pageXOffset || b.scrollLeft,
\r
1096 y : w.pageYOffset || b.scrollTop,
\r
1097 w : w.innerWidth || b.clientWidth,
\r
1098 h : w.innerHeight || b.clientHeight
\r
1102 getRect : function(e) {
\r
1103 var p, t = this, sr;
\r
1107 sr = t.getSize(e);
\r
1117 getSize : function(e) {
\r
1118 var t = this, w, h;
\r
1121 w = t.getStyle(e, 'width');
\r
1122 h = t.getStyle(e, 'height');
\r
1124 // Non pixel value, then force offset/clientWidth
\r
1125 if (w.indexOf('px') === -1)
\r
1128 // Non pixel value, then force offset/clientWidth
\r
1129 if (h.indexOf('px') === -1)
\r
1133 w : parseInt(w) || e.offsetWidth || e.clientWidth,
\r
1134 h : parseInt(h) || e.offsetHeight || e.clientHeight
\r
1138 getParent : function(n, f, r) {
\r
1139 return this.getParents(n, f, r, false);
\r
1142 getParents : function(n, f, r, c) {
\r
1143 var t = this, na, se = t.settings, o = [];
\r
1146 c = c === undefined;
\r
1148 if (se.strict_root)
\r
1149 r = r || t.getRoot();
\r
1151 // Wrap node name as func
\r
1152 if (is(f, 'string')) {
\r
1156 f = function(n) {return n.nodeType == 1;};
\r
1159 return t.is(n, na);
\r
1165 if (n == r || !n.nodeType || n.nodeType === 9)
\r
1178 return c ? o : null;
\r
1181 get : function(e) {
\r
1184 if (e && this.doc && typeof(e) == 'string') {
\r
1186 e = this.doc.getElementById(e);
\r
1188 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
\r
1189 if (e && e.id !== n)
\r
1190 return this.doc.getElementsByName(n)[1];
\r
1196 getNext : function(node, selector) {
\r
1197 return this._findSib(node, selector, 'nextSibling');
\r
1200 getPrev : function(node, selector) {
\r
1201 return this._findSib(node, selector, 'previousSibling');
\r
1205 select : function(pa, s) {
\r
1208 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
\r
1211 is : function(n, selector) {
\r
1214 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
\r
1215 if (n.length === undefined) {
\r
1216 // Simple all selector
\r
1217 if (selector === '*')
\r
1218 return n.nodeType == 1;
\r
1220 // Simple selector just elements
\r
1221 if (simpleSelectorRe.test(selector)) {
\r
1222 selector = selector.toLowerCase().split(/,/);
\r
1223 n = n.nodeName.toLowerCase();
\r
1225 for (i = selector.length - 1; i >= 0; i--) {
\r
1226 if (selector[i] == n)
\r
1234 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
\r
1238 add : function(p, n, a, h, c) {
\r
1241 return this.run(p, function(p) {
\r
1244 e = is(n, 'string') ? t.doc.createElement(n) : n;
\r
1245 t.setAttribs(e, a);
\r
1254 return !c ? p.appendChild(e) : e;
\r
1258 create : function(n, a, h) {
\r
1259 return this.add(this.doc.createElement(n), n, a, h, 1);
\r
1262 createHTML : function(n, a, h) {
\r
1263 var o = '', t = this, k;
\r
1268 if (a.hasOwnProperty(k))
\r
1269 o += ' ' + k + '="' + t.encode(a[k]) + '"';
\r
1272 // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
\r
1273 if (typeof(h) != "undefined")
\r
1274 return o + '>' + h + '</' + n + '>';
\r
1279 remove : function(node, keep_children) {
\r
1280 return this.run(node, function(node) {
\r
1281 var parent, child;
\r
1283 parent = node.parentNode;
\r
1288 if (keep_children) {
\r
1289 while (child = node.firstChild) {
\r
1290 // IE 8 will crash if you don't remove completely empty text nodes
\r
1291 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
\r
1292 parent.insertBefore(child, node);
\r
1294 node.removeChild(child);
\r
1298 return parent.removeChild(node);
\r
1302 setStyle : function(n, na, v) {
\r
1305 return t.run(n, function(e) {
\r
1310 // Camelcase it, if needed
\r
1311 na = na.replace(/-(\D)/g, function(a, b){
\r
1312 return b.toUpperCase();
\r
1315 // Default px suffix on these
\r
1316 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
\r
1321 // IE specific opacity
\r
1323 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
\r
1325 if (!n.currentStyle || !n.currentStyle.hasLayout)
\r
1326 s.display = 'inline-block';
\r
1329 // Fix for older browsers
\r
1330 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
\r
1334 isIE ? s.styleFloat = v : s.cssFloat = v;
\r
1341 // Force update of the style data
\r
1342 if (t.settings.update_styles)
\r
1343 t.setAttrib(e, '_mce_style');
\r
1347 getStyle : function(n, na, c) {
\r
1354 if (this.doc.defaultView && c) {
\r
1355 // Remove camelcase
\r
1356 na = na.replace(/[A-Z]/g, function(a){
\r
1361 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
\r
1363 // Old safari might fail
\r
1368 // Camelcase it, if needed
\r
1369 na = na.replace(/-(\D)/g, function(a, b){
\r
1370 return b.toUpperCase();
\r
1373 if (na == 'float')
\r
1374 na = isIE ? 'styleFloat' : 'cssFloat';
\r
1377 if (n.currentStyle && c)
\r
1378 return n.currentStyle[na];
\r
1380 return n.style[na];
\r
1383 setStyles : function(e, o) {
\r
1384 var t = this, s = t.settings, ol;
\r
1386 ol = s.update_styles;
\r
1387 s.update_styles = 0;
\r
1389 each(o, function(v, n) {
\r
1390 t.setStyle(e, n, v);
\r
1393 // Update style info
\r
1394 s.update_styles = ol;
\r
1395 if (s.update_styles)
\r
1396 t.setAttrib(e, s.cssText);
\r
1399 setAttrib : function(e, n, v) {
\r
1402 // Whats the point
\r
1406 // Strict XML mode
\r
1407 if (t.settings.strict)
\r
1408 n = n.toLowerCase();
\r
1410 return this.run(e, function(e) {
\r
1411 var s = t.settings;
\r
1415 if (!is(v, 'string')) {
\r
1416 each(v, function(v, n) {
\r
1417 t.setStyle(e, n, v);
\r
1423 // No mce_style for elements with these since they might get resized by the user
\r
1424 if (s.keep_values) {
\r
1425 if (v && !t._isRes(v))
\r
1426 e.setAttribute('_mce_style', v, 2);
\r
1428 e.removeAttribute('_mce_style', 2);
\r
1431 e.style.cssText = v;
\r
1435 e.className = v || ''; // Fix IE null bug
\r
1440 if (s.keep_values) {
\r
1441 if (s.url_converter)
\r
1442 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
\r
1444 t.setAttrib(e, '_mce_' + n, v, 2);
\r
1450 e.setAttribute('_mce_style', v);
\r
1454 if (is(v) && v !== null && v.length !== 0)
\r
1455 e.setAttribute(n, '' + v, 2);
\r
1457 e.removeAttribute(n, 2);
\r
1461 setAttribs : function(e, o) {
\r
1464 return this.run(e, function(e) {
\r
1465 each(o, function(v, n) {
\r
1466 t.setAttrib(e, n, v);
\r
1471 getAttrib : function(e, n, dv) {
\r
1476 if (!e || e.nodeType !== 1)
\r
1482 // Try the mce variant for these
\r
1483 if (/^(src|href|style|coords|shape)$/.test(n)) {
\r
1484 v = e.getAttribute("_mce_" + n);
\r
1490 if (isIE && t.props[n]) {
\r
1491 v = e[t.props[n]];
\r
1492 v = v && v.nodeValue ? v.nodeValue : v;
\r
1496 v = e.getAttribute(n, 2);
\r
1498 // Check boolean attribs
\r
1499 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
\r
1500 if (e[t.props[n]] === true && v === '')
\r
1503 return v ? n : '';
\r
1506 // Inner input elements will override attributes on form elements
\r
1507 if (e.nodeName === "FORM" && e.getAttributeNode(n))
\r
1508 return e.getAttributeNode(n).nodeValue;
\r
1510 if (n === 'style') {
\r
1511 v = v || e.style.cssText;
\r
1514 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
\r
1516 if (t.settings.keep_values && !t._isRes(v))
\r
1517 e.setAttribute('_mce_style', v);
\r
1521 // Remove Apple and WebKit stuff
\r
1522 if (isWebKit && n === "class" && v)
\r
1523 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
\r
1525 // Handle IE issues
\r
1530 // IE returns 1 as default value
\r
1537 // IE returns +0 as default value for size
\r
1538 if (v === '+0' || v === 20 || v === 0)
\r
1555 // IE returns -1 as default value
\r
1563 // IE returns default value
\r
1564 if (v === 32768 || v === 2147483647 || v === '32768')
\r
1579 v = v.toLowerCase();
\r
1583 // IE has odd anonymous function for event attributes
\r
1584 if (n.indexOf('on') === 0 && v)
\r
1585 v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
\r
1589 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
\r
1592 getPos : function(n, ro) {
\r
1593 var t = this, x = 0, y = 0, e, d = t.doc, r;
\r
1596 ro = ro || d.body;
\r
1599 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
\r
1600 if (isIE && !t.stdMode) {
\r
1601 n = n.getBoundingClientRect();
\r
1602 e = t.boxModel ? d.documentElement : d.body;
\r
1603 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
\r
1604 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
\r
1606 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
\r
1610 while (r && r != ro && r.nodeType) {
\r
1611 x += r.offsetLeft || 0;
\r
1612 y += r.offsetTop || 0;
\r
1613 r = r.offsetParent;
\r
1617 while (r && r != ro && r.nodeType) {
\r
1618 x -= r.scrollLeft || 0;
\r
1619 y -= r.scrollTop || 0;
\r
1624 return {x : x, y : y};
\r
1627 parseStyle : function(st) {
\r
1628 var t = this, s = t.settings, o = {};
\r
1633 function compress(p, s, ot) {
\r
1636 // Get values and check it it needs compressing
\r
1637 t = o[p + '-top' + s];
\r
1641 r = o[p + '-right' + s];
\r
1645 b = o[p + '-bottom' + s];
\r
1649 l = o[p + '-left' + s];
\r
1655 delete o[p + '-top' + s];
\r
1656 delete o[p + '-right' + s];
\r
1657 delete o[p + '-bottom' + s];
\r
1658 delete o[p + '-left' + s];
\r
1661 function compress2(ta, a, b, c) {
\r
1677 o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
\r
1683 st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
\r
1685 each(st.split(';'), function(v) {
\r
1689 v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
\r
1690 v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
\r
1692 sv = tinymce.trim(v[1]);
\r
1693 sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
\r
1695 sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
\r
1696 return t.toHex(v);
\r
1699 if (s.url_converter) {
\r
1700 sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
\r
1701 return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
\r
1705 o[tinymce.trim(v[0]).toLowerCase()] = sv;
\r
1709 compress("border", "", "border");
\r
1710 compress("border", "-width", "border-width");
\r
1711 compress("border", "-color", "border-color");
\r
1712 compress("border", "-style", "border-style");
\r
1713 compress("padding", "", "padding");
\r
1714 compress("margin", "", "margin");
\r
1715 compress2('border', 'border-width', 'border-style', 'border-color');
\r
1718 // Remove pointless border
\r
1719 if (o.border == 'medium none')
\r
1726 serializeStyle : function(o, name) {
\r
1727 var t = this, s = '';
\r
1729 function add(v, k) {
\r
1731 // Remove browser specific styles like -moz- or -webkit-
\r
1732 if (k.indexOf('-') === 0)
\r
1736 case 'font-weight':
\r
1737 // Opera will output bold as 700
\r
1744 case 'background-color':
\r
1745 v = v.toLowerCase();
\r
1749 s += (s ? ' ' : '') + k + ': ' + v + ';';
\r
1753 // Validate style output
\r
1754 if (name && t._styles) {
\r
1755 each(t._styles['*'], function(name) {
\r
1756 add(o[name], name);
\r
1759 each(t._styles[name.toLowerCase()], function(name) {
\r
1760 add(o[name], name);
\r
1768 loadCSS : function(u) {
\r
1769 var t = this, d = t.doc, head;
\r
1774 head = t.select('head')[0];
\r
1776 each(u.split(','), function(u) {
\r
1782 t.files[u] = true;
\r
1783 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
\r
1785 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
\r
1786 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
\r
1787 // It's ugly but it seems to work fine.
\r
1788 if (isIE && d.documentMode && d.recalc) {
\r
1789 link.onload = function() {
\r
1791 link.onload = null;
\r
1795 head.appendChild(link);
\r
1799 addClass : function(e, c) {
\r
1800 return this.run(e, function(e) {
\r
1806 if (this.hasClass(e, c))
\r
1807 return e.className;
\r
1809 o = this.removeClass(e, c);
\r
1811 return e.className = (o != '' ? (o + ' ') : '') + c;
\r
1815 removeClass : function(e, c) {
\r
1818 return t.run(e, function(e) {
\r
1821 if (t.hasClass(e, c)) {
\r
1823 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
\r
1825 v = e.className.replace(re, ' ');
\r
1826 v = tinymce.trim(v != ' ' ? v : '');
\r
1830 // Empty class attr
\r
1832 e.removeAttribute('class');
\r
1833 e.removeAttribute('className');
\r
1839 return e.className;
\r
1843 hasClass : function(n, c) {
\r
1849 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
\r
1852 show : function(e) {
\r
1853 return this.setStyle(e, 'display', 'block');
\r
1856 hide : function(e) {
\r
1857 return this.setStyle(e, 'display', 'none');
\r
1860 isHidden : function(e) {
\r
1863 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
\r
1866 uniqueId : function(p) {
\r
1867 return (!p ? 'mce_' : p) + (this.counter++);
\r
1870 setHTML : function(e, h) {
\r
1873 return this.run(e, function(e) {
\r
1874 var x, i, nl, n, p, x;
\r
1876 h = t.processHTML(h);
\r
1880 // Remove all child nodes
\r
1881 while (e.firstChild)
\r
1882 e.firstChild.removeNode();
\r
1885 // IE will remove comments from the beginning
\r
1886 // unless you padd the contents with something
\r
1887 e.innerHTML = '<br />' + h;
\r
1888 e.removeChild(e.firstChild);
\r
1890 // 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
1891 // This seems to fix this problem
\r
1893 // Create new div with HTML contents and a BR infront to keep comments
\r
1894 x = t.create('div');
\r
1895 x.innerHTML = '<br />' + h;
\r
1897 // Add all children from div to target
\r
1898 each (x.childNodes, function(n, i) {
\r
1899 // Skip br element
\r
1906 // IE has a serious bug when it comes to paragraphs it can produce an invalid
\r
1907 // DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
\r
1908 // It seems to be that IE doesn't like a root block element placed inside another root block element
\r
1909 if (t.settings.fix_ie_paragraphs)
\r
1910 h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true"> </p>');
\r
1914 if (t.settings.fix_ie_paragraphs) {
\r
1915 // Check for odd paragraphs this is a sign of a broken DOM
\r
1916 nl = e.getElementsByTagName("p");
\r
1917 for (i = nl.length - 1, x = 0; i >= 0; i--) {
\r
1920 if (!n.hasChildNodes()) {
\r
1921 if (!n._mce_keep) {
\r
1922 x = 1; // Is broken
\r
1926 n.removeAttribute('_mce_keep');
\r
1931 // Time to fix the madness IE left us
\r
1933 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
\r
1934 // after we use innerHTML we can fix the DOM tree
\r
1935 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
\r
1936 h = h.replace(/<\/p>/gi, '</div>');
\r
1938 // Set the new HTML with DIVs
\r
1941 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
\r
1942 // This is needed since IE has a annoying bug see above for details
\r
1943 // This is a slow process but it has to be done. :(
\r
1944 if (t.settings.fix_ie_paragraphs) {
\r
1945 nl = e.getElementsByTagName("DIV");
\r
1946 for (i = nl.length - 1; i >= 0; i--) {
\r
1949 // Is it a temp div
\r
1951 // Create new paragraph
\r
1952 p = t.doc.createElement('p');
\r
1954 // Copy all attributes
\r
1955 n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
\r
1958 if (b !== '_mce_tmp') {
\r
1959 v = n.getAttribute(b);
\r
1961 if (!v && b === 'class')
\r
1964 p.setAttribute(b, v);
\r
1968 // Append all children to new paragraph
\r
1969 for (x = 0; x<n.childNodes.length; x++)
\r
1970 p.appendChild(n.childNodes[x].cloneNode(true));
\r
1972 // Replace div with new paragraph
\r
1985 processHTML : function(h) {
\r
1986 var t = this, s = t.settings, codeBlocks = [];
\r
1988 if (!s.process_html)
\r
1992 h = h.replace(/'/g, '''); // IE can't handle apos
\r
1993 h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
\r
1996 // Force tags open, and on IE9 replace $1$2 that got left behind due to bugs in their RegExp engine
\r
1997 h = tinymce._replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>', h); // Force open
\r
1999 // Store away src and href in _mce_src and mce_href since browsers mess them up
\r
2000 if (s.keep_values) {
\r
2001 // Wrap scripts and styles in comments for serialization purposes
\r
2002 if (/<script|noscript|style/i.test(h)) {
\r
2003 function trim(s) {
\r
2004 // Remove prefix and suffix code for element
\r
2005 s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
\r
2006 s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
\r
2007 s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
\r
2008 s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
\r
2013 // Wrap the script contents in CDATA and keep them from executing
\r
2014 h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
\r
2015 // Force type attribute
\r
2017 attribs = ' type="text/javascript"';
\r
2019 // Convert the src attribute of the scripts
\r
2020 attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
\r
2021 if (s.url_converter)
\r
2022 url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
\r
2024 return '_mce_src="' + url + '"';
\r
2027 // Wrap text contents
\r
2028 if (tinymce.trim(text)) {
\r
2029 codeBlocks.push(trim(text));
\r
2030 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
\r
2033 return '<mce:script' + attribs + '>' + text + '</mce:script>';
\r
2036 // Wrap style elements
\r
2037 h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
\r
2038 // Wrap text contents
\r
2040 codeBlocks.push(trim(text));
\r
2041 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
\r
2044 return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
\r
2047 // Wrap noscript elements
\r
2048 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
2049 return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '--') + '--></mce:noscript>';
\r
2053 h = tinymce._replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->', h);
\r
2055 // This function processes the attributes in the HTML string to force boolean
\r
2056 // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
\r
2057 function processTags(html) {
\r
2058 return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
\r
2059 return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
\r
2062 name = name.toLowerCase();
\r
2063 value = value || val2 || val3 || "";
\r
2065 // Treat boolean attributes
\r
2066 if (boolAttrs[name]) {
\r
2067 // false or 0 is treated as a missing attribute
\r
2068 if (value === 'false' || value === '0')
\r
2071 return name + '="' + name + '"';
\r
2074 // Is attribute one that needs special treatment
\r
2075 if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
\r
2076 mceValue = t.decode(value);
\r
2078 // Convert URLs to relative/absolute ones
\r
2079 if (s.url_converter && (name == "src" || name == "href"))
\r
2080 mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
\r
2082 // Process styles lowercases them and compresses them
\r
2083 if (name == 'style')
\r
2084 mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
\r
2086 return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
\r
2094 h = processTags(h);
\r
2096 // Restore script blocks
\r
2097 h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
\r
2098 return codeBlocks[idx];
\r
2105 getOuterHTML : function(e) {
\r
2113 if (e.outerHTML !== undefined)
\r
2114 return e.outerHTML;
\r
2116 d = (e.ownerDocument || this.doc).createElement("body");
\r
2117 d.appendChild(e.cloneNode(true));
\r
2119 return d.innerHTML;
\r
2122 setOuterHTML : function(e, h, d) {
\r
2125 function setHTML(e, h, d) {
\r
2128 tp = d.createElement("body");
\r
2133 t.insertAfter(n.cloneNode(true), e);
\r
2134 n = n.previousSibling;
\r
2140 return this.run(e, function(e) {
\r
2143 // Only set HTML on elements
\r
2144 if (e.nodeType == 1) {
\r
2145 d = d || e.ownerDocument || t.doc;
\r
2149 // Try outerHTML for IE it sometimes produces an unknown runtime error
\r
2150 if (isIE && e.nodeType == 1)
\r
2155 // Fix for unknown runtime error
\r
2164 decode : function(s) {
\r
2167 // Look for entities to decode
\r
2168 if (/&[\w#]+;/.test(s)) {
\r
2169 // Decode the entities using a div element not super efficient but less code
\r
2170 e = this.doc.createElement("div");
\r
2178 } while (n = n.nextSibling);
\r
2187 encode : function(str) {
\r
2188 return ('' + str).replace(encodeCharsRe, function(chr) {
\r
2189 return encodedChars[chr];
\r
2193 insertAfter : function(node, reference_node) {
\r
2194 reference_node = this.get(reference_node);
\r
2196 return this.run(node, function(node) {
\r
2197 var parent, nextSibling;
\r
2199 parent = reference_node.parentNode;
\r
2200 nextSibling = reference_node.nextSibling;
\r
2203 parent.insertBefore(node, nextSibling);
\r
2205 parent.appendChild(node);
\r
2211 isBlock : function(n) {
\r
2212 if (n.nodeType && n.nodeType !== 1)
\r
2215 n = n.nodeName || n;
\r
2217 return blockRe.test(n);
\r
2220 replace : function(n, o, k) {
\r
2223 if (is(o, 'array'))
\r
2224 n = n.cloneNode(true);
\r
2226 return t.run(o, function(o) {
\r
2228 each(tinymce.grep(o.childNodes), function(c) {
\r
2233 return o.parentNode.replaceChild(n, o);
\r
2237 rename : function(elm, name) {
\r
2238 var t = this, newElm;
\r
2240 if (elm.nodeName != name.toUpperCase()) {
\r
2241 // Rename block element
\r
2242 newElm = t.create(name);
\r
2244 // Copy attribs to new block
\r
2245 each(t.getAttribs(elm), function(attr_node) {
\r
2246 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
\r
2250 t.replace(newElm, elm, 1);
\r
2253 return newElm || elm;
\r
2256 findCommonAncestor : function(a, b) {
\r
2262 while (pe && ps != pe)
\r
2263 pe = pe.parentNode;
\r
2268 ps = ps.parentNode;
\r
2271 if (!ps && a.ownerDocument)
\r
2272 return a.ownerDocument.documentElement;
\r
2277 toHex : function(s) {
\r
2278 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
\r
2281 s = parseInt(s).toString(16);
\r
2283 return s.length > 1 ? s : '0' + s; // 0 -> 00
\r
2287 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
\r
2295 getClasses : function() {
\r
2296 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
\r
2301 function addClasses(s) {
\r
2302 // IE style imports
\r
2303 each(s.imports, function(r) {
\r
2307 each(s.cssRules || s.rules, function(r) {
\r
2308 // Real type or fake it on IE
\r
2309 switch (r.type || 1) {
\r
2312 if (r.selectorText) {
\r
2313 each(r.selectorText.split(','), function(v) {
\r
2314 v = v.replace(/^\s*|\s*$|^\s\./g, "");
\r
2316 // Is internal or it doesn't contain a class
\r
2317 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
\r
2320 // Remove everything but class name
\r
2322 v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
\r
2325 if (f && !(v = f(v, ov)))
\r
2329 cl.push({'class' : v});
\r
2338 addClasses(r.styleSheet);
\r
2345 each(t.doc.styleSheets, addClasses);
\r
2350 if (cl.length > 0)
\r
2356 run : function(e, f, s) {
\r
2359 if (t.doc && typeof(e) === 'string')
\r
2366 if (!e.nodeType && (e.length || e.length === 0)) {
\r
2369 each(e, function(e, i) {
\r
2371 if (typeof(e) == 'string')
\r
2372 e = t.doc.getElementById(e);
\r
2374 o.push(f.call(s, e, i));
\r
2381 return f.call(s, e);
\r
2384 getAttribs : function(n) {
\r
2395 // Object will throw exception in IE
\r
2396 if (n.nodeName == 'OBJECT')
\r
2397 return n.attributes;
\r
2399 // IE doesn't keep the selected attribute if you clone option elements
\r
2400 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
\r
2401 o.push({specified : 1, nodeName : 'selected'});
\r
2403 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
\r
2404 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
\r
2405 o.push({specified : 1, nodeName : a});
\r
2411 return n.attributes;
\r
2414 destroy : function(s) {
\r
2418 t.events.destroy();
\r
2420 t.win = t.doc = t.root = t.events = null;
\r
2422 // Manual destroy then remove unload handler
\r
2424 tinymce.removeUnload(t.destroy);
\r
2427 createRng : function() {
\r
2430 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
\r
2433 nodeIndex : function(node, normalized) {
\r
2434 var idx = 0, lastNodeType, lastNode, nodeType;
\r
2437 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
\r
2438 nodeType = node.nodeType;
\r
2440 // Normalize text nodes
\r
2441 if (normalized && nodeType == 3) {
\r
2442 if (nodeType == lastNodeType || !node.nodeValue.length)
\r
2447 lastNodeType = nodeType;
\r
2454 split : function(pe, e, re) {
\r
2455 var t = this, r = t.createRng(), bef, aft, pa;
\r
2457 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
\r
2458 // but we don't want that in our code since it serves no purpose for the end user
\r
2459 // For example if this is chopped:
\r
2460 // <p>text 1<span><b>CHOP</b></span>text 2</p>
\r
2462 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
\r
2463 // this function will then trim of empty edges and produce:
\r
2464 // <p>text 1</p><b>CHOP</b><p>text 2</p>
\r
2465 function trim(node) {
\r
2466 var i, children = node.childNodes;
\r
2468 if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
\r
2471 for (i = children.length - 1; i >= 0; i--)
\r
2472 trim(children[i]);
\r
2474 if (node.nodeType != 9) {
\r
2475 // Keep non whitespace text nodes
\r
2476 if (node.nodeType == 3 && node.nodeValue.length > 0) {
\r
2477 // If parent element isn't a block or there isn't any useful contents for example "<p> </p>"
\r
2478 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
\r
2482 if (node.nodeType == 1) {
\r
2483 // If the only child is a bookmark then move it up
\r
2484 children = node.childNodes;
\r
2485 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
\r
2486 node.parentNode.insertBefore(children[0], node);
\r
2488 // Keep non empty elements or img, hr etc
\r
2489 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
\r
2500 // Get before chunk
\r
2501 r.setStart(pe.parentNode, t.nodeIndex(pe));
\r
2502 r.setEnd(e.parentNode, t.nodeIndex(e));
\r
2503 bef = r.extractContents();
\r
2505 // Get after chunk
\r
2506 r = t.createRng();
\r
2507 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
\r
2508 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
\r
2509 aft = r.extractContents();
\r
2511 // Insert before chunk
\r
2512 pa = pe.parentNode;
\r
2513 pa.insertBefore(trim(bef), pe);
\r
2515 // Insert middle chunk
\r
2517 pa.replaceChild(re, e);
\r
2519 pa.insertBefore(e, pe);
\r
2521 // Insert after chunk
\r
2522 pa.insertBefore(trim(aft), pe);
\r
2529 bind : function(target, name, func, scope) {
\r
2533 t.events = new tinymce.dom.EventUtils();
\r
2535 return t.events.add(target, name, func, scope || this);
\r
2538 unbind : function(target, name, func) {
\r
2542 t.events = new tinymce.dom.EventUtils();
\r
2544 return t.events.remove(target, name, func);
\r
2548 _findSib : function(node, selector, name) {
\r
2549 var t = this, f = selector;
\r
2552 // If expression make a function of it using is
\r
2553 if (is(f, 'string')) {
\r
2554 f = function(node) {
\r
2555 return t.is(node, selector);
\r
2559 // Loop all siblings
\r
2560 for (node = node[name]; node; node = node[name]) {
\r
2569 _isRes : function(c) {
\r
2570 // Is live resizble element
\r
2571 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
\r
2575 walk : function(n, f, s) {
\r
2576 var d = this.doc, w;
\r
2578 if (d.createTreeWalker) {
\r
2579 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
\r
2581 while ((n = w.nextNode()) != null)
\r
2582 f.call(s || this, n);
\r
2584 tinymce.walk(n, f, 'childNodes', s);
\r
2589 toRGB : function(s) {
\r
2590 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
\r
2593 // #FFF -> #FFFFFF
\r
2595 c[3] = c[2] = c[1];
\r
2597 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
\r
2605 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
\r
2609 // Range constructor
\r
2610 function Range(dom) {
\r
2618 START_OFFSET = 'startOffset',
\r
2619 START_CONTAINER = 'startContainer',
\r
2620 END_CONTAINER = 'endContainer',
\r
2621 END_OFFSET = 'endOffset',
\r
2622 extend = tinymce.extend,
\r
2623 nodeIndex = dom.nodeIndex;
\r
2627 startContainer : doc,
\r
2629 endContainer : doc,
\r
2632 commonAncestorContainer : doc,
\r
2634 // Range constants
\r
2635 START_TO_START : 0,
\r
2641 setStart : setStart,
\r
2643 setStartBefore : setStartBefore,
\r
2644 setStartAfter : setStartAfter,
\r
2645 setEndBefore : setEndBefore,
\r
2646 setEndAfter : setEndAfter,
\r
2647 collapse : collapse,
\r
2648 selectNode : selectNode,
\r
2649 selectNodeContents : selectNodeContents,
\r
2650 compareBoundaryPoints : compareBoundaryPoints,
\r
2651 deleteContents : deleteContents,
\r
2652 extractContents : extractContents,
\r
2653 cloneContents : cloneContents,
\r
2654 insertNode : insertNode,
\r
2655 surroundContents : surroundContents,
\r
2656 cloneRange : cloneRange
\r
2659 function setStart(n, o) {
\r
2660 _setEndPoint(TRUE, n, o);
\r
2663 function setEnd(n, o) {
\r
2664 _setEndPoint(FALSE, n, o);
\r
2667 function setStartBefore(n) {
\r
2668 setStart(n.parentNode, nodeIndex(n));
\r
2671 function setStartAfter(n) {
\r
2672 setStart(n.parentNode, nodeIndex(n) + 1);
\r
2675 function setEndBefore(n) {
\r
2676 setEnd(n.parentNode, nodeIndex(n));
\r
2679 function setEndAfter(n) {
\r
2680 setEnd(n.parentNode, nodeIndex(n) + 1);
\r
2683 function collapse(ts) {
\r
2685 t[END_CONTAINER] = t[START_CONTAINER];
\r
2686 t[END_OFFSET] = t[START_OFFSET];
\r
2688 t[START_CONTAINER] = t[END_CONTAINER];
\r
2689 t[START_OFFSET] = t[END_OFFSET];
\r
2692 t.collapsed = TRUE;
\r
2695 function selectNode(n) {
\r
2696 setStartBefore(n);
\r
2700 function selectNodeContents(n) {
\r
2702 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
\r
2705 function compareBoundaryPoints(h, r) {
\r
2706 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
\r
2708 // Check START_TO_START
\r
2710 return _compareBoundaryPoints(sc, so, sc, so);
\r
2712 // Check START_TO_END
\r
2714 return _compareBoundaryPoints(sc, so, ec, eo);
\r
2716 // Check END_TO_END
\r
2718 return _compareBoundaryPoints(ec, eo, ec, eo);
\r
2720 // Check END_TO_START
\r
2722 return _compareBoundaryPoints(ec, eo, sc, so);
\r
2725 function deleteContents() {
\r
2726 _traverse(DELETE);
\r
2729 function extractContents() {
\r
2730 return _traverse(EXTRACT);
\r
2733 function cloneContents() {
\r
2734 return _traverse(CLONE);
\r
2737 function insertNode(n) {
\r
2738 var startContainer = this[START_CONTAINER],
\r
2739 startOffset = this[START_OFFSET], nn, o;
\r
2741 // Node is TEXT_NODE or CDATA
\r
2742 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
\r
2743 if (!startOffset) {
\r
2744 // At the start of text
\r
2745 startContainer.parentNode.insertBefore(n, startContainer);
\r
2746 } else if (startOffset >= startContainer.nodeValue.length) {
\r
2747 // At the end of text
\r
2748 dom.insertAfter(n, startContainer);
\r
2750 // Middle, need to split
\r
2751 nn = startContainer.splitText(startOffset);
\r
2752 startContainer.parentNode.insertBefore(n, nn);
\r
2755 // Insert element node
\r
2756 if (startContainer.childNodes.length > 0)
\r
2757 o = startContainer.childNodes[startOffset];
\r
2760 startContainer.insertBefore(n, o);
\r
2762 startContainer.appendChild(n);
\r
2766 function surroundContents(n) {
\r
2767 var f = t.extractContents();
\r
2774 function cloneRange() {
\r
2775 return extend(new Range(dom), {
\r
2776 startContainer : t[START_CONTAINER],
\r
2777 startOffset : t[START_OFFSET],
\r
2778 endContainer : t[END_CONTAINER],
\r
2779 endOffset : t[END_OFFSET],
\r
2780 collapsed : t.collapsed,
\r
2781 commonAncestorContainer : t.commonAncestorContainer
\r
2785 // Private methods
\r
2787 function _getSelectedNode(container, offset) {
\r
2790 if (container.nodeType == 3 /* TEXT_NODE */)
\r
2796 child = container.firstChild;
\r
2797 while (child && offset > 0) {
\r
2799 child = child.nextSibling;
\r
2808 function _isCollapsed() {
\r
2809 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
\r
2812 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
\r
2813 var c, offsetC, n, cmnRoot, childA, childB;
\r
2815 // In the first case the boundary-points have the same container. A is before B
\r
2816 // if its offset is less than the offset of B, A is equal to B if its offset is
\r
2817 // equal to the offset of B, and A is after B if its offset is greater than the
\r
2819 if (containerA == containerB) {
\r
2820 if (offsetA == offsetB)
\r
2821 return 0; // equal
\r
2823 if (offsetA < offsetB)
\r
2824 return -1; // before
\r
2826 return 1; // after
\r
2829 // In the second case a child node C of the container of A is an ancestor
\r
2830 // container of B. In this case, A is before B if the offset of A is less than or
\r
2831 // equal to the index of the child node C and A is after B otherwise.
\r
2833 while (c && c.parentNode != containerA)
\r
2838 n = containerA.firstChild;
\r
2840 while (n != c && offsetC < offsetA) {
\r
2842 n = n.nextSibling;
\r
2845 if (offsetA <= offsetC)
\r
2846 return -1; // before
\r
2848 return 1; // after
\r
2851 // In the third case a child node C of the container of B is an ancestor container
\r
2852 // of A. In this case, A is before B if the index of the child node C is less than
\r
2853 // the offset of B and A is after B otherwise.
\r
2855 while (c && c.parentNode != containerB) {
\r
2861 n = containerB.firstChild;
\r
2863 while (n != c && offsetC < offsetB) {
\r
2865 n = n.nextSibling;
\r
2868 if (offsetC < offsetB)
\r
2869 return -1; // before
\r
2871 return 1; // after
\r
2874 // In the fourth case, none of three other cases hold: the containers of A and B
\r
2875 // are siblings or descendants of sibling nodes. In this case, A is before B if
\r
2876 // the container of A is before the container of B in a pre-order traversal of the
\r
2877 // Ranges' context tree and A is after B otherwise.
\r
2878 cmnRoot = dom.findCommonAncestor(containerA, containerB);
\r
2879 childA = containerA;
\r
2881 while (childA && childA.parentNode != cmnRoot)
\r
2882 childA = childA.parentNode;
\r
2887 childB = containerB;
\r
2888 while (childB && childB.parentNode != cmnRoot)
\r
2889 childB = childB.parentNode;
\r
2894 if (childA == childB)
\r
2895 return 0; // equal
\r
2897 n = cmnRoot.firstChild;
\r
2900 return -1; // before
\r
2903 return 1; // after
\r
2905 n = n.nextSibling;
\r
2909 function _setEndPoint(st, n, o) {
\r
2913 t[START_CONTAINER] = n;
\r
2914 t[START_OFFSET] = o;
\r
2916 t[END_CONTAINER] = n;
\r
2917 t[END_OFFSET] = o;
\r
2920 // If one boundary-point of a Range is set to have a root container
\r
2921 // other than the current one for the Range, the Range is collapsed to
\r
2922 // the new position. This enforces the restriction that both boundary-
\r
2923 // points of a Range must have the same root container.
\r
2924 ec = t[END_CONTAINER];
\r
2925 while (ec.parentNode)
\r
2926 ec = ec.parentNode;
\r
2928 sc = t[START_CONTAINER];
\r
2929 while (sc.parentNode)
\r
2930 sc = sc.parentNode;
\r
2933 // The start position of a Range is guaranteed to never be after the
\r
2934 // end position. To enforce this restriction, if the start is set to
\r
2935 // be at a position after the end, the Range is collapsed to that
\r
2937 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
\r
2942 t.collapsed = _isCollapsed();
\r
2943 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
\r
2946 function _traverse(how) {
\r
2947 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
\r
2949 if (t[START_CONTAINER] == t[END_CONTAINER])
\r
2950 return _traverseSameContainer(how);
\r
2952 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
2953 if (p == t[START_CONTAINER])
\r
2954 return _traverseCommonStartContainer(c, how);
\r
2956 ++endContainerDepth;
\r
2959 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
2960 if (p == t[END_CONTAINER])
\r
2961 return _traverseCommonEndContainer(c, how);
\r
2963 ++startContainerDepth;
\r
2966 depthDiff = startContainerDepth - endContainerDepth;
\r
2968 startNode = t[START_CONTAINER];
\r
2969 while (depthDiff > 0) {
\r
2970 startNode = startNode.parentNode;
\r
2974 endNode = t[END_CONTAINER];
\r
2975 while (depthDiff < 0) {
\r
2976 endNode = endNode.parentNode;
\r
2980 // ascend the ancestor hierarchy until we have a common parent.
\r
2981 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
\r
2986 return _traverseCommonAncestors(startNode, endNode, how);
\r
2989 function _traverseSameContainer(how) {
\r
2990 var frag, s, sub, n, cnt, sibling, xferNode;
\r
2992 if (how != DELETE)
\r
2993 frag = doc.createDocumentFragment();
\r
2995 // If selection is empty, just return the fragment
\r
2996 if (t[START_OFFSET] == t[END_OFFSET])
\r
2999 // Text node needs special case handling
\r
3000 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
\r
3001 // get the substring
\r
3002 s = t[START_CONTAINER].nodeValue;
\r
3003 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
\r
3005 // set the original text node to its new value
\r
3006 if (how != CLONE) {
\r
3007 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
\r
3009 // Nothing is partially selected, so collapse to start point
\r
3013 if (how == DELETE)
\r
3016 frag.appendChild(doc.createTextNode(sub));
\r
3020 // Copy nodes between the start/end offsets.
\r
3021 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
\r
3022 cnt = t[END_OFFSET] - t[START_OFFSET];
\r
3025 sibling = n.nextSibling;
\r
3026 xferNode = _traverseFullySelected(n, how);
\r
3029 frag.appendChild( xferNode );
\r
3035 // Nothing is partially selected, so collapse to start point
\r
3042 function _traverseCommonStartContainer(endAncestor, how) {
\r
3043 var frag, n, endIdx, cnt, sibling, xferNode;
\r
3045 if (how != DELETE)
\r
3046 frag = doc.createDocumentFragment();
\r
3048 n = _traverseRightBoundary(endAncestor, how);
\r
3051 frag.appendChild(n);
\r
3053 endIdx = nodeIndex(endAncestor);
\r
3054 cnt = endIdx - t[START_OFFSET];
\r
3057 // Collapse to just before the endAncestor, which
\r
3058 // is partially selected.
\r
3059 if (how != CLONE) {
\r
3060 t.setEndBefore(endAncestor);
\r
3061 t.collapse(FALSE);
\r
3067 n = endAncestor.previousSibling;
\r
3069 sibling = n.previousSibling;
\r
3070 xferNode = _traverseFullySelected(n, how);
\r
3073 frag.insertBefore(xferNode, frag.firstChild);
\r
3079 // Collapse to just before the endAncestor, which
\r
3080 // is partially selected.
\r
3081 if (how != CLONE) {
\r
3082 t.setEndBefore(endAncestor);
\r
3083 t.collapse(FALSE);
\r
3089 function _traverseCommonEndContainer(startAncestor, how) {
\r
3090 var frag, startIdx, n, cnt, sibling, xferNode;
\r
3092 if (how != DELETE)
\r
3093 frag = doc.createDocumentFragment();
\r
3095 n = _traverseLeftBoundary(startAncestor, how);
\r
3097 frag.appendChild(n);
\r
3099 startIdx = nodeIndex(startAncestor);
\r
3100 ++startIdx; // Because we already traversed it....
\r
3102 cnt = t[END_OFFSET] - startIdx;
\r
3103 n = startAncestor.nextSibling;
\r
3105 sibling = n.nextSibling;
\r
3106 xferNode = _traverseFullySelected(n, how);
\r
3109 frag.appendChild(xferNode);
\r
3115 if (how != CLONE) {
\r
3116 t.setStartAfter(startAncestor);
\r
3123 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
\r
3124 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
\r
3126 if (how != DELETE)
\r
3127 frag = doc.createDocumentFragment();
\r
3129 n = _traverseLeftBoundary(startAncestor, how);
\r
3131 frag.appendChild(n);
\r
3133 commonParent = startAncestor.parentNode;
\r
3134 startOffset = nodeIndex(startAncestor);
\r
3135 endOffset = nodeIndex(endAncestor);
\r
3138 cnt = endOffset - startOffset;
\r
3139 sibling = startAncestor.nextSibling;
\r
3142 nextSibling = sibling.nextSibling;
\r
3143 n = _traverseFullySelected(sibling, how);
\r
3146 frag.appendChild(n);
\r
3148 sibling = nextSibling;
\r
3152 n = _traverseRightBoundary(endAncestor, how);
\r
3155 frag.appendChild(n);
\r
3157 if (how != CLONE) {
\r
3158 t.setStartAfter(startAncestor);
\r
3165 function _traverseRightBoundary(root, how) {
\r
3166 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
\r
3169 return _traverseNode(next, isFullySelected, FALSE, how);
\r
3171 parent = next.parentNode;
\r
3172 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3176 prevSibling = next.previousSibling;
\r
3177 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
\r
3179 if (how != DELETE)
\r
3180 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
\r
3182 isFullySelected = TRUE;
\r
3183 next = prevSibling;
\r
3186 if (parent == root)
\r
3187 return clonedParent;
\r
3189 next = parent.previousSibling;
\r
3190 parent = parent.parentNode;
\r
3192 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3194 if (how != DELETE)
\r
3195 clonedGrandParent.appendChild(clonedParent);
\r
3197 clonedParent = clonedGrandParent;
\r
3201 function _traverseLeftBoundary(root, how) {
\r
3202 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
\r
3205 return _traverseNode(next, isFullySelected, TRUE, how);
\r
3207 parent = next.parentNode;
\r
3208 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3212 nextSibling = next.nextSibling;
\r
3213 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
\r
3215 if (how != DELETE)
\r
3216 clonedParent.appendChild(clonedChild);
\r
3218 isFullySelected = TRUE;
\r
3219 next = nextSibling;
\r
3222 if (parent == root)
\r
3223 return clonedParent;
\r
3225 next = parent.nextSibling;
\r
3226 parent = parent.parentNode;
\r
3228 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3230 if (how != DELETE)
\r
3231 clonedGrandParent.appendChild(clonedParent);
\r
3233 clonedParent = clonedGrandParent;
\r
3237 function _traverseNode(n, isFullySelected, isLeft, how) {
\r
3238 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
\r
3240 if (isFullySelected)
\r
3241 return _traverseFullySelected(n, how);
\r
3243 if (n.nodeType == 3 /* TEXT_NODE */) {
\r
3244 txtValue = n.nodeValue;
\r
3247 offset = t[START_OFFSET];
\r
3248 newNodeValue = txtValue.substring(offset);
\r
3249 oldNodeValue = txtValue.substring(0, offset);
\r
3251 offset = t[END_OFFSET];
\r
3252 newNodeValue = txtValue.substring(0, offset);
\r
3253 oldNodeValue = txtValue.substring(offset);
\r
3257 n.nodeValue = oldNodeValue;
\r
3259 if (how == DELETE)
\r
3262 newNode = n.cloneNode(FALSE);
\r
3263 newNode.nodeValue = newNodeValue;
\r
3268 if (how == DELETE)
\r
3271 return n.cloneNode(FALSE);
\r
3274 function _traverseFullySelected(n, how) {
\r
3275 if (how != DELETE)
\r
3276 return how == CLONE ? n.cloneNode(TRUE) : n;
\r
3278 n.parentNode.removeChild(n);
\r
3286 function Selection(selection) {
\r
3287 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
\r
3289 // Returns a W3C DOM compatible range object by using the IE Range API
\r
3290 function getRange() {
\r
3291 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
\r
3293 // If selection is outside the current document just return an empty range
\r
3294 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
\r
3295 if (element.ownerDocument != dom.doc)
\r
3298 // Handle control selection or text selection of a image
\r
3299 if (ieRange.item || !element.hasChildNodes()) {
\r
3300 domRange.setStart(element.parentNode, dom.nodeIndex(element));
\r
3301 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
\r
3306 collapsed = selection.isCollapsed();
\r
3308 function findEndPoint(start) {
\r
3309 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
\r
3311 // Setup temp range and collapse it
\r
3312 checkRng = ieRange.duplicate();
\r
3313 checkRng.collapse(start);
\r
3315 // Create marker and insert it at the end of the endpoints parent
\r
3316 marker = dom.create('a');
\r
3317 parent = checkRng.parentElement();
\r
3319 // If parent doesn't have any children then set the container to that parent and the index to 0
\r
3320 if (!parent.hasChildNodes()) {
\r
3321 domRange[start ? 'setStart' : 'setEnd'](parent, 0);
\r
3325 parent.appendChild(marker);
\r
3326 checkRng.moveToElementText(marker);
\r
3327 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
\r
3328 if (position > 0) {
\r
3329 // The position is after the end of the parent element.
\r
3330 // This is the case where IE puts the caret to the left edge of a table.
\r
3331 domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
\r
3332 dom.remove(marker);
\r
3336 // Setup node list and endIndex
\r
3337 nodes = tinymce.grep(parent.childNodes);
\r
3338 endIndex = nodes.length - 1;
\r
3339 // Perform a binary search for the position
\r
3340 while (startIndex <= endIndex) {
\r
3341 index = Math.floor((startIndex + endIndex) / 2);
\r
3343 // Insert marker and check it's position relative to the selection
\r
3344 parent.insertBefore(marker, nodes[index]);
\r
3345 checkRng.moveToElementText(marker);
\r
3346 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
\r
3347 if (position > 0) {
\r
3348 // Marker is to the right
\r
3349 startIndex = index + 1;
\r
3350 } else if (position < 0) {
\r
3351 // Marker is to the left
\r
3352 endIndex = index - 1;
\r
3354 // Maker is where we are
\r
3360 // Setup container
\r
3361 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
\r
3363 // Handle element selection
\r
3364 if (container.nodeType == 1) {
\r
3365 dom.remove(marker);
\r
3367 // Find offset and container
\r
3368 offset = dom.nodeIndex(container);
\r
3369 container = container.parentNode;
\r
3371 // Move the offset if we are setting the end or the position is after an element
\r
3372 if (!start || index > 0)
\r
3375 // Calculate offset within text node
\r
3376 if (position > 0 || index == 0) {
\r
3377 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
\r
3378 offset = checkRng.text.length;
\r
3380 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
\r
3381 offset = container.nodeValue.length - checkRng.text.length;
\r
3384 dom.remove(marker);
\r
3387 domRange[start ? 'setStart' : 'setEnd'](container, offset);
\r
3390 // Find start point
\r
3391 findEndPoint(true);
\r
3393 // Find end point if needed
\r
3400 this.addRange = function(rng) {
\r
3401 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
\r
3403 function setEndPoint(start) {
\r
3404 var container, offset, marker, tmpRng, nodes;
\r
3406 marker = dom.create('a');
\r
3407 container = start ? startContainer : endContainer;
\r
3408 offset = start ? startOffset : endOffset;
\r
3409 tmpRng = ieRng.duplicate();
\r
3411 if (container == doc) {
\r
3416 if (container.nodeType == 3) {
\r
3417 container.parentNode.insertBefore(marker, container);
\r
3418 tmpRng.moveToElementText(marker);
\r
3419 tmpRng.moveStart('character', offset);
\r
3420 dom.remove(marker);
\r
3421 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
\r
3423 nodes = container.childNodes;
\r
3425 if (nodes.length) {
\r
3426 if (offset >= nodes.length) {
\r
3427 dom.insertAfter(marker, nodes[nodes.length - 1]);
\r
3429 container.insertBefore(marker, nodes[offset]);
\r
3432 tmpRng.moveToElementText(marker);
\r
3434 // Empty node selection for example <div>|</div>
\r
3435 marker = doc.createTextNode(invisibleChar);
\r
3436 container.appendChild(marker);
\r
3437 tmpRng.moveToElementText(marker.parentNode);
\r
3438 tmpRng.collapse(TRUE);
\r
3441 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
\r
3442 dom.remove(marker);
\r
3446 // Destroy cached range
\r
3449 // Setup some shorter versions
\r
3450 startContainer = rng.startContainer;
\r
3451 startOffset = rng.startOffset;
\r
3452 endContainer = rng.endContainer;
\r
3453 endOffset = rng.endOffset;
\r
3454 ieRng = body.createTextRange();
\r
3456 // If single element selection then try making a control selection out of it
\r
3457 if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
\r
3458 if (startOffset == endOffset - 1) {
\r
3460 ctrlRng = body.createControlRange();
\r
3461 ctrlRng.addElement(startContainer.childNodes[startOffset]);
\r
3463 ctrlRng.scrollIntoView();
\r
3471 // Set start/end point of selection
\r
3472 setEndPoint(true);
\r
3475 // Select the new range and scroll it into view
\r
3477 ieRng.scrollIntoView();
\r
3480 this.getRangeAt = function() {
\r
3481 // Setup new range if the cache is empty
\r
3482 if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
\r
3483 range = getRange();
\r
3485 // Store away text range for next call
\r
3486 lastIERng = selection.getRng();
\r
3489 // IE will say that the range is equal then produce an invalid argument exception
\r
3490 // if you perform specific operations in a keyup event. For example Ctrl+Del.
\r
3491 // This hack will invalidate the range cache if the exception occurs
\r
3493 range.startContainer.nextSibling;
\r
3495 range = getRange();
\r
3499 // Return cached range
\r
3503 this.destroy = function() {
\r
3504 // Destroy cached range and last IE range to avoid memory leaks
\r
3505 lastIERng = range = null;
\r
3509 // Expose the selection object
\r
3510 tinymce.dom.TridentSelection = Selection;
\r
3515 * Sizzle CSS Selector Engine - v1.0
\r
3516 * Copyright 2009, The Dojo Foundation
\r
3517 * Released under the MIT, BSD, and GPL Licenses.
\r
3518 * More information: http://sizzlejs.com/
\r
3522 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
\r
3524 toString = Object.prototype.toString,
\r
3525 hasDuplicate = false,
\r
3526 baseHasDuplicate = true;
\r
3528 // Here we check if the JavaScript engine is using some sort of
\r
3529 // optimization where it does not always call our comparision
\r
3530 // function. If that is the case, discard the hasDuplicate value.
\r
3531 // Thus far that includes Google Chrome.
\r
3532 [0, 0].sort(function(){
\r
3533 baseHasDuplicate = false;
\r
3537 var Sizzle = function(selector, context, results, seed) {
\r
3538 results = results || [];
\r
3539 context = context || document;
\r
3541 var origContext = context;
\r
3543 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
\r
3547 if ( !selector || typeof selector !== "string" ) {
\r
3551 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
\r
3552 soFar = selector, ret, cur, pop, i;
\r
3554 // Reset the position of the chunker regexp (start from head)
\r
3557 m = chunker.exec(soFar);
\r
3562 parts.push( m[1] );
\r
3571 if ( parts.length > 1 && origPOS.exec( selector ) ) {
\r
3572 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
\r
3573 set = posProcess( parts[0] + parts[1], context );
\r
3575 set = Expr.relative[ parts[0] ] ?
\r
3577 Sizzle( parts.shift(), context );
\r
3579 while ( parts.length ) {
\r
3580 selector = parts.shift();
\r
3582 if ( Expr.relative[ selector ] ) {
\r
3583 selector += parts.shift();
\r
3586 set = posProcess( selector, set );
\r
3590 // Take a shortcut and set the context if the root selector is an ID
\r
3591 // (but not if it'll be faster if the inner selector is an ID)
\r
3592 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
\r
3593 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
\r
3594 ret = Sizzle.find( parts.shift(), context, contextXML );
\r
3595 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
\r
3600 { expr: parts.pop(), set: makeArray(seed) } :
\r
3601 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
\r
3602 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
\r
3604 if ( parts.length > 0 ) {
\r
3605 checkSet = makeArray(set);
\r
3610 while ( parts.length ) {
\r
3611 cur = parts.pop();
\r
3614 if ( !Expr.relative[ cur ] ) {
\r
3617 pop = parts.pop();
\r
3620 if ( pop == null ) {
\r
3624 Expr.relative[ cur ]( checkSet, pop, contextXML );
\r
3627 checkSet = parts = [];
\r
3631 if ( !checkSet ) {
\r
3635 if ( !checkSet ) {
\r
3636 Sizzle.error( cur || selector );
\r
3639 if ( toString.call(checkSet) === "[object Array]" ) {
\r
3641 results.push.apply( results, checkSet );
\r
3642 } else if ( context && context.nodeType === 1 ) {
\r
3643 for ( i = 0; checkSet[i] != null; i++ ) {
\r
3644 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
\r
3645 results.push( set[i] );
\r
3649 for ( i = 0; checkSet[i] != null; i++ ) {
\r
3650 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
\r
3651 results.push( set[i] );
\r
3656 makeArray( checkSet, results );
\r
3660 Sizzle( extra, origContext, results, seed );
\r
3661 Sizzle.uniqueSort( results );
\r
3667 Sizzle.uniqueSort = function(results){
\r
3668 if ( sortOrder ) {
\r
3669 hasDuplicate = baseHasDuplicate;
\r
3670 results.sort(sortOrder);
\r
3672 if ( hasDuplicate ) {
\r
3673 for ( var i = 1; i < results.length; i++ ) {
\r
3674 if ( results[i] === results[i-1] ) {
\r
3675 results.splice(i--, 1);
\r
3684 Sizzle.matches = function(expr, set){
\r
3685 return Sizzle(expr, null, null, set);
\r
3688 Sizzle.find = function(expr, context, isXML){
\r
3695 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
\r
3696 var type = Expr.order[i], match;
\r
3698 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
\r
3699 var left = match[1];
\r
3700 match.splice(1,1);
\r
3702 if ( left.substr( left.length - 1 ) !== "\\" ) {
\r
3703 match[1] = (match[1] || "").replace(/\\/g, "");
\r
3704 set = Expr.find[ type ]( match, context, isXML );
\r
3705 if ( set != null ) {
\r
3706 expr = expr.replace( Expr.match[ type ], "" );
\r
3714 set = context.getElementsByTagName("*");
\r
3717 return {set: set, expr: expr};
\r
3720 Sizzle.filter = function(expr, set, inplace, not){
\r
3721 var old = expr, result = [], curLoop = set, match, anyFound,
\r
3722 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
\r
3724 while ( expr && set.length ) {
\r
3725 for ( var type in Expr.filter ) {
\r
3726 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
\r
3727 var filter = Expr.filter[ type ], found, item, left = match[1];
\r
3730 match.splice(1,1);
\r
3732 if ( left.substr( left.length - 1 ) === "\\" ) {
\r
3736 if ( curLoop === result ) {
\r
3740 if ( Expr.preFilter[ type ] ) {
\r
3741 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
\r
3744 anyFound = found = true;
\r
3745 } else if ( match === true ) {
\r
3751 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
\r
3753 found = filter( item, match, i, curLoop );
\r
3754 var pass = not ^ !!found;
\r
3756 if ( inplace && found != null ) {
\r
3760 curLoop[i] = false;
\r
3762 } else if ( pass ) {
\r
3763 result.push( item );
\r
3770 if ( found !== undefined ) {
\r
3775 expr = expr.replace( Expr.match[ type ], "" );
\r
3777 if ( !anyFound ) {
\r
3786 // Improper expression
\r
3787 if ( expr === old ) {
\r
3788 if ( anyFound == null ) {
\r
3789 Sizzle.error( expr );
\r
3801 Sizzle.error = function( msg ) {
\r
3802 throw "Syntax error, unrecognized expression: " + msg;
\r
3805 var Expr = Sizzle.selectors = {
\r
3806 order: [ "ID", "NAME", "TAG" ],
\r
3808 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
\r
3809 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
\r
3810 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
\r
3811 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
\r
3812 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
\r
3813 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
\r
3814 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
\r
3815 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
\r
3819 "class": "className",
\r
3823 href: function(elem){
\r
3824 return elem.getAttribute("href");
\r
3828 "+": function(checkSet, part){
\r
3829 var isPartStr = typeof part === "string",
\r
3830 isTag = isPartStr && !/\W/.test(part),
\r
3831 isPartStrNotTag = isPartStr && !isTag;
\r
3834 part = part.toLowerCase();
\r
3837 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
\r
3838 if ( (elem = checkSet[i]) ) {
\r
3839 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
\r
3841 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
\r
3847 if ( isPartStrNotTag ) {
\r
3848 Sizzle.filter( part, checkSet, true );
\r
3851 ">": function(checkSet, part){
\r
3852 var isPartStr = typeof part === "string",
\r
3853 elem, i = 0, l = checkSet.length;
\r
3855 if ( isPartStr && !/\W/.test(part) ) {
\r
3856 part = part.toLowerCase();
\r
3858 for ( ; i < l; i++ ) {
\r
3859 elem = checkSet[i];
\r
3861 var parent = elem.parentNode;
\r
3862 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
\r
3866 for ( ; i < l; i++ ) {
\r
3867 elem = checkSet[i];
\r
3869 checkSet[i] = isPartStr ?
\r
3871 elem.parentNode === part;
\r
3875 if ( isPartStr ) {
\r
3876 Sizzle.filter( part, checkSet, true );
\r
3880 "": function(checkSet, part, isXML){
\r
3881 var doneName = done++, checkFn = dirCheck, nodeCheck;
\r
3883 if ( typeof part === "string" && !/\W/.test(part) ) {
\r
3884 part = part.toLowerCase();
\r
3886 checkFn = dirNodeCheck;
\r
3889 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
\r
3891 "~": function(checkSet, part, isXML){
\r
3892 var doneName = done++, checkFn = dirCheck, nodeCheck;
\r
3894 if ( typeof part === "string" && !/\W/.test(part) ) {
\r
3895 part = part.toLowerCase();
\r
3897 checkFn = dirNodeCheck;
\r
3900 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
\r
3904 ID: function(match, context, isXML){
\r
3905 if ( typeof context.getElementById !== "undefined" && !isXML ) {
\r
3906 var m = context.getElementById(match[1]);
\r
3907 return m ? [m] : [];
\r
3910 NAME: function(match, context){
\r
3911 if ( typeof context.getElementsByName !== "undefined" ) {
\r
3912 var ret = [], results = context.getElementsByName(match[1]);
\r
3914 for ( var i = 0, l = results.length; i < l; i++ ) {
\r
3915 if ( results[i].getAttribute("name") === match[1] ) {
\r
3916 ret.push( results[i] );
\r
3920 return ret.length === 0 ? null : ret;
\r
3923 TAG: function(match, context){
\r
3924 return context.getElementsByTagName(match[1]);
\r
3928 CLASS: function(match, curLoop, inplace, result, not, isXML){
\r
3929 match = " " + match[1].replace(/\\/g, "") + " ";
\r
3935 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
\r
3937 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
\r
3939 result.push( elem );
\r
3941 } else if ( inplace ) {
\r
3942 curLoop[i] = false;
\r
3949 ID: function(match){
\r
3950 return match[1].replace(/\\/g, "");
\r
3952 TAG: function(match, curLoop){
\r
3953 return match[1].toLowerCase();
\r
3955 CHILD: function(match){
\r
3956 if ( match[1] === "nth" ) {
\r
3957 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
\r
3958 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
\r
3959 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
\r
3960 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
\r
3962 // calculate the numbers (first)n+(last) including if they are negative
\r
3963 match[2] = (test[1] + (test[2] || 1)) - 0;
\r
3964 match[3] = test[3] - 0;
\r
3967 // TODO: Move to normal caching system
\r
3968 match[0] = done++;
\r
3972 ATTR: function(match, curLoop, inplace, result, not, isXML){
\r
3973 var name = match[1].replace(/\\/g, "");
\r
3975 if ( !isXML && Expr.attrMap[name] ) {
\r
3976 match[1] = Expr.attrMap[name];
\r
3979 if ( match[2] === "~=" ) {
\r
3980 match[4] = " " + match[4] + " ";
\r
3985 PSEUDO: function(match, curLoop, inplace, result, not){
\r
3986 if ( match[1] === "not" ) {
\r
3987 // If we're dealing with a complex expression, or a simple one
\r
3988 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
\r
3989 match[3] = Sizzle(match[3], null, null, curLoop);
\r
3991 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
\r
3993 result.push.apply( result, ret );
\r
3997 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
\r
4003 POS: function(match){
\r
4004 match.unshift( true );
\r
4009 enabled: function(elem){
\r
4010 return elem.disabled === false && elem.type !== "hidden";
\r
4012 disabled: function(elem){
\r
4013 return elem.disabled === true;
\r
4015 checked: function(elem){
\r
4016 return elem.checked === true;
\r
4018 selected: function(elem){
\r
4019 // Accessing this property makes selected-by-default
\r
4020 // options in Safari work properly
\r
4021 elem.parentNode.selectedIndex;
\r
4022 return elem.selected === true;
\r
4024 parent: function(elem){
\r
4025 return !!elem.firstChild;
\r
4027 empty: function(elem){
\r
4028 return !elem.firstChild;
\r
4030 has: function(elem, i, match){
\r
4031 return !!Sizzle( match[3], elem ).length;
\r
4033 header: function(elem){
\r
4034 return (/h\d/i).test( elem.nodeName );
\r
4036 text: function(elem){
\r
4037 return "text" === elem.type;
\r
4039 radio: function(elem){
\r
4040 return "radio" === elem.type;
\r
4042 checkbox: function(elem){
\r
4043 return "checkbox" === elem.type;
\r
4045 file: function(elem){
\r
4046 return "file" === elem.type;
\r
4048 password: function(elem){
\r
4049 return "password" === elem.type;
\r
4051 submit: function(elem){
\r
4052 return "submit" === elem.type;
\r
4054 image: function(elem){
\r
4055 return "image" === elem.type;
\r
4057 reset: function(elem){
\r
4058 return "reset" === elem.type;
\r
4060 button: function(elem){
\r
4061 return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
\r
4063 input: function(elem){
\r
4064 return (/input|select|textarea|button/i).test(elem.nodeName);
\r
4068 first: function(elem, i){
\r
4071 last: function(elem, i, match, array){
\r
4072 return i === array.length - 1;
\r
4074 even: function(elem, i){
\r
4075 return i % 2 === 0;
\r
4077 odd: function(elem, i){
\r
4078 return i % 2 === 1;
\r
4080 lt: function(elem, i, match){
\r
4081 return i < match[3] - 0;
\r
4083 gt: function(elem, i, match){
\r
4084 return i > match[3] - 0;
\r
4086 nth: function(elem, i, match){
\r
4087 return match[3] - 0 === i;
\r
4089 eq: function(elem, i, match){
\r
4090 return match[3] - 0 === i;
\r
4094 PSEUDO: function(elem, match, i, array){
\r
4095 var name = match[1], filter = Expr.filters[ name ];
\r
4098 return filter( elem, i, match, array );
\r
4099 } else if ( name === "contains" ) {
\r
4100 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
\r
4101 } else if ( name === "not" ) {
\r
4102 var not = match[3];
\r
4104 for ( var j = 0, l = not.length; j < l; j++ ) {
\r
4105 if ( not[j] === elem ) {
\r
4112 Sizzle.error( "Syntax error, unrecognized expression: " + name );
\r
4115 CHILD: function(elem, match){
\r
4116 var type = match[1], node = elem;
\r
4120 while ( (node = node.previousSibling) ) {
\r
4121 if ( node.nodeType === 1 ) {
\r
4125 if ( type === "first" ) {
\r
4130 while ( (node = node.nextSibling) ) {
\r
4131 if ( node.nodeType === 1 ) {
\r
4137 var first = match[2], last = match[3];
\r
4139 if ( first === 1 && last === 0 ) {
\r
4143 var doneName = match[0],
\r
4144 parent = elem.parentNode;
\r
4146 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
\r
4148 for ( node = parent.firstChild; node; node = node.nextSibling ) {
\r
4149 if ( node.nodeType === 1 ) {
\r
4150 node.nodeIndex = ++count;
\r
4153 parent.sizcache = doneName;
\r
4156 var diff = elem.nodeIndex - last;
\r
4157 if ( first === 0 ) {
\r
4158 return diff === 0;
\r
4160 return ( diff % first === 0 && diff / first >= 0 );
\r
4164 ID: function(elem, match){
\r
4165 return elem.nodeType === 1 && elem.getAttribute("id") === match;
\r
4167 TAG: function(elem, match){
\r
4168 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
\r
4170 CLASS: function(elem, match){
\r
4171 return (" " + (elem.className || elem.getAttribute("class")) + " ")
\r
4172 .indexOf( match ) > -1;
\r
4174 ATTR: function(elem, match){
\r
4175 var name = match[1],
\r
4176 result = Expr.attrHandle[ name ] ?
\r
4177 Expr.attrHandle[ name ]( elem ) :
\r
4178 elem[ name ] != null ?
\r
4180 elem.getAttribute( name ),
\r
4181 value = result + "",
\r
4185 return result == null ?
\r
4190 value.indexOf(check) >= 0 :
\r
4192 (" " + value + " ").indexOf(check) >= 0 :
\r
4194 value && result !== false :
\r
4198 value.indexOf(check) === 0 :
\r
4200 value.substr(value.length - check.length) === check :
\r
4202 value === check || value.substr(0, check.length + 1) === check + "-" :
\r
4205 POS: function(elem, match, i, array){
\r
4206 var name = match[2], filter = Expr.setFilters[ name ];
\r
4209 return filter( elem, i, match, array );
\r
4215 var origPOS = Expr.match.POS,
\r
4216 fescape = function(all, num){
\r
4217 return "\\" + (num - 0 + 1);
\r
4220 for ( var type in Expr.match ) {
\r
4221 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
\r
4222 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
\r
4225 var makeArray = function(array, results) {
\r
4226 array = Array.prototype.slice.call( array, 0 );
\r
4229 results.push.apply( results, array );
\r
4236 // Perform a simple check to determine if the browser is capable of
\r
4237 // converting a NodeList to an array using builtin methods.
\r
4238 // Also verifies that the returned array holds DOM nodes
\r
4239 // (which is not the case in the Blackberry browser)
\r
4241 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
\r
4243 // Provide a fallback method if it does not work
\r
4245 makeArray = function(array, results) {
\r
4246 var ret = results || [], i = 0;
\r
4248 if ( toString.call(array) === "[object Array]" ) {
\r
4249 Array.prototype.push.apply( ret, array );
\r
4251 if ( typeof array.length === "number" ) {
\r
4252 for ( var l = array.length; i < l; i++ ) {
\r
4253 ret.push( array[i] );
\r
4256 for ( ; array[i]; i++ ) {
\r
4257 ret.push( array[i] );
\r
4268 if ( document.documentElement.compareDocumentPosition ) {
\r
4269 sortOrder = function( a, b ) {
\r
4270 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
\r
4272 hasDuplicate = true;
\r
4274 return a.compareDocumentPosition ? -1 : 1;
\r
4277 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
\r
4278 if ( ret === 0 ) {
\r
4279 hasDuplicate = true;
\r
4283 } else if ( "sourceIndex" in document.documentElement ) {
\r
4284 sortOrder = function( a, b ) {
\r
4285 if ( !a.sourceIndex || !b.sourceIndex ) {
\r
4287 hasDuplicate = true;
\r
4289 return a.sourceIndex ? -1 : 1;
\r
4292 var ret = a.sourceIndex - b.sourceIndex;
\r
4293 if ( ret === 0 ) {
\r
4294 hasDuplicate = true;
\r
4298 } else if ( document.createRange ) {
\r
4299 sortOrder = function( a, b ) {
\r
4300 if ( !a.ownerDocument || !b.ownerDocument ) {
\r
4302 hasDuplicate = true;
\r
4304 return a.ownerDocument ? -1 : 1;
\r
4307 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
\r
4308 aRange.setStart(a, 0);
\r
4309 aRange.setEnd(a, 0);
\r
4310 bRange.setStart(b, 0);
\r
4311 bRange.setEnd(b, 0);
\r
4312 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
\r
4313 if ( ret === 0 ) {
\r
4314 hasDuplicate = true;
\r
4320 // Utility function for retreiving the text value of an array of DOM nodes
\r
4321 Sizzle.getText = function( elems ) {
\r
4322 var ret = "", elem;
\r
4324 for ( var i = 0; elems[i]; i++ ) {
\r
4327 // Get the text from text nodes and CDATA nodes
\r
4328 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
\r
4329 ret += elem.nodeValue;
\r
4331 // Traverse everything else, except comment nodes
\r
4332 } else if ( elem.nodeType !== 8 ) {
\r
4333 ret += Sizzle.getText( elem.childNodes );
\r
4340 // Check to see if the browser returns elements by name when
\r
4341 // querying by getElementById (and provide a workaround)
\r
4343 // We're going to inject a fake input element with a specified name
\r
4344 var form = document.createElement("div"),
\r
4345 id = "script" + (new Date()).getTime();
\r
4346 form.innerHTML = "<a name='" + id + "'/>";
\r
4348 // Inject it into the root element, check its status, and remove it quickly
\r
4349 var root = document.documentElement;
\r
4350 root.insertBefore( form, root.firstChild );
\r
4352 // The workaround has to do additional checks after a getElementById
\r
4353 // Which slows things down for other browsers (hence the branching)
\r
4354 if ( document.getElementById( id ) ) {
\r
4355 Expr.find.ID = function(match, context, isXML){
\r
4356 if ( typeof context.getElementById !== "undefined" && !isXML ) {
\r
4357 var m = context.getElementById(match[1]);
\r
4358 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
\r
4362 Expr.filter.ID = function(elem, match){
\r
4363 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
\r
4364 return elem.nodeType === 1 && node && node.nodeValue === match;
\r
4368 root.removeChild( form );
\r
4369 root = form = null; // release memory in IE
\r
4373 // Check to see if the browser returns only elements
\r
4374 // when doing getElementsByTagName("*")
\r
4376 // Create a fake element
\r
4377 var div = document.createElement("div");
\r
4378 div.appendChild( document.createComment("") );
\r
4380 // Make sure no comments are found
\r
4381 if ( div.getElementsByTagName("*").length > 0 ) {
\r
4382 Expr.find.TAG = function(match, context){
\r
4383 var results = context.getElementsByTagName(match[1]);
\r
4385 // Filter out possible comments
\r
4386 if ( match[1] === "*" ) {
\r
4389 for ( var i = 0; results[i]; i++ ) {
\r
4390 if ( results[i].nodeType === 1 ) {
\r
4391 tmp.push( results[i] );
\r
4402 // Check to see if an attribute returns normalized href attributes
\r
4403 div.innerHTML = "<a href='#'></a>";
\r
4404 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
\r
4405 div.firstChild.getAttribute("href") !== "#" ) {
\r
4406 Expr.attrHandle.href = function(elem){
\r
4407 return elem.getAttribute("href", 2);
\r
4411 div = null; // release memory in IE
\r
4414 if ( document.querySelectorAll ) {
\r
4416 var oldSizzle = Sizzle, div = document.createElement("div");
\r
4417 div.innerHTML = "<p class='TEST'></p>";
\r
4419 // Safari can't handle uppercase or unicode characters when
\r
4420 // in quirks mode.
\r
4421 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
\r
4425 Sizzle = function(query, context, extra, seed){
\r
4426 context = context || document;
\r
4428 // Only use querySelectorAll on non-XML documents
\r
4429 // (ID selectors don't work in non-HTML documents)
\r
4430 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
\r
4432 return makeArray( context.querySelectorAll(query), extra );
\r
4436 return oldSizzle(query, context, extra, seed);
\r
4439 for ( var prop in oldSizzle ) {
\r
4440 Sizzle[ prop ] = oldSizzle[ prop ];
\r
4443 div = null; // release memory in IE
\r
4448 var div = document.createElement("div");
\r
4450 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
\r
4452 // Opera can't find a second classname (in 9.6)
\r
4453 // Also, make sure that getElementsByClassName actually exists
\r
4454 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
\r
4458 // Safari caches class attributes, doesn't catch changes (in 3.2)
\r
4459 div.lastChild.className = "e";
\r
4461 if ( div.getElementsByClassName("e").length === 1 ) {
\r
4465 Expr.order.splice(1, 0, "CLASS");
\r
4466 Expr.find.CLASS = function(match, context, isXML) {
\r
4467 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
\r
4468 return context.getElementsByClassName(match[1]);
\r
4472 div = null; // release memory in IE
\r
4475 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
\r
4476 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
4477 var elem = checkSet[i];
\r
4480 var match = false;
\r
4483 if ( elem.sizcache === doneName ) {
\r
4484 match = checkSet[elem.sizset];
\r
4488 if ( elem.nodeType === 1 && !isXML ){
\r
4489 elem.sizcache = doneName;
\r
4493 if ( elem.nodeName.toLowerCase() === cur ) {
\r
4501 checkSet[i] = match;
\r
4506 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
\r
4507 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
4508 var elem = checkSet[i];
\r
4511 var match = false;
\r
4514 if ( elem.sizcache === doneName ) {
\r
4515 match = checkSet[elem.sizset];
\r
4519 if ( elem.nodeType === 1 ) {
\r
4521 elem.sizcache = doneName;
\r
4524 if ( typeof cur !== "string" ) {
\r
4525 if ( elem === cur ) {
\r
4530 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
\r
4539 checkSet[i] = match;
\r
4544 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
\r
4545 return !!(a.compareDocumentPosition(b) & 16);
\r
4546 } : function(a, b){
\r
4547 return a !== b && (a.contains ? a.contains(b) : true);
\r
4550 Sizzle.isXML = function(elem){
\r
4551 // documentElement is verified for cases where it doesn't yet exist
\r
4552 // (such as loading iframes in IE - #4833)
\r
4553 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
\r
4554 return documentElement ? documentElement.nodeName !== "HTML" : false;
\r
4557 var posProcess = function(selector, context){
\r
4558 var tmpSet = [], later = "", match,
\r
4559 root = context.nodeType ? [context] : context;
\r
4561 // Position selectors must be done after the filter
\r
4562 // And so must :not(positional) so we move all PSEUDOs to the end
\r
4563 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
\r
4564 later += match[0];
\r
4565 selector = selector.replace( Expr.match.PSEUDO, "" );
\r
4568 selector = Expr.relative[selector] ? selector + "*" : selector;
\r
4570 for ( var i = 0, l = root.length; i < l; i++ ) {
\r
4571 Sizzle( selector, root[i], tmpSet );
\r
4574 return Sizzle.filter( later, tmpSet );
\r
4579 window.tinymce.dom.Sizzle = Sizzle;
\r
4584 (function(tinymce) {
\r
4586 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
\r
4588 tinymce.create('tinymce.dom.EventUtils', {
\r
4589 EventUtils : function() {
\r
4594 add : function(o, n, f, s) {
\r
4595 var cb, t = this, el = t.events, r;
\r
4597 if (n instanceof Array) {
\r
4600 each(n, function(n) {
\r
4601 r.push(t.add(o, n, f, s));
\r
4608 if (o && o.hasOwnProperty && o instanceof Array) {
\r
4611 each(o, function(o) {
\r
4613 r.push(t.add(o, n, f, s));
\r
4624 // Setup event callback
\r
4625 cb = function(e) {
\r
4626 // Is all events disabled
\r
4630 e = e || window.event;
\r
4632 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
\r
4635 e.target = e.srcElement;
\r
4637 // Patch in preventDefault, stopPropagation methods for W3C compatibility
\r
4638 tinymce.extend(e, t._stoppers);
\r
4644 return f.call(s, e);
\r
4647 if (n == 'unload') {
\r
4648 tinymce.unloads.unshift({func : cb});
\r
4652 if (n == 'init') {
\r
4661 // Store away listener reference
\r
4675 remove : function(o, n, f) {
\r
4676 var t = this, a = t.events, s = false, r;
\r
4679 if (o && o.hasOwnProperty && o instanceof Array) {
\r
4682 each(o, function(o) {
\r
4684 r.push(t.remove(o, n, f));
\r
4692 each(a, function(e, i) {
\r
4693 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
\r
4695 t._remove(o, n, e.cfunc);
\r
4704 clear : function(o) {
\r
4705 var t = this, a = t.events, i, e;
\r
4710 for (i = a.length - 1; i >= 0; i--) {
\r
4713 if (e.obj === o) {
\r
4714 t._remove(e.obj, e.name, e.cfunc);
\r
4715 e.obj = e.cfunc = null;
\r
4722 cancel : function(e) {
\r
4728 return this.prevent(e);
\r
4731 stop : function(e) {
\r
4732 if (e.stopPropagation)
\r
4733 e.stopPropagation();
\r
4735 e.cancelBubble = true;
\r
4740 prevent : function(e) {
\r
4741 if (e.preventDefault)
\r
4742 e.preventDefault();
\r
4744 e.returnValue = false;
\r
4749 destroy : function() {
\r
4752 each(t.events, function(e, i) {
\r
4753 t._remove(e.obj, e.name, e.cfunc);
\r
4754 e.obj = e.cfunc = null;
\r
4761 _add : function(o, n, f) {
\r
4762 if (o.attachEvent)
\r
4763 o.attachEvent('on' + n, f);
\r
4764 else if (o.addEventListener)
\r
4765 o.addEventListener(n, f, false);
\r
4770 _remove : function(o, n, f) {
\r
4773 if (o.detachEvent)
\r
4774 o.detachEvent('on' + n, f);
\r
4775 else if (o.removeEventListener)
\r
4776 o.removeEventListener(n, f, false);
\r
4778 o['on' + n] = null;
\r
4780 // Might fail with permission denined on IE so we just ignore that
\r
4785 _pageInit : function(win) {
\r
4788 // Keep it from running more than once
\r
4792 t.domLoaded = true;
\r
4794 each(t.inits, function(c) {
\r
4801 _wait : function(win) {
\r
4802 var t = this, doc = win.document;
\r
4804 // No need since the document is already loaded
\r
4805 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
\r
4811 if (doc.attachEvent) {
\r
4812 doc.attachEvent("onreadystatechange", function() {
\r
4813 if (doc.readyState === "complete") {
\r
4814 doc.detachEvent("onreadystatechange", arguments.callee);
\r
4819 if (doc.documentElement.doScroll && win == win.top) {
\r
4825 // If IE is used, use the trick by Diego Perini
\r
4826 // http://javascript.nwbox.com/IEContentLoaded/
\r
4827 doc.documentElement.doScroll("left");
\r
4829 setTimeout(arguments.callee, 0);
\r
4836 } else if (doc.addEventListener) {
\r
4837 t._add(win, 'DOMContentLoaded', function() {
\r
4842 t._add(win, 'load', function() {
\r
4848 preventDefault : function() {
\r
4849 this.returnValue = false;
\r
4852 stopPropagation : function() {
\r
4853 this.cancelBubble = true;
\r
4858 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
\r
4860 // Dispatch DOM content loaded event for IE and Safari
\r
4861 Event._wait(window);
\r
4863 tinymce.addUnload(function() {
\r
4868 (function(tinymce) {
\r
4869 tinymce.dom.Element = function(id, settings) {
\r
4870 var t = this, dom, el;
\r
4872 t.settings = settings = settings || {};
\r
4874 t.dom = dom = settings.dom || tinymce.DOM;
\r
4876 // Only IE leaks DOM references, this is a lot faster
\r
4877 if (!tinymce.isIE)
\r
4878 el = dom.get(t.id);
\r
4881 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
\r
4882 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
\r
4883 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
\r
4884 'isHidden,setHTML,get').split(/,/)
\r
4886 t[k] = function() {
\r
4889 for (i = 0; i < arguments.length; i++)
\r
4890 a.push(arguments[i]);
\r
4892 a = dom[k].apply(dom, a);
\r
4899 tinymce.extend(t, {
\r
4900 on : function(n, f, s) {
\r
4901 return tinymce.dom.Event.add(t.id, n, f, s);
\r
4904 getXY : function() {
\r
4906 x : parseInt(t.getStyle('left')),
\r
4907 y : parseInt(t.getStyle('top'))
\r
4911 getSize : function() {
\r
4912 var n = dom.get(t.id);
\r
4915 w : parseInt(t.getStyle('width') || n.clientWidth),
\r
4916 h : parseInt(t.getStyle('height') || n.clientHeight)
\r
4920 moveTo : function(x, y) {
\r
4921 t.setStyles({left : x, top : y});
\r
4924 moveBy : function(x, y) {
\r
4925 var p = t.getXY();
\r
4927 t.moveTo(p.x + x, p.y + y);
\r
4930 resizeTo : function(w, h) {
\r
4931 t.setStyles({width : w, height : h});
\r
4934 resizeBy : function(w, h) {
\r
4935 var s = t.getSize();
\r
4937 t.resizeTo(s.w + w, s.h + h);
\r
4940 update : function(k) {
\r
4943 if (tinymce.isIE6 && settings.blocker) {
\r
4947 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
\r
4950 // Remove blocker on remove
\r
4951 if (k == 'remove') {
\r
4952 dom.remove(t.blocker);
\r
4957 t.blocker = dom.uniqueId();
\r
4958 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
\r
4959 dom.setStyle(b, 'opacity', 0);
\r
4961 b = dom.get(t.blocker);
\r
4963 dom.setStyles(b, {
\r
4964 left : t.getStyle('left', 1),
\r
4965 top : t.getStyle('top', 1),
\r
4966 width : t.getStyle('width', 1),
\r
4967 height : t.getStyle('height', 1),
\r
4968 display : t.getStyle('display', 1),
\r
4969 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
\r
4977 (function(tinymce) {
\r
4978 function trimNl(s) {
\r
4979 return s.replace(/[\n\r]+/g, '');
\r
4983 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
\r
4985 tinymce.create('tinymce.dom.Selection', {
\r
4986 Selection : function(dom, win, serializer) {
\r
4991 t.serializer = serializer;
\r
4995 'onBeforeSetContent',
\r
4996 'onBeforeGetContent',
\r
5000 t[e] = new tinymce.util.Dispatcher(t);
\r
5003 // No W3C Range support
\r
5004 if (!t.win.getSelection)
\r
5005 t.tridentSel = new tinymce.dom.TridentSelection(t);
\r
5007 if (tinymce.isIE && dom.boxModel)
\r
5008 this._fixIESelection();
\r
5011 tinymce.addUnload(t.destroy, t);
\r
5014 getContent : function(s) {
\r
5015 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
\r
5020 s.format = s.format || 'html';
\r
5021 t.onBeforeGetContent.dispatch(t, s);
\r
5023 if (s.format == 'text')
\r
5024 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
\r
5026 if (r.cloneContents) {
\r
5027 n = r.cloneContents();
\r
5031 } else if (is(r.item) || is(r.htmlText))
\r
5032 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
\r
5034 e.innerHTML = r.toString();
\r
5036 // Keep whitespace before and after
\r
5037 if (/^\s/.test(e.innerHTML))
\r
5040 if (/\s+$/.test(e.innerHTML))
\r
5043 s.getInner = true;
\r
5045 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
\r
5046 t.onGetContent.dispatch(t, s);
\r
5051 setContent : function(h, s) {
\r
5052 var t = this, r = t.getRng(), c, d = t.win.document;
\r
5054 s = s || {format : 'html'};
\r
5056 h = s.content = t.dom.processHTML(h);
\r
5058 // Dispatch before set content event
\r
5059 t.onBeforeSetContent.dispatch(t, s);
\r
5062 if (r.insertNode) {
\r
5063 // Make caret marker since insertNode places the caret in the beginning of text after insert
\r
5064 h += '<span id="__caret">_</span>';
\r
5066 // Delete and insert new node
\r
5068 if (r.startContainer == d && r.endContainer == d) {
\r
5069 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
\r
5070 d.body.innerHTML = h;
\r
5072 r.deleteContents();
\r
5073 if (d.body.childNodes.length == 0) {
\r
5074 d.body.innerHTML = h;
\r
5076 // createContextualFragment doesn't exists in IE 9 DOMRanges
\r
5077 if (r.createContextualFragment) {
\r
5078 r.insertNode(r.createContextualFragment(h));
\r
5080 // Fake createContextualFragment call in IE 9
\r
5081 var frag = d.createDocumentFragment(), temp = d.createElement('div');
\r
5083 frag.appendChild(temp);
\r
5084 temp.outerHTML = h;
\r
5086 r.insertNode(frag);
\r
5091 // Move to caret marker
\r
5092 c = t.dom.get('__caret');
\r
5093 // Make sure we wrap it compleatly, Opera fails with a simple select call
\r
5094 r = d.createRange();
\r
5095 r.setStartBefore(c);
\r
5096 r.setEndBefore(c);
\r
5099 // Remove the caret position
\r
5100 t.dom.remove('__caret');
\r
5103 // Delete content and get caret text selection
\r
5104 d.execCommand('Delete', false, null);
\r
5111 // Dispatch set content event
\r
5112 t.onSetContent.dispatch(t, s);
\r
5115 getStart : function() {
\r
5116 var rng = this.getRng(), startElement, parentElement, checkRng, node;
\r
5118 if (rng.duplicate || rng.item) {
\r
5119 // Control selection, return first item
\r
5121 return rng.item(0);
\r
5123 // Get start element
\r
5124 checkRng = rng.duplicate();
\r
5125 checkRng.collapse(1);
\r
5126 startElement = checkRng.parentElement();
\r
5128 // Check if range parent is inside the start element, then return the inner parent element
\r
5129 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
\r
5130 parentElement = node = rng.parentElement();
\r
5131 while (node = node.parentNode) {
\r
5132 if (node == startElement) {
\r
5133 startElement = parentElement;
\r
5138 // If start element is body element try to move to the first child if it exists
\r
5139 if (startElement && startElement.nodeName == 'BODY')
\r
5140 return startElement.firstChild || startElement;
\r
5142 return startElement;
\r
5144 startElement = rng.startContainer;
\r
5146 if (startElement.nodeType == 1 && startElement.hasChildNodes())
\r
5147 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
\r
5149 if (startElement && startElement.nodeType == 3)
\r
5150 return startElement.parentNode;
\r
5152 return startElement;
\r
5156 getEnd : function() {
\r
5157 var t = this, r = t.getRng(), e, eo;
\r
5159 if (r.duplicate || r.item) {
\r
5163 r = r.duplicate();
\r
5165 e = r.parentElement();
\r
5167 if (e && e.nodeName == 'BODY')
\r
5168 return e.lastChild || e;
\r
5172 e = r.endContainer;
\r
5175 if (e.nodeType == 1 && e.hasChildNodes())
\r
5176 e = e.childNodes[eo > 0 ? eo - 1 : eo];
\r
5178 if (e && e.nodeType == 3)
\r
5179 return e.parentNode;
\r
5185 getBookmark : function(type, normalized) {
\r
5186 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
\r
5188 function findIndex(name, element) {
\r
5191 each(dom.select(name), function(node, i) {
\r
5192 if (node == element)
\r
5200 function getLocation() {
\r
5201 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
\r
5203 function getPoint(rng, start) {
\r
5204 var container = rng[start ? 'startContainer' : 'endContainer'],
\r
5205 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
\r
5207 if (container.nodeType == 3) {
\r
5209 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
\r
5210 offset += node.nodeValue.length;
\r
5213 point.push(offset);
\r
5215 childNodes = container.childNodes;
\r
5217 if (offset >= childNodes.length && childNodes.length) {
\r
5219 offset = Math.max(0, childNodes.length - 1);
\r
5222 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
\r
5225 for (; container && container != root; container = container.parentNode)
\r
5226 point.push(t.dom.nodeIndex(container, normalized));
\r
5231 bookmark.start = getPoint(rng, true);
\r
5233 if (!t.isCollapsed())
\r
5234 bookmark.end = getPoint(rng);
\r
5239 return getLocation();
\r
5242 // Handle simple range
\r
5244 return {rng : t.getRng()};
\r
5247 id = dom.uniqueId();
\r
5248 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
\r
5249 styles = 'overflow:hidden;line-height:0px';
\r
5251 // Explorer method
\r
5252 if (rng.duplicate || rng.item) {
\r
5255 rng2 = rng.duplicate();
\r
5257 // Insert start marker
\r
5259 rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
\r
5261 // Insert end marker
\r
5263 rng2.collapse(false);
\r
5264 rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
\r
5267 // Control selection
\r
5268 element = rng.item(0);
\r
5269 name = element.nodeName;
\r
5271 return {name : name, index : findIndex(name, element)};
\r
5274 element = t.getNode();
\r
5275 name = element.nodeName;
\r
5276 if (name == 'IMG')
\r
5277 return {name : name, index : findIndex(name, element)};
\r
5280 rng2 = rng.cloneRange();
\r
5282 // Insert end marker
\r
5284 rng2.collapse(false);
\r
5285 rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
\r
5288 rng.collapse(true);
\r
5289 rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
\r
5292 t.moveToBookmark({id : id, keep : 1});
\r
5297 moveToBookmark : function(bookmark) {
\r
5298 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
\r
5300 // Clear selection cache
\r
5302 t.tridentSel.destroy();
\r
5305 if (bookmark.start) {
\r
5306 rng = dom.createRng();
\r
5307 root = dom.getRoot();
\r
5309 function setEndPoint(start) {
\r
5310 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
\r
5313 // Find container node
\r
5314 for (node = root, i = point.length - 1; i >= 1; i--) {
\r
5315 children = node.childNodes;
\r
5317 if (children.length)
\r
5318 node = children[point[i]];
\r
5321 // Set offset within container node
\r
5323 rng.setStart(node, point[0]);
\r
5325 rng.setEnd(node, point[0]);
\r
5329 setEndPoint(true);
\r
5333 } else if (bookmark.id) {
\r
5334 function restoreEndPoint(suffix) {
\r
5335 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
\r
5338 node = marker.parentNode;
\r
5340 if (suffix == 'start') {
\r
5342 idx = dom.nodeIndex(marker);
\r
5344 node = marker.firstChild;
\r
5348 startContainer = endContainer = node;
\r
5349 startOffset = endOffset = idx;
\r
5352 idx = dom.nodeIndex(marker);
\r
5354 node = marker.firstChild;
\r
5358 endContainer = node;
\r
5363 prev = marker.previousSibling;
\r
5364 next = marker.nextSibling;
\r
5366 // Remove all marker text nodes
\r
5367 each(tinymce.grep(marker.childNodes), function(node) {
\r
5368 if (node.nodeType == 3)
\r
5369 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
\r
5372 // Remove marker but keep children if for example contents where inserted into the marker
\r
5373 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
\r
5374 while (marker = dom.get(bookmark.id + '_' + suffix))
\r
5375 dom.remove(marker, 1);
\r
5377 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
\r
5378 // 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
5379 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
\r
5380 idx = prev.nodeValue.length;
\r
5381 prev.appendData(next.nodeValue);
\r
5384 if (suffix == 'start') {
\r
5385 startContainer = endContainer = prev;
\r
5386 startOffset = endOffset = idx;
\r
5388 endContainer = prev;
\r
5396 function addBogus(node) {
\r
5397 // Adds a bogus BR element for empty block elements
\r
5398 // on non IE browsers just to have a place to put the caret
\r
5399 if (!isIE && dom.isBlock(node) && !node.innerHTML)
\r
5400 node.innerHTML = '<br _mce_bogus="1" />';
\r
5405 // Restore start/end points
\r
5406 restoreEndPoint('start');
\r
5407 restoreEndPoint('end');
\r
5409 if (startContainer) {
\r
5410 rng = dom.createRng();
\r
5411 rng.setStart(addBogus(startContainer), startOffset);
\r
5412 rng.setEnd(addBogus(endContainer), endOffset);
\r
5415 } else if (bookmark.name) {
\r
5416 t.select(dom.select(bookmark.name)[bookmark.index]);
\r
5417 } else if (bookmark.rng)
\r
5418 t.setRng(bookmark.rng);
\r
5422 select : function(node, content) {
\r
5423 var t = this, dom = t.dom, rng = dom.createRng(), idx;
\r
5425 idx = dom.nodeIndex(node);
\r
5426 rng.setStart(node.parentNode, idx);
\r
5427 rng.setEnd(node.parentNode, idx + 1);
\r
5429 // Find first/last text node or BR element
\r
5431 function setPoint(node, start) {
\r
5432 var walker = new tinymce.dom.TreeWalker(node, node);
\r
5436 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
\r
5438 rng.setStart(node, 0);
\r
5440 rng.setEnd(node, node.nodeValue.length);
\r
5446 if (node.nodeName == 'BR') {
\r
5448 rng.setStartBefore(node);
\r
5450 rng.setEndBefore(node);
\r
5454 } while (node = (start ? walker.next() : walker.prev()));
\r
5457 setPoint(node, 1);
\r
5466 isCollapsed : function() {
\r
5467 var t = this, r = t.getRng(), s = t.getSel();
\r
5472 if (r.compareEndPoints)
\r
5473 return r.compareEndPoints('StartToEnd', r) === 0;
\r
5475 return !s || r.collapsed;
\r
5478 collapse : function(b) {
\r
5479 var t = this, r = t.getRng(), n;
\r
5481 // Control range on IE
\r
5484 r = this.win.document.body.createTextRange();
\r
5485 r.moveToElementText(n);
\r
5492 getSel : function() {
\r
5493 var t = this, w = this.win;
\r
5495 return w.getSelection ? w.getSelection() : w.document.selection;
\r
5498 getRng : function(w3c) {
\r
5499 var t = this, s, r, elm, doc = t.win.document;
\r
5501 // Found tridentSel object then we need to use that one
\r
5502 if (w3c && t.tridentSel)
\r
5503 return t.tridentSel.getRangeAt(0);
\r
5506 if (s = t.getSel())
\r
5507 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
\r
5509 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
\r
5512 // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
\r
5513 if (tinymce.isIE && r.setStart && doc.selection.createRange().item) {
\r
5514 elm = doc.selection.createRange().item(0);
\r
5515 r = doc.createRange();
\r
5516 r.setStartBefore(elm);
\r
5517 r.setEndAfter(elm);
\r
5520 // No range found then create an empty one
\r
5521 // This can occur when the editor is placed in a hidden container element on Gecko
\r
5522 // Or on IE when there was an exception
\r
5524 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
\r
5526 if (t.selectedRange && t.explicitRange) {
\r
5527 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
\r
5528 // Safari, Opera and Chrome only ever select text which causes the range to change.
\r
5529 // This lets us use the originally set range if the selection hasn't been changed by the user.
\r
5530 r = t.explicitRange;
\r
5532 t.selectedRange = null;
\r
5533 t.explicitRange = null;
\r
5539 setRng : function(r) {
\r
5542 if (!t.tridentSel) {
\r
5546 t.explicitRange = r;
\r
5547 s.removeAllRanges();
\r
5549 t.selectedRange = s.getRangeAt(0);
\r
5553 if (r.cloneRange) {
\r
5554 t.tridentSel.addRange(r);
\r
5558 // Is IE specific range
\r
5562 // Needed for some odd IE bug #1843306
\r
5567 setNode : function(n) {
\r
5570 t.setContent(t.dom.getOuterHTML(n));
\r
5575 getNode : function() {
\r
5576 var t = this, rng = t.getRng(), sel = t.getSel(), elm;
\r
5578 if (rng.setStart) {
\r
5579 // Range maybe lost after the editor is made visible again
\r
5581 return t.dom.getRoot();
\r
5583 elm = rng.commonAncestorContainer;
\r
5585 // Handle selection a image or other control like element such as anchors
\r
5586 if (!rng.collapsed) {
\r
5587 if (rng.startContainer == rng.endContainer) {
\r
5588 if (rng.startOffset - rng.endOffset < 2) {
\r
5589 if (rng.startContainer.hasChildNodes())
\r
5590 elm = rng.startContainer.childNodes[rng.startOffset];
\r
5594 // If the anchor node is a element instead of a text node then return this element
\r
5595 if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
\r
5596 return sel.anchorNode.childNodes[sel.anchorOffset];
\r
5599 if (elm && elm.nodeType == 3)
\r
5600 return elm.parentNode;
\r
5605 return rng.item ? rng.item(0) : rng.parentElement();
\r
5608 getSelectedBlocks : function(st, en) {
\r
5609 var t = this, dom = t.dom, sb, eb, n, bl = [];
\r
5611 sb = dom.getParent(st || t.getStart(), dom.isBlock);
\r
5612 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
\r
5617 if (sb && eb && sb != eb) {
\r
5620 while ((n = n.nextSibling) && n != eb) {
\r
5621 if (dom.isBlock(n))
\r
5626 if (eb && sb != eb)
\r
5632 destroy : function(s) {
\r
5638 t.tridentSel.destroy();
\r
5640 // Manual destroy then remove unload handler
\r
5642 tinymce.removeUnload(t.destroy);
\r
5645 // 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
5646 _fixIESelection : function() {
\r
5647 var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng;
\r
5649 // Make HTML element unselectable since we are going to handle selection by hand
\r
5650 doc.documentElement.unselectable = true;
\r
5652 // Return range from point or null if it failed
\r
5653 function rngFromPoint(x, y) {
\r
5654 var rng = body.createTextRange();
\r
5657 rng.moveToPoint(x, y);
\r
5659 // IE sometimes throws and exception, so lets just ignore it
\r
5666 // Fires while the selection is changing
\r
5667 function selectionChange(e) {
\r
5670 // Check if the button is down or not
\r
5672 // Create range from mouse position
\r
5673 pointRng = rngFromPoint(e.x, e.y);
\r
5676 // Check if pointRange is before/after selection then change the endPoint
\r
5677 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
\r
5678 pointRng.setEndPoint('StartToStart', startRng);
\r
5680 pointRng.setEndPoint('EndToEnd', startRng);
\r
5682 pointRng.select();
\r
5688 // Removes listeners
\r
5689 function endSelection() {
\r
5690 dom.unbind(doc, 'mouseup', endSelection);
\r
5691 dom.unbind(doc, 'mousemove', selectionChange);
\r
5695 // Detect when user selects outside BODY
\r
5696 dom.bind(doc, 'mousedown', function(e) {
\r
5697 if (e.target.nodeName === 'HTML') {
\r
5703 // Setup start position
\r
5704 startRng = rngFromPoint(e.x, e.y);
\r
5706 // Listen for selection change events
\r
5707 dom.bind(doc, 'mouseup', endSelection);
\r
5708 dom.bind(doc, 'mousemove', selectionChange);
\r
5711 startRng.select();
\r
5719 (function(tinymce) {
\r
5720 tinymce.create('tinymce.dom.XMLWriter', {
\r
5723 XMLWriter : function(s) {
\r
5724 // Get XML document
\r
5725 function getXML() {
\r
5726 var i = document.implementation;
\r
5728 if (!i || !i.createDocument) {
\r
5730 try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
\r
5731 try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
\r
5733 return i.createDocument('', '', null);
\r
5736 this.doc = getXML();
\r
5738 // Since Opera and WebKit doesn't escape > into > we need to do it our self to normalize the output for all browsers
\r
5739 this.valid = tinymce.isOpera || tinymce.isWebKit;
\r
5744 reset : function() {
\r
5745 var t = this, d = t.doc;
\r
5748 d.removeChild(d.firstChild);
\r
5750 t.node = d.appendChild(d.createElement("html"));
\r
5753 writeStartElement : function(n) {
\r
5756 t.node = t.node.appendChild(t.doc.createElement(n));
\r
5759 writeAttribute : function(n, v) {
\r
5761 v = v.replace(/>/g, '%MCGT%');
\r
5763 this.node.setAttribute(n, v);
\r
5766 writeEndElement : function() {
\r
5767 this.node = this.node.parentNode;
\r
5770 writeFullEndElement : function() {
\r
5771 var t = this, n = t.node;
\r
5773 n.appendChild(t.doc.createTextNode(""));
\r
5774 t.node = n.parentNode;
\r
5777 writeText : function(v) {
\r
5779 v = v.replace(/>/g, '%MCGT%');
\r
5781 this.node.appendChild(this.doc.createTextNode(v));
\r
5784 writeCDATA : function(v) {
\r
5785 this.node.appendChild(this.doc.createCDATASection(v));
\r
5788 writeComment : function(v) {
\r
5789 // Fix for bug #2035694
\r
5791 v = v.replace(/^\-|\-$/g, ' ');
\r
5793 this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
\r
5796 getContent : function() {
\r
5799 h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
\r
5800 h = h.replace(/<\?[^?]+\?>|<html[^>]*>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
\r
5801 h = h.replace(/ ?\/>/g, ' />');
\r
5804 h = h.replace(/\%MCGT%/g, '>');
\r
5811 (function(tinymce) {
\r
5812 var attrsCharsRegExp = /[&\"<>]/g, textCharsRegExp = /[<>&]/g, encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'};
\r
5814 tinymce.create('tinymce.dom.StringWriter', {
\r
5821 StringWriter : function(s) {
\r
5822 this.settings = tinymce.extend({
\r
5823 indent_char : ' ',
\r
5830 reset : function() {
\r
5837 writeStartElement : function(n) {
\r
5838 this._writeAttributesEnd();
\r
5839 this.writeRaw('<' + n);
\r
5840 this.tags.push(n);
\r
5841 this.inAttr = true;
\r
5843 this.elementCount = this.count;
\r
5847 writeAttribute : function(n, v) {
\r
5850 if (!t.attrs[n]) {
\r
5851 t.writeRaw(" " + t.encode(n, true) + '="' + t.encode(v, true) + '"');
\r
5856 writeEndElement : function() {
\r
5859 if (this.tags.length > 0) {
\r
5860 n = this.tags.pop();
\r
5862 if (this._writeAttributesEnd(1))
\r
5863 this.writeRaw('</' + n + '>');
\r
5865 if (this.settings.indentation > 0)
\r
5866 this.writeRaw('\n');
\r
5870 writeFullEndElement : function() {
\r
5871 if (this.tags.length > 0) {
\r
5872 this._writeAttributesEnd();
\r
5873 this.writeRaw('</' + this.tags.pop() + '>');
\r
5875 if (this.settings.indentation > 0)
\r
5876 this.writeRaw('\n');
\r
5880 writeText : function(v) {
\r
5881 this._writeAttributesEnd();
\r
5882 this.writeRaw(this.encode(v));
\r
5886 writeCDATA : function(v) {
\r
5887 this._writeAttributesEnd();
\r
5888 this.writeRaw('<![CDATA[' + v + ']]>');
\r
5892 writeComment : function(v) {
\r
5893 this._writeAttributesEnd();
\r
5894 this.writeRaw('<!--' + v + '-->');
\r
5898 writeRaw : function(v) {
\r
5902 encode : function(s, attr) {
\r
5903 return s.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(v) {
\r
5904 return encodedChars[v];
\r
5908 getContent : function() {
\r
5912 _writeAttributesEnd : function(s) {
\r
5916 this.inAttr = false;
\r
5918 if (s && this.elementCount == this.count) {
\r
5919 this.writeRaw(' />');
\r
5923 this.writeRaw('>');
\r
5930 (function(tinymce) {
\r
5932 var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
\r
5934 function wildcardToRE(s) {
\r
5935 return s.replace(/([?+*])/g, '.$1');
\r
5938 tinymce.create('tinymce.dom.Serializer', {
\r
5939 Serializer : function(s) {
\r
5943 t.onPreProcess = new Dispatcher(t);
\r
5944 t.onPostProcess = new Dispatcher(t);
\r
5947 t.writer = new tinymce.dom.XMLWriter();
\r
5949 // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
\r
5950 t.writer = new tinymce.dom.StringWriter();
\r
5953 // IE9 broke the XML attributes order so it can't be used anymore
\r
5954 if (tinymce.isIE && document.documentMode > 8) {
\r
5955 t.writer = new tinymce.dom.StringWriter();
\r
5958 // Default settings
\r
5959 t.settings = s = extend({
\r
5960 dom : tinymce.DOM,
\r
5964 invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
\r
5965 closed : /^(br|hr|input|meta|img|link|param|area)$/,
\r
5966 entity_encoding : 'named',
\r
5967 entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro',
\r
5968 valid_elements : '*[*]',
\r
5969 extended_valid_elements : 0,
\r
5970 invalid_elements : 0,
\r
5971 fix_table_elements : 1,
\r
5972 fix_list_elements : true,
\r
5973 fix_content_duplication : true,
\r
5974 convert_fonts_to_spans : false,
\r
5975 font_size_classes : 0,
\r
5976 apply_source_formatting : 0,
\r
5977 indent_mode : 'simple',
\r
5978 indent_char : '\t',
\r
5979 indent_levels : 1,
\r
5980 remove_linebreaks : 1,
\r
5981 remove_redundant_brs : 1,
\r
5982 element_format : 'xhtml'
\r
5986 t.schema = s.schema;
\r
5988 // Use raw entities if no entities are defined
\r
5989 if (s.entity_encoding == 'named' && !s.entities)
\r
5990 s.entity_encoding = 'raw';
\r
5992 if (s.remove_redundant_brs) {
\r
5993 t.onPostProcess.add(function(se, o) {
\r
5994 // Remove single BR at end of block elements since they get rendered
\r
5995 o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
\r
5996 // Check if it's a single element
\r
5997 if (/^<br \/>\s*<\//.test(a))
\r
5998 return '</' + c + '>';
\r
6005 // Remove XHTML element endings i.e. produce crap :) XHTML is better
\r
6006 if (s.element_format == 'html') {
\r
6007 t.onPostProcess.add(function(se, o) {
\r
6008 o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
\r
6012 if (s.fix_list_elements) {
\r
6013 t.onPreProcess.add(function(se, o) {
\r
6014 var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
\r
6016 function prevNode(e, n) {
\r
6017 var a = n.split(','), i;
\r
6019 while ((e = e.previousSibling) != null) {
\r
6020 for (i=0; i<a.length; i++) {
\r
6021 if (e.nodeName == a[i])
\r
6029 for (x=0; x<a.length; x++) {
\r
6030 nl = t.dom.select(a[x], o.node);
\r
6032 for (i=0; i<nl.length; i++) {
\r
6036 if (r.test(p.nodeName)) {
\r
6037 np = prevNode(n, 'LI');
\r
6040 np = t.dom.create('li');
\r
6041 np.innerHTML = ' ';
\r
6042 np.appendChild(n);
\r
6043 p.insertBefore(np, p.firstChild);
\r
6045 np.appendChild(n);
\r
6052 if (s.fix_table_elements) {
\r
6053 t.onPreProcess.add(function(se, o) {
\r
6054 // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
\r
6055 // so Opera users with an older version will have to live with less compaible output not much we can do here
\r
6056 if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
\r
6057 each(t.dom.select('p table', o.node).reverse(), function(n) {
\r
6058 var parent = t.dom.getParent(n.parentNode, 'table,p');
\r
6060 if (parent.nodeName != 'TABLE') {
\r
6062 t.dom.split(parent, n);
\r
6064 // IE can sometimes fire an unknown runtime error so we just ignore it
\r
6073 setEntities : function(s) {
\r
6074 var t = this, a, i, l = {}, v;
\r
6076 // No need to setup more than once
\r
6077 if (t.entityLookup)
\r
6080 // Build regex and lookup array
\r
6082 for (i = 0; i < a.length; i += 2) {
\r
6085 // Don't add default & " etc.
\r
6086 if (v == 34 || v == 38 || v == 60 || v == 62)
\r
6089 l[String.fromCharCode(a[i])] = a[i + 1];
\r
6091 v = parseInt(a[i]).toString(16);
\r
6094 t.entityLookup = l;
\r
6097 setRules : function(s) {
\r
6103 t.validElements = {};
\r
6105 return t.addRules(s);
\r
6108 addRules : function(s) {
\r
6116 each(s.split(','), function(s) {
\r
6117 var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
\r
6119 // Extend with default rules
\r
6121 at = tinymce.extend([], dr.attribs);
\r
6123 // Parse attributes
\r
6124 if (p.length > 1) {
\r
6125 each(p[1].split('|'), function(s) {
\r
6130 // Parse attribute rule
\r
6131 s = s.replace(/::/g, '~');
\r
6132 s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
\r
6133 s[2] = s[2].replace(/~/g, ':');
\r
6135 // Add required attributes
\r
6136 if (s[1] == '!') {
\r
6141 // Remove inherited attributes
\r
6142 if (s[1] == '-') {
\r
6143 for (i = 0; i <at.length; i++) {
\r
6144 if (at[i].name == s[2]) {
\r
6152 // Add default attrib values
\r
6154 ar.defaultVal = s[4] || '';
\r
6157 // Add forced attrib values
\r
6159 ar.forcedVal = s[4];
\r
6162 // Add validation values
\r
6164 ar.validVals = s[4].split('?');
\r
6168 if (/[*.?]/.test(s[2])) {
\r
6170 ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
\r
6181 // Handle element names
\r
6182 each(tn, function(s, i) {
\r
6183 var pr = s.charAt(0), x = 1, ru = {};
\r
6185 // Extend with default rule data
\r
6188 ru.noEmpty = dr.noEmpty;
\r
6191 ru.fullEnd = dr.fullEnd;
\r
6194 ru.padd = dr.padd;
\r
6197 // Handle prefixes
\r
6200 ru.noEmpty = true;
\r
6204 ru.fullEnd = true;
\r
6215 tn[i] = s = s.substring(x);
\r
6216 t.validElements[s] = 1;
\r
6218 // Add element name or element regex
\r
6219 if (/[*.?]/.test(tn[0])) {
\r
6220 ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
\r
6221 t.wildRules = t.wildRules || {};
\r
6222 t.wildRules.push(ru);
\r
6226 // Store away default rule
\r
6236 ru.requiredAttribs = ra;
\r
6239 // Build valid attributes regexp
\r
6241 each(va, function(v) {
\r
6245 s += '(' + wildcardToRE(v) + ')';
\r
6247 ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
\r
6248 ru.wildAttribs = wat;
\r
6253 // Build valid elements regexp
\r
6255 each(t.validElements, function(v, k) {
\r
6262 t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
\r
6264 //console.debug(t.validElementsRE.toString());
\r
6265 //console.dir(t.rules);
\r
6266 //console.dir(t.wildRules);
\r
6269 findRule : function(n) {
\r
6270 var t = this, rl = t.rules, i, r;
\r
6281 for (i = 0; i < rl.length; i++) {
\r
6282 if (rl[i].nameRE.test(n))
\r
6289 findAttribRule : function(ru, n) {
\r
6290 var i, wa = ru.wildAttribs;
\r
6292 for (i = 0; i < wa.length; i++) {
\r
6293 if (wa[i].nameRE.test(n))
\r
6300 serialize : function(n, o) {
\r
6301 var h, t = this, doc, oldDoc, impl, selected;
\r
6305 o.format = o.format || 'html';
\r
6308 // IE looses the selected attribute on option elements so we need to store it
\r
6309 // See: http://support.microsoft.com/kb/829907
\r
6312 each(n.getElementsByTagName('option'), function(n) {
\r
6313 var v = t.dom.getAttrib(n, 'selected');
\r
6315 selected.push(v ? v : null);
\r
6319 n = n.cloneNode(true);
\r
6321 // IE looses the selected attribute on option elements so we need to restore it
\r
6323 each(n.getElementsByTagName('option'), function(n, i) {
\r
6324 t.dom.setAttrib(n, 'selected', selected[i]);
\r
6328 // Nodes needs to be attached to something in WebKit/Opera
\r
6329 // Older builds of Opera crashes if you attach the node to an document created dynamically
\r
6330 // and since we can't feature detect a crash we need to sniff the acutal build number
\r
6331 // This fix will make DOM ranges and make Sizzle happy!
\r
6332 impl = n.ownerDocument.implementation;
\r
6333 if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
\r
6334 // Create an empty HTML document
\r
6335 doc = impl.createHTMLDocument("");
\r
6337 // Add the element or it's children if it's a body element to the new document
\r
6338 each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
\r
6339 doc.body.appendChild(doc.importNode(node, true));
\r
6342 // Grab first child or body element for serialization
\r
6343 if (n.nodeName != 'BODY')
\r
6344 n = doc.body.firstChild;
\r
6348 // set the new document in DOMUtils so createElement etc works
\r
6349 oldDoc = t.dom.doc;
\r
6353 t.key = '' + (parseInt(t.key) + 1);
\r
6356 if (!o.no_events) {
\r
6358 t.onPreProcess.dispatch(t, o);
\r
6361 // Serialize HTML DOM into a string
\r
6364 t._serializeNode(n, o.getInner);
\r
6367 o.content = t.writer.getContent();
\r
6369 // Restore the old document if it was changed
\r
6371 t.dom.doc = oldDoc;
\r
6374 t.onPostProcess.dispatch(t, o);
\r
6376 t._postProcess(o);
\r
6379 return tinymce.trim(o.content);
\r
6382 // Internal functions
\r
6384 _postProcess : function(o) {
\r
6385 var t = this, s = t.settings, h = o.content, sc = [], p;
\r
6387 if (o.format == 'html') {
\r
6388 // Protect some elements
\r
6392 {pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
\r
6393 {pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
\r
6394 {pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
\r
6395 {pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
\r
6396 {pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
\r
6403 if (s.entity_encoding !== 'raw')
\r
6406 // Use BR instead of padded P elements inside editor and use <p> </p> outside editor
\r
6408 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
\r
6410 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
\r
6412 // Since Gecko and Safari keeps whitespace in the DOM we need to
\r
6413 // remove it inorder to match other browsers. But I think Gecko and Safari is right.
\r
6414 // This process is only done when getting contents out from the editor.
\r
6416 // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
\r
6417 h = tinymce._replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1> </p>' : '<p$1> </p>', h);
\r
6419 if (s.remove_linebreaks) {
\r
6420 h = h.replace(/\r?\n|\r/g, ' ');
\r
6421 h = tinymce._replace(/(<[^>]+>)\s+/g, '$1 ', h);
\r
6422 h = tinymce._replace(/\s+(<\/[^>]+>)/g, ' $1', h);
\r
6423 h = tinymce._replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>', h); // Trim block start
\r
6424 h = tinymce._replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>', h); // Trim block start
\r
6425 h = tinymce._replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, '</$1>', h); // Trim block end
\r
6428 // Simple indentation
\r
6429 if (s.apply_source_formatting && s.indent_mode == 'simple') {
\r
6430 // Add line breaks before and after block elements
\r
6431 h = tinymce._replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n', h);
\r
6432 h = tinymce._replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>', h);
\r
6433 h = tinymce._replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n', h);
\r
6434 h = h.replace(/\n\n/g, '\n');
\r
6438 h = t._unprotect(h, p);
\r
6440 // Restore CDATA sections
\r
6441 h = tinymce._replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>', h);
\r
6443 // Restore the \u00a0 character if raw mode is enabled
\r
6444 if (s.entity_encoding == 'raw')
\r
6445 h = tinymce._replace(/<p> <\/p>|<p([^>]+)> <\/p>/g, '<p$1>\u00a0</p>', h);
\r
6447 // Restore noscript elements
\r
6448 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
6449 return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
\r
6456 _serializeNode : function(n, inner) {
\r
6457 var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type, scopeName;
\r
6459 if (!s.node_filter || s.node_filter(n)) {
\r
6460 switch (n.nodeType) {
\r
6461 case 1: // Element
\r
6462 if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
\r
6465 iv = keep = false;
\r
6466 hc = n.hasChildNodes();
\r
6467 nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
\r
6469 // Get internal type
\r
6470 type = n.getAttribute('_mce_type');
\r
6472 if (!t._info.cleanup) {
\r
6479 // Add correct prefix on IE
\r
6481 scopeName = n.scopeName;
\r
6482 if (scopeName && scopeName !== 'HTML' && scopeName !== 'html')
\r
6483 nn = scopeName + ':' + nn;
\r
6486 // Remove mce prefix on IE needed for the abbr element
\r
6487 if (nn.indexOf('mce:') === 0)
\r
6488 nn = nn.substring(4);
\r
6492 if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
\r
6499 // Fix IE content duplication (DOM can have multiple copies of the same node)
\r
6500 if (s.fix_content_duplication) {
\r
6501 if (n._mce_serialized == t.key)
\r
6504 n._mce_serialized = t.key;
\r
6507 // IE sometimes adds a / infront of the node name
\r
6508 if (nn.charAt(0) == '/')
\r
6509 nn = nn.substring(1);
\r
6510 } else if (isGecko) {
\r
6511 // Ignore br elements
\r
6512 if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
\r
6516 // Check if valid child
\r
6517 if (s.validate_children) {
\r
6518 if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
\r
6523 t.elementName = nn;
\r
6526 ru = t.findRule(nn);
\r
6528 // No valid rule for this element could be found then skip it
\r
6534 nn = ru.name || nn;
\r
6535 closed = s.closed.test(nn);
\r
6537 // Skip empty nodes or empty node name in IE
\r
6538 if ((!hc && ru.noEmpty) || (isIE && !nn)) {
\r
6544 if (ru.requiredAttribs) {
\r
6545 a = ru.requiredAttribs;
\r
6547 for (i = a.length - 1; i >= 0; i--) {
\r
6548 if (this.dom.getAttrib(n, a[i]) !== '')
\r
6552 // None of the required was there
\r
6559 w.writeStartElement(nn);
\r
6561 // Add ordered attributes
\r
6563 for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
\r
6565 v = t._getAttrib(n, a);
\r
6568 w.writeAttribute(a.name, v);
\r
6572 // Add wild attributes
\r
6573 if (ru.validAttribsRE) {
\r
6574 at = t.dom.getAttribs(n);
\r
6575 for (i=at.length-1; i>-1; i--) {
\r
6578 if (no.specified) {
\r
6579 a = no.nodeName.toLowerCase();
\r
6581 if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
\r
6584 ar = t.findAttribRule(ru, a);
\r
6585 v = t._getAttrib(n, ar, a);
\r
6588 w.writeAttribute(a, v);
\r
6593 // Keep type attribute
\r
6595 w.writeAttribute('_mce_type', type);
\r
6597 // Write text from script
\r
6598 if (nn === 'script' && tinymce.trim(n.innerHTML)) {
\r
6599 w.writeText('// '); // Padd it with a comment so it will parse on older browsers
\r
6600 w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
\r
6605 // Padd empty nodes with a
\r
6607 // If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
\r
6608 if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
\r
6609 if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
\r
6610 w.writeText('\u00a0');
\r
6612 w.writeText('\u00a0'); // No children then padd it
\r
6618 // Check if valid child
\r
6619 if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
\r
6622 return w.writeText(n.nodeValue);
\r
6625 return w.writeCDATA(n.nodeValue);
\r
6627 case 8: // Comment
\r
6628 return w.writeComment(n.nodeValue);
\r
6630 } else if (n.nodeType == 1)
\r
6631 hc = n.hasChildNodes();
\r
6633 if (hc && !closed) {
\r
6634 cn = n.firstChild;
\r
6637 t._serializeNode(cn);
\r
6638 t.elementName = nn;
\r
6639 cn = cn.nextSibling;
\r
6643 // Write element end
\r
6646 w.writeFullEndElement();
\r
6648 w.writeEndElement();
\r
6652 _protect : function(o) {
\r
6655 o.items = o.items || [];
\r
6658 return s.replace(/[\r\n\\]/g, function(c) {
\r
6661 else if (c === '\\')
\r
6669 return s.replace(/\\[\\rn]/g, function(c) {
\r
6672 else if (c === '\\\\')
\r
6679 each(o.patterns, function(p) {
\r
6680 o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
\r
6687 return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
\r
6694 _unprotect : function(h, o) {
\r
6695 h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
\r
6696 return o.items[parseInt(b)];
\r
6704 _encode : function(h) {
\r
6705 var t = this, s = t.settings, l;
\r
6708 if (s.entity_encoding !== 'raw') {
\r
6709 if (s.entity_encoding.indexOf('named') != -1) {
\r
6710 t.setEntities(s.entities);
\r
6711 l = t.entityLookup;
\r
6713 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
6717 a = '&' + v + ';';
\r
6723 if (s.entity_encoding.indexOf('numeric') != -1) {
\r
6724 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
6725 return '&#' + a.charCodeAt(0) + ';';
\r
6733 _setup : function() {
\r
6734 var t = this, s = this.settings;
\r
6741 t.setRules(s.valid_elements);
\r
6742 t.addRules(s.extended_valid_elements);
\r
6744 if (s.invalid_elements)
\r
6745 t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
\r
6747 if (s.attrib_value_filter)
\r
6748 t.attribValueFilter = s.attribValueFilter;
\r
6751 _getAttrib : function(n, a, na) {
\r
6754 na = na || a.name;
\r
6756 if (a.forcedVal && (v = a.forcedVal)) {
\r
6757 if (v === '{$uid}')
\r
6758 return this.dom.uniqueId();
\r
6763 v = this.dom.getAttrib(n, na);
\r
6768 // Whats the point? Remove usless attribute value
\r
6775 if (this.attribValueFilter)
\r
6776 v = this.attribValueFilter(na, v, n);
\r
6778 if (a.validVals) {
\r
6779 for (i = a.validVals.length - 1; i >= 0; i--) {
\r
6780 if (v == a.validVals[i])
\r
6788 if (v === '' && typeof(a.defaultVal) != 'undefined') {
\r
6791 if (v === '{$uid}')
\r
6792 return this.dom.uniqueId();
\r
6796 // Remove internal mceItemXX classes when content is extracted from editor
\r
6797 if (na == 'class' && this.processObj.get)
\r
6798 v = v.replace(/\s?mceItem\w+\s?/g, '');
\r
6810 (function(tinymce) {
\r
6811 tinymce.dom.ScriptLoader = function(settings) {
\r
6817 scriptLoadedCallbacks = {},
\r
6818 queueLoadedCallbacks = [],
\r
6822 function loadScript(url, callback) {
\r
6823 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
\r
6825 // Execute callback when script is loaded
\r
6830 elm.onreadystatechange = elm.onload = elm = null;
\r
6835 id = dom.uniqueId();
\r
6837 if (tinymce.isIE6) {
\r
6838 uri = new tinymce.util.URI(url);
\r
6841 // If script is from same domain and we
\r
6842 // use IE 6 then use XHR since it's more reliable
\r
6843 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
\r
6844 tinymce.util.XHR.send({
\r
6845 url : tinymce._addVer(uri.getURI()),
\r
6846 success : function(content) {
\r
6847 // Create new temp script element
\r
6848 var script = dom.create('script', {
\r
6849 type : 'text/javascript'
\r
6852 // Evaluate script in global scope
\r
6853 script.text = content;
\r
6854 document.getElementsByTagName('head')[0].appendChild(script);
\r
6855 dom.remove(script);
\r
6865 // Create new script element
\r
6866 elm = dom.create('script', {
\r
6868 type : 'text/javascript',
\r
6869 src : tinymce._addVer(url)
\r
6872 // Add onload listener for non IE browsers since IE9
\r
6873 // fires onload event before the script is parsed and executed
\r
6874 if (!tinymce.isIE)
\r
6875 elm.onload = done;
\r
6877 elm.onreadystatechange = function() {
\r
6878 var state = elm.readyState;
\r
6880 // Loaded state is passed on IE 6 however there
\r
6881 // are known issues with this method but we can't use
\r
6882 // XHR in a cross domain loading
\r
6883 if (state == 'complete' || state == 'loaded')
\r
6887 // Most browsers support this feature so we report errors
\r
6888 // for those at least to help users track their missing plugins etc
\r
6889 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
\r
6890 /*elm.onerror = function() {
\r
6891 alert('Failed to load: ' + url);
\r
6894 // Add script to document
\r
6895 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
\r
6898 this.isDone = function(url) {
\r
6899 return states[url] == LOADED;
\r
6902 this.markDone = function(url) {
\r
6903 states[url] = LOADED;
\r
6906 this.add = this.load = function(url, callback, scope) {
\r
6907 var item, state = states[url];
\r
6909 // Add url to load queue
\r
6910 if (state == undefined) {
\r
6912 states[url] = QUEUED;
\r
6916 // Store away callback for later execution
\r
6917 if (!scriptLoadedCallbacks[url])
\r
6918 scriptLoadedCallbacks[url] = [];
\r
6920 scriptLoadedCallbacks[url].push({
\r
6922 scope : scope || this
\r
6927 this.loadQueue = function(callback, scope) {
\r
6928 this.loadScripts(queue, callback, scope);
\r
6931 this.loadScripts = function(scripts, callback, scope) {
\r
6934 function execScriptLoadedCallbacks(url) {
\r
6935 // Execute URL callback functions
\r
6936 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
\r
6937 callback.func.call(callback.scope);
\r
6940 scriptLoadedCallbacks[url] = undefined;
\r
6943 queueLoadedCallbacks.push({
\r
6945 scope : scope || this
\r
6948 loadScripts = function() {
\r
6949 var loadingScripts = tinymce.grep(scripts);
\r
6951 // Current scripts has been handled
\r
6952 scripts.length = 0;
\r
6954 // Load scripts that needs to be loaded
\r
6955 tinymce.each(loadingScripts, function(url) {
\r
6956 // Script is already loaded then execute script callbacks directly
\r
6957 if (states[url] == LOADED) {
\r
6958 execScriptLoadedCallbacks(url);
\r
6962 // Is script not loading then start loading it
\r
6963 if (states[url] != LOADING) {
\r
6964 states[url] = LOADING;
\r
6967 loadScript(url, function() {
\r
6968 states[url] = LOADED;
\r
6971 execScriptLoadedCallbacks(url);
\r
6973 // Load more scripts if they where added by the recently loaded script
\r
6979 // No scripts are currently loading then execute all pending queue loaded callbacks
\r
6981 tinymce.each(queueLoadedCallbacks, function(callback) {
\r
6982 callback.func.call(callback.scope);
\r
6985 queueLoadedCallbacks.length = 0;
\r
6993 // Global script loader
\r
6994 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
\r
6997 tinymce.dom.TreeWalker = function(start_node, root_node) {
\r
6998 var node = start_node;
\r
7000 function findSibling(node, start_name, sibling_name, shallow) {
\r
7001 var sibling, parent;
\r
7004 // Walk into nodes if it has a start
\r
7005 if (!shallow && node[start_name])
\r
7006 return node[start_name];
\r
7008 // Return the sibling if it has one
\r
7009 if (node != root_node) {
\r
7010 sibling = node[sibling_name];
\r
7014 // Walk up the parents to look for siblings
\r
7015 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
\r
7016 sibling = parent[sibling_name];
\r
7024 this.current = function() {
\r
7028 this.next = function(shallow) {
\r
7029 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
\r
7032 this.prev = function(shallow) {
\r
7033 return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
\r
7038 var transitional = {};
\r
7040 function unpack(lookup, data) {
\r
7043 function replace(value) {
\r
7044 return value.replace(/[A-Z]+/g, function(key) {
\r
7045 return replace(lookup[key]);
\r
7050 for (key in lookup) {
\r
7051 if (lookup.hasOwnProperty(key))
\r
7052 lookup[key] = replace(lookup[key]);
\r
7055 // Unpack and parse data into object map
\r
7056 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
\r
7059 children = children.split(/\|/);
\r
7061 for (i = children.length - 1; i >= 0; i--)
\r
7062 map[children[i]] = 1;
\r
7064 transitional[name] = map;
\r
7068 // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
\r
7069 // we will later include the attributes here and use it as a default for valid elements but it
\r
7070 // requires us to rewrite the serializer engine
\r
7072 Z : '#|H|K|N|O|P',
\r
7073 Y : '#|X|form|R|Q',
\r
7074 X : 'p|T|div|U|W|isindex|fieldset|table',
\r
7075 W : 'pre|hr|blockquote|address|center|noframes',
\r
7076 U : 'ul|ol|dl|menu|dir',
\r
7077 ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
\r
7078 T : 'h1|h2|h3|h4|h5|h6',
\r
7081 ZA : '#|a|G|J|M|O|P',
\r
7082 R : '#|a|H|K|N|O',
\r
7084 P : 'ins|del|script',
\r
7085 O : 'input|select|textarea|label|button',
\r
7087 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
\r
7090 J : 'tt|i|b|u|s|strike',
\r
7091 I : 'big|small|font|basefont',
\r
7093 G : 'br|span|bdo',
\r
7094 F : 'object|applet|img|map|iframe'
\r
7097 'object[#|param|X|form|a|H|K|N|O|Q]' +
\r
7104 'applet[#|param|X|form|a|H|K|N|O|Q]' +
\r
7107 'map[X|form|Q|area]' +
\r
7109 'iframe[#|X|form|a|H|K|N|O|Q]' +
\r
7135 'select[optgroup|option]' +
\r
7136 'optgroup[option]' +
\r
7140 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
\r
7142 'ins[#|X|form|a|H|K|N|O|Q]' +
\r
7144 'del[#|X|form|a|H|K|N|O|Q]' +
\r
7146 'div[#|X|form|a|H|K|N|O|Q]' +
\r
7148 'li[#|X|form|a|H|K|N|O|Q]' +
\r
7152 'dd[#|X|form|a|H|K|N|O|Q]' +
\r
7157 'blockquote[#|X|form|a|H|K|N|O|Q]' +
\r
7159 'center[#|X|form|a|H|K|N|O|Q]' +
\r
7160 'noframes[#|X|form|a|H|K|N|O|Q]' +
\r
7162 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
\r
7164 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
\r
7167 'colgroup[col]' +
\r
7170 'th[#|X|form|a|H|K|N|O|Q]' +
\r
7171 'form[#|X|a|H|K|N|O|Q]' +
\r
7172 'noscript[#|X|form|a|H|K|N|O|Q]' +
\r
7173 'td[#|X|form|a|H|K|N|O|Q]' +
\r
7178 'body[#|X|form|a|H|K|N|O|Q]'
\r
7181 tinymce.dom.Schema = function() {
\r
7182 var t = this, elements = transitional;
\r
7184 t.isValid = function(name, child_name) {
\r
7185 var element = elements[name];
\r
7187 return !!(element && (!child_name || element[child_name]));
\r
7191 (function(tinymce) {
\r
7192 tinymce.dom.RangeUtils = function(dom) {
\r
7193 var INVISIBLE_CHAR = '\uFEFF';
\r
7195 this.walk = function(rng, callback) {
\r
7196 var startContainer = rng.startContainer,
\r
7197 startOffset = rng.startOffset,
\r
7198 endContainer = rng.endContainer,
\r
7199 endOffset = rng.endOffset,
\r
7200 ancestor, startPoint,
\r
7201 endPoint, node, parent, siblings, nodes;
\r
7203 // Handle table cell selection the table plugin enables
\r
7204 // you to fake select table cells and perform formatting actions on them
\r
7205 nodes = dom.select('td.mceSelected,th.mceSelected');
\r
7206 if (nodes.length > 0) {
\r
7207 tinymce.each(nodes, function(node) {
\r
7214 function collectSiblings(node, name, end_node) {
\r
7215 var siblings = [];
\r
7217 for (; node && node != end_node; node = node[name])
\r
7218 siblings.push(node);
\r
7223 function findEndPoint(node, root) {
\r
7225 if (node.parentNode == root)
\r
7228 node = node.parentNode;
\r
7232 function walkBoundary(start_node, end_node, next) {
\r
7233 var siblingName = next ? 'nextSibling' : 'previousSibling';
\r
7235 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
\r
7236 parent = node.parentNode;
\r
7237 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
\r
7239 if (siblings.length) {
\r
7241 siblings.reverse();
\r
7243 callback(siblings);
\r
7248 // If index based start position then resolve it
\r
7249 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
\r
7250 startContainer = startContainer.childNodes[startOffset];
\r
7252 // If index based end position then resolve it
\r
7253 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
\r
7254 endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
\r
7256 // Find common ancestor and end points
\r
7257 ancestor = dom.findCommonAncestor(startContainer, endContainer);
\r
7260 if (startContainer == endContainer)
\r
7261 return callback([startContainer]);
\r
7263 // Process left side
\r
7264 for (node = startContainer; node; node = node.parentNode) {
\r
7265 if (node == endContainer)
\r
7266 return walkBoundary(startContainer, ancestor, true);
\r
7268 if (node == ancestor)
\r
7272 // Process right side
\r
7273 for (node = endContainer; node; node = node.parentNode) {
\r
7274 if (node == startContainer)
\r
7275 return walkBoundary(endContainer, ancestor);
\r
7277 if (node == ancestor)
\r
7281 // Find start/end point
\r
7282 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
\r
7283 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
\r
7286 walkBoundary(startContainer, startPoint, true);
\r
7288 // Walk the middle from start to end point
\r
7289 siblings = collectSiblings(
\r
7290 startPoint == startContainer ? startPoint : startPoint.nextSibling,
\r
7292 endPoint == endContainer ? endPoint.nextSibling : endPoint
\r
7295 if (siblings.length)
\r
7296 callback(siblings);
\r
7298 // Walk right leaf
\r
7299 walkBoundary(endContainer, endPoint);
\r
7302 /* this.split = function(rng) {
\r
7303 var startContainer = rng.startContainer,
\r
7304 startOffset = rng.startOffset,
\r
7305 endContainer = rng.endContainer,
\r
7306 endOffset = rng.endOffset;
\r
7308 function splitText(node, offset) {
\r
7309 if (offset == node.nodeValue.length)
\r
7310 node.appendData(INVISIBLE_CHAR);
\r
7312 node = node.splitText(offset);
\r
7314 if (node.nodeValue === INVISIBLE_CHAR)
\r
7315 node.nodeValue = '';
\r
7320 // Handle single text node
\r
7321 if (startContainer == endContainer) {
\r
7322 if (startContainer.nodeType == 3) {
\r
7323 if (startOffset != 0)
\r
7324 startContainer = endContainer = splitText(startContainer, startOffset);
\r
7326 if (endOffset - startOffset != startContainer.nodeValue.length)
\r
7327 splitText(startContainer, endOffset - startOffset);
\r
7330 // Split startContainer text node if needed
\r
7331 if (startContainer.nodeType == 3 && startOffset != 0) {
\r
7332 startContainer = splitText(startContainer, startOffset);
\r
7336 // Split endContainer text node if needed
\r
7337 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
\r
7338 endContainer = splitText(endContainer, endOffset).previousSibling;
\r
7339 endOffset = endContainer.nodeValue.length;
\r
7344 startContainer : startContainer,
\r
7345 startOffset : startOffset,
\r
7346 endContainer : endContainer,
\r
7347 endOffset : endOffset
\r
7353 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
\r
7354 if (rng1 && rng2) {
\r
7355 // Compare native IE ranges
\r
7356 if (rng1.item || rng1.duplicate) {
\r
7357 // Both are control ranges and the selected element matches
\r
7358 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
\r
7361 // Both are text ranges and the range matches
\r
7362 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
\r
7365 // Compare w3c ranges
\r
7366 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
\r
7374 (function(tinymce) {
\r
7375 // Shorten class names
\r
7376 var DOM = tinymce.DOM, is = tinymce.is;
\r
7378 tinymce.create('tinymce.ui.Control', {
\r
7379 Control : function(id, s) {
\r
7381 this.settings = s = s || {};
\r
7382 this.rendered = false;
\r
7383 this.onRender = new tinymce.util.Dispatcher(this);
\r
7384 this.classPrefix = '';
\r
7385 this.scope = s.scope || this;
\r
7386 this.disabled = 0;
\r
7390 setDisabled : function(s) {
\r
7393 if (s != this.disabled) {
\r
7394 e = DOM.get(this.id);
\r
7396 // Add accessibility title for unavailable actions
\r
7397 if (e && this.settings.unavailable_prefix) {
\r
7399 this.prevTitle = e.title;
\r
7400 e.title = this.settings.unavailable_prefix + ": " + e.title;
\r
7402 e.title = this.prevTitle;
\r
7405 this.setState('Disabled', s);
\r
7406 this.setState('Enabled', !s);
\r
7407 this.disabled = s;
\r
7411 isDisabled : function() {
\r
7412 return this.disabled;
\r
7415 setActive : function(s) {
\r
7416 if (s != this.active) {
\r
7417 this.setState('Active', s);
\r
7422 isActive : function() {
\r
7423 return this.active;
\r
7426 setState : function(c, s) {
\r
7427 var n = DOM.get(this.id);
\r
7429 c = this.classPrefix + c;
\r
7432 DOM.addClass(n, c);
\r
7434 DOM.removeClass(n, c);
\r
7437 isRendered : function() {
\r
7438 return this.rendered;
\r
7441 renderHTML : function() {
\r
7444 renderTo : function(n) {
\r
7445 DOM.setHTML(n, this.renderHTML());
\r
7448 postRender : function() {
\r
7451 // Set pending states
\r
7452 if (is(t.disabled)) {
\r
7458 if (is(t.active)) {
\r
7465 remove : function() {
\r
7466 DOM.remove(this.id);
\r
7470 destroy : function() {
\r
7471 tinymce.dom.Event.clear(this.id);
\r
7475 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
\r
7476 Container : function(id, s) {
\r
7477 this.parent(id, s);
\r
7479 this.controls = [];
\r
7484 add : function(c) {
\r
7485 this.lookup[c.id] = c;
\r
7486 this.controls.push(c);
\r
7491 get : function(n) {
\r
7492 return this.lookup[n];
\r
7497 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
\r
7498 Separator : function(id, s) {
\r
7499 this.parent(id, s);
\r
7500 this.classPrefix = 'mceSeparator';
\r
7503 renderHTML : function() {
\r
7504 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
\r
7508 (function(tinymce) {
\r
7509 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
7511 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
\r
7512 MenuItem : function(id, s) {
\r
7513 this.parent(id, s);
\r
7514 this.classPrefix = 'mceMenuItem';
\r
7517 setSelected : function(s) {
\r
7518 this.setState('Selected', s);
\r
7519 this.selected = s;
\r
7522 isSelected : function() {
\r
7523 return this.selected;
\r
7526 postRender : function() {
\r
7531 // Set pending state
\r
7532 if (is(t.selected))
\r
7533 t.setSelected(t.selected);
\r
7538 (function(tinymce) {
\r
7539 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
7541 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
\r
7542 Menu : function(id, s) {
\r
7547 t.collapsed = false;
\r
7549 t.onAddItem = new tinymce.util.Dispatcher(this);
\r
7552 expand : function(d) {
\r
7556 walk(t, function(o) {
\r
7562 t.collapsed = false;
\r
7565 collapse : function(d) {
\r
7569 walk(t, function(o) {
\r
7575 t.collapsed = true;
\r
7578 isCollapsed : function() {
\r
7579 return this.collapsed;
\r
7582 add : function(o) {
\r
7584 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
\r
7586 this.onAddItem.dispatch(this, o);
\r
7588 return this.items[o.id] = o;
\r
7591 addSeparator : function() {
\r
7592 return this.add({separator : true});
\r
7595 addMenu : function(o) {
\r
7597 o = this.createMenu(o);
\r
7601 return this.add(o);
\r
7604 hasMenus : function() {
\r
7605 return this.menuCount !== 0;
\r
7608 remove : function(o) {
\r
7609 delete this.items[o.id];
\r
7612 removeAll : function() {
\r
7615 walk(t, function(o) {
\r
7627 createMenu : function(o) {
\r
7628 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
\r
7630 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
\r
7636 (function(tinymce) {
\r
7637 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
\r
7639 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
\r
7640 DropMenu : function(id, s) {
\r
7642 s.container = s.container || DOM.doc.body;
\r
7643 s.offset_x = s.offset_x || 0;
\r
7644 s.offset_y = s.offset_y || 0;
\r
7645 s.vp_offset_x = s.vp_offset_x || 0;
\r
7646 s.vp_offset_y = s.vp_offset_y || 0;
\r
7648 if (is(s.icons) && !s.icons)
\r
7649 s['class'] += ' mceNoIcons';
\r
7651 this.parent(id, s);
\r
7652 this.onShowMenu = new tinymce.util.Dispatcher(this);
\r
7653 this.onHideMenu = new tinymce.util.Dispatcher(this);
\r
7654 this.classPrefix = 'mceMenu';
\r
7657 createMenu : function(s) {
\r
7658 var t = this, cs = t.settings, m;
\r
7660 s.container = s.container || cs.container;
\r
7662 s.constrain = s.constrain || cs.constrain;
\r
7663 s['class'] = s['class'] || cs['class'];
\r
7664 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
\r
7665 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
\r
7666 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
\r
7668 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
\r
7673 update : function() {
\r
7674 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
\r
7676 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
\r
7677 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
\r
7679 if (!DOM.boxModel)
\r
7680 t.element.setStyles({width : tw + 2, height : th + 2});
\r
7682 t.element.setStyles({width : tw, height : th});
\r
7685 DOM.setStyle(co, 'width', tw);
\r
7687 if (s.max_height) {
\r
7688 DOM.setStyle(co, 'height', th);
\r
7690 if (tb.clientHeight < s.max_height)
\r
7691 DOM.setStyle(co, 'overflow', 'hidden');
\r
7695 showMenu : function(x, y, px) {
\r
7696 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
\r
7700 if (t.isMenuVisible)
\r
7703 if (!t.rendered) {
\r
7704 co = DOM.add(t.settings.container, t.renderNode());
\r
7706 each(t.items, function(o) {
\r
7710 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
7712 co = DOM.get('menu_' + t.id);
\r
7714 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
\r
7715 if (!tinymce.isOpera)
\r
7716 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
\r
7721 x += s.offset_x || 0;
\r
7722 y += s.offset_y || 0;
\r
7726 // Move inside viewport if not submenu
\r
7727 if (s.constrain) {
\r
7728 w = co.clientWidth - ot;
\r
7729 h = co.clientHeight - ot;
\r
7733 if ((x + s.vp_offset_x + w) > mx)
\r
7734 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
\r
7736 if ((y + s.vp_offset_y + h) > my)
\r
7737 y = Math.max(0, (my - s.vp_offset_y) - h);
\r
7740 DOM.setStyles(co, {left : x , top : y});
\r
7741 t.element.update();
\r
7743 t.isMenuVisible = 1;
\r
7744 t.mouseClickFunc = Event.add(co, 'click', function(e) {
\r
7749 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
\r
7750 m = t.items[e.id];
\r
7752 if (m.isDisabled())
\r
7761 dm = dm.settings.parent;
\r
7764 if (m.settings.onclick)
\r
7765 m.settings.onclick(e);
\r
7767 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
7771 if (t.hasMenus()) {
\r
7772 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
\r
7776 if (e && (e = DOM.getParent(e, 'tr'))) {
\r
7777 m = t.items[e.id];
\r
7780 t.lastMenu.collapse(1);
\r
7782 if (m.isDisabled())
\r
7785 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
\r
7786 //p = DOM.getPos(s.container);
\r
7787 r = DOM.getRect(e);
\r
7788 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
\r
7790 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
\r
7796 t.onShowMenu.dispatch(t);
\r
7798 if (s.keyboard_focus) {
\r
7799 Event.add(co, 'keydown', t._keyHandler, t);
\r
7800 DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
\r
7805 hideMenu : function(c) {
\r
7806 var t = this, co = DOM.get('menu_' + t.id), e;
\r
7808 if (!t.isMenuVisible)
\r
7811 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
7812 Event.remove(co, 'click', t.mouseClickFunc);
\r
7813 Event.remove(co, 'keydown', t._keyHandler);
\r
7815 t.isMenuVisible = 0;
\r
7823 if (e = DOM.get(t.id))
\r
7824 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
\r
7826 t.onHideMenu.dispatch(t);
\r
7829 add : function(o) {
\r
7834 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
\r
7835 t._add(DOM.select('tbody', co)[0], o);
\r
7840 collapse : function(d) {
\r
7845 remove : function(o) {
\r
7849 return this.parent(o);
\r
7852 destroy : function() {
\r
7853 var t = this, co = DOM.get('menu_' + t.id);
\r
7855 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
7856 Event.remove(co, 'click', t.mouseClickFunc);
\r
7859 t.element.remove();
\r
7864 renderNode : function() {
\r
7865 var t = this, s = t.settings, n, tb, co, w;
\r
7867 w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
\r
7868 co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
\r
7869 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
7872 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
\r
7874 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
\r
7875 n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
\r
7876 tb = DOM.add(n, 'tbody');
\r
7878 each(t.items, function(o) {
\r
7882 t.rendered = true;
\r
7887 // Internal functions
\r
7889 _keyHandler : function(e) {
\r
7890 var t = this, kc = e.keyCode;
\r
7892 function focus(d) {
\r
7893 var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
\r
7903 focus(-1); // Select first link
\r
7914 return this.hideMenu();
\r
7918 _add : function(tb, o) {
\r
7919 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
\r
7921 if (s.separator) {
\r
7922 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
\r
7923 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
\r
7925 if (n = ro.previousSibling)
\r
7926 DOM.addClass(n, 'mceLast');
\r
7931 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
\r
7932 n = it = DOM.add(n, 'td');
\r
7933 n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
\r
7935 DOM.addClass(it, s['class']);
\r
7936 // n = DOM.add(n, 'span', {'class' : 'item'});
\r
7938 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
\r
7941 DOM.add(ic, 'img', {src : s.icon_src});
\r
7943 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
\r
7945 if (o.settings.style)
\r
7946 DOM.setAttrib(n, 'style', o.settings.style);
\r
7948 if (tb.childNodes.length == 1)
\r
7949 DOM.addClass(ro, 'mceFirst');
\r
7951 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
\r
7952 DOM.addClass(ro, 'mceFirst');
\r
7955 DOM.addClass(ro, cp + 'ItemSub');
\r
7957 if (n = ro.previousSibling)
\r
7958 DOM.removeClass(n, 'mceLast');
\r
7960 DOM.addClass(ro, 'mceLast');
\r
7964 (function(tinymce) {
\r
7965 var DOM = tinymce.DOM;
\r
7967 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
\r
7968 Button : function(id, s) {
\r
7969 this.parent(id, s);
\r
7970 this.classPrefix = 'mceButton';
\r
7973 renderHTML : function() {
\r
7974 var cp = this.classPrefix, s = this.settings, h, l;
\r
7976 l = DOM.encode(s.label || '');
\r
7977 h = '<a id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" title="' + DOM.encode(s.title) + '">';
\r
7980 h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
\r
7982 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
\r
7987 postRender : function() {
\r
7988 var t = this, s = t.settings;
\r
7990 tinymce.dom.Event.add(t.id, 'click', function(e) {
\r
7991 if (!t.isDisabled())
\r
7992 return s.onclick.call(s.scope, e);
\r
7998 (function(tinymce) {
\r
7999 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
8001 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
\r
8002 ListBox : function(id, s) {
\r
8009 t.onChange = new Dispatcher(t);
\r
8011 t.onPostRender = new Dispatcher(t);
\r
8013 t.onAdd = new Dispatcher(t);
\r
8015 t.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
8017 t.classPrefix = 'mceListBox';
\r
8020 select : function(va) {
\r
8021 var t = this, fv, f;
\r
8023 if (va == undefined)
\r
8024 return t.selectByIndex(-1);
\r
8026 // Is string or number make function selector
\r
8027 if (va && va.call)
\r
8035 // Do we need to do something?
\r
8036 if (va != t.selectedValue) {
\r
8038 each(t.items, function(o, i) {
\r
8041 t.selectByIndex(i);
\r
8047 t.selectByIndex(-1);
\r
8051 selectByIndex : function(idx) {
\r
8052 var t = this, e, o;
\r
8054 if (idx != t.selectedIndex) {
\r
8055 e = DOM.get(t.id + '_text');
\r
8059 t.selectedValue = o.value;
\r
8060 t.selectedIndex = idx;
\r
8061 DOM.setHTML(e, DOM.encode(o.title));
\r
8062 DOM.removeClass(e, 'mceTitle');
\r
8064 DOM.setHTML(e, DOM.encode(t.settings.title));
\r
8065 DOM.addClass(e, 'mceTitle');
\r
8066 t.selectedValue = t.selectedIndex = null;
\r
8073 add : function(n, v, o) {
\r
8077 o = tinymce.extend(o, {
\r
8083 t.onAdd.dispatch(t, o);
\r
8086 getLength : function() {
\r
8087 return this.items.length;
\r
8090 renderHTML : function() {
\r
8091 var h = '', t = this, s = t.settings, cp = t.classPrefix;
\r
8093 h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
\r
8094 h += '<td>' + DOM.createHTML('a', {id : t.id + '_text', href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
\r
8095 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span></span>') + '</td>';
\r
8096 h += '</tr></tbody></table>';
\r
8101 showMenu : function() {
\r
8102 var t = this, p1, p2, e = DOM.get(this.id), m;
\r
8104 if (t.isDisabled() || t.items.length == 0)
\r
8107 if (t.menu && t.menu.isMenuVisible)
\r
8108 return t.hideMenu();
\r
8110 if (!t.isMenuRendered) {
\r
8112 t.isMenuRendered = true;
\r
8115 p1 = DOM.getPos(this.settings.menu_container);
\r
8116 p2 = DOM.getPos(e);
\r
8119 m.settings.offset_x = p2.x;
\r
8120 m.settings.offset_y = p2.y;
\r
8121 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
\r
8125 m.items[t.oldID].setSelected(0);
\r
8127 each(t.items, function(o) {
\r
8128 if (o.value === t.selectedValue) {
\r
8129 m.items[o.id].setSelected(1);
\r
8134 m.showMenu(0, e.clientHeight);
\r
8136 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8137 DOM.addClass(t.id, t.classPrefix + 'Selected');
\r
8139 //DOM.get(t.id + '_text').focus();
\r
8142 hideMenu : function(e) {
\r
8145 if (t.menu && t.menu.isMenuVisible) {
\r
8146 // Prevent double toogles by canceling the mouse click event to the button
\r
8147 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
\r
8150 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
8151 DOM.removeClass(t.id, t.classPrefix + 'Selected');
\r
8152 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8153 t.menu.hideMenu();
\r
8158 renderMenu : function() {
\r
8161 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
8163 'class' : t.classPrefix + 'Menu mceNoIcons',
\r
8168 m.onHideMenu.add(t.hideMenu, t);
\r
8171 title : t.settings.title,
\r
8172 'class' : 'mceMenuItemTitle',
\r
8173 onclick : function() {
\r
8174 if (t.settings.onselect('') !== false)
\r
8175 t.select(''); // Must be runned after
\r
8179 each(t.items, function(o) {
\r
8180 // No value then treat it as a title
\r
8181 if (o.value === undefined) {
\r
8184 'class' : 'mceMenuItemTitle',
\r
8185 onclick : function() {
\r
8186 if (t.settings.onselect('') !== false)
\r
8187 t.select(''); // Must be runned after
\r
8191 o.id = DOM.uniqueId();
\r
8192 o.onclick = function() {
\r
8193 if (t.settings.onselect(o.value) !== false)
\r
8194 t.select(o.value); // Must be runned after
\r
8201 t.onRenderMenu.dispatch(t, m);
\r
8205 postRender : function() {
\r
8206 var t = this, cp = t.classPrefix;
\r
8208 Event.add(t.id, 'click', t.showMenu, t);
\r
8209 Event.add(t.id + '_text', 'focus', function() {
\r
8210 if (!t._focused) {
\r
8211 t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
\r
8212 var idx = -1, v, kc = e.keyCode;
\r
8214 // Find current index
\r
8215 each(t.items, function(v, i) {
\r
8216 if (t.selectedValue == v.value)
\r
8222 v = t.items[idx - 1];
\r
8223 else if (kc == 40)
\r
8224 v = t.items[idx + 1];
\r
8225 else if (kc == 13) {
\r
8226 // Fake select on enter
\r
8227 v = t.selectedValue;
\r
8228 t.selectedValue = null; // Needs to be null to fake change
\r
8229 t.settings.onselect(v);
\r
8230 return Event.cancel(e);
\r
8235 t.select(v.value);
\r
8242 Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
\r
8244 // Old IE doesn't have hover on all elements
\r
8245 if (tinymce.isIE6 || !DOM.boxModel) {
\r
8246 Event.add(t.id, 'mouseover', function() {
\r
8247 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
8248 DOM.addClass(t.id, cp + 'Hover');
\r
8251 Event.add(t.id, 'mouseout', function() {
\r
8252 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
8253 DOM.removeClass(t.id, cp + 'Hover');
\r
8257 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
8260 destroy : function() {
\r
8263 Event.clear(this.id + '_text');
\r
8264 Event.clear(this.id + '_open');
\r
8268 (function(tinymce) {
\r
8269 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
8271 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
\r
8272 NativeListBox : function(id, s) {
\r
8273 this.parent(id, s);
\r
8274 this.classPrefix = 'mceNativeListBox';
\r
8277 setDisabled : function(s) {
\r
8278 DOM.get(this.id).disabled = s;
\r
8281 isDisabled : function() {
\r
8282 return DOM.get(this.id).disabled;
\r
8285 select : function(va) {
\r
8286 var t = this, fv, f;
\r
8288 if (va == undefined)
\r
8289 return t.selectByIndex(-1);
\r
8291 // Is string or number make function selector
\r
8292 if (va && va.call)
\r
8300 // Do we need to do something?
\r
8301 if (va != t.selectedValue) {
\r
8303 each(t.items, function(o, i) {
\r
8306 t.selectByIndex(i);
\r
8312 t.selectByIndex(-1);
\r
8316 selectByIndex : function(idx) {
\r
8317 DOM.get(this.id).selectedIndex = idx + 1;
\r
8318 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
\r
8321 add : function(n, v, a) {
\r
8327 if (t.isRendered())
\r
8328 DOM.add(DOM.get(this.id), 'option', a, n);
\r
8337 t.onAdd.dispatch(t, o);
\r
8340 getLength : function() {
\r
8341 return this.items.length;
\r
8344 renderHTML : function() {
\r
8347 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
\r
8349 each(t.items, function(it) {
\r
8350 h += DOM.createHTML('option', {value : it.value}, it.title);
\r
8353 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
\r
8358 postRender : function() {
\r
8361 t.rendered = true;
\r
8363 function onChange(e) {
\r
8364 var v = t.items[e.target.selectedIndex - 1];
\r
8366 if (v && (v = v.value)) {
\r
8367 t.onChange.dispatch(t, v);
\r
8369 if (t.settings.onselect)
\r
8370 t.settings.onselect(v);
\r
8374 Event.add(t.id, 'change', onChange);
\r
8376 // Accessibility keyhandler
\r
8377 Event.add(t.id, 'keydown', function(e) {
\r
8380 Event.remove(t.id, 'change', ch);
\r
8382 bf = Event.add(t.id, 'blur', function() {
\r
8383 Event.add(t.id, 'change', onChange);
\r
8384 Event.remove(t.id, 'blur', bf);
\r
8387 if (e.keyCode == 13 || e.keyCode == 32) {
\r
8389 return Event.cancel(e);
\r
8393 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
8397 (function(tinymce) {
\r
8398 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
8400 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
\r
8401 MenuButton : function(id, s) {
\r
8402 this.parent(id, s);
\r
8404 this.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
8406 s.menu_container = s.menu_container || DOM.doc.body;
\r
8409 showMenu : function() {
\r
8410 var t = this, p1, p2, e = DOM.get(t.id), m;
\r
8412 if (t.isDisabled())
\r
8415 if (!t.isMenuRendered) {
\r
8417 t.isMenuRendered = true;
\r
8420 if (t.isMenuVisible)
\r
8421 return t.hideMenu();
\r
8423 p1 = DOM.getPos(t.settings.menu_container);
\r
8424 p2 = DOM.getPos(e);
\r
8427 m.settings.offset_x = p2.x;
\r
8428 m.settings.offset_y = p2.y;
\r
8429 m.settings.vp_offset_x = p2.x;
\r
8430 m.settings.vp_offset_y = p2.y;
\r
8431 m.settings.keyboard_focus = t._focused;
\r
8432 m.showMenu(0, e.clientHeight);
\r
8434 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8435 t.setState('Selected', 1);
\r
8437 t.isMenuVisible = 1;
\r
8440 renderMenu : function() {
\r
8443 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
8445 'class' : this.classPrefix + 'Menu',
\r
8446 icons : t.settings.icons
\r
8449 m.onHideMenu.add(t.hideMenu, t);
\r
8451 t.onRenderMenu.dispatch(t, m);
\r
8455 hideMenu : function(e) {
\r
8458 // Prevent double toogles by canceling the mouse click event to the button
\r
8459 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
\r
8462 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
8463 t.setState('Selected', 0);
\r
8464 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8466 t.menu.hideMenu();
\r
8469 t.isMenuVisible = 0;
\r
8472 postRender : function() {
\r
8473 var t = this, s = t.settings;
\r
8475 Event.add(t.id, 'click', function() {
\r
8476 if (!t.isDisabled()) {
\r
8478 s.onclick(t.value);
\r
8487 (function(tinymce) {
\r
8488 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
8490 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
\r
8491 SplitButton : function(id, s) {
\r
8492 this.parent(id, s);
\r
8493 this.classPrefix = 'mceSplitButton';
\r
8496 renderHTML : function() {
\r
8497 var h, t = this, s = t.settings, h1;
\r
8499 h = '<tbody><tr>';
\r
8502 h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
\r
8504 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
\r
8506 h += '<td>' + DOM.createHTML('a', {id : t.id + '_action', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
\r
8508 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
\r
8509 h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
\r
8511 h += '</tr></tbody>';
\r
8513 return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
\r
8516 postRender : function() {
\r
8517 var t = this, s = t.settings;
\r
8520 Event.add(t.id + '_action', 'click', function() {
\r
8521 if (!t.isDisabled())
\r
8522 s.onclick(t.value);
\r
8526 Event.add(t.id + '_open', 'click', t.showMenu, t);
\r
8527 Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
\r
8528 Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
\r
8530 // Old IE doesn't have hover on all elements
\r
8531 if (tinymce.isIE6 || !DOM.boxModel) {
\r
8532 Event.add(t.id, 'mouseover', function() {
\r
8533 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
8534 DOM.addClass(t.id, 'mceSplitButtonHover');
\r
8537 Event.add(t.id, 'mouseout', function() {
\r
8538 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
8539 DOM.removeClass(t.id, 'mceSplitButtonHover');
\r
8544 destroy : function() {
\r
8547 Event.clear(this.id + '_action');
\r
8548 Event.clear(this.id + '_open');
\r
8553 (function(tinymce) {
\r
8554 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
\r
8556 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
\r
8557 ColorSplitButton : function(id, s) {
\r
8562 t.settings = s = tinymce.extend({
\r
8563 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
8565 default_color : '#888888'
\r
8568 t.onShowMenu = new tinymce.util.Dispatcher(t);
\r
8570 t.onHideMenu = new tinymce.util.Dispatcher(t);
\r
8572 t.value = s.default_color;
\r
8575 showMenu : function() {
\r
8576 var t = this, r, p, e, p2;
\r
8578 if (t.isDisabled())
\r
8581 if (!t.isMenuRendered) {
\r
8583 t.isMenuRendered = true;
\r
8586 if (t.isMenuVisible)
\r
8587 return t.hideMenu();
\r
8589 e = DOM.get(t.id);
\r
8590 DOM.show(t.id + '_menu');
\r
8591 DOM.addClass(e, 'mceSplitButtonSelected');
\r
8592 p2 = DOM.getPos(e);
\r
8593 DOM.setStyles(t.id + '_menu', {
\r
8595 top : p2.y + e.clientHeight,
\r
8600 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8601 t.onShowMenu.dispatch(t);
\r
8604 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
\r
8605 if (e.keyCode == 27)
\r
8609 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
\r
8612 t.isMenuVisible = 1;
\r
8615 hideMenu : function(e) {
\r
8618 // Prevent double toogles by canceling the mouse click event to the button
\r
8619 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
\r
8622 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
\r
8623 DOM.removeClass(t.id, 'mceSplitButtonSelected');
\r
8624 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8625 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
\r
8626 DOM.hide(t.id + '_menu');
\r
8629 t.onHideMenu.dispatch(t);
\r
8631 t.isMenuVisible = 0;
\r
8634 renderMenu : function() {
\r
8635 var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
\r
8637 w = DOM.add(s.menu_container, 'div', {id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
\r
8638 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
\r
8639 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
\r
8641 n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
\r
8642 tb = DOM.add(n, 'tbody');
\r
8644 // Generate color grid
\r
8646 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
\r
8647 c = c.replace(/^#/, '');
\r
8650 tr = DOM.add(tb, 'tr');
\r
8651 i = s.grid_width - 1;
\r
8654 n = DOM.add(tr, 'td');
\r
8656 n = DOM.add(n, 'a', {
\r
8657 href : 'javascript:;',
\r
8659 backgroundColor : '#' + c
\r
8661 _mce_color : '#' + c
\r
8665 if (s.more_colors_func) {
\r
8666 n = DOM.add(tb, 'tr');
\r
8667 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
\r
8668 n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
\r
8670 Event.add(n, 'click', function(e) {
\r
8671 s.more_colors_func.call(s.more_colors_scope || this);
\r
8672 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
8676 DOM.addClass(m, 'mceColorSplitMenu');
\r
8678 Event.add(t.id + '_menu', 'click', function(e) {
\r
8683 if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
\r
8686 return Event.cancel(e); // Prevent IE auto save warning
\r
8692 setColor : function(c) {
\r
8695 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
\r
8699 t.settings.onselect(c);
\r
8702 postRender : function() {
\r
8703 var t = this, id = t.id;
\r
8706 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
\r
8707 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
\r
8710 destroy : function() {
\r
8713 Event.clear(this.id + '_menu');
\r
8714 Event.clear(this.id + '_more');
\r
8715 DOM.remove(this.id + '_menu');
\r
8720 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
\r
8721 renderHTML : function() {
\r
8722 var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
\r
8725 for (i=0; i<cl.length; i++) {
\r
8726 // Get current control, prev control, next control and if the control is a list box or not
\r
8731 // Add toolbar start
\r
8733 c = 'mceToolbarStart';
\r
8736 c += ' mceToolbarStartButton';
\r
8737 else if (co.SplitButton)
\r
8738 c += ' mceToolbarStartSplitButton';
\r
8739 else if (co.ListBox)
\r
8740 c += ' mceToolbarStartListBox';
\r
8742 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8745 // Add toolbar end before list box and after the previous button
\r
8746 // This is to fix the o2k7 editor skins
\r
8747 if (pr && co.ListBox) {
\r
8748 if (pr.Button || pr.SplitButton)
\r
8749 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8752 // Render control HTML
\r
8754 // IE 8 quick fix, needed to propertly generate a hit area for anchors
\r
8756 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
\r
8758 h += '<td>' + co.renderHTML() + '</td>';
\r
8760 // Add toolbar start after list box and before the next button
\r
8761 // This is to fix the o2k7 editor skins
\r
8762 if (nx && co.ListBox) {
\r
8763 if (nx.Button || nx.SplitButton)
\r
8764 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8768 c = 'mceToolbarEnd';
\r
8771 c += ' mceToolbarEndButton';
\r
8772 else if (co.SplitButton)
\r
8773 c += ' mceToolbarEndSplitButton';
\r
8774 else if (co.ListBox)
\r
8775 c += ' mceToolbarEndListBox';
\r
8777 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8779 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '<tbody><tr>' + h + '</tr></tbody>');
\r
8783 (function(tinymce) {
\r
8784 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
\r
8786 tinymce.create('tinymce.AddOnManager', {
\r
8787 AddOnManager : function() {
\r
8793 self.onAdd = new Dispatcher(self);
\r
8796 get : function(n) {
\r
8797 return this.lookup[n];
\r
8800 requireLangPack : function(n) {
\r
8801 var s = tinymce.settings;
\r
8803 if (s && s.language)
\r
8804 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
\r
8807 add : function(id, o) {
\r
8808 this.items.push(o);
\r
8809 this.lookup[id] = o;
\r
8810 this.onAdd.dispatch(this, id, o);
\r
8815 load : function(n, u, cb, s) {
\r
8821 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
\r
8822 u = tinymce.baseURL + '/' + u;
\r
8824 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
\r
8827 tinymce.ScriptLoader.add(u, cb, s);
\r
8831 // Create plugin and theme managers
\r
8832 tinymce.PluginManager = new tinymce.AddOnManager();
\r
8833 tinymce.ThemeManager = new tinymce.AddOnManager();
\r
8836 (function(tinymce) {
\r
8838 var each = tinymce.each, extend = tinymce.extend,
\r
8839 DOM = tinymce.DOM, Event = tinymce.dom.Event,
\r
8840 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
8841 explode = tinymce.explode,
\r
8842 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
\r
8844 // Setup some URLs where the editor API is located and where the document is
\r
8845 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
\r
8846 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
\r
8847 tinymce.documentBaseURL += '/';
\r
8849 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
\r
8851 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
\r
8853 // Add before unload listener
\r
8854 // This was required since IE was leaking memory if you added and removed beforeunload listeners
\r
8855 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
\r
8856 tinymce.onBeforeUnload = new Dispatcher(tinymce);
\r
8858 // Must be on window or IE will leak if the editor is placed in frame or iframe
\r
8859 Event.add(window, 'beforeunload', function(e) {
\r
8860 tinymce.onBeforeUnload.dispatch(tinymce, e);
\r
8863 tinymce.onAddEditor = new Dispatcher(tinymce);
\r
8865 tinymce.onRemoveEditor = new Dispatcher(tinymce);
\r
8867 tinymce.EditorManager = extend(tinymce, {
\r
8872 activeEditor : null,
\r
8874 init : function(s) {
\r
8875 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
\r
8877 function execCallback(se, n, s) {
\r
8883 if (tinymce.is(f, 'string')) {
\r
8884 s = f.replace(/\.\w+$/, '');
\r
8885 s = s ? tinymce.resolve(s) : 0;
\r
8886 f = tinymce.resolve(f);
\r
8889 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
\r
8900 Event.add(document, 'init', function() {
\r
8903 execCallback(s, 'onpageload');
\r
8907 l = s.elements || '';
\r
8909 if(l.length > 0) {
\r
8910 each(explode(l), function(v) {
\r
8912 ed = new tinymce.Editor(v, s);
\r
8916 each(document.forms, function(f) {
\r
8917 each(f.elements, function(e) {
\r
8918 if (e.name === v) {
\r
8919 v = 'mce_editor_' + instanceCounter++;
\r
8920 DOM.setAttrib(e, 'id', v);
\r
8922 ed = new tinymce.Editor(v, s);
\r
8934 case "specific_textareas":
\r
8935 function hasClass(n, c) {
\r
8936 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
\r
8939 each(DOM.select('textarea'), function(v) {
\r
8940 if (s.editor_deselector && hasClass(v, s.editor_deselector))
\r
8943 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
\r
8944 // Can we use the name
\r
8945 e = DOM.get(v.name);
\r
8949 // Generate unique name if missing or already exists
\r
8950 if (!v.id || t.get(v.id))
\r
8951 v.id = DOM.uniqueId();
\r
8953 ed = new tinymce.Editor(v.id, s);
\r
8961 // Call onInit when all editors are initialized
\r
8965 each(el, function(ed) {
\r
8968 if (!ed.initialized) {
\r
8970 ed.onInit.add(function() {
\r
8975 execCallback(s, 'oninit');
\r
8982 execCallback(s, 'oninit');
\r
8988 get : function(id) {
\r
8989 if (id === undefined)
\r
8990 return this.editors;
\r
8992 return this.editors[id];
\r
8995 getInstanceById : function(id) {
\r
8996 return this.get(id);
\r
8999 add : function(editor) {
\r
9000 var self = this, editors = self.editors;
\r
9002 // Add named and index editor instance
\r
9003 editors[editor.id] = editor;
\r
9004 editors.push(editor);
\r
9006 self._setActive(editor);
\r
9007 self.onAddEditor.dispatch(self, editor);
\r
9013 remove : function(editor) {
\r
9014 var t = this, i, editors = t.editors;
\r
9016 // Not in the collection
\r
9017 if (!editors[editor.id])
\r
9020 delete editors[editor.id];
\r
9022 for (i = 0; i < editors.length; i++) {
\r
9023 if (editors[i] == editor) {
\r
9024 editors.splice(i, 1);
\r
9029 // Select another editor since the active one was removed
\r
9030 if (t.activeEditor == editor)
\r
9031 t._setActive(editors[0]);
\r
9034 t.onRemoveEditor.dispatch(t, editor);
\r
9039 execCommand : function(c, u, v) {
\r
9040 var t = this, ed = t.get(v), w;
\r
9042 // Manager commands
\r
9048 case "mceAddEditor":
\r
9049 case "mceAddControl":
\r
9051 new tinymce.Editor(v, t.settings).render();
\r
9055 case "mceAddFrameControl":
\r
9058 // Add tinyMCE global instance and tinymce namespace to specified window
\r
9059 w.tinyMCE = tinyMCE;
\r
9060 w.tinymce = tinymce;
\r
9062 tinymce.DOM.doc = w.document;
\r
9063 tinymce.DOM.win = w;
\r
9065 ed = new tinymce.Editor(v.element_id, v);
\r
9068 // Fix IE memory leaks
\r
9069 if (tinymce.isIE) {
\r
9072 w.detachEvent('onunload', clr);
\r
9073 w = w.tinyMCE = w.tinymce = null; // IE leak
\r
9076 w.attachEvent('onunload', clr);
\r
9079 v.page_window = null;
\r
9083 case "mceRemoveEditor":
\r
9084 case "mceRemoveControl":
\r
9090 case 'mceToggleEditor':
\r
9092 t.execCommand('mceAddControl', 0, v);
\r
9096 if (ed.isHidden())
\r
9104 // Run command on active editor
\r
9105 if (t.activeEditor)
\r
9106 return t.activeEditor.execCommand(c, u, v);
\r
9111 execInstanceCommand : function(id, c, u, v) {
\r
9112 var ed = this.get(id);
\r
9115 return ed.execCommand(c, u, v);
\r
9120 triggerSave : function() {
\r
9121 each(this.editors, function(e) {
\r
9126 addI18n : function(p, o) {
\r
9127 var lo, i18n = this.i18n;
\r
9129 if (!tinymce.is(p, 'string')) {
\r
9130 each(p, function(o, lc) {
\r
9131 each(o, function(o, g) {
\r
9132 each(o, function(o, k) {
\r
9133 if (g === 'common')
\r
9134 i18n[lc + '.' + k] = o;
\r
9136 i18n[lc + '.' + g + '.' + k] = o;
\r
9141 each(o, function(o, k) {
\r
9142 i18n[p + '.' + k] = o;
\r
9147 // Private methods
\r
9149 _setActive : function(editor) {
\r
9150 this.selectedInstance = this.activeEditor = editor;
\r
9155 (function(tinymce) {
\r
9156 // Shorten these names
\r
9157 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
\r
9158 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
\r
9159 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
\r
9160 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
9161 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
\r
9163 tinymce.create('tinymce.Editor', {
\r
9164 Editor : function(id, s) {
\r
9167 t.id = t.editorId = id;
\r
9169 t.execCommands = {};
\r
9170 t.queryStateCommands = {};
\r
9171 t.queryValueCommands = {};
\r
9173 t.isNotDirty = false;
\r
9177 // Add events to the editor
\r
9181 'onBeforeRenderUI',
\r
9221 'onBeforeSetContent',
\r
9223 'onBeforeGetContent',
\r
9237 'onBeforeExecCommand',
\r
9247 'onSetProgressState'
\r
9249 t[e] = new Dispatcher(t);
\r
9252 t.settings = s = extend({
\r
9255 docs_language : 'en',
\r
9262 document_base_url : tinymce.documentBaseURL,
\r
9263 add_form_submit_trigger : 1,
\r
9265 add_unload_trigger : 1,
\r
9267 relative_urls : 1,
\r
9268 remove_script_host : 1,
\r
9269 table_inline_editing : 0,
\r
9270 object_resizing : 1,
\r
9272 accessibility_focus : 1,
\r
9273 custom_shortcuts : 1,
\r
9274 custom_undo_redo_keyboard_shortcuts : 1,
\r
9275 custom_undo_redo_restore_selection : 1,
\r
9276 custom_undo_redo : 1,
\r
9277 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
9278 visual_table_class : 'mceItemTable',
\r
9280 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
\r
9281 apply_source_formatting : 1,
\r
9282 directionality : 'ltr',
\r
9283 forced_root_block : 'p',
\r
9284 valid_elements : '@[id|class|style|title|dir<ltr?rtl|lang|xml::lang|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],a[rel|rev|charset|hreflang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur],strong/b,em/i,strike,u,#p,-ol[type|compact],-ul[type|compact],-li,br,img[longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align],-sub,-sup,-blockquote[cite],-table[border|cellspacing|cellpadding|width|frame|rules|height|align|summary|bgcolor|background|bordercolor],-tr[rowspan|width|height|align|valign|bgcolor|background|bordercolor],tbody,thead,tfoot,#td[colspan|rowspan|width|height|align|valign|bgcolor|background|bordercolor|scope],#th[colspan|rowspan|width|height|align|valign|scope],caption,-div,-span,-code,-pre,address,-h1,-h2,-h3,-h4,-h5,-h6,hr[size|noshade],-font[face|size|color],dd,dl,dt,cite,abbr,acronym,del[datetime|cite],ins[datetime|cite],object[classid|width|height|codebase|*],param[name|value],embed[type|width|height|src|*],script[src|type],map[name],area[shape|coords|href|alt|target],bdo,button,col[align|char|charoff|span|valign|width],colgroup[align|char|charoff|span|valign|width],dfn,fieldset,form[action|accept|accept-charset|enctype|method],input[accept|alt|checked|disabled|maxlength|name|readonly|size|src|type|value|tabindex|accesskey],kbd,label[for],legend,noscript,optgroup[label|disabled],option[disabled|label|selected|value],q[cite],samp,select[disabled|multiple|name|size],small,textarea[cols|rows|disabled|name|readonly],tt,var,big',
\r
9286 padd_empty_editor : 1,
\r
9289 force_p_newlines : 1,
\r
9290 indentation : '30px',
\r
9292 fix_table_elements : 1,
\r
9293 inline_styles : 1,
\r
9294 convert_fonts_to_spans : true
\r
9297 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
\r
9298 base_uri : tinyMCE.baseURI
\r
9301 t.baseURI = tinymce.baseURI;
\r
9304 t.execCallback('setup', t);
\r
9307 render : function(nst) {
\r
9308 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
\r
9310 // Page is not loaded yet, wait for it
\r
9311 if (!Event.domLoaded) {
\r
9312 Event.add(document, 'init', function() {
\r
9318 tinyMCE.settings = s;
\r
9320 // Element not found, then skip initialization
\r
9321 if (!t.getElement())
\r
9324 // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
\r
9325 // browser says it has contentEditable support but there is no visible caret
\r
9326 // We will remove this check ones Apple implements full contentEditable support
\r
9327 if (tinymce.isIDevice)
\r
9330 // Add hidden input for non input elements inside form elements
\r
9331 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
\r
9332 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
\r
9334 if (tinymce.WindowManager)
\r
9335 t.windowManager = new tinymce.WindowManager(t);
\r
9337 if (s.encoding == 'xml') {
\r
9338 t.onGetContent.add(function(ed, o) {
\r
9340 o.content = DOM.encode(o.content);
\r
9344 if (s.add_form_submit_trigger) {
\r
9345 t.onSubmit.addToTop(function() {
\r
9346 if (t.initialized) {
\r
9353 if (s.add_unload_trigger) {
\r
9354 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
\r
9355 if (t.initialized && !t.destroyed && !t.isHidden())
\r
9356 t.save({format : 'raw', no_events : true});
\r
9360 tinymce.addUnload(t.destroy, t);
\r
9362 if (s.submit_patch) {
\r
9363 t.onBeforeRenderUI.add(function() {
\r
9364 var n = t.getElement().form;
\r
9369 // Already patched
\r
9370 if (n._mceOldSubmit)
\r
9373 // Check page uses id="submit" or name="submit" for it's submit button
\r
9374 if (!n.submit.nodeType && !n.submit.length) {
\r
9375 t.formElement = n;
\r
9376 n._mceOldSubmit = n.submit;
\r
9377 n.submit = function() {
\r
9378 // Save all instances
\r
9379 tinymce.triggerSave();
\r
9382 return t.formElement._mceOldSubmit(t.formElement);
\r
9391 function loadScripts() {
\r
9393 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
\r
9395 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
\r
9396 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
\r
9398 each(explode(s.plugins), function(p) {
\r
9399 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
\r
9400 // Skip safari plugin, since it is removed as of 3.3b1
\r
9401 if (p == 'safari')
\r
9404 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
\r
9408 // Init when que is loaded
\r
9409 sl.loadQueue(function() {
\r
9418 init : function() {
\r
9419 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
\r
9424 s.theme = s.theme.replace(/-/, '');
\r
9425 o = ThemeManager.get(s.theme);
\r
9426 t.theme = new o();
\r
9428 if (t.theme.init && s.init_theme)
\r
9429 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
\r
9432 // Create all plugins
\r
9433 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
\r
9434 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
\r
9439 t.plugins[p] = po;
\r
9446 // Setup popup CSS path(s)
\r
9447 if (s.popup_css !== false) {
\r
9449 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
\r
9451 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
\r
9454 if (s.popup_css_add)
\r
9455 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
\r
9457 t.controlManager = new tinymce.ControlManager(t);
\r
9459 if (s.custom_undo_redo) {
\r
9460 // Add initial undo level
\r
9461 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
\r
9462 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
\r
9463 if (!t.undoManager.hasUndo())
\r
9464 t.undoManager.add();
\r
9468 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
\r
9469 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
\r
9470 t.undoManager.add();
\r
9474 t.onExecCommand.add(function(ed, c) {
\r
9475 // Don't refresh the select lists until caret move
\r
9476 if (!/^(FontName|FontSize)$/.test(c))
\r
9480 // Remove ghost selections on images and tables in Gecko
\r
9482 function repaint(a, o) {
\r
9483 if (!o || !o.initial)
\r
9484 t.execCommand('mceRepaint');
\r
9487 t.onUndo.add(repaint);
\r
9488 t.onRedo.add(repaint);
\r
9489 t.onSetContent.add(repaint);
\r
9492 // Enables users to override the control factory
\r
9493 t.onBeforeRenderUI.dispatch(t, t.controlManager);
\r
9496 if (s.render_ui) {
\r
9497 w = s.width || e.style.width || e.offsetWidth;
\r
9498 h = s.height || e.style.height || e.offsetHeight;
\r
9499 t.orgDisplay = e.style.display;
\r
9500 re = /^[0-9\.]+(|px)$/i;
\r
9502 if (re.test('' + w))
\r
9503 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
\r
9505 if (re.test('' + h))
\r
9506 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
\r
9509 o = t.theme.renderUI({
\r
9513 deltaWidth : s.delta_width,
\r
9514 deltaHeight : s.delta_height
\r
9517 t.editorContainer = o.editorContainer;
\r
9521 // User specified a document.domain value
\r
9522 if (document.domain && location.hostname != document.domain)
\r
9523 tinymce.relaxedDomain = document.domain;
\r
9526 DOM.setStyles(o.sizeContainer || o.editorContainer, {
\r
9531 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
\r
9535 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
\r
9537 // We only need to override paths if we have to
\r
9538 // IE has a bug where it remove site absolute urls to relative ones if this is specified
\r
9539 if (s.document_base_url != tinymce.documentBaseURL)
\r
9540 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
\r
9542 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
\r
9544 if (tinymce.relaxedDomain)
\r
9545 t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
\r
9547 bi = s.body_id || 'tinymce';
\r
9548 if (bi.indexOf('=') != -1) {
\r
9549 bi = t.getParam('body_id', '', 'hash');
\r
9550 bi = bi[t.id] || bi;
\r
9553 bc = s.body_class || '';
\r
9554 if (bc.indexOf('=') != -1) {
\r
9555 bc = t.getParam('body_class', '', 'hash');
\r
9556 bc = bc[t.id] || '';
\r
9559 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
\r
9561 // Domain relaxing enabled, then set document domain
\r
9562 if (tinymce.relaxedDomain) {
\r
9563 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
\r
9564 if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
\r
9565 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
9566 else if (tinymce.isOpera)
\r
9567 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
\r
9571 n = DOM.add(o.iframeContainer, 'iframe', {
\r
9572 id : t.id + "_ifr",
\r
9573 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
\r
9574 frameBorder : '0',
\r
9581 t.contentAreaContainer = o.iframeContainer;
\r
9582 DOM.get(o.editorContainer).style.display = t.orgDisplay;
\r
9583 DOM.get(t.id).style.display = 'none';
\r
9585 if (!isIE || !tinymce.relaxedDomain)
\r
9588 e = n = o = null; // Cleanup
\r
9591 setupIframe : function() {
\r
9592 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
\r
9594 // Setup iframe body
\r
9595 if (!isIE || !tinymce.relaxedDomain) {
\r
9597 d.write(t.iframeHTML);
\r
9601 // Design mode needs to be added here Ctrl+A will fail otherwise
\r
9605 d.designMode = 'On';
\r
9607 // Will fail on Gecko if the editor is placed in an hidden container element
\r
9608 // The design mode will be set ones the editor is focused
\r
9612 // IE needs to use contentEditable or it will display non secure items for HTTPS
\r
9614 // It will not steal focus if we hide it while setting contentEditable
\r
9619 b.contentEditable = true;
\r
9624 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
\r
9625 keep_values : true,
\r
9626 url_converter : t.convertURL,
\r
9627 url_converter_scope : t,
\r
9628 hex_colors : s.force_hex_style_colors,
\r
9629 class_filter : s.class_filter,
\r
9630 update_styles : 1,
\r
9631 fix_ie_paragraphs : 1,
\r
9632 valid_styles : s.valid_styles
\r
9635 t.schema = new tinymce.dom.Schema();
\r
9637 t.serializer = new tinymce.dom.Serializer(extend(s, {
\r
9638 valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
\r
9643 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
\r
9645 t.formatter = new tinymce.Formatter(this);
\r
9647 // Register default formats
\r
9648 t.formatter.register({
\r
9650 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
\r
9651 {selector : 'img,table', styles : {'float' : 'left'}}
\r
9655 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
\r
9656 {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
\r
9657 {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
\r
9661 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
\r
9662 {selector : 'img,table', styles : {'float' : 'right'}}
\r
9666 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
\r
9670 {inline : 'strong'},
\r
9671 {inline : 'span', styles : {fontWeight : 'bold'}},
\r
9677 {inline : 'span', styles : {fontStyle : 'italic'}},
\r
9682 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
\r
9687 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
\r
9691 forecolor : {inline : 'span', styles : {color : '%value'}},
\r
9692 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
\r
9693 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
\r
9694 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
\r
9695 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
\r
9696 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
\r
9699 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
\r
9700 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
\r
9701 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
\r
9705 // Register default block formats
\r
9706 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
\r
9707 t.formatter.register(name, {block : name, remove : 'all'});
\r
9710 // Register user defined formats
\r
9711 t.formatter.register(t.settings.formats);
\r
9713 t.undoManager = new tinymce.UndoManager(t);
\r
9716 t.undoManager.onAdd.add(function(um, l) {
\r
9718 return t.onChange.dispatch(t, l, um);
\r
9721 t.undoManager.onUndo.add(function(um, l) {
\r
9722 return t.onUndo.dispatch(t, l, um);
\r
9725 t.undoManager.onRedo.add(function(um, l) {
\r
9726 return t.onRedo.dispatch(t, l, um);
\r
9729 t.forceBlocks = new tinymce.ForceBlocks(t, {
\r
9730 forced_root_block : s.forced_root_block
\r
9733 t.editorCommands = new tinymce.EditorCommands(t);
\r
9736 t.serializer.onPreProcess.add(function(se, o) {
\r
9737 return t.onPreProcess.dispatch(t, o, se);
\r
9740 t.serializer.onPostProcess.add(function(se, o) {
\r
9741 return t.onPostProcess.dispatch(t, o, se);
\r
9744 t.onPreInit.dispatch(t);
\r
9746 if (!s.gecko_spellcheck)
\r
9747 t.getBody().spellcheck = 0;
\r
9752 t.controlManager.onPostRender.dispatch(t, t.controlManager);
\r
9753 t.onPostRender.dispatch(t);
\r
9755 if (s.directionality)
\r
9756 t.getBody().dir = s.directionality;
\r
9759 t.getBody().style.whiteSpace = "nowrap";
\r
9761 if (s.custom_elements) {
\r
9762 function handleCustom(ed, o) {
\r
9763 each(explode(s.custom_elements), function(v) {
\r
9766 if (v.indexOf('~') === 0) {
\r
9767 v = v.substring(1);
\r
9772 o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
\r
9773 o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
\r
9777 t.onBeforeSetContent.add(handleCustom);
\r
9778 t.onPostProcess.add(function(ed, o) {
\r
9780 handleCustom(ed, o);
\r
9784 if (s.handle_node_change_callback) {
\r
9785 t.onNodeChange.add(function(ed, cm, n) {
\r
9786 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
\r
9790 if (s.save_callback) {
\r
9791 t.onSaveContent.add(function(ed, o) {
\r
9792 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
9799 if (s.onchange_callback) {
\r
9800 t.onChange.add(function(ed, l) {
\r
9801 t.execCallback('onchange_callback', t, l);
\r
9805 if (s.convert_newlines_to_brs) {
\r
9806 t.onBeforeSetContent.add(function(ed, o) {
\r
9808 o.content = o.content.replace(/\r?\n/g, '<br />');
\r
9812 if (s.fix_nesting && isIE) {
\r
9813 t.onBeforeSetContent.add(function(ed, o) {
\r
9814 o.content = t._fixNesting(o.content);
\r
9818 if (s.preformatted) {
\r
9819 t.onPostProcess.add(function(ed, o) {
\r
9820 o.content = o.content.replace(/^\s*<pre.*?>/, '');
\r
9821 o.content = o.content.replace(/<\/pre>\s*$/, '');
\r
9824 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
\r
9828 if (s.verify_css_classes) {
\r
9829 t.serializer.attribValueFilter = function(n, v) {
\r
9832 if (n == 'class') {
\r
9833 // Build regexp for classes
\r
9834 if (!t.classesRE) {
\r
9835 cl = t.dom.getClasses();
\r
9837 if (cl.length > 0) {
\r
9840 each (cl, function(o) {
\r
9841 s += (s ? '|' : '') + o['class'];
\r
9844 t.classesRE = new RegExp('(' + s + ')', 'gi');
\r
9848 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
\r
9855 if (s.cleanup_callback) {
\r
9856 t.onBeforeSetContent.add(function(ed, o) {
\r
9857 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
9860 t.onPreProcess.add(function(ed, o) {
\r
9862 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
\r
9865 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
\r
9868 t.onPostProcess.add(function(ed, o) {
\r
9870 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
9873 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
\r
9877 if (s.save_callback) {
\r
9878 t.onGetContent.add(function(ed, o) {
\r
9880 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
9884 if (s.handle_event_callback) {
\r
9885 t.onEvent.add(function(ed, e, o) {
\r
9886 if (t.execCallback('handle_event_callback', e, ed, o) === false)
\r
9891 // Add visual aids when new contents is added
\r
9892 t.onSetContent.add(function() {
\r
9893 t.addVisual(t.getBody());
\r
9896 // Remove empty contents
\r
9897 if (s.padd_empty_editor) {
\r
9898 t.onPostProcess.add(function(ed, o) {
\r
9899 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
\r
9904 // Fix gecko link bug, when a link is placed at the end of block elements there is
\r
9905 // no way to move the caret behind the link. This fix adds a bogus br element after the link
\r
9906 function fixLinks(ed, o) {
\r
9907 each(ed.dom.select('a'), function(n) {
\r
9908 var pn = n.parentNode;
\r
9910 if (ed.dom.isBlock(pn) && pn.lastChild === n)
\r
9911 ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
\r
9915 t.onExecCommand.add(function(ed, cmd) {
\r
9916 if (cmd === 'CreateLink')
\r
9920 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
\r
9922 if (!s.readonly) {
\r
9924 // Design mode must be set here once again to fix a bug where
\r
9925 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
\r
9926 d.designMode = 'Off';
\r
9927 d.designMode = 'On';
\r
9929 // Will fail on Gecko if the editor is placed in an hidden container element
\r
9930 // The design mode will be set ones the editor is focused
\r
9935 // A small timeout was needed since firefox will remove. Bug: #1838304
\r
9936 setTimeout(function () {
\r
9940 t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
\r
9941 t.startContent = t.getContent({format : 'raw'});
\r
9942 t.initialized = true;
\r
9944 t.onInit.dispatch(t);
\r
9945 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
\r
9946 t.execCallback('init_instance_callback', t);
\r
9948 t.nodeChanged({initial : 1});
\r
9950 // Load specified content CSS last
\r
9951 if (s.content_css) {
\r
9952 tinymce.each(explode(s.content_css), function(u) {
\r
9953 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
\r
9957 // Handle auto focus
\r
9958 if (s.auto_focus) {
\r
9959 setTimeout(function () {
\r
9960 var ed = tinymce.get(s.auto_focus);
\r
9962 ed.selection.select(ed.getBody(), 1);
\r
9963 ed.selection.collapse(1);
\r
9964 ed.getWin().focus();
\r
9973 focus : function(sf) {
\r
9974 var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
\r
9977 // Get selected control element
\r
9978 ieRng = t.selection.getRng();
\r
9980 controlElm = ieRng.item(0);
\r
9983 // Is not content editable
\r
9985 t.getWin().focus();
\r
9987 // Restore selected control element
\r
9988 // This is needed when for example an image is selected within a
\r
9989 // layer a call to focus will then remove the control selection
\r
9990 if (controlElm && controlElm.ownerDocument == doc) {
\r
9991 ieRng = doc.body.createControlRange();
\r
9992 ieRng.addElement(controlElm);
\r
9998 if (tinymce.activeEditor != t) {
\r
9999 if ((oed = tinymce.activeEditor) != null)
\r
10000 oed.onDeactivate.dispatch(oed, t);
\r
10002 t.onActivate.dispatch(t, oed);
\r
10005 tinymce._setActive(t);
\r
10008 execCallback : function(n) {
\r
10009 var t = this, f = t.settings[n], s;
\r
10014 // Look through lookup
\r
10015 if (t.callbackLookup && (s = t.callbackLookup[n])) {
\r
10020 if (is(f, 'string')) {
\r
10021 s = f.replace(/\.\w+$/, '');
\r
10022 s = s ? tinymce.resolve(s) : 0;
\r
10023 f = tinymce.resolve(f);
\r
10024 t.callbackLookup = t.callbackLookup || {};
\r
10025 t.callbackLookup[n] = {func : f, scope : s};
\r
10028 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
\r
10031 translate : function(s) {
\r
10032 var c = this.settings.language || 'en', i18n = tinymce.i18n;
\r
10037 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
\r
10038 return i18n[c + '.' + b] || '{#' + b + '}';
\r
10042 getLang : function(n, dv) {
\r
10043 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
\r
10046 getParam : function(n, dv, ty) {
\r
10047 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
\r
10049 if (ty === 'hash') {
\r
10052 if (is(v, 'string')) {
\r
10053 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
\r
10054 v = v.split('=');
\r
10056 if (v.length > 1)
\r
10057 o[tr(v[0])] = tr(v[1]);
\r
10059 o[tr(v[0])] = tr(v);
\r
10070 nodeChanged : function(o) {
\r
10071 var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody();
\r
10073 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
\r
10074 if (t.initialized) {
\r
10076 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
\r
10078 // Get parents and add them to object
\r
10080 t.dom.getParent(n, function(node) {
\r
10081 if (node.nodeName == 'BODY')
\r
10084 o.parents.push(node);
\r
10087 t.onNodeChange.dispatch(
\r
10089 o ? o.controlManager || t.controlManager : t.controlManager,
\r
10097 addButton : function(n, s) {
\r
10100 t.buttons = t.buttons || {};
\r
10101 t.buttons[n] = s;
\r
10104 addCommand : function(n, f, s) {
\r
10105 this.execCommands[n] = {func : f, scope : s || this};
\r
10108 addQueryStateHandler : function(n, f, s) {
\r
10109 this.queryStateCommands[n] = {func : f, scope : s || this};
\r
10112 addQueryValueHandler : function(n, f, s) {
\r
10113 this.queryValueCommands[n] = {func : f, scope : s || this};
\r
10116 addShortcut : function(pa, desc, cmd_func, sc) {
\r
10119 if (!t.settings.custom_shortcuts)
\r
10122 t.shortcuts = t.shortcuts || {};
\r
10124 if (is(cmd_func, 'string')) {
\r
10127 cmd_func = function() {
\r
10128 t.execCommand(c, false, null);
\r
10132 if (is(cmd_func, 'object')) {
\r
10135 cmd_func = function() {
\r
10136 t.execCommand(c[0], c[1], c[2]);
\r
10140 each(explode(pa), function(pa) {
\r
10143 scope : sc || this,
\r
10150 each(explode(pa, '+'), function(v) {
\r
10159 o.charCode = v.charCodeAt(0);
\r
10160 o.keyCode = v.toUpperCase().charCodeAt(0);
\r
10164 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
\r
10170 execCommand : function(cmd, ui, val, a) {
\r
10171 var t = this, s = 0, o, st;
\r
10173 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
\r
10177 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
\r
10181 // Command callback
\r
10182 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
\r
10183 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10187 // Registred commands
\r
10188 if (o = t.execCommands[cmd]) {
\r
10189 st = o.func.call(o.scope, ui, val);
\r
10191 // Fall through on true
\r
10192 if (st !== true) {
\r
10193 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10198 // Plugin commands
\r
10199 each(t.plugins, function(p) {
\r
10200 if (p.execCommand && p.execCommand(cmd, ui, val)) {
\r
10201 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10210 // Theme commands
\r
10211 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
\r
10212 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10216 // Execute global commands
\r
10217 if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
\r
10218 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10222 // Editor commands
\r
10223 if (t.editorCommands.execCommand(cmd, ui, val)) {
\r
10224 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10228 // Browser commands
\r
10229 t.getDoc().execCommand(cmd, ui, val);
\r
10230 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10233 queryCommandState : function(cmd) {
\r
10234 var t = this, o, s;
\r
10236 // Is hidden then return undefined
\r
10237 if (t._isHidden())
\r
10240 // Registred commands
\r
10241 if (o = t.queryStateCommands[cmd]) {
\r
10242 s = o.func.call(o.scope);
\r
10244 // Fall though on true
\r
10249 // Registred commands
\r
10250 o = t.editorCommands.queryCommandState(cmd);
\r
10254 // Browser commands
\r
10256 return this.getDoc().queryCommandState(cmd);
\r
10258 // Fails sometimes see bug: 1896577
\r
10262 queryCommandValue : function(c) {
\r
10263 var t = this, o, s;
\r
10265 // Is hidden then return undefined
\r
10266 if (t._isHidden())
\r
10269 // Registred commands
\r
10270 if (o = t.queryValueCommands[c]) {
\r
10271 s = o.func.call(o.scope);
\r
10273 // Fall though on true
\r
10278 // Registred commands
\r
10279 o = t.editorCommands.queryCommandValue(c);
\r
10283 // Browser commands
\r
10285 return this.getDoc().queryCommandValue(c);
\r
10287 // Fails sometimes see bug: 1896577
\r
10291 show : function() {
\r
10294 DOM.show(t.getContainer());
\r
10299 hide : function() {
\r
10300 var t = this, d = t.getDoc();
\r
10302 // Fixed bug where IE has a blinking cursor left from the editor
\r
10304 d.execCommand('SelectAll');
\r
10306 // We must save before we hide so Safari doesn't crash
\r
10308 DOM.hide(t.getContainer());
\r
10309 DOM.setStyle(t.id, 'display', t.orgDisplay);
\r
10312 isHidden : function() {
\r
10313 return !DOM.isHidden(this.id);
\r
10316 setProgressState : function(b, ti, o) {
\r
10317 this.onSetProgressState.dispatch(this, b, ti, o);
\r
10322 load : function(o) {
\r
10323 var t = this, e = t.getElement(), h;
\r
10329 // Double encode existing entities in the value
\r
10330 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
\r
10333 if (!o.no_events)
\r
10334 t.onLoadContent.dispatch(t, o);
\r
10336 o.element = e = null;
\r
10342 save : function(o) {
\r
10343 var t = this, e = t.getElement(), h, f;
\r
10345 if (!e || !t.initialized)
\r
10351 // Add undo level will trigger onchange event
\r
10352 if (!o.no_events) {
\r
10353 t.undoManager.typing = 0;
\r
10354 t.undoManager.add();
\r
10358 h = o.content = t.getContent(o);
\r
10360 if (!o.no_events)
\r
10361 t.onSaveContent.dispatch(t, o);
\r
10365 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
\r
10368 // Update hidden form element
\r
10369 if (f = DOM.getParent(t.id, 'form')) {
\r
10370 each(f.elements, function(e) {
\r
10371 if (e.name == t.id) {
\r
10380 o.element = e = null;
\r
10385 setContent : function(h, o) {
\r
10389 o.format = o.format || 'html';
\r
10393 if (!o.no_events)
\r
10394 t.onBeforeSetContent.dispatch(t, o);
\r
10396 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
\r
10397 // It will also be impossible to place the caret in the editor unless there is a BR element present
\r
10398 if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
\r
10399 o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
\r
10400 o.format = 'raw';
\r
10403 o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
\r
10405 if (o.format != 'raw' && t.settings.cleanup) {
\r
10406 o.getInner = true;
\r
10407 o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
\r
10410 if (!o.no_events)
\r
10411 t.onSetContent.dispatch(t, o);
\r
10413 return o.content;
\r
10416 getContent : function(o) {
\r
10420 o.format = o.format || 'html';
\r
10423 if (!o.no_events)
\r
10424 t.onBeforeGetContent.dispatch(t, o);
\r
10426 if (o.format != 'raw' && t.settings.cleanup) {
\r
10427 o.getInner = true;
\r
10428 h = t.serializer.serialize(t.getBody(), o);
\r
10430 h = t.getBody().innerHTML;
\r
10432 h = h.replace(/^\s*|\s*$/g, '');
\r
10435 if (!o.no_events)
\r
10436 t.onGetContent.dispatch(t, o);
\r
10438 return o.content;
\r
10441 isDirty : function() {
\r
10444 return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
\r
10447 getContainer : function() {
\r
10450 if (!t.container)
\r
10451 t.container = DOM.get(t.editorContainer || t.id + '_parent');
\r
10453 return t.container;
\r
10456 getContentAreaContainer : function() {
\r
10457 return this.contentAreaContainer;
\r
10460 getElement : function() {
\r
10461 return DOM.get(this.settings.content_element || this.id);
\r
10464 getWin : function() {
\r
10467 if (!t.contentWindow) {
\r
10468 e = DOM.get(t.id + "_ifr");
\r
10471 t.contentWindow = e.contentWindow;
\r
10474 return t.contentWindow;
\r
10477 getDoc : function() {
\r
10480 if (!t.contentDocument) {
\r
10484 t.contentDocument = w.document;
\r
10487 return t.contentDocument;
\r
10490 getBody : function() {
\r
10491 return this.bodyElement || this.getDoc().body;
\r
10494 convertURL : function(u, n, e) {
\r
10495 var t = this, s = t.settings;
\r
10497 // Use callback instead
\r
10498 if (s.urlconverter_callback)
\r
10499 return t.execCallback('urlconverter_callback', u, e, true, n);
\r
10501 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
\r
10502 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
\r
10505 // Convert to relative
\r
10506 if (s.relative_urls)
\r
10507 return t.documentBaseURI.toRelative(u);
\r
10509 // Convert to absolute
\r
10510 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
\r
10515 addVisual : function(e) {
\r
10516 var t = this, s = t.settings;
\r
10518 e = e || t.getBody();
\r
10520 if (!is(t.hasVisual))
\r
10521 t.hasVisual = s.visual;
\r
10523 each(t.dom.select('table,a', e), function(e) {
\r
10526 switch (e.nodeName) {
\r
10528 v = t.dom.getAttrib(e, 'border');
\r
10530 if (!v || v == '0') {
\r
10532 t.dom.addClass(e, s.visual_table_class);
\r
10534 t.dom.removeClass(e, s.visual_table_class);
\r
10540 v = t.dom.getAttrib(e, 'name');
\r
10544 t.dom.addClass(e, 'mceItemAnchor');
\r
10546 t.dom.removeClass(e, 'mceItemAnchor');
\r
10553 t.onVisualAid.dispatch(t, e, t.hasVisual);
\r
10556 remove : function() {
\r
10557 var t = this, e = t.getContainer();
\r
10559 t.removed = 1; // Cancels post remove event execution
\r
10562 t.execCallback('remove_instance_callback', t);
\r
10563 t.onRemove.dispatch(t);
\r
10565 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
\r
10566 t.onExecCommand.listeners = [];
\r
10568 tinymce.remove(t);
\r
10572 destroy : function(s) {
\r
10575 // One time is enough
\r
10580 tinymce.removeUnload(t.destroy);
\r
10581 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
\r
10583 // Manual destroy
\r
10584 if (t.theme && t.theme.destroy)
\r
10585 t.theme.destroy();
\r
10587 // Destroy controls, selection and dom
\r
10588 t.controlManager.destroy();
\r
10589 t.selection.destroy();
\r
10592 // Remove all events
\r
10594 // Don't clear the window or document if content editable
\r
10595 // is enabled since other instances might still be present
\r
10596 if (!t.settings.content_editable) {
\r
10597 Event.clear(t.getWin());
\r
10598 Event.clear(t.getDoc());
\r
10601 Event.clear(t.getBody());
\r
10602 Event.clear(t.formElement);
\r
10605 if (t.formElement) {
\r
10606 t.formElement.submit = t.formElement._mceOldSubmit;
\r
10607 t.formElement._mceOldSubmit = null;
\r
10610 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
\r
10613 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
\r
10618 // Internal functions
\r
10620 _addEvents : function() {
\r
10621 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
\r
10622 var t = this, i, s = t.settings, dom = t.dom, lo = {
\r
10623 mouseup : 'onMouseUp',
\r
10624 mousedown : 'onMouseDown',
\r
10625 click : 'onClick',
\r
10626 keyup : 'onKeyUp',
\r
10627 keydown : 'onKeyDown',
\r
10628 keypress : 'onKeyPress',
\r
10629 submit : 'onSubmit',
\r
10630 reset : 'onReset',
\r
10631 contextmenu : 'onContextMenu',
\r
10632 dblclick : 'onDblClick',
\r
10633 paste : 'onPaste' // Doesn't work in all browsers yet
\r
10636 function eventHandler(e, o) {
\r
10639 // Don't fire events when it's removed
\r
10643 // Generic event handler
\r
10644 if (t.onEvent.dispatch(t, e, o) !== false) {
\r
10645 // Specific event handler
\r
10646 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
\r
10650 // Add DOM events
\r
10651 each(lo, function(v, k) {
\r
10653 case 'contextmenu':
\r
10654 if (tinymce.isOpera) {
\r
10655 // Fake contextmenu on Opera
\r
10656 dom.bind(t.getBody(), 'mousedown', function(e) {
\r
10658 e.fakeType = 'contextmenu';
\r
10663 dom.bind(t.getBody(), k, eventHandler);
\r
10667 dom.bind(t.getBody(), k, function(e) {
\r
10674 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
\r
10678 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
\r
10682 dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
\r
10687 // Fixes bug where a specified document_base_uri could result in broken images
\r
10688 // This will also fix drag drop of images in Gecko
\r
10689 if (tinymce.isGecko) {
\r
10690 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
\r
10695 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
\r
10696 e.src = t.documentBaseURI.toAbsolute(v);
\r
10700 // Set various midas options in Gecko
\r
10702 function setOpts() {
\r
10703 var t = this, d = t.getDoc(), s = t.settings;
\r
10705 if (isGecko && !s.readonly) {
\r
10706 if (t._isHidden()) {
\r
10708 if (!s.content_editable)
\r
10709 d.designMode = 'On';
\r
10711 // Fails if it's hidden
\r
10716 // Try new Gecko method
\r
10717 d.execCommand("styleWithCSS", 0, false);
\r
10719 // Use old method
\r
10720 if (!t._isHidden())
\r
10721 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
\r
10724 if (!s.table_inline_editing)
\r
10725 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
\r
10727 if (!s.object_resizing)
\r
10728 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
\r
10732 t.onBeforeExecCommand.add(setOpts);
\r
10733 t.onMouseDown.add(setOpts);
\r
10736 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
\r
10737 // WebKit can't even do simple things like selecting an image
\r
10738 // This also fixes so it's possible to select mceItemAnchors
\r
10739 if (tinymce.isWebKit) {
\r
10740 t.onClick.add(function(ed, e) {
\r
10743 // Needs tobe the setBaseAndExtend or it will fail to select floated images
\r
10744 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))) {
\r
10745 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
\r
10751 // Add node change handlers
\r
10752 t.onMouseUp.add(t.nodeChanged);
\r
10753 //t.onClick.add(t.nodeChanged);
\r
10754 t.onKeyUp.add(function(ed, e) {
\r
10755 var c = e.keyCode;
\r
10757 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
10761 // Add reset handler
\r
10762 t.onReset.add(function() {
\r
10763 t.setContent(t.startContent, {format : 'raw'});
\r
10767 if (s.custom_shortcuts) {
\r
10768 if (s.custom_undo_redo_keyboard_shortcuts) {
\r
10769 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
\r
10770 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
\r
10773 // Add default shortcuts for gecko
\r
10774 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
\r
10775 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
\r
10776 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
\r
10778 // BlockFormat shortcuts keys
\r
10779 for (i=1; i<=6; i++)
\r
10780 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
\r
10782 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
\r
10783 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
\r
10784 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
\r
10786 function find(e) {
\r
10789 if (!e.altKey && !e.ctrlKey && !e.metaKey)
\r
10792 each(t.shortcuts, function(o) {
\r
10793 if (tinymce.isMac && o.ctrl != e.metaKey)
\r
10795 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
\r
10798 if (o.alt != e.altKey)
\r
10801 if (o.shift != e.shiftKey)
\r
10804 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
\r
10813 t.onKeyUp.add(function(ed, e) {
\r
10817 return Event.cancel(e);
\r
10820 t.onKeyPress.add(function(ed, e) {
\r
10824 return Event.cancel(e);
\r
10827 t.onKeyDown.add(function(ed, e) {
\r
10831 o.func.call(o.scope);
\r
10832 return Event.cancel(e);
\r
10837 if (tinymce.isIE) {
\r
10838 // Fix so resize will only update the width and height attributes not the styles of an image
\r
10839 // It will also block mceItemNoResize items
\r
10840 dom.bind(t.getDoc(), 'controlselect', function(e) {
\r
10841 var re = t.resizeInfo, cb;
\r
10845 // Don't do this action for non image elements
\r
10846 if (e.nodeName !== 'IMG')
\r
10850 dom.unbind(re.node, re.ev, re.cb);
\r
10852 if (!dom.hasClass(e, 'mceItemNoResize')) {
\r
10853 ev = 'resizeend';
\r
10854 cb = dom.bind(e, ev, function(e) {
\r
10859 if (v = dom.getStyle(e, 'width')) {
\r
10860 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
\r
10861 dom.setStyle(e, 'width', '');
\r
10864 if (v = dom.getStyle(e, 'height')) {
\r
10865 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
\r
10866 dom.setStyle(e, 'height', '');
\r
10870 ev = 'resizestart';
\r
10871 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
\r
10874 re = t.resizeInfo = {
\r
10881 t.onKeyDown.add(function(ed, e) {
\r
10882 switch (e.keyCode) {
\r
10884 // Fix IE control + backspace browser bug
\r
10885 if (t.selection.getRng().item) {
\r
10886 ed.dom.remove(t.selection.getRng().item(0));
\r
10887 return Event.cancel(e);
\r
10892 /*if (t.dom.boxModel) {
\r
10893 t.getBody().style.height = '100%';
\r
10895 Event.add(t.getWin(), 'resize', function(e) {
\r
10896 var docElm = t.getDoc().documentElement;
\r
10898 docElm.style.height = (docElm.offsetHeight - 10) + 'px';
\r
10903 if (tinymce.isOpera) {
\r
10904 t.onClick.add(function(ed, e) {
\r
10905 Event.prevent(e);
\r
10909 // Add custom undo/redo handlers
\r
10910 if (s.custom_undo_redo) {
\r
10911 function addUndo() {
\r
10912 t.undoManager.typing = 0;
\r
10913 t.undoManager.add();
\r
10916 dom.bind(t.getDoc(), 'focusout', function(e) {
\r
10917 if (!t.removed && t.undoManager.typing)
\r
10921 t.onKeyUp.add(function(ed, e) {
\r
10922 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
\r
10926 t.onKeyDown.add(function(ed, e) {
\r
10927 var rng, parent, bookmark;
\r
10929 // IE has a really odd bug where the DOM might include an node that doesn't have
\r
10930 // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
\r
10931 // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
\r
10932 // after you delete contents from it. See: #3008923
\r
10933 if (isIE && e.keyCode == 46) {
\r
10934 rng = t.selection.getRng();
\r
10936 if (rng.parentElement) {
\r
10937 parent = rng.parentElement();
\r
10939 // Select next word when ctrl key is used in combo with delete
\r
10941 rng.moveEnd('word', 1);
\r
10945 // Delete contents
\r
10946 t.selection.getSel().clear();
\r
10948 // Check if we are within the same parent
\r
10949 if (rng.parentElement() == parent) {
\r
10950 bookmark = t.selection.getBookmark();
\r
10953 // Update the HTML and hopefully it will remove the artifacts
\r
10954 parent.innerHTML = parent.innerHTML;
\r
10956 // And since it's IE it can sometimes produce an unknown runtime error
\r
10959 // Restore the caret position
\r
10960 t.selection.moveToBookmark(bookmark);
\r
10963 // Block the default delete behavior since it might be broken
\r
10964 e.preventDefault();
\r
10969 // Is caracter positon keys
\r
10970 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
\r
10971 if (t.undoManager.typing)
\r
10977 if (!t.undoManager.typing) {
\r
10978 t.undoManager.add();
\r
10979 t.undoManager.typing = 1;
\r
10983 t.onMouseDown.add(function() {
\r
10984 if (t.undoManager.typing)
\r
10990 _isHidden : function() {
\r
10996 // Weird, wheres that cursor selection?
\r
10997 s = this.selection.getSel();
\r
10998 return (!s || !s.rangeCount || s.rangeCount == 0);
\r
11001 // Fix for bug #1867292
\r
11002 _fixNesting : function(s) {
\r
11005 s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
\r
11008 // Handle end element
\r
11013 if (c !== d[d.length - 1].tag) {
\r
11014 for (i=d.length - 1; i>=0; i--) {
\r
11015 if (d[i].tag === c) {
\r
11025 if (d.length && d[d.length - 1].close) {
\r
11026 a = a + '</' + d[d.length - 1].tag + '>';
\r
11032 if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
\r
11035 // Ignore closed ones
\r
11036 if (/\/>$/.test(a))
\r
11039 d.push({tag : c}); // Push start element
\r
11045 // End all open tags
\r
11046 for (i=d.length - 1; i>=0; i--)
\r
11047 s += '</' + d[i].tag + '>';
\r
11054 (function(tinymce) {
\r
11055 // Added for compression purposes
\r
11056 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
\r
11058 tinymce.EditorCommands = function(editor) {
\r
11059 var dom = editor.dom,
\r
11060 selection = editor.selection,
\r
11061 commands = {state: {}, exec : {}, value : {}},
\r
11062 settings = editor.settings,
\r
11065 function execCommand(command, ui, value) {
\r
11068 command = command.toLowerCase();
\r
11069 if (func = commands.exec[command]) {
\r
11070 func(command, ui, value);
\r
11077 function queryCommandState(command) {
\r
11080 command = command.toLowerCase();
\r
11081 if (func = commands.state[command])
\r
11082 return func(command);
\r
11087 function queryCommandValue(command) {
\r
11090 command = command.toLowerCase();
\r
11091 if (func = commands.value[command])
\r
11092 return func(command);
\r
11097 function addCommands(command_list, type) {
\r
11098 type = type || 'exec';
\r
11100 each(command_list, function(callback, command) {
\r
11101 each(command.toLowerCase().split(','), function(command) {
\r
11102 commands[type][command] = callback;
\r
11107 // Expose public methods
\r
11108 tinymce.extend(this, {
\r
11109 execCommand : execCommand,
\r
11110 queryCommandState : queryCommandState,
\r
11111 queryCommandValue : queryCommandValue,
\r
11112 addCommands : addCommands
\r
11115 // Private methods
\r
11117 function execNativeCommand(command, ui, value) {
\r
11118 if (ui === undefined)
\r
11121 if (value === undefined)
\r
11124 return editor.getDoc().execCommand(command, ui, value);
\r
11127 function isFormatMatch(name) {
\r
11128 return editor.formatter.match(name);
\r
11131 function toggleFormat(name, value) {
\r
11132 editor.formatter.toggle(name, value ? {value : value} : undefined);
\r
11135 function storeSelection(type) {
\r
11136 bookmark = selection.getBookmark(type);
\r
11139 function restoreSelection() {
\r
11140 selection.moveToBookmark(bookmark);
\r
11143 // Add execCommand overrides
\r
11145 // Ignore these, added for compatibility
\r
11146 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
\r
11148 // Add undo manager logic
\r
11149 'mceEndUndoLevel,mceAddUndoLevel' : function() {
\r
11150 editor.undoManager.add();
\r
11153 'Cut,Copy,Paste' : function(command) {
\r
11154 var doc = editor.getDoc(), failed;
\r
11156 // Try executing the native command
\r
11158 execNativeCommand(command);
\r
11160 // Command failed
\r
11164 // Present alert message about clipboard access not being available
\r
11165 if (failed || !doc.queryCommandSupported(command)) {
\r
11166 if (tinymce.isGecko) {
\r
11167 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
\r
11169 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
\r
11172 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
\r
11176 // Override unlink command
\r
11177 unlink : function(command) {
\r
11178 if (selection.isCollapsed())
\r
11179 selection.select(selection.getNode());
\r
11181 execNativeCommand(command);
\r
11182 selection.collapse(FALSE);
\r
11185 // Override justify commands to use the text formatter engine
\r
11186 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
11187 var align = command.substring(7);
\r
11189 // Remove all other alignments first
\r
11190 each('left,center,right,full'.split(','), function(name) {
\r
11191 if (align != name)
\r
11192 editor.formatter.remove('align' + name);
\r
11195 toggleFormat('align' + align);
\r
11198 // Override list commands to fix WebKit bug
\r
11199 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
11200 var listElm, listParent;
\r
11202 execNativeCommand(command);
\r
11204 // WebKit produces lists within block elements so we need to split them
\r
11205 // we will replace the native list creation logic to custom logic later on
\r
11206 // TODO: Remove this when the list creation logic is removed
\r
11207 listElm = dom.getParent(selection.getNode(), 'ol,ul');
\r
11209 listParent = listElm.parentNode;
\r
11211 // If list is within a text block then split that block
\r
11212 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
\r
11213 storeSelection();
\r
11214 dom.split(listParent, listElm);
\r
11215 restoreSelection();
\r
11220 // Override commands to use the text formatter engine
\r
11221 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
11222 toggleFormat(command);
\r
11225 // Override commands to use the text formatter engine
\r
11226 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
\r
11227 toggleFormat(command, value);
\r
11230 FontSize : function(command, ui, value) {
\r
11231 var fontClasses, fontSizes;
\r
11233 // Convert font size 1-7 to styles
\r
11234 if (value >= 1 && value <= 7) {
\r
11235 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
11236 fontClasses = tinymce.explode(settings.font_size_classes);
\r
11239 value = fontClasses[value - 1] || value;
\r
11241 value = fontSizes[value - 1] || value;
\r
11244 toggleFormat(command, value);
\r
11247 RemoveFormat : function(command) {
\r
11248 editor.formatter.remove(command);
\r
11251 mceBlockQuote : function(command) {
\r
11252 toggleFormat('blockquote');
\r
11255 FormatBlock : function(command, ui, value) {
\r
11256 return toggleFormat(value || 'p');
\r
11259 mceCleanup : function() {
\r
11260 var bookmark = selection.getBookmark();
\r
11262 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
\r
11264 selection.moveToBookmark(bookmark);
\r
11267 mceRemoveNode : function(command, ui, value) {
\r
11268 var node = value || selection.getNode();
\r
11270 // Make sure that the body node isn't removed
\r
11271 if (node != editor.getBody()) {
\r
11272 storeSelection();
\r
11273 editor.dom.remove(node, TRUE);
\r
11274 restoreSelection();
\r
11278 mceSelectNodeDepth : function(command, ui, value) {
\r
11281 dom.getParent(selection.getNode(), function(node) {
\r
11282 if (node.nodeType == 1 && counter++ == value) {
\r
11283 selection.select(node);
\r
11286 }, editor.getBody());
\r
11289 mceSelectNode : function(command, ui, value) {
\r
11290 selection.select(value);
\r
11293 mceInsertContent : function(command, ui, value) {
\r
11294 selection.setContent(value);
\r
11297 mceInsertRawHTML : function(command, ui, value) {
\r
11298 selection.setContent('tiny_mce_marker');
\r
11299 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
\r
11302 mceSetContent : function(command, ui, value) {
\r
11303 editor.setContent(value);
\r
11306 'Indent,Outdent' : function(command) {
\r
11307 var intentValue, indentUnit, value;
\r
11309 // Setup indent level
\r
11310 intentValue = settings.indentation;
\r
11311 indentUnit = /[a-z%]+$/i.exec(intentValue);
\r
11312 intentValue = parseInt(intentValue);
\r
11314 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
\r
11315 each(selection.getSelectedBlocks(), function(element) {
\r
11316 if (command == 'outdent') {
\r
11317 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
\r
11318 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
\r
11320 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
\r
11323 execNativeCommand(command);
\r
11326 mceRepaint : function() {
\r
11329 if (tinymce.isGecko) {
\r
11331 storeSelection(TRUE);
\r
11333 if (selection.getSel())
\r
11334 selection.getSel().selectAllChildren(editor.getBody());
\r
11336 selection.collapse(TRUE);
\r
11337 restoreSelection();
\r
11344 mceToggleFormat : function(command, ui, value) {
\r
11345 editor.formatter.toggle(value);
\r
11348 InsertHorizontalRule : function() {
\r
11349 selection.setContent('<hr />');
\r
11352 mceToggleVisualAid : function() {
\r
11353 editor.hasVisual = !editor.hasVisual;
\r
11354 editor.addVisual();
\r
11357 mceReplaceContent : function(command, ui, value) {
\r
11358 selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
\r
11361 mceInsertLink : function(command, ui, value) {
\r
11362 var link = dom.getParent(selection.getNode(), 'a');
\r
11364 if (tinymce.is(value, 'string'))
\r
11365 value = {href : value};
\r
11368 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
\r
11369 each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
\r
11370 dom.setAttribs(link, value);
\r
11374 dom.setAttribs(link, value);
\r
11376 editor.dom.remove(link, TRUE);
\r
11380 selectAll : function() {
\r
11381 var root = dom.getRoot(), rng = dom.createRng();
\r
11383 rng.setStart(root, 0);
\r
11384 rng.setEnd(root, root.childNodes.length);
\r
11386 editor.selection.setRng(rng);
\r
11390 // Add queryCommandState overrides
\r
11392 // Override justify commands
\r
11393 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
11394 return isFormatMatch('align' + command.substring(7));
\r
11397 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
11398 return isFormatMatch(command);
\r
11401 mceBlockQuote : function() {
\r
11402 return isFormatMatch('blockquote');
\r
11405 Outdent : function() {
\r
11408 if (settings.inline_styles) {
\r
11409 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
11412 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
11416 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
\r
11419 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
11420 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
\r
11424 // Add queryCommandValue overrides
\r
11426 'FontSize,FontName' : function(command) {
\r
11427 var value = 0, parent;
\r
11429 if (parent = dom.getParent(selection.getNode(), 'span')) {
\r
11430 if (command == 'fontsize')
\r
11431 value = parent.style.fontSize;
\r
11433 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
\r
11440 // Add undo manager logic
\r
11441 if (settings.custom_undo_redo) {
\r
11443 Undo : function() {
\r
11444 editor.undoManager.undo();
\r
11447 Redo : function() {
\r
11448 editor.undoManager.redo();
\r
11454 (function(tinymce) {
\r
11455 var Dispatcher = tinymce.util.Dispatcher;
\r
11457 tinymce.UndoManager = function(editor) {
\r
11458 var self, index = 0, data = [];
\r
11460 function getContent() {
\r
11461 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
\r
11467 onAdd : new Dispatcher(self),
\r
11468 onUndo : new Dispatcher(self),
\r
11469 onRedo : new Dispatcher(self),
\r
11471 add : function(level) {
\r
11472 var i, settings = editor.settings, lastLevel;
\r
11474 level = level || {};
\r
11475 level.content = getContent();
\r
11477 // Add undo level if needed
\r
11478 lastLevel = data[index];
\r
11479 if (lastLevel && lastLevel.content == level.content) {
\r
11480 if (index > 0 || data.length == 1)
\r
11484 // Time to compress
\r
11485 if (settings.custom_undo_redo_levels) {
\r
11486 if (data.length > settings.custom_undo_redo_levels) {
\r
11487 for (i = 0; i < data.length - 1; i++)
\r
11488 data[i] = data[i + 1];
\r
11491 index = data.length;
\r
11495 // Get a non intrusive normalized bookmark
\r
11496 level.bookmark = editor.selection.getBookmark(2, true);
\r
11498 // Crop array if needed
\r
11499 if (index < data.length - 1) {
\r
11500 // Treat first level as initial
\r
11504 data.length = index + 1;
\r
11507 data.push(level);
\r
11508 index = data.length - 1;
\r
11510 self.onAdd.dispatch(self, level);
\r
11511 editor.isNotDirty = 0;
\r
11516 undo : function() {
\r
11519 if (self.typing) {
\r
11525 level = data[--index];
\r
11527 editor.setContent(level.content, {format : 'raw'});
\r
11528 editor.selection.moveToBookmark(level.bookmark);
\r
11530 self.onUndo.dispatch(self, level);
\r
11536 redo : function() {
\r
11539 if (index < data.length - 1) {
\r
11540 level = data[++index];
\r
11542 editor.setContent(level.content, {format : 'raw'});
\r
11543 editor.selection.moveToBookmark(level.bookmark);
\r
11545 self.onRedo.dispatch(self, level);
\r
11551 clear : function() {
\r
11553 index = self.typing = 0;
\r
11556 hasUndo : function() {
\r
11557 return index > 0 || self.typing;
\r
11560 hasRedo : function() {
\r
11561 return index < data.length - 1;
\r
11567 (function(tinymce) {
\r
11569 var Event = tinymce.dom.Event,
\r
11570 isIE = tinymce.isIE,
\r
11571 isGecko = tinymce.isGecko,
\r
11572 isOpera = tinymce.isOpera,
\r
11573 each = tinymce.each,
\r
11574 extend = tinymce.extend,
\r
11578 function cloneFormats(node) {
\r
11579 var clone, temp, inner;
\r
11582 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
\r
11584 temp = node.cloneNode(false);
\r
11585 temp.appendChild(clone);
\r
11588 clone = inner = node.cloneNode(false);
\r
11591 clone.removeAttribute('id');
\r
11593 } while (node = node.parentNode);
\r
11596 return {wrapper : clone, inner : inner};
\r
11599 // Checks if the selection/caret is at the end of the specified block element
\r
11600 function isAtEnd(rng, par) {
\r
11601 var rng2 = par.ownerDocument.createRange();
\r
11603 rng2.setStart(rng.endContainer, rng.endOffset);
\r
11604 rng2.setEndAfter(par);
\r
11606 // 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
11607 return rng2.cloneContents().textContent.length == 0;
\r
11610 function isEmpty(n) {
\r
11613 n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
\r
11614 n = n.replace(/<[^>]+>/g, ''); // Remove all tags
\r
11616 return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
\r
11619 function splitList(selection, dom, li) {
\r
11620 var listBlock, block;
\r
11622 if (isEmpty(li)) {
\r
11623 listBlock = dom.getParent(li, 'ul,ol');
\r
11625 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
\r
11626 dom.split(listBlock, li);
\r
11627 block = dom.create('p', 0, '<br _mce_bogus="1" />');
\r
11628 dom.replace(block, li);
\r
11629 selection.select(block, 1);
\r
11638 tinymce.create('tinymce.ForceBlocks', {
\r
11639 ForceBlocks : function(ed) {
\r
11640 var t = this, s = ed.settings, elm;
\r
11644 elm = (s.forced_root_block || 'p').toLowerCase();
\r
11645 s.element = elm.toUpperCase();
\r
11647 ed.onPreInit.add(t.setup, t);
\r
11649 t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi');
\r
11650 t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11651 t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11652 t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi');
\r
11653 t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11655 function padd(ed, o) {
\r
11657 o.content = o.content.replace(t.reOpera, '</' + elm + '>');
\r
11659 o.content = tinymce._replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>', o.content);
\r
11661 if (!isIE && !isOpera && o.set) {
\r
11662 // Use instead of BR in padded paragraphs
\r
11663 o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
\r
11664 o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
\r
11666 o.content = tinymce._replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>', o.content);
\r
11669 ed.onBeforeSetContent.add(padd);
\r
11670 ed.onPostProcess.add(padd);
\r
11672 if (s.forced_root_block) {
\r
11673 ed.onInit.add(t.forceRoots, t);
\r
11674 ed.onSetContent.add(t.forceRoots, t);
\r
11675 ed.onBeforeGetContent.add(t.forceRoots, t);
\r
11679 setup : function() {
\r
11680 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
\r
11682 // Force root blocks when typing and when getting output
\r
11683 if (s.forced_root_block) {
\r
11684 ed.onBeforeExecCommand.add(t.forceRoots, t);
\r
11685 ed.onKeyUp.add(t.forceRoots, t);
\r
11686 ed.onPreProcess.add(t.forceRoots, t);
\r
11689 if (s.force_br_newlines) {
\r
11690 // Force IE to produce BRs on enter
\r
11692 ed.onKeyPress.add(function(ed, e) {
\r
11695 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
\r
11696 selection.setContent('<br id="__" /> ', {format : 'raw'});
\r
11697 n = dom.get('__');
\r
11698 n.removeAttribute('id');
\r
11699 selection.select(n);
\r
11700 selection.collapse();
\r
11701 return Event.cancel(e);
\r
11707 if (s.force_p_newlines) {
\r
11709 ed.onKeyPress.add(function(ed, e) {
\r
11710 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
\r
11714 // Ungly hack to for IE to preserve the formatting when you press
\r
11715 // enter at the end of a block element with formatted contents
\r
11716 // This logic overrides the browsers default logic with
\r
11717 // custom logic that enables us to control the output
\r
11718 tinymce.addUnload(function() {
\r
11719 t._previousFormats = 0; // Fix IE leak
\r
11722 ed.onKeyPress.add(function(ed, e) {
\r
11723 t._previousFormats = 0;
\r
11725 // Clone the current formats, this will later be applied to the new block contents
\r
11726 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
\r
11727 t._previousFormats = cloneFormats(ed.selection.getStart());
\r
11730 ed.onKeyUp.add(function(ed, e) {
\r
11731 // Let IE break the element and the wrap the new caret location in the previous formats
\r
11732 if (e.keyCode == 13 && !e.shiftKey) {
\r
11733 var parent = ed.selection.getStart(), fmt = t._previousFormats;
\r
11735 // Parent is an empty block
\r
11736 if (!parent.hasChildNodes() && fmt) {
\r
11737 parent = dom.getParent(parent, dom.isBlock);
\r
11739 if (parent && parent.nodeName != 'LI') {
\r
11740 parent.innerHTML = '';
\r
11742 if (t._previousFormats) {
\r
11743 parent.appendChild(fmt.wrapper);
\r
11744 fmt.inner.innerHTML = '\uFEFF';
\r
11746 parent.innerHTML = '\uFEFF';
\r
11748 selection.select(parent, 1);
\r
11749 ed.getDoc().execCommand('Delete', false, null);
\r
11750 t._previousFormats = 0;
\r
11758 ed.onKeyDown.add(function(ed, e) {
\r
11759 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
\r
11760 t.backspaceDelete(e, e.keyCode == 8);
\r
11765 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
\r
11766 if (tinymce.isWebKit) {
\r
11767 function insertBr(ed) {
\r
11768 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
\r
11770 // Insert BR element
\r
11771 rng.insertNode(br = dom.create('br'));
\r
11773 // Place caret after BR
\r
11774 rng.setStartAfter(br);
\r
11775 rng.setEndAfter(br);
\r
11776 selection.setRng(rng);
\r
11778 // Could not place caret after BR then insert an nbsp entity and move the caret
\r
11779 if (selection.getSel().focusNode == br.previousSibling) {
\r
11780 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
\r
11781 selection.collapse(TRUE);
\r
11784 // Create a temporary DIV after the BR and get the position as it
\r
11785 // seems like getPos() returns 0 for text nodes and BR elements.
\r
11786 dom.insertAfter(div, br);
\r
11787 divYPos = dom.getPos(div).y;
\r
11790 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
\r
11791 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
\r
11792 ed.getWin().scrollTo(0, divYPos);
\r
11795 ed.onKeyPress.add(function(ed, e) {
\r
11796 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
\r
11803 // Padd empty inline elements within block elements
\r
11804 // For example: <p><strong><em></em></strong></p> becomes <p><strong><em> </em></strong></p>
\r
11805 ed.onPreProcess.add(function(ed, o) {
\r
11806 each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
\r
11807 if (isEmpty(p)) {
\r
11808 each(dom.select('span,em,strong,b,i', o.node), function(n) {
\r
11809 if (!n.hasChildNodes()) {
\r
11810 n.appendChild(ed.getDoc().createTextNode('\u00a0'));
\r
11811 return FALSE; // Break the loop one padding is enough
\r
11818 // IE specific fixes
\r
11820 // Replaces IE:s auto generated paragraphs with the specified element name
\r
11821 if (s.element != 'P') {
\r
11822 ed.onKeyPress.add(function(ed, e) {
\r
11823 t.lastElm = selection.getNode().nodeName;
\r
11826 ed.onKeyUp.add(function(ed, e) {
\r
11827 var bl, n = selection.getNode(), b = ed.getBody();
\r
11829 if (b.childNodes.length === 1 && n.nodeName == 'P') {
\r
11830 n = dom.rename(n, s.element);
\r
11831 selection.select(n);
\r
11832 selection.collapse();
\r
11833 ed.nodeChanged();
\r
11834 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
\r
11835 bl = dom.getParent(n, 'p');
\r
11838 dom.rename(bl, s.element);
\r
11839 ed.nodeChanged();
\r
11847 find : function(n, t, s) {
\r
11848 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
\r
11850 while (n = w.nextNode()) {
\r
11854 if (t == 0 && n == s)
\r
11858 if (t == 1 && c == s)
\r
11865 forceRoots : function(ed, e) {
\r
11866 var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
\r
11867 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
\r
11869 // Fix for bug #1863847
\r
11870 //if (e && e.keyCode == 13)
\r
11873 // Wrap non blocks into blocks
\r
11874 for (i = nl.length - 1; i >= 0; i--) {
\r
11877 // Ignore internal elements
\r
11878 if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
\r
11883 // Is text or non block element
\r
11884 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
\r
11886 // Create new block but ignore whitespace
\r
11887 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
\r
11888 // Store selection
\r
11889 if (si == -2 && r) {
\r
11890 if (!isIE || r.setStart) {
\r
11891 // If selection is element then mark it
\r
11892 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
\r
11893 // Save the id of the selected element
\r
11894 eid = n.getAttribute("id");
\r
11895 n.setAttribute("id", "__mce");
\r
11897 // If element is inside body, might not be the case in contentEdiable mode
\r
11898 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
\r
11899 so = r.startOffset;
\r
11900 eo = r.endOffset;
\r
11901 si = t.find(b, 0, r.startContainer);
\r
11902 ei = t.find(b, 0, r.endContainer);
\r
11906 // Force control range into text range
\r
11908 tr = d.body.createTextRange();
\r
11909 tr.moveToElementText(r.item(0));
\r
11913 tr = d.body.createTextRange();
\r
11914 tr.moveToElementText(b);
\r
11916 bp = tr.move('character', c) * -1;
\r
11918 tr = r.duplicate();
\r
11920 sp = tr.move('character', c) * -1;
\r
11922 tr = r.duplicate();
\r
11924 le = (tr.move('character', c) * -1) - sp;
\r
11931 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
\r
11932 // See: http://support.microsoft.com/kb/829907
\r
11933 bl = ed.dom.create(ed.settings.forced_root_block);
\r
11934 nx.parentNode.replaceChild(bl, nx);
\r
11935 bl.appendChild(nx);
\r
11938 if (bl.hasChildNodes())
\r
11939 bl.insertBefore(nx, bl.firstChild);
\r
11941 bl.appendChild(nx);
\r
11944 bl = null; // Time to create new block
\r
11947 // Restore selection
\r
11949 if (!isIE || r.setStart) {
\r
11950 bl = b.getElementsByTagName(ed.settings.element)[0];
\r
11951 r = d.createRange();
\r
11953 // Select last location or generated block
\r
11955 r.setStart(t.find(b, 1, si), so);
\r
11957 r.setStart(bl, 0);
\r
11959 // Select last location or generated block
\r
11961 r.setEnd(t.find(b, 1, ei), eo);
\r
11966 s.removeAllRanges();
\r
11971 r = s.createRange();
\r
11972 r.moveToElementText(b);
\r
11974 r.moveStart('character', si);
\r
11975 r.moveEnd('character', ei);
\r
11981 } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) {
\r
11982 // Restore the id of the selected element
\r
11984 n.setAttribute('id', eid);
\r
11986 n.removeAttribute('id');
\r
11988 // Move caret before selected element
\r
11989 r = d.createRange();
\r
11990 r.setStartBefore(n);
\r
11991 r.setEndBefore(n);
\r
11996 getParentBlock : function(n) {
\r
11997 var d = this.dom;
\r
11999 return d.getParent(n, d.isBlock);
\r
12002 insertPara : function(e) {
\r
12003 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
12004 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
12006 // If root blocks are forced then use Operas default behavior since it's really good
\r
12007 // Removed due to bug: #1853816
\r
12008 // if (se.forced_root_block && isOpera)
\r
12011 // Setup before range
\r
12012 rb = d.createRange();
\r
12014 // If is before the first block element and in body, then move it into first block element
\r
12015 rb.setStart(s.anchorNode, s.anchorOffset);
\r
12016 rb.collapse(TRUE);
\r
12018 // Setup after range
\r
12019 ra = d.createRange();
\r
12021 // If is before the first block element and in body, then move it into first block element
\r
12022 ra.setStart(s.focusNode, s.focusOffset);
\r
12023 ra.collapse(TRUE);
\r
12025 // Setup start/end points
\r
12026 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
\r
12027 sn = dir ? s.anchorNode : s.focusNode;
\r
12028 so = dir ? s.anchorOffset : s.focusOffset;
\r
12029 en = dir ? s.focusNode : s.anchorNode;
\r
12030 eo = dir ? s.focusOffset : s.anchorOffset;
\r
12032 // If selection is in empty table cell
\r
12033 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
\r
12034 if (sn.firstChild.nodeName == 'BR')
\r
12035 dom.remove(sn.firstChild); // Remove BR
\r
12037 // Create two new block elements
\r
12038 if (sn.childNodes.length == 0) {
\r
12039 ed.dom.add(sn, se.element, null, '<br />');
\r
12040 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
12042 n = sn.innerHTML;
\r
12043 sn.innerHTML = '';
\r
12044 ed.dom.add(sn, se.element, null, n);
\r
12045 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
12048 // Move caret into the last one
\r
12049 r = d.createRange();
\r
12050 r.selectNodeContents(aft);
\r
12052 ed.selection.setRng(r);
\r
12057 // If the caret is in an invalid location in FF we need to move it into the first block
\r
12058 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
\r
12059 sn = en = sn.firstChild;
\r
12061 rb = d.createRange();
\r
12062 rb.setStart(sn, 0);
\r
12063 ra = d.createRange();
\r
12064 ra.setStart(en, 0);
\r
12067 // Never use body as start or end node
\r
12068 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
12069 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
\r
12070 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
12071 en = en.nodeName == "BODY" ? en.firstChild : en;
\r
12073 // Get start and end blocks
\r
12074 sb = t.getParentBlock(sn);
\r
12075 eb = t.getParentBlock(en);
\r
12076 bn = sb ? sb.nodeName : se.element; // Get block name to create
\r
12078 // Return inside list use default browser behavior
\r
12079 if (n = t.dom.getParent(sb, 'li,pre')) {
\r
12080 if (n.nodeName == 'LI')
\r
12081 return splitList(ed.selection, t.dom, n);
\r
12086 // If caption or absolute layers then always generate new blocks within
\r
12087 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
12092 // If caption or absolute layers then always generate new blocks within
\r
12093 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
12099 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
\r
12104 // Setup new before and after blocks
\r
12105 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
\r
12106 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
\r
12108 // Remove id from after clone
\r
12109 aft.removeAttribute('id');
\r
12111 // Is header and cursor is at the end, then force paragraph under
\r
12112 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
\r
12113 aft = ed.dom.create(se.element);
\r
12115 // Find start chop node
\r
12118 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
12122 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
\r
12124 // Find end chop node
\r
12127 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
12131 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
\r
12133 // Place first chop part into before block element
\r
12134 if (sc.nodeName == bn)
\r
12135 rb.setStart(sc, 0);
\r
12137 rb.setStartBefore(sc);
\r
12139 rb.setEnd(sn, so);
\r
12140 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
12142 // Place secnd chop part within new block element
\r
12144 ra.setEndAfter(ec);
\r
12146 //console.debug(s.focusNode, s.focusOffset);
\r
12149 ra.setStart(en, eo);
\r
12150 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
12152 // Create range around everything
\r
12153 r = d.createRange();
\r
12154 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
\r
12155 r.setStartBefore(sc.parentNode);
\r
12157 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
\r
12158 r.setStartBefore(rb.startContainer);
\r
12160 r.setStart(rb.startContainer, rb.startOffset);
\r
12163 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
\r
12164 r.setEndAfter(ec.parentNode);
\r
12166 r.setEnd(ra.endContainer, ra.endOffset);
\r
12168 // Delete and replace it with new block elements
\r
12169 r.deleteContents();
\r
12172 ed.getWin().scrollTo(0, vp.y);
\r
12174 // Never wrap blocks in blocks
\r
12175 if (bef.firstChild && bef.firstChild.nodeName == bn)
\r
12176 bef.innerHTML = bef.firstChild.innerHTML;
\r
12178 if (aft.firstChild && aft.firstChild.nodeName == bn)
\r
12179 aft.innerHTML = aft.firstChild.innerHTML;
\r
12181 // Padd empty blocks
\r
12182 if (isEmpty(bef))
\r
12183 bef.innerHTML = '<br />';
\r
12185 function appendStyles(e, en) {
\r
12186 var nl = [], nn, n, i;
\r
12188 e.innerHTML = '';
\r
12190 // Make clones of style elements
\r
12191 if (se.keep_styles) {
\r
12194 // We only want style specific elements
\r
12195 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
\r
12196 nn = n.cloneNode(FALSE);
\r
12197 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
\r
12200 } while (n = n.parentNode);
\r
12203 // Append style elements to aft
\r
12204 if (nl.length > 0) {
\r
12205 for (i = nl.length - 1, nn = e; i >= 0; i--)
\r
12206 nn = nn.appendChild(nl[i]);
\r
12208 // Padd most inner style element
\r
12209 nl[0].innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
12210 return nl[0]; // Move caret to most inner element
\r
12212 e.innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
12215 // Fill empty afterblook with current style
\r
12216 if (isEmpty(aft))
\r
12217 car = appendStyles(aft, en);
\r
12219 // Opera needs this one backwards for older versions
\r
12220 if (isOpera && parseFloat(opera.version()) < 9.5) {
\r
12221 r.insertNode(bef);
\r
12222 r.insertNode(aft);
\r
12224 r.insertNode(aft);
\r
12225 r.insertNode(bef);
\r
12232 function first(n) {
\r
12233 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
\r
12236 // Move cursor and scroll into view
\r
12237 r = d.createRange();
\r
12238 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
\r
12240 s.removeAllRanges();
\r
12243 // 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
12244 y = ed.dom.getPos(aft).y;
\r
12245 ch = aft.clientHeight;
\r
12247 // Is element within viewport
\r
12248 if (y < vp.y || y + ch > vp.y + vp.h) {
\r
12249 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
12250 //console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight));
\r
12256 backspaceDelete : function(e, bs) {
\r
12257 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
12259 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
\r
12260 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
\r
12261 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
\r
12263 // Walk the dom backwards until we find a text node
\r
12264 for (n = sc.lastChild; n; n = walker.prev()) {
\r
12265 if (n.nodeType == 3) {
\r
12266 r.setStart(n, n.nodeValue.length);
\r
12267 r.collapse(true);
\r
12274 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
\r
12275 // This workaround removes the element by hand and moves the caret to the previous element
\r
12276 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
\r
12277 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
\r
12278 // Find previous block element
\r
12280 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
\r
12283 if (sc != b.firstChild) {
\r
12284 // Find last text node
\r
12285 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
\r
12286 while (tn = w.nextNode())
\r
12289 // Place caret at the end of last text node
\r
12290 r = ed.getDoc().createRange();
\r
12291 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
\r
12292 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
\r
12295 // Remove the target container
\r
12296 ed.dom.remove(sc);
\r
12299 return Event.cancel(e);
\r
12307 (function(tinymce) {
\r
12309 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
\r
12311 tinymce.create('tinymce.ControlManager', {
\r
12312 ControlManager : function(ed, s) {
\r
12318 t.onAdd = new tinymce.util.Dispatcher(t);
\r
12319 t.onPostRender = new tinymce.util.Dispatcher(t);
\r
12320 t.prefix = s.prefix || ed.id + '_';
\r
12323 t.onPostRender.add(function() {
\r
12324 each(t.controls, function(c) {
\r
12330 get : function(id) {
\r
12331 return this.controls[this.prefix + id] || this.controls[id];
\r
12334 setActive : function(id, s) {
\r
12337 if (c = this.get(id))
\r
12343 setDisabled : function(id, s) {
\r
12346 if (c = this.get(id))
\r
12347 c.setDisabled(s);
\r
12352 add : function(c) {
\r
12356 t.controls[c.id] = c;
\r
12357 t.onAdd.dispatch(c, t);
\r
12363 createControl : function(n) {
\r
12364 var c, t = this, ed = t.editor;
\r
12366 each(ed.plugins, function(p) {
\r
12367 if (p.createControl) {
\r
12368 c = p.createControl(n, t);
\r
12377 case "separator":
\r
12378 return t.createSeparator();
\r
12381 if (!c && ed.buttons && (c = ed.buttons[n]))
\r
12382 return t.createButton(n, c);
\r
12387 createDropMenu : function(id, s, cc) {
\r
12388 var t = this, ed = t.editor, c, bm, v, cls;
\r
12391 'class' : 'mceDropDown',
\r
12392 constrain : ed.settings.constrain_menus
\r
12395 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
\r
12396 if (v = ed.getParam('skin_variant'))
\r
12397 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
\r
12399 id = t.prefix + id;
\r
12400 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
\r
12401 c = t.controls[id] = new cls(id, s);
\r
12402 c.onAddItem.add(function(c, o) {
\r
12403 var s = o.settings;
\r
12405 s.title = ed.getLang(s.title, s.title);
\r
12407 if (!s.onclick) {
\r
12408 s.onclick = function(v) {
\r
12410 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
12415 ed.onRemove.add(function() {
\r
12419 // Fix for bug #1897785, #1898007
\r
12420 if (tinymce.isIE) {
\r
12421 c.onShowMenu.add(function() {
\r
12422 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
12425 bm = ed.selection.getBookmark(1);
\r
12428 c.onHideMenu.add(function() {
\r
12430 ed.selection.moveToBookmark(bm);
\r
12439 createListBox : function(id, s, cc) {
\r
12440 var t = this, ed = t.editor, cmd, c, cls;
\r
12445 s.title = ed.translate(s.title);
\r
12446 s.scope = s.scope || ed;
\r
12448 if (!s.onselect) {
\r
12449 s.onselect = function(v) {
\r
12450 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12456 'class' : 'mce_' + id,
\r
12458 control_manager : t
\r
12461 id = t.prefix + id;
\r
12463 if (ed.settings.use_native_selects)
\r
12464 c = new tinymce.ui.NativeListBox(id, s);
\r
12466 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
\r
12467 c = new cls(id, s);
\r
12470 t.controls[id] = c;
\r
12472 // Fix focus problem in Safari
\r
12473 if (tinymce.isWebKit) {
\r
12474 c.onPostRender.add(function(c, n) {
\r
12475 // Store bookmark on mousedown
\r
12476 Event.add(n, 'mousedown', function() {
\r
12477 ed.bookmark = ed.selection.getBookmark(1);
\r
12480 // Restore on focus, since it might be lost
\r
12481 Event.add(n, 'focus', function() {
\r
12482 ed.selection.moveToBookmark(ed.bookmark);
\r
12483 ed.bookmark = null;
\r
12489 ed.onMouseDown.add(c.hideMenu, c);
\r
12494 createButton : function(id, s, cc) {
\r
12495 var t = this, ed = t.editor, o, c, cls;
\r
12500 s.title = ed.translate(s.title);
\r
12501 s.label = ed.translate(s.label);
\r
12502 s.scope = s.scope || ed;
\r
12504 if (!s.onclick && !s.menu_button) {
\r
12505 s.onclick = function() {
\r
12506 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
12512 'class' : 'mce_' + id,
\r
12513 unavailable_prefix : ed.getLang('unavailable', ''),
\r
12515 control_manager : t
\r
12518 id = t.prefix + id;
\r
12520 if (s.menu_button) {
\r
12521 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
\r
12522 c = new cls(id, s);
\r
12523 ed.onMouseDown.add(c.hideMenu, c);
\r
12525 cls = t._cls.button || tinymce.ui.Button;
\r
12526 c = new cls(id, s);
\r
12532 createMenuButton : function(id, s, cc) {
\r
12534 s.menu_button = 1;
\r
12536 return this.createButton(id, s, cc);
\r
12539 createSplitButton : function(id, s, cc) {
\r
12540 var t = this, ed = t.editor, cmd, c, cls;
\r
12545 s.title = ed.translate(s.title);
\r
12546 s.scope = s.scope || ed;
\r
12548 if (!s.onclick) {
\r
12549 s.onclick = function(v) {
\r
12550 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12554 if (!s.onselect) {
\r
12555 s.onselect = function(v) {
\r
12556 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12562 'class' : 'mce_' + id,
\r
12564 control_manager : t
\r
12567 id = t.prefix + id;
\r
12568 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
\r
12569 c = t.add(new cls(id, s));
\r
12570 ed.onMouseDown.add(c.hideMenu, c);
\r
12575 createColorSplitButton : function(id, s, cc) {
\r
12576 var t = this, ed = t.editor, cmd, c, cls, bm;
\r
12581 s.title = ed.translate(s.title);
\r
12582 s.scope = s.scope || ed;
\r
12584 if (!s.onclick) {
\r
12585 s.onclick = function(v) {
\r
12586 if (tinymce.isIE)
\r
12587 bm = ed.selection.getBookmark(1);
\r
12589 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12593 if (!s.onselect) {
\r
12594 s.onselect = function(v) {
\r
12595 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12601 'class' : 'mce_' + id,
\r
12602 'menu_class' : ed.getParam('skin') + 'Skin',
\r
12604 more_colors_title : ed.getLang('more_colors')
\r
12607 id = t.prefix + id;
\r
12608 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
\r
12609 c = new cls(id, s);
\r
12610 ed.onMouseDown.add(c.hideMenu, c);
\r
12612 // Remove the menu element when the editor is removed
\r
12613 ed.onRemove.add(function() {
\r
12617 // Fix for bug #1897785, #1898007
\r
12618 if (tinymce.isIE) {
\r
12619 c.onShowMenu.add(function() {
\r
12620 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
12622 bm = ed.selection.getBookmark(1);
\r
12625 c.onHideMenu.add(function() {
\r
12627 ed.selection.moveToBookmark(bm);
\r
12636 createToolbar : function(id, s, cc) {
\r
12637 var c, t = this, cls;
\r
12639 id = t.prefix + id;
\r
12640 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
\r
12641 c = new cls(id, s);
\r
12649 createSeparator : function(cc) {
\r
12650 var cls = cc || this._cls.separator || tinymce.ui.Separator;
\r
12652 return new cls();
\r
12655 setControlType : function(n, c) {
\r
12656 return this._cls[n.toLowerCase()] = c;
\r
12659 destroy : function() {
\r
12660 each(this.controls, function(c) {
\r
12664 this.controls = null;
\r
12669 (function(tinymce) {
\r
12670 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
\r
12672 tinymce.create('tinymce.WindowManager', {
\r
12673 WindowManager : function(ed) {
\r
12677 t.onOpen = new Dispatcher(t);
\r
12678 t.onClose = new Dispatcher(t);
\r
12683 open : function(s, p) {
\r
12684 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
\r
12686 // Default some options
\r
12689 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
\r
12690 sh = isOpera ? vp.h : screen.height;
\r
12691 s.name = s.name || 'mc_' + new Date().getTime();
\r
12692 s.width = parseInt(s.width || 320);
\r
12693 s.height = parseInt(s.height || 240);
\r
12694 s.resizable = true;
\r
12695 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
\r
12696 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
\r
12697 p.inline = false;
\r
12698 p.mce_width = s.width;
\r
12699 p.mce_height = s.height;
\r
12700 p.mce_auto_focus = s.auto_focus;
\r
12706 s.dialogWidth = s.width + 'px';
\r
12707 s.dialogHeight = s.height + 'px';
\r
12708 s.scroll = s.scrollbars || false;
\r
12712 // Build features string
\r
12713 each(s, function(v, k) {
\r
12714 if (tinymce.is(v, 'boolean'))
\r
12715 v = v ? 'yes' : 'no';
\r
12717 if (!/^(name|url)$/.test(k)) {
\r
12719 f += (f ? ';' : '') + k + ':' + v;
\r
12721 f += (f ? ',' : '') + k + '=' + v;
\r
12727 t.onOpen.dispatch(t, s, p);
\r
12729 u = s.url || s.file;
\r
12730 u = tinymce._addVer(u);
\r
12733 if (isIE && mo) {
\r
12735 window.showModalDialog(u, window, f);
\r
12737 w = window.open(u, s.name, f);
\r
12743 alert(t.editor.getLang('popup_blocked'));
\r
12746 close : function(w) {
\r
12748 this.onClose.dispatch(this);
\r
12751 createInstance : function(cl, a, b, c, d, e) {
\r
12752 var f = tinymce.resolve(cl);
\r
12754 return new f(a, b, c, d, e);
\r
12757 confirm : function(t, cb, s, w) {
\r
12760 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
\r
12763 alert : function(tx, cb, s, w) {
\r
12767 w.alert(t._decode(t.editor.getLang(tx, tx)));
\r
12773 resizeBy : function(dw, dh, win) {
\r
12774 win.resizeBy(dw, dh);
\r
12777 // Internal functions
\r
12779 _decode : function(s) {
\r
12780 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
\r
12784 (function(tinymce) {
\r
12785 function CommandManager() {
\r
12786 var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
\r
12788 function add(collection, cmd, func, scope) {
\r
12789 if (typeof(cmd) == 'string')
\r
12792 tinymce.each(cmd, function(cmd) {
\r
12793 collection[cmd.toLowerCase()] = {func : func, scope : scope};
\r
12797 tinymce.extend(this, {
\r
12798 add : function(cmd, func, scope) {
\r
12799 add(execCommands, cmd, func, scope);
\r
12802 addQueryStateHandler : function(cmd, func, scope) {
\r
12803 add(queryStateCommands, cmd, func, scope);
\r
12806 addQueryValueHandler : function(cmd, func, scope) {
\r
12807 add(queryValueCommands, cmd, func, scope);
\r
12810 execCommand : function(scope, cmd, ui, value, args) {
\r
12811 if (cmd = execCommands[cmd.toLowerCase()]) {
\r
12812 if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
\r
12817 queryCommandValue : function() {
\r
12818 if (cmd = queryValueCommands[cmd.toLowerCase()])
\r
12819 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
12822 queryCommandState : function() {
\r
12823 if (cmd = queryStateCommands[cmd.toLowerCase()])
\r
12824 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
12829 tinymce.GlobalCommands = new CommandManager();
\r
12831 (function(tinymce) {
\r
12832 tinymce.Formatter = function(ed) {
\r
12833 var formats = {},
\r
12834 each = tinymce.each,
\r
12836 selection = ed.selection,
\r
12837 TreeWalker = tinymce.dom.TreeWalker,
\r
12838 rangeUtils = new tinymce.dom.RangeUtils(dom),
\r
12839 isValid = ed.schema.isValid,
\r
12840 isBlock = dom.isBlock,
\r
12841 forcedRootBlock = ed.settings.forced_root_block,
\r
12842 nodeIndex = dom.nodeIndex,
\r
12843 INVISIBLE_CHAR = '\uFEFF',
\r
12844 MCE_ATTR_RE = /^(src|href|style)$/,
\r
12848 pendingFormats = {apply : [], remove : []};
\r
12850 function isArray(obj) {
\r
12851 return obj instanceof Array;
\r
12854 function getParents(node, selector) {
\r
12855 return dom.getParents(node, selector, dom.getRoot());
\r
12858 function isCaretNode(node) {
\r
12859 return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
\r
12862 // Public functions
\r
12864 function get(name) {
\r
12865 return name ? formats[name] : formats;
\r
12868 function register(name, format) {
\r
12870 if (typeof(name) !== 'string') {
\r
12871 each(name, function(format, name) {
\r
12872 register(name, format);
\r
12875 // Force format into array and add it to internal collection
\r
12876 format = format.length ? format : [format];
\r
12878 each(format, function(format) {
\r
12879 // Set deep to false by default on selector formats this to avoid removing
\r
12880 // alignment on images inside paragraphs when alignment is changed on paragraphs
\r
12881 if (format.deep === undefined)
\r
12882 format.deep = !format.selector;
\r
12884 // Default to true
\r
12885 if (format.split === undefined)
\r
12886 format.split = !format.selector || format.inline;
\r
12888 // Default to true
\r
12889 if (format.remove === undefined && format.selector && !format.inline)
\r
12890 format.remove = 'none';
\r
12892 // Mark format as a mixed format inline + block level
\r
12893 if (format.selector && format.inline) {
\r
12894 format.mixed = true;
\r
12895 format.block_expand = true;
\r
12898 // Split classes if needed
\r
12899 if (typeof(format.classes) === 'string')
\r
12900 format.classes = format.classes.split(/\s+/);
\r
12903 formats[name] = format;
\r
12908 function apply(name, vars, node) {
\r
12909 var formatList = get(name), format = formatList[0], bookmark, rng, i;
\r
12911 function moveStart(rng) {
\r
12912 var container = rng.startContainer,
\r
12913 offset = rng.startOffset,
\r
12916 // Move startContainer/startOffset in to a suitable node
\r
12917 if (container.nodeType == 1 || container.nodeValue === "") {
\r
12918 container = container.nodeType == 1 ? container.childNodes[offset] : container;
\r
12920 // Might fail if the offset is behind the last element in it's container
\r
12922 walker = new TreeWalker(container, container.parentNode);
\r
12923 for (node = walker.current(); node; node = walker.next()) {
\r
12924 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
\r
12925 rng.setStart(node, 0);
\r
12935 function setElementFormat(elm, fmt) {
\r
12936 fmt = fmt || format;
\r
12939 each(fmt.styles, function(value, name) {
\r
12940 dom.setStyle(elm, name, replaceVars(value, vars));
\r
12943 each(fmt.attributes, function(value, name) {
\r
12944 dom.setAttrib(elm, name, replaceVars(value, vars));
\r
12947 each(fmt.classes, function(value) {
\r
12948 value = replaceVars(value, vars);
\r
12950 if (!dom.hasClass(elm, value))
\r
12951 dom.addClass(elm, value);
\r
12956 function applyRngStyle(rng) {
\r
12957 var newWrappers = [], wrapName, wrapElm;
\r
12959 // Setup wrapper element
\r
12960 wrapName = format.inline || format.block;
\r
12961 wrapElm = dom.create(wrapName);
\r
12962 setElementFormat(wrapElm);
\r
12964 rangeUtils.walk(rng, function(nodes) {
\r
12965 var currentWrapElm;
\r
12967 function process(node) {
\r
12968 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
\r
12970 // Stop wrapping on br elements
\r
12971 if (isEq(nodeName, 'br')) {
\r
12972 currentWrapElm = 0;
\r
12974 // Remove any br elements when we wrap things
\r
12975 if (format.block)
\r
12976 dom.remove(node);
\r
12981 // If node is wrapper type
\r
12982 if (format.wrapper && matchNode(node, name, vars)) {
\r
12983 currentWrapElm = 0;
\r
12987 // Can we rename the block
\r
12988 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
\r
12989 node = dom.rename(node, wrapName);
\r
12990 setElementFormat(node);
\r
12991 newWrappers.push(node);
\r
12992 currentWrapElm = 0;
\r
12996 // Handle selector patterns
\r
12997 if (format.selector) {
\r
12998 // Look for matching formats
\r
12999 each(formatList, function(format) {
\r
13000 if (dom.is(node, format.selector) && !isCaretNode(node)) {
\r
13001 setElementFormat(node, format);
\r
13006 // Continue processing if a selector match wasn't found and a inline element is defined
\r
13007 if (!format.inline || found) {
\r
13008 currentWrapElm = 0;
\r
13013 // Is it valid to wrap this item
\r
13014 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
\r
13015 // Start wrapping
\r
13016 if (!currentWrapElm) {
\r
13018 currentWrapElm = wrapElm.cloneNode(FALSE);
\r
13019 node.parentNode.insertBefore(currentWrapElm, node);
\r
13020 newWrappers.push(currentWrapElm);
\r
13023 currentWrapElm.appendChild(node);
\r
13025 // Start a new wrapper for possible children
\r
13026 currentWrapElm = 0;
\r
13028 each(tinymce.grep(node.childNodes), process);
\r
13030 // End the last wrapper
\r
13031 currentWrapElm = 0;
\r
13035 // Process siblings from range
\r
13036 each(nodes, process);
\r
13040 each(newWrappers, function(node) {
\r
13043 function getChildCount(node) {
\r
13046 each(node.childNodes, function(node) {
\r
13047 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
\r
13054 function mergeStyles(node) {
\r
13055 var child, clone;
\r
13057 each(node.childNodes, function(node) {
\r
13058 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
\r
13060 return FALSE; // break loop
\r
13064 // If child was found and of the same type as the current node
\r
13065 if (child && matchName(child, format)) {
\r
13066 clone = child.cloneNode(FALSE);
\r
13067 setElementFormat(clone);
\r
13069 dom.replace(clone, node, TRUE);
\r
13070 dom.remove(child, 1);
\r
13073 return clone || node;
\r
13076 childCount = getChildCount(node);
\r
13078 // Remove empty nodes
\r
13079 if (childCount === 0) {
\r
13080 dom.remove(node, 1);
\r
13084 if (format.inline || format.wrapper) {
\r
13085 // Merges the current node with it's children of similar type to reduce the number of elements
\r
13086 if (!format.exact && childCount === 1)
\r
13087 node = mergeStyles(node);
\r
13089 // Remove/merge children
\r
13090 each(formatList, function(format) {
\r
13091 // Merge all children of similar type will move styles from child to parent
\r
13092 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
\r
13093 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
\r
13094 each(dom.select(format.inline, node), function(child) {
\r
13095 removeFormat(format, vars, child, format.exact ? child : null);
\r
13099 // Remove child if direct parent is of same type
\r
13100 if (matchNode(node.parentNode, name, vars)) {
\r
13101 dom.remove(node, 1);
\r
13106 // Look for parent with similar style format
\r
13107 if (format.merge_with_parents) {
\r
13108 dom.getParent(node.parentNode, function(parent) {
\r
13109 if (matchNode(parent, name, vars)) {
\r
13110 dom.remove(node, 1);
\r
13117 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
\r
13119 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
\r
13120 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
\r
13128 rng = dom.createRng();
\r
13130 rng.setStartBefore(node);
\r
13131 rng.setEndAfter(node);
\r
13133 applyRngStyle(expandRng(rng, formatList));
\r
13135 if (!selection.isCollapsed() || !format.inline) {
\r
13136 // Apply formatting to selection
\r
13137 bookmark = selection.getBookmark();
\r
13138 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
\r
13140 selection.moveToBookmark(bookmark);
\r
13141 selection.setRng(moveStart(selection.getRng(TRUE)));
\r
13142 ed.nodeChanged();
\r
13144 performCaretAction('apply', name, vars);
\r
13149 function remove(name, vars, node) {
\r
13150 var formatList = get(name), format = formatList[0], bookmark, i, rng;
\r
13152 function moveStart(rng) {
\r
13153 var container = rng.startContainer,
\r
13154 offset = rng.startOffset,
\r
13155 walker, node, nodes, tmpNode;
\r
13157 // Convert text node into index if possible
\r
13158 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
\r
13159 container = container.parentNode;
\r
13160 offset = nodeIndex(container) + 1;
\r
13163 // Move startContainer/startOffset in to a suitable node
\r
13164 if (container.nodeType == 1) {
\r
13165 nodes = container.childNodes;
\r
13166 container = nodes[Math.min(offset, nodes.length - 1)];
\r
13167 walker = new TreeWalker(container);
\r
13169 // If offset is at end of the parent node walk to the next one
\r
13170 if (offset > nodes.length - 1)
\r
13173 for (node = walker.current(); node; node = walker.next()) {
\r
13174 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
\r
13175 // IE has a "neat" feature where it moves the start node into the closest element
\r
13176 // we can avoid this by inserting an element before it and then remove it after we set the selection
\r
13177 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
\r
13178 node.parentNode.insertBefore(tmpNode, node);
\r
13180 // Set selection and remove tmpNode
\r
13181 rng.setStart(node, 0);
\r
13182 selection.setRng(rng);
\r
13183 dom.remove(tmpNode);
\r
13191 // Merges the styles for each node
\r
13192 function process(node) {
\r
13193 var children, i, l;
\r
13195 // Grab the children first since the nodelist might be changed
\r
13196 children = tinymce.grep(node.childNodes);
\r
13198 // Process current node
\r
13199 for (i = 0, l = formatList.length; i < l; i++) {
\r
13200 if (removeFormat(formatList[i], vars, node, node))
\r
13204 // Process the children
\r
13205 if (format.deep) {
\r
13206 for (i = 0, l = children.length; i < l; i++)
\r
13207 process(children[i]);
\r
13211 function findFormatRoot(container) {
\r
13214 // Find format root
\r
13215 each(getParents(container.parentNode).reverse(), function(parent) {
\r
13218 // Find format root element
\r
13219 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
\r
13220 // Is the node matching the format we are looking for
\r
13221 format = matchNode(parent, name, vars);
\r
13222 if (format && format.split !== false)
\r
13223 formatRoot = parent;
\r
13227 return formatRoot;
\r
13230 function wrapAndSplit(format_root, container, target, split) {
\r
13231 var parent, clone, lastClone, firstClone, i, formatRootParent;
\r
13233 // Format root found then clone formats and split it
\r
13234 if (format_root) {
\r
13235 formatRootParent = format_root.parentNode;
\r
13237 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
\r
13238 clone = parent.cloneNode(FALSE);
\r
13240 for (i = 0; i < formatList.length; i++) {
\r
13241 if (removeFormat(formatList[i], vars, clone, clone)) {
\r
13247 // Build wrapper node
\r
13250 clone.appendChild(lastClone);
\r
13253 firstClone = clone;
\r
13255 lastClone = clone;
\r
13259 // Never split block elements if the format is mixed
\r
13260 if (split && (!format.mixed || !isBlock(format_root)))
\r
13261 container = dom.split(format_root, container);
\r
13263 // Wrap container in cloned formats
\r
13265 target.parentNode.insertBefore(lastClone, target);
\r
13266 firstClone.appendChild(target);
\r
13270 return container;
\r
13273 function splitToFormatRoot(container) {
\r
13274 return wrapAndSplit(findFormatRoot(container), container, container, true);
\r
13277 function unwrap(start) {
\r
13278 var node = dom.get(start ? '_start' : '_end'),
\r
13279 out = node[start ? 'firstChild' : 'lastChild'];
\r
13281 // If the end is placed within the start the result will be removed
\r
13282 // So this checks if the out node is a bookmark node if it is it
\r
13283 // checks for another more suitable node
\r
13284 if (isBookmarkNode(out))
\r
13285 out = out[start ? 'firstChild' : 'lastChild'];
\r
13287 dom.remove(node, true);
\r
13292 function removeRngStyle(rng) {
\r
13293 var startContainer, endContainer;
\r
13295 rng = expandRng(rng, formatList, TRUE);
\r
13297 if (format.split) {
\r
13298 startContainer = getContainer(rng, TRUE);
\r
13299 endContainer = getContainer(rng);
\r
13301 if (startContainer != endContainer) {
\r
13302 // Wrap start/end nodes in span element since these might be cloned/moved
\r
13303 startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
\r
13304 endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
\r
13306 // Split start/end
\r
13307 splitToFormatRoot(startContainer);
\r
13308 splitToFormatRoot(endContainer);
\r
13310 // Unwrap start/end to get real elements again
\r
13311 startContainer = unwrap(TRUE);
\r
13312 endContainer = unwrap();
\r
13314 startContainer = endContainer = splitToFormatRoot(startContainer);
\r
13316 // Update range positions since they might have changed after the split operations
\r
13317 rng.startContainer = startContainer.parentNode;
\r
13318 rng.startOffset = nodeIndex(startContainer);
\r
13319 rng.endContainer = endContainer.parentNode;
\r
13320 rng.endOffset = nodeIndex(endContainer) + 1;
\r
13323 // Remove items between start/end
\r
13324 rangeUtils.walk(rng, function(nodes) {
\r
13325 each(nodes, function(node) {
\r
13333 rng = dom.createRng();
\r
13334 rng.setStartBefore(node);
\r
13335 rng.setEndAfter(node);
\r
13336 removeRngStyle(rng);
\r
13340 if (!selection.isCollapsed() || !format.inline) {
\r
13341 bookmark = selection.getBookmark();
\r
13342 removeRngStyle(selection.getRng(TRUE));
\r
13343 selection.moveToBookmark(bookmark);
\r
13345 // 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
13346 if (match(name, vars, selection.getStart())) {
\r
13347 moveStart(selection.getRng(true));
\r
13350 ed.nodeChanged();
\r
13352 performCaretAction('remove', name, vars);
\r
13355 function toggle(name, vars, node) {
\r
13356 if (match(name, vars, node))
\r
13357 remove(name, vars, node);
\r
13359 apply(name, vars, node);
\r
13362 function matchNode(node, name, vars, similar) {
\r
13363 var formatList = get(name), format, i, classes;
\r
13365 function matchItems(node, format, item_name) {
\r
13366 var key, value, items = format[item_name], i;
\r
13368 // Check all items
\r
13370 // Non indexed object
\r
13371 if (items.length === undefined) {
\r
13372 for (key in items) {
\r
13373 if (items.hasOwnProperty(key)) {
\r
13374 if (item_name === 'attributes')
\r
13375 value = dom.getAttrib(node, key);
\r
13377 value = getStyle(node, key);
\r
13379 if (similar && !value && !format.exact)
\r
13382 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
\r
13387 // Only one match needed for indexed arrays
\r
13388 for (i = 0; i < items.length; i++) {
\r
13389 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
\r
13398 if (formatList && node) {
\r
13399 // Check each format in list
\r
13400 for (i = 0; i < formatList.length; i++) {
\r
13401 format = formatList[i];
\r
13403 // Name name, attributes, styles and classes
\r
13404 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
\r
13406 if (classes = format.classes) {
\r
13407 for (i = 0; i < classes.length; i++) {
\r
13408 if (!dom.hasClass(node, classes[i]))
\r
13419 function match(name, vars, node) {
\r
13420 var startNode, i;
\r
13422 function matchParents(node) {
\r
13423 // Find first node with similar format settings
\r
13424 node = dom.getParent(node, function(node) {
\r
13425 return !!matchNode(node, name, vars, true);
\r
13428 // Do an exact check on the similar format element
\r
13429 return matchNode(node, name, vars);
\r
13432 // Check specified node
\r
13434 return matchParents(node);
\r
13436 // Check pending formats
\r
13437 if (selection.isCollapsed()) {
\r
13438 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
13439 if (pendingFormats.apply[i].name == name)
\r
13443 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
13444 if (pendingFormats.remove[i].name == name)
\r
13448 return matchParents(selection.getNode());
\r
13451 // Check selected node
\r
13452 node = selection.getNode();
\r
13453 if (matchParents(node))
\r
13456 // Check start node if it's different
\r
13457 startNode = selection.getStart();
\r
13458 if (startNode != node) {
\r
13459 if (matchParents(startNode))
\r
13466 function matchAll(names, vars) {
\r
13467 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
\r
13469 // If the selection is collapsed then check pending formats
\r
13470 if (selection.isCollapsed()) {
\r
13471 for (ni = 0; ni < names.length; ni++) {
\r
13472 // If the name is to be removed, then stop it from being added
\r
13473 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
13474 name = names[ni];
\r
13476 if (pendingFormats.remove[i].name == name) {
\r
13477 checkedMap[name] = true;
\r
13483 // If the format is to be applied
\r
13484 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
13485 for (ni = 0; ni < names.length; ni++) {
\r
13486 name = names[ni];
\r
13488 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
\r
13489 checkedMap[name] = true;
\r
13490 matchedFormatNames.push(name);
\r
13496 // Check start of selection for formats
\r
13497 startElement = selection.getStart();
\r
13498 dom.getParent(startElement, function(node) {
\r
13501 for (i = 0; i < names.length; i++) {
\r
13504 if (!checkedMap[name] && matchNode(node, name, vars)) {
\r
13505 checkedMap[name] = true;
\r
13506 matchedFormatNames.push(name);
\r
13511 return matchedFormatNames;
\r
13514 function canApply(name) {
\r
13515 var formatList = get(name), startNode, parents, i, x, selector;
\r
13517 if (formatList) {
\r
13518 startNode = selection.getStart();
\r
13519 parents = getParents(startNode);
\r
13521 for (x = formatList.length - 1; x >= 0; x--) {
\r
13522 selector = formatList[x].selector;
\r
13524 // Format is not selector based, then always return TRUE
\r
13528 for (i = parents.length - 1; i >= 0; i--) {
\r
13529 if (dom.is(parents[i], selector))
\r
13538 // Expose to public
\r
13539 tinymce.extend(this, {
\r
13541 register : register,
\r
13546 matchAll : matchAll,
\r
13547 matchNode : matchNode,
\r
13548 canApply : canApply
\r
13551 // Private functions
\r
13553 function matchName(node, format) {
\r
13554 // Check for inline match
\r
13555 if (isEq(node, format.inline))
\r
13558 // Check for block match
\r
13559 if (isEq(node, format.block))
\r
13562 // Check for selector match
\r
13563 if (format.selector)
\r
13564 return dom.is(node, format.selector);
\r
13567 function isEq(str1, str2) {
\r
13568 str1 = str1 || '';
\r
13569 str2 = str2 || '';
\r
13571 str1 = '' + (str1.nodeName || str1);
\r
13572 str2 = '' + (str2.nodeName || str2);
\r
13574 return str1.toLowerCase() == str2.toLowerCase();
\r
13577 function getStyle(node, name) {
\r
13578 var styleVal = dom.getStyle(node, name);
\r
13580 // Force the format to hex
\r
13581 if (name == 'color' || name == 'backgroundColor')
\r
13582 styleVal = dom.toHex(styleVal);
\r
13584 // Opera will return bold as 700
\r
13585 if (name == 'fontWeight' && styleVal == 700)
\r
13586 styleVal = 'bold';
\r
13588 return '' + styleVal;
\r
13591 function replaceVars(value, vars) {
\r
13592 if (typeof(value) != "string")
\r
13593 value = value(vars);
\r
13595 value = value.replace(/%(\w+)/g, function(str, name) {
\r
13596 return vars[name] || str;
\r
13603 function isWhiteSpaceNode(node) {
\r
13604 return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
\r
13607 function wrap(node, name, attrs) {
\r
13608 var wrapper = dom.create(name, attrs);
\r
13610 node.parentNode.insertBefore(wrapper, node);
\r
13611 wrapper.appendChild(node);
\r
13616 function expandRng(rng, format, remove) {
\r
13617 var startContainer = rng.startContainer,
\r
13618 startOffset = rng.startOffset,
\r
13619 endContainer = rng.endContainer,
\r
13620 endOffset = rng.endOffset, sibling, lastIdx;
\r
13622 // This function walks up the tree if there is no siblings before/after the node
\r
13623 function findParentContainer(container, child_name, sibling_name, root) {
\r
13624 var parent, child;
\r
13626 root = root || dom.getRoot();
\r
13629 // Check if we can move up are we at root level or body level
\r
13630 parent = container.parentNode;
\r
13632 // Stop expanding on block elements or root depending on format
\r
13633 if (parent == root || (!format[0].block_expand && isBlock(parent)))
\r
13634 return container;
\r
13636 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
\r
13637 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
13638 return container;
\r
13640 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
13641 return container;
\r
13644 container = container.parentNode;
\r
13647 return container;
\r
13650 // If index based start position then resolve it
\r
13651 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
\r
13652 lastIdx = startContainer.childNodes.length - 1;
\r
13653 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
\r
13655 if (startContainer.nodeType == 3)
\r
13659 // If index based end position then resolve it
\r
13660 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
\r
13661 lastIdx = endContainer.childNodes.length - 1;
\r
13662 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
\r
13664 if (endContainer.nodeType == 3)
\r
13665 endOffset = endContainer.nodeValue.length;
\r
13668 // Exclude bookmark nodes if possible
\r
13669 if (isBookmarkNode(startContainer.parentNode))
\r
13670 startContainer = startContainer.parentNode;
\r
13672 if (isBookmarkNode(startContainer))
\r
13673 startContainer = startContainer.nextSibling || startContainer;
\r
13675 if (isBookmarkNode(endContainer.parentNode))
\r
13676 endContainer = endContainer.parentNode;
\r
13678 if (isBookmarkNode(endContainer))
\r
13679 endContainer = endContainer.previousSibling || endContainer;
\r
13681 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
\r
13682 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
\r
13683 // This will reduce the number of wrapper elements that needs to be created
\r
13684 // Move start point up the tree
\r
13685 if (format[0].inline || format[0].block_expand) {
\r
13686 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
13687 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
13690 // Expand start/end container to matching selector
\r
13691 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
\r
13692 function findSelectorEndPoint(container, sibling_name) {
\r
13693 var parents, i, y;
\r
13695 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
\r
13696 container = container[sibling_name];
\r
13698 parents = getParents(container);
\r
13699 for (i = 0; i < parents.length; i++) {
\r
13700 for (y = 0; y < format.length; y++) {
\r
13701 if (dom.is(parents[i], format[y].selector))
\r
13702 return parents[i];
\r
13706 return container;
\r
13709 // Find new startContainer/endContainer if there is better one
\r
13710 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
\r
13711 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
\r
13714 // Expand start/end container to matching block element or text node
\r
13715 if (format[0].block || format[0].selector) {
\r
13716 function findBlockEndPoint(container, sibling_name, sibling_name2) {
\r
13719 // Expand to block of similar type
\r
13720 if (!format[0].wrapper)
\r
13721 node = dom.getParent(container, format[0].block);
\r
13723 // Expand to first wrappable block element or any block element
\r
13725 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
\r
13727 // Exclude inner lists from wrapping
\r
13728 if (node && format[0].wrapper)
\r
13729 node = getParents(node, 'ul,ol').reverse()[0] || node;
\r
13731 // Didn't find a block element look for first/last wrappable element
\r
13733 node = container;
\r
13735 while (node[sibling_name] && !isBlock(node[sibling_name])) {
\r
13736 node = node[sibling_name];
\r
13738 // Break on BR but include it will be removed later on
\r
13739 // we can't remove it now since we need to check if it can be wrapped
\r
13740 if (isEq(node, 'br'))
\r
13745 return node || container;
\r
13748 // Find new startContainer/endContainer if there is better one
\r
13749 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
\r
13750 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
\r
13752 // Non block element then try to expand up the leaf
\r
13753 if (format[0].block) {
\r
13754 if (!isBlock(startContainer))
\r
13755 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
13757 if (!isBlock(endContainer))
\r
13758 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
13762 // Setup index for startContainer
\r
13763 if (startContainer.nodeType == 1) {
\r
13764 startOffset = nodeIndex(startContainer);
\r
13765 startContainer = startContainer.parentNode;
\r
13768 // Setup index for endContainer
\r
13769 if (endContainer.nodeType == 1) {
\r
13770 endOffset = nodeIndex(endContainer) + 1;
\r
13771 endContainer = endContainer.parentNode;
\r
13774 // Return new range like object
\r
13776 startContainer : startContainer,
\r
13777 startOffset : startOffset,
\r
13778 endContainer : endContainer,
\r
13779 endOffset : endOffset
\r
13783 function removeFormat(format, vars, node, compare_node) {
\r
13784 var i, attrs, stylesModified;
\r
13786 // Check if node matches format
\r
13787 if (!matchName(node, format))
\r
13790 // Should we compare with format attribs and styles
\r
13791 if (format.remove != 'all') {
\r
13793 each(format.styles, function(value, name) {
\r
13794 value = replaceVars(value, vars);
\r
13797 if (typeof(name) === 'number') {
\r
13799 compare_node = 0;
\r
13802 if (!compare_node || isEq(getStyle(compare_node, name), value))
\r
13803 dom.setStyle(node, name, '');
\r
13805 stylesModified = 1;
\r
13808 // Remove style attribute if it's empty
\r
13809 if (stylesModified && dom.getAttrib(node, 'style') == '') {
\r
13810 node.removeAttribute('style');
\r
13811 node.removeAttribute('_mce_style');
\r
13814 // Remove attributes
\r
13815 each(format.attributes, function(value, name) {
\r
13818 value = replaceVars(value, vars);
\r
13821 if (typeof(name) === 'number') {
\r
13823 compare_node = 0;
\r
13826 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
\r
13827 // Keep internal classes
\r
13828 if (name == 'class') {
\r
13829 value = dom.getAttrib(node, name);
\r
13831 // Build new class value where everything is removed except the internal prefixed classes
\r
13833 each(value.split(/\s+/), function(cls) {
\r
13834 if (/mce\w+/.test(cls))
\r
13835 valueOut += (valueOut ? ' ' : '') + cls;
\r
13838 // We got some internal classes left
\r
13840 dom.setAttrib(node, name, valueOut);
\r
13846 // IE6 has a bug where the attribute doesn't get removed correctly
\r
13847 if (name == "class")
\r
13848 node.removeAttribute('className');
\r
13850 // Remove mce prefixed attributes
\r
13851 if (MCE_ATTR_RE.test(name))
\r
13852 node.removeAttribute('_mce_' + name);
\r
13854 node.removeAttribute(name);
\r
13858 // Remove classes
\r
13859 each(format.classes, function(value) {
\r
13860 value = replaceVars(value, vars);
\r
13862 if (!compare_node || dom.hasClass(compare_node, value))
\r
13863 dom.removeClass(node, value);
\r
13866 // Check for non internal attributes
\r
13867 attrs = dom.getAttribs(node);
\r
13868 for (i = 0; i < attrs.length; i++) {
\r
13869 if (attrs[i].nodeName.indexOf('_') !== 0)
\r
13874 // Remove the inline child if it's empty for example <b> or <span>
\r
13875 if (format.remove != 'none') {
\r
13876 removeNode(node, format);
\r
13881 function removeNode(node, format) {
\r
13882 var parentNode = node.parentNode, rootBlockElm;
\r
13884 if (format.block) {
\r
13885 if (!forcedRootBlock) {
\r
13886 function find(node, next, inc) {
\r
13887 node = getNonWhiteSpaceSibling(node, next, inc);
\r
13889 return !node || (node.nodeName == 'BR' || isBlock(node));
\r
13892 // Append BR elements if needed before we remove the block
\r
13893 if (isBlock(node) && !isBlock(parentNode)) {
\r
13894 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
\r
13895 node.insertBefore(dom.create('br'), node.firstChild);
\r
13897 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
\r
13898 node.appendChild(dom.create('br'));
\r
13901 // Wrap the block in a forcedRootBlock if we are at the root of document
\r
13902 if (parentNode == dom.getRoot()) {
\r
13903 if (!format.list_block || !isEq(node, format.list_block)) {
\r
13904 each(tinymce.grep(node.childNodes), function(node) {
\r
13905 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
\r
13906 if (!rootBlockElm)
\r
13907 rootBlockElm = wrap(node, forcedRootBlock);
\r
13909 rootBlockElm.appendChild(node);
\r
13911 rootBlockElm = 0;
\r
13918 // Never remove nodes that isn't the specified inline element if a selector is specified too
\r
13919 if (format.selector && format.inline && !isEq(format.inline, node))
\r
13922 dom.remove(node, 1);
\r
13925 function getNonWhiteSpaceSibling(node, next, inc) {
\r
13927 next = next ? 'nextSibling' : 'previousSibling';
\r
13929 for (node = inc ? node : node[next]; node; node = node[next]) {
\r
13930 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
\r
13936 function isBookmarkNode(node) {
\r
13937 return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
\r
13940 function mergeSiblings(prev, next) {
\r
13941 var marker, sibling, tmpSibling;
\r
13943 function compareElements(node1, node2) {
\r
13944 // Not the same name
\r
13945 if (node1.nodeName != node2.nodeName)
\r
13948 function getAttribs(node) {
\r
13949 var attribs = {};
\r
13951 each(dom.getAttribs(node), function(attr) {
\r
13952 var name = attr.nodeName.toLowerCase();
\r
13954 // Don't compare internal attributes or style
\r
13955 if (name.indexOf('_') !== 0 && name !== 'style')
\r
13956 attribs[name] = dom.getAttrib(node, name);
\r
13962 function compareObjects(obj1, obj2) {
\r
13965 for (name in obj1) {
\r
13966 // Obj1 has item obj2 doesn't have
\r
13967 if (obj1.hasOwnProperty(name)) {
\r
13968 value = obj2[name];
\r
13970 // Obj2 doesn't have obj1 item
\r
13971 if (value === undefined)
\r
13974 // Obj2 item has a different value
\r
13975 if (obj1[name] != value)
\r
13978 // Delete similar value
\r
13979 delete obj2[name];
\r
13983 // Check if obj 2 has something obj 1 doesn't have
\r
13984 for (name in obj2) {
\r
13985 // Obj2 has item obj1 doesn't have
\r
13986 if (obj2.hasOwnProperty(name))
\r
13993 // Attribs are not the same
\r
13994 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
\r
13997 // Styles are not the same
\r
13998 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
\r
14004 // Check if next/prev exists and that they are elements
\r
14005 if (prev && next) {
\r
14006 function findElementSibling(node, sibling_name) {
\r
14007 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
\r
14008 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
14011 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
14018 // If previous sibling is empty then jump over it
\r
14019 prev = findElementSibling(prev, 'previousSibling');
\r
14020 next = findElementSibling(next, 'nextSibling');
\r
14022 // Compare next and previous nodes
\r
14023 if (compareElements(prev, next)) {
\r
14024 // Append nodes between
\r
14025 for (sibling = prev.nextSibling; sibling && sibling != next;) {
\r
14026 tmpSibling = sibling;
\r
14027 sibling = sibling.nextSibling;
\r
14028 prev.appendChild(tmpSibling);
\r
14031 // Remove next node
\r
14032 dom.remove(next);
\r
14034 // Move children into prev node
\r
14035 each(tinymce.grep(next.childNodes), function(node) {
\r
14036 prev.appendChild(node);
\r
14046 function isTextBlock(name) {
\r
14047 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
\r
14050 function getContainer(rng, start) {
\r
14051 var container, offset, lastIdx;
\r
14053 container = rng[start ? 'startContainer' : 'endContainer'];
\r
14054 offset = rng[start ? 'startOffset' : 'endOffset'];
\r
14056 if (container.nodeType == 1) {
\r
14057 lastIdx = container.childNodes.length - 1;
\r
14059 if (!start && offset)
\r
14062 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
\r
14065 return container;
\r
14068 function performCaretAction(type, name, vars) {
\r
14069 var i, currentPendingFormats = pendingFormats[type],
\r
14070 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
\r
14072 function hasPending() {
\r
14073 return pendingFormats.apply.length || pendingFormats.remove.length;
\r
14076 function resetPending() {
\r
14077 pendingFormats.apply = [];
\r
14078 pendingFormats.remove = [];
\r
14081 function perform(caret_node) {
\r
14082 // Apply pending formats
\r
14083 each(pendingFormats.apply.reverse(), function(item) {
\r
14084 apply(item.name, item.vars, caret_node);
\r
14087 // Remove pending formats
\r
14088 each(pendingFormats.remove.reverse(), function(item) {
\r
14089 remove(item.name, item.vars, caret_node);
\r
14092 dom.remove(caret_node, 1);
\r
14096 // Check if it already exists then ignore it
\r
14097 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
\r
14098 if (currentPendingFormats[i].name == name)
\r
14102 currentPendingFormats.push({name : name, vars : vars});
\r
14104 // Check if it's in the other type, then remove it
\r
14105 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
\r
14106 if (otherPendingFormats[i].name == name)
\r
14107 otherPendingFormats.splice(i, 1);
\r
14110 // Pending apply or remove formats
\r
14111 if (hasPending()) {
\r
14112 ed.getDoc().execCommand('FontName', false, 'mceinline');
\r
14113 pendingFormats.lastRng = selection.getRng();
\r
14115 // IE will convert the current word
\r
14116 each(dom.select('font,span'), function(node) {
\r
14119 if (isCaretNode(node)) {
\r
14120 bookmark = selection.getBookmark();
\r
14122 selection.moveToBookmark(bookmark);
\r
14123 ed.nodeChanged();
\r
14127 // Only register listeners once if we need to
\r
14128 if (!pendingFormats.isListening && hasPending()) {
\r
14129 pendingFormats.isListening = true;
\r
14131 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
\r
14132 ed[event].addToTop(function(ed, e) {
\r
14133 // Do we have pending formats and is the selection moved has moved
\r
14134 if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
\r
14135 each(dom.select('font,span'), function(node) {
\r
14136 var textNode, rng;
\r
14138 // Look for marker
\r
14139 if (isCaretNode(node)) {
\r
14140 textNode = node.firstChild;
\r
14145 rng = dom.createRng();
\r
14146 rng.setStart(textNode, textNode.nodeValue.length);
\r
14147 rng.setEnd(textNode, textNode.nodeValue.length);
\r
14148 selection.setRng(rng);
\r
14149 ed.nodeChanged();
\r
14151 dom.remove(node);
\r
14155 // Always unbind and clear pending styles on keyup
\r
14156 if (e.type == 'keyup' || e.type == 'mouseup')
\r
14167 tinymce.onAddEditor.add(function(tinymce, ed) {
\r
14168 var filters, fontSizes, dom, settings = ed.settings;
\r
14170 if (settings.inline_styles) {
\r
14171 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
14173 function replaceWithSpan(node, styles) {
\r
14174 tinymce.each(styles, function(value, name) {
\r
14176 dom.setStyle(node, name, value);
\r
14179 dom.rename(node, 'span');
\r
14183 font : function(dom, node) {
\r
14184 replaceWithSpan(node, {
\r
14185 backgroundColor : node.style.backgroundColor,
\r
14186 color : node.color,
\r
14187 fontFamily : node.face,
\r
14188 fontSize : fontSizes[parseInt(node.size) - 1]
\r
14192 u : function(dom, node) {
\r
14193 replaceWithSpan(node, {
\r
14194 textDecoration : 'underline'
\r
14198 strike : function(dom, node) {
\r
14199 replaceWithSpan(node, {
\r
14200 textDecoration : 'line-through'
\r
14205 function convert(editor, params) {
\r
14206 dom = editor.dom;
\r
14208 if (settings.convert_fonts_to_spans) {
\r
14209 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
\r
14210 filters[node.nodeName.toLowerCase()](ed.dom, node);
\r
14215 ed.onPreProcess.add(convert);
\r
14217 ed.onInit.add(function() {
\r
14218 ed.selection.onSetContent.add(convert);
\r