2 var whiteSpaceRe = /^\s*|\s*$/g,
\r
10 releaseDate : '2010-03-10',
\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 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
\r
30 if (win.tinyMCEPreInit) {
\r
31 t.suffix = tinyMCEPreInit.suffix;
\r
32 t.baseURL = tinyMCEPreInit.base;
\r
33 t.query = tinyMCEPreInit.query;
\r
37 // Get suffix and base
\r
40 // If base element found, add that infront of baseURL
\r
41 nl = d.getElementsByTagName('base');
\r
42 for (i=0; i<nl.length; i++) {
\r
43 if (v = nl[i].href) {
\r
44 // Host only value like http://site.com or http://site.com:8008
\r
45 if (/^https?:\/\/[^\/]+$/.test(v))
\r
48 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
\r
52 function getBase(n) {
\r
53 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
\r
54 if (/_(src|dev)\.js/g.test(n.src))
\r
57 if ((p = n.src.indexOf('?')) != -1)
\r
58 t.query = n.src.substring(p + 1);
\r
60 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
\r
62 // If path to script is relative and a base href was found add that one infront
\r
63 // the src property will always be an absolute one on non IE browsers and IE 8
\r
64 // so this logic will basically only be executed on older IE versions
\r
65 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
\r
66 t.baseURL = base + t.baseURL;
\r
75 nl = d.getElementsByTagName('script');
\r
76 for (i=0; i<nl.length; i++) {
\r
82 n = d.getElementsByTagName('head')[0];
\r
84 nl = n.getElementsByTagName('script');
\r
85 for (i=0; i<nl.length; i++) {
\r
94 is : function(o, t) {
\r
96 return o !== undefined;
\r
98 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
\r
101 return typeof(o) == t;
\r
104 each : function(o, cb, s) {
\r
112 if (o.length !== undefined) {
\r
113 // Indexed arrays, needed for Safari
\r
114 for (n=0, l = o.length; n < l; n++) {
\r
115 if (cb.call(s, o[n], n, o) === false)
\r
121 if (o.hasOwnProperty(n)) {
\r
122 if (cb.call(s, o[n], n, o) === false)
\r
132 map : function(a, f) {
\r
135 tinymce.each(a, function(v) {
\r
142 grep : function(a, f) {
\r
145 tinymce.each(a, function(v) {
\r
153 inArray : function(a, v) {
\r
157 for (i = 0, l = a.length; i < l; i++) {
\r
166 extend : function(o, e) {
\r
167 var i, l, a = arguments;
\r
169 for (i = 1, l = a.length; i < l; i++) {
\r
172 tinymce.each(e, function(v, n) {
\r
173 if (v !== undefined)
\r
182 trim : function(s) {
\r
183 return (s ? '' + s : '').replace(whiteSpaceRe, '');
\r
186 create : function(s, p) {
\r
187 var t = this, sp, ns, cn, scn, c, de = 0;
\r
189 // Parse : <prefix> <class>:<super class>
\r
190 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
\r
191 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
\r
193 // Create namespace for new class
\r
194 ns = t.createNS(s[3].replace(/\.\w+$/, ''));
\r
196 // Class already exists
\r
200 // Make pure static class
\r
201 if (s[2] == 'static') {
\r
205 this.onCreate(s[2], s[3], ns[cn]);
\r
210 // Create default constructor
\r
212 p[cn] = function() {};
\r
216 // Add constructor and methods
\r
218 t.extend(ns[cn].prototype, p);
\r
222 sp = t.resolve(s[5]).prototype;
\r
223 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
\r
225 // Extend constructor
\r
228 // Add passthrough constructor
\r
229 ns[cn] = function() {
\r
230 return sp[scn].apply(this, arguments);
\r
233 // Add inherit constructor
\r
234 ns[cn] = function() {
\r
235 this.parent = sp[scn];
\r
236 return c.apply(this, arguments);
\r
239 ns[cn].prototype[cn] = ns[cn];
\r
241 // Add super methods
\r
242 t.each(sp, function(f, n) {
\r
243 ns[cn].prototype[n] = sp[n];
\r
246 // Add overridden methods
\r
247 t.each(p, function(f, n) {
\r
248 // Extend methods if needed
\r
250 ns[cn].prototype[n] = function() {
\r
251 this.parent = sp[n];
\r
252 return f.apply(this, arguments);
\r
256 ns[cn].prototype[n] = f;
\r
261 // Add static methods
\r
262 t.each(p['static'], function(f, n) {
\r
267 this.onCreate(s[2], s[3], ns[cn].prototype);
\r
270 walk : function(o, f, n, s) {
\r
277 tinymce.each(o, function(o, i) {
\r
278 if (f.call(s, o, i, n) === false)
\r
281 tinymce.walk(o, f, n, s);
\r
286 createNS : function(n, o) {
\r
292 for (i=0; i<n.length; i++) {
\r
304 resolve : function(n, o) {
\r
310 for (i = 0, l = n.length; i < l; i++) {
\r
320 addUnload : function(f, s) {
\r
323 f = {func : f, scope : s || this};
\r
326 function unload() {
\r
327 var li = t.unloads, o, n;
\r
330 // Call unload handlers
\r
335 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
\r
338 // Detach unload function
\r
339 if (win.detachEvent) {
\r
340 win.detachEvent('onbeforeunload', fakeUnload);
\r
341 win.detachEvent('onunload', unload);
\r
342 } else if (win.removeEventListener)
\r
343 win.removeEventListener('unload', unload, false);
\r
345 // Destroy references
\r
346 t.unloads = o = li = w = unload = 0;
\r
348 // Run garbarge collector on IE
\r
349 if (win.CollectGarbage)
\r
354 function fakeUnload() {
\r
357 // Is there things still loading, then do some magic
\r
358 if (d.readyState == 'interactive') {
\r
360 // Prevent memory leak
\r
361 d.detachEvent('onstop', stop);
\r
363 // Call unload handler
\r
370 // Fire unload when the currently loading page is stopped
\r
372 d.attachEvent('onstop', stop);
\r
374 // Remove onstop listener after a while to prevent the unload function
\r
375 // to execute if the user presses cancel in an onbeforeunload
\r
376 // confirm dialog and then presses the browser stop button
\r
377 win.setTimeout(function() {
\r
379 d.detachEvent('onstop', stop);
\r
384 // Attach unload handler
\r
385 if (win.attachEvent) {
\r
386 win.attachEvent('onunload', unload);
\r
387 win.attachEvent('onbeforeunload', fakeUnload);
\r
388 } else if (win.addEventListener)
\r
389 win.addEventListener('unload', unload, false);
\r
391 // Setup initial unload handler array
\r
399 removeUnload : function(f) {
\r
400 var u = this.unloads, r = null;
\r
402 tinymce.each(u, function(o, i) {
\r
403 if (o && o.func == f) {
\r
413 explode : function(s, d) {
\r
414 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
\r
417 _addVer : function(u) {
\r
423 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
\r
425 if (u.indexOf('#') == -1)
\r
428 return u.replace('#', v + '#');
\r
433 // Initialize the API
\r
436 // Expose tinymce namespace to the global namespace (window)
\r
437 win.tinymce = win.tinyMCE = tinymce;
\r
441 tinymce.create('tinymce.util.Dispatcher', {
\r
445 Dispatcher : function(s) {
\r
446 this.scope = s || this;
\r
447 this.listeners = [];
\r
450 add : function(cb, s) {
\r
451 this.listeners.push({cb : cb, scope : s || this.scope});
\r
456 addToTop : function(cb, s) {
\r
457 this.listeners.unshift({cb : cb, scope : s || this.scope});
\r
462 remove : function(cb) {
\r
463 var l = this.listeners, o = null;
\r
465 tinymce.each(l, function(c, i) {
\r
476 dispatch : function() {
\r
477 var s, a = arguments, i, li = this.listeners, c;
\r
479 // Needs to be a real loop since the listener count might change while looping
\r
480 // And this is also more efficient
\r
481 for (i = 0; i<li.length; i++) {
\r
483 s = c.cb.apply(c.scope, a);
\r
495 var each = tinymce.each;
\r
497 tinymce.create('tinymce.util.URI', {
\r
498 URI : function(u, s) {
\r
499 var t = this, o, a, b;
\r
502 u = tinymce.trim(u);
\r
504 // Default settings
\r
505 s = t.settings = s || {};
\r
507 // Strange app protocol or local anchor
\r
508 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
\r
513 // Absolute path with no host, fake host and protocol
\r
514 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
\r
515 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
\r
517 // Relative path http:// or protocol relative //path
\r
518 if (!/^\w*:?\/\//.test(u))
\r
519 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
\r
521 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
\r
522 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
\r
523 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
\r
524 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
\r
527 // Zope 3 workaround, they use @@something
\r
529 s = s.replace(/\(mce_at\)/g, '@@');
\r
534 if (b = s.base_uri) {
\r
536 t.protocol = b.protocol;
\r
539 t.userInfo = b.userInfo;
\r
541 if (!t.port && t.host == 'mce_host')
\r
544 if (!t.host || t.host == 'mce_host')
\r
550 //t.path = t.path || '/';
\r
553 setPath : function(p) {
\r
556 p = /^(.*?)\/?(\w+)?$/.exec(p);
\r
558 // Update path parts
\r
560 t.directory = p[1];
\r
568 toRelative : function(u) {
\r
574 u = new tinymce.util.URI(u, {base_uri : t});
\r
576 // Not on same domain/port or protocol
\r
577 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
\r
580 o = t.toRelPath(t.path, u.path);
\r
584 o += '?' + u.query;
\r
588 o += '#' + u.anchor;
\r
593 toAbsolute : function(u, nh) {
\r
594 var u = new tinymce.util.URI(u, {base_uri : this});
\r
596 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
\r
599 toRelPath : function(base, path) {
\r
600 var items, bp = 0, out = '', i, l;
\r
603 base = base.substring(0, base.lastIndexOf('/'));
\r
604 base = base.split('/');
\r
605 items = path.split('/');
\r
607 if (base.length >= items.length) {
\r
608 for (i = 0, l = base.length; i < l; i++) {
\r
609 if (i >= items.length || base[i] != items[i]) {
\r
616 if (base.length < items.length) {
\r
617 for (i = 0, l = items.length; i < l; i++) {
\r
618 if (i >= base.length || base[i] != items[i]) {
\r
628 for (i = 0, l = base.length - (bp - 1); i < l; i++)
\r
631 for (i = bp - 1, l = items.length; i < l; i++) {
\r
633 out += "/" + items[i];
\r
641 toAbsPath : function(base, path) {
\r
642 var i, nb = 0, o = [], tr, outPath;
\r
645 tr = /\/$/.test(path) ? '/' : '';
\r
646 base = base.split('/');
\r
647 path = path.split('/');
\r
649 // Remove empty chunks
\r
650 each(base, function(k) {
\r
657 // Merge relURLParts chunks
\r
658 for (i = path.length - 1, o = []; i >= 0; i--) {
\r
659 // Ignore empty or .
\r
660 if (path[i].length == 0 || path[i] == ".")
\r
664 if (path[i] == '..') {
\r
678 i = base.length - nb;
\r
682 outPath = o.reverse().join('/');
\r
684 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
\r
686 // Add front / if it's needed
\r
687 if (outPath.indexOf('/') !== 0)
\r
688 outPath = '/' + outPath;
\r
690 // Add traling / if it's needed
\r
691 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
\r
697 getURI : function(nh) {
\r
701 if (!t.source || nh) {
\r
706 s += t.protocol + '://';
\r
709 s += t.userInfo + '@';
\r
722 s += '?' + t.query;
\r
725 s += '#' + t.anchor;
\r
736 var each = tinymce.each;
\r
738 tinymce.create('static tinymce.util.Cookie', {
\r
739 getHash : function(n) {
\r
740 var v = this.get(n), h;
\r
743 each(v.split('&'), function(v) {
\r
746 h[unescape(v[0])] = unescape(v[1]);
\r
753 setHash : function(n, v, e, p, d, s) {
\r
756 each(v, function(v, k) {
\r
757 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
\r
760 this.set(n, o, e, p, d, s);
\r
763 get : function(n) {
\r
764 var c = document.cookie, e, p = n + "=", b;
\r
770 b = c.indexOf("; " + p);
\r
780 e = c.indexOf(";", b);
\r
785 return unescape(c.substring(b + p.length, e));
\r
788 set : function(n, v, e, p, d, s) {
\r
789 document.cookie = n + "=" + escape(v) +
\r
790 ((e) ? "; expires=" + e.toGMTString() : "") +
\r
791 ((p) ? "; path=" + escape(p) : "") +
\r
792 ((d) ? "; domain=" + d : "") +
\r
793 ((s) ? "; secure" : "");
\r
796 remove : function(n, p) {
\r
797 var d = new Date();
\r
799 d.setTime(d.getTime() - 1000);
\r
801 this.set(n, '', d, p, d);
\r
806 tinymce.create('static tinymce.util.JSON', {
\r
807 serialize : function(o) {
\r
808 var i, v, s = tinymce.util.JSON.serialize, t;
\r
815 if (t == 'string') {
\r
816 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
\r
818 return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
\r
822 return '\\' + v.charAt(i + 1);
\r
824 a = b.charCodeAt().toString(16);
\r
826 return '\\u' + '0000'.substring(a.length) + a;
\r
830 if (t == 'object') {
\r
831 if (o.hasOwnProperty && o instanceof Array) {
\r
832 for (i=0, v = '['; i<o.length; i++)
\r
833 v += (i > 0 ? ',' : '') + s(o[i]);
\r
841 v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
\r
849 parse : function(s) {
\r
851 return eval('(' + s + ')');
\r
859 tinymce.create('static tinymce.util.XHR', {
\r
860 send : function(o) {
\r
861 var x, t, w = window, c = 0;
\r
863 // Default settings
\r
864 o.scope = o.scope || this;
\r
865 o.success_scope = o.success_scope || o.scope;
\r
866 o.error_scope = o.error_scope || o.scope;
\r
867 o.async = o.async === false ? false : true;
\r
868 o.data = o.data || '';
\r
874 x = new ActiveXObject(s);
\r
881 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
\r
884 if (x.overrideMimeType)
\r
885 x.overrideMimeType(o.content_type);
\r
887 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
\r
889 if (o.content_type)
\r
890 x.setRequestHeader('Content-Type', o.content_type);
\r
892 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
\r
897 if (!o.async || x.readyState == 4 || c++ > 10000) {
\r
898 if (o.success && c < 10000 && x.status == 200)
\r
899 o.success.call(o.success_scope, '' + x.responseText, x, o);
\r
901 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
\r
905 w.setTimeout(ready, 10);
\r
908 // Syncronous request
\r
912 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
\r
913 t = w.setTimeout(ready, 10);
\r
919 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
\r
921 tinymce.create('tinymce.util.JSONRequest', {
\r
922 JSONRequest : function(s) {
\r
923 this.settings = extend({
\r
928 send : function(o) {
\r
929 var ecb = o.error, scb = o.success;
\r
931 o = extend(this.settings, o);
\r
933 o.success = function(c, x) {
\r
936 if (typeof(c) == 'undefined') {
\r
938 error : 'JSON Parse error.'
\r
943 ecb.call(o.error_scope || o.scope, c.error, x);
\r
945 scb.call(o.success_scope || o.scope, c.result);
\r
948 o.error = function(ty, x) {
\r
949 ecb.call(o.error_scope || o.scope, ty, x);
\r
952 o.data = JSON.serialize({
\r
953 id : o.id || 'c' + (this.count++),
\r
958 // JSON content type for Ruby on rails. Bug: #1883287
\r
959 o.content_type = 'application/json';
\r
965 sendRPC : function(o) {
\r
966 return new tinymce.util.JSONRequest().send(o);
\r
971 (function(tinymce) {
\r
973 var each = tinymce.each,
\r
975 isWebKit = tinymce.isWebKit,
\r
976 isIE = tinymce.isIE,
\r
977 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
978 boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
\r
979 mceAttribs = makeMap('src,href,style,coords,shape'),
\r
980 encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'},
\r
981 encodeCharsRe = /[<>&\"]/g,
\r
982 simpleSelectorRe = /^([a-z0-9],?)+$/i,
\r
983 tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
\r
984 attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
\r
986 function makeMap(str) {
\r
989 str = str.split(',');
\r
990 for (i = str.length; i >= 0; i--)
\r
996 tinymce.create('tinymce.dom.DOMUtils', {
\r
1000 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
\r
1002 "for" : "htmlFor",
\r
1003 "class" : "className",
\r
1004 className : "className",
\r
1005 checked : "checked",
\r
1006 disabled : "disabled",
\r
1007 maxlength : "maxLength",
\r
1008 readonly : "readOnly",
\r
1009 selected : "selected",
\r
1016 DOMUtils : function(d, s) {
\r
1017 var t = this, globalStyle;
\r
1022 t.cssFlicker = false;
\r
1024 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat";
\r
1025 t.stdMode = d.documentMode === 8;
\r
1027 t.settings = s = tinymce.extend({
\r
1028 keep_values : false,
\r
1033 // Fix IE6SP2 flicker and check it failed for pre SP2
\r
1034 if (tinymce.isIE6) {
\r
1036 d.execCommand('BackgroundImageCache', false, true);
\r
1038 t.cssFlicker = true;
\r
1042 // Build styles list
\r
1043 if (s.valid_styles) {
\r
1046 // Convert styles into a rule list
\r
1047 each(s.valid_styles, function(value, key) {
\r
1048 t._styles[key] = tinymce.explode(value);
\r
1052 tinymce.addUnload(t.destroy, t);
\r
1055 getRoot : function() {
\r
1056 var t = this, s = t.settings;
\r
1058 return (s && t.get(s.root_element)) || t.doc.body;
\r
1061 getViewPort : function(w) {
\r
1064 w = !w ? this.win : w;
\r
1066 b = this.boxModel ? d.documentElement : d.body;
\r
1068 // Returns viewport size excluding scrollbars
\r
1070 x : w.pageXOffset || b.scrollLeft,
\r
1071 y : w.pageYOffset || b.scrollTop,
\r
1072 w : w.innerWidth || b.clientWidth,
\r
1073 h : w.innerHeight || b.clientHeight
\r
1077 getRect : function(e) {
\r
1078 var p, t = this, sr;
\r
1082 sr = t.getSize(e);
\r
1092 getSize : function(e) {
\r
1093 var t = this, w, h;
\r
1096 w = t.getStyle(e, 'width');
\r
1097 h = t.getStyle(e, 'height');
\r
1099 // Non pixel value, then force offset/clientWidth
\r
1100 if (w.indexOf('px') === -1)
\r
1103 // Non pixel value, then force offset/clientWidth
\r
1104 if (h.indexOf('px') === -1)
\r
1108 w : parseInt(w) || e.offsetWidth || e.clientWidth,
\r
1109 h : parseInt(h) || e.offsetHeight || e.clientHeight
\r
1113 getParent : function(n, f, r) {
\r
1114 return this.getParents(n, f, r, false);
\r
1117 getParents : function(n, f, r, c) {
\r
1118 var t = this, na, se = t.settings, o = [];
\r
1121 c = c === undefined;
\r
1123 if (se.strict_root)
\r
1124 r = r || t.getRoot();
\r
1126 // Wrap node name as func
\r
1127 if (is(f, 'string')) {
\r
1131 f = function(n) {return n.nodeType == 1;};
\r
1134 return t.is(n, na);
\r
1140 if (n == r || !n.nodeType || n.nodeType === 9)
\r
1153 return c ? o : null;
\r
1156 get : function(e) {
\r
1159 if (e && this.doc && typeof(e) == 'string') {
\r
1161 e = this.doc.getElementById(e);
\r
1163 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
\r
1164 if (e && e.id !== n)
\r
1165 return this.doc.getElementsByName(n)[1];
\r
1171 getNext : function(node, selector) {
\r
1172 return this._findSib(node, selector, 'nextSibling');
\r
1175 getPrev : function(node, selector) {
\r
1176 return this._findSib(node, selector, 'previousSibling');
\r
1180 select : function(pa, s) {
\r
1183 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
\r
1186 is : function(n, selector) {
\r
1189 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
\r
1190 if (n.length === undefined) {
\r
1191 // Simple all selector
\r
1192 if (selector === '*')
\r
1193 return n.nodeType == 1;
\r
1195 // Simple selector just elements
\r
1196 if (simpleSelectorRe.test(selector)) {
\r
1197 selector = selector.toLowerCase().split(/,/);
\r
1198 n = n.nodeName.toLowerCase();
\r
1200 for (i = selector.length - 1; i >= 0; i--) {
\r
1201 if (selector[i] == n)
\r
1209 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
\r
1213 add : function(p, n, a, h, c) {
\r
1216 return this.run(p, function(p) {
\r
1219 e = is(n, 'string') ? t.doc.createElement(n) : n;
\r
1220 t.setAttribs(e, a);
\r
1229 return !c ? p.appendChild(e) : e;
\r
1233 create : function(n, a, h) {
\r
1234 return this.add(this.doc.createElement(n), n, a, h, 1);
\r
1237 createHTML : function(n, a, h) {
\r
1238 var o = '', t = this, k;
\r
1243 if (a.hasOwnProperty(k))
\r
1244 o += ' ' + k + '="' + t.encode(a[k]) + '"';
\r
1247 if (tinymce.is(h))
\r
1248 return o + '>' + h + '</' + n + '>';
\r
1253 remove : function(node, keep_children) {
\r
1254 return this.run(node, function(node) {
\r
1255 var parent, child;
\r
1257 parent = node.parentNode;
\r
1262 if (keep_children) {
\r
1263 while (child = node.firstChild) {
\r
1264 // IE 8 will crash if you don't remove completely empty text nodes
\r
1265 if (child.nodeType !== 3 || child.nodeValue)
\r
1266 parent.insertBefore(child, node);
\r
1268 node.removeChild(child);
\r
1272 return parent.removeChild(node);
\r
1276 setStyle : function(n, na, v) {
\r
1279 return t.run(n, function(e) {
\r
1284 // Camelcase it, if needed
\r
1285 na = na.replace(/-(\D)/g, function(a, b){
\r
1286 return b.toUpperCase();
\r
1289 // Default px suffix on these
\r
1290 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
\r
1295 // IE specific opacity
\r
1297 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
\r
1299 if (!n.currentStyle || !n.currentStyle.hasLayout)
\r
1300 s.display = 'inline-block';
\r
1303 // Fix for older browsers
\r
1304 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
\r
1308 isIE ? s.styleFloat = v : s.cssFloat = v;
\r
1315 // Force update of the style data
\r
1316 if (t.settings.update_styles)
\r
1317 t.setAttrib(e, '_mce_style');
\r
1321 getStyle : function(n, na, c) {
\r
1328 if (this.doc.defaultView && c) {
\r
1329 // Remove camelcase
\r
1330 na = na.replace(/[A-Z]/g, function(a){
\r
1335 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
\r
1337 // Old safari might fail
\r
1342 // Camelcase it, if needed
\r
1343 na = na.replace(/-(\D)/g, function(a, b){
\r
1344 return b.toUpperCase();
\r
1347 if (na == 'float')
\r
1348 na = isIE ? 'styleFloat' : 'cssFloat';
\r
1351 if (n.currentStyle && c)
\r
1352 return n.currentStyle[na];
\r
1354 return n.style[na];
\r
1357 setStyles : function(e, o) {
\r
1358 var t = this, s = t.settings, ol;
\r
1360 ol = s.update_styles;
\r
1361 s.update_styles = 0;
\r
1363 each(o, function(v, n) {
\r
1364 t.setStyle(e, n, v);
\r
1367 // Update style info
\r
1368 s.update_styles = ol;
\r
1369 if (s.update_styles)
\r
1370 t.setAttrib(e, s.cssText);
\r
1373 setAttrib : function(e, n, v) {
\r
1376 // Whats the point
\r
1380 // Strict XML mode
\r
1381 if (t.settings.strict)
\r
1382 n = n.toLowerCase();
\r
1384 return this.run(e, function(e) {
\r
1385 var s = t.settings;
\r
1389 if (!is(v, 'string')) {
\r
1390 each(v, function(v, n) {
\r
1391 t.setStyle(e, n, v);
\r
1397 // No mce_style for elements with these since they might get resized by the user
\r
1398 if (s.keep_values) {
\r
1399 if (v && !t._isRes(v))
\r
1400 e.setAttribute('_mce_style', v, 2);
\r
1402 e.removeAttribute('_mce_style', 2);
\r
1405 e.style.cssText = v;
\r
1409 e.className = v || ''; // Fix IE null bug
\r
1414 if (s.keep_values) {
\r
1415 if (s.url_converter)
\r
1416 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
\r
1418 t.setAttrib(e, '_mce_' + n, v, 2);
\r
1424 e.setAttribute('_mce_style', v);
\r
1428 if (is(v) && v !== null && v.length !== 0)
\r
1429 e.setAttribute(n, '' + v, 2);
\r
1431 e.removeAttribute(n, 2);
\r
1435 setAttribs : function(e, o) {
\r
1438 return this.run(e, function(e) {
\r
1439 each(o, function(v, n) {
\r
1440 t.setAttrib(e, n, v);
\r
1445 getAttrib : function(e, n, dv) {
\r
1450 if (!e || e.nodeType !== 1)
\r
1456 // Try the mce variant for these
\r
1457 if (/^(src|href|style|coords|shape)$/.test(n)) {
\r
1458 v = e.getAttribute("_mce_" + n);
\r
1464 if (isIE && t.props[n]) {
\r
1465 v = e[t.props[n]];
\r
1466 v = v && v.nodeValue ? v.nodeValue : v;
\r
1470 v = e.getAttribute(n, 2);
\r
1472 // Check boolean attribs
\r
1473 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
\r
1474 if (e[t.props[n]] === true && v === '')
\r
1477 return v ? n : '';
\r
1480 // Inner input elements will override attributes on form elements
\r
1481 if (e.nodeName === "FORM" && e.getAttributeNode(n))
\r
1482 return e.getAttributeNode(n).nodeValue;
\r
1484 if (n === 'style') {
\r
1485 v = v || e.style.cssText;
\r
1488 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
\r
1490 if (t.settings.keep_values && !t._isRes(v))
\r
1491 e.setAttribute('_mce_style', v);
\r
1495 // Remove Apple and WebKit stuff
\r
1496 if (isWebKit && n === "class" && v)
\r
1497 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
\r
1499 // Handle IE issues
\r
1504 // IE returns 1 as default value
\r
1511 // IE returns +0 as default value for size
\r
1512 if (v === '+0' || v === 20 || v === 0)
\r
1529 // IE returns -1 as default value
\r
1537 // IE returns default value
\r
1538 if (v === 32768 || v === 2147483647 || v === '32768')
\r
1553 v = v.toLowerCase();
\r
1557 // IE has odd anonymous function for event attributes
\r
1558 if (n.indexOf('on') === 0 && v)
\r
1559 v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
\r
1563 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
\r
1566 getPos : function(n, ro) {
\r
1567 var t = this, x = 0, y = 0, e, d = t.doc, r;
\r
1570 ro = ro || d.body;
\r
1573 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
\r
1574 if (isIE && !t.stdMode) {
\r
1575 n = n.getBoundingClientRect();
\r
1576 e = t.boxModel ? d.documentElement : d.body;
\r
1577 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
\r
1578 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
\r
1579 n.top += t.win.self != t.win.top ? 2 : 0; // IE adds some strange extra cord if used in a frameset
\r
1581 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
\r
1585 while (r && r != ro && r.nodeType) {
\r
1586 x += r.offsetLeft || 0;
\r
1587 y += r.offsetTop || 0;
\r
1588 r = r.offsetParent;
\r
1592 while (r && r != ro && r.nodeType) {
\r
1593 x -= r.scrollLeft || 0;
\r
1594 y -= r.scrollTop || 0;
\r
1599 return {x : x, y : y};
\r
1602 parseStyle : function(st) {
\r
1603 var t = this, s = t.settings, o = {};
\r
1608 function compress(p, s, ot) {
\r
1611 // Get values and check it it needs compressing
\r
1612 t = o[p + '-top' + s];
\r
1616 r = o[p + '-right' + s];
\r
1620 b = o[p + '-bottom' + s];
\r
1624 l = o[p + '-left' + s];
\r
1630 delete o[p + '-top' + s];
\r
1631 delete o[p + '-right' + s];
\r
1632 delete o[p + '-bottom' + s];
\r
1633 delete o[p + '-left' + s];
\r
1636 function compress2(ta, a, b, c) {
\r
1652 o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
\r
1658 st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
\r
1660 each(st.split(';'), function(v) {
\r
1664 v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
\r
1665 v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
\r
1667 sv = tinymce.trim(v[1]);
\r
1668 sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
\r
1670 sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
\r
1671 return t.toHex(v);
\r
1674 if (s.url_converter) {
\r
1675 sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
\r
1676 return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
\r
1680 o[tinymce.trim(v[0]).toLowerCase()] = sv;
\r
1684 compress("border", "", "border");
\r
1685 compress("border", "-width", "border-width");
\r
1686 compress("border", "-color", "border-color");
\r
1687 compress("border", "-style", "border-style");
\r
1688 compress("padding", "", "padding");
\r
1689 compress("margin", "", "margin");
\r
1690 compress2('border', 'border-width', 'border-style', 'border-color');
\r
1693 // Remove pointless border
\r
1694 if (o.border == 'medium none')
\r
1701 serializeStyle : function(o, name) {
\r
1702 var t = this, s = '';
\r
1704 function add(v, k) {
\r
1706 // Remove browser specific styles like -moz- or -webkit-
\r
1707 if (k.indexOf('-') === 0)
\r
1711 case 'font-weight':
\r
1712 // Opera will output bold as 700
\r
1719 case 'background-color':
\r
1720 v = v.toLowerCase();
\r
1724 s += (s ? ' ' : '') + k + ': ' + v + ';';
\r
1728 // Validate style output
\r
1729 if (name && t._styles) {
\r
1730 each(t._styles['*'], function(name) {
\r
1731 add(o[name], name);
\r
1734 each(t._styles[name.toLowerCase()], function(name) {
\r
1735 add(o[name], name);
\r
1743 loadCSS : function(u) {
\r
1744 var t = this, d = t.doc, head;
\r
1749 head = t.select('head')[0];
\r
1751 each(u.split(','), function(u) {
\r
1757 t.files[u] = true;
\r
1758 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
\r
1760 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
\r
1761 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
\r
1762 // It's ugly but it seems to work fine.
\r
1763 if (isIE && d.documentMode) {
\r
1764 link.onload = function() {
\r
1766 link.onload = null;
\r
1770 head.appendChild(link);
\r
1774 addClass : function(e, c) {
\r
1775 return this.run(e, function(e) {
\r
1781 if (this.hasClass(e, c))
\r
1782 return e.className;
\r
1784 o = this.removeClass(e, c);
\r
1786 return e.className = (o != '' ? (o + ' ') : '') + c;
\r
1790 removeClass : function(e, c) {
\r
1793 return t.run(e, function(e) {
\r
1796 if (t.hasClass(e, c)) {
\r
1798 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
\r
1800 v = e.className.replace(re, ' ');
\r
1801 v = tinymce.trim(v != ' ' ? v : '');
\r
1805 // Empty class attr
\r
1807 e.removeAttribute('class');
\r
1812 return e.className;
\r
1816 hasClass : function(n, c) {
\r
1822 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
\r
1825 show : function(e) {
\r
1826 return this.setStyle(e, 'display', 'block');
\r
1829 hide : function(e) {
\r
1830 return this.setStyle(e, 'display', 'none');
\r
1833 isHidden : function(e) {
\r
1836 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
\r
1839 uniqueId : function(p) {
\r
1840 return (!p ? 'mce_' : p) + (this.counter++);
\r
1843 setHTML : function(e, h) {
\r
1846 return this.run(e, function(e) {
\r
1847 var x, i, nl, n, p, x;
\r
1849 h = t.processHTML(h);
\r
1853 // Remove all child nodes
\r
1854 while (e.firstChild)
\r
1855 e.firstChild.removeNode();
\r
1858 // IE will remove comments from the beginning
\r
1859 // unless you padd the contents with something
\r
1860 e.innerHTML = '<br />' + h;
\r
1861 e.removeChild(e.firstChild);
\r
1863 // 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
1864 // This seems to fix this problem
\r
1866 // Create new div with HTML contents and a BR infront to keep comments
\r
1867 x = t.create('div');
\r
1868 x.innerHTML = '<br />' + h;
\r
1870 // Add all children from div to target
\r
1871 each (x.childNodes, function(n, i) {
\r
1872 // Skip br element
\r
1879 // IE has a serious bug when it comes to paragraphs it can produce an invalid
\r
1880 // DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
\r
1881 // It seems to be that IE doesn't like a root block element placed inside another root block element
\r
1882 if (t.settings.fix_ie_paragraphs)
\r
1883 h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true"> </p>');
\r
1887 if (t.settings.fix_ie_paragraphs) {
\r
1888 // Check for odd paragraphs this is a sign of a broken DOM
\r
1889 nl = e.getElementsByTagName("p");
\r
1890 for (i = nl.length - 1, x = 0; i >= 0; i--) {
\r
1893 if (!n.hasChildNodes()) {
\r
1894 if (!n._mce_keep) {
\r
1895 x = 1; // Is broken
\r
1899 n.removeAttribute('_mce_keep');
\r
1904 // Time to fix the madness IE left us
\r
1906 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
\r
1907 // after we use innerHTML we can fix the DOM tree
\r
1908 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
\r
1909 h = h.replace(/<\/p>/g, '</div>');
\r
1911 // Set the new HTML with DIVs
\r
1914 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
\r
1915 // This is needed since IE has a annoying bug see above for details
\r
1916 // This is a slow process but it has to be done. :(
\r
1917 if (t.settings.fix_ie_paragraphs) {
\r
1918 nl = e.getElementsByTagName("DIV");
\r
1919 for (i = nl.length - 1; i >= 0; i--) {
\r
1922 // Is it a temp div
\r
1924 // Create new paragraph
\r
1925 p = t.doc.createElement('p');
\r
1927 // Copy all attributes
\r
1928 n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
\r
1931 if (b !== '_mce_tmp') {
\r
1932 v = n.getAttribute(b);
\r
1934 if (!v && b === 'class')
\r
1937 p.setAttribute(b, v);
\r
1941 // Append all children to new paragraph
\r
1942 for (x = 0; x<n.childNodes.length; x++)
\r
1943 p.appendChild(n.childNodes[x].cloneNode(true));
\r
1945 // Replace div with new paragraph
\r
1958 processHTML : function(h) {
\r
1959 var t = this, s = t.settings, codeBlocks = [];
\r
1961 if (!s.process_html)
\r
1965 h = h.replace(/'/g, '''); // IE can't handle apos
\r
1966 h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
\r
1969 // Fix some issues
\r
1970 h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
\r
1972 // Store away src and href in _mce_src and mce_href since browsers mess them up
\r
1973 if (s.keep_values) {
\r
1974 // Wrap scripts and styles in comments for serialization purposes
\r
1975 if (/<script|noscript|style/i.test(h)) {
\r
1976 function trim(s) {
\r
1977 // Remove prefix and suffix code for element
\r
1978 s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
\r
1979 s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
\r
1980 s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
\r
1981 s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
\r
1986 // Wrap the script contents in CDATA and keep them from executing
\r
1987 h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
\r
1988 // Force type attribute
\r
1990 attribs = ' type="text/javascript"';
\r
1992 // Convert the src attribute of the scripts
\r
1993 attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
\r
1994 if (s.url_converter)
\r
1995 url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
\r
1997 return '_mce_src="' + url + '"';
\r
2000 // Wrap text contents
\r
2001 if (tinymce.trim(text)) {
\r
2002 codeBlocks.push(trim(text));
\r
2003 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
\r
2006 return '<mce:script' + attribs + '>' + text + '</mce:script>';
\r
2009 // Wrap style elements
\r
2010 h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
\r
2011 // Wrap text contents
\r
2013 codeBlocks.push(trim(text));
\r
2014 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
\r
2017 return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
\r
2020 // Wrap noscript elements
\r
2021 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
2022 return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '--') + '--></mce:noscript>';
\r
2026 h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
\r
2028 // This function processes the attributes in the HTML string to force boolean
\r
2029 // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
\r
2030 function processTags(html) {
\r
2031 return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
\r
2032 return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
\r
2035 name = name.toLowerCase();
\r
2036 value = value || val2 || val3 || "";
\r
2038 // Treat boolean attributes
\r
2039 if (boolAttrs[name]) {
\r
2040 // false or 0 is treated as a missing attribute
\r
2041 if (value === 'false' || value === '0')
\r
2044 return name + '="' + name + '"';
\r
2047 // Is attribute one that needs special treatment
\r
2048 if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
\r
2049 mceValue = t.decode(value);
\r
2051 // Convert URLs to relative/absolute ones
\r
2052 if (s.url_converter && (name == "src" || name == "href"))
\r
2053 mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
\r
2055 // Process styles lowercases them and compresses them
\r
2056 if (name == 'style')
\r
2057 mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
\r
2059 return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
\r
2067 h = processTags(h);
\r
2069 // Restore script blocks
\r
2070 h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
\r
2071 return codeBlocks[idx];
\r
2078 getOuterHTML : function(e) {
\r
2086 if (e.outerHTML !== undefined)
\r
2087 return e.outerHTML;
\r
2089 d = (e.ownerDocument || this.doc).createElement("body");
\r
2090 d.appendChild(e.cloneNode(true));
\r
2092 return d.innerHTML;
\r
2095 setOuterHTML : function(e, h, d) {
\r
2098 function setHTML(e, h, d) {
\r
2101 tp = d.createElement("body");
\r
2106 t.insertAfter(n.cloneNode(true), e);
\r
2107 n = n.previousSibling;
\r
2113 return this.run(e, function(e) {
\r
2116 // Only set HTML on elements
\r
2117 if (e.nodeType == 1) {
\r
2118 d = d || e.ownerDocument || t.doc;
\r
2122 // Try outerHTML for IE it sometimes produces an unknown runtime error
\r
2123 if (isIE && e.nodeType == 1)
\r
2128 // Fix for unknown runtime error
\r
2137 decode : function(s) {
\r
2140 // Look for entities to decode
\r
2141 if (/&[\w#]+;/.test(s)) {
\r
2142 // Decode the entities using a div element not super efficient but less code
\r
2143 e = this.doc.createElement("div");
\r
2151 } while (n = n.nextSibling);
\r
2160 encode : function(str) {
\r
2161 return ('' + str).replace(encodeCharsRe, function(chr) {
\r
2162 return encodedChars[chr];
\r
2166 insertAfter : function(node, reference_node) {
\r
2167 reference_node = this.get(reference_node);
\r
2169 return this.run(node, function(node) {
\r
2170 var parent, nextSibling;
\r
2172 parent = reference_node.parentNode;
\r
2173 nextSibling = reference_node.nextSibling;
\r
2176 parent.insertBefore(node, nextSibling);
\r
2178 parent.appendChild(node);
\r
2184 isBlock : function(n) {
\r
2185 if (n.nodeType && n.nodeType !== 1)
\r
2188 n = n.nodeName || n;
\r
2190 return blockRe.test(n);
\r
2193 replace : function(n, o, k) {
\r
2196 if (is(o, 'array'))
\r
2197 n = n.cloneNode(true);
\r
2199 return t.run(o, function(o) {
\r
2201 each(tinymce.grep(o.childNodes), function(c) {
\r
2206 return o.parentNode.replaceChild(n, o);
\r
2210 rename : function(elm, name) {
\r
2211 var t = this, newElm;
\r
2213 if (elm.nodeName != name.toUpperCase()) {
\r
2214 // Rename block element
\r
2215 newElm = t.create(name);
\r
2217 // Copy attribs to new block
\r
2218 each(t.getAttribs(elm), function(attr_node) {
\r
2219 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
\r
2223 t.replace(newElm, elm, 1);
\r
2226 return newElm || elm;
\r
2229 findCommonAncestor : function(a, b) {
\r
2235 while (pe && ps != pe)
\r
2236 pe = pe.parentNode;
\r
2241 ps = ps.parentNode;
\r
2244 if (!ps && a.ownerDocument)
\r
2245 return a.ownerDocument.documentElement;
\r
2250 toHex : function(s) {
\r
2251 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
\r
2254 s = parseInt(s).toString(16);
\r
2256 return s.length > 1 ? s : '0' + s; // 0 -> 00
\r
2260 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
\r
2268 getClasses : function() {
\r
2269 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
\r
2274 function addClasses(s) {
\r
2275 // IE style imports
\r
2276 each(s.imports, function(r) {
\r
2280 each(s.cssRules || s.rules, function(r) {
\r
2281 // Real type or fake it on IE
\r
2282 switch (r.type || 1) {
\r
2285 if (r.selectorText) {
\r
2286 each(r.selectorText.split(','), function(v) {
\r
2287 v = v.replace(/^\s*|\s*$|^\s\./g, "");
\r
2289 // Is internal or it doesn't contain a class
\r
2290 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
\r
2293 // Remove everything but class name
\r
2295 v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
\r
2298 if (f && !(v = f(v, ov)))
\r
2302 cl.push({'class' : v});
\r
2311 addClasses(r.styleSheet);
\r
2318 each(t.doc.styleSheets, addClasses);
\r
2323 if (cl.length > 0)
\r
2329 run : function(e, f, s) {
\r
2332 if (t.doc && typeof(e) === 'string')
\r
2339 if (!e.nodeType && (e.length || e.length === 0)) {
\r
2342 each(e, function(e, i) {
\r
2344 if (typeof(e) == 'string')
\r
2345 e = t.doc.getElementById(e);
\r
2347 o.push(f.call(s, e, i));
\r
2354 return f.call(s, e);
\r
2357 getAttribs : function(n) {
\r
2368 // Object will throw exception in IE
\r
2369 if (n.nodeName == 'OBJECT')
\r
2370 return n.attributes;
\r
2372 // IE doesn't keep the selected attribute if you clone option elements
\r
2373 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
\r
2374 o.push({specified : 1, nodeName : 'selected'});
\r
2376 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
\r
2377 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
\r
2378 o.push({specified : 1, nodeName : a});
\r
2384 return n.attributes;
\r
2387 destroy : function(s) {
\r
2391 t.events.destroy();
\r
2393 t.win = t.doc = t.root = t.events = null;
\r
2395 // Manual destroy then remove unload handler
\r
2397 tinymce.removeUnload(t.destroy);
\r
2400 createRng : function() {
\r
2403 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
\r
2406 nodeIndex : function(node, normalized) {
\r
2407 var idx = 0, lastNode, nodeType;
\r
2410 for (node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
\r
2411 nodeType = node.nodeType;
\r
2413 // Text nodes needs special treatment if the normalized argument is specified
\r
2414 if (normalized && nodeType == 3) {
\r
2415 // Checks if the current node has contents and that the last node is a non text node or empty
\r
2416 if (node.nodeValue.length > 0 && (lastNode.nodeType != nodeType || lastNode.nodeValue.length === 0))
\r
2428 split : function(pe, e, re) {
\r
2429 var t = this, r = t.createRng(), bef, aft, pa;
\r
2431 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
\r
2432 // but we don't want that in our code since it serves no purpose for the end user
\r
2433 // For example if this is chopped:
\r
2434 // <p>text 1<span><b>CHOP</b></span>text 2</p>
\r
2436 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
\r
2437 // this function will then trim of empty edges and produce:
\r
2438 // <p>text 1</p><b>CHOP</b><p>text 2</p>
\r
2439 function trim(node) {
\r
2440 var i, children = node.childNodes;
\r
2442 if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
\r
2445 for (i = children.length - 1; i >= 0; i--)
\r
2446 trim(children[i]);
\r
2448 if (node.nodeType != 9) {
\r
2449 // Keep non whitespace text nodes
\r
2450 if (node.nodeType == 3 && node.nodeValue.length > 0)
\r
2453 if (node.nodeType == 1) {
\r
2454 // If the only child is a bookmark then move it up
\r
2455 children = node.childNodes;
\r
2456 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
\r
2457 node.parentNode.insertBefore(children[0], node);
\r
2459 // Keep non empty elements or img, hr etc
\r
2460 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
\r
2471 // Get before chunk
\r
2472 r.setStart(pe.parentNode, t.nodeIndex(pe));
\r
2473 r.setEnd(e.parentNode, t.nodeIndex(e));
\r
2474 bef = r.extractContents();
\r
2476 // Get after chunk
\r
2477 r = t.createRng();
\r
2478 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
\r
2479 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
\r
2480 aft = r.extractContents();
\r
2482 // Insert before chunk
\r
2483 pa = pe.parentNode;
\r
2484 pa.insertBefore(trim(bef), pe);
\r
2486 // Insert middle chunk
\r
2488 pa.replaceChild(re, e);
\r
2490 pa.insertBefore(e, pe);
\r
2492 // Insert after chunk
\r
2493 pa.insertBefore(trim(aft), pe);
\r
2500 bind : function(target, name, func, scope) {
\r
2504 t.events = new tinymce.dom.EventUtils();
\r
2506 return t.events.add(target, name, func, scope || this);
\r
2509 unbind : function(target, name, func) {
\r
2513 t.events = new tinymce.dom.EventUtils();
\r
2515 return t.events.remove(target, name, func);
\r
2519 _findSib : function(node, selector, name) {
\r
2520 var t = this, f = selector;
\r
2523 // If expression make a function of it using is
\r
2524 if (is(f, 'string')) {
\r
2525 f = function(node) {
\r
2526 return t.is(node, selector);
\r
2530 // Loop all siblings
\r
2531 for (node = node[name]; node; node = node[name]) {
\r
2540 _isRes : function(c) {
\r
2541 // Is live resizble element
\r
2542 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
\r
2546 walk : function(n, f, s) {
\r
2547 var d = this.doc, w;
\r
2549 if (d.createTreeWalker) {
\r
2550 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
\r
2552 while ((n = w.nextNode()) != null)
\r
2553 f.call(s || this, n);
\r
2555 tinymce.walk(n, f, 'childNodes', s);
\r
2560 toRGB : function(s) {
\r
2561 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
\r
2564 // #FFF -> #FFFFFF
\r
2566 c[3] = c[2] = c[1];
\r
2568 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
\r
2576 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
\r
2580 // Range constructor
\r
2581 function Range(dom) {
\r
2589 START_OFFSET = 'startOffset',
\r
2590 START_CONTAINER = 'startContainer',
\r
2591 END_CONTAINER = 'endContainer',
\r
2592 END_OFFSET = 'endOffset',
\r
2593 extend = tinymce.extend,
\r
2594 nodeIndex = dom.nodeIndex;
\r
2598 startContainer : doc,
\r
2600 endContainer : doc,
\r
2603 commonAncestorContainer : doc,
\r
2605 // Range constants
\r
2606 START_TO_START : 0,
\r
2612 setStart : setStart,
\r
2614 setStartBefore : setStartBefore,
\r
2615 setStartAfter : setStartAfter,
\r
2616 setEndBefore : setEndBefore,
\r
2617 setEndAfter : setEndAfter,
\r
2618 collapse : collapse,
\r
2619 selectNode : selectNode,
\r
2620 selectNodeContents : selectNodeContents,
\r
2621 compareBoundaryPoints : compareBoundaryPoints,
\r
2622 deleteContents : deleteContents,
\r
2623 extractContents : extractContents,
\r
2624 cloneContents : cloneContents,
\r
2625 insertNode : insertNode,
\r
2626 surroundContents : surroundContents,
\r
2627 cloneRange : cloneRange
\r
2630 function setStart(n, o) {
\r
2631 _setEndPoint(TRUE, n, o);
\r
2634 function setEnd(n, o) {
\r
2635 _setEndPoint(FALSE, n, o);
\r
2638 function setStartBefore(n) {
\r
2639 setStart(n.parentNode, nodeIndex(n));
\r
2642 function setStartAfter(n) {
\r
2643 setStart(n.parentNode, nodeIndex(n) + 1);
\r
2646 function setEndBefore(n) {
\r
2647 setEnd(n.parentNode, nodeIndex(n));
\r
2650 function setEndAfter(n) {
\r
2651 setEnd(n.parentNode, nodeIndex(n) + 1);
\r
2654 function collapse(ts) {
\r
2656 t[END_CONTAINER] = t[START_CONTAINER];
\r
2657 t[END_OFFSET] = t[START_OFFSET];
\r
2659 t[START_CONTAINER] = t[END_CONTAINER];
\r
2660 t[START_OFFSET] = t[END_OFFSET];
\r
2663 t.collapsed = TRUE;
\r
2666 function selectNode(n) {
\r
2667 setStartBefore(n);
\r
2671 function selectNodeContents(n) {
\r
2673 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
\r
2676 function compareBoundaryPoints(h, r) {
\r
2677 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
\r
2679 // Check START_TO_START
\r
2681 return _compareBoundaryPoints(sc, so, sc, so);
\r
2683 // Check START_TO_END
\r
2685 return _compareBoundaryPoints(sc, so, ec, eo);
\r
2687 // Check END_TO_END
\r
2689 return _compareBoundaryPoints(ec, eo, ec, eo);
\r
2691 // Check END_TO_START
\r
2693 return _compareBoundaryPoints(ec, eo, sc, so);
\r
2696 function deleteContents() {
\r
2697 _traverse(DELETE);
\r
2700 function extractContents() {
\r
2701 return _traverse(EXTRACT);
\r
2704 function cloneContents() {
\r
2705 return _traverse(CLONE);
\r
2708 function insertNode(n) {
\r
2709 var startContainer = this[START_CONTAINER],
\r
2710 startOffset = this[START_OFFSET], nn, o;
\r
2712 // Node is TEXT_NODE or CDATA
\r
2713 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
\r
2714 if (!startOffset) {
\r
2715 // At the start of text
\r
2716 startContainer.parentNode.insertBefore(n, startContainer);
\r
2717 } else if (startOffset >= startContainer.nodeValue.length) {
\r
2718 // At the end of text
\r
2719 dom.insertAfter(n, startContainer);
\r
2721 // Middle, need to split
\r
2722 nn = startContainer.splitText(startOffset);
\r
2723 startContainer.parentNode.insertBefore(n, nn);
\r
2726 // Insert element node
\r
2727 if (startContainer.childNodes.length > 0)
\r
2728 o = startContainer.childNodes[startOffset];
\r
2731 startContainer.insertBefore(n, o);
\r
2733 startContainer.appendChild(n);
\r
2737 function surroundContents(n) {
\r
2738 var f = t.extractContents();
\r
2745 function cloneRange() {
\r
2746 return extend(new Range(dom), {
\r
2747 startContainer : t[START_CONTAINER],
\r
2748 startOffset : t[START_OFFSET],
\r
2749 endContainer : t[END_CONTAINER],
\r
2750 endOffset : t[END_OFFSET],
\r
2751 collapsed : t.collapsed,
\r
2752 commonAncestorContainer : t.commonAncestorContainer
\r
2756 // Private methods
\r
2758 function _getSelectedNode(container, offset) {
\r
2761 if (container.nodeType == 3 /* TEXT_NODE */)
\r
2767 child = container.firstChild;
\r
2768 while (child && offset > 0) {
\r
2770 child = child.nextSibling;
\r
2779 function _isCollapsed() {
\r
2780 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
\r
2783 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
\r
2784 var c, offsetC, n, cmnRoot, childA, childB;
\r
2786 // In the first case the boundary-points have the same container. A is before B
\r
2787 // if its offset is less than the offset of B, A is equal to B if its offset is
\r
2788 // equal to the offset of B, and A is after B if its offset is greater than the
\r
2790 if (containerA == containerB) {
\r
2791 if (offsetA == offsetB)
\r
2792 return 0; // equal
\r
2794 if (offsetA < offsetB)
\r
2795 return -1; // before
\r
2797 return 1; // after
\r
2800 // In the second case a child node C of the container of A is an ancestor
\r
2801 // container of B. In this case, A is before B if the offset of A is less than or
\r
2802 // equal to the index of the child node C and A is after B otherwise.
\r
2804 while (c && c.parentNode != containerA)
\r
2809 n = containerA.firstChild;
\r
2811 while (n != c && offsetC < offsetA) {
\r
2813 n = n.nextSibling;
\r
2816 if (offsetA <= offsetC)
\r
2817 return -1; // before
\r
2819 return 1; // after
\r
2822 // In the third case a child node C of the container of B is an ancestor container
\r
2823 // of A. In this case, A is before B if the index of the child node C is less than
\r
2824 // the offset of B and A is after B otherwise.
\r
2826 while (c && c.parentNode != containerB) {
\r
2832 n = containerB.firstChild;
\r
2834 while (n != c && offsetC < offsetB) {
\r
2836 n = n.nextSibling;
\r
2839 if (offsetC < offsetB)
\r
2840 return -1; // before
\r
2842 return 1; // after
\r
2845 // In the fourth case, none of three other cases hold: the containers of A and B
\r
2846 // are siblings or descendants of sibling nodes. In this case, A is before B if
\r
2847 // the container of A is before the container of B in a pre-order traversal of the
\r
2848 // Ranges' context tree and A is after B otherwise.
\r
2849 cmnRoot = dom.findCommonAncestor(containerA, containerB);
\r
2850 childA = containerA;
\r
2852 while (childA && childA.parentNode != cmnRoot)
\r
2853 childA = childA.parentNode;
\r
2858 childB = containerB;
\r
2859 while (childB && childB.parentNode != cmnRoot)
\r
2860 childB = childB.parentNode;
\r
2865 if (childA == childB)
\r
2866 return 0; // equal
\r
2868 n = cmnRoot.firstChild;
\r
2871 return -1; // before
\r
2874 return 1; // after
\r
2876 n = n.nextSibling;
\r
2880 function _setEndPoint(st, n, o) {
\r
2884 t[START_CONTAINER] = n;
\r
2885 t[START_OFFSET] = o;
\r
2887 t[END_CONTAINER] = n;
\r
2888 t[END_OFFSET] = o;
\r
2891 // If one boundary-point of a Range is set to have a root container
\r
2892 // other than the current one for the Range, the Range is collapsed to
\r
2893 // the new position. This enforces the restriction that both boundary-
\r
2894 // points of a Range must have the same root container.
\r
2895 ec = t[END_CONTAINER];
\r
2896 while (ec.parentNode)
\r
2897 ec = ec.parentNode;
\r
2899 sc = t[START_CONTAINER];
\r
2900 while (sc.parentNode)
\r
2901 sc = sc.parentNode;
\r
2904 // The start position of a Range is guaranteed to never be after the
\r
2905 // end position. To enforce this restriction, if the start is set to
\r
2906 // be at a position after the end, the Range is collapsed to that
\r
2908 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
\r
2913 t.collapsed = _isCollapsed();
\r
2914 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
\r
2917 function _traverse(how) {
\r
2918 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
\r
2920 if (t[START_CONTAINER] == t[END_CONTAINER])
\r
2921 return _traverseSameContainer(how);
\r
2923 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
2924 if (p == t[START_CONTAINER])
\r
2925 return _traverseCommonStartContainer(c, how);
\r
2927 ++endContainerDepth;
\r
2930 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
2931 if (p == t[END_CONTAINER])
\r
2932 return _traverseCommonEndContainer(c, how);
\r
2934 ++startContainerDepth;
\r
2937 depthDiff = startContainerDepth - endContainerDepth;
\r
2939 startNode = t[START_CONTAINER];
\r
2940 while (depthDiff > 0) {
\r
2941 startNode = startNode.parentNode;
\r
2945 endNode = t[END_CONTAINER];
\r
2946 while (depthDiff < 0) {
\r
2947 endNode = endNode.parentNode;
\r
2951 // ascend the ancestor hierarchy until we have a common parent.
\r
2952 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
\r
2957 return _traverseCommonAncestors(startNode, endNode, how);
\r
2960 function _traverseSameContainer(how) {
\r
2961 var frag, s, sub, n, cnt, sibling, xferNode;
\r
2963 if (how != DELETE)
\r
2964 frag = doc.createDocumentFragment();
\r
2966 // If selection is empty, just return the fragment
\r
2967 if (t[START_OFFSET] == t[END_OFFSET])
\r
2970 // Text node needs special case handling
\r
2971 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
\r
2972 // get the substring
\r
2973 s = t[START_CONTAINER].nodeValue;
\r
2974 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
\r
2976 // set the original text node to its new value
\r
2977 if (how != CLONE) {
\r
2978 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
\r
2980 // Nothing is partially selected, so collapse to start point
\r
2984 if (how == DELETE)
\r
2987 frag.appendChild(doc.createTextNode(sub));
\r
2991 // Copy nodes between the start/end offsets.
\r
2992 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
\r
2993 cnt = t[END_OFFSET] - t[START_OFFSET];
\r
2996 sibling = n.nextSibling;
\r
2997 xferNode = _traverseFullySelected(n, how);
\r
3000 frag.appendChild( xferNode );
\r
3006 // Nothing is partially selected, so collapse to start point
\r
3013 function _traverseCommonStartContainer(endAncestor, how) {
\r
3014 var frag, n, endIdx, cnt, sibling, xferNode;
\r
3016 if (how != DELETE)
\r
3017 frag = doc.createDocumentFragment();
\r
3019 n = _traverseRightBoundary(endAncestor, how);
\r
3022 frag.appendChild(n);
\r
3024 endIdx = nodeIndex(endAncestor);
\r
3025 cnt = endIdx - t[START_OFFSET];
\r
3028 // Collapse to just before the endAncestor, which
\r
3029 // is partially selected.
\r
3030 if (how != CLONE) {
\r
3031 t.setEndBefore(endAncestor);
\r
3032 t.collapse(FALSE);
\r
3038 n = endAncestor.previousSibling;
\r
3040 sibling = n.previousSibling;
\r
3041 xferNode = _traverseFullySelected(n, how);
\r
3044 frag.insertBefore(xferNode, frag.firstChild);
\r
3050 // Collapse to just before the endAncestor, which
\r
3051 // is partially selected.
\r
3052 if (how != CLONE) {
\r
3053 t.setEndBefore(endAncestor);
\r
3054 t.collapse(FALSE);
\r
3060 function _traverseCommonEndContainer(startAncestor, how) {
\r
3061 var frag, startIdx, n, cnt, sibling, xferNode;
\r
3063 if (how != DELETE)
\r
3064 frag = doc.createDocumentFragment();
\r
3066 n = _traverseLeftBoundary(startAncestor, how);
\r
3068 frag.appendChild(n);
\r
3070 startIdx = nodeIndex(startAncestor);
\r
3071 ++startIdx; // Because we already traversed it....
\r
3073 cnt = t[END_OFFSET] - startIdx;
\r
3074 n = startAncestor.nextSibling;
\r
3076 sibling = n.nextSibling;
\r
3077 xferNode = _traverseFullySelected(n, how);
\r
3080 frag.appendChild(xferNode);
\r
3086 if (how != CLONE) {
\r
3087 t.setStartAfter(startAncestor);
\r
3094 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
\r
3095 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
\r
3097 if (how != DELETE)
\r
3098 frag = doc.createDocumentFragment();
\r
3100 n = _traverseLeftBoundary(startAncestor, how);
\r
3102 frag.appendChild(n);
\r
3104 commonParent = startAncestor.parentNode;
\r
3105 startOffset = nodeIndex(startAncestor);
\r
3106 endOffset = nodeIndex(endAncestor);
\r
3109 cnt = endOffset - startOffset;
\r
3110 sibling = startAncestor.nextSibling;
\r
3113 nextSibling = sibling.nextSibling;
\r
3114 n = _traverseFullySelected(sibling, how);
\r
3117 frag.appendChild(n);
\r
3119 sibling = nextSibling;
\r
3123 n = _traverseRightBoundary(endAncestor, how);
\r
3126 frag.appendChild(n);
\r
3128 if (how != CLONE) {
\r
3129 t.setStartAfter(startAncestor);
\r
3136 function _traverseRightBoundary(root, how) {
\r
3137 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
\r
3140 return _traverseNode(next, isFullySelected, FALSE, how);
\r
3142 parent = next.parentNode;
\r
3143 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3147 prevSibling = next.previousSibling;
\r
3148 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
\r
3150 if (how != DELETE)
\r
3151 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
\r
3153 isFullySelected = TRUE;
\r
3154 next = prevSibling;
\r
3157 if (parent == root)
\r
3158 return clonedParent;
\r
3160 next = parent.previousSibling;
\r
3161 parent = parent.parentNode;
\r
3163 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3165 if (how != DELETE)
\r
3166 clonedGrandParent.appendChild(clonedParent);
\r
3168 clonedParent = clonedGrandParent;
\r
3172 function _traverseLeftBoundary(root, how) {
\r
3173 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
\r
3176 return _traverseNode(next, isFullySelected, TRUE, how);
\r
3178 parent = next.parentNode;
\r
3179 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3183 nextSibling = next.nextSibling;
\r
3184 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
\r
3186 if (how != DELETE)
\r
3187 clonedParent.appendChild(clonedChild);
\r
3189 isFullySelected = TRUE;
\r
3190 next = nextSibling;
\r
3193 if (parent == root)
\r
3194 return clonedParent;
\r
3196 next = parent.nextSibling;
\r
3197 parent = parent.parentNode;
\r
3199 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3201 if (how != DELETE)
\r
3202 clonedGrandParent.appendChild(clonedParent);
\r
3204 clonedParent = clonedGrandParent;
\r
3208 function _traverseNode(n, isFullySelected, isLeft, how) {
\r
3209 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
\r
3211 if (isFullySelected)
\r
3212 return _traverseFullySelected(n, how);
\r
3214 if (n.nodeType == 3 /* TEXT_NODE */) {
\r
3215 txtValue = n.nodeValue;
\r
3218 offset = t[START_OFFSET];
\r
3219 newNodeValue = txtValue.substring(offset);
\r
3220 oldNodeValue = txtValue.substring(0, offset);
\r
3222 offset = t[END_OFFSET];
\r
3223 newNodeValue = txtValue.substring(0, offset);
\r
3224 oldNodeValue = txtValue.substring(offset);
\r
3228 n.nodeValue = oldNodeValue;
\r
3230 if (how == DELETE)
\r
3233 newNode = n.cloneNode(FALSE);
\r
3234 newNode.nodeValue = newNodeValue;
\r
3239 if (how == DELETE)
\r
3242 return n.cloneNode(FALSE);
\r
3245 function _traverseFullySelected(n, how) {
\r
3246 if (how != DELETE)
\r
3247 return how == CLONE ? n.cloneNode(TRUE) : n;
\r
3249 n.parentNode.removeChild(n);
\r
3257 function Selection(selection) {
\r
3258 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
\r
3260 // Compares two IE specific ranges to see if they are different
\r
3261 // this method is useful when invalidating the cached selection range
\r
3262 function compareRanges(rng1, rng2) {
\r
3263 if (rng1 && rng2) {
\r
3264 // Both are control ranges and the selected element matches
\r
3265 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
\r
3268 // Both are text ranges and the range matches
\r
3269 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
\r
3270 // IE will say that the range is equal then produce an invalid argument exception
\r
3271 // if you perform specific operations in a keyup event. For example Ctrl+Del.
\r
3272 // This hack will invalidate the range cache if the exception occurs
\r
3274 // Try accessing nextSibling will producer an invalid argument some times
\r
3275 range.startContainer.nextSibling;
\r
3286 // Returns a W3C DOM compatible range object by using the IE Range API
\r
3287 function getRange() {
\r
3288 var ieRange = selection.getRng(), domRange = dom.createRng(), ieRange2, element, collapsed, isMerged;
\r
3290 // If selection is outside the current document just return an empty range
\r
3291 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
\r
3292 if (element.ownerDocument != dom.doc)
\r
3295 // Handle control selection or text selection of a image
\r
3296 if (ieRange.item || !element.hasChildNodes()) {
\r
3297 domRange.setStart(element.parentNode, dom.nodeIndex(element));
\r
3298 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
\r
3303 // Duplicare IE selection range and check if the range is collapsed
\r
3304 ieRange2 = ieRange.duplicate();
\r
3305 collapsed = selection.isCollapsed();
\r
3307 // Insert invisible start marker
\r
3308 ieRange.collapse();
\r
3309 ieRange.pasteHTML('<span id="_mce_start" style="display:none;line-height:0">' + invisibleChar + '</span>');
\r
3311 // Insert invisible end marker
\r
3313 ieRange2.collapse(FALSE);
\r
3314 ieRange2.pasteHTML('<span id="_mce_end" style="display:none;line-height:0">' + invisibleChar + '</span>');
\r
3317 // Sets the end point of the range by looking for the marker
\r
3318 // This method also merges the text nodes it splits so that
\r
3319 // the DOM doesn't get fragmented.
\r
3320 function setEndPoint(start) {
\r
3321 var container, offset, marker, sibling;
\r
3323 // Look for endpoint marker
\r
3324 marker = dom.get('_mce_' + (start ? 'start' : 'end'));
\r
3325 sibling = marker.previousSibling;
\r
3327 // Is marker after a text node
\r
3328 if (sibling && sibling.nodeType == 3) {
\r
3329 // Get container node and calc offset
\r
3330 container = sibling;
\r
3331 offset = container.nodeValue.length;
\r
3332 dom.remove(marker);
\r
3334 // Merge text nodes to reduce DOM fragmentation
\r
3335 sibling = container.nextSibling;
\r
3336 if (sibling && sibling.nodeType == 3) {
\r
3338 container.appendData(sibling.nodeValue);
\r
3339 dom.remove(sibling);
\r
3342 sibling = marker.nextSibling;
\r
3344 // Is marker before a text node
\r
3345 if (sibling && sibling.nodeType == 3) {
\r
3346 container = sibling;
\r
3349 // Is marker before an element
\r
3351 offset = dom.nodeIndex(sibling) - 1;
\r
3353 offset = dom.nodeIndex(marker);
\r
3355 container = marker.parentNode;
\r
3358 dom.remove(marker);
\r
3361 // Set start of range
\r
3363 domRange.setStart(container, offset);
\r
3365 // Set end of range or automatically if it's collapsed to increase performance
\r
3366 if (!start || collapsed)
\r
3367 domRange.setEnd(container, offset);
\r
3370 // Set start of range
\r
3371 setEndPoint(TRUE);
\r
3373 // Set end of range if needed
\r
3375 setEndPoint(FALSE);
\r
3377 // Restore selection if the range contents was merged
\r
3378 // since the selection was then moved since the text nodes got changed
\r
3380 t.addRange(domRange);
\r
3385 this.addRange = function(rng) {
\r
3386 var ieRng, ieRng2, doc = selection.dom.doc, body = doc.body, startPos, endPos, sc, so, ec, eo, marker, lastIndex, skipStart, skipEnd;
\r
3390 // Setup some shorter versions
\r
3391 sc = rng.startContainer;
\r
3392 so = rng.startOffset;
\r
3393 ec = rng.endContainer;
\r
3394 eo = rng.endOffset;
\r
3395 ieRng = body.createTextRange();
\r
3397 // If document selection move caret to first node in document
\r
3398 if (sc == doc || ec == doc) {
\r
3399 ieRng = body.createTextRange();
\r
3405 // If child index resolve it
\r
3406 if (sc.nodeType == 1 && sc.hasChildNodes()) {
\r
3407 lastIndex = sc.childNodes.length - 1;
\r
3409 // Index is higher that the child count then we need to jump over the start container
\r
3410 if (so > lastIndex) {
\r
3412 sc = sc.childNodes[lastIndex];
\r
3414 sc = sc.childNodes[so];
\r
3416 // Child was text node then move offset to start of it
\r
3417 if (sc.nodeType == 3)
\r
3421 // If child index resolve it
\r
3422 if (ec.nodeType == 1 && ec.hasChildNodes()) {
\r
3423 lastIndex = ec.childNodes.length - 1;
\r
3427 ec = ec.childNodes[0];
\r
3429 ec = ec.childNodes[Math.min(lastIndex, eo - 1)];
\r
3431 // Child was text node then move offset to end of text node
\r
3432 if (ec.nodeType == 3)
\r
3433 eo = ec.nodeValue.length;
\r
3437 // Single element selection
\r
3438 if (sc == ec && sc.nodeType == 1) {
\r
3439 // Make control selection for some elements
\r
3440 if (/^(IMG|TABLE)$/.test(sc.nodeName) && so != eo) {
\r
3441 ieRng = body.createControlRange();
\r
3442 ieRng.addElement(sc);
\r
3444 ieRng = body.createTextRange();
\r
3446 // Padd empty elements with invisible character
\r
3447 if (!sc.hasChildNodes() && sc.canHaveHTML)
\r
3448 sc.innerHTML = invisibleChar;
\r
3450 // Select element contents
\r
3451 ieRng.moveToElementText(sc);
\r
3453 // If it's only containing a padding remove it so the caret remains
\r
3454 if (sc.innerHTML == invisibleChar) {
\r
3455 ieRng.collapse(TRUE);
\r
3456 sc.removeChild(sc.firstChild);
\r
3461 ieRng.collapse(eo <= rng.endContainer.childNodes.length - 1);
\r
3464 ieRng.scrollIntoView();
\r
3468 // Create range and marker
\r
3469 ieRng = body.createTextRange();
\r
3470 marker = doc.createElement('span');
\r
3471 marker.innerHTML = ' ';
\r
3473 // Set start of range to startContainer/startOffset
\r
3474 if (sc.nodeType == 3) {
\r
3475 // Insert marker after/before startContainer
\r
3477 dom.insertAfter(marker, sc);
\r
3479 sc.parentNode.insertBefore(marker, sc);
\r
3481 // Select marker the caret to offset position
\r
3482 ieRng.moveToElementText(marker);
\r
3483 marker.parentNode.removeChild(marker);
\r
3484 ieRng.move('character', so);
\r
3486 ieRng.moveToElementText(sc);
\r
3489 ieRng.collapse(FALSE);
\r
3492 // If same text container then we can do a more simple move
\r
3493 if (sc == ec && sc.nodeType == 3) {
\r
3494 ieRng.moveEnd('character', eo - so);
\r
3496 ieRng.scrollIntoView();
\r
3500 // Set end of range to endContainer/endOffset
\r
3501 ieRng2 = body.createTextRange();
\r
3502 if (ec.nodeType == 3) {
\r
3503 // Insert marker after/before startContainer
\r
3504 ec.parentNode.insertBefore(marker, ec);
\r
3506 // Move selection to end marker and move caret to end offset
\r
3507 ieRng2.moveToElementText(marker);
\r
3508 marker.parentNode.removeChild(marker);
\r
3509 ieRng2.move('character', eo);
\r
3510 ieRng.setEndPoint('EndToStart', ieRng2);
\r
3512 ieRng2.moveToElementText(ec);
\r
3513 ieRng2.collapse(!!skipEnd);
\r
3514 ieRng.setEndPoint('EndToEnd', ieRng2);
\r
3518 ieRng.scrollIntoView();
\r
3521 this.getRangeAt = function() {
\r
3522 // Setup new range if the cache is empty
\r
3523 if (!range || !compareRanges(lastIERng, selection.getRng())) {
\r
3524 range = getRange();
\r
3526 // Store away text range for next call
\r
3527 lastIERng = selection.getRng();
\r
3530 // Return cached range
\r
3534 this.destroy = function() {
\r
3535 // Destroy cached range and last IE range to avoid memory leaks
\r
3536 lastIERng = range = null;
\r
3539 // 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
3540 if (selection.dom.boxModel) {
\r
3542 var doc = dom.doc, body = doc.body, started, startRng;
\r
3544 // Make HTML element unselectable since we are going to handle selection by hand
\r
3545 doc.documentElement.unselectable = TRUE;
\r
3547 // Return range from point or null if it failed
\r
3548 function rngFromPoint(x, y) {
\r
3549 var rng = body.createTextRange();
\r
3552 rng.moveToPoint(x, y);
\r
3554 // IE sometimes throws and exception, so lets just ignore it
\r
3561 // Fires while the selection is changing
\r
3562 function selectionChange(e) {
\r
3565 // Check if the button is down or not
\r
3567 // Create range from mouse position
\r
3568 pointRng = rngFromPoint(e.x, e.y);
\r
3571 // Check if pointRange is before/after selection then change the endPoint
\r
3572 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
\r
3573 pointRng.setEndPoint('StartToStart', startRng);
\r
3575 pointRng.setEndPoint('EndToEnd', startRng);
\r
3577 pointRng.select();
\r
3583 // Removes listeners
\r
3584 function endSelection() {
\r
3585 dom.unbind(doc, 'mouseup', endSelection);
\r
3586 dom.unbind(doc, 'mousemove', selectionChange);
\r
3590 // Detect when user selects outside BODY
\r
3591 dom.bind(doc, 'mousedown', function(e) {
\r
3592 if (e.target.nodeName === 'HTML') {
\r
3598 // Setup start position
\r
3599 startRng = rngFromPoint(e.x, e.y);
\r
3601 // Listen for selection change events
\r
3602 dom.bind(doc, 'mouseup', endSelection);
\r
3603 dom.bind(doc, 'mousemove', selectionChange);
\r
3605 startRng.select();
\r
3613 // Expose the selection object
\r
3614 tinymce.dom.TridentSelection = Selection;
\r
3619 * Sizzle CSS Selector Engine - v1.0
\r
3620 * Copyright 2009, The Dojo Foundation
\r
3621 * Released under the MIT, BSD, and GPL Licenses.
\r
3622 * More information: http://sizzlejs.com/
\r
3626 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
\r
3628 toString = Object.prototype.toString,
\r
3629 hasDuplicate = false;
\r
3631 var Sizzle = function(selector, context, results, seed) {
\r
3632 results = results || [];
\r
3633 var origContext = context = context || document;
\r
3635 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
\r
3639 if ( !selector || typeof selector !== "string" ) {
\r
3643 var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context);
\r
3645 // Reset the position of the chunker regexp (start from head)
\r
3646 chunker.lastIndex = 0;
\r
3648 while ( (m = chunker.exec(selector)) !== null ) {
\r
3649 parts.push( m[1] );
\r
3652 extra = RegExp.rightContext;
\r
3657 if ( parts.length > 1 && origPOS.exec( selector ) ) {
\r
3658 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
\r
3659 set = posProcess( parts[0] + parts[1], context );
\r
3661 set = Expr.relative[ parts[0] ] ?
\r
3663 Sizzle( parts.shift(), context );
\r
3665 while ( parts.length ) {
\r
3666 selector = parts.shift();
\r
3668 if ( Expr.relative[ selector ] )
\r
3669 selector += parts.shift();
\r
3671 set = posProcess( selector, set );
\r
3675 // Take a shortcut and set the context if the root selector is an ID
\r
3676 // (but not if it'll be faster if the inner selector is an ID)
\r
3677 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
\r
3678 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
\r
3679 var ret = Sizzle.find( parts.shift(), context, contextXML );
\r
3680 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
\r
3685 { expr: parts.pop(), set: makeArray(seed) } :
\r
3686 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
\r
3687 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
\r
3689 if ( parts.length > 0 ) {
\r
3690 checkSet = makeArray(set);
\r
3695 while ( parts.length ) {
\r
3696 var cur = parts.pop(), pop = cur;
\r
3698 if ( !Expr.relative[ cur ] ) {
\r
3701 pop = parts.pop();
\r
3704 if ( pop == null ) {
\r
3708 Expr.relative[ cur ]( checkSet, pop, contextXML );
\r
3711 checkSet = parts = [];
\r
3715 if ( !checkSet ) {
\r
3719 if ( !checkSet ) {
\r
3720 throw "Syntax error, unrecognized expression: " + (cur || selector);
\r
3723 if ( toString.call(checkSet) === "[object Array]" ) {
\r
3725 results.push.apply( results, checkSet );
\r
3726 } else if ( context && context.nodeType === 1 ) {
\r
3727 for ( var i = 0; checkSet[i] != null; i++ ) {
\r
3728 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
\r
3729 results.push( set[i] );
\r
3733 for ( var i = 0; checkSet[i] != null; i++ ) {
\r
3734 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
\r
3735 results.push( set[i] );
\r
3740 makeArray( checkSet, results );
\r
3744 Sizzle( extra, origContext, results, seed );
\r
3745 Sizzle.uniqueSort( results );
\r
3751 Sizzle.uniqueSort = function(results){
\r
3752 if ( sortOrder ) {
\r
3753 hasDuplicate = false;
\r
3754 results.sort(sortOrder);
\r
3756 if ( hasDuplicate ) {
\r
3757 for ( var i = 1; i < results.length; i++ ) {
\r
3758 if ( results[i] === results[i-1] ) {
\r
3759 results.splice(i--, 1);
\r
3766 Sizzle.matches = function(expr, set){
\r
3767 return Sizzle(expr, null, null, set);
\r
3770 Sizzle.find = function(expr, context, isXML){
\r
3777 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
\r
3778 var type = Expr.order[i], match;
\r
3780 if ( (match = Expr.match[ type ].exec( expr )) ) {
\r
3781 var left = RegExp.leftContext;
\r
3783 if ( left.substr( left.length - 1 ) !== "\\" ) {
\r
3784 match[1] = (match[1] || "").replace(/\\/g, "");
\r
3785 set = Expr.find[ type ]( match, context, isXML );
\r
3786 if ( set != null ) {
\r
3787 expr = expr.replace( Expr.match[ type ], "" );
\r
3795 set = context.getElementsByTagName("*");
\r
3798 return {set: set, expr: expr};
\r
3801 Sizzle.filter = function(expr, set, inplace, not){
\r
3802 var old = expr, result = [], curLoop = set, match, anyFound,
\r
3803 isXMLFilter = set && set[0] && isXML(set[0]);
\r
3805 while ( expr && set.length ) {
\r
3806 for ( var type in Expr.filter ) {
\r
3807 if ( (match = Expr.match[ type ].exec( expr )) != null ) {
\r
3808 var filter = Expr.filter[ type ], found, item;
\r
3811 if ( curLoop == result ) {
\r
3815 if ( Expr.preFilter[ type ] ) {
\r
3816 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
\r
3819 anyFound = found = true;
\r
3820 } else if ( match === true ) {
\r
3826 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
\r
3828 found = filter( item, match, i, curLoop );
\r
3829 var pass = not ^ !!found;
\r
3831 if ( inplace && found != null ) {
\r
3835 curLoop[i] = false;
\r
3837 } else if ( pass ) {
\r
3838 result.push( item );
\r
3845 if ( found !== undefined ) {
\r
3850 expr = expr.replace( Expr.match[ type ], "" );
\r
3852 if ( !anyFound ) {
\r
3861 // Improper expression
\r
3862 if ( expr == old ) {
\r
3863 if ( anyFound == null ) {
\r
3864 throw "Syntax error, unrecognized expression: " + expr;
\r
3876 var Expr = Sizzle.selectors = {
\r
3877 order: [ "ID", "NAME", "TAG" ],
\r
3879 ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
\r
3880 CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
\r
3881 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
\r
3882 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
\r
3883 TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
\r
3884 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
\r
3885 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
\r
3886 PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
\r
3889 "class": "className",
\r
3893 href: function(elem){
\r
3894 return elem.getAttribute("href");
\r
3898 "+": function(checkSet, part, isXML){
\r
3899 var isPartStr = typeof part === "string",
\r
3900 isTag = isPartStr && !/\W/.test(part),
\r
3901 isPartStrNotTag = isPartStr && !isTag;
\r
3903 if ( isTag && !isXML ) {
\r
3904 part = part.toUpperCase();
\r
3907 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
\r
3908 if ( (elem = checkSet[i]) ) {
\r
3909 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
\r
3911 checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
\r
3917 if ( isPartStrNotTag ) {
\r
3918 Sizzle.filter( part, checkSet, true );
\r
3921 ">": function(checkSet, part, isXML){
\r
3922 var isPartStr = typeof part === "string";
\r
3924 if ( isPartStr && !/\W/.test(part) ) {
\r
3925 part = isXML ? part : part.toUpperCase();
\r
3927 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
3928 var elem = checkSet[i];
\r
3930 var parent = elem.parentNode;
\r
3931 checkSet[i] = parent.nodeName === part ? parent : false;
\r
3935 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
3936 var elem = checkSet[i];
\r
3938 checkSet[i] = isPartStr ?
\r
3940 elem.parentNode === part;
\r
3944 if ( isPartStr ) {
\r
3945 Sizzle.filter( part, checkSet, true );
\r
3949 "": function(checkSet, part, isXML){
\r
3950 var doneName = done++, checkFn = dirCheck;
\r
3952 if ( !part.match(/\W/) ) {
\r
3953 var nodeCheck = part = isXML ? part : part.toUpperCase();
\r
3954 checkFn = dirNodeCheck;
\r
3957 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
\r
3959 "~": function(checkSet, part, isXML){
\r
3960 var doneName = done++, checkFn = dirCheck;
\r
3962 if ( typeof part === "string" && !part.match(/\W/) ) {
\r
3963 var nodeCheck = part = isXML ? part : part.toUpperCase();
\r
3964 checkFn = dirNodeCheck;
\r
3967 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
\r
3971 ID: function(match, context, isXML){
\r
3972 if ( typeof context.getElementById !== "undefined" && !isXML ) {
\r
3973 var m = context.getElementById(match[1]);
\r
3974 return m ? [m] : [];
\r
3977 NAME: function(match, context, isXML){
\r
3978 if ( typeof context.getElementsByName !== "undefined" ) {
\r
3979 var ret = [], results = context.getElementsByName(match[1]);
\r
3981 for ( var i = 0, l = results.length; i < l; i++ ) {
\r
3982 if ( results[i].getAttribute("name") === match[1] ) {
\r
3983 ret.push( results[i] );
\r
3987 return ret.length === 0 ? null : ret;
\r
3990 TAG: function(match, context){
\r
3991 return context.getElementsByTagName(match[1]);
\r
3995 CLASS: function(match, curLoop, inplace, result, not, isXML){
\r
3996 match = " " + match[1].replace(/\\/g, "") + " ";
\r
4002 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
\r
4004 if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
\r
4006 result.push( elem );
\r
4007 } else if ( inplace ) {
\r
4008 curLoop[i] = false;
\r
4015 ID: function(match){
\r
4016 return match[1].replace(/\\/g, "");
\r
4018 TAG: function(match, curLoop){
\r
4019 for ( var i = 0; curLoop[i] === false; i++ ){}
\r
4020 return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
\r
4022 CHILD: function(match){
\r
4023 if ( match[1] == "nth" ) {
\r
4024 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
\r
4025 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
\r
4026 match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
\r
4027 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
\r
4029 // calculate the numbers (first)n+(last) including if they are negative
\r
4030 match[2] = (test[1] + (test[2] || 1)) - 0;
\r
4031 match[3] = test[3] - 0;
\r
4034 // TODO: Move to normal caching system
\r
4035 match[0] = done++;
\r
4039 ATTR: function(match, curLoop, inplace, result, not, isXML){
\r
4040 var name = match[1].replace(/\\/g, "");
\r
4042 if ( !isXML && Expr.attrMap[name] ) {
\r
4043 match[1] = Expr.attrMap[name];
\r
4046 if ( match[2] === "~=" ) {
\r
4047 match[4] = " " + match[4] + " ";
\r
4052 PSEUDO: function(match, curLoop, inplace, result, not){
\r
4053 if ( match[1] === "not" ) {
\r
4054 // If we're dealing with a complex expression, or a simple one
\r
4055 if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
\r
4056 match[3] = Sizzle(match[3], null, null, curLoop);
\r
4058 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
\r
4060 result.push.apply( result, ret );
\r
4064 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
\r
4070 POS: function(match){
\r
4071 match.unshift( true );
\r
4076 enabled: function(elem){
\r
4077 return elem.disabled === false && elem.type !== "hidden";
\r
4079 disabled: function(elem){
\r
4080 return elem.disabled === true;
\r
4082 checked: function(elem){
\r
4083 return elem.checked === true;
\r
4085 selected: function(elem){
\r
4086 // Accessing this property makes selected-by-default
\r
4087 // options in Safari work properly
\r
4088 elem.parentNode.selectedIndex;
\r
4089 return elem.selected === true;
\r
4091 parent: function(elem){
\r
4092 return !!elem.firstChild;
\r
4094 empty: function(elem){
\r
4095 return !elem.firstChild;
\r
4097 has: function(elem, i, match){
\r
4098 return !!Sizzle( match[3], elem ).length;
\r
4100 header: function(elem){
\r
4101 return /h\d/i.test( elem.nodeName );
\r
4103 text: function(elem){
\r
4104 return "text" === elem.type;
\r
4106 radio: function(elem){
\r
4107 return "radio" === elem.type;
\r
4109 checkbox: function(elem){
\r
4110 return "checkbox" === elem.type;
\r
4112 file: function(elem){
\r
4113 return "file" === elem.type;
\r
4115 password: function(elem){
\r
4116 return "password" === elem.type;
\r
4118 submit: function(elem){
\r
4119 return "submit" === elem.type;
\r
4121 image: function(elem){
\r
4122 return "image" === elem.type;
\r
4124 reset: function(elem){
\r
4125 return "reset" === elem.type;
\r
4127 button: function(elem){
\r
4128 return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
\r
4130 input: function(elem){
\r
4131 return /input|select|textarea|button/i.test(elem.nodeName);
\r
4135 first: function(elem, i){
\r
4138 last: function(elem, i, match, array){
\r
4139 return i === array.length - 1;
\r
4141 even: function(elem, i){
\r
4142 return i % 2 === 0;
\r
4144 odd: function(elem, i){
\r
4145 return i % 2 === 1;
\r
4147 lt: function(elem, i, match){
\r
4148 return i < match[3] - 0;
\r
4150 gt: function(elem, i, match){
\r
4151 return i > match[3] - 0;
\r
4153 nth: function(elem, i, match){
\r
4154 return match[3] - 0 == i;
\r
4156 eq: function(elem, i, match){
\r
4157 return match[3] - 0 == i;
\r
4161 PSEUDO: function(elem, match, i, array){
\r
4162 var name = match[1], filter = Expr.filters[ name ];
\r
4165 return filter( elem, i, match, array );
\r
4166 } else if ( name === "contains" ) {
\r
4167 return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
\r
4168 } else if ( name === "not" ) {
\r
4169 var not = match[3];
\r
4171 for ( var i = 0, l = not.length; i < l; i++ ) {
\r
4172 if ( not[i] === elem ) {
\r
4180 CHILD: function(elem, match){
\r
4181 var type = match[1], node = elem;
\r
4185 while (node = node.previousSibling) {
\r
4186 if ( node.nodeType === 1 ) return false;
\r
4188 if ( type == 'first') return true;
\r
4191 while (node = node.nextSibling) {
\r
4192 if ( node.nodeType === 1 ) return false;
\r
4196 var first = match[2], last = match[3];
\r
4198 if ( first == 1 && last == 0 ) {
\r
4202 var doneName = match[0],
\r
4203 parent = elem.parentNode;
\r
4205 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
\r
4207 for ( node = parent.firstChild; node; node = node.nextSibling ) {
\r
4208 if ( node.nodeType === 1 ) {
\r
4209 node.nodeIndex = ++count;
\r
4212 parent.sizcache = doneName;
\r
4215 var diff = elem.nodeIndex - last;
\r
4216 if ( first == 0 ) {
\r
4219 return ( diff % first == 0 && diff / first >= 0 );
\r
4223 ID: function(elem, match){
\r
4224 return elem.nodeType === 1 && elem.getAttribute("id") === match;
\r
4226 TAG: function(elem, match){
\r
4227 return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
\r
4229 CLASS: function(elem, match){
\r
4230 return (" " + (elem.className || elem.getAttribute("class")) + " ")
\r
4231 .indexOf( match ) > -1;
\r
4233 ATTR: function(elem, match){
\r
4234 var name = match[1],
\r
4235 result = Expr.attrHandle[ name ] ?
\r
4236 Expr.attrHandle[ name ]( elem ) :
\r
4237 elem[ name ] != null ?
\r
4239 elem.getAttribute( name ),
\r
4240 value = result + "",
\r
4244 return result == null ?
\r
4249 value.indexOf(check) >= 0 :
\r
4251 (" " + value + " ").indexOf(check) >= 0 :
\r
4253 value && result !== false :
\r
4257 value.indexOf(check) === 0 :
\r
4259 value.substr(value.length - check.length) === check :
\r
4261 value === check || value.substr(0, check.length + 1) === check + "-" :
\r
4264 POS: function(elem, match, i, array){
\r
4265 var name = match[2], filter = Expr.setFilters[ name ];
\r
4268 return filter( elem, i, match, array );
\r
4274 var origPOS = Expr.match.POS;
\r
4276 for ( var type in Expr.match ) {
\r
4277 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
\r
4280 var makeArray = function(array, results) {
\r
4281 array = Array.prototype.slice.call( array );
\r
4284 results.push.apply( results, array );
\r
4291 // Perform a simple check to determine if the browser is capable of
\r
4292 // converting a NodeList to an array using builtin methods.
\r
4294 Array.prototype.slice.call( document.documentElement.childNodes );
\r
4296 // Provide a fallback method if it does not work
\r
4298 makeArray = function(array, results) {
\r
4299 var ret = results || [];
\r
4301 if ( toString.call(array) === "[object Array]" ) {
\r
4302 Array.prototype.push.apply( ret, array );
\r
4304 if ( typeof array.length === "number" ) {
\r
4305 for ( var i = 0, l = array.length; i < l; i++ ) {
\r
4306 ret.push( array[i] );
\r
4309 for ( var i = 0; array[i]; i++ ) {
\r
4310 ret.push( array[i] );
\r
4321 if ( document.documentElement.compareDocumentPosition ) {
\r
4322 sortOrder = function( a, b ) {
\r
4323 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
\r
4324 if ( ret === 0 ) {
\r
4325 hasDuplicate = true;
\r
4329 } else if ( "sourceIndex" in document.documentElement ) {
\r
4330 sortOrder = function( a, b ) {
\r
4331 var ret = a.sourceIndex - b.sourceIndex;
\r
4332 if ( ret === 0 ) {
\r
4333 hasDuplicate = true;
\r
4337 } else if ( document.createRange ) {
\r
4338 sortOrder = function( a, b ) {
\r
4339 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
\r
4340 aRange.setStart(a, 0);
\r
4341 aRange.setEnd(a, 0);
\r
4342 bRange.setStart(b, 0);
\r
4343 bRange.setEnd(b, 0);
\r
4344 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
\r
4345 if ( ret === 0 ) {
\r
4346 hasDuplicate = true;
\r
4352 // Check to see if the browser returns elements by name when
\r
4353 // querying by getElementById (and provide a workaround)
\r
4355 // We're going to inject a fake input element with a specified name
\r
4356 var form = document.createElement("div"),
\r
4357 id = "script" + (new Date).getTime();
\r
4358 form.innerHTML = "<a name='" + id + "'/>";
\r
4360 // Inject it into the root element, check its status, and remove it quickly
\r
4361 var root = document.documentElement;
\r
4362 root.insertBefore( form, root.firstChild );
\r
4364 // The workaround has to do additional checks after a getElementById
\r
4365 // Which slows things down for other browsers (hence the branching)
\r
4366 if ( !!document.getElementById( id ) ) {
\r
4367 Expr.find.ID = function(match, context, isXML){
\r
4368 if ( typeof context.getElementById !== "undefined" && !isXML ) {
\r
4369 var m = context.getElementById(match[1]);
\r
4370 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
\r
4374 Expr.filter.ID = function(elem, match){
\r
4375 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
\r
4376 return elem.nodeType === 1 && node && node.nodeValue === match;
\r
4380 root.removeChild( form );
\r
4384 // Check to see if the browser returns only elements
\r
4385 // when doing getElementsByTagName("*")
\r
4387 // Create a fake element
\r
4388 var div = document.createElement("div");
\r
4389 div.appendChild( document.createComment("") );
\r
4391 // Make sure no comments are found
\r
4392 if ( div.getElementsByTagName("*").length > 0 ) {
\r
4393 Expr.find.TAG = function(match, context){
\r
4394 var results = context.getElementsByTagName(match[1]);
\r
4396 // Filter out possible comments
\r
4397 if ( match[1] === "*" ) {
\r
4400 for ( var i = 0; results[i]; i++ ) {
\r
4401 if ( results[i].nodeType === 1 ) {
\r
4402 tmp.push( results[i] );
\r
4413 // Check to see if an attribute returns normalized href attributes
\r
4414 div.innerHTML = "<a href='#'></a>";
\r
4415 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
\r
4416 div.firstChild.getAttribute("href") !== "#" ) {
\r
4417 Expr.attrHandle.href = function(elem){
\r
4418 return elem.getAttribute("href", 2);
\r
4423 if ( document.querySelectorAll ) (function(){
\r
4424 var oldSizzle = Sizzle, div = document.createElement("div");
\r
4425 div.innerHTML = "<p class='TEST'></p>";
\r
4427 // Safari can't handle uppercase or unicode characters when
\r
4428 // in quirks mode.
\r
4429 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
\r
4433 Sizzle = function(query, context, extra, seed){
\r
4434 context = context || document;
\r
4436 // Only use querySelectorAll on non-XML documents
\r
4437 // (ID selectors don't work in non-HTML documents)
\r
4438 if ( !seed && context.nodeType === 9 && !isXML(context) ) {
\r
4440 return makeArray( context.querySelectorAll(query), extra );
\r
4444 return oldSizzle(query, context, extra, seed);
\r
4447 for ( var prop in oldSizzle ) {
\r
4448 Sizzle[ prop ] = oldSizzle[ prop ];
\r
4452 if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
\r
4453 var div = document.createElement("div");
\r
4454 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
\r
4456 // Opera can't find a second classname (in 9.6)
\r
4457 if ( div.getElementsByClassName("e").length === 0 )
\r
4460 // Safari caches class attributes, doesn't catch changes (in 3.2)
\r
4461 div.lastChild.className = "e";
\r
4463 if ( div.getElementsByClassName("e").length === 1 )
\r
4466 Expr.order.splice(1, 0, "CLASS");
\r
4467 Expr.find.CLASS = function(match, context, isXML) {
\r
4468 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
\r
4469 return context.getElementsByClassName(match[1]);
\r
4474 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
\r
4475 var sibDir = dir == "previousSibling" && !isXML;
\r
4476 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
4477 var elem = checkSet[i];
\r
4479 if ( sibDir && elem.nodeType === 1 ){
\r
4480 elem.sizcache = doneName;
\r
4484 var match = false;
\r
4487 if ( elem.sizcache === doneName ) {
\r
4488 match = checkSet[elem.sizset];
\r
4492 if ( elem.nodeType === 1 && !isXML ){
\r
4493 elem.sizcache = doneName;
\r
4497 if ( elem.nodeName === cur ) {
\r
4505 checkSet[i] = match;
\r
4510 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
\r
4511 var sibDir = dir == "previousSibling" && !isXML;
\r
4512 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
4513 var elem = checkSet[i];
\r
4515 if ( sibDir && elem.nodeType === 1 ) {
\r
4516 elem.sizcache = doneName;
\r
4520 var match = false;
\r
4523 if ( elem.sizcache === doneName ) {
\r
4524 match = checkSet[elem.sizset];
\r
4528 if ( elem.nodeType === 1 ) {
\r
4530 elem.sizcache = doneName;
\r
4533 if ( typeof cur !== "string" ) {
\r
4534 if ( elem === cur ) {
\r
4539 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
\r
4548 checkSet[i] = match;
\r
4553 var contains = document.compareDocumentPosition ? function(a, b){
\r
4554 return a.compareDocumentPosition(b) & 16;
\r
4555 } : function(a, b){
\r
4556 return a !== b && (a.contains ? a.contains(b) : true);
\r
4559 var isXML = function(elem){
\r
4560 return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
\r
4561 !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
\r
4564 var posProcess = function(selector, context){
\r
4565 var tmpSet = [], later = "", match,
\r
4566 root = context.nodeType ? [context] : context;
\r
4568 // Position selectors must be done after the filter
\r
4569 // And so must :not(positional) so we move all PSEUDOs to the end
\r
4570 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
\r
4571 later += match[0];
\r
4572 selector = selector.replace( Expr.match.PSEUDO, "" );
\r
4575 selector = Expr.relative[selector] ? selector + "*" : selector;
\r
4577 for ( var i = 0, l = root.length; i < l; i++ ) {
\r
4578 Sizzle( selector, root[i], tmpSet );
\r
4581 return Sizzle.filter( later, tmpSet );
\r
4586 window.tinymce.dom.Sizzle = Sizzle;
\r
4591 (function(tinymce) {
\r
4593 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
\r
4595 tinymce.create('tinymce.dom.EventUtils', {
\r
4596 EventUtils : function() {
\r
4601 add : function(o, n, f, s) {
\r
4602 var cb, t = this, el = t.events, r;
\r
4604 if (n instanceof Array) {
\r
4607 each(n, function(n) {
\r
4608 r.push(t.add(o, n, f, s));
\r
4615 if (o && o.hasOwnProperty && o instanceof Array) {
\r
4618 each(o, function(o) {
\r
4620 r.push(t.add(o, n, f, s));
\r
4631 // Setup event callback
\r
4632 cb = function(e) {
\r
4633 // Is all events disabled
\r
4637 e = e || window.event;
\r
4639 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
\r
4642 e.target = e.srcElement;
\r
4644 // Patch in preventDefault, stopPropagation methods for W3C compatibility
\r
4645 tinymce.extend(e, t._stoppers);
\r
4651 return f.call(s, e);
\r
4654 if (n == 'unload') {
\r
4655 tinymce.unloads.unshift({func : cb});
\r
4659 if (n == 'init') {
\r
4668 // Store away listener reference
\r
4682 remove : function(o, n, f) {
\r
4683 var t = this, a = t.events, s = false, r;
\r
4686 if (o && o.hasOwnProperty && o instanceof Array) {
\r
4689 each(o, function(o) {
\r
4691 r.push(t.remove(o, n, f));
\r
4699 each(a, function(e, i) {
\r
4700 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
\r
4702 t._remove(o, n, e.cfunc);
\r
4711 clear : function(o) {
\r
4712 var t = this, a = t.events, i, e;
\r
4717 for (i = a.length - 1; i >= 0; i--) {
\r
4720 if (e.obj === o) {
\r
4721 t._remove(e.obj, e.name, e.cfunc);
\r
4722 e.obj = e.cfunc = null;
\r
4729 cancel : function(e) {
\r
4735 return this.prevent(e);
\r
4738 stop : function(e) {
\r
4739 if (e.stopPropagation)
\r
4740 e.stopPropagation();
\r
4742 e.cancelBubble = true;
\r
4747 prevent : function(e) {
\r
4748 if (e.preventDefault)
\r
4749 e.preventDefault();
\r
4751 e.returnValue = false;
\r
4756 destroy : function() {
\r
4759 each(t.events, function(e, i) {
\r
4760 t._remove(e.obj, e.name, e.cfunc);
\r
4761 e.obj = e.cfunc = null;
\r
4768 _add : function(o, n, f) {
\r
4769 if (o.attachEvent)
\r
4770 o.attachEvent('on' + n, f);
\r
4771 else if (o.addEventListener)
\r
4772 o.addEventListener(n, f, false);
\r
4777 _remove : function(o, n, f) {
\r
4780 if (o.detachEvent)
\r
4781 o.detachEvent('on' + n, f);
\r
4782 else if (o.removeEventListener)
\r
4783 o.removeEventListener(n, f, false);
\r
4785 o['on' + n] = null;
\r
4787 // Might fail with permission denined on IE so we just ignore that
\r
4792 _pageInit : function(win) {
\r
4795 // Keep it from running more than once
\r
4799 t.domLoaded = true;
\r
4801 each(t.inits, function(c) {
\r
4808 _wait : function(win) {
\r
4809 var t = this, doc = win.document;
\r
4811 // No need since the document is already loaded
\r
4812 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
\r
4818 if (doc.attachEvent) {
\r
4819 doc.attachEvent("onreadystatechange", function() {
\r
4820 if (doc.readyState === "complete") {
\r
4821 doc.detachEvent("onreadystatechange", arguments.callee);
\r
4826 if (doc.documentElement.doScroll && win == win.top) {
\r
4832 // If IE is used, use the trick by Diego Perini
\r
4833 // http://javascript.nwbox.com/IEContentLoaded/
\r
4834 doc.documentElement.doScroll("left");
\r
4836 setTimeout(arguments.callee, 0);
\r
4843 } else if (doc.addEventListener) {
\r
4844 t._add(win, 'DOMContentLoaded', function() {
\r
4849 t._add(win, 'load', function() {
\r
4855 preventDefault : function() {
\r
4856 this.returnValue = false;
\r
4859 stopPropagation : function() {
\r
4860 this.cancelBubble = true;
\r
4865 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
\r
4867 // Dispatch DOM content loaded event for IE and Safari
\r
4868 Event._wait(window);
\r
4870 tinymce.addUnload(function() {
\r
4875 (function(tinymce) {
\r
4876 tinymce.dom.Element = function(id, settings) {
\r
4877 var t = this, dom, el;
\r
4879 t.settings = settings = settings || {};
\r
4881 t.dom = dom = settings.dom || tinymce.DOM;
\r
4883 // Only IE leaks DOM references, this is a lot faster
\r
4884 if (!tinymce.isIE)
\r
4885 el = dom.get(t.id);
\r
4888 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
\r
4889 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
\r
4890 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
\r
4891 'isHidden,setHTML,get').split(/,/)
\r
4893 t[k] = function() {
\r
4896 for (i = 0; i < arguments.length; i++)
\r
4897 a.push(arguments[i]);
\r
4899 a = dom[k].apply(dom, a);
\r
4906 tinymce.extend(t, {
\r
4907 on : function(n, f, s) {
\r
4908 return tinymce.dom.Event.add(t.id, n, f, s);
\r
4911 getXY : function() {
\r
4913 x : parseInt(t.getStyle('left')),
\r
4914 y : parseInt(t.getStyle('top'))
\r
4918 getSize : function() {
\r
4919 var n = dom.get(t.id);
\r
4922 w : parseInt(t.getStyle('width') || n.clientWidth),
\r
4923 h : parseInt(t.getStyle('height') || n.clientHeight)
\r
4927 moveTo : function(x, y) {
\r
4928 t.setStyles({left : x, top : y});
\r
4931 moveBy : function(x, y) {
\r
4932 var p = t.getXY();
\r
4934 t.moveTo(p.x + x, p.y + y);
\r
4937 resizeTo : function(w, h) {
\r
4938 t.setStyles({width : w, height : h});
\r
4941 resizeBy : function(w, h) {
\r
4942 var s = t.getSize();
\r
4944 t.resizeTo(s.w + w, s.h + h);
\r
4947 update : function(k) {
\r
4950 if (tinymce.isIE6 && settings.blocker) {
\r
4954 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
\r
4957 // Remove blocker on remove
\r
4958 if (k == 'remove') {
\r
4959 dom.remove(t.blocker);
\r
4964 t.blocker = dom.uniqueId();
\r
4965 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
\r
4966 dom.setStyle(b, 'opacity', 0);
\r
4968 b = dom.get(t.blocker);
\r
4970 dom.setStyles(b, {
\r
4971 left : t.getStyle('left', 1),
\r
4972 top : t.getStyle('top', 1),
\r
4973 width : t.getStyle('width', 1),
\r
4974 height : t.getStyle('height', 1),
\r
4975 display : t.getStyle('display', 1),
\r
4976 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
\r
4984 (function(tinymce) {
\r
4985 function trimNl(s) {
\r
4986 return s.replace(/[\n\r]+/g, '');
\r
4990 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
\r
4992 tinymce.create('tinymce.dom.Selection', {
\r
4993 Selection : function(dom, win, serializer) {
\r
4998 t.serializer = serializer;
\r
5002 'onBeforeSetContent',
\r
5003 'onBeforeGetContent',
\r
5007 t[e] = new tinymce.util.Dispatcher(t);
\r
5010 // No W3C Range support
\r
5011 if (!t.win.getSelection)
\r
5012 t.tridentSel = new tinymce.dom.TridentSelection(t);
\r
5015 tinymce.addUnload(t.destroy, t);
\r
5018 getContent : function(s) {
\r
5019 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
\r
5024 s.format = s.format || 'html';
\r
5025 t.onBeforeGetContent.dispatch(t, s);
\r
5027 if (s.format == 'text')
\r
5028 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
\r
5030 if (r.cloneContents) {
\r
5031 n = r.cloneContents();
\r
5035 } else if (is(r.item) || is(r.htmlText))
\r
5036 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
\r
5038 e.innerHTML = r.toString();
\r
5040 // Keep whitespace before and after
\r
5041 if (/^\s/.test(e.innerHTML))
\r
5044 if (/\s+$/.test(e.innerHTML))
\r
5047 s.getInner = true;
\r
5049 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
\r
5050 t.onGetContent.dispatch(t, s);
\r
5055 setContent : function(h, s) {
\r
5056 var t = this, r = t.getRng(), c, d = t.win.document;
\r
5058 s = s || {format : 'html'};
\r
5060 h = s.content = t.dom.processHTML(h);
\r
5062 // Dispatch before set content event
\r
5063 t.onBeforeSetContent.dispatch(t, s);
\r
5066 if (r.insertNode) {
\r
5067 // Make caret marker since insertNode places the caret in the beginning of text after insert
\r
5068 h += '<span id="__caret">_</span>';
\r
5070 // Delete and insert new node
\r
5071 if (r.startContainer == d && r.endContainer == d) {
\r
5072 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
\r
5073 d.body.innerHTML = h;
\r
5075 r.deleteContents();
\r
5076 r.insertNode(t.getRng().createContextualFragment(h));
\r
5079 // Move to caret marker
\r
5080 c = t.dom.get('__caret');
\r
5082 // Make sure we wrap it compleatly, Opera fails with a simple select call
\r
5083 r = d.createRange();
\r
5084 r.setStartBefore(c);
\r
5085 r.setEndBefore(c);
\r
5088 // Remove the caret position
\r
5089 t.dom.remove('__caret');
\r
5092 // Delete content and get caret text selection
\r
5093 d.execCommand('Delete', false, null);
\r
5100 // Dispatch set content event
\r
5101 t.onSetContent.dispatch(t, s);
\r
5104 getStart : function() {
\r
5105 var t = this, r = t.getRng(), e;
\r
5111 r = r.duplicate();
\r
5113 e = r.parentElement();
\r
5115 if (e && e.nodeName == 'BODY')
\r
5116 return e.firstChild || e;
\r
5120 e = r.startContainer;
\r
5122 if (e.nodeType == 1 && e.hasChildNodes())
\r
5123 e = e.childNodes[Math.min(e.childNodes.length - 1, r.startOffset)];
\r
5125 if (e && e.nodeType == 3)
\r
5126 return e.parentNode;
\r
5132 getEnd : function() {
\r
5133 var t = this, r = t.getRng(), e, eo;
\r
5139 r = r.duplicate();
\r
5141 e = r.parentElement();
\r
5143 if (e && e.nodeName == 'BODY')
\r
5144 return e.lastChild || e;
\r
5148 e = r.endContainer;
\r
5151 if (e.nodeType == 1 && e.hasChildNodes())
\r
5152 e = e.childNodes[eo > 0 ? eo - 1 : eo];
\r
5154 if (e && e.nodeType == 3)
\r
5155 return e.parentNode;
\r
5161 getBookmark : function(type, normalized) {
\r
5162 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
\r
5164 function findIndex(name, element) {
\r
5167 each(dom.select(name), function(node, i) {
\r
5168 if (node == element)
\r
5176 function getLocation() {
\r
5177 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
\r
5179 function getPoint(rng, start) {
\r
5180 var indexes = [], node, lastIdx,
\r
5181 container = rng[start ? 'startContainer' : 'endContainer'],
\r
5182 offset = rng[start ? 'startOffset' : 'endOffset'], exclude, point = {};
\r
5184 // Resolve element index
\r
5185 if (container.nodeType == 1 && container.hasChildNodes()) {
\r
5186 lastIdx = container.childNodes.length - 1;
\r
5187 point.exclude = (start && offset > lastIdx) || (!start && offset == 0);
\r
5189 if (!start && offset)
\r
5192 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
\r
5194 if (container.nodeType == 3)
\r
5195 offset = start ? 0 : container.nodeValue.length;
\r
5198 if (container.nodeType == 3) {
\r
5200 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
\r
5201 offset += node.nodeValue.length;
\r
5204 point.offset = offset;
\r
5207 for (; container && container != root; container = container.parentNode)
\r
5208 indexes.push(t.dom.nodeIndex(container, normalized));
\r
5210 point.indexes = indexes;
\r
5215 bookmark.start = getPoint(rng, true);
\r
5217 if (!t.isCollapsed())
\r
5218 bookmark.end = getPoint(rng);
\r
5223 return getLocation();
\r
5226 // Handle simple range
\r
5228 return {rng : t.getRng()};
\r
5231 id = dom.uniqueId();
\r
5232 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
\r
5233 styles = 'overflow:hidden;line-height:0px';
\r
5235 // Explorer method
\r
5236 if (rng.duplicate || rng.item) {
\r
5239 rng2 = rng.duplicate();
\r
5241 // Insert start marker
\r
5243 rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
\r
5245 // Insert end marker
\r
5247 rng2.collapse(false);
\r
5248 rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
\r
5251 // Control selection
\r
5252 element = rng.item(0);
\r
5253 name = element.nodeName;
\r
5255 return {name : name, index : findIndex(name, element)};
\r
5258 element = t.getNode();
\r
5259 name = element.nodeName;
\r
5260 if (name == 'IMG')
\r
5261 return {name : name, index : findIndex(name, element)};
\r
5264 rng2 = rng.cloneRange();
\r
5266 // Insert end marker
\r
5268 rng2.collapse(false);
\r
5269 rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
\r
5272 rng.collapse(true);
\r
5273 rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
\r
5276 t.moveToBookmark({id : id, keep : 1});
\r
5281 moveToBookmark : function(bookmark) {
\r
5282 var t = this, dom = t.dom, marker1, marker2, rng, root;
\r
5284 // Clear selection cache
\r
5286 t.tridentSel.destroy();
\r
5289 if (bookmark.start) {
\r
5290 rng = dom.createRng();
\r
5291 root = dom.getRoot();
\r
5293 function setEndPoint(start) {
\r
5294 var point = bookmark[start ? 'start' : 'end'], i, node, offset;
\r
5297 for (node = root, i = point.indexes.length - 1; i >= 0; i--)
\r
5298 node = node.childNodes[point.indexes[i]] || node;
\r
5301 if (node.nodeType == 3 && point.offset)
\r
5302 rng.setStart(node, point.offset);
\r
5304 if (point.exclude)
\r
5305 rng.setStartAfter(node);
\r
5307 rng.setStartBefore(node);
\r
5310 if (node.nodeType == 3 && point.offset)
\r
5311 rng.setEnd(node, point.offset);
\r
5313 if (point.exclude)
\r
5314 rng.setEndBefore(node);
\r
5316 rng.setEndAfter(node);
\r
5322 setEndPoint(true);
\r
5326 } else if (bookmark.id) {
\r
5327 rng = dom.createRng();
\r
5329 function restoreEndPoint(suffix) {
\r
5330 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
\r
5333 node = marker.parentNode;
\r
5335 if (suffix == 'start') {
\r
5337 idx = dom.nodeIndex(marker);
\r
5343 rng.setStart(node, idx);
\r
5344 rng.setEnd(node, idx);
\r
5347 idx = dom.nodeIndex(marker);
\r
5353 rng.setEnd(node, idx);
\r
5357 prev = marker.previousSibling;
\r
5358 next = marker.nextSibling;
\r
5360 // Remove all marker text nodes
\r
5361 each(tinymce.grep(marker.childNodes), function(node) {
\r
5362 if (node.nodeType == 3)
\r
5363 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
\r
5366 // Remove marker but keep children if for example contents where inserted into the marker
\r
5367 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
\r
5368 while (marker = dom.get(bookmark.id + '_' + suffix))
\r
5369 dom.remove(marker, 1);
\r
5371 // If siblings are text nodes then merge them
\r
5372 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
\r
5373 idx = prev.nodeValue.length;
\r
5374 prev.appendData(next.nodeValue);
\r
5377 if (suffix == 'start') {
\r
5378 rng.setStart(prev, idx);
\r
5379 rng.setEnd(prev, idx);
\r
5381 rng.setEnd(prev, idx);
\r
5387 // Restore start/end points
\r
5388 restoreEndPoint('start');
\r
5389 restoreEndPoint('end');
\r
5392 } else if (bookmark.name) {
\r
5393 t.select(dom.select(bookmark.name)[bookmark.index]);
\r
5394 } else if (bookmark.rng)
\r
5395 t.setRng(bookmark.rng);
\r
5399 select : function(node, content) {
\r
5400 var t = this, dom = t.dom, rng = dom.createRng(), idx;
\r
5402 idx = dom.nodeIndex(node);
\r
5403 rng.setStart(node.parentNode, idx);
\r
5404 rng.setEnd(node.parentNode, idx + 1);
\r
5406 // Find first/last text node or BR element
\r
5408 function setPoint(node, start) {
\r
5409 var walker = new tinymce.dom.TreeWalker(node, node);
\r
5413 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
\r
5415 rng.setStart(node, 0);
\r
5417 rng.setEnd(node, node.nodeValue.length);
\r
5423 if (node.nodeName == 'BR') {
\r
5425 rng.setStartBefore(node);
\r
5427 rng.setEndBefore(node);
\r
5431 } while (node = (start ? walker.next() : walker.prev()));
\r
5434 setPoint(node, 1);
\r
5443 isCollapsed : function() {
\r
5444 var t = this, r = t.getRng(), s = t.getSel();
\r
5449 if (r.compareEndPoints)
\r
5450 return r.compareEndPoints('StartToEnd', r) === 0;
\r
5452 return !s || r.collapsed;
\r
5455 collapse : function(b) {
\r
5456 var t = this, r = t.getRng(), n;
\r
5458 // Control range on IE
\r
5461 r = this.win.document.body.createTextRange();
\r
5462 r.moveToElementText(n);
\r
5469 getSel : function() {
\r
5470 var t = this, w = this.win;
\r
5472 return w.getSelection ? w.getSelection() : w.document.selection;
\r
5475 getRng : function(w3c) {
\r
5476 var t = this, s, r;
\r
5478 // Found tridentSel object then we need to use that one
\r
5479 if (w3c && t.tridentSel)
\r
5480 return t.tridentSel.getRangeAt(0);
\r
5483 if (s = t.getSel())
\r
5484 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
\r
5486 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
\r
5489 // No range found then create an empty one
\r
5490 // This can occur when the editor is placed in a hidden container element on Gecko
\r
5491 // Or on IE when there was an exception
\r
5493 r = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange();
\r
5498 setRng : function(r) {
\r
5501 if (!t.tridentSel) {
\r
5505 s.removeAllRanges();
\r
5510 if (r.cloneRange) {
\r
5511 t.tridentSel.addRange(r);
\r
5515 // Is IE specific range
\r
5519 // Needed for some odd IE bug #1843306
\r
5524 setNode : function(n) {
\r
5527 t.setContent(t.dom.getOuterHTML(n));
\r
5532 getNode : function() {
\r
5533 var t = this, rng = t.getRng(), sel = t.getSel(), elm;
\r
5536 // Range maybe lost after the editor is made visible again
\r
5538 return t.dom.getRoot();
\r
5540 elm = rng.commonAncestorContainer;
\r
5542 // Handle selection a image or other control like element such as anchors
\r
5543 if (!rng.collapsed) {
\r
5544 if (rng.startContainer == rng.endContainer) {
\r
5545 if (rng.startOffset - rng.endOffset < 2) {
\r
5546 if (rng.startContainer.hasChildNodes())
\r
5547 elm = rng.startContainer.childNodes[rng.startOffset];
\r
5551 // If the anchor node is a element instead of a text node then return this element
\r
5552 if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
\r
5553 return sel.anchorNode.childNodes[sel.anchorOffset];
\r
5556 if (elm && elm.nodeType == 3)
\r
5557 return elm.parentNode;
\r
5562 return rng.item ? rng.item(0) : rng.parentElement();
\r
5565 getSelectedBlocks : function(st, en) {
\r
5566 var t = this, dom = t.dom, sb, eb, n, bl = [];
\r
5568 sb = dom.getParent(st || t.getStart(), dom.isBlock);
\r
5569 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
\r
5574 if (sb && eb && sb != eb) {
\r
5577 while ((n = n.nextSibling) && n != eb) {
\r
5578 if (dom.isBlock(n))
\r
5583 if (eb && sb != eb)
\r
5589 destroy : function(s) {
\r
5595 t.tridentSel.destroy();
\r
5597 // Manual destroy then remove unload handler
\r
5599 tinymce.removeUnload(t.destroy);
\r
5604 (function(tinymce) {
\r
5605 tinymce.create('tinymce.dom.XMLWriter', {
\r
5608 XMLWriter : function(s) {
\r
5609 // Get XML document
\r
5610 function getXML() {
\r
5611 var i = document.implementation;
\r
5613 if (!i || !i.createDocument) {
\r
5615 try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
\r
5616 try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
\r
5618 return i.createDocument('', '', null);
\r
5621 this.doc = getXML();
\r
5623 // Since Opera and WebKit doesn't escape > into > we need to do it our self to normalize the output for all browsers
\r
5624 this.valid = tinymce.isOpera || tinymce.isWebKit;
\r
5629 reset : function() {
\r
5630 var t = this, d = t.doc;
\r
5633 d.removeChild(d.firstChild);
\r
5635 t.node = d.appendChild(d.createElement("html"));
\r
5638 writeStartElement : function(n) {
\r
5641 t.node = t.node.appendChild(t.doc.createElement(n));
\r
5644 writeAttribute : function(n, v) {
\r
5646 v = v.replace(/>/g, '%MCGT%');
\r
5648 this.node.setAttribute(n, v);
\r
5651 writeEndElement : function() {
\r
5652 this.node = this.node.parentNode;
\r
5655 writeFullEndElement : function() {
\r
5656 var t = this, n = t.node;
\r
5658 n.appendChild(t.doc.createTextNode(""));
\r
5659 t.node = n.parentNode;
\r
5662 writeText : function(v) {
\r
5664 v = v.replace(/>/g, '%MCGT%');
\r
5666 this.node.appendChild(this.doc.createTextNode(v));
\r
5669 writeCDATA : function(v) {
\r
5670 this.node.appendChild(this.doc.createCDATASection(v));
\r
5673 writeComment : function(v) {
\r
5674 // Fix for bug #2035694
\r
5676 v = v.replace(/^\-|\-$/g, ' ');
\r
5678 this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
\r
5681 getContent : function() {
\r
5684 h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
\r
5685 h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
\r
5686 h = h.replace(/ ?\/>/g, ' />');
\r
5689 h = h.replace(/\%MCGT%/g, '>');
\r
5696 (function(tinymce) {
\r
5697 tinymce.create('tinymce.dom.StringWriter', {
\r
5704 StringWriter : function(s) {
\r
5705 this.settings = tinymce.extend({
\r
5706 indent_char : ' ',
\r
5713 reset : function() {
\r
5720 writeStartElement : function(n) {
\r
5721 this._writeAttributesEnd();
\r
5722 this.writeRaw('<' + n);
\r
5723 this.tags.push(n);
\r
5724 this.inAttr = true;
\r
5726 this.elementCount = this.count;
\r
5729 writeAttribute : function(n, v) {
\r
5732 t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
\r
5735 writeEndElement : function() {
\r
5738 if (this.tags.length > 0) {
\r
5739 n = this.tags.pop();
\r
5741 if (this._writeAttributesEnd(1))
\r
5742 this.writeRaw('</' + n + '>');
\r
5744 if (this.settings.indentation > 0)
\r
5745 this.writeRaw('\n');
\r
5749 writeFullEndElement : function() {
\r
5750 if (this.tags.length > 0) {
\r
5751 this._writeAttributesEnd();
\r
5752 this.writeRaw('</' + this.tags.pop() + '>');
\r
5754 if (this.settings.indentation > 0)
\r
5755 this.writeRaw('\n');
\r
5759 writeText : function(v) {
\r
5760 this._writeAttributesEnd();
\r
5761 this.writeRaw(this.encode(v));
\r
5765 writeCDATA : function(v) {
\r
5766 this._writeAttributesEnd();
\r
5767 this.writeRaw('<![CDATA[' + v + ']]>');
\r
5771 writeComment : function(v) {
\r
5772 this._writeAttributesEnd();
\r
5773 this.writeRaw('<!-- ' + v + '-->');
\r
5777 writeRaw : function(v) {
\r
5781 encode : function(s) {
\r
5782 return s.replace(/[<>&"]/g, function(v) {
\r
5801 getContent : function() {
\r
5805 _writeAttributesEnd : function(s) {
\r
5809 this.inAttr = false;
\r
5811 if (s && this.elementCount == this.count) {
\r
5812 this.writeRaw(' />');
\r
5816 this.writeRaw('>');
\r
5823 (function(tinymce) {
\r
5825 var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
\r
5827 function wildcardToRE(s) {
\r
5828 return s.replace(/([?+*])/g, '.$1');
\r
5831 tinymce.create('tinymce.dom.Serializer', {
\r
5832 Serializer : function(s) {
\r
5836 t.onPreProcess = new Dispatcher(t);
\r
5837 t.onPostProcess = new Dispatcher(t);
\r
5840 t.writer = new tinymce.dom.XMLWriter();
\r
5842 // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
\r
5843 t.writer = new tinymce.dom.StringWriter();
\r
5846 // Default settings
\r
5847 t.settings = s = extend({
\r
5848 dom : tinymce.DOM,
\r
5852 invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
\r
5853 closed : /^(br|hr|input|meta|img|link|param|area)$/,
\r
5854 entity_encoding : 'named',
\r
5855 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
5856 valid_elements : '*[*]',
\r
5857 extended_valid_elements : 0,
\r
5858 invalid_elements : 0,
\r
5859 fix_table_elements : 1,
\r
5860 fix_list_elements : true,
\r
5861 fix_content_duplication : true,
\r
5862 convert_fonts_to_spans : false,
\r
5863 font_size_classes : 0,
\r
5864 apply_source_formatting : 0,
\r
5865 indent_mode : 'simple',
\r
5866 indent_char : '\t',
\r
5867 indent_levels : 1,
\r
5868 remove_linebreaks : 1,
\r
5869 remove_redundant_brs : 1,
\r
5870 element_format : 'xhtml'
\r
5874 t.schema = s.schema;
\r
5876 // Use raw entities if no entities are defined
\r
5877 if (s.entity_encoding == 'named' && !s.entities)
\r
5878 s.entity_encoding = 'raw';
\r
5880 if (s.remove_redundant_brs) {
\r
5881 t.onPostProcess.add(function(se, o) {
\r
5882 // Remove single BR at end of block elements since they get rendered
\r
5883 o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
\r
5884 // Check if it's a single element
\r
5885 if (/^<br \/>\s*<\//.test(a))
\r
5886 return '</' + c + '>';
\r
5893 // Remove XHTML element endings i.e. produce crap :) XHTML is better
\r
5894 if (s.element_format == 'html') {
\r
5895 t.onPostProcess.add(function(se, o) {
\r
5896 o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
\r
5900 if (s.fix_list_elements) {
\r
5901 t.onPreProcess.add(function(se, o) {
\r
5902 var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
\r
5904 function prevNode(e, n) {
\r
5905 var a = n.split(','), i;
\r
5907 while ((e = e.previousSibling) != null) {
\r
5908 for (i=0; i<a.length; i++) {
\r
5909 if (e.nodeName == a[i])
\r
5917 for (x=0; x<a.length; x++) {
\r
5918 nl = t.dom.select(a[x], o.node);
\r
5920 for (i=0; i<nl.length; i++) {
\r
5924 if (r.test(p.nodeName)) {
\r
5925 np = prevNode(n, 'LI');
\r
5928 np = t.dom.create('li');
\r
5929 np.innerHTML = ' ';
\r
5930 np.appendChild(n);
\r
5931 p.insertBefore(np, p.firstChild);
\r
5933 np.appendChild(n);
\r
5940 if (s.fix_table_elements) {
\r
5941 t.onPreProcess.add(function(se, o) {
\r
5942 // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
\r
5943 // so Opera users with an older version will have to live with less compaible output not much we can do here
\r
5944 if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
\r
5945 each(t.dom.select('p table', o.node).reverse(), function(n) {
\r
5946 var parent = t.dom.getParent(n.parentNode, 'table,p');
\r
5948 if (parent.nodeName != 'TABLE') {
\r
5950 t.dom.split(parent, n);
\r
5952 // IE can sometimes fire an unknown runtime error so we just ignore it
\r
5961 setEntities : function(s) {
\r
5962 var t = this, a, i, l = {}, v;
\r
5964 // No need to setup more than once
\r
5965 if (t.entityLookup)
\r
5968 // Build regex and lookup array
\r
5970 for (i = 0; i < a.length; i += 2) {
\r
5973 // Don't add default & " etc.
\r
5974 if (v == 34 || v == 38 || v == 60 || v == 62)
\r
5977 l[String.fromCharCode(a[i])] = a[i + 1];
\r
5979 v = parseInt(a[i]).toString(16);
\r
5982 t.entityLookup = l;
\r
5985 setRules : function(s) {
\r
5991 t.validElements = {};
\r
5993 return t.addRules(s);
\r
5996 addRules : function(s) {
\r
6004 each(s.split(','), function(s) {
\r
6005 var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
\r
6007 // Extend with default rules
\r
6009 at = tinymce.extend([], dr.attribs);
\r
6011 // Parse attributes
\r
6012 if (p.length > 1) {
\r
6013 each(p[1].split('|'), function(s) {
\r
6018 // Parse attribute rule
\r
6019 s = s.replace(/::/g, '~');
\r
6020 s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
\r
6021 s[2] = s[2].replace(/~/g, ':');
\r
6023 // Add required attributes
\r
6024 if (s[1] == '!') {
\r
6029 // Remove inherited attributes
\r
6030 if (s[1] == '-') {
\r
6031 for (i = 0; i <at.length; i++) {
\r
6032 if (at[i].name == s[2]) {
\r
6040 // Add default attrib values
\r
6042 ar.defaultVal = s[4] || '';
\r
6045 // Add forced attrib values
\r
6047 ar.forcedVal = s[4];
\r
6050 // Add validation values
\r
6052 ar.validVals = s[4].split('?');
\r
6056 if (/[*.?]/.test(s[2])) {
\r
6058 ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
\r
6069 // Handle element names
\r
6070 each(tn, function(s, i) {
\r
6071 var pr = s.charAt(0), x = 1, ru = {};
\r
6073 // Extend with default rule data
\r
6076 ru.noEmpty = dr.noEmpty;
\r
6079 ru.fullEnd = dr.fullEnd;
\r
6082 ru.padd = dr.padd;
\r
6085 // Handle prefixes
\r
6088 ru.noEmpty = true;
\r
6092 ru.fullEnd = true;
\r
6103 tn[i] = s = s.substring(x);
\r
6104 t.validElements[s] = 1;
\r
6106 // Add element name or element regex
\r
6107 if (/[*.?]/.test(tn[0])) {
\r
6108 ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
\r
6109 t.wildRules = t.wildRules || {};
\r
6110 t.wildRules.push(ru);
\r
6114 // Store away default rule
\r
6124 ru.requiredAttribs = ra;
\r
6127 // Build valid attributes regexp
\r
6129 each(va, function(v) {
\r
6133 s += '(' + wildcardToRE(v) + ')';
\r
6135 ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
\r
6136 ru.wildAttribs = wat;
\r
6141 // Build valid elements regexp
\r
6143 each(t.validElements, function(v, k) {
\r
6150 t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
\r
6152 //console.debug(t.validElementsRE.toString());
\r
6153 //console.dir(t.rules);
\r
6154 //console.dir(t.wildRules);
\r
6157 findRule : function(n) {
\r
6158 var t = this, rl = t.rules, i, r;
\r
6169 for (i = 0; i < rl.length; i++) {
\r
6170 if (rl[i].nameRE.test(n))
\r
6177 findAttribRule : function(ru, n) {
\r
6178 var i, wa = ru.wildAttribs;
\r
6180 for (i = 0; i < wa.length; i++) {
\r
6181 if (wa[i].nameRE.test(n))
\r
6188 serialize : function(n, o) {
\r
6189 var h, t = this, doc, oldDoc, impl, selected;
\r
6193 o.format = o.format || 'html';
\r
6196 // IE looses the selected attribute on option elements so we need to store it
\r
6197 // See: http://support.microsoft.com/kb/829907
\r
6200 each(n.getElementsByTagName('option'), function(n) {
\r
6201 var v = t.dom.getAttrib(n, 'selected');
\r
6203 selected.push(v ? v : null);
\r
6207 n = n.cloneNode(true);
\r
6209 // IE looses the selected attribute on option elements so we need to restore it
\r
6211 each(n.getElementsByTagName('option'), function(n, i) {
\r
6212 t.dom.setAttrib(n, 'selected', selected[i]);
\r
6216 // Nodes needs to be attached to something in WebKit/Opera
\r
6217 // Older builds of Opera crashes if you attach the node to an document created dynamically
\r
6218 // and since we can't feature detect a crash we need to sniff the acutal build number
\r
6219 // This fix will make DOM ranges and make Sizzle happy!
\r
6220 impl = n.ownerDocument.implementation;
\r
6221 if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
\r
6222 // Create an empty HTML document
\r
6223 doc = impl.createHTMLDocument("");
\r
6225 // Add the element or it's children if it's a body element to the new document
\r
6226 each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
\r
6227 doc.body.appendChild(doc.importNode(node, true));
\r
6230 // Grab first child or body element for serialization
\r
6231 if (n.nodeName != 'BODY')
\r
6232 n = doc.body.firstChild;
\r
6236 // set the new document in DOMUtils so createElement etc works
\r
6237 oldDoc = t.dom.doc;
\r
6241 t.key = '' + (parseInt(t.key) + 1);
\r
6244 if (!o.no_events) {
\r
6246 t.onPreProcess.dispatch(t, o);
\r
6249 // Serialize HTML DOM into a string
\r
6252 t._serializeNode(n, o.getInner);
\r
6255 o.content = t.writer.getContent();
\r
6257 // Restore the old document if it was changed
\r
6259 t.dom.doc = oldDoc;
\r
6262 t.onPostProcess.dispatch(t, o);
\r
6264 t._postProcess(o);
\r
6267 return tinymce.trim(o.content);
\r
6270 // Internal functions
\r
6272 _postProcess : function(o) {
\r
6273 var t = this, s = t.settings, h = o.content, sc = [], p;
\r
6275 if (o.format == 'html') {
\r
6276 // Protect some elements
\r
6280 {pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
\r
6281 {pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
\r
6282 {pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
\r
6283 {pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
\r
6284 {pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
\r
6291 if (s.entity_encoding !== 'raw')
\r
6294 // Use BR instead of padded P elements inside editor and use <p> </p> outside editor
\r
6296 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
\r
6298 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
\r
6300 // Since Gecko and Safari keeps whitespace in the DOM we need to
\r
6301 // remove it inorder to match other browsers. But I think Gecko and Safari is right.
\r
6302 // This process is only done when getting contents out from the editor.
\r
6304 // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
\r
6305 h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1> </p>' : '<p$1> </p>');
\r
6307 if (s.remove_linebreaks) {
\r
6308 h = h.replace(/\r?\n|\r/g, ' ');
\r
6309 h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
\r
6310 h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
\r
6311 h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start
\r
6312 h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>'); // Trim block start
\r
6313 h = h.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, '</$1>'); // Trim block end
\r
6316 // Simple indentation
\r
6317 if (s.apply_source_formatting && s.indent_mode == 'simple') {
\r
6318 // Add line breaks before and after block elements
\r
6319 h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
\r
6320 h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
\r
6321 h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
\r
6322 h = h.replace(/\n\n/g, '\n');
\r
6326 h = t._unprotect(h, p);
\r
6328 // Restore CDATA sections
\r
6329 h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
\r
6331 // Restore the \u00a0 character if raw mode is enabled
\r
6332 if (s.entity_encoding == 'raw')
\r
6333 h = h.replace(/<p> <\/p>|<p([^>]+)> <\/p>/g, '<p$1>\u00a0</p>');
\r
6335 // Restore noscript elements
\r
6336 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
6337 return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
\r
6344 _serializeNode : function(n, inner) {
\r
6345 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;
\r
6347 if (!s.node_filter || s.node_filter(n)) {
\r
6348 switch (n.nodeType) {
\r
6349 case 1: // Element
\r
6350 if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
\r
6353 iv = keep = false;
\r
6354 hc = n.hasChildNodes();
\r
6355 nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
\r
6357 // Get internal type
\r
6358 type = n.getAttribute('_mce_type');
\r
6360 if (!t._info.cleanup) {
\r
6367 // Add correct prefix on IE
\r
6369 if (n.scopeName !== 'HTML' && n.scopeName !== 'html')
\r
6370 nn = n.scopeName + ':' + nn;
\r
6373 // Remove mce prefix on IE needed for the abbr element
\r
6374 if (nn.indexOf('mce:') === 0)
\r
6375 nn = nn.substring(4);
\r
6379 if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
\r
6386 // Fix IE content duplication (DOM can have multiple copies of the same node)
\r
6387 if (s.fix_content_duplication) {
\r
6388 if (n._mce_serialized == t.key)
\r
6391 n._mce_serialized = t.key;
\r
6394 // IE sometimes adds a / infront of the node name
\r
6395 if (nn.charAt(0) == '/')
\r
6396 nn = nn.substring(1);
\r
6397 } else if (isGecko) {
\r
6398 // Ignore br elements
\r
6399 if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
\r
6403 // Check if valid child
\r
6404 if (s.validate_children) {
\r
6405 if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
\r
6410 t.elementName = nn;
\r
6413 ru = t.findRule(nn);
\r
6414 nn = ru.name || nn;
\r
6415 closed = s.closed.test(nn);
\r
6417 // Skip empty nodes or empty node name in IE
\r
6418 if ((!hc && ru.noEmpty) || (isIE && !nn)) {
\r
6424 if (ru.requiredAttribs) {
\r
6425 a = ru.requiredAttribs;
\r
6427 for (i = a.length - 1; i >= 0; i--) {
\r
6428 if (this.dom.getAttrib(n, a[i]) !== '')
\r
6432 // None of the required was there
\r
6439 w.writeStartElement(nn);
\r
6441 // Add ordered attributes
\r
6443 for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
\r
6445 v = t._getAttrib(n, a);
\r
6448 w.writeAttribute(a.name, v);
\r
6452 // Add wild attributes
\r
6453 if (ru.validAttribsRE) {
\r
6454 at = t.dom.getAttribs(n);
\r
6455 for (i=at.length-1; i>-1; i--) {
\r
6458 if (no.specified) {
\r
6459 a = no.nodeName.toLowerCase();
\r
6461 if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
\r
6464 ar = t.findAttribRule(ru, a);
\r
6465 v = t._getAttrib(n, ar, a);
\r
6468 w.writeAttribute(a, v);
\r
6473 // Keep type attribute
\r
6475 w.writeAttribute('_mce_type', type);
\r
6477 // Write text from script
\r
6478 if (nn === 'script' && tinymce.trim(n.innerHTML)) {
\r
6479 w.writeText('// '); // Padd it with a comment so it will parse on older browsers
\r
6480 w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
\r
6485 // Padd empty nodes with a
\r
6487 // If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
\r
6488 if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
\r
6489 if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
\r
6490 w.writeText('\u00a0');
\r
6492 w.writeText('\u00a0'); // No children then padd it
\r
6498 // Check if valid child
\r
6499 if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
\r
6502 return w.writeText(n.nodeValue);
\r
6505 return w.writeCDATA(n.nodeValue);
\r
6507 case 8: // Comment
\r
6508 return w.writeComment(n.nodeValue);
\r
6510 } else if (n.nodeType == 1)
\r
6511 hc = n.hasChildNodes();
\r
6513 if (hc && !closed) {
\r
6514 cn = n.firstChild;
\r
6517 t._serializeNode(cn);
\r
6518 t.elementName = nn;
\r
6519 cn = cn.nextSibling;
\r
6523 // Write element end
\r
6526 w.writeFullEndElement();
\r
6528 w.writeEndElement();
\r
6532 _protect : function(o) {
\r
6535 o.items = o.items || [];
\r
6538 return s.replace(/[\r\n\\]/g, function(c) {
\r
6541 else if (c === '\\')
\r
6549 return s.replace(/\\[\\rn]/g, function(c) {
\r
6552 else if (c === '\\\\')
\r
6559 each(o.patterns, function(p) {
\r
6560 o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
\r
6567 return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
\r
6574 _unprotect : function(h, o) {
\r
6575 h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
\r
6576 return o.items[parseInt(b)];
\r
6584 _encode : function(h) {
\r
6585 var t = this, s = t.settings, l;
\r
6588 if (s.entity_encoding !== 'raw') {
\r
6589 if (s.entity_encoding.indexOf('named') != -1) {
\r
6590 t.setEntities(s.entities);
\r
6591 l = t.entityLookup;
\r
6593 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
6597 a = '&' + v + ';';
\r
6603 if (s.entity_encoding.indexOf('numeric') != -1) {
\r
6604 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
6605 return '&#' + a.charCodeAt(0) + ';';
\r
6613 _setup : function() {
\r
6614 var t = this, s = this.settings;
\r
6621 t.setRules(s.valid_elements);
\r
6622 t.addRules(s.extended_valid_elements);
\r
6624 if (s.invalid_elements)
\r
6625 t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
\r
6627 if (s.attrib_value_filter)
\r
6628 t.attribValueFilter = s.attribValueFilter;
\r
6631 _getAttrib : function(n, a, na) {
\r
6634 na = na || a.name;
\r
6636 if (a.forcedVal && (v = a.forcedVal)) {
\r
6637 if (v === '{$uid}')
\r
6638 return this.dom.uniqueId();
\r
6643 v = this.dom.getAttrib(n, na);
\r
6648 // Whats the point? Remove usless attribute value
\r
6655 if (this.attribValueFilter)
\r
6656 v = this.attribValueFilter(na, v, n);
\r
6658 if (a.validVals) {
\r
6659 for (i = a.validVals.length - 1; i >= 0; i--) {
\r
6660 if (v == a.validVals[i])
\r
6668 if (v === '' && typeof(a.defaultVal) != 'undefined') {
\r
6671 if (v === '{$uid}')
\r
6672 return this.dom.uniqueId();
\r
6676 // Remove internal mceItemXX classes when content is extracted from editor
\r
6677 if (na == 'class' && this.processObj.get)
\r
6678 v = v.replace(/\s?mceItem\w+\s?/g, '');
\r
6690 (function(tinymce) {
\r
6691 tinymce.dom.ScriptLoader = function(settings) {
\r
6697 scriptLoadedCallbacks = {},
\r
6698 queueLoadedCallbacks = [],
\r
6702 function loadScript(url, callback) {
\r
6703 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
\r
6705 // Execute callback when script is loaded
\r
6710 elm.onreadystatechange = elm.onload = elm = null;
\r
6715 id = dom.uniqueId();
\r
6717 if (tinymce.isIE6) {
\r
6718 uri = new tinymce.util.URI(url);
\r
6721 // If script is from same domain and we
\r
6722 // use IE 6 then use XHR since it's more reliable
\r
6723 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
\r
6724 tinymce.util.XHR.send({
\r
6725 url : tinymce._addVer(uri.getURI()),
\r
6726 success : function(content) {
\r
6727 // Create new temp script element
\r
6728 var script = dom.create('script', {
\r
6729 type : 'text/javascript'
\r
6732 // Evaluate script in global scope
\r
6733 script.text = content;
\r
6734 document.getElementsByTagName('head')[0].appendChild(script);
\r
6735 dom.remove(script);
\r
6745 // Create new script element
\r
6746 elm = dom.create('script', {
\r
6748 type : 'text/javascript',
\r
6749 src : tinymce._addVer(url)
\r
6752 // Add onload and readystate listeners
\r
6753 elm.onload = done;
\r
6754 elm.onreadystatechange = function() {
\r
6755 var state = elm.readyState;
\r
6757 // Loaded state is passed on IE 6 however there
\r
6758 // are known issues with this method but we can't use
\r
6759 // XHR in a cross domain loading
\r
6760 if (state == 'complete' || state == 'loaded')
\r
6764 // Most browsers support this feature so we report errors
\r
6765 // for those at least to help users track their missing plugins etc
\r
6766 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
\r
6767 /*elm.onerror = function() {
\r
6768 alert('Failed to load: ' + url);
\r
6771 // Add script to document
\r
6772 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
\r
6775 this.isDone = function(url) {
\r
6776 return states[url] == LOADED;
\r
6779 this.markDone = function(url) {
\r
6780 states[url] = LOADED;
\r
6783 this.add = this.load = function(url, callback, scope) {
\r
6784 var item, state = states[url];
\r
6786 // Add url to load queue
\r
6787 if (state == undefined) {
\r
6789 states[url] = QUEUED;
\r
6793 // Store away callback for later execution
\r
6794 if (!scriptLoadedCallbacks[url])
\r
6795 scriptLoadedCallbacks[url] = [];
\r
6797 scriptLoadedCallbacks[url].push({
\r
6799 scope : scope || this
\r
6804 this.loadQueue = function(callback, scope) {
\r
6805 this.loadScripts(queue, callback, scope);
\r
6808 this.loadScripts = function(scripts, callback, scope) {
\r
6811 function execScriptLoadedCallbacks(url) {
\r
6812 // Execute URL callback functions
\r
6813 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
\r
6814 callback.func.call(callback.scope);
\r
6817 scriptLoadedCallbacks[url] = undefined;
\r
6820 queueLoadedCallbacks.push({
\r
6822 scope : scope || this
\r
6825 loadScripts = function() {
\r
6826 var loadingScripts = tinymce.grep(scripts);
\r
6828 // Current scripts has been handled
\r
6829 scripts.length = 0;
\r
6831 // Load scripts that needs to be loaded
\r
6832 tinymce.each(loadingScripts, function(url) {
\r
6833 // Script is already loaded then execute script callbacks directly
\r
6834 if (states[url] == LOADED) {
\r
6835 execScriptLoadedCallbacks(url);
\r
6839 // Is script not loading then start loading it
\r
6840 if (states[url] != LOADING) {
\r
6841 states[url] = LOADING;
\r
6844 loadScript(url, function() {
\r
6845 states[url] = LOADED;
\r
6848 execScriptLoadedCallbacks(url);
\r
6850 // Load more scripts if they where added by the recently loaded script
\r
6856 // No scripts are currently loading then execute all pending queue loaded callbacks
\r
6858 tinymce.each(queueLoadedCallbacks, function(callback) {
\r
6859 callback.func.call(callback.scope);
\r
6862 queueLoadedCallbacks.length = 0;
\r
6870 // Global script loader
\r
6871 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
\r
6874 tinymce.dom.TreeWalker = function(start_node, root_node) {
\r
6875 var node = start_node;
\r
6877 function findSibling(node, start_name, sibling_name, shallow) {
\r
6878 var sibling, parent;
\r
6881 // Walk into nodes if it has a start
\r
6882 if (!shallow && node[start_name])
\r
6883 return node[start_name];
\r
6885 // Return the sibling if it has one
\r
6886 if (node != root_node) {
\r
6887 sibling = node[sibling_name];
\r
6891 // Walk up the parents to look for siblings
\r
6892 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
\r
6893 sibling = parent[sibling_name];
\r
6901 this.current = function() {
\r
6905 this.next = function(shallow) {
\r
6906 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
\r
6909 this.prev = function(shallow) {
\r
6910 return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
\r
6915 var transitional = {};
\r
6917 function unpack(lookup, data) {
\r
6920 function replace(value) {
\r
6921 return value.replace(/[A-Z]+/g, function(key) {
\r
6922 return replace(lookup[key]);
\r
6927 for (key in lookup) {
\r
6928 if (lookup.hasOwnProperty(key))
\r
6929 lookup[key] = replace(lookup[key]);
\r
6932 // Unpack and parse data into object map
\r
6933 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
\r
6936 children = children.split(/\|/);
\r
6938 for (i = children.length - 1; i >= 0; i--)
\r
6939 map[children[i]] = 1;
\r
6941 transitional[name] = map;
\r
6945 // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
\r
6946 // we will later include the attributes here and use it as a default for valid elements but it
\r
6947 // requires us to rewrite the serializer engine
\r
6949 Z : '#|H|K|N|O|P',
\r
6950 Y : '#|X|form|R|Q',
\r
6951 X : 'p|T|div|U|W|isindex|fieldset|table',
\r
6952 W : 'pre|hr|blockquote|address|center|noframes',
\r
6953 U : 'ul|ol|dl|menu|dir',
\r
6954 ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
\r
6955 T : 'h1|h2|h3|h4|h5|h6',
\r
6958 ZA : '#|a|G|J|M|O|P',
\r
6959 R : '#|a|H|K|N|O',
\r
6961 P : 'ins|del|script',
\r
6962 O : 'input|select|textarea|label|button',
\r
6964 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
\r
6967 J : 'tt|i|b|u|s|strike',
\r
6968 I : 'big|small|font|basefont',
\r
6970 G : 'br|span|bdo',
\r
6971 F : 'object|applet|img|map|iframe'
\r
6974 'object[#|param|X|form|a|H|K|N|O|Q]' +
\r
6981 'applet[#|param|X|form|a|H|K|N|O|Q]' +
\r
6984 'map[X|form|Q|area]' +
\r
6986 'iframe[#|X|form|a|H|K|N|O|Q]' +
\r
7012 'select[optgroup|option]' +
\r
7013 'optgroup[option]' +
\r
7017 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
\r
7019 'ins[#|X|form|a|H|K|N|O|Q]' +
\r
7021 'del[#|X|form|a|H|K|N|O|Q]' +
\r
7023 'div[#|X|form|a|H|K|N|O|Q]' +
\r
7025 'li[#|X|form|a|H|K|N|O|Q]' +
\r
7029 'dd[#|X|form|a|H|K|N|O|Q]' +
\r
7034 'blockquote[#|X|form|a|H|K|N|O|Q]' +
\r
7036 'center[#|X|form|a|H|K|N|O|Q]' +
\r
7037 'noframes[#|X|form|a|H|K|N|O|Q]' +
\r
7039 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
\r
7041 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
\r
7044 'colgroup[col]' +
\r
7047 'th[#|X|form|a|H|K|N|O|Q]' +
\r
7048 'form[#|X|a|H|K|N|O|Q]' +
\r
7049 'noscript[#|X|form|a|H|K|N|O|Q]' +
\r
7050 'td[#|X|form|a|H|K|N|O|Q]' +
\r
7055 'body[#|X|form|a|H|K|N|O|Q]'
\r
7058 tinymce.dom.Schema = function() {
\r
7059 var t = this, elements = transitional;
\r
7061 t.isValid = function(name, child_name) {
\r
7062 var element = elements[name];
\r
7064 return !!(element && (!child_name || element[child_name]));
\r
7068 (function(tinymce) {
\r
7069 tinymce.dom.RangeUtils = function(dom) {
\r
7070 var INVISIBLE_CHAR = '\uFEFF';
\r
7072 this.walk = function(rng, callback) {
\r
7073 var startContainer = rng.startContainer,
\r
7074 startOffset = rng.startOffset,
\r
7075 endContainer = rng.endContainer,
\r
7076 endOffset = rng.endOffset,
\r
7077 ancestor, startPoint,
\r
7078 endPoint, node, parent, siblings, nodes;
\r
7080 // Handle table cell selection the table plugin enables
\r
7081 // you to fake select table cells and perform formatting actions on them
\r
7082 nodes = dom.select('td.mceSelected,th.mceSelected');
\r
7083 if (nodes.length > 0) {
\r
7084 tinymce.each(nodes, function(node) {
\r
7091 function collectSiblings(node, name, end_node) {
\r
7092 var siblings = [];
\r
7094 for (; node && node != end_node; node = node[name])
\r
7095 siblings.push(node);
\r
7100 function findEndPoint(node, root) {
\r
7102 if (node.parentNode == root)
\r
7105 node = node.parentNode;
\r
7109 function walkBoundary(start_node, end_node, next) {
\r
7110 var siblingName = next ? 'nextSibling' : 'previousSibling';
\r
7112 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
\r
7113 parent = node.parentNode;
\r
7114 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
\r
7116 if (siblings.length) {
\r
7118 siblings.reverse();
\r
7120 callback(siblings);
\r
7125 // If index based start position then resolve it
\r
7126 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
\r
7127 startContainer = startContainer.childNodes[startOffset];
\r
7129 // If index based end position then resolve it
\r
7130 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
\r
7131 endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
\r
7133 // Find common ancestor and end points
\r
7134 ancestor = dom.findCommonAncestor(startContainer, endContainer);
\r
7137 if (startContainer == endContainer)
\r
7138 return callback([startContainer]);
\r
7140 // Process left side
\r
7141 for (node = startContainer; node; node = node.parentNode) {
\r
7142 if (node == endContainer)
\r
7143 return walkBoundary(startContainer, ancestor, true);
\r
7145 if (node == ancestor)
\r
7149 // Process right side
\r
7150 for (node = endContainer; node; node = node.parentNode) {
\r
7151 if (node == startContainer)
\r
7152 return walkBoundary(endContainer, ancestor);
\r
7154 if (node == ancestor)
\r
7158 // Find start/end point
\r
7159 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
\r
7160 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
\r
7163 walkBoundary(startContainer, startPoint, true);
\r
7165 // Walk the middle from start to end point
\r
7166 siblings = collectSiblings(
\r
7167 startPoint == startContainer ? startPoint : startPoint.nextSibling,
\r
7169 endPoint == endContainer ? endPoint.nextSibling : endPoint
\r
7172 if (siblings.length)
\r
7173 callback(siblings);
\r
7175 // Walk right leaf
\r
7176 walkBoundary(endContainer, endPoint);
\r
7179 /* this.split = function(rng) {
\r
7180 var startContainer = rng.startContainer,
\r
7181 startOffset = rng.startOffset,
\r
7182 endContainer = rng.endContainer,
\r
7183 endOffset = rng.endOffset;
\r
7185 function splitText(node, offset) {
\r
7186 if (offset == node.nodeValue.length)
\r
7187 node.appendData(INVISIBLE_CHAR);
\r
7189 node = node.splitText(offset);
\r
7191 if (node.nodeValue === INVISIBLE_CHAR)
\r
7192 node.nodeValue = '';
\r
7197 // Handle single text node
\r
7198 if (startContainer == endContainer) {
\r
7199 if (startContainer.nodeType == 3) {
\r
7200 if (startOffset != 0)
\r
7201 startContainer = endContainer = splitText(startContainer, startOffset);
\r
7203 if (endOffset - startOffset != startContainer.nodeValue.length)
\r
7204 splitText(startContainer, endOffset - startOffset);
\r
7207 // Split startContainer text node if needed
\r
7208 if (startContainer.nodeType == 3 && startOffset != 0) {
\r
7209 startContainer = splitText(startContainer, startOffset);
\r
7213 // Split endContainer text node if needed
\r
7214 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
\r
7215 endContainer = splitText(endContainer, endOffset).previousSibling;
\r
7216 endOffset = endContainer.nodeValue.length;
\r
7221 startContainer : startContainer,
\r
7222 startOffset : startOffset,
\r
7223 endContainer : endContainer,
\r
7224 endOffset : endOffset
\r
7231 (function(tinymce) {
\r
7232 // Shorten class names
\r
7233 var DOM = tinymce.DOM, is = tinymce.is;
\r
7235 tinymce.create('tinymce.ui.Control', {
\r
7236 Control : function(id, s) {
\r
7238 this.settings = s = s || {};
\r
7239 this.rendered = false;
\r
7240 this.onRender = new tinymce.util.Dispatcher(this);
\r
7241 this.classPrefix = '';
\r
7242 this.scope = s.scope || this;
\r
7243 this.disabled = 0;
\r
7247 setDisabled : function(s) {
\r
7250 if (s != this.disabled) {
\r
7251 e = DOM.get(this.id);
\r
7253 // Add accessibility title for unavailable actions
\r
7254 if (e && this.settings.unavailable_prefix) {
\r
7256 this.prevTitle = e.title;
\r
7257 e.title = this.settings.unavailable_prefix + ": " + e.title;
\r
7259 e.title = this.prevTitle;
\r
7262 this.setState('Disabled', s);
\r
7263 this.setState('Enabled', !s);
\r
7264 this.disabled = s;
\r
7268 isDisabled : function() {
\r
7269 return this.disabled;
\r
7272 setActive : function(s) {
\r
7273 if (s != this.active) {
\r
7274 this.setState('Active', s);
\r
7279 isActive : function() {
\r
7280 return this.active;
\r
7283 setState : function(c, s) {
\r
7284 var n = DOM.get(this.id);
\r
7286 c = this.classPrefix + c;
\r
7289 DOM.addClass(n, c);
\r
7291 DOM.removeClass(n, c);
\r
7294 isRendered : function() {
\r
7295 return this.rendered;
\r
7298 renderHTML : function() {
\r
7301 renderTo : function(n) {
\r
7302 DOM.setHTML(n, this.renderHTML());
\r
7305 postRender : function() {
\r
7308 // Set pending states
\r
7309 if (is(t.disabled)) {
\r
7315 if (is(t.active)) {
\r
7322 remove : function() {
\r
7323 DOM.remove(this.id);
\r
7327 destroy : function() {
\r
7328 tinymce.dom.Event.clear(this.id);
\r
7332 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
\r
7333 Container : function(id, s) {
\r
7334 this.parent(id, s);
\r
7336 this.controls = [];
\r
7341 add : function(c) {
\r
7342 this.lookup[c.id] = c;
\r
7343 this.controls.push(c);
\r
7348 get : function(n) {
\r
7349 return this.lookup[n];
\r
7354 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
\r
7355 Separator : function(id, s) {
\r
7356 this.parent(id, s);
\r
7357 this.classPrefix = 'mceSeparator';
\r
7360 renderHTML : function() {
\r
7361 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
\r
7365 (function(tinymce) {
\r
7366 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
7368 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
\r
7369 MenuItem : function(id, s) {
\r
7370 this.parent(id, s);
\r
7371 this.classPrefix = 'mceMenuItem';
\r
7374 setSelected : function(s) {
\r
7375 this.setState('Selected', s);
\r
7376 this.selected = s;
\r
7379 isSelected : function() {
\r
7380 return this.selected;
\r
7383 postRender : function() {
\r
7388 // Set pending state
\r
7389 if (is(t.selected))
\r
7390 t.setSelected(t.selected);
\r
7395 (function(tinymce) {
\r
7396 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
7398 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
\r
7399 Menu : function(id, s) {
\r
7404 t.collapsed = false;
\r
7406 t.onAddItem = new tinymce.util.Dispatcher(this);
\r
7409 expand : function(d) {
\r
7413 walk(t, function(o) {
\r
7419 t.collapsed = false;
\r
7422 collapse : function(d) {
\r
7426 walk(t, function(o) {
\r
7432 t.collapsed = true;
\r
7435 isCollapsed : function() {
\r
7436 return this.collapsed;
\r
7439 add : function(o) {
\r
7441 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
\r
7443 this.onAddItem.dispatch(this, o);
\r
7445 return this.items[o.id] = o;
\r
7448 addSeparator : function() {
\r
7449 return this.add({separator : true});
\r
7452 addMenu : function(o) {
\r
7454 o = this.createMenu(o);
\r
7458 return this.add(o);
\r
7461 hasMenus : function() {
\r
7462 return this.menuCount !== 0;
\r
7465 remove : function(o) {
\r
7466 delete this.items[o.id];
\r
7469 removeAll : function() {
\r
7472 walk(t, function(o) {
\r
7484 createMenu : function(o) {
\r
7485 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
\r
7487 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
\r
7493 (function(tinymce) {
\r
7494 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
\r
7496 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
\r
7497 DropMenu : function(id, s) {
\r
7499 s.container = s.container || DOM.doc.body;
\r
7500 s.offset_x = s.offset_x || 0;
\r
7501 s.offset_y = s.offset_y || 0;
\r
7502 s.vp_offset_x = s.vp_offset_x || 0;
\r
7503 s.vp_offset_y = s.vp_offset_y || 0;
\r
7505 if (is(s.icons) && !s.icons)
\r
7506 s['class'] += ' mceNoIcons';
\r
7508 this.parent(id, s);
\r
7509 this.onShowMenu = new tinymce.util.Dispatcher(this);
\r
7510 this.onHideMenu = new tinymce.util.Dispatcher(this);
\r
7511 this.classPrefix = 'mceMenu';
\r
7514 createMenu : function(s) {
\r
7515 var t = this, cs = t.settings, m;
\r
7517 s.container = s.container || cs.container;
\r
7519 s.constrain = s.constrain || cs.constrain;
\r
7520 s['class'] = s['class'] || cs['class'];
\r
7521 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
\r
7522 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
\r
7523 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
\r
7525 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
\r
7530 update : function() {
\r
7531 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
\r
7533 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
\r
7534 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
\r
7536 if (!DOM.boxModel)
\r
7537 t.element.setStyles({width : tw + 2, height : th + 2});
\r
7539 t.element.setStyles({width : tw, height : th});
\r
7542 DOM.setStyle(co, 'width', tw);
\r
7544 if (s.max_height) {
\r
7545 DOM.setStyle(co, 'height', th);
\r
7547 if (tb.clientHeight < s.max_height)
\r
7548 DOM.setStyle(co, 'overflow', 'hidden');
\r
7552 showMenu : function(x, y, px) {
\r
7553 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
\r
7557 if (t.isMenuVisible)
\r
7560 if (!t.rendered) {
\r
7561 co = DOM.add(t.settings.container, t.renderNode());
\r
7563 each(t.items, function(o) {
\r
7567 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
7569 co = DOM.get('menu_' + t.id);
\r
7571 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
\r
7572 if (!tinymce.isOpera)
\r
7573 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
\r
7578 x += s.offset_x || 0;
\r
7579 y += s.offset_y || 0;
\r
7583 // Move inside viewport if not submenu
\r
7584 if (s.constrain) {
\r
7585 w = co.clientWidth - ot;
\r
7586 h = co.clientHeight - ot;
\r
7590 if ((x + s.vp_offset_x + w) > mx)
\r
7591 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
\r
7593 if ((y + s.vp_offset_y + h) > my)
\r
7594 y = Math.max(0, (my - s.vp_offset_y) - h);
\r
7597 DOM.setStyles(co, {left : x , top : y});
\r
7598 t.element.update();
\r
7600 t.isMenuVisible = 1;
\r
7601 t.mouseClickFunc = Event.add(co, 'click', function(e) {
\r
7606 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
\r
7607 m = t.items[e.id];
\r
7609 if (m.isDisabled())
\r
7618 dm = dm.settings.parent;
\r
7621 if (m.settings.onclick)
\r
7622 m.settings.onclick(e);
\r
7624 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
7628 if (t.hasMenus()) {
\r
7629 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
\r
7633 if (e && (e = DOM.getParent(e, 'tr'))) {
\r
7634 m = t.items[e.id];
\r
7637 t.lastMenu.collapse(1);
\r
7639 if (m.isDisabled())
\r
7642 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
\r
7643 //p = DOM.getPos(s.container);
\r
7644 r = DOM.getRect(e);
\r
7645 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
\r
7647 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
\r
7653 t.onShowMenu.dispatch(t);
\r
7655 if (s.keyboard_focus) {
\r
7656 Event.add(co, 'keydown', t._keyHandler, t);
\r
7657 DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
\r
7662 hideMenu : function(c) {
\r
7663 var t = this, co = DOM.get('menu_' + t.id), e;
\r
7665 if (!t.isMenuVisible)
\r
7668 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
7669 Event.remove(co, 'click', t.mouseClickFunc);
\r
7670 Event.remove(co, 'keydown', t._keyHandler);
\r
7672 t.isMenuVisible = 0;
\r
7680 if (e = DOM.get(t.id))
\r
7681 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
\r
7683 t.onHideMenu.dispatch(t);
\r
7686 add : function(o) {
\r
7691 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
\r
7692 t._add(DOM.select('tbody', co)[0], o);
\r
7697 collapse : function(d) {
\r
7702 remove : function(o) {
\r
7706 return this.parent(o);
\r
7709 destroy : function() {
\r
7710 var t = this, co = DOM.get('menu_' + t.id);
\r
7712 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
7713 Event.remove(co, 'click', t.mouseClickFunc);
\r
7716 t.element.remove();
\r
7721 renderNode : function() {
\r
7722 var t = this, s = t.settings, n, tb, co, w;
\r
7724 w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
\r
7725 co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
\r
7726 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
7729 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
\r
7731 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
\r
7732 n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
\r
7733 tb = DOM.add(n, 'tbody');
\r
7735 each(t.items, function(o) {
\r
7739 t.rendered = true;
\r
7744 // Internal functions
\r
7746 _keyHandler : function(e) {
\r
7747 var t = this, kc = e.keyCode;
\r
7749 function focus(d) {
\r
7750 var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
\r
7760 focus(-1); // Select first link
\r
7771 return this.hideMenu();
\r
7775 _add : function(tb, o) {
\r
7776 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
\r
7778 if (s.separator) {
\r
7779 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
\r
7780 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
\r
7782 if (n = ro.previousSibling)
\r
7783 DOM.addClass(n, 'mceLast');
\r
7788 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
\r
7789 n = it = DOM.add(n, 'td');
\r
7790 n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
\r
7792 DOM.addClass(it, s['class']);
\r
7793 // n = DOM.add(n, 'span', {'class' : 'item'});
\r
7795 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
\r
7798 DOM.add(ic, 'img', {src : s.icon_src});
\r
7800 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
\r
7802 if (o.settings.style)
\r
7803 DOM.setAttrib(n, 'style', o.settings.style);
\r
7805 if (tb.childNodes.length == 1)
\r
7806 DOM.addClass(ro, 'mceFirst');
\r
7808 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
\r
7809 DOM.addClass(ro, 'mceFirst');
\r
7812 DOM.addClass(ro, cp + 'ItemSub');
\r
7814 if (n = ro.previousSibling)
\r
7815 DOM.removeClass(n, 'mceLast');
\r
7817 DOM.addClass(ro, 'mceLast');
\r
7821 (function(tinymce) {
\r
7822 var DOM = tinymce.DOM;
\r
7824 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
\r
7825 Button : function(id, s) {
\r
7826 this.parent(id, s);
\r
7827 this.classPrefix = 'mceButton';
\r
7830 renderHTML : function() {
\r
7831 var cp = this.classPrefix, s = this.settings, h, l;
\r
7833 l = DOM.encode(s.label || '');
\r
7834 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
7837 h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
\r
7839 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
\r
7844 postRender : function() {
\r
7845 var t = this, s = t.settings;
\r
7847 tinymce.dom.Event.add(t.id, 'click', function(e) {
\r
7848 if (!t.isDisabled())
\r
7849 return s.onclick.call(s.scope, e);
\r
7855 (function(tinymce) {
\r
7856 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
7858 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
\r
7859 ListBox : function(id, s) {
\r
7866 t.onChange = new Dispatcher(t);
\r
7868 t.onPostRender = new Dispatcher(t);
\r
7870 t.onAdd = new Dispatcher(t);
\r
7872 t.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
7874 t.classPrefix = 'mceListBox';
\r
7877 select : function(va) {
\r
7878 var t = this, fv, f;
\r
7880 if (va == undefined)
\r
7881 return t.selectByIndex(-1);
\r
7883 // Is string or number make function selector
\r
7884 if (va && va.call)
\r
7892 // Do we need to do something?
\r
7893 if (va != t.selectedValue) {
\r
7895 each(t.items, function(o, i) {
\r
7898 t.selectByIndex(i);
\r
7904 t.selectByIndex(-1);
\r
7908 selectByIndex : function(idx) {
\r
7909 var t = this, e, o;
\r
7911 if (idx != t.selectedIndex) {
\r
7912 e = DOM.get(t.id + '_text');
\r
7916 t.selectedValue = o.value;
\r
7917 t.selectedIndex = idx;
\r
7918 DOM.setHTML(e, DOM.encode(o.title));
\r
7919 DOM.removeClass(e, 'mceTitle');
\r
7921 DOM.setHTML(e, DOM.encode(t.settings.title));
\r
7922 DOM.addClass(e, 'mceTitle');
\r
7923 t.selectedValue = t.selectedIndex = null;
\r
7930 add : function(n, v, o) {
\r
7934 o = tinymce.extend(o, {
\r
7940 t.onAdd.dispatch(t, o);
\r
7943 getLength : function() {
\r
7944 return this.items.length;
\r
7947 renderHTML : function() {
\r
7948 var h = '', t = this, s = t.settings, cp = t.classPrefix;
\r
7950 h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
\r
7951 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
7952 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
7953 h += '</tr></tbody></table>';
\r
7958 showMenu : function() {
\r
7959 var t = this, p1, p2, e = DOM.get(this.id), m;
\r
7961 if (t.isDisabled() || t.items.length == 0)
\r
7964 if (t.menu && t.menu.isMenuVisible)
\r
7965 return t.hideMenu();
\r
7967 if (!t.isMenuRendered) {
\r
7969 t.isMenuRendered = true;
\r
7972 p1 = DOM.getPos(this.settings.menu_container);
\r
7973 p2 = DOM.getPos(e);
\r
7976 m.settings.offset_x = p2.x;
\r
7977 m.settings.offset_y = p2.y;
\r
7978 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
\r
7982 m.items[t.oldID].setSelected(0);
\r
7984 each(t.items, function(o) {
\r
7985 if (o.value === t.selectedValue) {
\r
7986 m.items[o.id].setSelected(1);
\r
7991 m.showMenu(0, e.clientHeight);
\r
7993 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7994 DOM.addClass(t.id, t.classPrefix + 'Selected');
\r
7996 //DOM.get(t.id + '_text').focus();
\r
7999 hideMenu : function(e) {
\r
8002 if (t.menu && t.menu.isMenuVisible) {
\r
8003 // Prevent double toogles by canceling the mouse click event to the button
\r
8004 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
\r
8007 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
8008 DOM.removeClass(t.id, t.classPrefix + 'Selected');
\r
8009 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8010 t.menu.hideMenu();
\r
8015 renderMenu : function() {
\r
8018 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
8020 'class' : t.classPrefix + 'Menu mceNoIcons',
\r
8025 m.onHideMenu.add(t.hideMenu, t);
\r
8028 title : t.settings.title,
\r
8029 'class' : 'mceMenuItemTitle',
\r
8030 onclick : function() {
\r
8031 if (t.settings.onselect('') !== false)
\r
8032 t.select(''); // Must be runned after
\r
8036 each(t.items, function(o) {
\r
8037 // No value then treat it as a title
\r
8038 if (o.value === undefined) {
\r
8041 'class' : 'mceMenuItemTitle',
\r
8042 onclick : function() {
\r
8043 if (t.settings.onselect('') !== false)
\r
8044 t.select(''); // Must be runned after
\r
8048 o.id = DOM.uniqueId();
\r
8049 o.onclick = function() {
\r
8050 if (t.settings.onselect(o.value) !== false)
\r
8051 t.select(o.value); // Must be runned after
\r
8058 t.onRenderMenu.dispatch(t, m);
\r
8062 postRender : function() {
\r
8063 var t = this, cp = t.classPrefix;
\r
8065 Event.add(t.id, 'click', t.showMenu, t);
\r
8066 Event.add(t.id + '_text', 'focus', function(e) {
\r
8067 if (!t._focused) {
\r
8068 t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
\r
8069 var idx = -1, v, kc = e.keyCode;
\r
8071 // Find current index
\r
8072 each(t.items, function(v, i) {
\r
8073 if (t.selectedValue == v.value)
\r
8079 v = t.items[idx - 1];
\r
8080 else if (kc == 40)
\r
8081 v = t.items[idx + 1];
\r
8082 else if (kc == 13) {
\r
8083 // Fake select on enter
\r
8084 v = t.selectedValue;
\r
8085 t.selectedValue = null; // Needs to be null to fake change
\r
8086 t.settings.onselect(v);
\r
8087 return Event.cancel(e);
\r
8092 t.select(v.value);
\r
8099 Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
\r
8101 // Old IE doesn't have hover on all elements
\r
8102 if (tinymce.isIE6 || !DOM.boxModel) {
\r
8103 Event.add(t.id, 'mouseover', function() {
\r
8104 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
8105 DOM.addClass(t.id, cp + 'Hover');
\r
8108 Event.add(t.id, 'mouseout', function() {
\r
8109 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
8110 DOM.removeClass(t.id, cp + 'Hover');
\r
8114 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
8117 destroy : function() {
\r
8120 Event.clear(this.id + '_text');
\r
8121 Event.clear(this.id + '_open');
\r
8125 (function(tinymce) {
\r
8126 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
8128 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
\r
8129 NativeListBox : function(id, s) {
\r
8130 this.parent(id, s);
\r
8131 this.classPrefix = 'mceNativeListBox';
\r
8134 setDisabled : function(s) {
\r
8135 DOM.get(this.id).disabled = s;
\r
8138 isDisabled : function() {
\r
8139 return DOM.get(this.id).disabled;
\r
8142 select : function(va) {
\r
8143 var t = this, fv, f;
\r
8145 if (va == undefined)
\r
8146 return t.selectByIndex(-1);
\r
8148 // Is string or number make function selector
\r
8149 if (va && va.call)
\r
8157 // Do we need to do something?
\r
8158 if (va != t.selectedValue) {
\r
8160 each(t.items, function(o, i) {
\r
8163 t.selectByIndex(i);
\r
8169 t.selectByIndex(-1);
\r
8173 selectByIndex : function(idx) {
\r
8174 DOM.get(this.id).selectedIndex = idx + 1;
\r
8175 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
\r
8178 add : function(n, v, a) {
\r
8184 if (t.isRendered())
\r
8185 DOM.add(DOM.get(this.id), 'option', a, n);
\r
8194 t.onAdd.dispatch(t, o);
\r
8197 getLength : function() {
\r
8198 return DOM.get(this.id).options.length - 1;
\r
8201 renderHTML : function() {
\r
8204 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
\r
8206 each(t.items, function(it) {
\r
8207 h += DOM.createHTML('option', {value : it.value}, it.title);
\r
8210 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
\r
8215 postRender : function() {
\r
8218 t.rendered = true;
\r
8220 function onChange(e) {
\r
8221 var v = t.items[e.target.selectedIndex - 1];
\r
8223 if (v && (v = v.value)) {
\r
8224 t.onChange.dispatch(t, v);
\r
8226 if (t.settings.onselect)
\r
8227 t.settings.onselect(v);
\r
8231 Event.add(t.id, 'change', onChange);
\r
8233 // Accessibility keyhandler
\r
8234 Event.add(t.id, 'keydown', function(e) {
\r
8237 Event.remove(t.id, 'change', ch);
\r
8239 bf = Event.add(t.id, 'blur', function() {
\r
8240 Event.add(t.id, 'change', onChange);
\r
8241 Event.remove(t.id, 'blur', bf);
\r
8244 if (e.keyCode == 13 || e.keyCode == 32) {
\r
8246 return Event.cancel(e);
\r
8250 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
8254 (function(tinymce) {
\r
8255 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
8257 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
\r
8258 MenuButton : function(id, s) {
\r
8259 this.parent(id, s);
\r
8261 this.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
8263 s.menu_container = s.menu_container || DOM.doc.body;
\r
8266 showMenu : function() {
\r
8267 var t = this, p1, p2, e = DOM.get(t.id), m;
\r
8269 if (t.isDisabled())
\r
8272 if (!t.isMenuRendered) {
\r
8274 t.isMenuRendered = true;
\r
8277 if (t.isMenuVisible)
\r
8278 return t.hideMenu();
\r
8280 p1 = DOM.getPos(t.settings.menu_container);
\r
8281 p2 = DOM.getPos(e);
\r
8284 m.settings.offset_x = p2.x;
\r
8285 m.settings.offset_y = p2.y;
\r
8286 m.settings.vp_offset_x = p2.x;
\r
8287 m.settings.vp_offset_y = p2.y;
\r
8288 m.settings.keyboard_focus = t._focused;
\r
8289 m.showMenu(0, e.clientHeight);
\r
8291 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8292 t.setState('Selected', 1);
\r
8294 t.isMenuVisible = 1;
\r
8297 renderMenu : function() {
\r
8300 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
8302 'class' : this.classPrefix + 'Menu',
\r
8303 icons : t.settings.icons
\r
8306 m.onHideMenu.add(t.hideMenu, t);
\r
8308 t.onRenderMenu.dispatch(t, m);
\r
8312 hideMenu : function(e) {
\r
8315 // Prevent double toogles by canceling the mouse click event to the button
\r
8316 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
\r
8319 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
8320 t.setState('Selected', 0);
\r
8321 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8323 t.menu.hideMenu();
\r
8326 t.isMenuVisible = 0;
\r
8329 postRender : function() {
\r
8330 var t = this, s = t.settings;
\r
8332 Event.add(t.id, 'click', function() {
\r
8333 if (!t.isDisabled()) {
\r
8335 s.onclick(t.value);
\r
8344 (function(tinymce) {
\r
8345 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
8347 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
\r
8348 SplitButton : function(id, s) {
\r
8349 this.parent(id, s);
\r
8350 this.classPrefix = 'mceSplitButton';
\r
8353 renderHTML : function() {
\r
8354 var h, t = this, s = t.settings, h1;
\r
8356 h = '<tbody><tr>';
\r
8359 h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
\r
8361 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
\r
8363 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
8365 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
\r
8366 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
8368 h += '</tr></tbody>';
\r
8370 return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
\r
8373 postRender : function() {
\r
8374 var t = this, s = t.settings;
\r
8377 Event.add(t.id + '_action', 'click', function() {
\r
8378 if (!t.isDisabled())
\r
8379 s.onclick(t.value);
\r
8383 Event.add(t.id + '_open', 'click', t.showMenu, t);
\r
8384 Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
\r
8385 Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
\r
8387 // Old IE doesn't have hover on all elements
\r
8388 if (tinymce.isIE6 || !DOM.boxModel) {
\r
8389 Event.add(t.id, 'mouseover', function() {
\r
8390 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
8391 DOM.addClass(t.id, 'mceSplitButtonHover');
\r
8394 Event.add(t.id, 'mouseout', function() {
\r
8395 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
8396 DOM.removeClass(t.id, 'mceSplitButtonHover');
\r
8401 destroy : function() {
\r
8404 Event.clear(this.id + '_action');
\r
8405 Event.clear(this.id + '_open');
\r
8410 (function(tinymce) {
\r
8411 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
\r
8413 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
\r
8414 ColorSplitButton : function(id, s) {
\r
8419 t.settings = s = tinymce.extend({
\r
8420 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
8422 default_color : '#888888'
\r
8425 t.onShowMenu = new tinymce.util.Dispatcher(t);
\r
8427 t.onHideMenu = new tinymce.util.Dispatcher(t);
\r
8429 t.value = s.default_color;
\r
8432 showMenu : function() {
\r
8433 var t = this, r, p, e, p2;
\r
8435 if (t.isDisabled())
\r
8438 if (!t.isMenuRendered) {
\r
8440 t.isMenuRendered = true;
\r
8443 if (t.isMenuVisible)
\r
8444 return t.hideMenu();
\r
8446 e = DOM.get(t.id);
\r
8447 DOM.show(t.id + '_menu');
\r
8448 DOM.addClass(e, 'mceSplitButtonSelected');
\r
8449 p2 = DOM.getPos(e);
\r
8450 DOM.setStyles(t.id + '_menu', {
\r
8452 top : p2.y + e.clientHeight,
\r
8457 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8458 t.onShowMenu.dispatch(t);
\r
8461 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
\r
8462 if (e.keyCode == 27)
\r
8466 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
\r
8469 t.isMenuVisible = 1;
\r
8472 hideMenu : function(e) {
\r
8475 // Prevent double toogles by canceling the mouse click event to the button
\r
8476 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
\r
8479 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
\r
8480 DOM.removeClass(t.id, 'mceSplitButtonSelected');
\r
8481 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8482 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
\r
8483 DOM.hide(t.id + '_menu');
\r
8486 t.onHideMenu.dispatch(t);
\r
8488 t.isMenuVisible = 0;
\r
8491 renderMenu : function() {
\r
8492 var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
\r
8494 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
8495 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
\r
8496 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
\r
8498 n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
\r
8499 tb = DOM.add(n, 'tbody');
\r
8501 // Generate color grid
\r
8503 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
\r
8504 c = c.replace(/^#/, '');
\r
8507 tr = DOM.add(tb, 'tr');
\r
8508 i = s.grid_width - 1;
\r
8511 n = DOM.add(tr, 'td');
\r
8513 n = DOM.add(n, 'a', {
\r
8514 href : 'javascript:;',
\r
8516 backgroundColor : '#' + c
\r
8518 _mce_color : '#' + c
\r
8522 if (s.more_colors_func) {
\r
8523 n = DOM.add(tb, 'tr');
\r
8524 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
\r
8525 n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
\r
8527 Event.add(n, 'click', function(e) {
\r
8528 s.more_colors_func.call(s.more_colors_scope || this);
\r
8529 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
8533 DOM.addClass(m, 'mceColorSplitMenu');
\r
8535 Event.add(t.id + '_menu', 'click', function(e) {
\r
8540 if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
\r
8543 return Event.cancel(e); // Prevent IE auto save warning
\r
8549 setColor : function(c) {
\r
8552 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
\r
8556 t.settings.onselect(c);
\r
8559 postRender : function() {
\r
8560 var t = this, id = t.id;
\r
8563 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
\r
8564 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
\r
8567 destroy : function() {
\r
8570 Event.clear(this.id + '_menu');
\r
8571 Event.clear(this.id + '_more');
\r
8572 DOM.remove(this.id + '_menu');
\r
8577 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
\r
8578 renderHTML : function() {
\r
8579 var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
\r
8582 for (i=0; i<cl.length; i++) {
\r
8583 // Get current control, prev control, next control and if the control is a list box or not
\r
8588 // Add toolbar start
\r
8590 c = 'mceToolbarStart';
\r
8593 c += ' mceToolbarStartButton';
\r
8594 else if (co.SplitButton)
\r
8595 c += ' mceToolbarStartSplitButton';
\r
8596 else if (co.ListBox)
\r
8597 c += ' mceToolbarStartListBox';
\r
8599 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8602 // Add toolbar end before list box and after the previous button
\r
8603 // This is to fix the o2k7 editor skins
\r
8604 if (pr && co.ListBox) {
\r
8605 if (pr.Button || pr.SplitButton)
\r
8606 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8609 // Render control HTML
\r
8611 // IE 8 quick fix, needed to propertly generate a hit area for anchors
\r
8613 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
\r
8615 h += '<td>' + co.renderHTML() + '</td>';
\r
8617 // Add toolbar start after list box and before the next button
\r
8618 // This is to fix the o2k7 editor skins
\r
8619 if (nx && co.ListBox) {
\r
8620 if (nx.Button || nx.SplitButton)
\r
8621 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8625 c = 'mceToolbarEnd';
\r
8628 c += ' mceToolbarEndButton';
\r
8629 else if (co.SplitButton)
\r
8630 c += ' mceToolbarEndSplitButton';
\r
8631 else if (co.ListBox)
\r
8632 c += ' mceToolbarEndListBox';
\r
8634 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8636 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
8640 (function(tinymce) {
\r
8641 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
\r
8643 tinymce.create('tinymce.AddOnManager', {
\r
8648 onAdd : new Dispatcher(this),
\r
8650 get : function(n) {
\r
8651 return this.lookup[n];
\r
8654 requireLangPack : function(n) {
\r
8655 var s = tinymce.settings;
\r
8657 if (s && s.language)
\r
8658 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
\r
8661 add : function(id, o) {
\r
8662 this.items.push(o);
\r
8663 this.lookup[id] = o;
\r
8664 this.onAdd.dispatch(this, id, o);
\r
8669 load : function(n, u, cb, s) {
\r
8675 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
\r
8676 u = tinymce.baseURL + '/' + u;
\r
8678 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
\r
8679 tinymce.ScriptLoader.add(u, cb, s);
\r
8683 // Create plugin and theme managers
\r
8684 tinymce.PluginManager = new tinymce.AddOnManager();
\r
8685 tinymce.ThemeManager = new tinymce.AddOnManager();
\r
8688 (function(tinymce) {
\r
8690 var each = tinymce.each, extend = tinymce.extend,
\r
8691 DOM = tinymce.DOM, Event = tinymce.dom.Event,
\r
8692 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
8693 explode = tinymce.explode,
\r
8694 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
\r
8696 // Setup some URLs where the editor API is located and where the document is
\r
8697 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
\r
8698 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
\r
8699 tinymce.documentBaseURL += '/';
\r
8701 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
\r
8703 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
\r
8705 // Add before unload listener
\r
8706 // This was required since IE was leaking memory if you added and removed beforeunload listeners
\r
8707 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
\r
8708 tinymce.onBeforeUnload = new Dispatcher(tinymce);
\r
8710 // Must be on window or IE will leak if the editor is placed in frame or iframe
\r
8711 Event.add(window, 'beforeunload', function(e) {
\r
8712 tinymce.onBeforeUnload.dispatch(tinymce, e);
\r
8715 tinymce.onAddEditor = new Dispatcher(tinymce);
\r
8717 tinymce.onRemoveEditor = new Dispatcher(tinymce);
\r
8719 tinymce.EditorManager = extend(tinymce, {
\r
8724 activeEditor : null,
\r
8726 init : function(s) {
\r
8727 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
\r
8729 function execCallback(se, n, s) {
\r
8735 if (tinymce.is(f, 'string')) {
\r
8736 s = f.replace(/\.\w+$/, '');
\r
8737 s = s ? tinymce.resolve(s) : 0;
\r
8738 f = tinymce.resolve(f);
\r
8741 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
\r
8752 Event.add(document, 'init', function() {
\r
8755 execCallback(s, 'onpageload');
\r
8759 l = s.elements || '';
\r
8761 if(l.length > 0) {
\r
8762 each(explode(l), function(v) {
\r
8764 ed = new tinymce.Editor(v, s);
\r
8768 each(document.forms, function(f) {
\r
8769 each(f.elements, function(e) {
\r
8770 if (e.name === v) {
\r
8771 v = 'mce_editor_' + instanceCounter++;
\r
8772 DOM.setAttrib(e, 'id', v);
\r
8774 ed = new tinymce.Editor(v, s);
\r
8786 case "specific_textareas":
\r
8787 function hasClass(n, c) {
\r
8788 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
\r
8791 each(DOM.select('textarea'), function(v) {
\r
8792 if (s.editor_deselector && hasClass(v, s.editor_deselector))
\r
8795 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
\r
8796 // Can we use the name
\r
8797 e = DOM.get(v.name);
\r
8801 // Generate unique name if missing or already exists
\r
8802 if (!v.id || t.get(v.id))
\r
8803 v.id = DOM.uniqueId();
\r
8805 ed = new tinymce.Editor(v.id, s);
\r
8813 // Call onInit when all editors are initialized
\r
8817 each(el, function(ed) {
\r
8820 if (!ed.initialized) {
\r
8822 ed.onInit.add(function() {
\r
8827 execCallback(s, 'oninit');
\r
8834 execCallback(s, 'oninit');
\r
8840 get : function(id) {
\r
8841 if (id === undefined)
\r
8842 return this.editors;
\r
8844 return this.editors[id];
\r
8847 getInstanceById : function(id) {
\r
8848 return this.get(id);
\r
8851 add : function(editor) {
\r
8852 var self = this, editors = self.editors;
\r
8854 // Add named and index editor instance
\r
8855 editors[editor.id] = editor;
\r
8856 editors.push(editor);
\r
8858 self._setActive(editor);
\r
8859 self.onAddEditor.dispatch(self, editor);
\r
8865 remove : function(editor) {
\r
8866 var t = this, i, editors = t.editors;
\r
8868 // Not in the collection
\r
8869 if (!editors[editor.id])
\r
8872 delete editors[editor.id];
\r
8874 for (i = 0; i < editors.length; i++) {
\r
8875 if (editors[i] == editor) {
\r
8876 editors.splice(i, 1);
\r
8881 // Select another editor since the active one was removed
\r
8882 if (t.activeEditor == editor)
\r
8883 t._setActive(editors[0]);
\r
8886 t.onRemoveEditor.dispatch(t, editor);
\r
8891 execCommand : function(c, u, v) {
\r
8892 var t = this, ed = t.get(v), w;
\r
8894 // Manager commands
\r
8900 case "mceAddEditor":
\r
8901 case "mceAddControl":
\r
8903 new tinymce.Editor(v, t.settings).render();
\r
8907 case "mceAddFrameControl":
\r
8910 // Add tinyMCE global instance and tinymce namespace to specified window
\r
8911 w.tinyMCE = tinyMCE;
\r
8912 w.tinymce = tinymce;
\r
8914 tinymce.DOM.doc = w.document;
\r
8915 tinymce.DOM.win = w;
\r
8917 ed = new tinymce.Editor(v.element_id, v);
\r
8920 // Fix IE memory leaks
\r
8921 if (tinymce.isIE) {
\r
8924 w.detachEvent('onunload', clr);
\r
8925 w = w.tinyMCE = w.tinymce = null; // IE leak
\r
8928 w.attachEvent('onunload', clr);
\r
8931 v.page_window = null;
\r
8935 case "mceRemoveEditor":
\r
8936 case "mceRemoveControl":
\r
8942 case 'mceToggleEditor':
\r
8944 t.execCommand('mceAddControl', 0, v);
\r
8948 if (ed.isHidden())
\r
8956 // Run command on active editor
\r
8957 if (t.activeEditor)
\r
8958 return t.activeEditor.execCommand(c, u, v);
\r
8963 execInstanceCommand : function(id, c, u, v) {
\r
8964 var ed = this.get(id);
\r
8967 return ed.execCommand(c, u, v);
\r
8972 triggerSave : function() {
\r
8973 each(this.editors, function(e) {
\r
8978 addI18n : function(p, o) {
\r
8979 var lo, i18n = this.i18n;
\r
8981 if (!tinymce.is(p, 'string')) {
\r
8982 each(p, function(o, lc) {
\r
8983 each(o, function(o, g) {
\r
8984 each(o, function(o, k) {
\r
8985 if (g === 'common')
\r
8986 i18n[lc + '.' + k] = o;
\r
8988 i18n[lc + '.' + g + '.' + k] = o;
\r
8993 each(o, function(o, k) {
\r
8994 i18n[p + '.' + k] = o;
\r
8999 // Private methods
\r
9001 _setActive : function(editor) {
\r
9002 this.selectedInstance = this.activeEditor = editor;
\r
9007 (function(tinymce) {
\r
9008 // Shorten these names
\r
9009 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
\r
9010 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
\r
9011 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
\r
9012 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
9013 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
\r
9015 tinymce.create('tinymce.Editor', {
\r
9016 Editor : function(id, s) {
\r
9019 t.id = t.editorId = id;
\r
9021 t.execCommands = {};
\r
9022 t.queryStateCommands = {};
\r
9023 t.queryValueCommands = {};
\r
9025 t.isNotDirty = false;
\r
9029 // Add events to the editor
\r
9033 'onBeforeRenderUI',
\r
9073 'onBeforeSetContent',
\r
9075 'onBeforeGetContent',
\r
9089 'onBeforeExecCommand',
\r
9099 'onSetProgressState'
\r
9101 t[e] = new Dispatcher(t);
\r
9104 t.settings = s = extend({
\r
9107 docs_language : 'en',
\r
9114 document_base_url : tinymce.documentBaseURL,
\r
9115 add_form_submit_trigger : 1,
\r
9117 add_unload_trigger : 1,
\r
9119 relative_urls : 1,
\r
9120 remove_script_host : 1,
\r
9121 table_inline_editing : 0,
\r
9122 object_resizing : 1,
\r
9124 accessibility_focus : 1,
\r
9125 custom_shortcuts : 1,
\r
9126 custom_undo_redo_keyboard_shortcuts : 1,
\r
9127 custom_undo_redo_restore_selection : 1,
\r
9128 custom_undo_redo : 1,
\r
9129 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
9130 visual_table_class : 'mceItemTable',
\r
9132 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
\r
9133 apply_source_formatting : 1,
\r
9134 directionality : 'ltr',
\r
9135 forced_root_block : 'p',
\r
9136 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
9138 padd_empty_editor : 1,
\r
9141 force_p_newlines : 1,
\r
9142 indentation : '30px',
\r
9144 fix_table_elements : 1,
\r
9145 inline_styles : 1,
\r
9146 convert_fonts_to_spans : true
\r
9149 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
\r
9150 base_uri : tinyMCE.baseURI
\r
9153 t.baseURI = tinymce.baseURI;
\r
9156 t.execCallback('setup', t);
\r
9159 render : function(nst) {
\r
9160 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
\r
9162 // Page is not loaded yet, wait for it
\r
9163 if (!Event.domLoaded) {
\r
9164 Event.add(document, 'init', function() {
\r
9170 tinyMCE.settings = s;
\r
9172 // Element not found, then skip initialization
\r
9173 if (!t.getElement())
\r
9176 // Add hidden input for non input elements inside form elements
\r
9177 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
\r
9178 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
\r
9180 if (tinymce.WindowManager)
\r
9181 t.windowManager = new tinymce.WindowManager(t);
\r
9183 if (s.encoding == 'xml') {
\r
9184 t.onGetContent.add(function(ed, o) {
\r
9186 o.content = DOM.encode(o.content);
\r
9190 if (s.add_form_submit_trigger) {
\r
9191 t.onSubmit.addToTop(function() {
\r
9192 if (t.initialized) {
\r
9199 if (s.add_unload_trigger) {
\r
9200 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
\r
9201 if (t.initialized && !t.destroyed && !t.isHidden())
\r
9202 t.save({format : 'raw', no_events : true});
\r
9206 tinymce.addUnload(t.destroy, t);
\r
9208 if (s.submit_patch) {
\r
9209 t.onBeforeRenderUI.add(function() {
\r
9210 var n = t.getElement().form;
\r
9215 // Already patched
\r
9216 if (n._mceOldSubmit)
\r
9219 // Check page uses id="submit" or name="submit" for it's submit button
\r
9220 if (!n.submit.nodeType && !n.submit.length) {
\r
9221 t.formElement = n;
\r
9222 n._mceOldSubmit = n.submit;
\r
9223 n.submit = function() {
\r
9224 // Save all instances
\r
9225 tinymce.triggerSave();
\r
9228 return t.formElement._mceOldSubmit(t.formElement);
\r
9237 function loadScripts() {
\r
9239 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
\r
9241 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
\r
9242 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
\r
9244 each(explode(s.plugins), function(p) {
\r
9245 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
\r
9246 // Skip safari plugin, since it is removed as of 3.3b1
\r
9247 if (p == 'safari')
\r
9250 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
\r
9254 // Init when que is loaded
\r
9255 sl.loadQueue(function() {
\r
9264 init : function() {
\r
9265 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
\r
9270 s.theme = s.theme.replace(/-/, '');
\r
9271 o = ThemeManager.get(s.theme);
\r
9272 t.theme = new o();
\r
9274 if (t.theme.init && s.init_theme)
\r
9275 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
\r
9278 // Create all plugins
\r
9279 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
\r
9280 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
\r
9285 t.plugins[p] = po;
\r
9292 // Setup popup CSS path(s)
\r
9293 if (s.popup_css !== false) {
\r
9295 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
\r
9297 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
\r
9300 if (s.popup_css_add)
\r
9301 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
\r
9303 t.controlManager = new tinymce.ControlManager(t);
\r
9305 if (s.custom_undo_redo) {
\r
9306 // Add initial undo level
\r
9307 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
\r
9308 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
\r
9309 if (!t.undoManager.hasUndo())
\r
9310 t.undoManager.add();
\r
9314 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
\r
9315 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
\r
9316 t.undoManager.add();
\r
9320 t.onExecCommand.add(function(ed, c) {
\r
9321 // Don't refresh the select lists until caret move
\r
9322 if (!/^(FontName|FontSize)$/.test(c))
\r
9326 // Remove ghost selections on images and tables in Gecko
\r
9328 function repaint(a, o) {
\r
9329 if (!o || !o.initial)
\r
9330 t.execCommand('mceRepaint');
\r
9333 t.onUndo.add(repaint);
\r
9334 t.onRedo.add(repaint);
\r
9335 t.onSetContent.add(repaint);
\r
9338 // Enables users to override the control factory
\r
9339 t.onBeforeRenderUI.dispatch(t, t.controlManager);
\r
9342 if (s.render_ui) {
\r
9343 w = s.width || e.style.width || e.offsetWidth;
\r
9344 h = s.height || e.style.height || e.offsetHeight;
\r
9345 t.orgDisplay = e.style.display;
\r
9346 re = /^[0-9\.]+(|px)$/i;
\r
9348 if (re.test('' + w))
\r
9349 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
\r
9351 if (re.test('' + h))
\r
9352 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
\r
9355 o = t.theme.renderUI({
\r
9359 deltaWidth : s.delta_width,
\r
9360 deltaHeight : s.delta_height
\r
9363 t.editorContainer = o.editorContainer;
\r
9367 // User specified a document.domain value
\r
9368 if (document.domain && location.hostname != document.domain)
\r
9369 tinymce.relaxedDomain = document.domain;
\r
9372 DOM.setStyles(o.sizeContainer || o.editorContainer, {
\r
9377 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
\r
9381 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
\r
9383 // We only need to override paths if we have to
\r
9384 // IE has a bug where it remove site absolute urls to relative ones if this is specified
\r
9385 if (s.document_base_url != tinymce.documentBaseURL)
\r
9386 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
\r
9388 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
\r
9390 if (tinymce.relaxedDomain)
\r
9391 t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
\r
9393 bi = s.body_id || 'tinymce';
\r
9394 if (bi.indexOf('=') != -1) {
\r
9395 bi = t.getParam('body_id', '', 'hash');
\r
9396 bi = bi[t.id] || bi;
\r
9399 bc = s.body_class || '';
\r
9400 if (bc.indexOf('=') != -1) {
\r
9401 bc = t.getParam('body_class', '', 'hash');
\r
9402 bc = bc[t.id] || '';
\r
9405 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
\r
9407 // Domain relaxing enabled, then set document domain
\r
9408 if (tinymce.relaxedDomain) {
\r
9409 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
\r
9410 if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
\r
9411 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
9412 else if (tinymce.isOpera)
\r
9413 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
\r
9417 n = DOM.add(o.iframeContainer, 'iframe', {
\r
9418 id : t.id + "_ifr",
\r
9419 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
\r
9420 frameBorder : '0',
\r
9427 t.contentAreaContainer = o.iframeContainer;
\r
9428 DOM.get(o.editorContainer).style.display = t.orgDisplay;
\r
9429 DOM.get(t.id).style.display = 'none';
\r
9431 if (!isIE || !tinymce.relaxedDomain)
\r
9434 e = n = o = null; // Cleanup
\r
9437 setupIframe : function() {
\r
9438 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
\r
9440 // Setup iframe body
\r
9441 if (!isIE || !tinymce.relaxedDomain) {
\r
9443 d.write(t.iframeHTML);
\r
9447 // Design mode needs to be added here Ctrl+A will fail otherwise
\r
9451 d.designMode = 'On';
\r
9453 // Will fail on Gecko if the editor is placed in an hidden container element
\r
9454 // The design mode will be set ones the editor is focused
\r
9458 // IE needs to use contentEditable or it will display non secure items for HTTPS
\r
9460 // It will not steal focus if we hide it while setting contentEditable
\r
9465 b.contentEditable = true;
\r
9470 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
\r
9471 keep_values : true,
\r
9472 url_converter : t.convertURL,
\r
9473 url_converter_scope : t,
\r
9474 hex_colors : s.force_hex_style_colors,
\r
9475 class_filter : s.class_filter,
\r
9476 update_styles : 1,
\r
9477 fix_ie_paragraphs : 1,
\r
9478 valid_styles : s.valid_styles
\r
9481 t.schema = new tinymce.dom.Schema();
\r
9483 t.serializer = new tinymce.dom.Serializer(extend(s, {
\r
9484 valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
\r
9489 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
\r
9491 t.formatter = new tinymce.Formatter(this);
\r
9493 // Register default formats
\r
9494 t.formatter.register({
\r
9496 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
\r
9497 {selector : 'img,table', styles : {'float' : 'left'}}
\r
9501 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
\r
9502 {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
\r
9503 {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
\r
9507 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
\r
9508 {selector : 'img,table', styles : {'float' : 'right'}}
\r
9512 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
\r
9516 {inline : 'strong'},
\r
9517 {inline : 'span', styles : {fontWeight : 'bold'}},
\r
9523 {inline : 'span', styles : {fontStyle : 'italic'}},
\r
9528 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
\r
9533 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
\r
9537 forecolor : {inline : 'span', styles : {color : '%value'}},
\r
9538 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
\r
9539 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
\r
9540 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
\r
9541 blockquote : {block : 'blockquote', wrapper : 1},
\r
9544 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
\r
9545 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
\r
9546 {selector : '*', attributes : ['style', 'class'], expand : false, deep : true}
\r
9550 // Register default block formats
\r
9551 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
\r
9552 t.formatter.register(name, {block : name});
\r
9555 // Register user defined formats
\r
9556 t.formatter.register(t.settings.formats);
\r
9558 t.undoManager = new tinymce.UndoManager(t);
\r
9561 t.undoManager.onAdd.add(function(um, l) {
\r
9563 return t.onChange.dispatch(t, l, um);
\r
9566 t.undoManager.onUndo.add(function(um, l) {
\r
9567 return t.onUndo.dispatch(t, l, um);
\r
9570 t.undoManager.onRedo.add(function(um, l) {
\r
9571 return t.onRedo.dispatch(t, l, um);
\r
9574 t.forceBlocks = new tinymce.ForceBlocks(t, {
\r
9575 forced_root_block : s.forced_root_block
\r
9578 t.editorCommands = new tinymce.EditorCommands(t);
\r
9581 t.serializer.onPreProcess.add(function(se, o) {
\r
9582 return t.onPreProcess.dispatch(t, o, se);
\r
9585 t.serializer.onPostProcess.add(function(se, o) {
\r
9586 return t.onPostProcess.dispatch(t, o, se);
\r
9589 t.onPreInit.dispatch(t);
\r
9591 if (!s.gecko_spellcheck)
\r
9592 t.getBody().spellcheck = 0;
\r
9597 t.controlManager.onPostRender.dispatch(t, t.controlManager);
\r
9598 t.onPostRender.dispatch(t);
\r
9600 if (s.directionality)
\r
9601 t.getBody().dir = s.directionality;
\r
9604 t.getBody().style.whiteSpace = "nowrap";
\r
9606 if (s.custom_elements) {
\r
9607 function handleCustom(ed, o) {
\r
9608 each(explode(s.custom_elements), function(v) {
\r
9611 if (v.indexOf('~') === 0) {
\r
9612 v = v.substring(1);
\r
9617 o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
\r
9618 o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
\r
9622 t.onBeforeSetContent.add(handleCustom);
\r
9623 t.onPostProcess.add(function(ed, o) {
\r
9625 handleCustom(ed, o);
\r
9629 if (s.handle_node_change_callback) {
\r
9630 t.onNodeChange.add(function(ed, cm, n) {
\r
9631 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
\r
9635 if (s.save_callback) {
\r
9636 t.onSaveContent.add(function(ed, o) {
\r
9637 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
9644 if (s.onchange_callback) {
\r
9645 t.onChange.add(function(ed, l) {
\r
9646 t.execCallback('onchange_callback', t, l);
\r
9650 if (s.convert_newlines_to_brs) {
\r
9651 t.onBeforeSetContent.add(function(ed, o) {
\r
9653 o.content = o.content.replace(/\r?\n/g, '<br />');
\r
9657 if (s.fix_nesting && isIE) {
\r
9658 t.onBeforeSetContent.add(function(ed, o) {
\r
9659 o.content = t._fixNesting(o.content);
\r
9663 if (s.preformatted) {
\r
9664 t.onPostProcess.add(function(ed, o) {
\r
9665 o.content = o.content.replace(/^\s*<pre.*?>/, '');
\r
9666 o.content = o.content.replace(/<\/pre>\s*$/, '');
\r
9669 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
\r
9673 if (s.verify_css_classes) {
\r
9674 t.serializer.attribValueFilter = function(n, v) {
\r
9677 if (n == 'class') {
\r
9678 // Build regexp for classes
\r
9679 if (!t.classesRE) {
\r
9680 cl = t.dom.getClasses();
\r
9682 if (cl.length > 0) {
\r
9685 each (cl, function(o) {
\r
9686 s += (s ? '|' : '') + o['class'];
\r
9689 t.classesRE = new RegExp('(' + s + ')', 'gi');
\r
9693 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
\r
9700 if (s.cleanup_callback) {
\r
9701 t.onBeforeSetContent.add(function(ed, o) {
\r
9702 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
9705 t.onPreProcess.add(function(ed, o) {
\r
9707 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
\r
9710 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
\r
9713 t.onPostProcess.add(function(ed, o) {
\r
9715 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
9718 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
\r
9722 if (s.save_callback) {
\r
9723 t.onGetContent.add(function(ed, o) {
\r
9725 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
9729 if (s.handle_event_callback) {
\r
9730 t.onEvent.add(function(ed, e, o) {
\r
9731 if (t.execCallback('handle_event_callback', e, ed, o) === false)
\r
9736 // Add visual aids when new contents is added
\r
9737 t.onSetContent.add(function() {
\r
9738 t.addVisual(t.getBody());
\r
9741 // Remove empty contents
\r
9742 if (s.padd_empty_editor) {
\r
9743 t.onPostProcess.add(function(ed, o) {
\r
9744 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
\r
9749 // Fix gecko link bug, when a link is placed at the end of block elements there is
\r
9750 // no way to move the caret behind the link. This fix adds a bogus br element after the link
\r
9751 function fixLinks(ed, o) {
\r
9752 each(ed.dom.select('a'), function(n) {
\r
9753 var pn = n.parentNode;
\r
9755 if (ed.dom.isBlock(pn) && pn.lastChild === n)
\r
9756 ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
\r
9760 t.onExecCommand.add(function(ed, cmd) {
\r
9761 if (cmd === 'CreateLink')
\r
9765 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
\r
9767 if (!s.readonly) {
\r
9769 // Design mode must be set here once again to fix a bug where
\r
9770 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
\r
9771 d.designMode = 'Off';
\r
9772 d.designMode = 'On';
\r
9774 // Will fail on Gecko if the editor is placed in an hidden container element
\r
9775 // The design mode will be set ones the editor is focused
\r
9780 // A small timeout was needed since firefox will remove. Bug: #1838304
\r
9781 setTimeout(function () {
\r
9785 t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
\r
9786 t.startContent = t.getContent({format : 'raw'});
\r
9787 t.initialized = true;
\r
9789 t.onInit.dispatch(t);
\r
9790 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
\r
9791 t.execCallback('init_instance_callback', t);
\r
9793 t.nodeChanged({initial : 1});
\r
9795 // Load specified content CSS last
\r
9796 if (s.content_css) {
\r
9797 tinymce.each(explode(s.content_css), function(u) {
\r
9798 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
\r
9802 // Handle auto focus
\r
9803 if (s.auto_focus) {
\r
9804 setTimeout(function () {
\r
9805 var ed = tinymce.get(s.auto_focus);
\r
9807 ed.selection.select(ed.getBody(), 1);
\r
9808 ed.selection.collapse(1);
\r
9809 ed.getWin().focus();
\r
9818 focus : function(sf) {
\r
9819 var oed, t = this, ce = t.settings.content_editable;
\r
9822 // Is not content editable or the selection is outside the area in IE
\r
9823 // the IE statement is needed to avoid bluring if element selections inside layers since
\r
9824 // the layer is like it's own document in IE
\r
9825 if (!ce && (!isIE || t.selection.getNode().ownerDocument != t.getDoc()))
\r
9826 t.getWin().focus();
\r
9830 if (tinymce.activeEditor != t) {
\r
9831 if ((oed = tinymce.activeEditor) != null)
\r
9832 oed.onDeactivate.dispatch(oed, t);
\r
9834 t.onActivate.dispatch(t, oed);
\r
9837 tinymce._setActive(t);
\r
9840 execCallback : function(n) {
\r
9841 var t = this, f = t.settings[n], s;
\r
9846 // Look through lookup
\r
9847 if (t.callbackLookup && (s = t.callbackLookup[n])) {
\r
9852 if (is(f, 'string')) {
\r
9853 s = f.replace(/\.\w+$/, '');
\r
9854 s = s ? tinymce.resolve(s) : 0;
\r
9855 f = tinymce.resolve(f);
\r
9856 t.callbackLookup = t.callbackLookup || {};
\r
9857 t.callbackLookup[n] = {func : f, scope : s};
\r
9860 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
\r
9863 translate : function(s) {
\r
9864 var c = this.settings.language || 'en', i18n = tinymce.i18n;
\r
9869 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
\r
9870 return i18n[c + '.' + b] || '{#' + b + '}';
\r
9874 getLang : function(n, dv) {
\r
9875 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
\r
9878 getParam : function(n, dv, ty) {
\r
9879 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
\r
9881 if (ty === 'hash') {
\r
9884 if (is(v, 'string')) {
\r
9885 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
\r
9889 o[tr(v[0])] = tr(v[1]);
\r
9891 o[tr(v[0])] = tr(v);
\r
9902 nodeChanged : function(o) {
\r
9903 var t = this, s = t.selection, n = s.getNode() || t.getBody();
\r
9905 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
\r
9906 if (t.initialized) {
\r
9908 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
\r
9910 // Get parents and add them to object
\r
9912 t.dom.getParent(n, function(node) {
\r
9913 if (node.nodeName == 'BODY')
\r
9916 o.parents.push(node);
\r
9919 t.onNodeChange.dispatch(
\r
9921 o ? o.controlManager || t.controlManager : t.controlManager,
\r
9929 addButton : function(n, s) {
\r
9932 t.buttons = t.buttons || {};
\r
9936 addCommand : function(n, f, s) {
\r
9937 this.execCommands[n] = {func : f, scope : s || this};
\r
9940 addQueryStateHandler : function(n, f, s) {
\r
9941 this.queryStateCommands[n] = {func : f, scope : s || this};
\r
9944 addQueryValueHandler : function(n, f, s) {
\r
9945 this.queryValueCommands[n] = {func : f, scope : s || this};
\r
9948 addShortcut : function(pa, desc, cmd_func, sc) {
\r
9951 if (!t.settings.custom_shortcuts)
\r
9954 t.shortcuts = t.shortcuts || {};
\r
9956 if (is(cmd_func, 'string')) {
\r
9959 cmd_func = function() {
\r
9960 t.execCommand(c, false, null);
\r
9964 if (is(cmd_func, 'object')) {
\r
9967 cmd_func = function() {
\r
9968 t.execCommand(c[0], c[1], c[2]);
\r
9972 each(explode(pa), function(pa) {
\r
9975 scope : sc || this,
\r
9982 each(explode(pa, '+'), function(v) {
\r
9991 o.charCode = v.charCodeAt(0);
\r
9992 o.keyCode = v.toUpperCase().charCodeAt(0);
\r
9996 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
\r
10002 execCommand : function(cmd, ui, val, a) {
\r
10003 var t = this, s = 0, o, st;
\r
10005 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
\r
10009 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
\r
10013 // Command callback
\r
10014 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
\r
10015 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10019 // Registred commands
\r
10020 if (o = t.execCommands[cmd]) {
\r
10021 st = o.func.call(o.scope, ui, val);
\r
10023 // Fall through on true
\r
10024 if (st !== true) {
\r
10025 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10030 // Plugin commands
\r
10031 each(t.plugins, function(p) {
\r
10032 if (p.execCommand && p.execCommand(cmd, ui, val)) {
\r
10033 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10042 // Theme commands
\r
10043 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
\r
10044 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10048 // Execute global commands
\r
10049 if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
\r
10050 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10054 // Editor commands
\r
10055 if (t.editorCommands.execCommand(cmd, ui, val)) {
\r
10056 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10060 // Browser commands
\r
10061 t.getDoc().execCommand(cmd, ui, val);
\r
10062 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10065 queryCommandState : function(cmd) {
\r
10066 var t = this, o, s;
\r
10068 // Is hidden then return undefined
\r
10069 if (t._isHidden())
\r
10072 // Registred commands
\r
10073 if (o = t.queryStateCommands[cmd]) {
\r
10074 s = o.func.call(o.scope);
\r
10076 // Fall though on true
\r
10081 // Registred commands
\r
10082 o = t.editorCommands.queryCommandState(cmd);
\r
10086 // Browser commands
\r
10088 return this.getDoc().queryCommandState(cmd);
\r
10090 // Fails sometimes see bug: 1896577
\r
10094 queryCommandValue : function(c) {
\r
10095 var t = this, o, s;
\r
10097 // Is hidden then return undefined
\r
10098 if (t._isHidden())
\r
10101 // Registred commands
\r
10102 if (o = t.queryValueCommands[c]) {
\r
10103 s = o.func.call(o.scope);
\r
10105 // Fall though on true
\r
10110 // Registred commands
\r
10111 o = t.editorCommands.queryCommandValue(c);
\r
10115 // Browser commands
\r
10117 return this.getDoc().queryCommandValue(c);
\r
10119 // Fails sometimes see bug: 1896577
\r
10123 show : function() {
\r
10126 DOM.show(t.getContainer());
\r
10131 hide : function() {
\r
10132 var t = this, d = t.getDoc();
\r
10134 // Fixed bug where IE has a blinking cursor left from the editor
\r
10136 d.execCommand('SelectAll');
\r
10138 // We must save before we hide so Safari doesn't crash
\r
10140 DOM.hide(t.getContainer());
\r
10141 DOM.setStyle(t.id, 'display', t.orgDisplay);
\r
10144 isHidden : function() {
\r
10145 return !DOM.isHidden(this.id);
\r
10148 setProgressState : function(b, ti, o) {
\r
10149 this.onSetProgressState.dispatch(this, b, ti, o);
\r
10154 load : function(o) {
\r
10155 var t = this, e = t.getElement(), h;
\r
10161 // Double encode existing entities in the value
\r
10162 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
\r
10165 if (!o.no_events)
\r
10166 t.onLoadContent.dispatch(t, o);
\r
10168 o.element = e = null;
\r
10174 save : function(o) {
\r
10175 var t = this, e = t.getElement(), h, f;
\r
10177 if (!e || !t.initialized)
\r
10183 // Add undo level will trigger onchange event
\r
10184 if (!o.no_events) {
\r
10185 t.undoManager.typing = 0;
\r
10186 t.undoManager.add();
\r
10190 h = o.content = t.getContent(o);
\r
10192 if (!o.no_events)
\r
10193 t.onSaveContent.dispatch(t, o);
\r
10197 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
\r
10200 // Update hidden form element
\r
10201 if (f = DOM.getParent(t.id, 'form')) {
\r
10202 each(f.elements, function(e) {
\r
10203 if (e.name == t.id) {
\r
10212 o.element = e = null;
\r
10217 setContent : function(h, o) {
\r
10221 o.format = o.format || 'html';
\r
10225 if (!o.no_events)
\r
10226 t.onBeforeSetContent.dispatch(t, o);
\r
10228 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
\r
10229 // It will also be impossible to place the caret in the editor unless there is a BR element present
\r
10230 if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
\r
10231 o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
\r
10232 o.format = 'raw';
\r
10235 o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
\r
10237 if (o.format != 'raw' && t.settings.cleanup) {
\r
10238 o.getInner = true;
\r
10239 o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
\r
10242 if (!o.no_events)
\r
10243 t.onSetContent.dispatch(t, o);
\r
10245 return o.content;
\r
10248 getContent : function(o) {
\r
10252 o.format = o.format || 'html';
\r
10255 if (!o.no_events)
\r
10256 t.onBeforeGetContent.dispatch(t, o);
\r
10258 if (o.format != 'raw' && t.settings.cleanup) {
\r
10259 o.getInner = true;
\r
10260 h = t.serializer.serialize(t.getBody(), o);
\r
10262 h = t.getBody().innerHTML;
\r
10264 h = h.replace(/^\s*|\s*$/g, '');
\r
10267 if (!o.no_events)
\r
10268 t.onGetContent.dispatch(t, o);
\r
10270 return o.content;
\r
10273 isDirty : function() {
\r
10276 return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
\r
10279 getContainer : function() {
\r
10282 if (!t.container)
\r
10283 t.container = DOM.get(t.editorContainer || t.id + '_parent');
\r
10285 return t.container;
\r
10288 getContentAreaContainer : function() {
\r
10289 return this.contentAreaContainer;
\r
10292 getElement : function() {
\r
10293 return DOM.get(this.settings.content_element || this.id);
\r
10296 getWin : function() {
\r
10299 if (!t.contentWindow) {
\r
10300 e = DOM.get(t.id + "_ifr");
\r
10303 t.contentWindow = e.contentWindow;
\r
10306 return t.contentWindow;
\r
10309 getDoc : function() {
\r
10312 if (!t.contentDocument) {
\r
10316 t.contentDocument = w.document;
\r
10319 return t.contentDocument;
\r
10322 getBody : function() {
\r
10323 return this.bodyElement || this.getDoc().body;
\r
10326 convertURL : function(u, n, e) {
\r
10327 var t = this, s = t.settings;
\r
10329 // Use callback instead
\r
10330 if (s.urlconverter_callback)
\r
10331 return t.execCallback('urlconverter_callback', u, e, true, n);
\r
10333 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
\r
10334 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
\r
10337 // Convert to relative
\r
10338 if (s.relative_urls)
\r
10339 return t.documentBaseURI.toRelative(u);
\r
10341 // Convert to absolute
\r
10342 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
\r
10347 addVisual : function(e) {
\r
10348 var t = this, s = t.settings;
\r
10350 e = e || t.getBody();
\r
10352 if (!is(t.hasVisual))
\r
10353 t.hasVisual = s.visual;
\r
10355 each(t.dom.select('table,a', e), function(e) {
\r
10358 switch (e.nodeName) {
\r
10360 v = t.dom.getAttrib(e, 'border');
\r
10362 if (!v || v == '0') {
\r
10364 t.dom.addClass(e, s.visual_table_class);
\r
10366 t.dom.removeClass(e, s.visual_table_class);
\r
10372 v = t.dom.getAttrib(e, 'name');
\r
10376 t.dom.addClass(e, 'mceItemAnchor');
\r
10378 t.dom.removeClass(e, 'mceItemAnchor');
\r
10385 t.onVisualAid.dispatch(t, e, t.hasVisual);
\r
10388 remove : function() {
\r
10389 var t = this, e = t.getContainer();
\r
10391 t.removed = 1; // Cancels post remove event execution
\r
10394 t.execCallback('remove_instance_callback', t);
\r
10395 t.onRemove.dispatch(t);
\r
10397 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
\r
10398 t.onExecCommand.listeners = [];
\r
10400 tinymce.remove(t);
\r
10404 destroy : function(s) {
\r
10407 // One time is enough
\r
10412 tinymce.removeUnload(t.destroy);
\r
10413 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
\r
10415 // Manual destroy
\r
10416 if (t.theme && t.theme.destroy)
\r
10417 t.theme.destroy();
\r
10419 // Destroy controls, selection and dom
\r
10420 t.controlManager.destroy();
\r
10421 t.selection.destroy();
\r
10424 // Remove all events
\r
10426 // Don't clear the window or document if content editable
\r
10427 // is enabled since other instances might still be present
\r
10428 if (!t.settings.content_editable) {
\r
10429 Event.clear(t.getWin());
\r
10430 Event.clear(t.getDoc());
\r
10433 Event.clear(t.getBody());
\r
10434 Event.clear(t.formElement);
\r
10437 if (t.formElement) {
\r
10438 t.formElement.submit = t.formElement._mceOldSubmit;
\r
10439 t.formElement._mceOldSubmit = null;
\r
10442 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
\r
10445 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
\r
10450 // Internal functions
\r
10452 _addEvents : function() {
\r
10453 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
\r
10454 var t = this, i, s = t.settings, lo = {
\r
10455 mouseup : 'onMouseUp',
\r
10456 mousedown : 'onMouseDown',
\r
10457 click : 'onClick',
\r
10458 keyup : 'onKeyUp',
\r
10459 keydown : 'onKeyDown',
\r
10460 keypress : 'onKeyPress',
\r
10461 submit : 'onSubmit',
\r
10462 reset : 'onReset',
\r
10463 contextmenu : 'onContextMenu',
\r
10464 dblclick : 'onDblClick',
\r
10465 paste : 'onPaste' // Doesn't work in all browsers yet
\r
10468 function eventHandler(e, o) {
\r
10471 // Don't fire events when it's removed
\r
10475 // Generic event handler
\r
10476 if (t.onEvent.dispatch(t, e, o) !== false) {
\r
10477 // Specific event handler
\r
10478 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
\r
10482 // Add DOM events
\r
10483 each(lo, function(v, k) {
\r
10485 case 'contextmenu':
\r
10486 if (tinymce.isOpera) {
\r
10487 // Fake contextmenu on Opera
\r
10488 t.dom.bind(t.getBody(), 'mousedown', function(e) {
\r
10490 e.fakeType = 'contextmenu';
\r
10495 t.dom.bind(t.getBody(), k, eventHandler);
\r
10499 t.dom.bind(t.getBody(), k, function(e) {
\r
10506 t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
\r
10510 t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
\r
10514 t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
\r
10519 // Fixes bug where a specified document_base_uri could result in broken images
\r
10520 // This will also fix drag drop of images in Gecko
\r
10521 if (tinymce.isGecko) {
\r
10522 // Convert all images to absolute URLs
\r
10523 /* t.onSetContent.add(function(ed, o) {
\r
10524 each(ed.dom.select('img'), function(e) {
\r
10527 if (v = e.getAttribute('_mce_src'))
\r
10528 e.src = t.documentBaseURI.toAbsolute(v);
\r
10532 t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
\r
10537 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
\r
10538 e.src = t.documentBaseURI.toAbsolute(v);
\r
10542 // Set various midas options in Gecko
\r
10544 function setOpts() {
\r
10545 var t = this, d = t.getDoc(), s = t.settings;
\r
10547 if (isGecko && !s.readonly) {
\r
10548 if (t._isHidden()) {
\r
10550 if (!s.content_editable)
\r
10551 d.designMode = 'On';
\r
10553 // Fails if it's hidden
\r
10558 // Try new Gecko method
\r
10559 d.execCommand("styleWithCSS", 0, false);
\r
10561 // Use old method
\r
10562 if (!t._isHidden())
\r
10563 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
\r
10566 if (!s.table_inline_editing)
\r
10567 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
\r
10569 if (!s.object_resizing)
\r
10570 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
\r
10574 t.onBeforeExecCommand.add(setOpts);
\r
10575 t.onMouseDown.add(setOpts);
\r
10578 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
\r
10579 // WebKit can't even do simple things like selecting an image
\r
10580 // This also fixes so it's possible to select mceItemAnchors
\r
10581 if (tinymce.isWebKit) {
\r
10582 t.onClick.add(function(ed, e) {
\r
10585 // Needs tobe the setBaseAndExtend or it will fail to select floated images
\r
10586 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor')))
\r
10587 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
\r
10591 // Add node change handlers
\r
10592 t.onMouseUp.add(t.nodeChanged);
\r
10593 t.onClick.add(t.nodeChanged);
\r
10594 t.onKeyUp.add(function(ed, e) {
\r
10595 var c = e.keyCode;
\r
10597 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
10601 // Add reset handler
\r
10602 t.onReset.add(function() {
\r
10603 t.setContent(t.startContent, {format : 'raw'});
\r
10607 if (s.custom_shortcuts) {
\r
10608 if (s.custom_undo_redo_keyboard_shortcuts) {
\r
10609 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
\r
10610 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
\r
10613 // Add default shortcuts for gecko
\r
10615 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
\r
10616 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
\r
10617 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
\r
10620 // BlockFormat shortcuts keys
\r
10621 for (i=1; i<=6; i++)
\r
10622 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
\r
10624 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
\r
10625 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
\r
10626 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
\r
10628 function find(e) {
\r
10631 if (!e.altKey && !e.ctrlKey && !e.metaKey)
\r
10634 each(t.shortcuts, function(o) {
\r
10635 if (tinymce.isMac && o.ctrl != e.metaKey)
\r
10637 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
\r
10640 if (o.alt != e.altKey)
\r
10643 if (o.shift != e.shiftKey)
\r
10646 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
\r
10655 t.onKeyUp.add(function(ed, e) {
\r
10659 return Event.cancel(e);
\r
10662 t.onKeyPress.add(function(ed, e) {
\r
10666 return Event.cancel(e);
\r
10669 t.onKeyDown.add(function(ed, e) {
\r
10673 o.func.call(o.scope);
\r
10674 return Event.cancel(e);
\r
10679 if (tinymce.isIE) {
\r
10680 // Fix so resize will only update the width and height attributes not the styles of an image
\r
10681 // It will also block mceItemNoResize items
\r
10682 t.dom.bind(t.getDoc(), 'controlselect', function(e) {
\r
10683 var re = t.resizeInfo, cb;
\r
10687 // Don't do this action for non image elements
\r
10688 if (e.nodeName !== 'IMG')
\r
10692 t.dom.unbind(re.node, re.ev, re.cb);
\r
10694 if (!t.dom.hasClass(e, 'mceItemNoResize')) {
\r
10695 ev = 'resizeend';
\r
10696 cb = t.dom.bind(e, ev, function(e) {
\r
10701 if (v = t.dom.getStyle(e, 'width')) {
\r
10702 t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
\r
10703 t.dom.setStyle(e, 'width', '');
\r
10706 if (v = t.dom.getStyle(e, 'height')) {
\r
10707 t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
\r
10708 t.dom.setStyle(e, 'height', '');
\r
10712 ev = 'resizestart';
\r
10713 cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
\r
10716 re = t.resizeInfo = {
\r
10723 t.onKeyDown.add(function(ed, e) {
\r
10724 switch (e.keyCode) {
\r
10726 // Fix IE control + backspace browser bug
\r
10727 if (t.selection.getRng().item) {
\r
10728 ed.dom.remove(t.selection.getRng().item(0));
\r
10729 return Event.cancel(e);
\r
10734 /*if (t.dom.boxModel) {
\r
10735 t.getBody().style.height = '100%';
\r
10737 Event.add(t.getWin(), 'resize', function(e) {
\r
10738 var docElm = t.getDoc().documentElement;
\r
10740 docElm.style.height = (docElm.offsetHeight - 10) + 'px';
\r
10745 if (tinymce.isOpera) {
\r
10746 t.onClick.add(function(ed, e) {
\r
10747 Event.prevent(e);
\r
10751 // Add custom undo/redo handlers
\r
10752 if (s.custom_undo_redo) {
\r
10753 function addUndo() {
\r
10754 t.undoManager.typing = 0;
\r
10755 t.undoManager.add();
\r
10758 t.dom.bind(t.getDoc(), 'focusout', function(e) {
\r
10759 if (!t.removed && t.undoManager.typing)
\r
10763 t.onKeyUp.add(function(ed, e) {
\r
10764 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
\r
10768 t.onKeyDown.add(function(ed, e) {
\r
10769 // Is caracter positon keys
\r
10770 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
\r
10771 if (t.undoManager.typing)
\r
10777 if (!t.undoManager.typing) {
\r
10778 t.undoManager.add();
\r
10779 t.undoManager.typing = 1;
\r
10783 t.onMouseDown.add(function() {
\r
10784 if (t.undoManager.typing)
\r
10790 _isHidden : function() {
\r
10796 // Weird, wheres that cursor selection?
\r
10797 s = this.selection.getSel();
\r
10798 return (!s || !s.rangeCount || s.rangeCount == 0);
\r
10801 // Fix for bug #1867292
\r
10802 _fixNesting : function(s) {
\r
10805 s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
\r
10808 // Handle end element
\r
10813 if (c !== d[d.length - 1].tag) {
\r
10814 for (i=d.length - 1; i>=0; i--) {
\r
10815 if (d[i].tag === c) {
\r
10825 if (d.length && d[d.length - 1].close) {
\r
10826 a = a + '</' + d[d.length - 1].tag + '>';
\r
10832 if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
\r
10835 // Ignore closed ones
\r
10836 if (/\/>$/.test(a))
\r
10839 d.push({tag : c}); // Push start element
\r
10845 // End all open tags
\r
10846 for (i=d.length - 1; i>=0; i--)
\r
10847 s += '</' + d[i].tag + '>';
\r
10854 (function(tinymce) {
\r
10855 // Added for compression purposes
\r
10856 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
\r
10858 tinymce.EditorCommands = function(editor) {
\r
10859 var dom = editor.dom,
\r
10860 selection = editor.selection,
\r
10861 commands = {state: {}, exec : {}, value : {}},
\r
10862 settings = editor.settings,
\r
10865 function execCommand(command, ui, value) {
\r
10868 command = command.toLowerCase();
\r
10869 if (func = commands.exec[command]) {
\r
10870 func(command, ui, value);
\r
10877 function queryCommandState(command) {
\r
10880 command = command.toLowerCase();
\r
10881 if (func = commands.state[command])
\r
10882 return func(command);
\r
10887 function queryCommandValue(command) {
\r
10890 command = command.toLowerCase();
\r
10891 if (func = commands.value[command])
\r
10892 return func(command);
\r
10897 function addCommands(command_list, type) {
\r
10898 type = type || 'exec';
\r
10900 each(command_list, function(callback, command) {
\r
10901 each(command.toLowerCase().split(','), function(command) {
\r
10902 commands[type][command] = callback;
\r
10907 // Expose public methods
\r
10908 tinymce.extend(this, {
\r
10909 execCommand : execCommand,
\r
10910 queryCommandState : queryCommandState,
\r
10911 queryCommandValue : queryCommandValue,
\r
10912 addCommands : addCommands
\r
10915 // Private methods
\r
10917 function execNativeCommand(command, ui, value) {
\r
10918 if (ui === undefined)
\r
10921 if (value === undefined)
\r
10924 return editor.getDoc().execCommand(command, ui, value);
\r
10927 function isFormatMatch(name) {
\r
10928 return editor.formatter.match(name);
\r
10931 function toggleFormat(name, value) {
\r
10932 editor.formatter.toggle(name, value ? {value : value} : undefined);
\r
10935 function storeSelection(type) {
\r
10936 bookmark = selection.getBookmark(type);
\r
10939 function restoreSelection() {
\r
10940 selection.moveToBookmark(bookmark);
\r
10943 // Add execCommand overrides
\r
10945 // Ignore these, added for compatibility
\r
10946 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
\r
10948 // Add undo manager logic
\r
10949 'mceEndUndoLevel,mceAddUndoLevel' : function() {
\r
10950 editor.undoManager.add();
\r
10953 'Cut,Copy,Paste' : function(command) {
\r
10954 var doc = editor.getDoc(), failed;
\r
10956 // Try executing the native command
\r
10958 execNativeCommand(command);
\r
10960 // Command failed
\r
10964 // Present alert message about clipboard access not being available
\r
10965 if (failed || !doc.queryCommandEnabled(command)) {
\r
10966 if (tinymce.isGecko) {
\r
10967 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
\r
10969 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
\r
10972 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
\r
10976 // Override unlink command
\r
10977 unlink : function(command) {
\r
10978 if (selection.isCollapsed())
\r
10979 selection.select(selection.getNode());
\r
10981 execNativeCommand(command);
\r
10982 selection.collapse(FALSE);
\r
10985 // Override justify commands to use the text formatter engine
\r
10986 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
10987 var align = command.substring(7);
\r
10989 // Remove all other alignments first
\r
10990 each('left,center,right,full'.split(','), function(name) {
\r
10991 if (align != name)
\r
10992 editor.formatter.remove('align' + name);
\r
10995 toggleFormat('align' + align);
\r
10998 // Override list commands to fix WebKit bug
\r
10999 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
11000 var listElm, listParent;
\r
11002 execNativeCommand(command);
\r
11004 // WebKit produces lists within block elements so we need to split them
\r
11005 // we will replace the native list creation logic to custom logic later on
\r
11006 // TODO: Remove this when the list creation logic is removed
\r
11007 listElm = dom.getParent(selection.getNode(), 'ol,ul');
\r
11009 listParent = listElm.parentNode;
\r
11011 // If list is within a text block then split that block
\r
11012 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
\r
11013 storeSelection();
\r
11014 dom.split(listParent, listElm);
\r
11015 restoreSelection();
\r
11020 // Override commands to use the text formatter engine
\r
11021 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
11022 toggleFormat(command);
\r
11025 // Override commands to use the text formatter engine
\r
11026 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
\r
11027 toggleFormat(command, value);
\r
11030 FontSize : function(command, ui, value) {
\r
11031 var fontClasses, fontSizes;
\r
11033 // Convert font size 1-7 to styles
\r
11034 if (value >= 1 && value <= 7) {
\r
11035 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
11036 fontClasses = tinymce.explode(settings.font_size_classes);
\r
11039 value = fontClasses[value - 1] || value;
\r
11041 value = fontSizes[value - 1] || value;
\r
11044 toggleFormat(command, value);
\r
11047 RemoveFormat : function(command) {
\r
11048 editor.formatter.remove(command);
\r
11051 mceBlockQuote : function(command) {
\r
11052 toggleFormat('blockquote');
\r
11055 FormatBlock : function(command, ui, value) {
\r
11056 return toggleFormat(value);
\r
11059 mceCleanup : function() {
\r
11060 storeSelection();
\r
11061 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
\r
11062 restoreSelection();
\r
11065 mceRemoveNode : function(command, ui, value) {
\r
11066 var node = value || selection.getNode();
\r
11068 // Make sure that the body node isn't removed
\r
11069 if (node != ed.getBody()) {
\r
11070 storeSelection();
\r
11071 editor.dom.remove(node, TRUE);
\r
11072 restoreSelection();
\r
11076 mceSelectNodeDepth : function(command, ui, value) {
\r
11079 dom.getParent(selection.getNode(), function(node) {
\r
11080 if (node.nodeType == 1 && counter++ == value) {
\r
11081 selection.select(node);
\r
11084 }, editor.getBody());
\r
11087 mceSelectNode : function(command, ui, value) {
\r
11088 selection.select(value);
\r
11091 mceInsertContent : function(command, ui, value) {
\r
11092 selection.setContent(value);
\r
11095 mceInsertRawHTML : function(command, ui, value) {
\r
11096 selection.setContent('tiny_mce_marker');
\r
11097 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value));
\r
11100 mceSetContent : function(command, ui, value) {
\r
11101 editor.setContent(value);
\r
11104 'Indent,Outdent' : function(command) {
\r
11105 var intentValue, indentUnit, value;
\r
11107 // Setup indent level
\r
11108 intentValue = settings.indentation;
\r
11109 indentUnit = /[a-z%]+$/i.exec(intentValue);
\r
11110 intentValue = parseInt(intentValue);
\r
11112 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
\r
11113 each(selection.getSelectedBlocks(), function(element) {
\r
11114 if (command == 'outdent') {
\r
11115 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
\r
11116 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
\r
11118 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
\r
11121 execNativeCommand(command);
\r
11124 mceRepaint : function() {
\r
11127 if (tinymce.isGecko) {
\r
11129 storeSelection(TRUE);
\r
11131 if (selection.getSel())
\r
11132 selection.getSel().selectAllChildren(editor.getBody());
\r
11134 selection.collapse(TRUE);
\r
11135 restoreSelection();
\r
11142 InsertHorizontalRule : function() {
\r
11143 selection.setContent('<hr />');
\r
11146 mceToggleVisualAid : function() {
\r
11147 editor.hasVisual = !editor.hasVisual;
\r
11148 editor.addVisual();
\r
11151 mceReplaceContent : function(command, ui, value) {
\r
11152 selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
\r
11155 mceInsertLink : function(command, ui, value) {
\r
11156 var link = dom.getParent(selection.getNode(), 'a');
\r
11158 if (tinymce.is(value, 'string'))
\r
11159 value = {href : value};
\r
11162 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
\r
11163 each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
\r
11164 dom.setAttribs(link, value);
\r
11168 dom.setAttribs(link, value);
\r
11170 ed.dom.remove(link, TRUE);
\r
11175 // Add queryCommandState overrides
\r
11177 // Override justify commands
\r
11178 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
11179 return isFormatMatch('align' + command.substring(7));
\r
11182 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
11183 return isFormatMatch(command);
\r
11186 mceBlockQuote : function() {
\r
11187 return isFormatMatch('blockquote');
\r
11190 Outdent : function() {
\r
11193 if (settings.inline_styles) {
\r
11194 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
11197 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
11201 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
\r
11204 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
11205 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
\r
11209 // Add queryCommandValue overrides
\r
11211 'FontSize,FontName' : function(command) {
\r
11212 var value = 0, parent;
\r
11214 if (parent = dom.getParent(selection.getNode(), 'span')) {
\r
11215 if (command == 'fontsize')
\r
11216 value = parent.style.fontSize;
\r
11218 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
\r
11225 // Add undo manager logic
\r
11226 if (settings.custom_undo_redo) {
\r
11228 Undo : function() {
\r
11229 editor.undoManager.undo();
\r
11232 Redo : function() {
\r
11233 editor.undoManager.redo();
\r
11239 (function(tinymce) {
\r
11240 tinymce.create('tinymce.UndoManager', {
\r
11245 UndoManager : function(ed) {
\r
11246 var t = this, Dispatcher = tinymce.util.Dispatcher;
\r
11250 t.onAdd = new Dispatcher(this);
\r
11251 t.onUndo = new Dispatcher(this);
\r
11252 t.onRedo = new Dispatcher(this);
\r
11255 add : function(l) {
\r
11256 var t = this, i, ed = t.editor, b, s = ed.settings, la;
\r
11259 l.content = l.content || ed.getContent({format : 'raw', no_events : 1});
\r
11260 l.content = l.content.replace(/^\s*|\s*$/g, '');
\r
11262 // Add undo level if needed
\r
11263 la = t.data[t.index];
\r
11264 if (la && la.content == l.content) {
\r
11265 if (t.index > 0 || t.data.length == 1)
\r
11269 // Time to compress
\r
11270 if (s.custom_undo_redo_levels) {
\r
11271 if (t.data.length > s.custom_undo_redo_levels) {
\r
11272 for (i = 0; i < t.data.length - 1; i++)
\r
11273 t.data[i] = t.data[i + 1];
\r
11276 t.index = t.data.length;
\r
11280 if (s.custom_undo_redo_restore_selection)
\r
11281 l.bookmark = b = l.bookmark || ed.selection.getBookmark(2, true);
\r
11283 // Crop array if needed
\r
11284 if (t.index < t.data.length - 1) {
\r
11285 // Treat first level as initial
\r
11286 if (t.index == 0)
\r
11289 t.data.length = t.index + 1;
\r
11293 t.index = t.data.length - 1;
\r
11295 t.onAdd.dispatch(t, l);
\r
11296 ed.isNotDirty = 0;
\r
11298 //console.log(t.index);
\r
11299 //console.dir(t.data);
\r
11304 undo : function() {
\r
11305 var t = this, ed = t.editor, l = l, i;
\r
11312 if (t.index > 0) {
\r
11313 l = t.data[--t.index];
\r
11315 ed.setContent(l.content, {format : 'raw'});
\r
11316 ed.selection.moveToBookmark(l.bookmark);
\r
11318 t.onUndo.dispatch(t, l);
\r
11324 redo : function() {
\r
11325 var t = this, ed = t.editor, l = null;
\r
11327 if (t.index < t.data.length - 1) {
\r
11328 l = t.data[++t.index];
\r
11329 ed.setContent(l.content, {format : 'raw'});
\r
11330 ed.selection.moveToBookmark(l.bookmark);
\r
11332 t.onRedo.dispatch(t, l);
\r
11338 clear : function() {
\r
11346 hasUndo : function() {
\r
11347 return this.index > 0 || this.typing;
\r
11350 hasRedo : function() {
\r
11351 return this.index < this.data.length - 1;
\r
11356 (function(tinymce) {
\r
11358 var Event = tinymce.dom.Event,
\r
11359 isIE = tinymce.isIE,
\r
11360 isGecko = tinymce.isGecko,
\r
11361 isOpera = tinymce.isOpera,
\r
11362 each = tinymce.each,
\r
11363 extend = tinymce.extend,
\r
11367 // Checks if the selection/caret is at the end of the specified block element
\r
11368 function isAtEnd(rng, par) {
\r
11369 var rng2 = par.ownerDocument.createRange();
\r
11371 rng2.setStart(rng.endContainer, rng.endOffset);
\r
11372 rng2.setEndAfter(par);
\r
11374 // 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
11375 return rng2.cloneContents().textContent.length == 0;
\r
11378 function isEmpty(n) {
\r
11381 n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
\r
11382 n = n.replace(/<[^>]+>/g, ''); // Remove all tags
\r
11384 return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
\r
11387 function splitList(selection, dom, li) {
\r
11388 var listBlock, block;
\r
11390 if (isEmpty(li)) {
\r
11391 listBlock = dom.getParent(li, 'ul,ol');
\r
11393 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
\r
11394 dom.split(listBlock, li);
\r
11395 block = dom.create('p', 0, '<br _mce_bogus="1" />');
\r
11396 dom.replace(block, li);
\r
11397 selection.select(block, 1);
\r
11406 tinymce.create('tinymce.ForceBlocks', {
\r
11407 ForceBlocks : function(ed) {
\r
11408 var t = this, s = ed.settings, elm;
\r
11412 elm = (s.forced_root_block || 'p').toLowerCase();
\r
11413 s.element = elm.toUpperCase();
\r
11415 ed.onPreInit.add(t.setup, t);
\r
11417 t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi');
\r
11418 t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11419 t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11420 t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi');
\r
11421 t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11423 function padd(ed, o) {
\r
11425 o.content = o.content.replace(t.reOpera, '</' + elm + '>');
\r
11427 o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
\r
11429 if (!isIE && !isOpera && o.set) {
\r
11430 // Use instead of BR in padded paragraphs
\r
11431 o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
\r
11432 o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
\r
11434 o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
\r
11437 ed.onBeforeSetContent.add(padd);
\r
11438 ed.onPostProcess.add(padd);
\r
11440 if (s.forced_root_block) {
\r
11441 ed.onInit.add(t.forceRoots, t);
\r
11442 ed.onSetContent.add(t.forceRoots, t);
\r
11443 ed.onBeforeGetContent.add(t.forceRoots, t);
\r
11447 setup : function() {
\r
11448 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
\r
11450 // Force root blocks when typing and when getting output
\r
11451 if (s.forced_root_block) {
\r
11452 ed.onBeforeExecCommand.add(t.forceRoots, t);
\r
11453 ed.onKeyUp.add(t.forceRoots, t);
\r
11454 ed.onPreProcess.add(t.forceRoots, t);
\r
11457 if (s.force_br_newlines) {
\r
11458 // Force IE to produce BRs on enter
\r
11460 ed.onKeyPress.add(function(ed, e) {
\r
11463 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
\r
11464 selection.setContent('<br id="__" /> ', {format : 'raw'});
\r
11465 n = dom.get('__');
\r
11466 n.removeAttribute('id');
\r
11467 selection.select(n);
\r
11468 selection.collapse();
\r
11469 return Event.cancel(e);
\r
11475 if (!isIE && s.force_p_newlines) {
\r
11476 ed.onKeyPress.add(function(ed, e) {
\r
11477 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
\r
11482 ed.onKeyDown.add(function(ed, e) {
\r
11483 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
\r
11484 t.backspaceDelete(e, e.keyCode == 8);
\r
11489 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
\r
11490 if (tinymce.isWebKit) {
\r
11491 function insertBr(ed) {
\r
11492 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
\r
11494 // Insert BR element
\r
11495 rng.insertNode(br = dom.create('br'));
\r
11497 // Place caret after BR
\r
11498 rng.setStartAfter(br);
\r
11499 rng.setEndAfter(br);
\r
11500 selection.setRng(rng);
\r
11502 // Could not place caret after BR then insert an nbsp entity and move the caret
\r
11503 if (selection.getSel().focusNode == br.previousSibling) {
\r
11504 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
\r
11505 selection.collapse(TRUE);
\r
11508 // Create a temporary DIV after the BR and get the position as it
\r
11509 // seems like getPos() returns 0 for text nodes and BR elements.
\r
11510 dom.insertAfter(div, br);
\r
11511 divYPos = dom.getPos(div).y;
\r
11514 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
\r
11515 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
\r
11516 ed.getWin().scrollTo(0, divYPos);
\r
11519 ed.onKeyPress.add(function(ed, e) {
\r
11520 if (e.keyCode == 13 && (e.shiftKey || s.force_br_newlines)) {
\r
11527 // Padd empty inline elements within block elements
\r
11528 // For example: <p><strong><em></em></strong></p> becomes <p><strong><em> </em></strong></p>
\r
11529 ed.onPreProcess.add(function(ed, o) {
\r
11530 each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
\r
11531 if (isEmpty(p)) {
\r
11532 each(dom.select('span,em,strong,b,i', o.node), function(n) {
\r
11533 if (!n.hasChildNodes()) {
\r
11534 n.appendChild(ed.getDoc().createTextNode('\u00a0'));
\r
11535 return FALSE; // Break the loop one padding is enough
\r
11542 // IE specific fixes
\r
11544 // Replaces IE:s auto generated paragraphs with the specified element name
\r
11545 if (s.element != 'P') {
\r
11546 ed.onKeyPress.add(function(ed, e) {
\r
11547 t.lastElm = selection.getNode().nodeName;
\r
11550 ed.onKeyUp.add(function(ed, e) {
\r
11551 var bl, n = selection.getNode(), b = ed.getBody();
\r
11553 if (b.childNodes.length === 1 && n.nodeName == 'P') {
\r
11554 n = dom.rename(n, s.element);
\r
11555 selection.select(n);
\r
11556 selection.collapse();
\r
11557 ed.nodeChanged();
\r
11558 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
\r
11559 bl = dom.getParent(n, 'p');
\r
11562 dom.rename(bl, s.element);
\r
11563 ed.nodeChanged();
\r
11571 find : function(n, t, s) {
\r
11572 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
\r
11574 while (n = w.nextNode()) {
\r
11578 if (t == 0 && n == s)
\r
11582 if (t == 1 && c == s)
\r
11589 forceRoots : function(ed, e) {
\r
11590 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
11591 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
\r
11593 // Fix for bug #1863847
\r
11594 //if (e && e.keyCode == 13)
\r
11597 // Wrap non blocks into blocks
\r
11598 for (i = nl.length - 1; i >= 0; i--) {
\r
11601 // Ignore internal elements
\r
11602 if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
\r
11607 // Is text or non block element
\r
11608 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
\r
11610 // Create new block but ignore whitespace
\r
11611 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
\r
11612 // Store selection
\r
11613 if (si == -2 && r) {
\r
11615 // If selection is element then mark it
\r
11616 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
\r
11617 // Save the id of the selected element
\r
11618 eid = n.getAttribute("id");
\r
11619 n.setAttribute("id", "__mce");
\r
11621 // If element is inside body, might not be the case in contentEdiable mode
\r
11622 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
\r
11623 so = r.startOffset;
\r
11624 eo = r.endOffset;
\r
11625 si = t.find(b, 0, r.startContainer);
\r
11626 ei = t.find(b, 0, r.endContainer);
\r
11630 tr = d.body.createTextRange();
\r
11631 tr.moveToElementText(b);
\r
11633 bp = tr.move('character', c) * -1;
\r
11635 tr = r.duplicate();
\r
11637 sp = tr.move('character', c) * -1;
\r
11639 tr = r.duplicate();
\r
11641 le = (tr.move('character', c) * -1) - sp;
\r
11648 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
\r
11649 // See: http://support.microsoft.com/kb/829907
\r
11650 bl = ed.dom.create(ed.settings.forced_root_block);
\r
11651 nx.parentNode.replaceChild(bl, nx);
\r
11652 bl.appendChild(nx);
\r
11655 if (bl.hasChildNodes())
\r
11656 bl.insertBefore(nx, bl.firstChild);
\r
11658 bl.appendChild(nx);
\r
11661 bl = null; // Time to create new block
\r
11664 // Restore selection
\r
11667 bl = b.getElementsByTagName(ed.settings.element)[0];
\r
11668 r = d.createRange();
\r
11670 // Select last location or generated block
\r
11672 r.setStart(t.find(b, 1, si), so);
\r
11674 r.setStart(bl, 0);
\r
11676 // Select last location or generated block
\r
11678 r.setEnd(t.find(b, 1, ei), eo);
\r
11683 s.removeAllRanges();
\r
11688 r = s.createRange();
\r
11689 r.moveToElementText(b);
\r
11691 r.moveStart('character', si);
\r
11692 r.moveEnd('character', ei);
\r
11698 } else if (!isIE && (n = ed.dom.get('__mce'))) {
\r
11699 // Restore the id of the selected element
\r
11701 n.setAttribute('id', eid);
\r
11703 n.removeAttribute('id');
\r
11705 // Move caret before selected element
\r
11706 r = d.createRange();
\r
11707 r.setStartBefore(n);
\r
11708 r.setEndBefore(n);
\r
11713 getParentBlock : function(n) {
\r
11714 var d = this.dom;
\r
11716 return d.getParent(n, d.isBlock);
\r
11719 insertPara : function(e) {
\r
11720 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
11721 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
11723 // If root blocks are forced then use Operas default behavior since it's really good
\r
11724 // Removed due to bug: #1853816
\r
11725 // if (se.forced_root_block && isOpera)
\r
11728 // Setup before range
\r
11729 rb = d.createRange();
\r
11731 // If is before the first block element and in body, then move it into first block element
\r
11732 rb.setStart(s.anchorNode, s.anchorOffset);
\r
11733 rb.collapse(TRUE);
\r
11735 // Setup after range
\r
11736 ra = d.createRange();
\r
11738 // If is before the first block element and in body, then move it into first block element
\r
11739 ra.setStart(s.focusNode, s.focusOffset);
\r
11740 ra.collapse(TRUE);
\r
11742 // Setup start/end points
\r
11743 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
\r
11744 sn = dir ? s.anchorNode : s.focusNode;
\r
11745 so = dir ? s.anchorOffset : s.focusOffset;
\r
11746 en = dir ? s.focusNode : s.anchorNode;
\r
11747 eo = dir ? s.focusOffset : s.anchorOffset;
\r
11749 // If selection is in empty table cell
\r
11750 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
\r
11751 if (sn.firstChild.nodeName == 'BR')
\r
11752 dom.remove(sn.firstChild); // Remove BR
\r
11754 // Create two new block elements
\r
11755 if (sn.childNodes.length == 0) {
\r
11756 ed.dom.add(sn, se.element, null, '<br />');
\r
11757 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
11759 n = sn.innerHTML;
\r
11760 sn.innerHTML = '';
\r
11761 ed.dom.add(sn, se.element, null, n);
\r
11762 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
11765 // Move caret into the last one
\r
11766 r = d.createRange();
\r
11767 r.selectNodeContents(aft);
\r
11769 ed.selection.setRng(r);
\r
11774 // If the caret is in an invalid location in FF we need to move it into the first block
\r
11775 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
\r
11776 sn = en = sn.firstChild;
\r
11778 rb = d.createRange();
\r
11779 rb.setStart(sn, 0);
\r
11780 ra = d.createRange();
\r
11781 ra.setStart(en, 0);
\r
11784 // Never use body as start or end node
\r
11785 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
11786 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
\r
11787 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
11788 en = en.nodeName == "BODY" ? en.firstChild : en;
\r
11790 // Get start and end blocks
\r
11791 sb = t.getParentBlock(sn);
\r
11792 eb = t.getParentBlock(en);
\r
11793 bn = sb ? sb.nodeName : se.element; // Get block name to create
\r
11795 // Return inside list use default browser behavior
\r
11796 if (n = t.dom.getParent(sb, 'li,pre')) {
\r
11797 if (n.nodeName == 'LI')
\r
11798 return splitList(ed.selection, t.dom, n);
\r
11803 // If caption or absolute layers then always generate new blocks within
\r
11804 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
11809 // If caption or absolute layers then always generate new blocks within
\r
11810 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
11816 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
\r
11821 // Setup new before and after blocks
\r
11822 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
\r
11823 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
\r
11825 // Remove id from after clone
\r
11826 aft.removeAttribute('id');
\r
11828 // Is header and cursor is at the end, then force paragraph under
\r
11829 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
\r
11830 aft = ed.dom.create(se.element);
\r
11832 // Find start chop node
\r
11835 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
11839 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
\r
11841 // Find end chop node
\r
11844 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
11848 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
\r
11850 // Place first chop part into before block element
\r
11851 if (sc.nodeName == bn)
\r
11852 rb.setStart(sc, 0);
\r
11854 rb.setStartBefore(sc);
\r
11856 rb.setEnd(sn, so);
\r
11857 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
11859 // Place secnd chop part within new block element
\r
11861 ra.setEndAfter(ec);
\r
11863 //console.debug(s.focusNode, s.focusOffset);
\r
11866 ra.setStart(en, eo);
\r
11867 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
11869 // Create range around everything
\r
11870 r = d.createRange();
\r
11871 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
\r
11872 r.setStartBefore(sc.parentNode);
\r
11874 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
\r
11875 r.setStartBefore(rb.startContainer);
\r
11877 r.setStart(rb.startContainer, rb.startOffset);
\r
11880 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
\r
11881 r.setEndAfter(ec.parentNode);
\r
11883 r.setEnd(ra.endContainer, ra.endOffset);
\r
11885 // Delete and replace it with new block elements
\r
11886 r.deleteContents();
\r
11889 ed.getWin().scrollTo(0, vp.y);
\r
11891 // Never wrap blocks in blocks
\r
11892 if (bef.firstChild && bef.firstChild.nodeName == bn)
\r
11893 bef.innerHTML = bef.firstChild.innerHTML;
\r
11895 if (aft.firstChild && aft.firstChild.nodeName == bn)
\r
11896 aft.innerHTML = aft.firstChild.innerHTML;
\r
11898 // Padd empty blocks
\r
11899 if (isEmpty(bef))
\r
11900 bef.innerHTML = '<br />';
\r
11902 function appendStyles(e, en) {
\r
11903 var nl = [], nn, n, i;
\r
11905 e.innerHTML = '';
\r
11907 // Make clones of style elements
\r
11908 if (se.keep_styles) {
\r
11911 // We only want style specific elements
\r
11912 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
\r
11913 nn = n.cloneNode(FALSE);
\r
11914 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
\r
11917 } while (n = n.parentNode);
\r
11920 // Append style elements to aft
\r
11921 if (nl.length > 0) {
\r
11922 for (i = nl.length - 1, nn = e; i >= 0; i--)
\r
11923 nn = nn.appendChild(nl[i]);
\r
11925 // Padd most inner style element
\r
11926 nl[0].innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
11927 return nl[0]; // Move caret to most inner element
\r
11929 e.innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
11932 // Fill empty afterblook with current style
\r
11933 if (isEmpty(aft))
\r
11934 car = appendStyles(aft, en);
\r
11936 // Opera needs this one backwards for older versions
\r
11937 if (isOpera && parseFloat(opera.version()) < 9.5) {
\r
11938 r.insertNode(bef);
\r
11939 r.insertNode(aft);
\r
11941 r.insertNode(aft);
\r
11942 r.insertNode(bef);
\r
11949 function first(n) {
\r
11950 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
\r
11953 // Move cursor and scroll into view
\r
11954 r = d.createRange();
\r
11955 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
\r
11957 s.removeAllRanges();
\r
11960 // 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
11961 y = ed.dom.getPos(aft).y;
\r
11962 ch = aft.clientHeight;
\r
11964 // Is element within viewport
\r
11965 if (y < vp.y || y + ch > vp.y + vp.h) {
\r
11966 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
11967 //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
11973 backspaceDelete : function(e, bs) {
\r
11974 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;
\r
11976 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
\r
11977 // This workaround removes the element by hand and moves the caret to the previous element
\r
11978 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
\r
11979 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
\r
11980 // Find previous block element
\r
11982 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
\r
11985 if (sc != b.firstChild) {
\r
11986 // Find last text node
\r
11987 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
\r
11988 while (tn = w.nextNode())
\r
11991 // Place caret at the end of last text node
\r
11992 r = ed.getDoc().createRange();
\r
11993 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
\r
11994 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
\r
11997 // Remove the target container
\r
11998 ed.dom.remove(sc);
\r
12001 return Event.cancel(e);
\r
12006 // Gecko generates BR elements here and there, we don't like those so lets remove them
\r
12007 function handler(e) {
\r
12012 // A new BR was created in a block element, remove it
\r
12013 if (e && e.parentNode && e.nodeName == 'BR' && (n = t.getParentBlock(e))) {
\r
12014 pr = e.previousSibling;
\r
12016 Event.remove(b, 'DOMNodeInserted', handler);
\r
12018 // Is there whitespace at the end of the node before then we might need the pesky BR
\r
12019 // to place the caret at a correct location see bug: #2013943
\r
12020 if (pr && pr.nodeType == 3 && /\s+$/.test(pr.nodeValue))
\r
12023 // Only remove BR elements that got inserted in the middle of the text
\r
12024 if (e.previousSibling || e.nextSibling)
\r
12025 ed.dom.remove(e);
\r
12029 // Listen for new nodes
\r
12030 Event._add(b, 'DOMNodeInserted', handler);
\r
12032 // Remove listener
\r
12033 window.setTimeout(function() {
\r
12034 Event._remove(b, 'DOMNodeInserted', handler);
\r
12040 (function(tinymce) {
\r
12042 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
\r
12044 tinymce.create('tinymce.ControlManager', {
\r
12045 ControlManager : function(ed, s) {
\r
12051 t.onAdd = new tinymce.util.Dispatcher(t);
\r
12052 t.onPostRender = new tinymce.util.Dispatcher(t);
\r
12053 t.prefix = s.prefix || ed.id + '_';
\r
12056 t.onPostRender.add(function() {
\r
12057 each(t.controls, function(c) {
\r
12063 get : function(id) {
\r
12064 return this.controls[this.prefix + id] || this.controls[id];
\r
12067 setActive : function(id, s) {
\r
12070 if (c = this.get(id))
\r
12076 setDisabled : function(id, s) {
\r
12079 if (c = this.get(id))
\r
12080 c.setDisabled(s);
\r
12085 add : function(c) {
\r
12089 t.controls[c.id] = c;
\r
12090 t.onAdd.dispatch(c, t);
\r
12096 createControl : function(n) {
\r
12097 var c, t = this, ed = t.editor;
\r
12099 each(ed.plugins, function(p) {
\r
12100 if (p.createControl) {
\r
12101 c = p.createControl(n, t);
\r
12110 case "separator":
\r
12111 return t.createSeparator();
\r
12114 if (!c && ed.buttons && (c = ed.buttons[n]))
\r
12115 return t.createButton(n, c);
\r
12120 createDropMenu : function(id, s, cc) {
\r
12121 var t = this, ed = t.editor, c, bm, v, cls;
\r
12124 'class' : 'mceDropDown',
\r
12125 constrain : ed.settings.constrain_menus
\r
12128 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
\r
12129 if (v = ed.getParam('skin_variant'))
\r
12130 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
\r
12132 id = t.prefix + id;
\r
12133 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
\r
12134 c = t.controls[id] = new cls(id, s);
\r
12135 c.onAddItem.add(function(c, o) {
\r
12136 var s = o.settings;
\r
12138 s.title = ed.getLang(s.title, s.title);
\r
12140 if (!s.onclick) {
\r
12141 s.onclick = function(v) {
\r
12143 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
12148 ed.onRemove.add(function() {
\r
12152 // Fix for bug #1897785, #1898007
\r
12153 if (tinymce.isIE) {
\r
12154 c.onShowMenu.add(function() {
\r
12155 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
12158 bm = ed.selection.getBookmark(1);
\r
12161 c.onHideMenu.add(function() {
\r
12163 ed.selection.moveToBookmark(bm);
\r
12172 createListBox : function(id, s, cc) {
\r
12173 var t = this, ed = t.editor, cmd, c, cls;
\r
12178 s.title = ed.translate(s.title);
\r
12179 s.scope = s.scope || ed;
\r
12181 if (!s.onselect) {
\r
12182 s.onselect = function(v) {
\r
12183 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12189 'class' : 'mce_' + id,
\r
12191 control_manager : t
\r
12194 id = t.prefix + id;
\r
12196 if (ed.settings.use_native_selects)
\r
12197 c = new tinymce.ui.NativeListBox(id, s);
\r
12199 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
\r
12200 c = new cls(id, s);
\r
12203 t.controls[id] = c;
\r
12205 // Fix focus problem in Safari
\r
12206 if (tinymce.isWebKit) {
\r
12207 c.onPostRender.add(function(c, n) {
\r
12208 // Store bookmark on mousedown
\r
12209 Event.add(n, 'mousedown', function() {
\r
12210 ed.bookmark = ed.selection.getBookmark(1);
\r
12213 // Restore on focus, since it might be lost
\r
12214 Event.add(n, 'focus', function() {
\r
12215 ed.selection.moveToBookmark(ed.bookmark);
\r
12216 ed.bookmark = null;
\r
12222 ed.onMouseDown.add(c.hideMenu, c);
\r
12227 createButton : function(id, s, cc) {
\r
12228 var t = this, ed = t.editor, o, c, cls;
\r
12233 s.title = ed.translate(s.title);
\r
12234 s.label = ed.translate(s.label);
\r
12235 s.scope = s.scope || ed;
\r
12237 if (!s.onclick && !s.menu_button) {
\r
12238 s.onclick = function() {
\r
12239 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
12245 'class' : 'mce_' + id,
\r
12246 unavailable_prefix : ed.getLang('unavailable', ''),
\r
12248 control_manager : t
\r
12251 id = t.prefix + id;
\r
12253 if (s.menu_button) {
\r
12254 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
\r
12255 c = new cls(id, s);
\r
12256 ed.onMouseDown.add(c.hideMenu, c);
\r
12258 cls = t._cls.button || tinymce.ui.Button;
\r
12259 c = new cls(id, s);
\r
12265 createMenuButton : function(id, s, cc) {
\r
12267 s.menu_button = 1;
\r
12269 return this.createButton(id, s, cc);
\r
12272 createSplitButton : function(id, s, cc) {
\r
12273 var t = this, ed = t.editor, cmd, c, cls;
\r
12278 s.title = ed.translate(s.title);
\r
12279 s.scope = s.scope || ed;
\r
12281 if (!s.onclick) {
\r
12282 s.onclick = function(v) {
\r
12283 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12287 if (!s.onselect) {
\r
12288 s.onselect = function(v) {
\r
12289 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12295 'class' : 'mce_' + id,
\r
12297 control_manager : t
\r
12300 id = t.prefix + id;
\r
12301 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
\r
12302 c = t.add(new cls(id, s));
\r
12303 ed.onMouseDown.add(c.hideMenu, c);
\r
12308 createColorSplitButton : function(id, s, cc) {
\r
12309 var t = this, ed = t.editor, cmd, c, cls, bm;
\r
12314 s.title = ed.translate(s.title);
\r
12315 s.scope = s.scope || ed;
\r
12317 if (!s.onclick) {
\r
12318 s.onclick = function(v) {
\r
12319 if (tinymce.isIE)
\r
12320 bm = ed.selection.getBookmark(1);
\r
12322 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12326 if (!s.onselect) {
\r
12327 s.onselect = function(v) {
\r
12328 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12334 'class' : 'mce_' + id,
\r
12335 'menu_class' : ed.getParam('skin') + 'Skin',
\r
12337 more_colors_title : ed.getLang('more_colors')
\r
12340 id = t.prefix + id;
\r
12341 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
\r
12342 c = new cls(id, s);
\r
12343 ed.onMouseDown.add(c.hideMenu, c);
\r
12345 // Remove the menu element when the editor is removed
\r
12346 ed.onRemove.add(function() {
\r
12350 // Fix for bug #1897785, #1898007
\r
12351 if (tinymce.isIE) {
\r
12352 c.onShowMenu.add(function() {
\r
12353 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
12355 bm = ed.selection.getBookmark(1);
\r
12358 c.onHideMenu.add(function() {
\r
12360 ed.selection.moveToBookmark(bm);
\r
12369 createToolbar : function(id, s, cc) {
\r
12370 var c, t = this, cls;
\r
12372 id = t.prefix + id;
\r
12373 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
\r
12374 c = new cls(id, s);
\r
12382 createSeparator : function(cc) {
\r
12383 var cls = cc || this._cls.separator || tinymce.ui.Separator;
\r
12385 return new cls();
\r
12388 setControlType : function(n, c) {
\r
12389 return this._cls[n.toLowerCase()] = c;
\r
12392 destroy : function() {
\r
12393 each(this.controls, function(c) {
\r
12397 this.controls = null;
\r
12402 (function(tinymce) {
\r
12403 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
\r
12405 tinymce.create('tinymce.WindowManager', {
\r
12406 WindowManager : function(ed) {
\r
12410 t.onOpen = new Dispatcher(t);
\r
12411 t.onClose = new Dispatcher(t);
\r
12416 open : function(s, p) {
\r
12417 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
\r
12419 // Default some options
\r
12422 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
\r
12423 sh = isOpera ? vp.h : screen.height;
\r
12424 s.name = s.name || 'mc_' + new Date().getTime();
\r
12425 s.width = parseInt(s.width || 320);
\r
12426 s.height = parseInt(s.height || 240);
\r
12427 s.resizable = true;
\r
12428 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
\r
12429 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
\r
12430 p.inline = false;
\r
12431 p.mce_width = s.width;
\r
12432 p.mce_height = s.height;
\r
12433 p.mce_auto_focus = s.auto_focus;
\r
12439 s.dialogWidth = s.width + 'px';
\r
12440 s.dialogHeight = s.height + 'px';
\r
12441 s.scroll = s.scrollbars || false;
\r
12445 // Build features string
\r
12446 each(s, function(v, k) {
\r
12447 if (tinymce.is(v, 'boolean'))
\r
12448 v = v ? 'yes' : 'no';
\r
12450 if (!/^(name|url)$/.test(k)) {
\r
12452 f += (f ? ';' : '') + k + ':' + v;
\r
12454 f += (f ? ',' : '') + k + '=' + v;
\r
12460 t.onOpen.dispatch(t, s, p);
\r
12462 u = s.url || s.file;
\r
12463 u = tinymce._addVer(u);
\r
12466 if (isIE && mo) {
\r
12468 window.showModalDialog(u, window, f);
\r
12470 w = window.open(u, s.name, f);
\r
12476 alert(t.editor.getLang('popup_blocked'));
\r
12479 close : function(w) {
\r
12481 this.onClose.dispatch(this);
\r
12484 createInstance : function(cl, a, b, c, d, e) {
\r
12485 var f = tinymce.resolve(cl);
\r
12487 return new f(a, b, c, d, e);
\r
12490 confirm : function(t, cb, s, w) {
\r
12493 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
\r
12496 alert : function(tx, cb, s, w) {
\r
12500 w.alert(t._decode(t.editor.getLang(tx, tx)));
\r
12506 resizeBy : function(dw, dh, win) {
\r
12507 win.resizeBy(dw, dh);
\r
12510 // Internal functions
\r
12512 _decode : function(s) {
\r
12513 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
\r
12517 (function(tinymce) {
\r
12518 function CommandManager() {
\r
12519 var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
\r
12521 function add(collection, cmd, func, scope) {
\r
12522 if (typeof(cmd) == 'string')
\r
12525 tinymce.each(cmd, function(cmd) {
\r
12526 collection[cmd.toLowerCase()] = {func : func, scope : scope};
\r
12530 tinymce.extend(this, {
\r
12531 add : function(cmd, func, scope) {
\r
12532 add(execCommands, cmd, func, scope);
\r
12535 addQueryStateHandler : function(cmd, func, scope) {
\r
12536 add(queryStateCommands, cmd, func, scope);
\r
12539 addQueryValueHandler : function(cmd, func, scope) {
\r
12540 add(queryValueCommands, cmd, func, scope);
\r
12543 execCommand : function(scope, cmd, ui, value, args) {
\r
12544 if (cmd = execCommands[cmd.toLowerCase()]) {
\r
12545 if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
\r
12550 queryCommandValue : function() {
\r
12551 if (cmd = queryValueCommands[cmd.toLowerCase()])
\r
12552 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
12555 queryCommandState : function() {
\r
12556 if (cmd = queryStateCommands[cmd.toLowerCase()])
\r
12557 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
12562 tinymce.GlobalCommands = new CommandManager();
\r
12564 (function(tinymce) {
\r
12565 tinymce.Formatter = function(ed) {
\r
12566 var formats = {},
\r
12567 each = tinymce.each,
\r
12569 selection = ed.selection,
\r
12570 TreeWalker = tinymce.dom.TreeWalker,
\r
12571 rangeUtils = new tinymce.dom.RangeUtils(dom),
\r
12572 isValid = ed.schema.isValid,
\r
12573 isBlock = dom.isBlock,
\r
12574 forcedRootBlock = ed.settings.forced_root_block,
\r
12575 nodeIndex = dom.nodeIndex,
\r
12576 INVISIBLE_CHAR = '\uFEFF',
\r
12577 MCE_ATTR_RE = /^(src|href|style)$/,
\r
12584 function getParents(node, selector) {
\r
12585 return dom.getParents(node, selector, dom.getRoot());
\r
12588 function resetPending() {
\r
12590 if (!pendingFormats || pendingFormats.apply.length || pendingFormats.remove.length)
\r
12591 pendingFormats = {apply : [], remove : []};
\r
12594 ed.onMouseUp.add(resetPending);
\r
12597 // Public functions
\r
12599 function get(name) {
\r
12600 return name ? formats[name] : formats;
\r
12603 function register(name, format) {
\r
12605 if (typeof(name) !== 'string') {
\r
12606 each(name, function(format, name) {
\r
12607 register(name, format);
\r
12610 // Force format into array and add it to internal collection
\r
12611 format = format.length ? format : [format];
\r
12613 each(format, function(format) {
\r
12614 // Set deep to false by default on selector formats this to avoid removing
\r
12615 // alignment on images inside paragraphs when alignment is changed on paragraphs
\r
12616 if (format.deep === undefined)
\r
12617 format.deep = !format.selector;
\r
12619 // Default to true
\r
12620 if (format.split === undefined)
\r
12621 format.split = !format.selector;
\r
12623 // Default to true
\r
12624 if (format.remove === undefined && format.selector)
\r
12625 format.remove = 'none';
\r
12627 // Split classes if needed
\r
12628 if (typeof(format.classes) === 'string')
\r
12629 format.classes = format.classes.split(/\s+/);
\r
12632 formats[name] = format;
\r
12637 function apply(name, vars, node) {
\r
12638 var formatList = get(name), format = formatList[0], bookmark, rng, i;
\r
12640 function moveStart(rng) {
\r
12641 var container = rng.startContainer,
\r
12642 offset = rng.startOffset,
\r
12645 // Move startContainer/startOffset in to a suitable node
\r
12646 if (container.nodeType == 1 || container.nodeValue === "") {
\r
12647 walker = new TreeWalker(container.childNodes[offset]);
\r
12648 for (node = walker.current(); node; node = walker.next()) {
\r
12649 if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) {
\r
12650 rng.setStart(node, 0);
\r
12659 function setElementFormat(elm, fmt) {
\r
12660 fmt = fmt || format;
\r
12663 each(fmt.styles, function(value, name) {
\r
12664 dom.setStyle(elm, name, replaceVars(value, vars));
\r
12667 each(fmt.attributes, function(value, name) {
\r
12668 dom.setAttrib(elm, name, replaceVars(value, vars));
\r
12671 each(fmt.classes, function(value) {
\r
12672 value = replaceVars(value, vars);
\r
12674 if (!dom.hasClass(elm, value))
\r
12675 dom.addClass(elm, value);
\r
12680 function applyRngStyle(rng) {
\r
12681 var newWrappers = [], wrapName, wrapElm;
\r
12683 // Setup wrapper element
\r
12684 wrapName = format.inline || format.block;
\r
12685 wrapElm = dom.create(wrapName);
\r
12686 setElementFormat(wrapElm);
\r
12688 rangeUtils.walk(rng, function(nodes) {
\r
12689 var currentWrapElm;
\r
12691 function process(node) {
\r
12692 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase();
\r
12694 // Stop wrapping on br elements
\r
12695 if (isEq(nodeName, 'br')) {
\r
12696 currentWrapElm = 0;
\r
12698 // Remove any br elements when we wrap things
\r
12699 if (format.block)
\r
12700 dom.remove(node);
\r
12705 // If node is wrapper type
\r
12706 if (format.wrapper && matchNode(node, name, vars)) {
\r
12707 currentWrapElm = 0;
\r
12711 // Can we rename the block
\r
12712 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
\r
12713 node = dom.rename(node, wrapName);
\r
12714 setElementFormat(node);
\r
12715 newWrappers.push(node);
\r
12716 currentWrapElm = 0;
\r
12720 // Handle selector patterns
\r
12721 if (format.selector) {
\r
12722 // Look for matching formats
\r
12723 each(formatList, function(format) {
\r
12724 if (dom.is(node, format.selector))
\r
12725 setElementFormat(node, format);
\r
12731 // Is it valid to wrap this item
\r
12732 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
\r
12733 // Start wrapping
\r
12734 if (!currentWrapElm) {
\r
12736 currentWrapElm = wrapElm.cloneNode(FALSE);
\r
12737 node.parentNode.insertBefore(currentWrapElm, node);
\r
12738 newWrappers.push(currentWrapElm);
\r
12741 currentWrapElm.appendChild(node);
\r
12743 // Start a new wrapper for possible children
\r
12744 currentWrapElm = 0;
\r
12746 each(tinymce.grep(node.childNodes), process);
\r
12748 // End the last wrapper
\r
12749 currentWrapElm = 0;
\r
12753 // Process siblings from range
\r
12754 each(nodes, process);
\r
12758 each(newWrappers, function(node) {
\r
12761 function getChildCount(node) {
\r
12764 each(node.childNodes, function(node) {
\r
12765 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
\r
12772 function mergeStyles(node) {
\r
12773 var child, clone;
\r
12775 each(node.childNodes, function(node) {
\r
12776 if (node.nodeType == 1 && !isBookmarkNode(node)) {
\r
12778 return FALSE; // break loop
\r
12782 // If child was found and of the same type as the current node
\r
12783 if (child && matchName(child, format)) {
\r
12784 clone = child.cloneNode(FALSE);
\r
12785 setElementFormat(clone);
\r
12787 dom.replace(clone, node, TRUE);
\r
12788 dom.remove(child, 1);
\r
12791 return clone || node;
\r
12794 childCount = getChildCount(node);
\r
12796 // Remove empty nodes
\r
12797 if (childCount === 0) {
\r
12798 dom.remove(node, 1);
\r
12802 if (format.inline || format.wrapper) {
\r
12803 // Merges the current node with it's children of similar type to reduce the number of elements
\r
12804 if (!format.exact && childCount === 1)
\r
12805 node = mergeStyles(node);
\r
12807 // Remove/merge children
\r
12808 each(formatList, function(format) {
\r
12809 // Merge all children of similar type will move styles from child to parent
\r
12810 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
\r
12811 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
\r
12812 each(dom.select(format.inline, node), function(child) {
\r
12813 removeFormat(format, vars, child, format.exact ? child : null);
\r
12817 // Look for parent with similar style format
\r
12818 dom.getParent(node.parentNode, function(parent) {
\r
12819 if (matchNode(parent, name, vars)) {
\r
12820 dom.remove(node, 1);
\r
12826 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
\r
12828 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
\r
12829 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
\r
12837 rng = dom.createRng();
\r
12839 rng.setStartBefore(node);
\r
12840 rng.setEndAfter(node);
\r
12842 applyRngStyle(rng);
\r
12844 if (!selection.isCollapsed() || !format.inline) {
\r
12845 // Apply formatting to selection
\r
12846 bookmark = selection.getBookmark();
\r
12847 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
\r
12849 selection.moveToBookmark(bookmark);
\r
12850 selection.setRng(moveStart(selection.getRng(TRUE)));
\r
12851 ed.nodeChanged();
\r
12853 performCaretAction('apply', name, vars);
\r
12858 function remove(name, vars, node) {
\r
12859 var formatList = get(name), format = formatList[0], bookmark, i, rng;
\r
12861 // Merges the styles for each node
\r
12862 function process(node) {
\r
12863 var children, i, l;
\r
12865 // Grab the children first since the nodelist might be changed
\r
12866 children = tinymce.grep(node.childNodes);
\r
12868 // Process current node
\r
12869 for (i = 0, l = formatList.length; i < l; i++) {
\r
12870 if (removeFormat(formatList[i], vars, node, node))
\r
12874 // Process the children
\r
12875 if (format.deep) {
\r
12876 for (i = 0, l = children.length; i < l; i++)
\r
12877 process(children[i]);
\r
12881 function findFormatRoot(container) {
\r
12884 // Find format root
\r
12885 each(getParents(container.parentNode).reverse(), function(parent) {
\r
12886 // Find format root element
\r
12887 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
\r
12888 // If the matched format has a remove none flag we shouldn't split it
\r
12889 if (!isBlock(parent) && matchNode(parent, name, vars))
\r
12890 formatRoot = parent;
\r
12894 return formatRoot;
\r
12897 function wrapAndSplit(format_root, container, target, split) {
\r
12898 var parent, clone, lastClone, firstClone, i, formatRootParent;
\r
12900 // Format root found then clone formats and split it
\r
12901 if (format_root) {
\r
12902 formatRootParent = format_root.parentNode;
\r
12904 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
\r
12905 clone = parent.cloneNode(FALSE);
\r
12907 for (i = 0; i < formatList.length; i++) {
\r
12908 if (removeFormat(formatList[i], vars, clone, clone)) {
\r
12914 // Build wrapper node
\r
12917 clone.appendChild(lastClone);
\r
12920 firstClone = clone;
\r
12922 lastClone = clone;
\r
12927 container = dom.split(format_root, container);
\r
12929 // Wrap container in cloned formats
\r
12931 target.parentNode.insertBefore(lastClone, target);
\r
12932 firstClone.appendChild(target);
\r
12936 return container;
\r
12939 function splitToFormatRoot(container) {
\r
12940 return wrapAndSplit(findFormatRoot(container), container, container, true);
\r
12943 function unwrap(start) {
\r
12944 var node = dom.get(start ? '_start' : '_end'),
\r
12945 out = node[start ? 'firstChild' : 'lastChild'];
\r
12947 dom.remove(node, 1);
\r
12952 function removeRngStyle(rng) {
\r
12953 var startContainer, endContainer;
\r
12955 rng = expandRng(rng, formatList, TRUE);
\r
12957 if (format.split) {
\r
12958 startContainer = getContainer(rng, TRUE);
\r
12959 endContainer = getContainer(rng);
\r
12961 if (startContainer != endContainer) {
\r
12962 // Wrap start/end nodes in span element since these might be cloned/moved
\r
12963 startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
\r
12964 endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
\r
12966 // Split start/end
\r
12967 splitToFormatRoot(startContainer);
\r
12968 splitToFormatRoot(endContainer);
\r
12970 // Unwrap start/end to get real elements again
\r
12971 startContainer = unwrap(TRUE);
\r
12972 endContainer = unwrap();
\r
12974 startContainer = endContainer = splitToFormatRoot(startContainer);
\r
12976 // Update range positions since they might have changed after the split operations
\r
12977 rng.startContainer = startContainer.parentNode;
\r
12978 rng.startOffset = nodeIndex(startContainer);
\r
12979 rng.endContainer = endContainer.parentNode;
\r
12980 rng.endOffset = nodeIndex(endContainer) + 1;
\r
12983 // Remove items between start/end
\r
12984 rangeUtils.walk(rng, function(nodes) {
\r
12985 each(nodes, function(node) {
\r
12993 rng = dom.createRng();
\r
12994 rng.setStartBefore(node);
\r
12995 rng.setEndAfter(node);
\r
12996 removeRngStyle(rng);
\r
13000 if (!selection.isCollapsed() || !format.inline) {
\r
13001 bookmark = selection.getBookmark();
\r
13002 removeRngStyle(selection.getRng(TRUE));
\r
13003 selection.moveToBookmark(bookmark);
\r
13004 ed.nodeChanged();
\r
13006 performCaretAction('remove', name, vars);
\r
13009 function toggle(name, vars, node) {
\r
13010 if (match(name, vars, node))
\r
13011 remove(name, vars, node);
\r
13013 apply(name, vars, node);
\r
13016 function matchNode(node, name, vars) {
\r
13017 var formatList = get(name), format, i, classes;
\r
13019 function matchItems(node, format, item_name) {
\r
13020 var key, value, items = format[item_name], i;
\r
13022 // Check all items
\r
13024 // Non indexed object
\r
13025 if (items.length === undefined) {
\r
13026 for (key in items) {
\r
13027 if (items.hasOwnProperty(key)) {
\r
13028 if (item_name === 'attributes')
\r
13029 value = dom.getAttrib(node, key);
\r
13031 value = getStyle(node, key);
\r
13033 if (!isEq(value, replaceVars(items[key], vars)))
\r
13038 // Only one match needed for indexed arrays
\r
13039 for (i = 0; i < items.length; i++) {
\r
13040 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
\r
13049 if (formatList && node) {
\r
13050 // Check each format in list
\r
13051 for (i = 0; i < formatList.length; i++) {
\r
13052 format = formatList[i];
\r
13054 // Name name, attributes, styles and classes
\r
13055 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
\r
13057 if (classes = format.classes) {
\r
13058 for (i = 0; i < classes.length; i++) {
\r
13059 if (!dom.hasClass(node, classes[i]))
\r
13070 function match(name, vars, node) {
\r
13071 var startNode, i;
\r
13073 function matchParents(node) {
\r
13074 // Find first node with similar format settings
\r
13075 node = dom.getParent(node, function(node) {
\r
13076 return !!matchNode(node, name, vars);
\r
13079 // Do an exact check on the similar format element
\r
13080 return matchNode(node, name, vars);
\r
13083 // Check specified node
\r
13085 return matchParents(node);
\r
13087 // Check pending formats
\r
13088 if (selection.isCollapsed()) {
\r
13089 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
13090 if (pendingFormats.apply[i].name == name)
\r
13094 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
13095 if (pendingFormats.remove[i].name == name)
\r
13099 return matchParents(selection.getNode());
\r
13102 // Check selected node
\r
13103 node = selection.getNode();
\r
13104 if (matchParents(node))
\r
13107 // Check start node if it's different
\r
13108 startNode = selection.getStart();
\r
13109 if (startNode != node) {
\r
13110 if (matchParents(startNode))
\r
13117 function canApply(name) {
\r
13118 var formatList = get(name), startNode, parents, i, x, selector;
\r
13120 if (formatList) {
\r
13121 startNode = selection.getStart();
\r
13122 parents = getParents(startNode);
\r
13124 for (x = formatList.length - 1; x >= 0; x--) {
\r
13125 selector = formatList[x].selector;
\r
13127 // Format is not selector based, then always return TRUE
\r
13131 for (i = parents.length - 1; i >= 0; i--) {
\r
13132 if (dom.is(parents[i], selector))
\r
13141 // Expose to public
\r
13142 tinymce.extend(this, {
\r
13144 register : register,
\r
13149 matchNode : matchNode,
\r
13150 canApply : canApply
\r
13153 // Private functions
\r
13155 function matchName(node, format) {
\r
13156 // Check for inline match
\r
13157 if (isEq(node, format.inline))
\r
13160 // Check for block match
\r
13161 if (isEq(node, format.block))
\r
13164 // Check for selector match
\r
13165 if (format.selector)
\r
13166 return dom.is(node, format.selector);
\r
13169 function isEq(str1, str2) {
\r
13170 str1 = str1 || '';
\r
13171 str2 = str2 || '';
\r
13173 str1 = str1.nodeName || str1;
\r
13174 str2 = str2.nodeName || str2;
\r
13176 return str1.toLowerCase() == str2.toLowerCase();
\r
13179 function getStyle(node, name) {
\r
13180 var styleVal = dom.getStyle(node, name);
\r
13182 // Force the format to hex
\r
13183 if (name == 'color' || name == 'backgroundColor')
\r
13184 styleVal = dom.toHex(styleVal);
\r
13186 // Opera will return bold as 700
\r
13187 if (name == 'fontWeight' && styleVal == 700)
\r
13188 styleVal = 'bold';
\r
13190 return '' + styleVal;
\r
13193 function replaceVars(value, vars) {
\r
13194 if (typeof(value) != "string")
\r
13195 value = value(vars);
\r
13197 value = value.replace(/%(\w+)/g, function(str, name) {
\r
13198 return vars[name] || str;
\r
13205 function isWhiteSpaceNode(node) {
\r
13206 return node && node.nodeType === 3 && /^\s*$/.test(node.nodeValue);
\r
13209 function wrap(node, name, attrs) {
\r
13210 var wrapper = dom.create(name, attrs);
\r
13212 node.parentNode.insertBefore(wrapper, node);
\r
13213 wrapper.appendChild(node);
\r
13218 function expandRng(rng, format, remove) {
\r
13219 var startContainer = rng.startContainer,
\r
13220 startOffset = rng.startOffset,
\r
13221 endContainer = rng.endContainer,
\r
13222 endOffset = rng.endOffset, sibling, lastIdx;
\r
13224 // This function walks up the tree if there is no siblings before/after the node
\r
13225 function findParentContainer(container, child_name, sibling_name, root) {
\r
13226 var parent, child;
\r
13228 root = root || dom.getRoot();
\r
13231 // Check if we can move up are we at root level or body level
\r
13232 parent = container.parentNode;
\r
13234 // Stop expanding on block elements or root depending on format
\r
13235 if (parent == root || (!format[0].block_expand && isBlock(parent)))
\r
13236 return container;
\r
13238 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
\r
13239 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
13240 return container;
\r
13242 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
13243 return container;
\r
13246 container = container.parentNode;
\r
13249 return container;
\r
13252 // If index based start position then resolve it
\r
13253 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
\r
13254 lastIdx = startContainer.childNodes.length - 1;
\r
13255 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
\r
13257 if (startContainer.nodeType == 3)
\r
13261 // If index based end position then resolve it
\r
13262 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
\r
13263 lastIdx = endContainer.childNodes.length - 1;
\r
13264 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
\r
13266 if (endContainer.nodeType == 3)
\r
13267 endOffset = endContainer.nodeValue.length;
\r
13270 // Exclude bookmark nodes if possible
\r
13271 if (isBookmarkNode(startContainer.parentNode))
\r
13272 startContainer = startContainer.parentNode;
\r
13274 if (isBookmarkNode(startContainer))
\r
13275 startContainer = startContainer.nextSibling || startContainer;
\r
13277 if (isBookmarkNode(endContainer.parentNode))
\r
13278 endContainer = endContainer.parentNode;
\r
13280 if (isBookmarkNode(endContainer))
\r
13281 endContainer = endContainer.previousSibling || endContainer;
\r
13283 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
\r
13284 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
\r
13285 // This will reduce the number of wrapper elements that needs to be created
\r
13286 // Move start point up the tree
\r
13287 if (format[0].inline || format[0].block_expand) {
\r
13288 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
13289 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
13292 // Expand start/end container to matching selector
\r
13293 if (format[0].selector && format[0].expand !== FALSE) {
\r
13294 function findSelectorEndPoint(container, sibling_name) {
\r
13295 var parents, i, y;
\r
13297 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
\r
13298 container = container[sibling_name];
\r
13300 parents = getParents(container);
\r
13301 for (i = 0; i < parents.length; i++) {
\r
13302 for (y = 0; y < format.length; y++) {
\r
13303 if (dom.is(parents[i], format[y].selector))
\r
13304 return parents[i];
\r
13308 return container;
\r
13311 // Find new startContainer/endContainer if there is better one
\r
13312 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
\r
13313 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
\r
13316 // Expand start/end container to matching block element or text node
\r
13317 if (format[0].block || format[0].selector) {
\r
13318 function findBlockEndPoint(container, sibling_name, sibling_name2) {
\r
13321 // Expand to block of similar type
\r
13322 if (!format[0].wrapper)
\r
13323 node = dom.getParent(container, format[0].block);
\r
13325 // Expand to first wrappable block element or any block element
\r
13327 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
\r
13329 // Exclude inner lists from wrapping
\r
13330 if (node && format[0].wrapper)
\r
13331 node = getParents(node, 'ul,ol').reverse()[0] || node;
\r
13333 // Didn't find a block element look for first/last wrappable element
\r
13335 node = container;
\r
13337 while (node[sibling_name] && !isBlock(node[sibling_name])) {
\r
13338 node = node[sibling_name];
\r
13340 // Break on BR but include it will be removed later on
\r
13341 // we can't remove it now since we need to check if it can be wrapped
\r
13342 if (isEq(node, 'br'))
\r
13347 return node || container;
\r
13350 // Find new startContainer/endContainer if there is better one
\r
13351 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
\r
13352 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
\r
13354 // Non block element then try to expand up the leaf
\r
13355 if (format[0].block) {
\r
13356 if (!isBlock(startContainer))
\r
13357 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
13359 if (!isBlock(endContainer))
\r
13360 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
13364 // Setup index for startContainer
\r
13365 if (startContainer.nodeType == 1) {
\r
13366 startOffset = nodeIndex(startContainer);
\r
13367 startContainer = startContainer.parentNode;
\r
13370 // Setup index for endContainer
\r
13371 if (endContainer.nodeType == 1) {
\r
13372 endOffset = nodeIndex(endContainer) + 1;
\r
13373 endContainer = endContainer.parentNode;
\r
13376 // Return new range like object
\r
13378 startContainer : startContainer,
\r
13379 startOffset : startOffset,
\r
13380 endContainer : endContainer,
\r
13381 endOffset : endOffset
\r
13385 function removeFormat(format, vars, node, compare_node) {
\r
13386 var i, attrs, stylesModified;
\r
13388 // Check if node matches format
\r
13389 if (!matchName(node, format))
\r
13392 // Should we compare with format attribs and styles
\r
13393 if (format.remove != 'all') {
\r
13395 each(format.styles, function(value, name) {
\r
13396 value = replaceVars(value, vars);
\r
13399 if (typeof(name) === 'number') {
\r
13401 compare_node = 0;
\r
13404 if (!compare_node || isEq(getStyle(compare_node, name), value))
\r
13405 dom.setStyle(node, name, '');
\r
13407 stylesModified = 1;
\r
13410 // Remove style attribute if it's empty
\r
13411 if (stylesModified && dom.getAttrib(node, 'style') == '') {
\r
13412 node.removeAttribute('style');
\r
13413 node.removeAttribute('_mce_style');
\r
13416 // Remove attributes
\r
13417 each(format.attributes, function(value, name) {
\r
13420 value = replaceVars(value, vars);
\r
13423 if (typeof(name) === 'number') {
\r
13425 compare_node = 0;
\r
13428 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
\r
13429 // Keep internal classes
\r
13430 if (name == 'class') {
\r
13431 value = dom.getAttrib(node, name);
\r
13433 // Build new class value where everything is removed except the internal prefixed classes
\r
13435 each(value.split(/\s+/), function(cls) {
\r
13436 if (/mce\w+/.test(cls))
\r
13437 valueOut += (valueOut ? ' ' : '') + cls;
\r
13440 // We got some internal classes left
\r
13442 dom.setAttrib(node, name, valueOut);
\r
13448 // IE6 has a bug where the attribute doesn't get removed correctly
\r
13449 if (name == "class")
\r
13450 node.removeAttribute('className');
\r
13452 // Remove mce prefixed attributes
\r
13453 if (MCE_ATTR_RE.test(name))
\r
13454 node.removeAttribute('_mce_' + name);
\r
13456 node.removeAttribute(name);
\r
13460 // Remove classes
\r
13461 each(format.classes, function(value) {
\r
13462 value = replaceVars(value, vars);
\r
13464 if (!compare_node || dom.hasClass(compare_node, value))
\r
13465 dom.removeClass(node, value);
\r
13468 // Check for non internal attributes
\r
13469 attrs = dom.getAttribs(node);
\r
13470 for (i = 0; i < attrs.length; i++) {
\r
13471 if (attrs[i].nodeName.indexOf('_') !== 0)
\r
13476 // Remove the inline child if it's empty for example <b> or <span>
\r
13477 if (format.remove != 'none') {
\r
13478 removeNode(node, format);
\r
13483 function removeNode(node, format) {
\r
13484 var parentNode = node.parentNode, rootBlockElm;
\r
13486 if (format.block) {
\r
13487 if (!forcedRootBlock) {
\r
13488 function find(node, next, inc) {
\r
13489 node = getNonWhiteSpaceSibling(node, next, inc);
\r
13491 return !node || (node.nodeName == 'BR' || isBlock(node));
\r
13494 // Append BR elements if needed before we remove the block
\r
13495 if (isBlock(node) && !isBlock(parentNode)) {
\r
13496 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
\r
13497 node.insertBefore(dom.create('br'), node.firstChild);
\r
13499 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
\r
13500 node.appendChild(dom.create('br'));
\r
13503 // Wrap the block in a forcedRootBlock if we are at the root of document
\r
13504 if (parentNode == dom.getRoot()) {
\r
13505 if (!format.list_block || !isEq(node, format.list_block)) {
\r
13506 each(tinymce.grep(node.childNodes), function(node) {
\r
13507 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
\r
13508 if (!rootBlockElm)
\r
13509 rootBlockElm = wrap(node, forcedRootBlock);
\r
13511 rootBlockElm.appendChild(node);
\r
13513 rootBlockElm = 0;
\r
13520 dom.remove(node, 1);
\r
13523 function getNonWhiteSpaceSibling(node, next, inc) {
\r
13525 next = next ? 'nextSibling' : 'previousSibling';
\r
13527 for (node = inc ? node : node[next]; node; node = node[next]) {
\r
13528 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
\r
13534 function isBookmarkNode(node) {
\r
13535 return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
\r
13538 function mergeSiblings(prev, next) {
\r
13539 var marker, sibling, tmpSibling;
\r
13541 function compareElements(node1, node2) {
\r
13542 // Not the same name
\r
13543 if (node1.nodeName != node2.nodeName)
\r
13546 function getAttribs(node) {
\r
13547 var attribs = {};
\r
13549 each(dom.getAttribs(node), function(attr) {
\r
13550 var name = attr.nodeName.toLowerCase();
\r
13552 // Don't compare internal attributes or style
\r
13553 if (name.indexOf('_') !== 0 && name !== 'style')
\r
13554 attribs[name] = dom.getAttrib(node, name);
\r
13560 function compareObjects(obj1, obj2) {
\r
13563 for (name in obj1) {
\r
13564 // Obj1 has item obj2 doesn't have
\r
13565 if (obj1.hasOwnProperty(name)) {
\r
13566 value = obj2[name];
\r
13568 // Obj2 doesn't have obj1 item
\r
13569 if (value === undefined)
\r
13572 // Obj2 item has a different value
\r
13573 if (obj1[name] != value)
\r
13576 // Delete similar value
\r
13577 delete obj2[name];
\r
13581 // Check if obj 2 has something obj 1 doesn't have
\r
13582 for (name in obj2) {
\r
13583 // Obj2 has item obj1 doesn't have
\r
13584 if (obj2.hasOwnProperty(name))
\r
13591 // Attribs are not the same
\r
13592 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
\r
13595 // Styles are not the same
\r
13596 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
\r
13602 // Check if next/prev exists and that they are elements
\r
13603 if (prev && next) {
\r
13604 function findElementSibling(node, sibling_name) {
\r
13605 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
\r
13606 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
13609 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
13616 // If previous sibling is empty then jump over it
\r
13617 prev = findElementSibling(prev, 'previousSibling');
\r
13618 next = findElementSibling(next, 'nextSibling');
\r
13620 // Compare next and previous nodes
\r
13621 if (compareElements(prev, next)) {
\r
13622 // Append nodes between
\r
13623 for (sibling = prev.nextSibling; sibling && sibling != next;) {
\r
13624 tmpSibling = sibling;
\r
13625 sibling = sibling.nextSibling;
\r
13626 prev.appendChild(tmpSibling);
\r
13629 // Remove next node
\r
13630 dom.remove(next);
\r
13632 // Move children into prev node
\r
13633 each(tinymce.grep(next.childNodes), function(node) {
\r
13634 prev.appendChild(node);
\r
13644 function isTextBlock(name) {
\r
13645 return /^(h[1-6]|p|div|pre|address)$/.test(name);
\r
13648 function getContainer(rng, start) {
\r
13649 var container, offset, lastIdx;
\r
13651 container = rng[start ? 'startContainer' : 'endContainer'];
\r
13652 offset = rng[start ? 'startOffset' : 'endOffset'];
\r
13654 if (container.nodeType == 1) {
\r
13655 lastIdx = container.childNodes.length - 1;
\r
13657 if (!start && offset)
\r
13660 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
\r
13663 return container;
\r
13666 function performCaretAction(type, name, vars) {
\r
13667 var i, rng, selectedNode = selection.getNode().parentNode,
\r
13668 doc = ed.getDoc(), marker = 'mceinline',
\r
13669 events = ['onKeyDown', 'onKeyUp', 'onKeyPress'],
\r
13670 currentPendingFormats = pendingFormats[type],
\r
13671 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
\r
13673 // Check if it already exists
\r
13674 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
\r
13675 if (currentPendingFormats[i].name == name)
\r
13679 currentPendingFormats.push({name : name, vars : vars});
\r
13681 // Check if it's in the oter type
\r
13682 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
\r
13683 if (otherPendingFormats[i].name == name)
\r
13684 otherPendingFormats.splice(i, 1);
\r
13687 function unbind() {
\r
13688 if (caretHandler) {
\r
13689 each(events, function(event) {
\r
13690 ed[event].remove(caretHandler);
\r
13693 caretHandler = 0;
\r
13697 function perform(caret_node) {
\r
13698 // Apply pending formats
\r
13699 each(pendingFormats.apply.reverse(), function(item) {
\r
13700 apply(item.name, item.vars, caret_node);
\r
13703 // Remove pending formats
\r
13704 each(pendingFormats.remove.reverse(), function(item) {
\r
13705 remove(item.name, item.vars, caret_node);
\r
13708 dom.remove(caret_node, 1);
\r
13712 function isMarker(node) {
\r
13713 return node.face == marker || node.style.fontFamily == marker;
\r
13718 doc.execCommand('FontName', false, marker);
\r
13720 // IE will convert the current word
\r
13721 each(dom.select('font,span', selectedNode), function(node) {
\r
13724 if (isMarker(node)) {
\r
13725 bookmark = selection.getBookmark();
\r
13727 selection.moveToBookmark(bookmark);
\r
13728 ed.nodeChanged();
\r
13729 selectedNode = 0;
\r
13733 if (selectedNode) {
\r
13734 caretHandler = function(ed, e) {
\r
13735 each(dom.select('font,span', selectedNode), function(node) {
\r
13736 var bookmark, textNode;
\r
13738 // Look for marker
\r
13739 if (node.face == marker || node.style.fontFamily == marker) {
\r
13740 textNode = node.firstChild;
\r
13744 rng = dom.createRng();
\r
13745 rng.setStart(textNode, textNode.nodeValue.length);
\r
13746 rng.setEnd(textNode, textNode.nodeValue.length);
\r
13747 selection.setRng(rng);
\r
13748 ed.nodeChanged();
\r
13754 // Always unbind and clear pending styles on keyup
\r
13755 if (e.type == 'keyup') {
\r
13761 each(events, function(event) {
\r
13762 ed[event].addToTop(caretHandler);
\r
13769 tinymce.onAddEditor.add(function(tinymce, ed) {
\r
13770 var filters, fontSizes, dom, settings = ed.settings;
\r
13772 if (settings.inline_styles) {
\r
13773 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
13775 function replaceWithSpan(node, styles) {
\r
13776 dom.replace(dom.create('span', {
\r
13782 font : function(dom, node) {
\r
13783 replaceWithSpan(node, {
\r
13784 backgroundColor : node.style.backgroundColor,
\r
13785 color : node.color,
\r
13786 fontFamily : node.face,
\r
13787 fontSize : fontSizes[parseInt(node.size) - 1]
\r
13791 u : function(dom, node) {
\r
13792 replaceWithSpan(node, {
\r
13793 textDecoration : 'underline'
\r
13797 strike : function(dom, node) {
\r
13798 replaceWithSpan(node, {
\r
13799 textDecoration : 'line-through'
\r
13804 function convert(editor, params) {
\r
13805 dom = editor.dom;
\r
13807 if (settings.convert_fonts_to_spans) {
\r
13808 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
\r
13809 filters[node.nodeName.toLowerCase()](ed.dom, node);
\r
13814 ed.onPreProcess.add(convert);
\r
13816 ed.onInit.add(function() {
\r
13817 ed.selection.onSetContent.add(convert);
\r