2 var whiteSpaceRe = /^\s*|\s*$/g,
\r
8 minorVersion : '3.8',
\r
10 releaseDate : '2010-06-30',
\r
12 _init : function() {
\r
13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
\r
15 t.isOpera = win.opera && opera.buildNumber;
\r
17 t.isWebKit = /WebKit/.test(ua);
\r
19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
\r
21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
\r
23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
\r
25 t.isMac = ua.indexOf('Mac') != -1;
\r
27 t.isAir = /adobeair/i.test(ua);
\r
29 t.isIDevice = /(iPad|iPhone)/.test(ua);
\r
31 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
\r
32 if (win.tinyMCEPreInit) {
\r
33 t.suffix = tinyMCEPreInit.suffix;
\r
34 t.baseURL = tinyMCEPreInit.base;
\r
35 t.query = tinyMCEPreInit.query;
\r
39 // Get suffix and base
\r
42 // If base element found, add that infront of baseURL
\r
43 nl = d.getElementsByTagName('base');
\r
44 for (i=0; i<nl.length; i++) {
\r
45 if (v = nl[i].href) {
\r
46 // Host only value like http://site.com or http://site.com:8008
\r
47 if (/^https?:\/\/[^\/]+$/.test(v))
\r
50 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
\r
54 function getBase(n) {
\r
55 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
\r
56 if (/_(src|dev)\.js/g.test(n.src))
\r
59 if ((p = n.src.indexOf('?')) != -1)
\r
60 t.query = n.src.substring(p + 1);
\r
62 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
\r
64 // If path to script is relative and a base href was found add that one infront
\r
65 // the src property will always be an absolute one on non IE browsers and IE 8
\r
66 // so this logic will basically only be executed on older IE versions
\r
67 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
\r
68 t.baseURL = base + t.baseURL;
\r
77 nl = d.getElementsByTagName('script');
\r
78 for (i=0; i<nl.length; i++) {
\r
84 n = d.getElementsByTagName('head')[0];
\r
86 nl = n.getElementsByTagName('script');
\r
87 for (i=0; i<nl.length; i++) {
\r
96 is : function(o, t) {
\r
98 return o !== undefined;
\r
100 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
\r
103 return typeof(o) == t;
\r
106 each : function(o, cb, s) {
\r
114 if (o.length !== undefined) {
\r
115 // Indexed arrays, needed for Safari
\r
116 for (n=0, l = o.length; n < l; n++) {
\r
117 if (cb.call(s, o[n], n, o) === false)
\r
123 if (o.hasOwnProperty(n)) {
\r
124 if (cb.call(s, o[n], n, o) === false)
\r
134 map : function(a, f) {
\r
137 tinymce.each(a, function(v) {
\r
144 grep : function(a, f) {
\r
147 tinymce.each(a, function(v) {
\r
155 inArray : function(a, v) {
\r
159 for (i = 0, l = a.length; i < l; i++) {
\r
168 extend : function(o, e) {
\r
169 var i, l, a = arguments;
\r
171 for (i = 1, l = a.length; i < l; i++) {
\r
174 tinymce.each(e, function(v, n) {
\r
175 if (v !== undefined)
\r
184 trim : function(s) {
\r
185 return (s ? '' + s : '').replace(whiteSpaceRe, '');
\r
188 create : function(s, p) {
\r
189 var t = this, sp, ns, cn, scn, c, de = 0;
\r
191 // Parse : <prefix> <class>:<super class>
\r
192 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
\r
193 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
\r
195 // Create namespace for new class
\r
196 ns = t.createNS(s[3].replace(/\.\w+$/, ''));
\r
198 // Class already exists
\r
202 // Make pure static class
\r
203 if (s[2] == 'static') {
\r
207 this.onCreate(s[2], s[3], ns[cn]);
\r
212 // Create default constructor
\r
214 p[cn] = function() {};
\r
218 // Add constructor and methods
\r
220 t.extend(ns[cn].prototype, p);
\r
224 sp = t.resolve(s[5]).prototype;
\r
225 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
\r
227 // Extend constructor
\r
230 // Add passthrough constructor
\r
231 ns[cn] = function() {
\r
232 return sp[scn].apply(this, arguments);
\r
235 // Add inherit constructor
\r
236 ns[cn] = function() {
\r
237 this.parent = sp[scn];
\r
238 return c.apply(this, arguments);
\r
241 ns[cn].prototype[cn] = ns[cn];
\r
243 // Add super methods
\r
244 t.each(sp, function(f, n) {
\r
245 ns[cn].prototype[n] = sp[n];
\r
248 // Add overridden methods
\r
249 t.each(p, function(f, n) {
\r
250 // Extend methods if needed
\r
252 ns[cn].prototype[n] = function() {
\r
253 this.parent = sp[n];
\r
254 return f.apply(this, arguments);
\r
258 ns[cn].prototype[n] = f;
\r
263 // Add static methods
\r
264 t.each(p['static'], function(f, n) {
\r
269 this.onCreate(s[2], s[3], ns[cn].prototype);
\r
272 walk : function(o, f, n, s) {
\r
279 tinymce.each(o, function(o, i) {
\r
280 if (f.call(s, o, i, n) === false)
\r
283 tinymce.walk(o, f, n, s);
\r
288 createNS : function(n, o) {
\r
294 for (i=0; i<n.length; i++) {
\r
306 resolve : function(n, o) {
\r
312 for (i = 0, l = n.length; i < l; i++) {
\r
322 addUnload : function(f, s) {
\r
325 f = {func : f, scope : s || this};
\r
328 function unload() {
\r
329 var li = t.unloads, o, n;
\r
332 // Call unload handlers
\r
337 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
\r
340 // Detach unload function
\r
341 if (win.detachEvent) {
\r
342 win.detachEvent('onbeforeunload', fakeUnload);
\r
343 win.detachEvent('onunload', unload);
\r
344 } else if (win.removeEventListener)
\r
345 win.removeEventListener('unload', unload, false);
\r
347 // Destroy references
\r
348 t.unloads = o = li = w = unload = 0;
\r
350 // Run garbarge collector on IE
\r
351 if (win.CollectGarbage)
\r
356 function fakeUnload() {
\r
359 // Is there things still loading, then do some magic
\r
360 if (d.readyState == 'interactive') {
\r
362 // Prevent memory leak
\r
363 d.detachEvent('onstop', stop);
\r
365 // Call unload handler
\r
372 // Fire unload when the currently loading page is stopped
\r
374 d.attachEvent('onstop', stop);
\r
376 // Remove onstop listener after a while to prevent the unload function
\r
377 // to execute if the user presses cancel in an onbeforeunload
\r
378 // confirm dialog and then presses the browser stop button
\r
379 win.setTimeout(function() {
\r
381 d.detachEvent('onstop', stop);
\r
386 // Attach unload handler
\r
387 if (win.attachEvent) {
\r
388 win.attachEvent('onunload', unload);
\r
389 win.attachEvent('onbeforeunload', fakeUnload);
\r
390 } else if (win.addEventListener)
\r
391 win.addEventListener('unload', unload, false);
\r
393 // Setup initial unload handler array
\r
401 removeUnload : function(f) {
\r
402 var u = this.unloads, r = null;
\r
404 tinymce.each(u, function(o, i) {
\r
405 if (o && o.func == f) {
\r
415 explode : function(s, d) {
\r
416 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
\r
419 _addVer : function(u) {
\r
425 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
\r
427 if (u.indexOf('#') == -1)
\r
430 return u.replace('#', v + '#');
\r
435 // Initialize the API
\r
438 // Expose tinymce namespace to the global namespace (window)
\r
439 win.tinymce = win.tinyMCE = tinymce;
\r
443 tinymce.create('tinymce.util.Dispatcher', {
\r
447 Dispatcher : function(s) {
\r
448 this.scope = s || this;
\r
449 this.listeners = [];
\r
452 add : function(cb, s) {
\r
453 this.listeners.push({cb : cb, scope : s || this.scope});
\r
458 addToTop : function(cb, s) {
\r
459 this.listeners.unshift({cb : cb, scope : s || this.scope});
\r
464 remove : function(cb) {
\r
465 var l = this.listeners, o = null;
\r
467 tinymce.each(l, function(c, i) {
\r
478 dispatch : function() {
\r
479 var s, a = arguments, i, li = this.listeners, c;
\r
481 // Needs to be a real loop since the listener count might change while looping
\r
482 // And this is also more efficient
\r
483 for (i = 0; i<li.length; i++) {
\r
485 s = c.cb.apply(c.scope, a);
\r
497 var each = tinymce.each;
\r
499 tinymce.create('tinymce.util.URI', {
\r
500 URI : function(u, s) {
\r
501 var t = this, o, a, b;
\r
504 u = tinymce.trim(u);
\r
506 // Default settings
\r
507 s = t.settings = s || {};
\r
509 // Strange app protocol or local anchor
\r
510 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
\r
515 // Absolute path with no host, fake host and protocol
\r
516 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
\r
517 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
\r
519 // Relative path http:// or protocol relative //path
\r
520 if (!/^\w*:?\/\//.test(u))
\r
521 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
\r
523 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
\r
524 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
\r
525 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
\r
526 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
\r
529 // Zope 3 workaround, they use @@something
\r
531 s = s.replace(/\(mce_at\)/g, '@@');
\r
536 if (b = s.base_uri) {
\r
538 t.protocol = b.protocol;
\r
541 t.userInfo = b.userInfo;
\r
543 if (!t.port && t.host == 'mce_host')
\r
546 if (!t.host || t.host == 'mce_host')
\r
552 //t.path = t.path || '/';
\r
555 setPath : function(p) {
\r
558 p = /^(.*?)\/?(\w+)?$/.exec(p);
\r
560 // Update path parts
\r
562 t.directory = p[1];
\r
570 toRelative : function(u) {
\r
576 u = new tinymce.util.URI(u, {base_uri : t});
\r
578 // Not on same domain/port or protocol
\r
579 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
\r
582 o = t.toRelPath(t.path, u.path);
\r
586 o += '?' + u.query;
\r
590 o += '#' + u.anchor;
\r
595 toAbsolute : function(u, nh) {
\r
596 var u = new tinymce.util.URI(u, {base_uri : this});
\r
598 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
\r
601 toRelPath : function(base, path) {
\r
602 var items, bp = 0, out = '', i, l;
\r
605 base = base.substring(0, base.lastIndexOf('/'));
\r
606 base = base.split('/');
\r
607 items = path.split('/');
\r
609 if (base.length >= items.length) {
\r
610 for (i = 0, l = base.length; i < l; i++) {
\r
611 if (i >= items.length || base[i] != items[i]) {
\r
618 if (base.length < items.length) {
\r
619 for (i = 0, l = items.length; i < l; i++) {
\r
620 if (i >= base.length || base[i] != items[i]) {
\r
630 for (i = 0, l = base.length - (bp - 1); i < l; i++)
\r
633 for (i = bp - 1, l = items.length; i < l; i++) {
\r
635 out += "/" + items[i];
\r
643 toAbsPath : function(base, path) {
\r
644 var i, nb = 0, o = [], tr, outPath;
\r
647 tr = /\/$/.test(path) ? '/' : '';
\r
648 base = base.split('/');
\r
649 path = path.split('/');
\r
651 // Remove empty chunks
\r
652 each(base, function(k) {
\r
659 // Merge relURLParts chunks
\r
660 for (i = path.length - 1, o = []; i >= 0; i--) {
\r
661 // Ignore empty or .
\r
662 if (path[i].length == 0 || path[i] == ".")
\r
666 if (path[i] == '..') {
\r
680 i = base.length - nb;
\r
684 outPath = o.reverse().join('/');
\r
686 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
\r
688 // Add front / if it's needed
\r
689 if (outPath.indexOf('/') !== 0)
\r
690 outPath = '/' + outPath;
\r
692 // Add traling / if it's needed
\r
693 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
\r
699 getURI : function(nh) {
\r
703 if (!t.source || nh) {
\r
708 s += t.protocol + '://';
\r
711 s += t.userInfo + '@';
\r
724 s += '?' + t.query;
\r
727 s += '#' + t.anchor;
\r
738 var each = tinymce.each;
\r
740 tinymce.create('static tinymce.util.Cookie', {
\r
741 getHash : function(n) {
\r
742 var v = this.get(n), h;
\r
745 each(v.split('&'), function(v) {
\r
748 h[unescape(v[0])] = unescape(v[1]);
\r
755 setHash : function(n, v, e, p, d, s) {
\r
758 each(v, function(v, k) {
\r
759 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
\r
762 this.set(n, o, e, p, d, s);
\r
765 get : function(n) {
\r
766 var c = document.cookie, e, p = n + "=", b;
\r
772 b = c.indexOf("; " + p);
\r
782 e = c.indexOf(";", b);
\r
787 return unescape(c.substring(b + p.length, e));
\r
790 set : function(n, v, e, p, d, s) {
\r
791 document.cookie = n + "=" + escape(v) +
\r
792 ((e) ? "; expires=" + e.toGMTString() : "") +
\r
793 ((p) ? "; path=" + escape(p) : "") +
\r
794 ((d) ? "; domain=" + d : "") +
\r
795 ((s) ? "; secure" : "");
\r
798 remove : function(n, p) {
\r
799 var d = new Date();
\r
801 d.setTime(d.getTime() - 1000);
\r
803 this.set(n, '', d, p, d);
\r
808 tinymce.create('static tinymce.util.JSON', {
\r
809 serialize : function(o) {
\r
810 var i, v, s = tinymce.util.JSON.serialize, t;
\r
817 if (t == 'string') {
\r
818 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
\r
820 return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
\r
824 return '\\' + v.charAt(i + 1);
\r
826 a = b.charCodeAt().toString(16);
\r
828 return '\\u' + '0000'.substring(a.length) + a;
\r
832 if (t == 'object') {
\r
833 if (o.hasOwnProperty && o instanceof Array) {
\r
834 for (i=0, v = '['; i<o.length; i++)
\r
835 v += (i > 0 ? ',' : '') + s(o[i]);
\r
843 v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
\r
851 parse : function(s) {
\r
853 return eval('(' + s + ')');
\r
861 tinymce.create('static tinymce.util.XHR', {
\r
862 send : function(o) {
\r
863 var x, t, w = window, c = 0;
\r
865 // Default settings
\r
866 o.scope = o.scope || this;
\r
867 o.success_scope = o.success_scope || o.scope;
\r
868 o.error_scope = o.error_scope || o.scope;
\r
869 o.async = o.async === false ? false : true;
\r
870 o.data = o.data || '';
\r
876 x = new ActiveXObject(s);
\r
883 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
\r
886 if (x.overrideMimeType)
\r
887 x.overrideMimeType(o.content_type);
\r
889 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
\r
891 if (o.content_type)
\r
892 x.setRequestHeader('Content-Type', o.content_type);
\r
894 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
\r
899 if (!o.async || x.readyState == 4 || c++ > 10000) {
\r
900 if (o.success && c < 10000 && x.status == 200)
\r
901 o.success.call(o.success_scope, '' + x.responseText, x, o);
\r
903 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
\r
907 w.setTimeout(ready, 10);
\r
910 // Syncronous request
\r
914 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
\r
915 t = w.setTimeout(ready, 10);
\r
921 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
\r
923 tinymce.create('tinymce.util.JSONRequest', {
\r
924 JSONRequest : function(s) {
\r
925 this.settings = extend({
\r
930 send : function(o) {
\r
931 var ecb = o.error, scb = o.success;
\r
933 o = extend(this.settings, o);
\r
935 o.success = function(c, x) {
\r
938 if (typeof(c) == 'undefined') {
\r
940 error : 'JSON Parse error.'
\r
945 ecb.call(o.error_scope || o.scope, c.error, x);
\r
947 scb.call(o.success_scope || o.scope, c.result);
\r
950 o.error = function(ty, x) {
\r
951 ecb.call(o.error_scope || o.scope, ty, x);
\r
954 o.data = JSON.serialize({
\r
955 id : o.id || 'c' + (this.count++),
\r
960 // JSON content type for Ruby on rails. Bug: #1883287
\r
961 o.content_type = 'application/json';
\r
967 sendRPC : function(o) {
\r
968 return new tinymce.util.JSONRequest().send(o);
\r
973 (function(tinymce) {
\r
975 var each = tinymce.each,
\r
977 isWebKit = tinymce.isWebKit,
\r
978 isIE = tinymce.isIE,
\r
979 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
980 boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
\r
981 mceAttribs = makeMap('src,href,style,coords,shape'),
\r
982 encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'},
\r
983 encodeCharsRe = /[<>&\"]/g,
\r
984 simpleSelectorRe = /^([a-z0-9],?)+$/i,
\r
985 tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
\r
986 attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
\r
988 function makeMap(str) {
\r
991 str = str.split(',');
\r
992 for (i = str.length; i >= 0; i--)
\r
998 tinymce.create('tinymce.dom.DOMUtils', {
\r
1002 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
\r
1004 "for" : "htmlFor",
\r
1005 "class" : "className",
\r
1006 className : "className",
\r
1007 checked : "checked",
\r
1008 disabled : "disabled",
\r
1009 maxlength : "maxLength",
\r
1010 readonly : "readOnly",
\r
1011 selected : "selected",
\r
1018 DOMUtils : function(d, s) {
\r
1019 var t = this, globalStyle;
\r
1024 t.cssFlicker = false;
\r
1026 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat";
\r
1027 t.stdMode = d.documentMode === 8;
\r
1029 t.settings = s = tinymce.extend({
\r
1030 keep_values : false,
\r
1035 // Fix IE6SP2 flicker and check it failed for pre SP2
\r
1036 if (tinymce.isIE6) {
\r
1038 d.execCommand('BackgroundImageCache', false, true);
\r
1040 t.cssFlicker = true;
\r
1044 // Build styles list
\r
1045 if (s.valid_styles) {
\r
1048 // Convert styles into a rule list
\r
1049 each(s.valid_styles, function(value, key) {
\r
1050 t._styles[key] = tinymce.explode(value);
\r
1054 tinymce.addUnload(t.destroy, t);
\r
1057 getRoot : function() {
\r
1058 var t = this, s = t.settings;
\r
1060 return (s && t.get(s.root_element)) || t.doc.body;
\r
1063 getViewPort : function(w) {
\r
1066 w = !w ? this.win : w;
\r
1068 b = this.boxModel ? d.documentElement : d.body;
\r
1070 // Returns viewport size excluding scrollbars
\r
1072 x : w.pageXOffset || b.scrollLeft,
\r
1073 y : w.pageYOffset || b.scrollTop,
\r
1074 w : w.innerWidth || b.clientWidth,
\r
1075 h : w.innerHeight || b.clientHeight
\r
1079 getRect : function(e) {
\r
1080 var p, t = this, sr;
\r
1084 sr = t.getSize(e);
\r
1094 getSize : function(e) {
\r
1095 var t = this, w, h;
\r
1098 w = t.getStyle(e, 'width');
\r
1099 h = t.getStyle(e, 'height');
\r
1101 // Non pixel value, then force offset/clientWidth
\r
1102 if (w.indexOf('px') === -1)
\r
1105 // Non pixel value, then force offset/clientWidth
\r
1106 if (h.indexOf('px') === -1)
\r
1110 w : parseInt(w) || e.offsetWidth || e.clientWidth,
\r
1111 h : parseInt(h) || e.offsetHeight || e.clientHeight
\r
1115 getParent : function(n, f, r) {
\r
1116 return this.getParents(n, f, r, false);
\r
1119 getParents : function(n, f, r, c) {
\r
1120 var t = this, na, se = t.settings, o = [];
\r
1123 c = c === undefined;
\r
1125 if (se.strict_root)
\r
1126 r = r || t.getRoot();
\r
1128 // Wrap node name as func
\r
1129 if (is(f, 'string')) {
\r
1133 f = function(n) {return n.nodeType == 1;};
\r
1136 return t.is(n, na);
\r
1142 if (n == r || !n.nodeType || n.nodeType === 9)
\r
1155 return c ? o : null;
\r
1158 get : function(e) {
\r
1161 if (e && this.doc && typeof(e) == 'string') {
\r
1163 e = this.doc.getElementById(e);
\r
1165 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
\r
1166 if (e && e.id !== n)
\r
1167 return this.doc.getElementsByName(n)[1];
\r
1173 getNext : function(node, selector) {
\r
1174 return this._findSib(node, selector, 'nextSibling');
\r
1177 getPrev : function(node, selector) {
\r
1178 return this._findSib(node, selector, 'previousSibling');
\r
1182 select : function(pa, s) {
\r
1185 return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
\r
1188 is : function(n, selector) {
\r
1191 // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
\r
1192 if (n.length === undefined) {
\r
1193 // Simple all selector
\r
1194 if (selector === '*')
\r
1195 return n.nodeType == 1;
\r
1197 // Simple selector just elements
\r
1198 if (simpleSelectorRe.test(selector)) {
\r
1199 selector = selector.toLowerCase().split(/,/);
\r
1200 n = n.nodeName.toLowerCase();
\r
1202 for (i = selector.length - 1; i >= 0; i--) {
\r
1203 if (selector[i] == n)
\r
1211 return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
\r
1215 add : function(p, n, a, h, c) {
\r
1218 return this.run(p, function(p) {
\r
1221 e = is(n, 'string') ? t.doc.createElement(n) : n;
\r
1222 t.setAttribs(e, a);
\r
1231 return !c ? p.appendChild(e) : e;
\r
1235 create : function(n, a, h) {
\r
1236 return this.add(this.doc.createElement(n), n, a, h, 1);
\r
1239 createHTML : function(n, a, h) {
\r
1240 var o = '', t = this, k;
\r
1245 if (a.hasOwnProperty(k))
\r
1246 o += ' ' + k + '="' + t.encode(a[k]) + '"';
\r
1249 if (tinymce.is(h))
\r
1250 return o + '>' + h + '</' + n + '>';
\r
1255 remove : function(node, keep_children) {
\r
1256 return this.run(node, function(node) {
\r
1257 var parent, child;
\r
1259 parent = node.parentNode;
\r
1264 if (keep_children) {
\r
1265 while (child = node.firstChild) {
\r
1266 // IE 8 will crash if you don't remove completely empty text nodes
\r
1267 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
\r
1268 parent.insertBefore(child, node);
\r
1270 node.removeChild(child);
\r
1274 return parent.removeChild(node);
\r
1278 setStyle : function(n, na, v) {
\r
1281 return t.run(n, function(e) {
\r
1286 // Camelcase it, if needed
\r
1287 na = na.replace(/-(\D)/g, function(a, b){
\r
1288 return b.toUpperCase();
\r
1291 // Default px suffix on these
\r
1292 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
\r
1297 // IE specific opacity
\r
1299 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
\r
1301 if (!n.currentStyle || !n.currentStyle.hasLayout)
\r
1302 s.display = 'inline-block';
\r
1305 // Fix for older browsers
\r
1306 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
\r
1310 isIE ? s.styleFloat = v : s.cssFloat = v;
\r
1317 // Force update of the style data
\r
1318 if (t.settings.update_styles)
\r
1319 t.setAttrib(e, '_mce_style');
\r
1323 getStyle : function(n, na, c) {
\r
1330 if (this.doc.defaultView && c) {
\r
1331 // Remove camelcase
\r
1332 na = na.replace(/[A-Z]/g, function(a){
\r
1337 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
\r
1339 // Old safari might fail
\r
1344 // Camelcase it, if needed
\r
1345 na = na.replace(/-(\D)/g, function(a, b){
\r
1346 return b.toUpperCase();
\r
1349 if (na == 'float')
\r
1350 na = isIE ? 'styleFloat' : 'cssFloat';
\r
1353 if (n.currentStyle && c)
\r
1354 return n.currentStyle[na];
\r
1356 return n.style[na];
\r
1359 setStyles : function(e, o) {
\r
1360 var t = this, s = t.settings, ol;
\r
1362 ol = s.update_styles;
\r
1363 s.update_styles = 0;
\r
1365 each(o, function(v, n) {
\r
1366 t.setStyle(e, n, v);
\r
1369 // Update style info
\r
1370 s.update_styles = ol;
\r
1371 if (s.update_styles)
\r
1372 t.setAttrib(e, s.cssText);
\r
1375 setAttrib : function(e, n, v) {
\r
1378 // Whats the point
\r
1382 // Strict XML mode
\r
1383 if (t.settings.strict)
\r
1384 n = n.toLowerCase();
\r
1386 return this.run(e, function(e) {
\r
1387 var s = t.settings;
\r
1391 if (!is(v, 'string')) {
\r
1392 each(v, function(v, n) {
\r
1393 t.setStyle(e, n, v);
\r
1399 // No mce_style for elements with these since they might get resized by the user
\r
1400 if (s.keep_values) {
\r
1401 if (v && !t._isRes(v))
\r
1402 e.setAttribute('_mce_style', v, 2);
\r
1404 e.removeAttribute('_mce_style', 2);
\r
1407 e.style.cssText = v;
\r
1411 e.className = v || ''; // Fix IE null bug
\r
1416 if (s.keep_values) {
\r
1417 if (s.url_converter)
\r
1418 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
\r
1420 t.setAttrib(e, '_mce_' + n, v, 2);
\r
1426 e.setAttribute('_mce_style', v);
\r
1430 if (is(v) && v !== null && v.length !== 0)
\r
1431 e.setAttribute(n, '' + v, 2);
\r
1433 e.removeAttribute(n, 2);
\r
1437 setAttribs : function(e, o) {
\r
1440 return this.run(e, function(e) {
\r
1441 each(o, function(v, n) {
\r
1442 t.setAttrib(e, n, v);
\r
1447 getAttrib : function(e, n, dv) {
\r
1452 if (!e || e.nodeType !== 1)
\r
1458 // Try the mce variant for these
\r
1459 if (/^(src|href|style|coords|shape)$/.test(n)) {
\r
1460 v = e.getAttribute("_mce_" + n);
\r
1466 if (isIE && t.props[n]) {
\r
1467 v = e[t.props[n]];
\r
1468 v = v && v.nodeValue ? v.nodeValue : v;
\r
1472 v = e.getAttribute(n, 2);
\r
1474 // Check boolean attribs
\r
1475 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
\r
1476 if (e[t.props[n]] === true && v === '')
\r
1479 return v ? n : '';
\r
1482 // Inner input elements will override attributes on form elements
\r
1483 if (e.nodeName === "FORM" && e.getAttributeNode(n))
\r
1484 return e.getAttributeNode(n).nodeValue;
\r
1486 if (n === 'style') {
\r
1487 v = v || e.style.cssText;
\r
1490 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
\r
1492 if (t.settings.keep_values && !t._isRes(v))
\r
1493 e.setAttribute('_mce_style', v);
\r
1497 // Remove Apple and WebKit stuff
\r
1498 if (isWebKit && n === "class" && v)
\r
1499 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
\r
1501 // Handle IE issues
\r
1506 // IE returns 1 as default value
\r
1513 // IE returns +0 as default value for size
\r
1514 if (v === '+0' || v === 20 || v === 0)
\r
1531 // IE returns -1 as default value
\r
1539 // IE returns default value
\r
1540 if (v === 32768 || v === 2147483647 || v === '32768')
\r
1555 v = v.toLowerCase();
\r
1559 // IE has odd anonymous function for event attributes
\r
1560 if (n.indexOf('on') === 0 && v)
\r
1561 v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
\r
1565 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
\r
1568 getPos : function(n, ro) {
\r
1569 var t = this, x = 0, y = 0, e, d = t.doc, r;
\r
1572 ro = ro || d.body;
\r
1575 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
\r
1576 if (isIE && !t.stdMode) {
\r
1577 n = n.getBoundingClientRect();
\r
1578 e = t.boxModel ? d.documentElement : d.body;
\r
1579 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
\r
1580 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
\r
1582 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
\r
1586 while (r && r != ro && r.nodeType) {
\r
1587 x += r.offsetLeft || 0;
\r
1588 y += r.offsetTop || 0;
\r
1589 r = r.offsetParent;
\r
1593 while (r && r != ro && r.nodeType) {
\r
1594 x -= r.scrollLeft || 0;
\r
1595 y -= r.scrollTop || 0;
\r
1600 return {x : x, y : y};
\r
1603 parseStyle : function(st) {
\r
1604 var t = this, s = t.settings, o = {};
\r
1609 function compress(p, s, ot) {
\r
1612 // Get values and check it it needs compressing
\r
1613 t = o[p + '-top' + s];
\r
1617 r = o[p + '-right' + s];
\r
1621 b = o[p + '-bottom' + s];
\r
1625 l = o[p + '-left' + s];
\r
1631 delete o[p + '-top' + s];
\r
1632 delete o[p + '-right' + s];
\r
1633 delete o[p + '-bottom' + s];
\r
1634 delete o[p + '-left' + s];
\r
1637 function compress2(ta, a, b, c) {
\r
1653 o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
\r
1659 st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
\r
1661 each(st.split(';'), function(v) {
\r
1665 v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
\r
1666 v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
\r
1668 sv = tinymce.trim(v[1]);
\r
1669 sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
\r
1671 sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
\r
1672 return t.toHex(v);
\r
1675 if (s.url_converter) {
\r
1676 sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
\r
1677 return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
\r
1681 o[tinymce.trim(v[0]).toLowerCase()] = sv;
\r
1685 compress("border", "", "border");
\r
1686 compress("border", "-width", "border-width");
\r
1687 compress("border", "-color", "border-color");
\r
1688 compress("border", "-style", "border-style");
\r
1689 compress("padding", "", "padding");
\r
1690 compress("margin", "", "margin");
\r
1691 compress2('border', 'border-width', 'border-style', 'border-color');
\r
1694 // Remove pointless border
\r
1695 if (o.border == 'medium none')
\r
1702 serializeStyle : function(o, name) {
\r
1703 var t = this, s = '';
\r
1705 function add(v, k) {
\r
1707 // Remove browser specific styles like -moz- or -webkit-
\r
1708 if (k.indexOf('-') === 0)
\r
1712 case 'font-weight':
\r
1713 // Opera will output bold as 700
\r
1720 case 'background-color':
\r
1721 v = v.toLowerCase();
\r
1725 s += (s ? ' ' : '') + k + ': ' + v + ';';
\r
1729 // Validate style output
\r
1730 if (name && t._styles) {
\r
1731 each(t._styles['*'], function(name) {
\r
1732 add(o[name], name);
\r
1735 each(t._styles[name.toLowerCase()], function(name) {
\r
1736 add(o[name], name);
\r
1744 loadCSS : function(u) {
\r
1745 var t = this, d = t.doc, head;
\r
1750 head = t.select('head')[0];
\r
1752 each(u.split(','), function(u) {
\r
1758 t.files[u] = true;
\r
1759 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
\r
1761 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
\r
1762 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
\r
1763 // It's ugly but it seems to work fine.
\r
1764 if (isIE && d.documentMode) {
\r
1765 link.onload = function() {
\r
1767 link.onload = null;
\r
1771 head.appendChild(link);
\r
1775 addClass : function(e, c) {
\r
1776 return this.run(e, function(e) {
\r
1782 if (this.hasClass(e, c))
\r
1783 return e.className;
\r
1785 o = this.removeClass(e, c);
\r
1787 return e.className = (o != '' ? (o + ' ') : '') + c;
\r
1791 removeClass : function(e, c) {
\r
1794 return t.run(e, function(e) {
\r
1797 if (t.hasClass(e, c)) {
\r
1799 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
\r
1801 v = e.className.replace(re, ' ');
\r
1802 v = tinymce.trim(v != ' ' ? v : '');
\r
1806 // Empty class attr
\r
1808 e.removeAttribute('class');
\r
1809 e.removeAttribute('className');
\r
1815 return e.className;
\r
1819 hasClass : function(n, c) {
\r
1825 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
\r
1828 show : function(e) {
\r
1829 return this.setStyle(e, 'display', 'block');
\r
1832 hide : function(e) {
\r
1833 return this.setStyle(e, 'display', 'none');
\r
1836 isHidden : function(e) {
\r
1839 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
\r
1842 uniqueId : function(p) {
\r
1843 return (!p ? 'mce_' : p) + (this.counter++);
\r
1846 setHTML : function(e, h) {
\r
1849 return this.run(e, function(e) {
\r
1850 var x, i, nl, n, p, x;
\r
1852 h = t.processHTML(h);
\r
1856 // Remove all child nodes
\r
1857 while (e.firstChild)
\r
1858 e.firstChild.removeNode();
\r
1861 // IE will remove comments from the beginning
\r
1862 // unless you padd the contents with something
\r
1863 e.innerHTML = '<br />' + h;
\r
1864 e.removeChild(e.firstChild);
\r
1866 // 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
1867 // This seems to fix this problem
\r
1869 // Create new div with HTML contents and a BR infront to keep comments
\r
1870 x = t.create('div');
\r
1871 x.innerHTML = '<br />' + h;
\r
1873 // Add all children from div to target
\r
1874 each (x.childNodes, function(n, i) {
\r
1875 // Skip br element
\r
1882 // IE has a serious bug when it comes to paragraphs it can produce an invalid
\r
1883 // DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
\r
1884 // It seems to be that IE doesn't like a root block element placed inside another root block element
\r
1885 if (t.settings.fix_ie_paragraphs)
\r
1886 h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true"> </p>');
\r
1890 if (t.settings.fix_ie_paragraphs) {
\r
1891 // Check for odd paragraphs this is a sign of a broken DOM
\r
1892 nl = e.getElementsByTagName("p");
\r
1893 for (i = nl.length - 1, x = 0; i >= 0; i--) {
\r
1896 if (!n.hasChildNodes()) {
\r
1897 if (!n._mce_keep) {
\r
1898 x = 1; // Is broken
\r
1902 n.removeAttribute('_mce_keep');
\r
1907 // Time to fix the madness IE left us
\r
1909 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
\r
1910 // after we use innerHTML we can fix the DOM tree
\r
1911 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
\r
1912 h = h.replace(/<\/p>/gi, '</div>');
\r
1914 // Set the new HTML with DIVs
\r
1917 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
\r
1918 // This is needed since IE has a annoying bug see above for details
\r
1919 // This is a slow process but it has to be done. :(
\r
1920 if (t.settings.fix_ie_paragraphs) {
\r
1921 nl = e.getElementsByTagName("DIV");
\r
1922 for (i = nl.length - 1; i >= 0; i--) {
\r
1925 // Is it a temp div
\r
1927 // Create new paragraph
\r
1928 p = t.doc.createElement('p');
\r
1930 // Copy all attributes
\r
1931 n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
\r
1934 if (b !== '_mce_tmp') {
\r
1935 v = n.getAttribute(b);
\r
1937 if (!v && b === 'class')
\r
1940 p.setAttribute(b, v);
\r
1944 // Append all children to new paragraph
\r
1945 for (x = 0; x<n.childNodes.length; x++)
\r
1946 p.appendChild(n.childNodes[x].cloneNode(true));
\r
1948 // Replace div with new paragraph
\r
1961 processHTML : function(h) {
\r
1962 var t = this, s = t.settings, codeBlocks = [];
\r
1964 if (!s.process_html)
\r
1968 h = h.replace(/'/g, '''); // IE can't handle apos
\r
1969 h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
\r
1972 // Fix some issues
\r
1973 h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
\r
1975 // Store away src and href in _mce_src and mce_href since browsers mess them up
\r
1976 if (s.keep_values) {
\r
1977 // Wrap scripts and styles in comments for serialization purposes
\r
1978 if (/<script|noscript|style/i.test(h)) {
\r
1979 function trim(s) {
\r
1980 // Remove prefix and suffix code for element
\r
1981 s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
\r
1982 s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
\r
1983 s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
\r
1984 s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
\r
1989 // Wrap the script contents in CDATA and keep them from executing
\r
1990 h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
\r
1991 // Force type attribute
\r
1993 attribs = ' type="text/javascript"';
\r
1995 // Convert the src attribute of the scripts
\r
1996 attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
\r
1997 if (s.url_converter)
\r
1998 url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
\r
2000 return '_mce_src="' + url + '"';
\r
2003 // Wrap text contents
\r
2004 if (tinymce.trim(text)) {
\r
2005 codeBlocks.push(trim(text));
\r
2006 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
\r
2009 return '<mce:script' + attribs + '>' + text + '</mce:script>';
\r
2012 // Wrap style elements
\r
2013 h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
\r
2014 // Wrap text contents
\r
2016 codeBlocks.push(trim(text));
\r
2017 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
\r
2020 return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
\r
2023 // Wrap noscript elements
\r
2024 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
2025 return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '--') + '--></mce:noscript>';
\r
2029 h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
\r
2031 // This function processes the attributes in the HTML string to force boolean
\r
2032 // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
\r
2033 function processTags(html) {
\r
2034 return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
\r
2035 return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
\r
2038 name = name.toLowerCase();
\r
2039 value = value || val2 || val3 || "";
\r
2041 // Treat boolean attributes
\r
2042 if (boolAttrs[name]) {
\r
2043 // false or 0 is treated as a missing attribute
\r
2044 if (value === 'false' || value === '0')
\r
2047 return name + '="' + name + '"';
\r
2050 // Is attribute one that needs special treatment
\r
2051 if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
\r
2052 mceValue = t.decode(value);
\r
2054 // Convert URLs to relative/absolute ones
\r
2055 if (s.url_converter && (name == "src" || name == "href"))
\r
2056 mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
\r
2058 // Process styles lowercases them and compresses them
\r
2059 if (name == 'style')
\r
2060 mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
\r
2062 return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
\r
2070 h = processTags(h);
\r
2072 // Restore script blocks
\r
2073 h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
\r
2074 return codeBlocks[idx];
\r
2081 getOuterHTML : function(e) {
\r
2089 if (e.outerHTML !== undefined)
\r
2090 return e.outerHTML;
\r
2092 d = (e.ownerDocument || this.doc).createElement("body");
\r
2093 d.appendChild(e.cloneNode(true));
\r
2095 return d.innerHTML;
\r
2098 setOuterHTML : function(e, h, d) {
\r
2101 function setHTML(e, h, d) {
\r
2104 tp = d.createElement("body");
\r
2109 t.insertAfter(n.cloneNode(true), e);
\r
2110 n = n.previousSibling;
\r
2116 return this.run(e, function(e) {
\r
2119 // Only set HTML on elements
\r
2120 if (e.nodeType == 1) {
\r
2121 d = d || e.ownerDocument || t.doc;
\r
2125 // Try outerHTML for IE it sometimes produces an unknown runtime error
\r
2126 if (isIE && e.nodeType == 1)
\r
2131 // Fix for unknown runtime error
\r
2140 decode : function(s) {
\r
2143 // Look for entities to decode
\r
2144 if (/&[\w#]+;/.test(s)) {
\r
2145 // Decode the entities using a div element not super efficient but less code
\r
2146 e = this.doc.createElement("div");
\r
2154 } while (n = n.nextSibling);
\r
2163 encode : function(str) {
\r
2164 return ('' + str).replace(encodeCharsRe, function(chr) {
\r
2165 return encodedChars[chr];
\r
2169 insertAfter : function(node, reference_node) {
\r
2170 reference_node = this.get(reference_node);
\r
2172 return this.run(node, function(node) {
\r
2173 var parent, nextSibling;
\r
2175 parent = reference_node.parentNode;
\r
2176 nextSibling = reference_node.nextSibling;
\r
2179 parent.insertBefore(node, nextSibling);
\r
2181 parent.appendChild(node);
\r
2187 isBlock : function(n) {
\r
2188 if (n.nodeType && n.nodeType !== 1)
\r
2191 n = n.nodeName || n;
\r
2193 return blockRe.test(n);
\r
2196 replace : function(n, o, k) {
\r
2199 if (is(o, 'array'))
\r
2200 n = n.cloneNode(true);
\r
2202 return t.run(o, function(o) {
\r
2204 each(tinymce.grep(o.childNodes), function(c) {
\r
2209 return o.parentNode.replaceChild(n, o);
\r
2213 rename : function(elm, name) {
\r
2214 var t = this, newElm;
\r
2216 if (elm.nodeName != name.toUpperCase()) {
\r
2217 // Rename block element
\r
2218 newElm = t.create(name);
\r
2220 // Copy attribs to new block
\r
2221 each(t.getAttribs(elm), function(attr_node) {
\r
2222 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
\r
2226 t.replace(newElm, elm, 1);
\r
2229 return newElm || elm;
\r
2232 findCommonAncestor : function(a, b) {
\r
2238 while (pe && ps != pe)
\r
2239 pe = pe.parentNode;
\r
2244 ps = ps.parentNode;
\r
2247 if (!ps && a.ownerDocument)
\r
2248 return a.ownerDocument.documentElement;
\r
2253 toHex : function(s) {
\r
2254 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
\r
2257 s = parseInt(s).toString(16);
\r
2259 return s.length > 1 ? s : '0' + s; // 0 -> 00
\r
2263 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
\r
2271 getClasses : function() {
\r
2272 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
\r
2277 function addClasses(s) {
\r
2278 // IE style imports
\r
2279 each(s.imports, function(r) {
\r
2283 each(s.cssRules || s.rules, function(r) {
\r
2284 // Real type or fake it on IE
\r
2285 switch (r.type || 1) {
\r
2288 if (r.selectorText) {
\r
2289 each(r.selectorText.split(','), function(v) {
\r
2290 v = v.replace(/^\s*|\s*$|^\s\./g, "");
\r
2292 // Is internal or it doesn't contain a class
\r
2293 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
\r
2296 // Remove everything but class name
\r
2298 v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
\r
2301 if (f && !(v = f(v, ov)))
\r
2305 cl.push({'class' : v});
\r
2314 addClasses(r.styleSheet);
\r
2321 each(t.doc.styleSheets, addClasses);
\r
2326 if (cl.length > 0)
\r
2332 run : function(e, f, s) {
\r
2335 if (t.doc && typeof(e) === 'string')
\r
2342 if (!e.nodeType && (e.length || e.length === 0)) {
\r
2345 each(e, function(e, i) {
\r
2347 if (typeof(e) == 'string')
\r
2348 e = t.doc.getElementById(e);
\r
2350 o.push(f.call(s, e, i));
\r
2357 return f.call(s, e);
\r
2360 getAttribs : function(n) {
\r
2371 // Object will throw exception in IE
\r
2372 if (n.nodeName == 'OBJECT')
\r
2373 return n.attributes;
\r
2375 // IE doesn't keep the selected attribute if you clone option elements
\r
2376 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
\r
2377 o.push({specified : 1, nodeName : 'selected'});
\r
2379 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
\r
2380 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
\r
2381 o.push({specified : 1, nodeName : a});
\r
2387 return n.attributes;
\r
2390 destroy : function(s) {
\r
2394 t.events.destroy();
\r
2396 t.win = t.doc = t.root = t.events = null;
\r
2398 // Manual destroy then remove unload handler
\r
2400 tinymce.removeUnload(t.destroy);
\r
2403 createRng : function() {
\r
2406 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
\r
2409 nodeIndex : function(node, normalized) {
\r
2410 var idx = 0, lastNodeType, lastNode, nodeType;
\r
2413 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
\r
2414 nodeType = node.nodeType;
\r
2416 // Normalize text nodes
\r
2417 if (normalized && nodeType == 3) {
\r
2418 if (nodeType == lastNodeType || !node.nodeValue.length)
\r
2423 lastNodeType = nodeType;
\r
2430 split : function(pe, e, re) {
\r
2431 var t = this, r = t.createRng(), bef, aft, pa;
\r
2433 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
\r
2434 // but we don't want that in our code since it serves no purpose for the end user
\r
2435 // For example if this is chopped:
\r
2436 // <p>text 1<span><b>CHOP</b></span>text 2</p>
\r
2438 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
\r
2439 // this function will then trim of empty edges and produce:
\r
2440 // <p>text 1</p><b>CHOP</b><p>text 2</p>
\r
2441 function trim(node) {
\r
2442 var i, children = node.childNodes;
\r
2444 if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
\r
2447 for (i = children.length - 1; i >= 0; i--)
\r
2448 trim(children[i]);
\r
2450 if (node.nodeType != 9) {
\r
2451 // Keep non whitespace text nodes
\r
2452 if (node.nodeType == 3 && node.nodeValue.length > 0)
\r
2455 if (node.nodeType == 1) {
\r
2456 // If the only child is a bookmark then move it up
\r
2457 children = node.childNodes;
\r
2458 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
\r
2459 node.parentNode.insertBefore(children[0], node);
\r
2461 // Keep non empty elements or img, hr etc
\r
2462 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
\r
2473 // Get before chunk
\r
2474 r.setStart(pe.parentNode, t.nodeIndex(pe));
\r
2475 r.setEnd(e.parentNode, t.nodeIndex(e));
\r
2476 bef = r.extractContents();
\r
2478 // Get after chunk
\r
2479 r = t.createRng();
\r
2480 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
\r
2481 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
\r
2482 aft = r.extractContents();
\r
2484 // Insert before chunk
\r
2485 pa = pe.parentNode;
\r
2486 pa.insertBefore(trim(bef), pe);
\r
2488 // Insert middle chunk
\r
2490 pa.replaceChild(re, e);
\r
2492 pa.insertBefore(e, pe);
\r
2494 // Insert after chunk
\r
2495 pa.insertBefore(trim(aft), pe);
\r
2502 bind : function(target, name, func, scope) {
\r
2506 t.events = new tinymce.dom.EventUtils();
\r
2508 return t.events.add(target, name, func, scope || this);
\r
2511 unbind : function(target, name, func) {
\r
2515 t.events = new tinymce.dom.EventUtils();
\r
2517 return t.events.remove(target, name, func);
\r
2521 _findSib : function(node, selector, name) {
\r
2522 var t = this, f = selector;
\r
2525 // If expression make a function of it using is
\r
2526 if (is(f, 'string')) {
\r
2527 f = function(node) {
\r
2528 return t.is(node, selector);
\r
2532 // Loop all siblings
\r
2533 for (node = node[name]; node; node = node[name]) {
\r
2542 _isRes : function(c) {
\r
2543 // Is live resizble element
\r
2544 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
\r
2548 walk : function(n, f, s) {
\r
2549 var d = this.doc, w;
\r
2551 if (d.createTreeWalker) {
\r
2552 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
\r
2554 while ((n = w.nextNode()) != null)
\r
2555 f.call(s || this, n);
\r
2557 tinymce.walk(n, f, 'childNodes', s);
\r
2562 toRGB : function(s) {
\r
2563 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
\r
2566 // #FFF -> #FFFFFF
\r
2568 c[3] = c[2] = c[1];
\r
2570 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
\r
2578 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
\r
2582 // Range constructor
\r
2583 function Range(dom) {
\r
2591 START_OFFSET = 'startOffset',
\r
2592 START_CONTAINER = 'startContainer',
\r
2593 END_CONTAINER = 'endContainer',
\r
2594 END_OFFSET = 'endOffset',
\r
2595 extend = tinymce.extend,
\r
2596 nodeIndex = dom.nodeIndex;
\r
2600 startContainer : doc,
\r
2602 endContainer : doc,
\r
2605 commonAncestorContainer : doc,
\r
2607 // Range constants
\r
2608 START_TO_START : 0,
\r
2614 setStart : setStart,
\r
2616 setStartBefore : setStartBefore,
\r
2617 setStartAfter : setStartAfter,
\r
2618 setEndBefore : setEndBefore,
\r
2619 setEndAfter : setEndAfter,
\r
2620 collapse : collapse,
\r
2621 selectNode : selectNode,
\r
2622 selectNodeContents : selectNodeContents,
\r
2623 compareBoundaryPoints : compareBoundaryPoints,
\r
2624 deleteContents : deleteContents,
\r
2625 extractContents : extractContents,
\r
2626 cloneContents : cloneContents,
\r
2627 insertNode : insertNode,
\r
2628 surroundContents : surroundContents,
\r
2629 cloneRange : cloneRange
\r
2632 function setStart(n, o) {
\r
2633 _setEndPoint(TRUE, n, o);
\r
2636 function setEnd(n, o) {
\r
2637 _setEndPoint(FALSE, n, o);
\r
2640 function setStartBefore(n) {
\r
2641 setStart(n.parentNode, nodeIndex(n));
\r
2644 function setStartAfter(n) {
\r
2645 setStart(n.parentNode, nodeIndex(n) + 1);
\r
2648 function setEndBefore(n) {
\r
2649 setEnd(n.parentNode, nodeIndex(n));
\r
2652 function setEndAfter(n) {
\r
2653 setEnd(n.parentNode, nodeIndex(n) + 1);
\r
2656 function collapse(ts) {
\r
2658 t[END_CONTAINER] = t[START_CONTAINER];
\r
2659 t[END_OFFSET] = t[START_OFFSET];
\r
2661 t[START_CONTAINER] = t[END_CONTAINER];
\r
2662 t[START_OFFSET] = t[END_OFFSET];
\r
2665 t.collapsed = TRUE;
\r
2668 function selectNode(n) {
\r
2669 setStartBefore(n);
\r
2673 function selectNodeContents(n) {
\r
2675 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
\r
2678 function compareBoundaryPoints(h, r) {
\r
2679 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
\r
2681 // Check START_TO_START
\r
2683 return _compareBoundaryPoints(sc, so, sc, so);
\r
2685 // Check START_TO_END
\r
2687 return _compareBoundaryPoints(sc, so, ec, eo);
\r
2689 // Check END_TO_END
\r
2691 return _compareBoundaryPoints(ec, eo, ec, eo);
\r
2693 // Check END_TO_START
\r
2695 return _compareBoundaryPoints(ec, eo, sc, so);
\r
2698 function deleteContents() {
\r
2699 _traverse(DELETE);
\r
2702 function extractContents() {
\r
2703 return _traverse(EXTRACT);
\r
2706 function cloneContents() {
\r
2707 return _traverse(CLONE);
\r
2710 function insertNode(n) {
\r
2711 var startContainer = this[START_CONTAINER],
\r
2712 startOffset = this[START_OFFSET], nn, o;
\r
2714 // Node is TEXT_NODE or CDATA
\r
2715 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
\r
2716 if (!startOffset) {
\r
2717 // At the start of text
\r
2718 startContainer.parentNode.insertBefore(n, startContainer);
\r
2719 } else if (startOffset >= startContainer.nodeValue.length) {
\r
2720 // At the end of text
\r
2721 dom.insertAfter(n, startContainer);
\r
2723 // Middle, need to split
\r
2724 nn = startContainer.splitText(startOffset);
\r
2725 startContainer.parentNode.insertBefore(n, nn);
\r
2728 // Insert element node
\r
2729 if (startContainer.childNodes.length > 0)
\r
2730 o = startContainer.childNodes[startOffset];
\r
2733 startContainer.insertBefore(n, o);
\r
2735 startContainer.appendChild(n);
\r
2739 function surroundContents(n) {
\r
2740 var f = t.extractContents();
\r
2747 function cloneRange() {
\r
2748 return extend(new Range(dom), {
\r
2749 startContainer : t[START_CONTAINER],
\r
2750 startOffset : t[START_OFFSET],
\r
2751 endContainer : t[END_CONTAINER],
\r
2752 endOffset : t[END_OFFSET],
\r
2753 collapsed : t.collapsed,
\r
2754 commonAncestorContainer : t.commonAncestorContainer
\r
2758 // Private methods
\r
2760 function _getSelectedNode(container, offset) {
\r
2763 if (container.nodeType == 3 /* TEXT_NODE */)
\r
2769 child = container.firstChild;
\r
2770 while (child && offset > 0) {
\r
2772 child = child.nextSibling;
\r
2781 function _isCollapsed() {
\r
2782 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
\r
2785 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
\r
2786 var c, offsetC, n, cmnRoot, childA, childB;
\r
2788 // In the first case the boundary-points have the same container. A is before B
\r
2789 // if its offset is less than the offset of B, A is equal to B if its offset is
\r
2790 // equal to the offset of B, and A is after B if its offset is greater than the
\r
2792 if (containerA == containerB) {
\r
2793 if (offsetA == offsetB)
\r
2794 return 0; // equal
\r
2796 if (offsetA < offsetB)
\r
2797 return -1; // before
\r
2799 return 1; // after
\r
2802 // In the second case a child node C of the container of A is an ancestor
\r
2803 // container of B. In this case, A is before B if the offset of A is less than or
\r
2804 // equal to the index of the child node C and A is after B otherwise.
\r
2806 while (c && c.parentNode != containerA)
\r
2811 n = containerA.firstChild;
\r
2813 while (n != c && offsetC < offsetA) {
\r
2815 n = n.nextSibling;
\r
2818 if (offsetA <= offsetC)
\r
2819 return -1; // before
\r
2821 return 1; // after
\r
2824 // In the third case a child node C of the container of B is an ancestor container
\r
2825 // of A. In this case, A is before B if the index of the child node C is less than
\r
2826 // the offset of B and A is after B otherwise.
\r
2828 while (c && c.parentNode != containerB) {
\r
2834 n = containerB.firstChild;
\r
2836 while (n != c && offsetC < offsetB) {
\r
2838 n = n.nextSibling;
\r
2841 if (offsetC < offsetB)
\r
2842 return -1; // before
\r
2844 return 1; // after
\r
2847 // In the fourth case, none of three other cases hold: the containers of A and B
\r
2848 // are siblings or descendants of sibling nodes. In this case, A is before B if
\r
2849 // the container of A is before the container of B in a pre-order traversal of the
\r
2850 // Ranges' context tree and A is after B otherwise.
\r
2851 cmnRoot = dom.findCommonAncestor(containerA, containerB);
\r
2852 childA = containerA;
\r
2854 while (childA && childA.parentNode != cmnRoot)
\r
2855 childA = childA.parentNode;
\r
2860 childB = containerB;
\r
2861 while (childB && childB.parentNode != cmnRoot)
\r
2862 childB = childB.parentNode;
\r
2867 if (childA == childB)
\r
2868 return 0; // equal
\r
2870 n = cmnRoot.firstChild;
\r
2873 return -1; // before
\r
2876 return 1; // after
\r
2878 n = n.nextSibling;
\r
2882 function _setEndPoint(st, n, o) {
\r
2886 t[START_CONTAINER] = n;
\r
2887 t[START_OFFSET] = o;
\r
2889 t[END_CONTAINER] = n;
\r
2890 t[END_OFFSET] = o;
\r
2893 // If one boundary-point of a Range is set to have a root container
\r
2894 // other than the current one for the Range, the Range is collapsed to
\r
2895 // the new position. This enforces the restriction that both boundary-
\r
2896 // points of a Range must have the same root container.
\r
2897 ec = t[END_CONTAINER];
\r
2898 while (ec.parentNode)
\r
2899 ec = ec.parentNode;
\r
2901 sc = t[START_CONTAINER];
\r
2902 while (sc.parentNode)
\r
2903 sc = sc.parentNode;
\r
2906 // The start position of a Range is guaranteed to never be after the
\r
2907 // end position. To enforce this restriction, if the start is set to
\r
2908 // be at a position after the end, the Range is collapsed to that
\r
2910 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
\r
2915 t.collapsed = _isCollapsed();
\r
2916 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
\r
2919 function _traverse(how) {
\r
2920 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
\r
2922 if (t[START_CONTAINER] == t[END_CONTAINER])
\r
2923 return _traverseSameContainer(how);
\r
2925 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
2926 if (p == t[START_CONTAINER])
\r
2927 return _traverseCommonStartContainer(c, how);
\r
2929 ++endContainerDepth;
\r
2932 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
2933 if (p == t[END_CONTAINER])
\r
2934 return _traverseCommonEndContainer(c, how);
\r
2936 ++startContainerDepth;
\r
2939 depthDiff = startContainerDepth - endContainerDepth;
\r
2941 startNode = t[START_CONTAINER];
\r
2942 while (depthDiff > 0) {
\r
2943 startNode = startNode.parentNode;
\r
2947 endNode = t[END_CONTAINER];
\r
2948 while (depthDiff < 0) {
\r
2949 endNode = endNode.parentNode;
\r
2953 // ascend the ancestor hierarchy until we have a common parent.
\r
2954 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
\r
2959 return _traverseCommonAncestors(startNode, endNode, how);
\r
2962 function _traverseSameContainer(how) {
\r
2963 var frag, s, sub, n, cnt, sibling, xferNode;
\r
2965 if (how != DELETE)
\r
2966 frag = doc.createDocumentFragment();
\r
2968 // If selection is empty, just return the fragment
\r
2969 if (t[START_OFFSET] == t[END_OFFSET])
\r
2972 // Text node needs special case handling
\r
2973 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
\r
2974 // get the substring
\r
2975 s = t[START_CONTAINER].nodeValue;
\r
2976 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
\r
2978 // set the original text node to its new value
\r
2979 if (how != CLONE) {
\r
2980 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
\r
2982 // Nothing is partially selected, so collapse to start point
\r
2986 if (how == DELETE)
\r
2989 frag.appendChild(doc.createTextNode(sub));
\r
2993 // Copy nodes between the start/end offsets.
\r
2994 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
\r
2995 cnt = t[END_OFFSET] - t[START_OFFSET];
\r
2998 sibling = n.nextSibling;
\r
2999 xferNode = _traverseFullySelected(n, how);
\r
3002 frag.appendChild( xferNode );
\r
3008 // Nothing is partially selected, so collapse to start point
\r
3015 function _traverseCommonStartContainer(endAncestor, how) {
\r
3016 var frag, n, endIdx, cnt, sibling, xferNode;
\r
3018 if (how != DELETE)
\r
3019 frag = doc.createDocumentFragment();
\r
3021 n = _traverseRightBoundary(endAncestor, how);
\r
3024 frag.appendChild(n);
\r
3026 endIdx = nodeIndex(endAncestor);
\r
3027 cnt = endIdx - t[START_OFFSET];
\r
3030 // Collapse to just before the endAncestor, which
\r
3031 // is partially selected.
\r
3032 if (how != CLONE) {
\r
3033 t.setEndBefore(endAncestor);
\r
3034 t.collapse(FALSE);
\r
3040 n = endAncestor.previousSibling;
\r
3042 sibling = n.previousSibling;
\r
3043 xferNode = _traverseFullySelected(n, how);
\r
3046 frag.insertBefore(xferNode, frag.firstChild);
\r
3052 // Collapse to just before the endAncestor, which
\r
3053 // is partially selected.
\r
3054 if (how != CLONE) {
\r
3055 t.setEndBefore(endAncestor);
\r
3056 t.collapse(FALSE);
\r
3062 function _traverseCommonEndContainer(startAncestor, how) {
\r
3063 var frag, startIdx, n, cnt, sibling, xferNode;
\r
3065 if (how != DELETE)
\r
3066 frag = doc.createDocumentFragment();
\r
3068 n = _traverseLeftBoundary(startAncestor, how);
\r
3070 frag.appendChild(n);
\r
3072 startIdx = nodeIndex(startAncestor);
\r
3073 ++startIdx; // Because we already traversed it....
\r
3075 cnt = t[END_OFFSET] - startIdx;
\r
3076 n = startAncestor.nextSibling;
\r
3078 sibling = n.nextSibling;
\r
3079 xferNode = _traverseFullySelected(n, how);
\r
3082 frag.appendChild(xferNode);
\r
3088 if (how != CLONE) {
\r
3089 t.setStartAfter(startAncestor);
\r
3096 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
\r
3097 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
\r
3099 if (how != DELETE)
\r
3100 frag = doc.createDocumentFragment();
\r
3102 n = _traverseLeftBoundary(startAncestor, how);
\r
3104 frag.appendChild(n);
\r
3106 commonParent = startAncestor.parentNode;
\r
3107 startOffset = nodeIndex(startAncestor);
\r
3108 endOffset = nodeIndex(endAncestor);
\r
3111 cnt = endOffset - startOffset;
\r
3112 sibling = startAncestor.nextSibling;
\r
3115 nextSibling = sibling.nextSibling;
\r
3116 n = _traverseFullySelected(sibling, how);
\r
3119 frag.appendChild(n);
\r
3121 sibling = nextSibling;
\r
3125 n = _traverseRightBoundary(endAncestor, how);
\r
3128 frag.appendChild(n);
\r
3130 if (how != CLONE) {
\r
3131 t.setStartAfter(startAncestor);
\r
3138 function _traverseRightBoundary(root, how) {
\r
3139 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
\r
3142 return _traverseNode(next, isFullySelected, FALSE, how);
\r
3144 parent = next.parentNode;
\r
3145 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3149 prevSibling = next.previousSibling;
\r
3150 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
\r
3152 if (how != DELETE)
\r
3153 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
\r
3155 isFullySelected = TRUE;
\r
3156 next = prevSibling;
\r
3159 if (parent == root)
\r
3160 return clonedParent;
\r
3162 next = parent.previousSibling;
\r
3163 parent = parent.parentNode;
\r
3165 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3167 if (how != DELETE)
\r
3168 clonedGrandParent.appendChild(clonedParent);
\r
3170 clonedParent = clonedGrandParent;
\r
3174 function _traverseLeftBoundary(root, how) {
\r
3175 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
\r
3178 return _traverseNode(next, isFullySelected, TRUE, how);
\r
3180 parent = next.parentNode;
\r
3181 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3185 nextSibling = next.nextSibling;
\r
3186 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
\r
3188 if (how != DELETE)
\r
3189 clonedParent.appendChild(clonedChild);
\r
3191 isFullySelected = TRUE;
\r
3192 next = nextSibling;
\r
3195 if (parent == root)
\r
3196 return clonedParent;
\r
3198 next = parent.nextSibling;
\r
3199 parent = parent.parentNode;
\r
3201 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3203 if (how != DELETE)
\r
3204 clonedGrandParent.appendChild(clonedParent);
\r
3206 clonedParent = clonedGrandParent;
\r
3210 function _traverseNode(n, isFullySelected, isLeft, how) {
\r
3211 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
\r
3213 if (isFullySelected)
\r
3214 return _traverseFullySelected(n, how);
\r
3216 if (n.nodeType == 3 /* TEXT_NODE */) {
\r
3217 txtValue = n.nodeValue;
\r
3220 offset = t[START_OFFSET];
\r
3221 newNodeValue = txtValue.substring(offset);
\r
3222 oldNodeValue = txtValue.substring(0, offset);
\r
3224 offset = t[END_OFFSET];
\r
3225 newNodeValue = txtValue.substring(0, offset);
\r
3226 oldNodeValue = txtValue.substring(offset);
\r
3230 n.nodeValue = oldNodeValue;
\r
3232 if (how == DELETE)
\r
3235 newNode = n.cloneNode(FALSE);
\r
3236 newNode.nodeValue = newNodeValue;
\r
3241 if (how == DELETE)
\r
3244 return n.cloneNode(FALSE);
\r
3247 function _traverseFullySelected(n, how) {
\r
3248 if (how != DELETE)
\r
3249 return how == CLONE ? n.cloneNode(TRUE) : n;
\r
3251 n.parentNode.removeChild(n);
\r
3259 function Selection(selection) {
\r
3260 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
\r
3262 // Returns a W3C DOM compatible range object by using the IE Range API
\r
3263 function getRange() {
\r
3264 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
\r
3266 // If selection is outside the current document just return an empty range
\r
3267 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
\r
3268 if (element.ownerDocument != dom.doc)
\r
3271 // Handle control selection or text selection of a image
\r
3272 if (ieRange.item || !element.hasChildNodes()) {
\r
3273 domRange.setStart(element.parentNode, dom.nodeIndex(element));
\r
3274 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
\r
3279 collapsed = selection.isCollapsed();
\r
3281 function findEndPoint(start) {
\r
3282 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
\r
3284 // Setup temp range and collapse it
\r
3285 checkRng = ieRange.duplicate();
\r
3286 checkRng.collapse(start);
\r
3288 // Create marker and insert it at the end of the endpoints parent
\r
3289 marker = dom.create('a');
\r
3290 parent = checkRng.parentElement();
\r
3292 // If parent doesn't have any children then set the container to that parent and the index to 0
\r
3293 if (!parent.hasChildNodes()) {
\r
3294 domRange[start ? 'setStart' : 'setEnd'](parent, 0);
\r
3298 parent.appendChild(marker);
\r
3299 checkRng.moveToElementText(marker);
\r
3300 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
\r
3301 if (position > 0) {
\r
3302 // The position is after the end of the parent element.
\r
3303 // This is the case where IE puts the caret to the left edge of a table.
\r
3304 domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
\r
3305 dom.remove(marker);
\r
3309 // Setup node list and endIndex
\r
3310 nodes = tinymce.grep(parent.childNodes);
\r
3311 endIndex = nodes.length - 1;
\r
3312 // Perform a binary search for the position
\r
3313 while (startIndex <= endIndex) {
\r
3314 index = Math.floor((startIndex + endIndex) / 2);
\r
3316 // Insert marker and check it's position relative to the selection
\r
3317 parent.insertBefore(marker, nodes[index]);
\r
3318 checkRng.moveToElementText(marker);
\r
3319 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
\r
3320 if (position > 0) {
\r
3321 // Marker is to the right
\r
3322 startIndex = index + 1;
\r
3323 } else if (position < 0) {
\r
3324 // Marker is to the left
\r
3325 endIndex = index - 1;
\r
3327 // Maker is where we are
\r
3333 // Setup container
\r
3334 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
\r
3336 // Handle element selection
\r
3337 if (container.nodeType == 1) {
\r
3338 dom.remove(marker);
\r
3340 // Find offset and container
\r
3341 offset = dom.nodeIndex(container);
\r
3342 container = container.parentNode;
\r
3344 // Move the offset if we are setting the end or the position is after an element
\r
3345 if (!start || index > 0)
\r
3348 // Calculate offset within text node
\r
3349 if (position > 0 || index == 0) {
\r
3350 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
\r
3351 offset = checkRng.text.length;
\r
3353 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
\r
3354 offset = container.nodeValue.length - checkRng.text.length;
\r
3357 dom.remove(marker);
\r
3360 domRange[start ? 'setStart' : 'setEnd'](container, offset);
\r
3363 // Find start point
\r
3364 findEndPoint(true);
\r
3366 // Find end point if needed
\r
3373 this.addRange = function(rng) {
\r
3374 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
\r
3376 function setEndPoint(start) {
\r
3377 var container, offset, marker, tmpRng, nodes;
\r
3379 marker = dom.create('a');
\r
3380 container = start ? startContainer : endContainer;
\r
3381 offset = start ? startOffset : endOffset;
\r
3382 tmpRng = ieRng.duplicate();
\r
3384 if (container == doc) {
\r
3389 if (container.nodeType == 3) {
\r
3390 container.parentNode.insertBefore(marker, container);
\r
3391 tmpRng.moveToElementText(marker);
\r
3392 tmpRng.moveStart('character', offset);
\r
3393 dom.remove(marker);
\r
3394 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
\r
3396 nodes = container.childNodes;
\r
3398 if (nodes.length) {
\r
3399 if (offset >= nodes.length) {
\r
3400 dom.insertAfter(marker, nodes[nodes.length - 1]);
\r
3402 container.insertBefore(marker, nodes[offset]);
\r
3405 tmpRng.moveToElementText(marker);
\r
3407 // Empty node selection for example <div>|</div>
\r
3408 marker = doc.createTextNode(invisibleChar);
\r
3409 container.appendChild(marker);
\r
3410 tmpRng.moveToElementText(marker.parentNode);
\r
3411 tmpRng.collapse(TRUE);
\r
3414 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
\r
3415 dom.remove(marker);
\r
3419 // Destroy cached range
\r
3422 // Setup some shorter versions
\r
3423 startContainer = rng.startContainer;
\r
3424 startOffset = rng.startOffset;
\r
3425 endContainer = rng.endContainer;
\r
3426 endOffset = rng.endOffset;
\r
3427 ieRng = body.createTextRange();
\r
3429 // If single element selection then try making a control selection out of it
\r
3430 if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
\r
3431 if (startOffset == endOffset - 1) {
\r
3433 ctrlRng = body.createControlRange();
\r
3434 ctrlRng.addElement(startContainer.childNodes[startOffset]);
\r
3436 ctrlRng.scrollIntoView();
\r
3444 // Set start/end point of selection
\r
3445 setEndPoint(true);
\r
3448 // Select the new range and scroll it into view
\r
3450 ieRng.scrollIntoView();
\r
3453 this.getRangeAt = function() {
\r
3454 // Setup new range if the cache is empty
\r
3455 if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
\r
3456 range = getRange();
\r
3458 // Store away text range for next call
\r
3459 lastIERng = selection.getRng();
\r
3462 // IE will say that the range is equal then produce an invalid argument exception
\r
3463 // if you perform specific operations in a keyup event. For example Ctrl+Del.
\r
3464 // This hack will invalidate the range cache if the exception occurs
\r
3466 range.startContainer.nextSibling;
\r
3468 range = getRange();
\r
3472 // Return cached range
\r
3476 this.destroy = function() {
\r
3477 // Destroy cached range and last IE range to avoid memory leaks
\r
3478 lastIERng = range = null;
\r
3481 // 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
3482 if (selection.dom.boxModel) {
\r
3484 var doc = dom.doc, body = doc.body, started, startRng;
\r
3486 // Make HTML element unselectable since we are going to handle selection by hand
\r
3487 doc.documentElement.unselectable = TRUE;
\r
3489 // Return range from point or null if it failed
\r
3490 function rngFromPoint(x, y) {
\r
3491 var rng = body.createTextRange();
\r
3494 rng.moveToPoint(x, y);
\r
3496 // IE sometimes throws and exception, so lets just ignore it
\r
3503 // Fires while the selection is changing
\r
3504 function selectionChange(e) {
\r
3507 // Check if the button is down or not
\r
3509 // Create range from mouse position
\r
3510 pointRng = rngFromPoint(e.x, e.y);
\r
3513 // Check if pointRange is before/after selection then change the endPoint
\r
3514 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
\r
3515 pointRng.setEndPoint('StartToStart', startRng);
\r
3517 pointRng.setEndPoint('EndToEnd', startRng);
\r
3519 pointRng.select();
\r
3525 // Removes listeners
\r
3526 function endSelection() {
\r
3527 dom.unbind(doc, 'mouseup', endSelection);
\r
3528 dom.unbind(doc, 'mousemove', selectionChange);
\r
3532 // Detect when user selects outside BODY
\r
3533 dom.bind(doc, 'mousedown', function(e) {
\r
3534 if (e.target.nodeName === 'HTML') {
\r
3540 // Setup start position
\r
3541 startRng = rngFromPoint(e.x, e.y);
\r
3543 // Listen for selection change events
\r
3544 dom.bind(doc, 'mouseup', endSelection);
\r
3545 dom.bind(doc, 'mousemove', selectionChange);
\r
3547 startRng.select();
\r
3555 // Expose the selection object
\r
3556 tinymce.dom.TridentSelection = Selection;
\r
3561 * Sizzle CSS Selector Engine - v1.0
\r
3562 * Copyright 2009, The Dojo Foundation
\r
3563 * Released under the MIT, BSD, and GPL Licenses.
\r
3564 * More information: http://sizzlejs.com/
\r
3568 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
\r
3570 toString = Object.prototype.toString,
\r
3571 hasDuplicate = false,
\r
3572 baseHasDuplicate = true;
\r
3574 // Here we check if the JavaScript engine is using some sort of
\r
3575 // optimization where it does not always call our comparision
\r
3576 // function. If that is the case, discard the hasDuplicate value.
\r
3577 // Thus far that includes Google Chrome.
\r
3578 [0, 0].sort(function(){
\r
3579 baseHasDuplicate = false;
\r
3583 var Sizzle = function(selector, context, results, seed) {
\r
3584 results = results || [];
\r
3585 context = context || document;
\r
3587 var origContext = context;
\r
3589 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
\r
3593 if ( !selector || typeof selector !== "string" ) {
\r
3597 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
\r
3598 soFar = selector, ret, cur, pop, i;
\r
3600 // Reset the position of the chunker regexp (start from head)
\r
3603 m = chunker.exec(soFar);
\r
3608 parts.push( m[1] );
\r
3617 if ( parts.length > 1 && origPOS.exec( selector ) ) {
\r
3618 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
\r
3619 set = posProcess( parts[0] + parts[1], context );
\r
3621 set = Expr.relative[ parts[0] ] ?
\r
3623 Sizzle( parts.shift(), context );
\r
3625 while ( parts.length ) {
\r
3626 selector = parts.shift();
\r
3628 if ( Expr.relative[ selector ] ) {
\r
3629 selector += parts.shift();
\r
3632 set = posProcess( selector, set );
\r
3636 // Take a shortcut and set the context if the root selector is an ID
\r
3637 // (but not if it'll be faster if the inner selector is an ID)
\r
3638 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
\r
3639 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
\r
3640 ret = Sizzle.find( parts.shift(), context, contextXML );
\r
3641 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
\r
3646 { expr: parts.pop(), set: makeArray(seed) } :
\r
3647 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
\r
3648 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
\r
3650 if ( parts.length > 0 ) {
\r
3651 checkSet = makeArray(set);
\r
3656 while ( parts.length ) {
\r
3657 cur = parts.pop();
\r
3660 if ( !Expr.relative[ cur ] ) {
\r
3663 pop = parts.pop();
\r
3666 if ( pop == null ) {
\r
3670 Expr.relative[ cur ]( checkSet, pop, contextXML );
\r
3673 checkSet = parts = [];
\r
3677 if ( !checkSet ) {
\r
3681 if ( !checkSet ) {
\r
3682 Sizzle.error( cur || selector );
\r
3685 if ( toString.call(checkSet) === "[object Array]" ) {
\r
3687 results.push.apply( results, checkSet );
\r
3688 } else if ( context && context.nodeType === 1 ) {
\r
3689 for ( i = 0; checkSet[i] != null; i++ ) {
\r
3690 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
\r
3691 results.push( set[i] );
\r
3695 for ( i = 0; checkSet[i] != null; i++ ) {
\r
3696 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
\r
3697 results.push( set[i] );
\r
3702 makeArray( checkSet, results );
\r
3706 Sizzle( extra, origContext, results, seed );
\r
3707 Sizzle.uniqueSort( results );
\r
3713 Sizzle.uniqueSort = function(results){
\r
3714 if ( sortOrder ) {
\r
3715 hasDuplicate = baseHasDuplicate;
\r
3716 results.sort(sortOrder);
\r
3718 if ( hasDuplicate ) {
\r
3719 for ( var i = 1; i < results.length; i++ ) {
\r
3720 if ( results[i] === results[i-1] ) {
\r
3721 results.splice(i--, 1);
\r
3730 Sizzle.matches = function(expr, set){
\r
3731 return Sizzle(expr, null, null, set);
\r
3734 Sizzle.find = function(expr, context, isXML){
\r
3741 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
\r
3742 var type = Expr.order[i], match;
\r
3744 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
\r
3745 var left = match[1];
\r
3746 match.splice(1,1);
\r
3748 if ( left.substr( left.length - 1 ) !== "\\" ) {
\r
3749 match[1] = (match[1] || "").replace(/\\/g, "");
\r
3750 set = Expr.find[ type ]( match, context, isXML );
\r
3751 if ( set != null ) {
\r
3752 expr = expr.replace( Expr.match[ type ], "" );
\r
3760 set = context.getElementsByTagName("*");
\r
3763 return {set: set, expr: expr};
\r
3766 Sizzle.filter = function(expr, set, inplace, not){
\r
3767 var old = expr, result = [], curLoop = set, match, anyFound,
\r
3768 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
\r
3770 while ( expr && set.length ) {
\r
3771 for ( var type in Expr.filter ) {
\r
3772 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
\r
3773 var filter = Expr.filter[ type ], found, item, left = match[1];
\r
3776 match.splice(1,1);
\r
3778 if ( left.substr( left.length - 1 ) === "\\" ) {
\r
3782 if ( curLoop === result ) {
\r
3786 if ( Expr.preFilter[ type ] ) {
\r
3787 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
\r
3790 anyFound = found = true;
\r
3791 } else if ( match === true ) {
\r
3797 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
\r
3799 found = filter( item, match, i, curLoop );
\r
3800 var pass = not ^ !!found;
\r
3802 if ( inplace && found != null ) {
\r
3806 curLoop[i] = false;
\r
3808 } else if ( pass ) {
\r
3809 result.push( item );
\r
3816 if ( found !== undefined ) {
\r
3821 expr = expr.replace( Expr.match[ type ], "" );
\r
3823 if ( !anyFound ) {
\r
3832 // Improper expression
\r
3833 if ( expr === old ) {
\r
3834 if ( anyFound == null ) {
\r
3835 Sizzle.error( expr );
\r
3847 Sizzle.error = function( msg ) {
\r
3848 throw "Syntax error, unrecognized expression: " + msg;
\r
3851 var Expr = Sizzle.selectors = {
\r
3852 order: [ "ID", "NAME", "TAG" ],
\r
3854 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
\r
3855 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
\r
3856 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
\r
3857 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
\r
3858 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
\r
3859 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
\r
3860 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
\r
3861 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
\r
3865 "class": "className",
\r
3869 href: function(elem){
\r
3870 return elem.getAttribute("href");
\r
3874 "+": function(checkSet, part){
\r
3875 var isPartStr = typeof part === "string",
\r
3876 isTag = isPartStr && !/\W/.test(part),
\r
3877 isPartStrNotTag = isPartStr && !isTag;
\r
3880 part = part.toLowerCase();
\r
3883 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
\r
3884 if ( (elem = checkSet[i]) ) {
\r
3885 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
\r
3887 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
\r
3893 if ( isPartStrNotTag ) {
\r
3894 Sizzle.filter( part, checkSet, true );
\r
3897 ">": function(checkSet, part){
\r
3898 var isPartStr = typeof part === "string",
\r
3899 elem, i = 0, l = checkSet.length;
\r
3901 if ( isPartStr && !/\W/.test(part) ) {
\r
3902 part = part.toLowerCase();
\r
3904 for ( ; i < l; i++ ) {
\r
3905 elem = checkSet[i];
\r
3907 var parent = elem.parentNode;
\r
3908 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
\r
3912 for ( ; i < l; i++ ) {
\r
3913 elem = checkSet[i];
\r
3915 checkSet[i] = isPartStr ?
\r
3917 elem.parentNode === part;
\r
3921 if ( isPartStr ) {
\r
3922 Sizzle.filter( part, checkSet, true );
\r
3926 "": function(checkSet, part, isXML){
\r
3927 var doneName = done++, checkFn = dirCheck, nodeCheck;
\r
3929 if ( typeof part === "string" && !/\W/.test(part) ) {
\r
3930 part = part.toLowerCase();
\r
3932 checkFn = dirNodeCheck;
\r
3935 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
\r
3937 "~": function(checkSet, part, isXML){
\r
3938 var doneName = done++, checkFn = dirCheck, nodeCheck;
\r
3940 if ( typeof part === "string" && !/\W/.test(part) ) {
\r
3941 part = part.toLowerCase();
\r
3943 checkFn = dirNodeCheck;
\r
3946 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
\r
3950 ID: function(match, context, isXML){
\r
3951 if ( typeof context.getElementById !== "undefined" && !isXML ) {
\r
3952 var m = context.getElementById(match[1]);
\r
3953 return m ? [m] : [];
\r
3956 NAME: function(match, context){
\r
3957 if ( typeof context.getElementsByName !== "undefined" ) {
\r
3958 var ret = [], results = context.getElementsByName(match[1]);
\r
3960 for ( var i = 0, l = results.length; i < l; i++ ) {
\r
3961 if ( results[i].getAttribute("name") === match[1] ) {
\r
3962 ret.push( results[i] );
\r
3966 return ret.length === 0 ? null : ret;
\r
3969 TAG: function(match, context){
\r
3970 return context.getElementsByTagName(match[1]);
\r
3974 CLASS: function(match, curLoop, inplace, result, not, isXML){
\r
3975 match = " " + match[1].replace(/\\/g, "") + " ";
\r
3981 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
\r
3983 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
\r
3985 result.push( elem );
\r
3987 } else if ( inplace ) {
\r
3988 curLoop[i] = false;
\r
3995 ID: function(match){
\r
3996 return match[1].replace(/\\/g, "");
\r
3998 TAG: function(match, curLoop){
\r
3999 return match[1].toLowerCase();
\r
4001 CHILD: function(match){
\r
4002 if ( match[1] === "nth" ) {
\r
4003 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
\r
4004 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
\r
4005 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
\r
4006 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
\r
4008 // calculate the numbers (first)n+(last) including if they are negative
\r
4009 match[2] = (test[1] + (test[2] || 1)) - 0;
\r
4010 match[3] = test[3] - 0;
\r
4013 // TODO: Move to normal caching system
\r
4014 match[0] = done++;
\r
4018 ATTR: function(match, curLoop, inplace, result, not, isXML){
\r
4019 var name = match[1].replace(/\\/g, "");
\r
4021 if ( !isXML && Expr.attrMap[name] ) {
\r
4022 match[1] = Expr.attrMap[name];
\r
4025 if ( match[2] === "~=" ) {
\r
4026 match[4] = " " + match[4] + " ";
\r
4031 PSEUDO: function(match, curLoop, inplace, result, not){
\r
4032 if ( match[1] === "not" ) {
\r
4033 // If we're dealing with a complex expression, or a simple one
\r
4034 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
\r
4035 match[3] = Sizzle(match[3], null, null, curLoop);
\r
4037 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
\r
4039 result.push.apply( result, ret );
\r
4043 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
\r
4049 POS: function(match){
\r
4050 match.unshift( true );
\r
4055 enabled: function(elem){
\r
4056 return elem.disabled === false && elem.type !== "hidden";
\r
4058 disabled: function(elem){
\r
4059 return elem.disabled === true;
\r
4061 checked: function(elem){
\r
4062 return elem.checked === true;
\r
4064 selected: function(elem){
\r
4065 // Accessing this property makes selected-by-default
\r
4066 // options in Safari work properly
\r
4067 elem.parentNode.selectedIndex;
\r
4068 return elem.selected === true;
\r
4070 parent: function(elem){
\r
4071 return !!elem.firstChild;
\r
4073 empty: function(elem){
\r
4074 return !elem.firstChild;
\r
4076 has: function(elem, i, match){
\r
4077 return !!Sizzle( match[3], elem ).length;
\r
4079 header: function(elem){
\r
4080 return (/h\d/i).test( elem.nodeName );
\r
4082 text: function(elem){
\r
4083 return "text" === elem.type;
\r
4085 radio: function(elem){
\r
4086 return "radio" === elem.type;
\r
4088 checkbox: function(elem){
\r
4089 return "checkbox" === elem.type;
\r
4091 file: function(elem){
\r
4092 return "file" === elem.type;
\r
4094 password: function(elem){
\r
4095 return "password" === elem.type;
\r
4097 submit: function(elem){
\r
4098 return "submit" === elem.type;
\r
4100 image: function(elem){
\r
4101 return "image" === elem.type;
\r
4103 reset: function(elem){
\r
4104 return "reset" === elem.type;
\r
4106 button: function(elem){
\r
4107 return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
\r
4109 input: function(elem){
\r
4110 return (/input|select|textarea|button/i).test(elem.nodeName);
\r
4114 first: function(elem, i){
\r
4117 last: function(elem, i, match, array){
\r
4118 return i === array.length - 1;
\r
4120 even: function(elem, i){
\r
4121 return i % 2 === 0;
\r
4123 odd: function(elem, i){
\r
4124 return i % 2 === 1;
\r
4126 lt: function(elem, i, match){
\r
4127 return i < match[3] - 0;
\r
4129 gt: function(elem, i, match){
\r
4130 return i > match[3] - 0;
\r
4132 nth: function(elem, i, match){
\r
4133 return match[3] - 0 === i;
\r
4135 eq: function(elem, i, match){
\r
4136 return match[3] - 0 === i;
\r
4140 PSEUDO: function(elem, match, i, array){
\r
4141 var name = match[1], filter = Expr.filters[ name ];
\r
4144 return filter( elem, i, match, array );
\r
4145 } else if ( name === "contains" ) {
\r
4146 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
\r
4147 } else if ( name === "not" ) {
\r
4148 var not = match[3];
\r
4150 for ( var j = 0, l = not.length; j < l; j++ ) {
\r
4151 if ( not[j] === elem ) {
\r
4158 Sizzle.error( "Syntax error, unrecognized expression: " + name );
\r
4161 CHILD: function(elem, match){
\r
4162 var type = match[1], node = elem;
\r
4166 while ( (node = node.previousSibling) ) {
\r
4167 if ( node.nodeType === 1 ) {
\r
4171 if ( type === "first" ) {
\r
4176 while ( (node = node.nextSibling) ) {
\r
4177 if ( node.nodeType === 1 ) {
\r
4183 var first = match[2], last = match[3];
\r
4185 if ( first === 1 && last === 0 ) {
\r
4189 var doneName = match[0],
\r
4190 parent = elem.parentNode;
\r
4192 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
\r
4194 for ( node = parent.firstChild; node; node = node.nextSibling ) {
\r
4195 if ( node.nodeType === 1 ) {
\r
4196 node.nodeIndex = ++count;
\r
4199 parent.sizcache = doneName;
\r
4202 var diff = elem.nodeIndex - last;
\r
4203 if ( first === 0 ) {
\r
4204 return diff === 0;
\r
4206 return ( diff % first === 0 && diff / first >= 0 );
\r
4210 ID: function(elem, match){
\r
4211 return elem.nodeType === 1 && elem.getAttribute("id") === match;
\r
4213 TAG: function(elem, match){
\r
4214 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
\r
4216 CLASS: function(elem, match){
\r
4217 return (" " + (elem.className || elem.getAttribute("class")) + " ")
\r
4218 .indexOf( match ) > -1;
\r
4220 ATTR: function(elem, match){
\r
4221 var name = match[1],
\r
4222 result = Expr.attrHandle[ name ] ?
\r
4223 Expr.attrHandle[ name ]( elem ) :
\r
4224 elem[ name ] != null ?
\r
4226 elem.getAttribute( name ),
\r
4227 value = result + "",
\r
4231 return result == null ?
\r
4236 value.indexOf(check) >= 0 :
\r
4238 (" " + value + " ").indexOf(check) >= 0 :
\r
4240 value && result !== false :
\r
4244 value.indexOf(check) === 0 :
\r
4246 value.substr(value.length - check.length) === check :
\r
4248 value === check || value.substr(0, check.length + 1) === check + "-" :
\r
4251 POS: function(elem, match, i, array){
\r
4252 var name = match[2], filter = Expr.setFilters[ name ];
\r
4255 return filter( elem, i, match, array );
\r
4261 var origPOS = Expr.match.POS,
\r
4262 fescape = function(all, num){
\r
4263 return "\\" + (num - 0 + 1);
\r
4266 for ( var type in Expr.match ) {
\r
4267 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
\r
4268 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
\r
4271 var makeArray = function(array, results) {
\r
4272 array = Array.prototype.slice.call( array, 0 );
\r
4275 results.push.apply( results, array );
\r
4282 // Perform a simple check to determine if the browser is capable of
\r
4283 // converting a NodeList to an array using builtin methods.
\r
4284 // Also verifies that the returned array holds DOM nodes
\r
4285 // (which is not the case in the Blackberry browser)
\r
4287 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
\r
4289 // Provide a fallback method if it does not work
\r
4291 makeArray = function(array, results) {
\r
4292 var ret = results || [], i = 0;
\r
4294 if ( toString.call(array) === "[object Array]" ) {
\r
4295 Array.prototype.push.apply( ret, array );
\r
4297 if ( typeof array.length === "number" ) {
\r
4298 for ( var l = array.length; i < l; i++ ) {
\r
4299 ret.push( array[i] );
\r
4302 for ( ; array[i]; i++ ) {
\r
4303 ret.push( array[i] );
\r
4314 if ( document.documentElement.compareDocumentPosition ) {
\r
4315 sortOrder = function( a, b ) {
\r
4316 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
\r
4318 hasDuplicate = true;
\r
4320 return a.compareDocumentPosition ? -1 : 1;
\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 if ( !a.sourceIndex || !b.sourceIndex ) {
\r
4333 hasDuplicate = true;
\r
4335 return a.sourceIndex ? -1 : 1;
\r
4338 var ret = a.sourceIndex - b.sourceIndex;
\r
4339 if ( ret === 0 ) {
\r
4340 hasDuplicate = true;
\r
4344 } else if ( document.createRange ) {
\r
4345 sortOrder = function( a, b ) {
\r
4346 if ( !a.ownerDocument || !b.ownerDocument ) {
\r
4348 hasDuplicate = true;
\r
4350 return a.ownerDocument ? -1 : 1;
\r
4353 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
\r
4354 aRange.setStart(a, 0);
\r
4355 aRange.setEnd(a, 0);
\r
4356 bRange.setStart(b, 0);
\r
4357 bRange.setEnd(b, 0);
\r
4358 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
\r
4359 if ( ret === 0 ) {
\r
4360 hasDuplicate = true;
\r
4366 // Utility function for retreiving the text value of an array of DOM nodes
\r
4367 Sizzle.getText = function( elems ) {
\r
4368 var ret = "", elem;
\r
4370 for ( var i = 0; elems[i]; i++ ) {
\r
4373 // Get the text from text nodes and CDATA nodes
\r
4374 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
\r
4375 ret += elem.nodeValue;
\r
4377 // Traverse everything else, except comment nodes
\r
4378 } else if ( elem.nodeType !== 8 ) {
\r
4379 ret += Sizzle.getText( elem.childNodes );
\r
4386 // Check to see if the browser returns elements by name when
\r
4387 // querying by getElementById (and provide a workaround)
\r
4389 // We're going to inject a fake input element with a specified name
\r
4390 var form = document.createElement("div"),
\r
4391 id = "script" + (new Date()).getTime();
\r
4392 form.innerHTML = "<a name='" + id + "'/>";
\r
4394 // Inject it into the root element, check its status, and remove it quickly
\r
4395 var root = document.documentElement;
\r
4396 root.insertBefore( form, root.firstChild );
\r
4398 // The workaround has to do additional checks after a getElementById
\r
4399 // Which slows things down for other browsers (hence the branching)
\r
4400 if ( document.getElementById( id ) ) {
\r
4401 Expr.find.ID = function(match, context, isXML){
\r
4402 if ( typeof context.getElementById !== "undefined" && !isXML ) {
\r
4403 var m = context.getElementById(match[1]);
\r
4404 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
\r
4408 Expr.filter.ID = function(elem, match){
\r
4409 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
\r
4410 return elem.nodeType === 1 && node && node.nodeValue === match;
\r
4414 root.removeChild( form );
\r
4415 root = form = null; // release memory in IE
\r
4419 // Check to see if the browser returns only elements
\r
4420 // when doing getElementsByTagName("*")
\r
4422 // Create a fake element
\r
4423 var div = document.createElement("div");
\r
4424 div.appendChild( document.createComment("") );
\r
4426 // Make sure no comments are found
\r
4427 if ( div.getElementsByTagName("*").length > 0 ) {
\r
4428 Expr.find.TAG = function(match, context){
\r
4429 var results = context.getElementsByTagName(match[1]);
\r
4431 // Filter out possible comments
\r
4432 if ( match[1] === "*" ) {
\r
4435 for ( var i = 0; results[i]; i++ ) {
\r
4436 if ( results[i].nodeType === 1 ) {
\r
4437 tmp.push( results[i] );
\r
4448 // Check to see if an attribute returns normalized href attributes
\r
4449 div.innerHTML = "<a href='#'></a>";
\r
4450 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
\r
4451 div.firstChild.getAttribute("href") !== "#" ) {
\r
4452 Expr.attrHandle.href = function(elem){
\r
4453 return elem.getAttribute("href", 2);
\r
4457 div = null; // release memory in IE
\r
4460 if ( document.querySelectorAll ) {
\r
4462 var oldSizzle = Sizzle, div = document.createElement("div");
\r
4463 div.innerHTML = "<p class='TEST'></p>";
\r
4465 // Safari can't handle uppercase or unicode characters when
\r
4466 // in quirks mode.
\r
4467 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
\r
4471 Sizzle = function(query, context, extra, seed){
\r
4472 context = context || document;
\r
4474 // Only use querySelectorAll on non-XML documents
\r
4475 // (ID selectors don't work in non-HTML documents)
\r
4476 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
\r
4478 return makeArray( context.querySelectorAll(query), extra );
\r
4482 return oldSizzle(query, context, extra, seed);
\r
4485 for ( var prop in oldSizzle ) {
\r
4486 Sizzle[ prop ] = oldSizzle[ prop ];
\r
4489 div = null; // release memory in IE
\r
4494 var div = document.createElement("div");
\r
4496 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
\r
4498 // Opera can't find a second classname (in 9.6)
\r
4499 // Also, make sure that getElementsByClassName actually exists
\r
4500 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
\r
4504 // Safari caches class attributes, doesn't catch changes (in 3.2)
\r
4505 div.lastChild.className = "e";
\r
4507 if ( div.getElementsByClassName("e").length === 1 ) {
\r
4511 Expr.order.splice(1, 0, "CLASS");
\r
4512 Expr.find.CLASS = function(match, context, isXML) {
\r
4513 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
\r
4514 return context.getElementsByClassName(match[1]);
\r
4518 div = null; // release memory in IE
\r
4521 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
\r
4522 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
4523 var elem = checkSet[i];
\r
4526 var match = false;
\r
4529 if ( elem.sizcache === doneName ) {
\r
4530 match = checkSet[elem.sizset];
\r
4534 if ( elem.nodeType === 1 && !isXML ){
\r
4535 elem.sizcache = doneName;
\r
4539 if ( elem.nodeName.toLowerCase() === cur ) {
\r
4547 checkSet[i] = match;
\r
4552 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
\r
4553 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
4554 var elem = checkSet[i];
\r
4557 var match = false;
\r
4560 if ( elem.sizcache === doneName ) {
\r
4561 match = checkSet[elem.sizset];
\r
4565 if ( elem.nodeType === 1 ) {
\r
4567 elem.sizcache = doneName;
\r
4570 if ( typeof cur !== "string" ) {
\r
4571 if ( elem === cur ) {
\r
4576 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
\r
4585 checkSet[i] = match;
\r
4590 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
\r
4591 return !!(a.compareDocumentPosition(b) & 16);
\r
4592 } : function(a, b){
\r
4593 return a !== b && (a.contains ? a.contains(b) : true);
\r
4596 Sizzle.isXML = function(elem){
\r
4597 // documentElement is verified for cases where it doesn't yet exist
\r
4598 // (such as loading iframes in IE - #4833)
\r
4599 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
\r
4600 return documentElement ? documentElement.nodeName !== "HTML" : false;
\r
4603 var posProcess = function(selector, context){
\r
4604 var tmpSet = [], later = "", match,
\r
4605 root = context.nodeType ? [context] : context;
\r
4607 // Position selectors must be done after the filter
\r
4608 // And so must :not(positional) so we move all PSEUDOs to the end
\r
4609 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
\r
4610 later += match[0];
\r
4611 selector = selector.replace( Expr.match.PSEUDO, "" );
\r
4614 selector = Expr.relative[selector] ? selector + "*" : selector;
\r
4616 for ( var i = 0, l = root.length; i < l; i++ ) {
\r
4617 Sizzle( selector, root[i], tmpSet );
\r
4620 return Sizzle.filter( later, tmpSet );
\r
4625 window.tinymce.dom.Sizzle = Sizzle;
\r
4630 (function(tinymce) {
\r
4632 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
\r
4634 tinymce.create('tinymce.dom.EventUtils', {
\r
4635 EventUtils : function() {
\r
4640 add : function(o, n, f, s) {
\r
4641 var cb, t = this, el = t.events, r;
\r
4643 if (n instanceof Array) {
\r
4646 each(n, function(n) {
\r
4647 r.push(t.add(o, n, f, s));
\r
4654 if (o && o.hasOwnProperty && o instanceof Array) {
\r
4657 each(o, function(o) {
\r
4659 r.push(t.add(o, n, f, s));
\r
4670 // Setup event callback
\r
4671 cb = function(e) {
\r
4672 // Is all events disabled
\r
4676 e = e || window.event;
\r
4678 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
\r
4681 e.target = e.srcElement;
\r
4683 // Patch in preventDefault, stopPropagation methods for W3C compatibility
\r
4684 tinymce.extend(e, t._stoppers);
\r
4690 return f.call(s, e);
\r
4693 if (n == 'unload') {
\r
4694 tinymce.unloads.unshift({func : cb});
\r
4698 if (n == 'init') {
\r
4707 // Store away listener reference
\r
4721 remove : function(o, n, f) {
\r
4722 var t = this, a = t.events, s = false, r;
\r
4725 if (o && o.hasOwnProperty && o instanceof Array) {
\r
4728 each(o, function(o) {
\r
4730 r.push(t.remove(o, n, f));
\r
4738 each(a, function(e, i) {
\r
4739 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
\r
4741 t._remove(o, n, e.cfunc);
\r
4750 clear : function(o) {
\r
4751 var t = this, a = t.events, i, e;
\r
4756 for (i = a.length - 1; i >= 0; i--) {
\r
4759 if (e.obj === o) {
\r
4760 t._remove(e.obj, e.name, e.cfunc);
\r
4761 e.obj = e.cfunc = null;
\r
4768 cancel : function(e) {
\r
4774 return this.prevent(e);
\r
4777 stop : function(e) {
\r
4778 if (e.stopPropagation)
\r
4779 e.stopPropagation();
\r
4781 e.cancelBubble = true;
\r
4786 prevent : function(e) {
\r
4787 if (e.preventDefault)
\r
4788 e.preventDefault();
\r
4790 e.returnValue = false;
\r
4795 destroy : function() {
\r
4798 each(t.events, function(e, i) {
\r
4799 t._remove(e.obj, e.name, e.cfunc);
\r
4800 e.obj = e.cfunc = null;
\r
4807 _add : function(o, n, f) {
\r
4808 if (o.attachEvent)
\r
4809 o.attachEvent('on' + n, f);
\r
4810 else if (o.addEventListener)
\r
4811 o.addEventListener(n, f, false);
\r
4816 _remove : function(o, n, f) {
\r
4819 if (o.detachEvent)
\r
4820 o.detachEvent('on' + n, f);
\r
4821 else if (o.removeEventListener)
\r
4822 o.removeEventListener(n, f, false);
\r
4824 o['on' + n] = null;
\r
4826 // Might fail with permission denined on IE so we just ignore that
\r
4831 _pageInit : function(win) {
\r
4834 // Keep it from running more than once
\r
4838 t.domLoaded = true;
\r
4840 each(t.inits, function(c) {
\r
4847 _wait : function(win) {
\r
4848 var t = this, doc = win.document;
\r
4850 // No need since the document is already loaded
\r
4851 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
\r
4857 if (doc.attachEvent) {
\r
4858 doc.attachEvent("onreadystatechange", function() {
\r
4859 if (doc.readyState === "complete") {
\r
4860 doc.detachEvent("onreadystatechange", arguments.callee);
\r
4865 if (doc.documentElement.doScroll && win == win.top) {
\r
4871 // If IE is used, use the trick by Diego Perini
\r
4872 // http://javascript.nwbox.com/IEContentLoaded/
\r
4873 doc.documentElement.doScroll("left");
\r
4875 setTimeout(arguments.callee, 0);
\r
4882 } else if (doc.addEventListener) {
\r
4883 t._add(win, 'DOMContentLoaded', function() {
\r
4888 t._add(win, 'load', function() {
\r
4894 preventDefault : function() {
\r
4895 this.returnValue = false;
\r
4898 stopPropagation : function() {
\r
4899 this.cancelBubble = true;
\r
4904 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
\r
4906 // Dispatch DOM content loaded event for IE and Safari
\r
4907 Event._wait(window);
\r
4909 tinymce.addUnload(function() {
\r
4914 (function(tinymce) {
\r
4915 tinymce.dom.Element = function(id, settings) {
\r
4916 var t = this, dom, el;
\r
4918 t.settings = settings = settings || {};
\r
4920 t.dom = dom = settings.dom || tinymce.DOM;
\r
4922 // Only IE leaks DOM references, this is a lot faster
\r
4923 if (!tinymce.isIE)
\r
4924 el = dom.get(t.id);
\r
4927 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
\r
4928 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
\r
4929 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
\r
4930 'isHidden,setHTML,get').split(/,/)
\r
4932 t[k] = function() {
\r
4935 for (i = 0; i < arguments.length; i++)
\r
4936 a.push(arguments[i]);
\r
4938 a = dom[k].apply(dom, a);
\r
4945 tinymce.extend(t, {
\r
4946 on : function(n, f, s) {
\r
4947 return tinymce.dom.Event.add(t.id, n, f, s);
\r
4950 getXY : function() {
\r
4952 x : parseInt(t.getStyle('left')),
\r
4953 y : parseInt(t.getStyle('top'))
\r
4957 getSize : function() {
\r
4958 var n = dom.get(t.id);
\r
4961 w : parseInt(t.getStyle('width') || n.clientWidth),
\r
4962 h : parseInt(t.getStyle('height') || n.clientHeight)
\r
4966 moveTo : function(x, y) {
\r
4967 t.setStyles({left : x, top : y});
\r
4970 moveBy : function(x, y) {
\r
4971 var p = t.getXY();
\r
4973 t.moveTo(p.x + x, p.y + y);
\r
4976 resizeTo : function(w, h) {
\r
4977 t.setStyles({width : w, height : h});
\r
4980 resizeBy : function(w, h) {
\r
4981 var s = t.getSize();
\r
4983 t.resizeTo(s.w + w, s.h + h);
\r
4986 update : function(k) {
\r
4989 if (tinymce.isIE6 && settings.blocker) {
\r
4993 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
\r
4996 // Remove blocker on remove
\r
4997 if (k == 'remove') {
\r
4998 dom.remove(t.blocker);
\r
5003 t.blocker = dom.uniqueId();
\r
5004 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
\r
5005 dom.setStyle(b, 'opacity', 0);
\r
5007 b = dom.get(t.blocker);
\r
5009 dom.setStyles(b, {
\r
5010 left : t.getStyle('left', 1),
\r
5011 top : t.getStyle('top', 1),
\r
5012 width : t.getStyle('width', 1),
\r
5013 height : t.getStyle('height', 1),
\r
5014 display : t.getStyle('display', 1),
\r
5015 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
\r
5023 (function(tinymce) {
\r
5024 function trimNl(s) {
\r
5025 return s.replace(/[\n\r]+/g, '');
\r
5029 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
\r
5031 tinymce.create('tinymce.dom.Selection', {
\r
5032 Selection : function(dom, win, serializer) {
\r
5037 t.serializer = serializer;
\r
5041 'onBeforeSetContent',
\r
5042 'onBeforeGetContent',
\r
5046 t[e] = new tinymce.util.Dispatcher(t);
\r
5049 // No W3C Range support
\r
5050 if (!t.win.getSelection)
\r
5051 t.tridentSel = new tinymce.dom.TridentSelection(t);
\r
5054 tinymce.addUnload(t.destroy, t);
\r
5057 getContent : function(s) {
\r
5058 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
\r
5063 s.format = s.format || 'html';
\r
5064 t.onBeforeGetContent.dispatch(t, s);
\r
5066 if (s.format == 'text')
\r
5067 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
\r
5069 if (r.cloneContents) {
\r
5070 n = r.cloneContents();
\r
5074 } else if (is(r.item) || is(r.htmlText))
\r
5075 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
\r
5077 e.innerHTML = r.toString();
\r
5079 // Keep whitespace before and after
\r
5080 if (/^\s/.test(e.innerHTML))
\r
5083 if (/\s+$/.test(e.innerHTML))
\r
5086 s.getInner = true;
\r
5088 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
\r
5089 t.onGetContent.dispatch(t, s);
\r
5094 setContent : function(h, s) {
\r
5095 var t = this, r = t.getRng(), c, d = t.win.document;
\r
5097 s = s || {format : 'html'};
\r
5099 h = s.content = t.dom.processHTML(h);
\r
5101 // Dispatch before set content event
\r
5102 t.onBeforeSetContent.dispatch(t, s);
\r
5105 if (r.insertNode) {
\r
5106 // Make caret marker since insertNode places the caret in the beginning of text after insert
\r
5107 h += '<span id="__caret">_</span>';
\r
5109 // Delete and insert new node
\r
5111 if (r.startContainer == d && r.endContainer == d) {
\r
5112 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
\r
5113 d.body.innerHTML = h;
\r
5115 r.deleteContents();
\r
5116 if (d.body.childNodes.length == 0) {
\r
5117 d.body.innerHTML = h;
\r
5119 r.insertNode(r.createContextualFragment(h));
\r
5123 // Move to caret marker
\r
5124 c = t.dom.get('__caret');
\r
5125 // Make sure we wrap it compleatly, Opera fails with a simple select call
\r
5126 r = d.createRange();
\r
5127 r.setStartBefore(c);
\r
5128 r.setEndBefore(c);
\r
5131 // Remove the caret position
\r
5132 t.dom.remove('__caret');
\r
5135 // Delete content and get caret text selection
\r
5136 d.execCommand('Delete', false, null);
\r
5143 // Dispatch set content event
\r
5144 t.onSetContent.dispatch(t, s);
\r
5147 getStart : function() {
\r
5148 var rng = this.getRng(), startElement, parentElement, checkRng, node;
\r
5150 if (rng.duplicate || rng.item) {
\r
5151 // Control selection, return first item
\r
5153 return rng.item(0);
\r
5155 // Get start element
\r
5156 checkRng = rng.duplicate();
\r
5157 checkRng.collapse(1);
\r
5158 startElement = checkRng.parentElement();
\r
5160 // Check if range parent is inside the start element, then return the inner parent element
\r
5161 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
\r
5162 parentElement = node = rng.parentElement();
\r
5163 while (node = node.parentNode) {
\r
5164 if (node == startElement) {
\r
5165 startElement = parentElement;
\r
5170 // If start element is body element try to move to the first child if it exists
\r
5171 if (startElement && startElement.nodeName == 'BODY')
\r
5172 return startElement.firstChild || startElement;
\r
5174 return startElement;
\r
5176 startElement = rng.startContainer;
\r
5178 if (startElement.nodeType == 1 && startElement.hasChildNodes())
\r
5179 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
\r
5181 if (startElement && startElement.nodeType == 3)
\r
5182 return startElement.parentNode;
\r
5184 return startElement;
\r
5188 getEnd : function() {
\r
5189 var t = this, r = t.getRng(), e, eo;
\r
5191 if (r.duplicate || r.item) {
\r
5195 r = r.duplicate();
\r
5197 e = r.parentElement();
\r
5199 if (e && e.nodeName == 'BODY')
\r
5200 return e.lastChild || e;
\r
5204 e = r.endContainer;
\r
5207 if (e.nodeType == 1 && e.hasChildNodes())
\r
5208 e = e.childNodes[eo > 0 ? eo - 1 : eo];
\r
5210 if (e && e.nodeType == 3)
\r
5211 return e.parentNode;
\r
5217 getBookmark : function(type, normalized) {
\r
5218 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
\r
5220 function findIndex(name, element) {
\r
5223 each(dom.select(name), function(node, i) {
\r
5224 if (node == element)
\r
5232 function getLocation() {
\r
5233 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
\r
5235 function getPoint(rng, start) {
\r
5236 var container = rng[start ? 'startContainer' : 'endContainer'],
\r
5237 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
\r
5239 if (container.nodeType == 3) {
\r
5241 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
\r
5242 offset += node.nodeValue.length;
\r
5245 point.push(offset);
\r
5247 childNodes = container.childNodes;
\r
5249 if (offset >= childNodes.length && childNodes.length) {
\r
5251 offset = Math.max(0, childNodes.length - 1);
\r
5254 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
\r
5257 for (; container && container != root; container = container.parentNode)
\r
5258 point.push(t.dom.nodeIndex(container, normalized));
\r
5263 bookmark.start = getPoint(rng, true);
\r
5265 if (!t.isCollapsed())
\r
5266 bookmark.end = getPoint(rng);
\r
5271 return getLocation();
\r
5274 // Handle simple range
\r
5276 return {rng : t.getRng()};
\r
5279 id = dom.uniqueId();
\r
5280 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
\r
5281 styles = 'overflow:hidden;line-height:0px';
\r
5283 // Explorer method
\r
5284 if (rng.duplicate || rng.item) {
\r
5287 rng2 = rng.duplicate();
\r
5289 // Insert start marker
\r
5291 rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
\r
5293 // Insert end marker
\r
5295 rng2.collapse(false);
\r
5296 rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
\r
5299 // Control selection
\r
5300 element = rng.item(0);
\r
5301 name = element.nodeName;
\r
5303 return {name : name, index : findIndex(name, element)};
\r
5306 element = t.getNode();
\r
5307 name = element.nodeName;
\r
5308 if (name == 'IMG')
\r
5309 return {name : name, index : findIndex(name, element)};
\r
5312 rng2 = rng.cloneRange();
\r
5314 // Insert end marker
\r
5316 rng2.collapse(false);
\r
5317 rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
\r
5320 rng.collapse(true);
\r
5321 rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
\r
5324 t.moveToBookmark({id : id, keep : 1});
\r
5329 moveToBookmark : function(bookmark) {
\r
5330 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
\r
5332 // Clear selection cache
\r
5334 t.tridentSel.destroy();
\r
5337 if (bookmark.start) {
\r
5338 rng = dom.createRng();
\r
5339 root = dom.getRoot();
\r
5341 function setEndPoint(start) {
\r
5342 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
\r
5345 // Find container node
\r
5346 for (node = root, i = point.length - 1; i >= 1; i--) {
\r
5347 children = node.childNodes;
\r
5349 if (children.length)
\r
5350 node = children[point[i]];
\r
5353 // Set offset within container node
\r
5355 rng.setStart(node, point[0]);
\r
5357 rng.setEnd(node, point[0]);
\r
5361 setEndPoint(true);
\r
5365 } else if (bookmark.id) {
\r
5366 function restoreEndPoint(suffix) {
\r
5367 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
\r
5370 node = marker.parentNode;
\r
5372 if (suffix == 'start') {
\r
5374 idx = dom.nodeIndex(marker);
\r
5376 node = marker.firstChild;
\r
5380 startContainer = endContainer = node;
\r
5381 startOffset = endOffset = idx;
\r
5384 idx = dom.nodeIndex(marker);
\r
5386 node = marker.firstChild;
\r
5390 endContainer = node;
\r
5395 prev = marker.previousSibling;
\r
5396 next = marker.nextSibling;
\r
5398 // Remove all marker text nodes
\r
5399 each(tinymce.grep(marker.childNodes), function(node) {
\r
5400 if (node.nodeType == 3)
\r
5401 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
\r
5404 // Remove marker but keep children if for example contents where inserted into the marker
\r
5405 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
\r
5406 while (marker = dom.get(bookmark.id + '_' + suffix))
\r
5407 dom.remove(marker, 1);
\r
5409 // If siblings are text nodes then merge them
\r
5410 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
\r
5411 idx = prev.nodeValue.length;
\r
5412 prev.appendData(next.nodeValue);
\r
5415 if (suffix == 'start') {
\r
5416 startContainer = endContainer = prev;
\r
5417 startOffset = endOffset = idx;
\r
5419 endContainer = prev;
\r
5427 function addBogus(node) {
\r
5428 // Adds a bogus BR element for empty block elements
\r
5429 // on non IE browsers just to have a place to put the caret
\r
5430 if (!isIE && dom.isBlock(node) && !node.innerHTML)
\r
5431 node.innerHTML = '<br _mce_bogus="1" />';
\r
5436 // Restore start/end points
\r
5437 restoreEndPoint('start');
\r
5438 restoreEndPoint('end');
\r
5440 rng = dom.createRng();
\r
5441 rng.setStart(addBogus(startContainer), startOffset);
\r
5442 rng.setEnd(addBogus(endContainer), endOffset);
\r
5444 } else if (bookmark.name) {
\r
5445 t.select(dom.select(bookmark.name)[bookmark.index]);
\r
5446 } else if (bookmark.rng)
\r
5447 t.setRng(bookmark.rng);
\r
5451 select : function(node, content) {
\r
5452 var t = this, dom = t.dom, rng = dom.createRng(), idx;
\r
5454 idx = dom.nodeIndex(node);
\r
5455 rng.setStart(node.parentNode, idx);
\r
5456 rng.setEnd(node.parentNode, idx + 1);
\r
5458 // Find first/last text node or BR element
\r
5460 function setPoint(node, start) {
\r
5461 var walker = new tinymce.dom.TreeWalker(node, node);
\r
5465 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
\r
5467 rng.setStart(node, 0);
\r
5469 rng.setEnd(node, node.nodeValue.length);
\r
5475 if (node.nodeName == 'BR') {
\r
5477 rng.setStartBefore(node);
\r
5479 rng.setEndBefore(node);
\r
5483 } while (node = (start ? walker.next() : walker.prev()));
\r
5486 setPoint(node, 1);
\r
5495 isCollapsed : function() {
\r
5496 var t = this, r = t.getRng(), s = t.getSel();
\r
5501 if (r.compareEndPoints)
\r
5502 return r.compareEndPoints('StartToEnd', r) === 0;
\r
5504 return !s || r.collapsed;
\r
5507 collapse : function(b) {
\r
5508 var t = this, r = t.getRng(), n;
\r
5510 // Control range on IE
\r
5513 r = this.win.document.body.createTextRange();
\r
5514 r.moveToElementText(n);
\r
5521 getSel : function() {
\r
5522 var t = this, w = this.win;
\r
5524 return w.getSelection ? w.getSelection() : w.document.selection;
\r
5527 getRng : function(w3c) {
\r
5528 var t = this, s, r;
\r
5530 // Found tridentSel object then we need to use that one
\r
5531 if (w3c && t.tridentSel)
\r
5532 return t.tridentSel.getRangeAt(0);
\r
5535 if (s = t.getSel())
\r
5536 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
\r
5538 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
\r
5541 // No range found then create an empty one
\r
5542 // This can occur when the editor is placed in a hidden container element on Gecko
\r
5543 // Or on IE when there was an exception
\r
5545 r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange();
\r
5547 if (t.selectedRange && t.explicitRange) {
\r
5548 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
\r
5549 // Safari, Opera and Chrome only ever select text which causes the range to change.
\r
5550 // This lets us use the originally set range if the selection hasn't been changed by the user.
\r
5551 r = t.explicitRange;
\r
5553 t.selectedRange = null;
\r
5554 t.explicitRange = null;
\r
5560 setRng : function(r) {
\r
5563 if (!t.tridentSel) {
\r
5567 t.explicitRange = r;
\r
5568 s.removeAllRanges();
\r
5570 t.selectedRange = s.getRangeAt(0);
\r
5574 if (r.cloneRange) {
\r
5575 t.tridentSel.addRange(r);
\r
5579 // Is IE specific range
\r
5583 // Needed for some odd IE bug #1843306
\r
5588 setNode : function(n) {
\r
5591 t.setContent(t.dom.getOuterHTML(n));
\r
5596 getNode : function() {
\r
5597 var t = this, rng = t.getRng(), sel = t.getSel(), elm;
\r
5599 if (rng.setStart) {
\r
5600 // Range maybe lost after the editor is made visible again
\r
5602 return t.dom.getRoot();
\r
5604 elm = rng.commonAncestorContainer;
\r
5606 // Handle selection a image or other control like element such as anchors
\r
5607 if (!rng.collapsed) {
\r
5608 if (rng.startContainer == rng.endContainer) {
\r
5609 if (rng.startOffset - rng.endOffset < 2) {
\r
5610 if (rng.startContainer.hasChildNodes())
\r
5611 elm = rng.startContainer.childNodes[rng.startOffset];
\r
5615 // If the anchor node is a element instead of a text node then return this element
\r
5616 if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
\r
5617 return sel.anchorNode.childNodes[sel.anchorOffset];
\r
5620 if (elm && elm.nodeType == 3)
\r
5621 return elm.parentNode;
\r
5626 return rng.item ? rng.item(0) : rng.parentElement();
\r
5629 getSelectedBlocks : function(st, en) {
\r
5630 var t = this, dom = t.dom, sb, eb, n, bl = [];
\r
5632 sb = dom.getParent(st || t.getStart(), dom.isBlock);
\r
5633 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
\r
5638 if (sb && eb && sb != eb) {
\r
5641 while ((n = n.nextSibling) && n != eb) {
\r
5642 if (dom.isBlock(n))
\r
5647 if (eb && sb != eb)
\r
5653 destroy : function(s) {
\r
5659 t.tridentSel.destroy();
\r
5661 // Manual destroy then remove unload handler
\r
5663 tinymce.removeUnload(t.destroy);
\r
5668 (function(tinymce) {
\r
5669 tinymce.create('tinymce.dom.XMLWriter', {
\r
5672 XMLWriter : function(s) {
\r
5673 // Get XML document
\r
5674 function getXML() {
\r
5675 var i = document.implementation;
\r
5677 if (!i || !i.createDocument) {
\r
5679 try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
\r
5680 try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
\r
5682 return i.createDocument('', '', null);
\r
5685 this.doc = getXML();
\r
5687 // Since Opera and WebKit doesn't escape > into > we need to do it our self to normalize the output for all browsers
\r
5688 this.valid = tinymce.isOpera || tinymce.isWebKit;
\r
5693 reset : function() {
\r
5694 var t = this, d = t.doc;
\r
5697 d.removeChild(d.firstChild);
\r
5699 t.node = d.appendChild(d.createElement("html"));
\r
5702 writeStartElement : function(n) {
\r
5705 t.node = t.node.appendChild(t.doc.createElement(n));
\r
5708 writeAttribute : function(n, v) {
\r
5710 v = v.replace(/>/g, '%MCGT%');
\r
5712 this.node.setAttribute(n, v);
\r
5715 writeEndElement : function() {
\r
5716 this.node = this.node.parentNode;
\r
5719 writeFullEndElement : function() {
\r
5720 var t = this, n = t.node;
\r
5722 n.appendChild(t.doc.createTextNode(""));
\r
5723 t.node = n.parentNode;
\r
5726 writeText : function(v) {
\r
5728 v = v.replace(/>/g, '%MCGT%');
\r
5730 this.node.appendChild(this.doc.createTextNode(v));
\r
5733 writeCDATA : function(v) {
\r
5734 this.node.appendChild(this.doc.createCDATASection(v));
\r
5737 writeComment : function(v) {
\r
5738 // Fix for bug #2035694
\r
5740 v = v.replace(/^\-|\-$/g, ' ');
\r
5742 this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
\r
5745 getContent : function() {
\r
5748 h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
\r
5749 h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
\r
5750 h = h.replace(/ ?\/>/g, ' />');
\r
5753 h = h.replace(/\%MCGT%/g, '>');
\r
5760 (function(tinymce) {
\r
5761 tinymce.create('tinymce.dom.StringWriter', {
\r
5768 StringWriter : function(s) {
\r
5769 this.settings = tinymce.extend({
\r
5770 indent_char : ' ',
\r
5777 reset : function() {
\r
5784 writeStartElement : function(n) {
\r
5785 this._writeAttributesEnd();
\r
5786 this.writeRaw('<' + n);
\r
5787 this.tags.push(n);
\r
5788 this.inAttr = true;
\r
5790 this.elementCount = this.count;
\r
5793 writeAttribute : function(n, v) {
\r
5796 t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
\r
5799 writeEndElement : function() {
\r
5802 if (this.tags.length > 0) {
\r
5803 n = this.tags.pop();
\r
5805 if (this._writeAttributesEnd(1))
\r
5806 this.writeRaw('</' + n + '>');
\r
5808 if (this.settings.indentation > 0)
\r
5809 this.writeRaw('\n');
\r
5813 writeFullEndElement : function() {
\r
5814 if (this.tags.length > 0) {
\r
5815 this._writeAttributesEnd();
\r
5816 this.writeRaw('</' + this.tags.pop() + '>');
\r
5818 if (this.settings.indentation > 0)
\r
5819 this.writeRaw('\n');
\r
5823 writeText : function(v) {
\r
5824 this._writeAttributesEnd();
\r
5825 this.writeRaw(this.encode(v));
\r
5829 writeCDATA : function(v) {
\r
5830 this._writeAttributesEnd();
\r
5831 this.writeRaw('<![CDATA[' + v + ']]>');
\r
5835 writeComment : function(v) {
\r
5836 this._writeAttributesEnd();
\r
5837 this.writeRaw('<!-- ' + v + '-->');
\r
5841 writeRaw : function(v) {
\r
5845 encode : function(s) {
\r
5846 return s.replace(/[<>&"]/g, function(v) {
\r
5865 getContent : function() {
\r
5869 _writeAttributesEnd : function(s) {
\r
5873 this.inAttr = false;
\r
5875 if (s && this.elementCount == this.count) {
\r
5876 this.writeRaw(' />');
\r
5880 this.writeRaw('>');
\r
5887 (function(tinymce) {
\r
5889 var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
\r
5891 function wildcardToRE(s) {
\r
5892 return s.replace(/([?+*])/g, '.$1');
\r
5895 tinymce.create('tinymce.dom.Serializer', {
\r
5896 Serializer : function(s) {
\r
5900 t.onPreProcess = new Dispatcher(t);
\r
5901 t.onPostProcess = new Dispatcher(t);
\r
5904 t.writer = new tinymce.dom.XMLWriter();
\r
5906 // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
\r
5907 t.writer = new tinymce.dom.StringWriter();
\r
5910 // Default settings
\r
5911 t.settings = s = extend({
\r
5912 dom : tinymce.DOM,
\r
5916 invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
\r
5917 closed : /^(br|hr|input|meta|img|link|param|area)$/,
\r
5918 entity_encoding : 'named',
\r
5919 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
5920 valid_elements : '*[*]',
\r
5921 extended_valid_elements : 0,
\r
5922 invalid_elements : 0,
\r
5923 fix_table_elements : 1,
\r
5924 fix_list_elements : true,
\r
5925 fix_content_duplication : true,
\r
5926 convert_fonts_to_spans : false,
\r
5927 font_size_classes : 0,
\r
5928 apply_source_formatting : 0,
\r
5929 indent_mode : 'simple',
\r
5930 indent_char : '\t',
\r
5931 indent_levels : 1,
\r
5932 remove_linebreaks : 1,
\r
5933 remove_redundant_brs : 1,
\r
5934 element_format : 'xhtml'
\r
5938 t.schema = s.schema;
\r
5940 // Use raw entities if no entities are defined
\r
5941 if (s.entity_encoding == 'named' && !s.entities)
\r
5942 s.entity_encoding = 'raw';
\r
5944 if (s.remove_redundant_brs) {
\r
5945 t.onPostProcess.add(function(se, o) {
\r
5946 // Remove single BR at end of block elements since they get rendered
\r
5947 o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
\r
5948 // Check if it's a single element
\r
5949 if (/^<br \/>\s*<\//.test(a))
\r
5950 return '</' + c + '>';
\r
5957 // Remove XHTML element endings i.e. produce crap :) XHTML is better
\r
5958 if (s.element_format == 'html') {
\r
5959 t.onPostProcess.add(function(se, o) {
\r
5960 o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
\r
5964 if (s.fix_list_elements) {
\r
5965 t.onPreProcess.add(function(se, o) {
\r
5966 var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
\r
5968 function prevNode(e, n) {
\r
5969 var a = n.split(','), i;
\r
5971 while ((e = e.previousSibling) != null) {
\r
5972 for (i=0; i<a.length; i++) {
\r
5973 if (e.nodeName == a[i])
\r
5981 for (x=0; x<a.length; x++) {
\r
5982 nl = t.dom.select(a[x], o.node);
\r
5984 for (i=0; i<nl.length; i++) {
\r
5988 if (r.test(p.nodeName)) {
\r
5989 np = prevNode(n, 'LI');
\r
5992 np = t.dom.create('li');
\r
5993 np.innerHTML = ' ';
\r
5994 np.appendChild(n);
\r
5995 p.insertBefore(np, p.firstChild);
\r
5997 np.appendChild(n);
\r
6004 if (s.fix_table_elements) {
\r
6005 t.onPreProcess.add(function(se, o) {
\r
6006 // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
\r
6007 // so Opera users with an older version will have to live with less compaible output not much we can do here
\r
6008 if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
\r
6009 each(t.dom.select('p table', o.node).reverse(), function(n) {
\r
6010 var parent = t.dom.getParent(n.parentNode, 'table,p');
\r
6012 if (parent.nodeName != 'TABLE') {
\r
6014 t.dom.split(parent, n);
\r
6016 // IE can sometimes fire an unknown runtime error so we just ignore it
\r
6025 setEntities : function(s) {
\r
6026 var t = this, a, i, l = {}, v;
\r
6028 // No need to setup more than once
\r
6029 if (t.entityLookup)
\r
6032 // Build regex and lookup array
\r
6034 for (i = 0; i < a.length; i += 2) {
\r
6037 // Don't add default & " etc.
\r
6038 if (v == 34 || v == 38 || v == 60 || v == 62)
\r
6041 l[String.fromCharCode(a[i])] = a[i + 1];
\r
6043 v = parseInt(a[i]).toString(16);
\r
6046 t.entityLookup = l;
\r
6049 setRules : function(s) {
\r
6055 t.validElements = {};
\r
6057 return t.addRules(s);
\r
6060 addRules : function(s) {
\r
6068 each(s.split(','), function(s) {
\r
6069 var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
\r
6071 // Extend with default rules
\r
6073 at = tinymce.extend([], dr.attribs);
\r
6075 // Parse attributes
\r
6076 if (p.length > 1) {
\r
6077 each(p[1].split('|'), function(s) {
\r
6082 // Parse attribute rule
\r
6083 s = s.replace(/::/g, '~');
\r
6084 s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
\r
6085 s[2] = s[2].replace(/~/g, ':');
\r
6087 // Add required attributes
\r
6088 if (s[1] == '!') {
\r
6093 // Remove inherited attributes
\r
6094 if (s[1] == '-') {
\r
6095 for (i = 0; i <at.length; i++) {
\r
6096 if (at[i].name == s[2]) {
\r
6104 // Add default attrib values
\r
6106 ar.defaultVal = s[4] || '';
\r
6109 // Add forced attrib values
\r
6111 ar.forcedVal = s[4];
\r
6114 // Add validation values
\r
6116 ar.validVals = s[4].split('?');
\r
6120 if (/[*.?]/.test(s[2])) {
\r
6122 ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
\r
6133 // Handle element names
\r
6134 each(tn, function(s, i) {
\r
6135 var pr = s.charAt(0), x = 1, ru = {};
\r
6137 // Extend with default rule data
\r
6140 ru.noEmpty = dr.noEmpty;
\r
6143 ru.fullEnd = dr.fullEnd;
\r
6146 ru.padd = dr.padd;
\r
6149 // Handle prefixes
\r
6152 ru.noEmpty = true;
\r
6156 ru.fullEnd = true;
\r
6167 tn[i] = s = s.substring(x);
\r
6168 t.validElements[s] = 1;
\r
6170 // Add element name or element regex
\r
6171 if (/[*.?]/.test(tn[0])) {
\r
6172 ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
\r
6173 t.wildRules = t.wildRules || {};
\r
6174 t.wildRules.push(ru);
\r
6178 // Store away default rule
\r
6188 ru.requiredAttribs = ra;
\r
6191 // Build valid attributes regexp
\r
6193 each(va, function(v) {
\r
6197 s += '(' + wildcardToRE(v) + ')';
\r
6199 ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
\r
6200 ru.wildAttribs = wat;
\r
6205 // Build valid elements regexp
\r
6207 each(t.validElements, function(v, k) {
\r
6214 t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
\r
6216 //console.debug(t.validElementsRE.toString());
\r
6217 //console.dir(t.rules);
\r
6218 //console.dir(t.wildRules);
\r
6221 findRule : function(n) {
\r
6222 var t = this, rl = t.rules, i, r;
\r
6233 for (i = 0; i < rl.length; i++) {
\r
6234 if (rl[i].nameRE.test(n))
\r
6241 findAttribRule : function(ru, n) {
\r
6242 var i, wa = ru.wildAttribs;
\r
6244 for (i = 0; i < wa.length; i++) {
\r
6245 if (wa[i].nameRE.test(n))
\r
6252 serialize : function(n, o) {
\r
6253 var h, t = this, doc, oldDoc, impl, selected;
\r
6257 o.format = o.format || 'html';
\r
6260 // IE looses the selected attribute on option elements so we need to store it
\r
6261 // See: http://support.microsoft.com/kb/829907
\r
6264 each(n.getElementsByTagName('option'), function(n) {
\r
6265 var v = t.dom.getAttrib(n, 'selected');
\r
6267 selected.push(v ? v : null);
\r
6271 n = n.cloneNode(true);
\r
6273 // IE looses the selected attribute on option elements so we need to restore it
\r
6275 each(n.getElementsByTagName('option'), function(n, i) {
\r
6276 t.dom.setAttrib(n, 'selected', selected[i]);
\r
6280 // Nodes needs to be attached to something in WebKit/Opera
\r
6281 // Older builds of Opera crashes if you attach the node to an document created dynamically
\r
6282 // and since we can't feature detect a crash we need to sniff the acutal build number
\r
6283 // This fix will make DOM ranges and make Sizzle happy!
\r
6284 impl = n.ownerDocument.implementation;
\r
6285 if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
\r
6286 // Create an empty HTML document
\r
6287 doc = impl.createHTMLDocument("");
\r
6289 // Add the element or it's children if it's a body element to the new document
\r
6290 each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
\r
6291 doc.body.appendChild(doc.importNode(node, true));
\r
6294 // Grab first child or body element for serialization
\r
6295 if (n.nodeName != 'BODY')
\r
6296 n = doc.body.firstChild;
\r
6300 // set the new document in DOMUtils so createElement etc works
\r
6301 oldDoc = t.dom.doc;
\r
6305 t.key = '' + (parseInt(t.key) + 1);
\r
6308 if (!o.no_events) {
\r
6310 t.onPreProcess.dispatch(t, o);
\r
6313 // Serialize HTML DOM into a string
\r
6316 t._serializeNode(n, o.getInner);
\r
6319 o.content = t.writer.getContent();
\r
6321 // Restore the old document if it was changed
\r
6323 t.dom.doc = oldDoc;
\r
6326 t.onPostProcess.dispatch(t, o);
\r
6328 t._postProcess(o);
\r
6331 return tinymce.trim(o.content);
\r
6334 // Internal functions
\r
6336 _postProcess : function(o) {
\r
6337 var t = this, s = t.settings, h = o.content, sc = [], p;
\r
6339 if (o.format == 'html') {
\r
6340 // Protect some elements
\r
6344 {pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
\r
6345 {pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
\r
6346 {pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
\r
6347 {pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
\r
6348 {pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
\r
6355 if (s.entity_encoding !== 'raw')
\r
6358 // Use BR instead of padded P elements inside editor and use <p> </p> outside editor
\r
6360 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
\r
6362 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
\r
6364 // Since Gecko and Safari keeps whitespace in the DOM we need to
\r
6365 // remove it inorder to match other browsers. But I think Gecko and Safari is right.
\r
6366 // This process is only done when getting contents out from the editor.
\r
6368 // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
\r
6369 h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1> </p>' : '<p$1> </p>');
\r
6371 if (s.remove_linebreaks) {
\r
6372 h = h.replace(/\r?\n|\r/g, ' ');
\r
6373 h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
\r
6374 h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
\r
6375 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
6376 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
6377 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
6380 // Simple indentation
\r
6381 if (s.apply_source_formatting && s.indent_mode == 'simple') {
\r
6382 // Add line breaks before and after block elements
\r
6383 h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
\r
6384 h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
\r
6385 h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
\r
6386 h = h.replace(/\n\n/g, '\n');
\r
6390 h = t._unprotect(h, p);
\r
6392 // Restore CDATA sections
\r
6393 h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
\r
6395 // Restore the \u00a0 character if raw mode is enabled
\r
6396 if (s.entity_encoding == 'raw')
\r
6397 h = h.replace(/<p> <\/p>|<p([^>]+)> <\/p>/g, '<p$1>\u00a0</p>');
\r
6399 // Restore noscript elements
\r
6400 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
6401 return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
\r
6408 _serializeNode : function(n, inner) {
\r
6409 var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type, scopeName;
\r
6411 if (!s.node_filter || s.node_filter(n)) {
\r
6412 switch (n.nodeType) {
\r
6413 case 1: // Element
\r
6414 if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
\r
6417 iv = keep = false;
\r
6418 hc = n.hasChildNodes();
\r
6419 nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
\r
6421 // Get internal type
\r
6422 type = n.getAttribute('_mce_type');
\r
6424 if (!t._info.cleanup) {
\r
6431 // Add correct prefix on IE
\r
6433 scopeName = n.scopeName;
\r
6434 if (scopeName && scopeName !== 'HTML' && scopeName !== 'html')
\r
6435 nn = scopeName + ':' + nn;
\r
6438 // Remove mce prefix on IE needed for the abbr element
\r
6439 if (nn.indexOf('mce:') === 0)
\r
6440 nn = nn.substring(4);
\r
6444 if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
\r
6451 // Fix IE content duplication (DOM can have multiple copies of the same node)
\r
6452 if (s.fix_content_duplication) {
\r
6453 if (n._mce_serialized == t.key)
\r
6456 n._mce_serialized = t.key;
\r
6459 // IE sometimes adds a / infront of the node name
\r
6460 if (nn.charAt(0) == '/')
\r
6461 nn = nn.substring(1);
\r
6462 } else if (isGecko) {
\r
6463 // Ignore br elements
\r
6464 if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
\r
6468 // Check if valid child
\r
6469 if (s.validate_children) {
\r
6470 if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
\r
6475 t.elementName = nn;
\r
6478 ru = t.findRule(nn);
\r
6480 // No valid rule for this element could be found then skip it
\r
6486 nn = ru.name || nn;
\r
6487 closed = s.closed.test(nn);
\r
6489 // Skip empty nodes or empty node name in IE
\r
6490 if ((!hc && ru.noEmpty) || (isIE && !nn)) {
\r
6496 if (ru.requiredAttribs) {
\r
6497 a = ru.requiredAttribs;
\r
6499 for (i = a.length - 1; i >= 0; i--) {
\r
6500 if (this.dom.getAttrib(n, a[i]) !== '')
\r
6504 // None of the required was there
\r
6511 w.writeStartElement(nn);
\r
6513 // Add ordered attributes
\r
6515 for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
\r
6517 v = t._getAttrib(n, a);
\r
6520 w.writeAttribute(a.name, v);
\r
6524 // Add wild attributes
\r
6525 if (ru.validAttribsRE) {
\r
6526 at = t.dom.getAttribs(n);
\r
6527 for (i=at.length-1; i>-1; i--) {
\r
6530 if (no.specified) {
\r
6531 a = no.nodeName.toLowerCase();
\r
6533 if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
\r
6536 ar = t.findAttribRule(ru, a);
\r
6537 v = t._getAttrib(n, ar, a);
\r
6540 w.writeAttribute(a, v);
\r
6545 // Keep type attribute
\r
6547 w.writeAttribute('_mce_type', type);
\r
6549 // Write text from script
\r
6550 if (nn === 'script' && tinymce.trim(n.innerHTML)) {
\r
6551 w.writeText('// '); // Padd it with a comment so it will parse on older browsers
\r
6552 w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
\r
6557 // Padd empty nodes with a
\r
6559 // If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
\r
6560 if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
\r
6561 if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
\r
6562 w.writeText('\u00a0');
\r
6564 w.writeText('\u00a0'); // No children then padd it
\r
6570 // Check if valid child
\r
6571 if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
\r
6574 return w.writeText(n.nodeValue);
\r
6577 return w.writeCDATA(n.nodeValue);
\r
6579 case 8: // Comment
\r
6580 return w.writeComment(n.nodeValue);
\r
6582 } else if (n.nodeType == 1)
\r
6583 hc = n.hasChildNodes();
\r
6585 if (hc && !closed) {
\r
6586 cn = n.firstChild;
\r
6589 t._serializeNode(cn);
\r
6590 t.elementName = nn;
\r
6591 cn = cn.nextSibling;
\r
6595 // Write element end
\r
6598 w.writeFullEndElement();
\r
6600 w.writeEndElement();
\r
6604 _protect : function(o) {
\r
6607 o.items = o.items || [];
\r
6610 return s.replace(/[\r\n\\]/g, function(c) {
\r
6613 else if (c === '\\')
\r
6621 return s.replace(/\\[\\rn]/g, function(c) {
\r
6624 else if (c === '\\\\')
\r
6631 each(o.patterns, function(p) {
\r
6632 o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
\r
6639 return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
\r
6646 _unprotect : function(h, o) {
\r
6647 h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
\r
6648 return o.items[parseInt(b)];
\r
6656 _encode : function(h) {
\r
6657 var t = this, s = t.settings, l;
\r
6660 if (s.entity_encoding !== 'raw') {
\r
6661 if (s.entity_encoding.indexOf('named') != -1) {
\r
6662 t.setEntities(s.entities);
\r
6663 l = t.entityLookup;
\r
6665 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
6669 a = '&' + v + ';';
\r
6675 if (s.entity_encoding.indexOf('numeric') != -1) {
\r
6676 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
6677 return '&#' + a.charCodeAt(0) + ';';
\r
6685 _setup : function() {
\r
6686 var t = this, s = this.settings;
\r
6693 t.setRules(s.valid_elements);
\r
6694 t.addRules(s.extended_valid_elements);
\r
6696 if (s.invalid_elements)
\r
6697 t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
\r
6699 if (s.attrib_value_filter)
\r
6700 t.attribValueFilter = s.attribValueFilter;
\r
6703 _getAttrib : function(n, a, na) {
\r
6706 na = na || a.name;
\r
6708 if (a.forcedVal && (v = a.forcedVal)) {
\r
6709 if (v === '{$uid}')
\r
6710 return this.dom.uniqueId();
\r
6715 v = this.dom.getAttrib(n, na);
\r
6720 // Whats the point? Remove usless attribute value
\r
6727 if (this.attribValueFilter)
\r
6728 v = this.attribValueFilter(na, v, n);
\r
6730 if (a.validVals) {
\r
6731 for (i = a.validVals.length - 1; i >= 0; i--) {
\r
6732 if (v == a.validVals[i])
\r
6740 if (v === '' && typeof(a.defaultVal) != 'undefined') {
\r
6743 if (v === '{$uid}')
\r
6744 return this.dom.uniqueId();
\r
6748 // Remove internal mceItemXX classes when content is extracted from editor
\r
6749 if (na == 'class' && this.processObj.get)
\r
6750 v = v.replace(/\s?mceItem\w+\s?/g, '');
\r
6762 (function(tinymce) {
\r
6763 tinymce.dom.ScriptLoader = function(settings) {
\r
6769 scriptLoadedCallbacks = {},
\r
6770 queueLoadedCallbacks = [],
\r
6774 function loadScript(url, callback) {
\r
6775 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
\r
6777 // Execute callback when script is loaded
\r
6782 elm.onreadystatechange = elm.onload = elm = null;
\r
6787 id = dom.uniqueId();
\r
6789 if (tinymce.isIE6) {
\r
6790 uri = new tinymce.util.URI(url);
\r
6793 // If script is from same domain and we
\r
6794 // use IE 6 then use XHR since it's more reliable
\r
6795 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
\r
6796 tinymce.util.XHR.send({
\r
6797 url : tinymce._addVer(uri.getURI()),
\r
6798 success : function(content) {
\r
6799 // Create new temp script element
\r
6800 var script = dom.create('script', {
\r
6801 type : 'text/javascript'
\r
6804 // Evaluate script in global scope
\r
6805 script.text = content;
\r
6806 document.getElementsByTagName('head')[0].appendChild(script);
\r
6807 dom.remove(script);
\r
6817 // Create new script element
\r
6818 elm = dom.create('script', {
\r
6820 type : 'text/javascript',
\r
6821 src : tinymce._addVer(url)
\r
6824 // Add onload and readystate listeners
\r
6825 elm.onload = done;
\r
6826 elm.onreadystatechange = function() {
\r
6827 var state = elm.readyState;
\r
6829 // Loaded state is passed on IE 6 however there
\r
6830 // are known issues with this method but we can't use
\r
6831 // XHR in a cross domain loading
\r
6832 if (state == 'complete' || state == 'loaded')
\r
6836 // Most browsers support this feature so we report errors
\r
6837 // for those at least to help users track their missing plugins etc
\r
6838 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
\r
6839 /*elm.onerror = function() {
\r
6840 alert('Failed to load: ' + url);
\r
6843 // Add script to document
\r
6844 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
\r
6847 this.isDone = function(url) {
\r
6848 return states[url] == LOADED;
\r
6851 this.markDone = function(url) {
\r
6852 states[url] = LOADED;
\r
6855 this.add = this.load = function(url, callback, scope) {
\r
6856 var item, state = states[url];
\r
6858 // Add url to load queue
\r
6859 if (state == undefined) {
\r
6861 states[url] = QUEUED;
\r
6865 // Store away callback for later execution
\r
6866 if (!scriptLoadedCallbacks[url])
\r
6867 scriptLoadedCallbacks[url] = [];
\r
6869 scriptLoadedCallbacks[url].push({
\r
6871 scope : scope || this
\r
6876 this.loadQueue = function(callback, scope) {
\r
6877 this.loadScripts(queue, callback, scope);
\r
6880 this.loadScripts = function(scripts, callback, scope) {
\r
6883 function execScriptLoadedCallbacks(url) {
\r
6884 // Execute URL callback functions
\r
6885 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
\r
6886 callback.func.call(callback.scope);
\r
6889 scriptLoadedCallbacks[url] = undefined;
\r
6892 queueLoadedCallbacks.push({
\r
6894 scope : scope || this
\r
6897 loadScripts = function() {
\r
6898 var loadingScripts = tinymce.grep(scripts);
\r
6900 // Current scripts has been handled
\r
6901 scripts.length = 0;
\r
6903 // Load scripts that needs to be loaded
\r
6904 tinymce.each(loadingScripts, function(url) {
\r
6905 // Script is already loaded then execute script callbacks directly
\r
6906 if (states[url] == LOADED) {
\r
6907 execScriptLoadedCallbacks(url);
\r
6911 // Is script not loading then start loading it
\r
6912 if (states[url] != LOADING) {
\r
6913 states[url] = LOADING;
\r
6916 loadScript(url, function() {
\r
6917 states[url] = LOADED;
\r
6920 execScriptLoadedCallbacks(url);
\r
6922 // Load more scripts if they where added by the recently loaded script
\r
6928 // No scripts are currently loading then execute all pending queue loaded callbacks
\r
6930 tinymce.each(queueLoadedCallbacks, function(callback) {
\r
6931 callback.func.call(callback.scope);
\r
6934 queueLoadedCallbacks.length = 0;
\r
6942 // Global script loader
\r
6943 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
\r
6946 tinymce.dom.TreeWalker = function(start_node, root_node) {
\r
6947 var node = start_node;
\r
6949 function findSibling(node, start_name, sibling_name, shallow) {
\r
6950 var sibling, parent;
\r
6953 // Walk into nodes if it has a start
\r
6954 if (!shallow && node[start_name])
\r
6955 return node[start_name];
\r
6957 // Return the sibling if it has one
\r
6958 if (node != root_node) {
\r
6959 sibling = node[sibling_name];
\r
6963 // Walk up the parents to look for siblings
\r
6964 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
\r
6965 sibling = parent[sibling_name];
\r
6973 this.current = function() {
\r
6977 this.next = function(shallow) {
\r
6978 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
\r
6981 this.prev = function(shallow) {
\r
6982 return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
\r
6987 var transitional = {};
\r
6989 function unpack(lookup, data) {
\r
6992 function replace(value) {
\r
6993 return value.replace(/[A-Z]+/g, function(key) {
\r
6994 return replace(lookup[key]);
\r
6999 for (key in lookup) {
\r
7000 if (lookup.hasOwnProperty(key))
\r
7001 lookup[key] = replace(lookup[key]);
\r
7004 // Unpack and parse data into object map
\r
7005 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
\r
7008 children = children.split(/\|/);
\r
7010 for (i = children.length - 1; i >= 0; i--)
\r
7011 map[children[i]] = 1;
\r
7013 transitional[name] = map;
\r
7017 // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
\r
7018 // we will later include the attributes here and use it as a default for valid elements but it
\r
7019 // requires us to rewrite the serializer engine
\r
7021 Z : '#|H|K|N|O|P',
\r
7022 Y : '#|X|form|R|Q',
\r
7023 X : 'p|T|div|U|W|isindex|fieldset|table',
\r
7024 W : 'pre|hr|blockquote|address|center|noframes',
\r
7025 U : 'ul|ol|dl|menu|dir',
\r
7026 ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
\r
7027 T : 'h1|h2|h3|h4|h5|h6',
\r
7030 ZA : '#|a|G|J|M|O|P',
\r
7031 R : '#|a|H|K|N|O',
\r
7033 P : 'ins|del|script',
\r
7034 O : 'input|select|textarea|label|button',
\r
7036 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
\r
7039 J : 'tt|i|b|u|s|strike',
\r
7040 I : 'big|small|font|basefont',
\r
7042 G : 'br|span|bdo',
\r
7043 F : 'object|applet|img|map|iframe'
\r
7046 'object[#|param|X|form|a|H|K|N|O|Q]' +
\r
7053 'applet[#|param|X|form|a|H|K|N|O|Q]' +
\r
7056 'map[X|form|Q|area]' +
\r
7058 'iframe[#|X|form|a|H|K|N|O|Q]' +
\r
7084 'select[optgroup|option]' +
\r
7085 'optgroup[option]' +
\r
7089 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
\r
7091 'ins[#|X|form|a|H|K|N|O|Q]' +
\r
7093 'del[#|X|form|a|H|K|N|O|Q]' +
\r
7095 'div[#|X|form|a|H|K|N|O|Q]' +
\r
7097 'li[#|X|form|a|H|K|N|O|Q]' +
\r
7101 'dd[#|X|form|a|H|K|N|O|Q]' +
\r
7106 'blockquote[#|X|form|a|H|K|N|O|Q]' +
\r
7108 'center[#|X|form|a|H|K|N|O|Q]' +
\r
7109 'noframes[#|X|form|a|H|K|N|O|Q]' +
\r
7111 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
\r
7113 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
\r
7116 'colgroup[col]' +
\r
7119 'th[#|X|form|a|H|K|N|O|Q]' +
\r
7120 'form[#|X|a|H|K|N|O|Q]' +
\r
7121 'noscript[#|X|form|a|H|K|N|O|Q]' +
\r
7122 'td[#|X|form|a|H|K|N|O|Q]' +
\r
7127 'body[#|X|form|a|H|K|N|O|Q]'
\r
7130 tinymce.dom.Schema = function() {
\r
7131 var t = this, elements = transitional;
\r
7133 t.isValid = function(name, child_name) {
\r
7134 var element = elements[name];
\r
7136 return !!(element && (!child_name || element[child_name]));
\r
7140 (function(tinymce) {
\r
7141 tinymce.dom.RangeUtils = function(dom) {
\r
7142 var INVISIBLE_CHAR = '\uFEFF';
\r
7144 this.walk = function(rng, callback) {
\r
7145 var startContainer = rng.startContainer,
\r
7146 startOffset = rng.startOffset,
\r
7147 endContainer = rng.endContainer,
\r
7148 endOffset = rng.endOffset,
\r
7149 ancestor, startPoint,
\r
7150 endPoint, node, parent, siblings, nodes;
\r
7152 // Handle table cell selection the table plugin enables
\r
7153 // you to fake select table cells and perform formatting actions on them
\r
7154 nodes = dom.select('td.mceSelected,th.mceSelected');
\r
7155 if (nodes.length > 0) {
\r
7156 tinymce.each(nodes, function(node) {
\r
7163 function collectSiblings(node, name, end_node) {
\r
7164 var siblings = [];
\r
7166 for (; node && node != end_node; node = node[name])
\r
7167 siblings.push(node);
\r
7172 function findEndPoint(node, root) {
\r
7174 if (node.parentNode == root)
\r
7177 node = node.parentNode;
\r
7181 function walkBoundary(start_node, end_node, next) {
\r
7182 var siblingName = next ? 'nextSibling' : 'previousSibling';
\r
7184 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
\r
7185 parent = node.parentNode;
\r
7186 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
\r
7188 if (siblings.length) {
\r
7190 siblings.reverse();
\r
7192 callback(siblings);
\r
7197 // If index based start position then resolve it
\r
7198 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
\r
7199 startContainer = startContainer.childNodes[startOffset];
\r
7201 // If index based end position then resolve it
\r
7202 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
\r
7203 endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
\r
7205 // Find common ancestor and end points
\r
7206 ancestor = dom.findCommonAncestor(startContainer, endContainer);
\r
7209 if (startContainer == endContainer)
\r
7210 return callback([startContainer]);
\r
7212 // Process left side
\r
7213 for (node = startContainer; node; node = node.parentNode) {
\r
7214 if (node == endContainer)
\r
7215 return walkBoundary(startContainer, ancestor, true);
\r
7217 if (node == ancestor)
\r
7221 // Process right side
\r
7222 for (node = endContainer; node; node = node.parentNode) {
\r
7223 if (node == startContainer)
\r
7224 return walkBoundary(endContainer, ancestor);
\r
7226 if (node == ancestor)
\r
7230 // Find start/end point
\r
7231 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
\r
7232 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
\r
7235 walkBoundary(startContainer, startPoint, true);
\r
7237 // Walk the middle from start to end point
\r
7238 siblings = collectSiblings(
\r
7239 startPoint == startContainer ? startPoint : startPoint.nextSibling,
\r
7241 endPoint == endContainer ? endPoint.nextSibling : endPoint
\r
7244 if (siblings.length)
\r
7245 callback(siblings);
\r
7247 // Walk right leaf
\r
7248 walkBoundary(endContainer, endPoint);
\r
7251 /* this.split = function(rng) {
\r
7252 var startContainer = rng.startContainer,
\r
7253 startOffset = rng.startOffset,
\r
7254 endContainer = rng.endContainer,
\r
7255 endOffset = rng.endOffset;
\r
7257 function splitText(node, offset) {
\r
7258 if (offset == node.nodeValue.length)
\r
7259 node.appendData(INVISIBLE_CHAR);
\r
7261 node = node.splitText(offset);
\r
7263 if (node.nodeValue === INVISIBLE_CHAR)
\r
7264 node.nodeValue = '';
\r
7269 // Handle single text node
\r
7270 if (startContainer == endContainer) {
\r
7271 if (startContainer.nodeType == 3) {
\r
7272 if (startOffset != 0)
\r
7273 startContainer = endContainer = splitText(startContainer, startOffset);
\r
7275 if (endOffset - startOffset != startContainer.nodeValue.length)
\r
7276 splitText(startContainer, endOffset - startOffset);
\r
7279 // Split startContainer text node if needed
\r
7280 if (startContainer.nodeType == 3 && startOffset != 0) {
\r
7281 startContainer = splitText(startContainer, startOffset);
\r
7285 // Split endContainer text node if needed
\r
7286 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
\r
7287 endContainer = splitText(endContainer, endOffset).previousSibling;
\r
7288 endOffset = endContainer.nodeValue.length;
\r
7293 startContainer : startContainer,
\r
7294 startOffset : startOffset,
\r
7295 endContainer : endContainer,
\r
7296 endOffset : endOffset
\r
7302 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
\r
7303 if (rng1 && rng2) {
\r
7304 // Compare native IE ranges
\r
7305 if (rng1.item || rng1.duplicate) {
\r
7306 // Both are control ranges and the selected element matches
\r
7307 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
\r
7310 // Both are text ranges and the range matches
\r
7311 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
\r
7314 // Compare w3c ranges
\r
7315 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
\r
7323 (function(tinymce) {
\r
7324 // Shorten class names
\r
7325 var DOM = tinymce.DOM, is = tinymce.is;
\r
7327 tinymce.create('tinymce.ui.Control', {
\r
7328 Control : function(id, s) {
\r
7330 this.settings = s = s || {};
\r
7331 this.rendered = false;
\r
7332 this.onRender = new tinymce.util.Dispatcher(this);
\r
7333 this.classPrefix = '';
\r
7334 this.scope = s.scope || this;
\r
7335 this.disabled = 0;
\r
7339 setDisabled : function(s) {
\r
7342 if (s != this.disabled) {
\r
7343 e = DOM.get(this.id);
\r
7345 // Add accessibility title for unavailable actions
\r
7346 if (e && this.settings.unavailable_prefix) {
\r
7348 this.prevTitle = e.title;
\r
7349 e.title = this.settings.unavailable_prefix + ": " + e.title;
\r
7351 e.title = this.prevTitle;
\r
7354 this.setState('Disabled', s);
\r
7355 this.setState('Enabled', !s);
\r
7356 this.disabled = s;
\r
7360 isDisabled : function() {
\r
7361 return this.disabled;
\r
7364 setActive : function(s) {
\r
7365 if (s != this.active) {
\r
7366 this.setState('Active', s);
\r
7371 isActive : function() {
\r
7372 return this.active;
\r
7375 setState : function(c, s) {
\r
7376 var n = DOM.get(this.id);
\r
7378 c = this.classPrefix + c;
\r
7381 DOM.addClass(n, c);
\r
7383 DOM.removeClass(n, c);
\r
7386 isRendered : function() {
\r
7387 return this.rendered;
\r
7390 renderHTML : function() {
\r
7393 renderTo : function(n) {
\r
7394 DOM.setHTML(n, this.renderHTML());
\r
7397 postRender : function() {
\r
7400 // Set pending states
\r
7401 if (is(t.disabled)) {
\r
7407 if (is(t.active)) {
\r
7414 remove : function() {
\r
7415 DOM.remove(this.id);
\r
7419 destroy : function() {
\r
7420 tinymce.dom.Event.clear(this.id);
\r
7424 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
\r
7425 Container : function(id, s) {
\r
7426 this.parent(id, s);
\r
7428 this.controls = [];
\r
7433 add : function(c) {
\r
7434 this.lookup[c.id] = c;
\r
7435 this.controls.push(c);
\r
7440 get : function(n) {
\r
7441 return this.lookup[n];
\r
7446 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
\r
7447 Separator : function(id, s) {
\r
7448 this.parent(id, s);
\r
7449 this.classPrefix = 'mceSeparator';
\r
7452 renderHTML : function() {
\r
7453 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
\r
7457 (function(tinymce) {
\r
7458 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
7460 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
\r
7461 MenuItem : function(id, s) {
\r
7462 this.parent(id, s);
\r
7463 this.classPrefix = 'mceMenuItem';
\r
7466 setSelected : function(s) {
\r
7467 this.setState('Selected', s);
\r
7468 this.selected = s;
\r
7471 isSelected : function() {
\r
7472 return this.selected;
\r
7475 postRender : function() {
\r
7480 // Set pending state
\r
7481 if (is(t.selected))
\r
7482 t.setSelected(t.selected);
\r
7487 (function(tinymce) {
\r
7488 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
7490 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
\r
7491 Menu : function(id, s) {
\r
7496 t.collapsed = false;
\r
7498 t.onAddItem = new tinymce.util.Dispatcher(this);
\r
7501 expand : function(d) {
\r
7505 walk(t, function(o) {
\r
7511 t.collapsed = false;
\r
7514 collapse : function(d) {
\r
7518 walk(t, function(o) {
\r
7524 t.collapsed = true;
\r
7527 isCollapsed : function() {
\r
7528 return this.collapsed;
\r
7531 add : function(o) {
\r
7533 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
\r
7535 this.onAddItem.dispatch(this, o);
\r
7537 return this.items[o.id] = o;
\r
7540 addSeparator : function() {
\r
7541 return this.add({separator : true});
\r
7544 addMenu : function(o) {
\r
7546 o = this.createMenu(o);
\r
7550 return this.add(o);
\r
7553 hasMenus : function() {
\r
7554 return this.menuCount !== 0;
\r
7557 remove : function(o) {
\r
7558 delete this.items[o.id];
\r
7561 removeAll : function() {
\r
7564 walk(t, function(o) {
\r
7576 createMenu : function(o) {
\r
7577 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
\r
7579 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
\r
7585 (function(tinymce) {
\r
7586 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
\r
7588 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
\r
7589 DropMenu : function(id, s) {
\r
7591 s.container = s.container || DOM.doc.body;
\r
7592 s.offset_x = s.offset_x || 0;
\r
7593 s.offset_y = s.offset_y || 0;
\r
7594 s.vp_offset_x = s.vp_offset_x || 0;
\r
7595 s.vp_offset_y = s.vp_offset_y || 0;
\r
7597 if (is(s.icons) && !s.icons)
\r
7598 s['class'] += ' mceNoIcons';
\r
7600 this.parent(id, s);
\r
7601 this.onShowMenu = new tinymce.util.Dispatcher(this);
\r
7602 this.onHideMenu = new tinymce.util.Dispatcher(this);
\r
7603 this.classPrefix = 'mceMenu';
\r
7606 createMenu : function(s) {
\r
7607 var t = this, cs = t.settings, m;
\r
7609 s.container = s.container || cs.container;
\r
7611 s.constrain = s.constrain || cs.constrain;
\r
7612 s['class'] = s['class'] || cs['class'];
\r
7613 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
\r
7614 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
\r
7615 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
\r
7617 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
\r
7622 update : function() {
\r
7623 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
\r
7625 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
\r
7626 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
\r
7628 if (!DOM.boxModel)
\r
7629 t.element.setStyles({width : tw + 2, height : th + 2});
\r
7631 t.element.setStyles({width : tw, height : th});
\r
7634 DOM.setStyle(co, 'width', tw);
\r
7636 if (s.max_height) {
\r
7637 DOM.setStyle(co, 'height', th);
\r
7639 if (tb.clientHeight < s.max_height)
\r
7640 DOM.setStyle(co, 'overflow', 'hidden');
\r
7644 showMenu : function(x, y, px) {
\r
7645 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
\r
7649 if (t.isMenuVisible)
\r
7652 if (!t.rendered) {
\r
7653 co = DOM.add(t.settings.container, t.renderNode());
\r
7655 each(t.items, function(o) {
\r
7659 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
7661 co = DOM.get('menu_' + t.id);
\r
7663 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
\r
7664 if (!tinymce.isOpera)
\r
7665 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
\r
7670 x += s.offset_x || 0;
\r
7671 y += s.offset_y || 0;
\r
7675 // Move inside viewport if not submenu
\r
7676 if (s.constrain) {
\r
7677 w = co.clientWidth - ot;
\r
7678 h = co.clientHeight - ot;
\r
7682 if ((x + s.vp_offset_x + w) > mx)
\r
7683 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
\r
7685 if ((y + s.vp_offset_y + h) > my)
\r
7686 y = Math.max(0, (my - s.vp_offset_y) - h);
\r
7689 DOM.setStyles(co, {left : x , top : y});
\r
7690 t.element.update();
\r
7692 t.isMenuVisible = 1;
\r
7693 t.mouseClickFunc = Event.add(co, 'click', function(e) {
\r
7698 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
\r
7699 m = t.items[e.id];
\r
7701 if (m.isDisabled())
\r
7710 dm = dm.settings.parent;
\r
7713 if (m.settings.onclick)
\r
7714 m.settings.onclick(e);
\r
7716 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
7720 if (t.hasMenus()) {
\r
7721 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
\r
7725 if (e && (e = DOM.getParent(e, 'tr'))) {
\r
7726 m = t.items[e.id];
\r
7729 t.lastMenu.collapse(1);
\r
7731 if (m.isDisabled())
\r
7734 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
\r
7735 //p = DOM.getPos(s.container);
\r
7736 r = DOM.getRect(e);
\r
7737 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
\r
7739 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
\r
7745 t.onShowMenu.dispatch(t);
\r
7747 if (s.keyboard_focus) {
\r
7748 Event.add(co, 'keydown', t._keyHandler, t);
\r
7749 DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
\r
7754 hideMenu : function(c) {
\r
7755 var t = this, co = DOM.get('menu_' + t.id), e;
\r
7757 if (!t.isMenuVisible)
\r
7760 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
7761 Event.remove(co, 'click', t.mouseClickFunc);
\r
7762 Event.remove(co, 'keydown', t._keyHandler);
\r
7764 t.isMenuVisible = 0;
\r
7772 if (e = DOM.get(t.id))
\r
7773 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
\r
7775 t.onHideMenu.dispatch(t);
\r
7778 add : function(o) {
\r
7783 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
\r
7784 t._add(DOM.select('tbody', co)[0], o);
\r
7789 collapse : function(d) {
\r
7794 remove : function(o) {
\r
7798 return this.parent(o);
\r
7801 destroy : function() {
\r
7802 var t = this, co = DOM.get('menu_' + t.id);
\r
7804 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
7805 Event.remove(co, 'click', t.mouseClickFunc);
\r
7808 t.element.remove();
\r
7813 renderNode : function() {
\r
7814 var t = this, s = t.settings, n, tb, co, w;
\r
7816 w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
\r
7817 co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
\r
7818 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
7821 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
\r
7823 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
\r
7824 n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
\r
7825 tb = DOM.add(n, 'tbody');
\r
7827 each(t.items, function(o) {
\r
7831 t.rendered = true;
\r
7836 // Internal functions
\r
7838 _keyHandler : function(e) {
\r
7839 var t = this, kc = e.keyCode;
\r
7841 function focus(d) {
\r
7842 var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
\r
7852 focus(-1); // Select first link
\r
7863 return this.hideMenu();
\r
7867 _add : function(tb, o) {
\r
7868 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
\r
7870 if (s.separator) {
\r
7871 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
\r
7872 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
\r
7874 if (n = ro.previousSibling)
\r
7875 DOM.addClass(n, 'mceLast');
\r
7880 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
\r
7881 n = it = DOM.add(n, 'td');
\r
7882 n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
\r
7884 DOM.addClass(it, s['class']);
\r
7885 // n = DOM.add(n, 'span', {'class' : 'item'});
\r
7887 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
\r
7890 DOM.add(ic, 'img', {src : s.icon_src});
\r
7892 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
\r
7894 if (o.settings.style)
\r
7895 DOM.setAttrib(n, 'style', o.settings.style);
\r
7897 if (tb.childNodes.length == 1)
\r
7898 DOM.addClass(ro, 'mceFirst');
\r
7900 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
\r
7901 DOM.addClass(ro, 'mceFirst');
\r
7904 DOM.addClass(ro, cp + 'ItemSub');
\r
7906 if (n = ro.previousSibling)
\r
7907 DOM.removeClass(n, 'mceLast');
\r
7909 DOM.addClass(ro, 'mceLast');
\r
7913 (function(tinymce) {
\r
7914 var DOM = tinymce.DOM;
\r
7916 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
\r
7917 Button : function(id, s) {
\r
7918 this.parent(id, s);
\r
7919 this.classPrefix = 'mceButton';
\r
7922 renderHTML : function() {
\r
7923 var cp = this.classPrefix, s = this.settings, h, l;
\r
7925 l = DOM.encode(s.label || '');
\r
7926 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
7929 h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
\r
7931 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
\r
7936 postRender : function() {
\r
7937 var t = this, s = t.settings;
\r
7939 tinymce.dom.Event.add(t.id, 'click', function(e) {
\r
7940 if (!t.isDisabled())
\r
7941 return s.onclick.call(s.scope, e);
\r
7947 (function(tinymce) {
\r
7948 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
7950 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
\r
7951 ListBox : function(id, s) {
\r
7958 t.onChange = new Dispatcher(t);
\r
7960 t.onPostRender = new Dispatcher(t);
\r
7962 t.onAdd = new Dispatcher(t);
\r
7964 t.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
7966 t.classPrefix = 'mceListBox';
\r
7969 select : function(va) {
\r
7970 var t = this, fv, f;
\r
7972 if (va == undefined)
\r
7973 return t.selectByIndex(-1);
\r
7975 // Is string or number make function selector
\r
7976 if (va && va.call)
\r
7984 // Do we need to do something?
\r
7985 if (va != t.selectedValue) {
\r
7987 each(t.items, function(o, i) {
\r
7990 t.selectByIndex(i);
\r
7996 t.selectByIndex(-1);
\r
8000 selectByIndex : function(idx) {
\r
8001 var t = this, e, o;
\r
8003 if (idx != t.selectedIndex) {
\r
8004 e = DOM.get(t.id + '_text');
\r
8008 t.selectedValue = o.value;
\r
8009 t.selectedIndex = idx;
\r
8010 DOM.setHTML(e, DOM.encode(o.title));
\r
8011 DOM.removeClass(e, 'mceTitle');
\r
8013 DOM.setHTML(e, DOM.encode(t.settings.title));
\r
8014 DOM.addClass(e, 'mceTitle');
\r
8015 t.selectedValue = t.selectedIndex = null;
\r
8022 add : function(n, v, o) {
\r
8026 o = tinymce.extend(o, {
\r
8032 t.onAdd.dispatch(t, o);
\r
8035 getLength : function() {
\r
8036 return this.items.length;
\r
8039 renderHTML : function() {
\r
8040 var h = '', t = this, s = t.settings, cp = t.classPrefix;
\r
8042 h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
\r
8043 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
8044 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
8045 h += '</tr></tbody></table>';
\r
8050 showMenu : function() {
\r
8051 var t = this, p1, p2, e = DOM.get(this.id), m;
\r
8053 if (t.isDisabled() || t.items.length == 0)
\r
8056 if (t.menu && t.menu.isMenuVisible)
\r
8057 return t.hideMenu();
\r
8059 if (!t.isMenuRendered) {
\r
8061 t.isMenuRendered = true;
\r
8064 p1 = DOM.getPos(this.settings.menu_container);
\r
8065 p2 = DOM.getPos(e);
\r
8068 m.settings.offset_x = p2.x;
\r
8069 m.settings.offset_y = p2.y;
\r
8070 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
\r
8074 m.items[t.oldID].setSelected(0);
\r
8076 each(t.items, function(o) {
\r
8077 if (o.value === t.selectedValue) {
\r
8078 m.items[o.id].setSelected(1);
\r
8083 m.showMenu(0, e.clientHeight);
\r
8085 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8086 DOM.addClass(t.id, t.classPrefix + 'Selected');
\r
8088 //DOM.get(t.id + '_text').focus();
\r
8091 hideMenu : function(e) {
\r
8094 if (t.menu && t.menu.isMenuVisible) {
\r
8095 // Prevent double toogles by canceling the mouse click event to the button
\r
8096 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
\r
8099 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
8100 DOM.removeClass(t.id, t.classPrefix + 'Selected');
\r
8101 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8102 t.menu.hideMenu();
\r
8107 renderMenu : function() {
\r
8110 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
8112 'class' : t.classPrefix + 'Menu mceNoIcons',
\r
8117 m.onHideMenu.add(t.hideMenu, t);
\r
8120 title : t.settings.title,
\r
8121 'class' : 'mceMenuItemTitle',
\r
8122 onclick : function() {
\r
8123 if (t.settings.onselect('') !== false)
\r
8124 t.select(''); // Must be runned after
\r
8128 each(t.items, function(o) {
\r
8129 // No value then treat it as a title
\r
8130 if (o.value === undefined) {
\r
8133 'class' : 'mceMenuItemTitle',
\r
8134 onclick : function() {
\r
8135 if (t.settings.onselect('') !== false)
\r
8136 t.select(''); // Must be runned after
\r
8140 o.id = DOM.uniqueId();
\r
8141 o.onclick = function() {
\r
8142 if (t.settings.onselect(o.value) !== false)
\r
8143 t.select(o.value); // Must be runned after
\r
8150 t.onRenderMenu.dispatch(t, m);
\r
8154 postRender : function() {
\r
8155 var t = this, cp = t.classPrefix;
\r
8157 Event.add(t.id, 'click', t.showMenu, t);
\r
8158 Event.add(t.id + '_text', 'focus', function() {
\r
8159 if (!t._focused) {
\r
8160 t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
\r
8161 var idx = -1, v, kc = e.keyCode;
\r
8163 // Find current index
\r
8164 each(t.items, function(v, i) {
\r
8165 if (t.selectedValue == v.value)
\r
8171 v = t.items[idx - 1];
\r
8172 else if (kc == 40)
\r
8173 v = t.items[idx + 1];
\r
8174 else if (kc == 13) {
\r
8175 // Fake select on enter
\r
8176 v = t.selectedValue;
\r
8177 t.selectedValue = null; // Needs to be null to fake change
\r
8178 t.settings.onselect(v);
\r
8179 return Event.cancel(e);
\r
8184 t.select(v.value);
\r
8191 Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
\r
8193 // Old IE doesn't have hover on all elements
\r
8194 if (tinymce.isIE6 || !DOM.boxModel) {
\r
8195 Event.add(t.id, 'mouseover', function() {
\r
8196 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
8197 DOM.addClass(t.id, cp + 'Hover');
\r
8200 Event.add(t.id, 'mouseout', function() {
\r
8201 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
8202 DOM.removeClass(t.id, cp + 'Hover');
\r
8206 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
8209 destroy : function() {
\r
8212 Event.clear(this.id + '_text');
\r
8213 Event.clear(this.id + '_open');
\r
8217 (function(tinymce) {
\r
8218 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
8220 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
\r
8221 NativeListBox : function(id, s) {
\r
8222 this.parent(id, s);
\r
8223 this.classPrefix = 'mceNativeListBox';
\r
8226 setDisabled : function(s) {
\r
8227 DOM.get(this.id).disabled = s;
\r
8230 isDisabled : function() {
\r
8231 return DOM.get(this.id).disabled;
\r
8234 select : function(va) {
\r
8235 var t = this, fv, f;
\r
8237 if (va == undefined)
\r
8238 return t.selectByIndex(-1);
\r
8240 // Is string or number make function selector
\r
8241 if (va && va.call)
\r
8249 // Do we need to do something?
\r
8250 if (va != t.selectedValue) {
\r
8252 each(t.items, function(o, i) {
\r
8255 t.selectByIndex(i);
\r
8261 t.selectByIndex(-1);
\r
8265 selectByIndex : function(idx) {
\r
8266 DOM.get(this.id).selectedIndex = idx + 1;
\r
8267 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
\r
8270 add : function(n, v, a) {
\r
8276 if (t.isRendered())
\r
8277 DOM.add(DOM.get(this.id), 'option', a, n);
\r
8286 t.onAdd.dispatch(t, o);
\r
8289 getLength : function() {
\r
8290 return this.items.length;
\r
8293 renderHTML : function() {
\r
8296 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
\r
8298 each(t.items, function(it) {
\r
8299 h += DOM.createHTML('option', {value : it.value}, it.title);
\r
8302 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
\r
8307 postRender : function() {
\r
8310 t.rendered = true;
\r
8312 function onChange(e) {
\r
8313 var v = t.items[e.target.selectedIndex - 1];
\r
8315 if (v && (v = v.value)) {
\r
8316 t.onChange.dispatch(t, v);
\r
8318 if (t.settings.onselect)
\r
8319 t.settings.onselect(v);
\r
8323 Event.add(t.id, 'change', onChange);
\r
8325 // Accessibility keyhandler
\r
8326 Event.add(t.id, 'keydown', function(e) {
\r
8329 Event.remove(t.id, 'change', ch);
\r
8331 bf = Event.add(t.id, 'blur', function() {
\r
8332 Event.add(t.id, 'change', onChange);
\r
8333 Event.remove(t.id, 'blur', bf);
\r
8336 if (e.keyCode == 13 || e.keyCode == 32) {
\r
8338 return Event.cancel(e);
\r
8342 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
8346 (function(tinymce) {
\r
8347 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
8349 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
\r
8350 MenuButton : function(id, s) {
\r
8351 this.parent(id, s);
\r
8353 this.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
8355 s.menu_container = s.menu_container || DOM.doc.body;
\r
8358 showMenu : function() {
\r
8359 var t = this, p1, p2, e = DOM.get(t.id), m;
\r
8361 if (t.isDisabled())
\r
8364 if (!t.isMenuRendered) {
\r
8366 t.isMenuRendered = true;
\r
8369 if (t.isMenuVisible)
\r
8370 return t.hideMenu();
\r
8372 p1 = DOM.getPos(t.settings.menu_container);
\r
8373 p2 = DOM.getPos(e);
\r
8376 m.settings.offset_x = p2.x;
\r
8377 m.settings.offset_y = p2.y;
\r
8378 m.settings.vp_offset_x = p2.x;
\r
8379 m.settings.vp_offset_y = p2.y;
\r
8380 m.settings.keyboard_focus = t._focused;
\r
8381 m.showMenu(0, e.clientHeight);
\r
8383 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8384 t.setState('Selected', 1);
\r
8386 t.isMenuVisible = 1;
\r
8389 renderMenu : function() {
\r
8392 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
8394 'class' : this.classPrefix + 'Menu',
\r
8395 icons : t.settings.icons
\r
8398 m.onHideMenu.add(t.hideMenu, t);
\r
8400 t.onRenderMenu.dispatch(t, m);
\r
8404 hideMenu : function(e) {
\r
8407 // Prevent double toogles by canceling the mouse click event to the button
\r
8408 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
\r
8411 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
8412 t.setState('Selected', 0);
\r
8413 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8415 t.menu.hideMenu();
\r
8418 t.isMenuVisible = 0;
\r
8421 postRender : function() {
\r
8422 var t = this, s = t.settings;
\r
8424 Event.add(t.id, 'click', function() {
\r
8425 if (!t.isDisabled()) {
\r
8427 s.onclick(t.value);
\r
8436 (function(tinymce) {
\r
8437 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
8439 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
\r
8440 SplitButton : function(id, s) {
\r
8441 this.parent(id, s);
\r
8442 this.classPrefix = 'mceSplitButton';
\r
8445 renderHTML : function() {
\r
8446 var h, t = this, s = t.settings, h1;
\r
8448 h = '<tbody><tr>';
\r
8451 h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
\r
8453 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
\r
8455 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
8457 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
\r
8458 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
8460 h += '</tr></tbody>';
\r
8462 return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
\r
8465 postRender : function() {
\r
8466 var t = this, s = t.settings;
\r
8469 Event.add(t.id + '_action', 'click', function() {
\r
8470 if (!t.isDisabled())
\r
8471 s.onclick(t.value);
\r
8475 Event.add(t.id + '_open', 'click', t.showMenu, t);
\r
8476 Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
\r
8477 Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
\r
8479 // Old IE doesn't have hover on all elements
\r
8480 if (tinymce.isIE6 || !DOM.boxModel) {
\r
8481 Event.add(t.id, 'mouseover', function() {
\r
8482 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
8483 DOM.addClass(t.id, 'mceSplitButtonHover');
\r
8486 Event.add(t.id, 'mouseout', function() {
\r
8487 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
8488 DOM.removeClass(t.id, 'mceSplitButtonHover');
\r
8493 destroy : function() {
\r
8496 Event.clear(this.id + '_action');
\r
8497 Event.clear(this.id + '_open');
\r
8502 (function(tinymce) {
\r
8503 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
\r
8505 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
\r
8506 ColorSplitButton : function(id, s) {
\r
8511 t.settings = s = tinymce.extend({
\r
8512 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
8514 default_color : '#888888'
\r
8517 t.onShowMenu = new tinymce.util.Dispatcher(t);
\r
8519 t.onHideMenu = new tinymce.util.Dispatcher(t);
\r
8521 t.value = s.default_color;
\r
8524 showMenu : function() {
\r
8525 var t = this, r, p, e, p2;
\r
8527 if (t.isDisabled())
\r
8530 if (!t.isMenuRendered) {
\r
8532 t.isMenuRendered = true;
\r
8535 if (t.isMenuVisible)
\r
8536 return t.hideMenu();
\r
8538 e = DOM.get(t.id);
\r
8539 DOM.show(t.id + '_menu');
\r
8540 DOM.addClass(e, 'mceSplitButtonSelected');
\r
8541 p2 = DOM.getPos(e);
\r
8542 DOM.setStyles(t.id + '_menu', {
\r
8544 top : p2.y + e.clientHeight,
\r
8549 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8550 t.onShowMenu.dispatch(t);
\r
8553 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
\r
8554 if (e.keyCode == 27)
\r
8558 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
\r
8561 t.isMenuVisible = 1;
\r
8564 hideMenu : function(e) {
\r
8567 // Prevent double toogles by canceling the mouse click event to the button
\r
8568 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
\r
8571 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
\r
8572 DOM.removeClass(t.id, 'mceSplitButtonSelected');
\r
8573 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8574 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
\r
8575 DOM.hide(t.id + '_menu');
\r
8578 t.onHideMenu.dispatch(t);
\r
8580 t.isMenuVisible = 0;
\r
8583 renderMenu : function() {
\r
8584 var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
\r
8586 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
8587 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
\r
8588 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
\r
8590 n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
\r
8591 tb = DOM.add(n, 'tbody');
\r
8593 // Generate color grid
\r
8595 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
\r
8596 c = c.replace(/^#/, '');
\r
8599 tr = DOM.add(tb, 'tr');
\r
8600 i = s.grid_width - 1;
\r
8603 n = DOM.add(tr, 'td');
\r
8605 n = DOM.add(n, 'a', {
\r
8606 href : 'javascript:;',
\r
8608 backgroundColor : '#' + c
\r
8610 _mce_color : '#' + c
\r
8614 if (s.more_colors_func) {
\r
8615 n = DOM.add(tb, 'tr');
\r
8616 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
\r
8617 n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
\r
8619 Event.add(n, 'click', function(e) {
\r
8620 s.more_colors_func.call(s.more_colors_scope || this);
\r
8621 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
8625 DOM.addClass(m, 'mceColorSplitMenu');
\r
8627 Event.add(t.id + '_menu', 'click', function(e) {
\r
8632 if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
\r
8635 return Event.cancel(e); // Prevent IE auto save warning
\r
8641 setColor : function(c) {
\r
8644 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
\r
8648 t.settings.onselect(c);
\r
8651 postRender : function() {
\r
8652 var t = this, id = t.id;
\r
8655 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
\r
8656 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
\r
8659 destroy : function() {
\r
8662 Event.clear(this.id + '_menu');
\r
8663 Event.clear(this.id + '_more');
\r
8664 DOM.remove(this.id + '_menu');
\r
8669 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
\r
8670 renderHTML : function() {
\r
8671 var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
\r
8674 for (i=0; i<cl.length; i++) {
\r
8675 // Get current control, prev control, next control and if the control is a list box or not
\r
8680 // Add toolbar start
\r
8682 c = 'mceToolbarStart';
\r
8685 c += ' mceToolbarStartButton';
\r
8686 else if (co.SplitButton)
\r
8687 c += ' mceToolbarStartSplitButton';
\r
8688 else if (co.ListBox)
\r
8689 c += ' mceToolbarStartListBox';
\r
8691 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8694 // Add toolbar end before list box and after the previous button
\r
8695 // This is to fix the o2k7 editor skins
\r
8696 if (pr && co.ListBox) {
\r
8697 if (pr.Button || pr.SplitButton)
\r
8698 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8701 // Render control HTML
\r
8703 // IE 8 quick fix, needed to propertly generate a hit area for anchors
\r
8705 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
\r
8707 h += '<td>' + co.renderHTML() + '</td>';
\r
8709 // Add toolbar start after list box and before the next button
\r
8710 // This is to fix the o2k7 editor skins
\r
8711 if (nx && co.ListBox) {
\r
8712 if (nx.Button || nx.SplitButton)
\r
8713 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8717 c = 'mceToolbarEnd';
\r
8720 c += ' mceToolbarEndButton';
\r
8721 else if (co.SplitButton)
\r
8722 c += ' mceToolbarEndSplitButton';
\r
8723 else if (co.ListBox)
\r
8724 c += ' mceToolbarEndListBox';
\r
8726 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8728 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
8732 (function(tinymce) {
\r
8733 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
\r
8735 tinymce.create('tinymce.AddOnManager', {
\r
8740 onAdd : new Dispatcher(this),
\r
8742 get : function(n) {
\r
8743 return this.lookup[n];
\r
8746 requireLangPack : function(n) {
\r
8747 var s = tinymce.settings;
\r
8749 if (s && s.language)
\r
8750 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
\r
8753 add : function(id, o) {
\r
8754 this.items.push(o);
\r
8755 this.lookup[id] = o;
\r
8756 this.onAdd.dispatch(this, id, o);
\r
8761 load : function(n, u, cb, s) {
\r
8767 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
\r
8768 u = tinymce.baseURL + '/' + u;
\r
8770 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
\r
8771 tinymce.ScriptLoader.add(u, cb, s);
\r
8775 // Create plugin and theme managers
\r
8776 tinymce.PluginManager = new tinymce.AddOnManager();
\r
8777 tinymce.ThemeManager = new tinymce.AddOnManager();
\r
8780 (function(tinymce) {
\r
8782 var each = tinymce.each, extend = tinymce.extend,
\r
8783 DOM = tinymce.DOM, Event = tinymce.dom.Event,
\r
8784 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
8785 explode = tinymce.explode,
\r
8786 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
\r
8788 // Setup some URLs where the editor API is located and where the document is
\r
8789 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
\r
8790 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
\r
8791 tinymce.documentBaseURL += '/';
\r
8793 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
\r
8795 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
\r
8797 // Add before unload listener
\r
8798 // This was required since IE was leaking memory if you added and removed beforeunload listeners
\r
8799 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
\r
8800 tinymce.onBeforeUnload = new Dispatcher(tinymce);
\r
8802 // Must be on window or IE will leak if the editor is placed in frame or iframe
\r
8803 Event.add(window, 'beforeunload', function(e) {
\r
8804 tinymce.onBeforeUnload.dispatch(tinymce, e);
\r
8807 tinymce.onAddEditor = new Dispatcher(tinymce);
\r
8809 tinymce.onRemoveEditor = new Dispatcher(tinymce);
\r
8811 tinymce.EditorManager = extend(tinymce, {
\r
8816 activeEditor : null,
\r
8818 init : function(s) {
\r
8819 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
\r
8821 function execCallback(se, n, s) {
\r
8827 if (tinymce.is(f, 'string')) {
\r
8828 s = f.replace(/\.\w+$/, '');
\r
8829 s = s ? tinymce.resolve(s) : 0;
\r
8830 f = tinymce.resolve(f);
\r
8833 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
\r
8844 Event.add(document, 'init', function() {
\r
8847 execCallback(s, 'onpageload');
\r
8851 l = s.elements || '';
\r
8853 if(l.length > 0) {
\r
8854 each(explode(l), function(v) {
\r
8856 ed = new tinymce.Editor(v, s);
\r
8860 each(document.forms, function(f) {
\r
8861 each(f.elements, function(e) {
\r
8862 if (e.name === v) {
\r
8863 v = 'mce_editor_' + instanceCounter++;
\r
8864 DOM.setAttrib(e, 'id', v);
\r
8866 ed = new tinymce.Editor(v, s);
\r
8878 case "specific_textareas":
\r
8879 function hasClass(n, c) {
\r
8880 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
\r
8883 each(DOM.select('textarea'), function(v) {
\r
8884 if (s.editor_deselector && hasClass(v, s.editor_deselector))
\r
8887 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
\r
8888 // Can we use the name
\r
8889 e = DOM.get(v.name);
\r
8893 // Generate unique name if missing or already exists
\r
8894 if (!v.id || t.get(v.id))
\r
8895 v.id = DOM.uniqueId();
\r
8897 ed = new tinymce.Editor(v.id, s);
\r
8905 // Call onInit when all editors are initialized
\r
8909 each(el, function(ed) {
\r
8912 if (!ed.initialized) {
\r
8914 ed.onInit.add(function() {
\r
8919 execCallback(s, 'oninit');
\r
8926 execCallback(s, 'oninit');
\r
8932 get : function(id) {
\r
8933 if (id === undefined)
\r
8934 return this.editors;
\r
8936 return this.editors[id];
\r
8939 getInstanceById : function(id) {
\r
8940 return this.get(id);
\r
8943 add : function(editor) {
\r
8944 var self = this, editors = self.editors;
\r
8946 // Add named and index editor instance
\r
8947 editors[editor.id] = editor;
\r
8948 editors.push(editor);
\r
8950 self._setActive(editor);
\r
8951 self.onAddEditor.dispatch(self, editor);
\r
8957 remove : function(editor) {
\r
8958 var t = this, i, editors = t.editors;
\r
8960 // Not in the collection
\r
8961 if (!editors[editor.id])
\r
8964 delete editors[editor.id];
\r
8966 for (i = 0; i < editors.length; i++) {
\r
8967 if (editors[i] == editor) {
\r
8968 editors.splice(i, 1);
\r
8973 // Select another editor since the active one was removed
\r
8974 if (t.activeEditor == editor)
\r
8975 t._setActive(editors[0]);
\r
8978 t.onRemoveEditor.dispatch(t, editor);
\r
8983 execCommand : function(c, u, v) {
\r
8984 var t = this, ed = t.get(v), w;
\r
8986 // Manager commands
\r
8992 case "mceAddEditor":
\r
8993 case "mceAddControl":
\r
8995 new tinymce.Editor(v, t.settings).render();
\r
8999 case "mceAddFrameControl":
\r
9002 // Add tinyMCE global instance and tinymce namespace to specified window
\r
9003 w.tinyMCE = tinyMCE;
\r
9004 w.tinymce = tinymce;
\r
9006 tinymce.DOM.doc = w.document;
\r
9007 tinymce.DOM.win = w;
\r
9009 ed = new tinymce.Editor(v.element_id, v);
\r
9012 // Fix IE memory leaks
\r
9013 if (tinymce.isIE) {
\r
9016 w.detachEvent('onunload', clr);
\r
9017 w = w.tinyMCE = w.tinymce = null; // IE leak
\r
9020 w.attachEvent('onunload', clr);
\r
9023 v.page_window = null;
\r
9027 case "mceRemoveEditor":
\r
9028 case "mceRemoveControl":
\r
9034 case 'mceToggleEditor':
\r
9036 t.execCommand('mceAddControl', 0, v);
\r
9040 if (ed.isHidden())
\r
9048 // Run command on active editor
\r
9049 if (t.activeEditor)
\r
9050 return t.activeEditor.execCommand(c, u, v);
\r
9055 execInstanceCommand : function(id, c, u, v) {
\r
9056 var ed = this.get(id);
\r
9059 return ed.execCommand(c, u, v);
\r
9064 triggerSave : function() {
\r
9065 each(this.editors, function(e) {
\r
9070 addI18n : function(p, o) {
\r
9071 var lo, i18n = this.i18n;
\r
9073 if (!tinymce.is(p, 'string')) {
\r
9074 each(p, function(o, lc) {
\r
9075 each(o, function(o, g) {
\r
9076 each(o, function(o, k) {
\r
9077 if (g === 'common')
\r
9078 i18n[lc + '.' + k] = o;
\r
9080 i18n[lc + '.' + g + '.' + k] = o;
\r
9085 each(o, function(o, k) {
\r
9086 i18n[p + '.' + k] = o;
\r
9091 // Private methods
\r
9093 _setActive : function(editor) {
\r
9094 this.selectedInstance = this.activeEditor = editor;
\r
9099 (function(tinymce) {
\r
9100 // Shorten these names
\r
9101 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
\r
9102 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
\r
9103 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
\r
9104 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
9105 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
\r
9107 tinymce.create('tinymce.Editor', {
\r
9108 Editor : function(id, s) {
\r
9111 t.id = t.editorId = id;
\r
9113 t.execCommands = {};
\r
9114 t.queryStateCommands = {};
\r
9115 t.queryValueCommands = {};
\r
9117 t.isNotDirty = false;
\r
9121 // Add events to the editor
\r
9125 'onBeforeRenderUI',
\r
9165 'onBeforeSetContent',
\r
9167 'onBeforeGetContent',
\r
9181 'onBeforeExecCommand',
\r
9191 'onSetProgressState'
\r
9193 t[e] = new Dispatcher(t);
\r
9196 t.settings = s = extend({
\r
9199 docs_language : 'en',
\r
9206 document_base_url : tinymce.documentBaseURL,
\r
9207 add_form_submit_trigger : 1,
\r
9209 add_unload_trigger : 1,
\r
9211 relative_urls : 1,
\r
9212 remove_script_host : 1,
\r
9213 table_inline_editing : 0,
\r
9214 object_resizing : 1,
\r
9216 accessibility_focus : 1,
\r
9217 custom_shortcuts : 1,
\r
9218 custom_undo_redo_keyboard_shortcuts : 1,
\r
9219 custom_undo_redo_restore_selection : 1,
\r
9220 custom_undo_redo : 1,
\r
9221 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
9222 visual_table_class : 'mceItemTable',
\r
9224 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
\r
9225 apply_source_formatting : 1,
\r
9226 directionality : 'ltr',
\r
9227 forced_root_block : 'p',
\r
9228 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
9230 padd_empty_editor : 1,
\r
9233 force_p_newlines : 1,
\r
9234 indentation : '30px',
\r
9236 fix_table_elements : 1,
\r
9237 inline_styles : 1,
\r
9238 convert_fonts_to_spans : true
\r
9241 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
\r
9242 base_uri : tinyMCE.baseURI
\r
9245 t.baseURI = tinymce.baseURI;
\r
9248 t.execCallback('setup', t);
\r
9251 render : function(nst) {
\r
9252 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
\r
9254 // Page is not loaded yet, wait for it
\r
9255 if (!Event.domLoaded) {
\r
9256 Event.add(document, 'init', function() {
\r
9262 tinyMCE.settings = s;
\r
9264 // Element not found, then skip initialization
\r
9265 if (!t.getElement())
\r
9268 // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
\r
9269 // browser says it has contentEditable support but there is no visible caret
\r
9270 // We will remove this check ones Apple implements full contentEditable support
\r
9271 if (tinymce.isIDevice)
\r
9274 // Add hidden input for non input elements inside form elements
\r
9275 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
\r
9276 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
\r
9278 if (tinymce.WindowManager)
\r
9279 t.windowManager = new tinymce.WindowManager(t);
\r
9281 if (s.encoding == 'xml') {
\r
9282 t.onGetContent.add(function(ed, o) {
\r
9284 o.content = DOM.encode(o.content);
\r
9288 if (s.add_form_submit_trigger) {
\r
9289 t.onSubmit.addToTop(function() {
\r
9290 if (t.initialized) {
\r
9297 if (s.add_unload_trigger) {
\r
9298 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
\r
9299 if (t.initialized && !t.destroyed && !t.isHidden())
\r
9300 t.save({format : 'raw', no_events : true});
\r
9304 tinymce.addUnload(t.destroy, t);
\r
9306 if (s.submit_patch) {
\r
9307 t.onBeforeRenderUI.add(function() {
\r
9308 var n = t.getElement().form;
\r
9313 // Already patched
\r
9314 if (n._mceOldSubmit)
\r
9317 // Check page uses id="submit" or name="submit" for it's submit button
\r
9318 if (!n.submit.nodeType && !n.submit.length) {
\r
9319 t.formElement = n;
\r
9320 n._mceOldSubmit = n.submit;
\r
9321 n.submit = function() {
\r
9322 // Save all instances
\r
9323 tinymce.triggerSave();
\r
9326 return t.formElement._mceOldSubmit(t.formElement);
\r
9335 function loadScripts() {
\r
9337 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
\r
9339 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
\r
9340 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
\r
9342 each(explode(s.plugins), function(p) {
\r
9343 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
\r
9344 // Skip safari plugin, since it is removed as of 3.3b1
\r
9345 if (p == 'safari')
\r
9348 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
\r
9352 // Init when que is loaded
\r
9353 sl.loadQueue(function() {
\r
9362 init : function() {
\r
9363 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
\r
9368 s.theme = s.theme.replace(/-/, '');
\r
9369 o = ThemeManager.get(s.theme);
\r
9370 t.theme = new o();
\r
9372 if (t.theme.init && s.init_theme)
\r
9373 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
\r
9376 // Create all plugins
\r
9377 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
\r
9378 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
\r
9383 t.plugins[p] = po;
\r
9390 // Setup popup CSS path(s)
\r
9391 if (s.popup_css !== false) {
\r
9393 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
\r
9395 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
\r
9398 if (s.popup_css_add)
\r
9399 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
\r
9401 t.controlManager = new tinymce.ControlManager(t);
\r
9403 if (s.custom_undo_redo) {
\r
9404 // Add initial undo level
\r
9405 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
\r
9406 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
\r
9407 if (!t.undoManager.hasUndo())
\r
9408 t.undoManager.add();
\r
9412 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
\r
9413 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
\r
9414 t.undoManager.add();
\r
9418 t.onExecCommand.add(function(ed, c) {
\r
9419 // Don't refresh the select lists until caret move
\r
9420 if (!/^(FontName|FontSize)$/.test(c))
\r
9424 // Remove ghost selections on images and tables in Gecko
\r
9426 function repaint(a, o) {
\r
9427 if (!o || !o.initial)
\r
9428 t.execCommand('mceRepaint');
\r
9431 t.onUndo.add(repaint);
\r
9432 t.onRedo.add(repaint);
\r
9433 t.onSetContent.add(repaint);
\r
9436 // Enables users to override the control factory
\r
9437 t.onBeforeRenderUI.dispatch(t, t.controlManager);
\r
9440 if (s.render_ui) {
\r
9441 w = s.width || e.style.width || e.offsetWidth;
\r
9442 h = s.height || e.style.height || e.offsetHeight;
\r
9443 t.orgDisplay = e.style.display;
\r
9444 re = /^[0-9\.]+(|px)$/i;
\r
9446 if (re.test('' + w))
\r
9447 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
\r
9449 if (re.test('' + h))
\r
9450 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
\r
9453 o = t.theme.renderUI({
\r
9457 deltaWidth : s.delta_width,
\r
9458 deltaHeight : s.delta_height
\r
9461 t.editorContainer = o.editorContainer;
\r
9465 // User specified a document.domain value
\r
9466 if (document.domain && location.hostname != document.domain)
\r
9467 tinymce.relaxedDomain = document.domain;
\r
9470 DOM.setStyles(o.sizeContainer || o.editorContainer, {
\r
9475 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
\r
9479 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
\r
9481 // We only need to override paths if we have to
\r
9482 // IE has a bug where it remove site absolute urls to relative ones if this is specified
\r
9483 if (s.document_base_url != tinymce.documentBaseURL)
\r
9484 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
\r
9486 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
\r
9488 if (tinymce.relaxedDomain)
\r
9489 t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
\r
9491 bi = s.body_id || 'tinymce';
\r
9492 if (bi.indexOf('=') != -1) {
\r
9493 bi = t.getParam('body_id', '', 'hash');
\r
9494 bi = bi[t.id] || bi;
\r
9497 bc = s.body_class || '';
\r
9498 if (bc.indexOf('=') != -1) {
\r
9499 bc = t.getParam('body_class', '', 'hash');
\r
9500 bc = bc[t.id] || '';
\r
9503 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
\r
9505 // Domain relaxing enabled, then set document domain
\r
9506 if (tinymce.relaxedDomain) {
\r
9507 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
\r
9508 if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
\r
9509 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
9510 else if (tinymce.isOpera)
\r
9511 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
\r
9515 n = DOM.add(o.iframeContainer, 'iframe', {
\r
9516 id : t.id + "_ifr",
\r
9517 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
\r
9518 frameBorder : '0',
\r
9525 t.contentAreaContainer = o.iframeContainer;
\r
9526 DOM.get(o.editorContainer).style.display = t.orgDisplay;
\r
9527 DOM.get(t.id).style.display = 'none';
\r
9529 if (!isIE || !tinymce.relaxedDomain)
\r
9532 e = n = o = null; // Cleanup
\r
9535 setupIframe : function() {
\r
9536 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
\r
9538 // Setup iframe body
\r
9539 if (!isIE || !tinymce.relaxedDomain) {
\r
9541 d.write(t.iframeHTML);
\r
9545 // Design mode needs to be added here Ctrl+A will fail otherwise
\r
9549 d.designMode = 'On';
\r
9551 // Will fail on Gecko if the editor is placed in an hidden container element
\r
9552 // The design mode will be set ones the editor is focused
\r
9556 // IE needs to use contentEditable or it will display non secure items for HTTPS
\r
9558 // It will not steal focus if we hide it while setting contentEditable
\r
9563 b.contentEditable = true;
\r
9568 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
\r
9569 keep_values : true,
\r
9570 url_converter : t.convertURL,
\r
9571 url_converter_scope : t,
\r
9572 hex_colors : s.force_hex_style_colors,
\r
9573 class_filter : s.class_filter,
\r
9574 update_styles : 1,
\r
9575 fix_ie_paragraphs : 1,
\r
9576 valid_styles : s.valid_styles
\r
9579 t.schema = new tinymce.dom.Schema();
\r
9581 t.serializer = new tinymce.dom.Serializer(extend(s, {
\r
9582 valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
\r
9587 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
\r
9589 t.formatter = new tinymce.Formatter(this);
\r
9591 // Register default formats
\r
9592 t.formatter.register({
\r
9594 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
\r
9595 {selector : 'img,table', styles : {'float' : 'left'}}
\r
9599 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
\r
9600 {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
\r
9601 {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
\r
9605 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
\r
9606 {selector : 'img,table', styles : {'float' : 'right'}}
\r
9610 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
\r
9614 {inline : 'strong'},
\r
9615 {inline : 'span', styles : {fontWeight : 'bold'}},
\r
9621 {inline : 'span', styles : {fontStyle : 'italic'}},
\r
9626 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
\r
9631 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
\r
9635 forecolor : {inline : 'span', styles : {color : '%value'}},
\r
9636 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
\r
9637 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
\r
9638 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
\r
9639 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
\r
9640 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
\r
9643 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
\r
9644 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
\r
9645 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
\r
9649 // Register default block formats
\r
9650 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
\r
9651 t.formatter.register(name, {block : name, remove : 'all'});
\r
9654 // Register user defined formats
\r
9655 t.formatter.register(t.settings.formats);
\r
9657 t.undoManager = new tinymce.UndoManager(t);
\r
9660 t.undoManager.onAdd.add(function(um, l) {
\r
9662 return t.onChange.dispatch(t, l, um);
\r
9665 t.undoManager.onUndo.add(function(um, l) {
\r
9666 return t.onUndo.dispatch(t, l, um);
\r
9669 t.undoManager.onRedo.add(function(um, l) {
\r
9670 return t.onRedo.dispatch(t, l, um);
\r
9673 t.forceBlocks = new tinymce.ForceBlocks(t, {
\r
9674 forced_root_block : s.forced_root_block
\r
9677 t.editorCommands = new tinymce.EditorCommands(t);
\r
9680 t.serializer.onPreProcess.add(function(se, o) {
\r
9681 return t.onPreProcess.dispatch(t, o, se);
\r
9684 t.serializer.onPostProcess.add(function(se, o) {
\r
9685 return t.onPostProcess.dispatch(t, o, se);
\r
9688 t.onPreInit.dispatch(t);
\r
9690 if (!s.gecko_spellcheck)
\r
9691 t.getBody().spellcheck = 0;
\r
9696 t.controlManager.onPostRender.dispatch(t, t.controlManager);
\r
9697 t.onPostRender.dispatch(t);
\r
9699 if (s.directionality)
\r
9700 t.getBody().dir = s.directionality;
\r
9703 t.getBody().style.whiteSpace = "nowrap";
\r
9705 if (s.custom_elements) {
\r
9706 function handleCustom(ed, o) {
\r
9707 each(explode(s.custom_elements), function(v) {
\r
9710 if (v.indexOf('~') === 0) {
\r
9711 v = v.substring(1);
\r
9716 o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
\r
9717 o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
\r
9721 t.onBeforeSetContent.add(handleCustom);
\r
9722 t.onPostProcess.add(function(ed, o) {
\r
9724 handleCustom(ed, o);
\r
9728 if (s.handle_node_change_callback) {
\r
9729 t.onNodeChange.add(function(ed, cm, n) {
\r
9730 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
\r
9734 if (s.save_callback) {
\r
9735 t.onSaveContent.add(function(ed, o) {
\r
9736 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
9743 if (s.onchange_callback) {
\r
9744 t.onChange.add(function(ed, l) {
\r
9745 t.execCallback('onchange_callback', t, l);
\r
9749 if (s.convert_newlines_to_brs) {
\r
9750 t.onBeforeSetContent.add(function(ed, o) {
\r
9752 o.content = o.content.replace(/\r?\n/g, '<br />');
\r
9756 if (s.fix_nesting && isIE) {
\r
9757 t.onBeforeSetContent.add(function(ed, o) {
\r
9758 o.content = t._fixNesting(o.content);
\r
9762 if (s.preformatted) {
\r
9763 t.onPostProcess.add(function(ed, o) {
\r
9764 o.content = o.content.replace(/^\s*<pre.*?>/, '');
\r
9765 o.content = o.content.replace(/<\/pre>\s*$/, '');
\r
9768 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
\r
9772 if (s.verify_css_classes) {
\r
9773 t.serializer.attribValueFilter = function(n, v) {
\r
9776 if (n == 'class') {
\r
9777 // Build regexp for classes
\r
9778 if (!t.classesRE) {
\r
9779 cl = t.dom.getClasses();
\r
9781 if (cl.length > 0) {
\r
9784 each (cl, function(o) {
\r
9785 s += (s ? '|' : '') + o['class'];
\r
9788 t.classesRE = new RegExp('(' + s + ')', 'gi');
\r
9792 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
\r
9799 if (s.cleanup_callback) {
\r
9800 t.onBeforeSetContent.add(function(ed, o) {
\r
9801 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
9804 t.onPreProcess.add(function(ed, o) {
\r
9806 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
\r
9809 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
\r
9812 t.onPostProcess.add(function(ed, o) {
\r
9814 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
9817 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
\r
9821 if (s.save_callback) {
\r
9822 t.onGetContent.add(function(ed, o) {
\r
9824 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
9828 if (s.handle_event_callback) {
\r
9829 t.onEvent.add(function(ed, e, o) {
\r
9830 if (t.execCallback('handle_event_callback', e, ed, o) === false)
\r
9835 // Add visual aids when new contents is added
\r
9836 t.onSetContent.add(function() {
\r
9837 t.addVisual(t.getBody());
\r
9840 // Remove empty contents
\r
9841 if (s.padd_empty_editor) {
\r
9842 t.onPostProcess.add(function(ed, o) {
\r
9843 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
\r
9848 // Fix gecko link bug, when a link is placed at the end of block elements there is
\r
9849 // no way to move the caret behind the link. This fix adds a bogus br element after the link
\r
9850 function fixLinks(ed, o) {
\r
9851 each(ed.dom.select('a'), function(n) {
\r
9852 var pn = n.parentNode;
\r
9854 if (ed.dom.isBlock(pn) && pn.lastChild === n)
\r
9855 ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
\r
9859 t.onExecCommand.add(function(ed, cmd) {
\r
9860 if (cmd === 'CreateLink')
\r
9864 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
\r
9866 if (!s.readonly) {
\r
9868 // Design mode must be set here once again to fix a bug where
\r
9869 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
\r
9870 d.designMode = 'Off';
\r
9871 d.designMode = 'On';
\r
9873 // Will fail on Gecko if the editor is placed in an hidden container element
\r
9874 // The design mode will be set ones the editor is focused
\r
9879 // A small timeout was needed since firefox will remove. Bug: #1838304
\r
9880 setTimeout(function () {
\r
9884 t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
\r
9885 t.startContent = t.getContent({format : 'raw'});
\r
9886 t.initialized = true;
\r
9888 t.onInit.dispatch(t);
\r
9889 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
\r
9890 t.execCallback('init_instance_callback', t);
\r
9892 t.nodeChanged({initial : 1});
\r
9894 // Load specified content CSS last
\r
9895 if (s.content_css) {
\r
9896 tinymce.each(explode(s.content_css), function(u) {
\r
9897 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
\r
9901 // Handle auto focus
\r
9902 if (s.auto_focus) {
\r
9903 setTimeout(function () {
\r
9904 var ed = tinymce.get(s.auto_focus);
\r
9906 ed.selection.select(ed.getBody(), 1);
\r
9907 ed.selection.collapse(1);
\r
9908 ed.getWin().focus();
\r
9917 focus : function(sf) {
\r
9918 var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
\r
9921 // Get selected control element
\r
9922 ieRng = t.selection.getRng();
\r
9924 controlElm = ieRng.item(0);
\r
9927 // Is not content editable
\r
9929 t.getWin().focus();
\r
9931 // Restore selected control element
\r
9932 // This is needed when for example an image is selected within a
\r
9933 // layer a call to focus will then remove the control selection
\r
9934 if (controlElm && controlElm.ownerDocument == doc) {
\r
9935 ieRng = doc.body.createControlRange();
\r
9936 ieRng.addElement(controlElm);
\r
9942 if (tinymce.activeEditor != t) {
\r
9943 if ((oed = tinymce.activeEditor) != null)
\r
9944 oed.onDeactivate.dispatch(oed, t);
\r
9946 t.onActivate.dispatch(t, oed);
\r
9949 tinymce._setActive(t);
\r
9952 execCallback : function(n) {
\r
9953 var t = this, f = t.settings[n], s;
\r
9958 // Look through lookup
\r
9959 if (t.callbackLookup && (s = t.callbackLookup[n])) {
\r
9964 if (is(f, 'string')) {
\r
9965 s = f.replace(/\.\w+$/, '');
\r
9966 s = s ? tinymce.resolve(s) : 0;
\r
9967 f = tinymce.resolve(f);
\r
9968 t.callbackLookup = t.callbackLookup || {};
\r
9969 t.callbackLookup[n] = {func : f, scope : s};
\r
9972 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
\r
9975 translate : function(s) {
\r
9976 var c = this.settings.language || 'en', i18n = tinymce.i18n;
\r
9981 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
\r
9982 return i18n[c + '.' + b] || '{#' + b + '}';
\r
9986 getLang : function(n, dv) {
\r
9987 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
\r
9990 getParam : function(n, dv, ty) {
\r
9991 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
\r
9993 if (ty === 'hash') {
\r
9996 if (is(v, 'string')) {
\r
9997 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
\r
10000 if (v.length > 1)
\r
10001 o[tr(v[0])] = tr(v[1]);
\r
10003 o[tr(v[0])] = tr(v);
\r
10014 nodeChanged : function(o) {
\r
10015 var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody();
\r
10017 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
\r
10018 if (t.initialized) {
\r
10020 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
\r
10022 // Get parents and add them to object
\r
10024 t.dom.getParent(n, function(node) {
\r
10025 if (node.nodeName == 'BODY')
\r
10028 o.parents.push(node);
\r
10031 t.onNodeChange.dispatch(
\r
10033 o ? o.controlManager || t.controlManager : t.controlManager,
\r
10041 addButton : function(n, s) {
\r
10044 t.buttons = t.buttons || {};
\r
10045 t.buttons[n] = s;
\r
10048 addCommand : function(n, f, s) {
\r
10049 this.execCommands[n] = {func : f, scope : s || this};
\r
10052 addQueryStateHandler : function(n, f, s) {
\r
10053 this.queryStateCommands[n] = {func : f, scope : s || this};
\r
10056 addQueryValueHandler : function(n, f, s) {
\r
10057 this.queryValueCommands[n] = {func : f, scope : s || this};
\r
10060 addShortcut : function(pa, desc, cmd_func, sc) {
\r
10063 if (!t.settings.custom_shortcuts)
\r
10066 t.shortcuts = t.shortcuts || {};
\r
10068 if (is(cmd_func, 'string')) {
\r
10071 cmd_func = function() {
\r
10072 t.execCommand(c, false, null);
\r
10076 if (is(cmd_func, 'object')) {
\r
10079 cmd_func = function() {
\r
10080 t.execCommand(c[0], c[1], c[2]);
\r
10084 each(explode(pa), function(pa) {
\r
10087 scope : sc || this,
\r
10094 each(explode(pa, '+'), function(v) {
\r
10103 o.charCode = v.charCodeAt(0);
\r
10104 o.keyCode = v.toUpperCase().charCodeAt(0);
\r
10108 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
\r
10114 execCommand : function(cmd, ui, val, a) {
\r
10115 var t = this, s = 0, o, st;
\r
10117 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
\r
10121 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
\r
10125 // Command callback
\r
10126 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
\r
10127 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10131 // Registred commands
\r
10132 if (o = t.execCommands[cmd]) {
\r
10133 st = o.func.call(o.scope, ui, val);
\r
10135 // Fall through on true
\r
10136 if (st !== true) {
\r
10137 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10142 // Plugin commands
\r
10143 each(t.plugins, function(p) {
\r
10144 if (p.execCommand && p.execCommand(cmd, ui, val)) {
\r
10145 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10154 // Theme commands
\r
10155 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
\r
10156 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10160 // Execute global commands
\r
10161 if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
\r
10162 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10166 // Editor commands
\r
10167 if (t.editorCommands.execCommand(cmd, ui, val)) {
\r
10168 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10172 // Browser commands
\r
10173 t.getDoc().execCommand(cmd, ui, val);
\r
10174 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10177 queryCommandState : function(cmd) {
\r
10178 var t = this, o, s;
\r
10180 // Is hidden then return undefined
\r
10181 if (t._isHidden())
\r
10184 // Registred commands
\r
10185 if (o = t.queryStateCommands[cmd]) {
\r
10186 s = o.func.call(o.scope);
\r
10188 // Fall though on true
\r
10193 // Registred commands
\r
10194 o = t.editorCommands.queryCommandState(cmd);
\r
10198 // Browser commands
\r
10200 return this.getDoc().queryCommandState(cmd);
\r
10202 // Fails sometimes see bug: 1896577
\r
10206 queryCommandValue : function(c) {
\r
10207 var t = this, o, s;
\r
10209 // Is hidden then return undefined
\r
10210 if (t._isHidden())
\r
10213 // Registred commands
\r
10214 if (o = t.queryValueCommands[c]) {
\r
10215 s = o.func.call(o.scope);
\r
10217 // Fall though on true
\r
10222 // Registred commands
\r
10223 o = t.editorCommands.queryCommandValue(c);
\r
10227 // Browser commands
\r
10229 return this.getDoc().queryCommandValue(c);
\r
10231 // Fails sometimes see bug: 1896577
\r
10235 show : function() {
\r
10238 DOM.show(t.getContainer());
\r
10243 hide : function() {
\r
10244 var t = this, d = t.getDoc();
\r
10246 // Fixed bug where IE has a blinking cursor left from the editor
\r
10248 d.execCommand('SelectAll');
\r
10250 // We must save before we hide so Safari doesn't crash
\r
10252 DOM.hide(t.getContainer());
\r
10253 DOM.setStyle(t.id, 'display', t.orgDisplay);
\r
10256 isHidden : function() {
\r
10257 return !DOM.isHidden(this.id);
\r
10260 setProgressState : function(b, ti, o) {
\r
10261 this.onSetProgressState.dispatch(this, b, ti, o);
\r
10266 load : function(o) {
\r
10267 var t = this, e = t.getElement(), h;
\r
10273 // Double encode existing entities in the value
\r
10274 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
\r
10277 if (!o.no_events)
\r
10278 t.onLoadContent.dispatch(t, o);
\r
10280 o.element = e = null;
\r
10286 save : function(o) {
\r
10287 var t = this, e = t.getElement(), h, f;
\r
10289 if (!e || !t.initialized)
\r
10295 // Add undo level will trigger onchange event
\r
10296 if (!o.no_events) {
\r
10297 t.undoManager.typing = 0;
\r
10298 t.undoManager.add();
\r
10302 h = o.content = t.getContent(o);
\r
10304 if (!o.no_events)
\r
10305 t.onSaveContent.dispatch(t, o);
\r
10309 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
\r
10312 // Update hidden form element
\r
10313 if (f = DOM.getParent(t.id, 'form')) {
\r
10314 each(f.elements, function(e) {
\r
10315 if (e.name == t.id) {
\r
10324 o.element = e = null;
\r
10329 setContent : function(h, o) {
\r
10333 o.format = o.format || 'html';
\r
10337 if (!o.no_events)
\r
10338 t.onBeforeSetContent.dispatch(t, o);
\r
10340 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
\r
10341 // It will also be impossible to place the caret in the editor unless there is a BR element present
\r
10342 if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
\r
10343 o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
\r
10344 o.format = 'raw';
\r
10347 o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
\r
10349 if (o.format != 'raw' && t.settings.cleanup) {
\r
10350 o.getInner = true;
\r
10351 o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
\r
10354 if (!o.no_events)
\r
10355 t.onSetContent.dispatch(t, o);
\r
10357 return o.content;
\r
10360 getContent : function(o) {
\r
10364 o.format = o.format || 'html';
\r
10367 if (!o.no_events)
\r
10368 t.onBeforeGetContent.dispatch(t, o);
\r
10370 if (o.format != 'raw' && t.settings.cleanup) {
\r
10371 o.getInner = true;
\r
10372 h = t.serializer.serialize(t.getBody(), o);
\r
10374 h = t.getBody().innerHTML;
\r
10376 h = h.replace(/^\s*|\s*$/g, '');
\r
10379 if (!o.no_events)
\r
10380 t.onGetContent.dispatch(t, o);
\r
10382 return o.content;
\r
10385 isDirty : function() {
\r
10388 return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
\r
10391 getContainer : function() {
\r
10394 if (!t.container)
\r
10395 t.container = DOM.get(t.editorContainer || t.id + '_parent');
\r
10397 return t.container;
\r
10400 getContentAreaContainer : function() {
\r
10401 return this.contentAreaContainer;
\r
10404 getElement : function() {
\r
10405 return DOM.get(this.settings.content_element || this.id);
\r
10408 getWin : function() {
\r
10411 if (!t.contentWindow) {
\r
10412 e = DOM.get(t.id + "_ifr");
\r
10415 t.contentWindow = e.contentWindow;
\r
10418 return t.contentWindow;
\r
10421 getDoc : function() {
\r
10424 if (!t.contentDocument) {
\r
10428 t.contentDocument = w.document;
\r
10431 return t.contentDocument;
\r
10434 getBody : function() {
\r
10435 return this.bodyElement || this.getDoc().body;
\r
10438 convertURL : function(u, n, e) {
\r
10439 var t = this, s = t.settings;
\r
10441 // Use callback instead
\r
10442 if (s.urlconverter_callback)
\r
10443 return t.execCallback('urlconverter_callback', u, e, true, n);
\r
10445 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
\r
10446 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
\r
10449 // Convert to relative
\r
10450 if (s.relative_urls)
\r
10451 return t.documentBaseURI.toRelative(u);
\r
10453 // Convert to absolute
\r
10454 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
\r
10459 addVisual : function(e) {
\r
10460 var t = this, s = t.settings;
\r
10462 e = e || t.getBody();
\r
10464 if (!is(t.hasVisual))
\r
10465 t.hasVisual = s.visual;
\r
10467 each(t.dom.select('table,a', e), function(e) {
\r
10470 switch (e.nodeName) {
\r
10472 v = t.dom.getAttrib(e, 'border');
\r
10474 if (!v || v == '0') {
\r
10476 t.dom.addClass(e, s.visual_table_class);
\r
10478 t.dom.removeClass(e, s.visual_table_class);
\r
10484 v = t.dom.getAttrib(e, 'name');
\r
10488 t.dom.addClass(e, 'mceItemAnchor');
\r
10490 t.dom.removeClass(e, 'mceItemAnchor');
\r
10497 t.onVisualAid.dispatch(t, e, t.hasVisual);
\r
10500 remove : function() {
\r
10501 var t = this, e = t.getContainer();
\r
10503 t.removed = 1; // Cancels post remove event execution
\r
10506 t.execCallback('remove_instance_callback', t);
\r
10507 t.onRemove.dispatch(t);
\r
10509 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
\r
10510 t.onExecCommand.listeners = [];
\r
10512 tinymce.remove(t);
\r
10516 destroy : function(s) {
\r
10519 // One time is enough
\r
10524 tinymce.removeUnload(t.destroy);
\r
10525 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
\r
10527 // Manual destroy
\r
10528 if (t.theme && t.theme.destroy)
\r
10529 t.theme.destroy();
\r
10531 // Destroy controls, selection and dom
\r
10532 t.controlManager.destroy();
\r
10533 t.selection.destroy();
\r
10536 // Remove all events
\r
10538 // Don't clear the window or document if content editable
\r
10539 // is enabled since other instances might still be present
\r
10540 if (!t.settings.content_editable) {
\r
10541 Event.clear(t.getWin());
\r
10542 Event.clear(t.getDoc());
\r
10545 Event.clear(t.getBody());
\r
10546 Event.clear(t.formElement);
\r
10549 if (t.formElement) {
\r
10550 t.formElement.submit = t.formElement._mceOldSubmit;
\r
10551 t.formElement._mceOldSubmit = null;
\r
10554 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
\r
10557 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
\r
10562 // Internal functions
\r
10564 _addEvents : function() {
\r
10565 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
\r
10566 var t = this, i, s = t.settings, lo = {
\r
10567 mouseup : 'onMouseUp',
\r
10568 mousedown : 'onMouseDown',
\r
10569 click : 'onClick',
\r
10570 keyup : 'onKeyUp',
\r
10571 keydown : 'onKeyDown',
\r
10572 keypress : 'onKeyPress',
\r
10573 submit : 'onSubmit',
\r
10574 reset : 'onReset',
\r
10575 contextmenu : 'onContextMenu',
\r
10576 dblclick : 'onDblClick',
\r
10577 paste : 'onPaste' // Doesn't work in all browsers yet
\r
10580 function eventHandler(e, o) {
\r
10583 // Don't fire events when it's removed
\r
10587 // Generic event handler
\r
10588 if (t.onEvent.dispatch(t, e, o) !== false) {
\r
10589 // Specific event handler
\r
10590 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
\r
10594 // Add DOM events
\r
10595 each(lo, function(v, k) {
\r
10597 case 'contextmenu':
\r
10598 if (tinymce.isOpera) {
\r
10599 // Fake contextmenu on Opera
\r
10600 t.dom.bind(t.getBody(), 'mousedown', function(e) {
\r
10602 e.fakeType = 'contextmenu';
\r
10607 t.dom.bind(t.getBody(), k, eventHandler);
\r
10611 t.dom.bind(t.getBody(), k, function(e) {
\r
10618 t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
\r
10622 t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
\r
10626 t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
\r
10631 // Fixes bug where a specified document_base_uri could result in broken images
\r
10632 // This will also fix drag drop of images in Gecko
\r
10633 if (tinymce.isGecko) {
\r
10634 // Convert all images to absolute URLs
\r
10635 /* t.onSetContent.add(function(ed, o) {
\r
10636 each(ed.dom.select('img'), function(e) {
\r
10639 if (v = e.getAttribute('_mce_src'))
\r
10640 e.src = t.documentBaseURI.toAbsolute(v);
\r
10644 t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
\r
10649 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
\r
10650 e.src = t.documentBaseURI.toAbsolute(v);
\r
10654 // Set various midas options in Gecko
\r
10656 function setOpts() {
\r
10657 var t = this, d = t.getDoc(), s = t.settings;
\r
10659 if (isGecko && !s.readonly) {
\r
10660 if (t._isHidden()) {
\r
10662 if (!s.content_editable)
\r
10663 d.designMode = 'On';
\r
10665 // Fails if it's hidden
\r
10670 // Try new Gecko method
\r
10671 d.execCommand("styleWithCSS", 0, false);
\r
10673 // Use old method
\r
10674 if (!t._isHidden())
\r
10675 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
\r
10678 if (!s.table_inline_editing)
\r
10679 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
\r
10681 if (!s.object_resizing)
\r
10682 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
\r
10686 t.onBeforeExecCommand.add(setOpts);
\r
10687 t.onMouseDown.add(setOpts);
\r
10690 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
\r
10691 // WebKit can't even do simple things like selecting an image
\r
10692 // This also fixes so it's possible to select mceItemAnchors
\r
10693 if (tinymce.isWebKit) {
\r
10694 t.onClick.add(function(ed, e) {
\r
10697 // Needs tobe the setBaseAndExtend or it will fail to select floated images
\r
10698 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor')))
\r
10699 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
\r
10703 // Add node change handlers
\r
10704 t.onMouseUp.add(t.nodeChanged);
\r
10705 //t.onClick.add(t.nodeChanged);
\r
10706 t.onKeyUp.add(function(ed, e) {
\r
10707 var c = e.keyCode;
\r
10709 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
10713 // Add reset handler
\r
10714 t.onReset.add(function() {
\r
10715 t.setContent(t.startContent, {format : 'raw'});
\r
10719 if (s.custom_shortcuts) {
\r
10720 if (s.custom_undo_redo_keyboard_shortcuts) {
\r
10721 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
\r
10722 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
\r
10725 // Add default shortcuts for gecko
\r
10726 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
\r
10727 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
\r
10728 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
\r
10730 // BlockFormat shortcuts keys
\r
10731 for (i=1; i<=6; i++)
\r
10732 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
\r
10734 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
\r
10735 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
\r
10736 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
\r
10738 function find(e) {
\r
10741 if (!e.altKey && !e.ctrlKey && !e.metaKey)
\r
10744 each(t.shortcuts, function(o) {
\r
10745 if (tinymce.isMac && o.ctrl != e.metaKey)
\r
10747 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
\r
10750 if (o.alt != e.altKey)
\r
10753 if (o.shift != e.shiftKey)
\r
10756 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
\r
10765 t.onKeyUp.add(function(ed, e) {
\r
10769 return Event.cancel(e);
\r
10772 t.onKeyPress.add(function(ed, e) {
\r
10776 return Event.cancel(e);
\r
10779 t.onKeyDown.add(function(ed, e) {
\r
10783 o.func.call(o.scope);
\r
10784 return Event.cancel(e);
\r
10789 if (tinymce.isIE) {
\r
10790 // Fix so resize will only update the width and height attributes not the styles of an image
\r
10791 // It will also block mceItemNoResize items
\r
10792 t.dom.bind(t.getDoc(), 'controlselect', function(e) {
\r
10793 var re = t.resizeInfo, cb;
\r
10797 // Don't do this action for non image elements
\r
10798 if (e.nodeName !== 'IMG')
\r
10802 t.dom.unbind(re.node, re.ev, re.cb);
\r
10804 if (!t.dom.hasClass(e, 'mceItemNoResize')) {
\r
10805 ev = 'resizeend';
\r
10806 cb = t.dom.bind(e, ev, function(e) {
\r
10811 if (v = t.dom.getStyle(e, 'width')) {
\r
10812 t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
\r
10813 t.dom.setStyle(e, 'width', '');
\r
10816 if (v = t.dom.getStyle(e, 'height')) {
\r
10817 t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
\r
10818 t.dom.setStyle(e, 'height', '');
\r
10822 ev = 'resizestart';
\r
10823 cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
\r
10826 re = t.resizeInfo = {
\r
10833 t.onKeyDown.add(function(ed, e) {
\r
10834 switch (e.keyCode) {
\r
10836 // Fix IE control + backspace browser bug
\r
10837 if (t.selection.getRng().item) {
\r
10838 ed.dom.remove(t.selection.getRng().item(0));
\r
10839 return Event.cancel(e);
\r
10844 /*if (t.dom.boxModel) {
\r
10845 t.getBody().style.height = '100%';
\r
10847 Event.add(t.getWin(), 'resize', function(e) {
\r
10848 var docElm = t.getDoc().documentElement;
\r
10850 docElm.style.height = (docElm.offsetHeight - 10) + 'px';
\r
10855 if (tinymce.isOpera) {
\r
10856 t.onClick.add(function(ed, e) {
\r
10857 Event.prevent(e);
\r
10861 // Add custom undo/redo handlers
\r
10862 if (s.custom_undo_redo) {
\r
10863 function addUndo() {
\r
10864 t.undoManager.typing = 0;
\r
10865 t.undoManager.add();
\r
10868 t.dom.bind(t.getDoc(), 'focusout', function(e) {
\r
10869 if (!t.removed && t.undoManager.typing)
\r
10873 t.onKeyUp.add(function(ed, e) {
\r
10874 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
\r
10878 t.onKeyDown.add(function(ed, e) {
\r
10879 var rng, parent, bookmark;
\r
10881 // IE has a really odd bug where the DOM might include an node that doesn't have
\r
10882 // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
\r
10883 // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
\r
10884 // after you delete contents from it. See: #3008923
\r
10885 if (isIE && e.keyCode == 46) {
\r
10886 rng = t.selection.getRng();
\r
10888 if (rng.parentElement) {
\r
10889 parent = rng.parentElement();
\r
10891 // Select next word when ctrl key is used in combo with delete
\r
10893 rng.moveEnd('word', 1);
\r
10897 // Delete contents
\r
10898 t.selection.getSel().clear();
\r
10900 // Check if we are within the same parent
\r
10901 if (rng.parentElement() == parent) {
\r
10902 bookmark = t.selection.getBookmark();
\r
10905 // Update the HTML and hopefully it will remove the artifacts
\r
10906 parent.innerHTML = parent.innerHTML;
\r
10908 // And since it's IE it can sometimes produce an unknown runtime error
\r
10911 // Restore the caret position
\r
10912 t.selection.moveToBookmark(bookmark);
\r
10915 // Block the default delete behavior since it might be broken
\r
10916 e.preventDefault();
\r
10921 // Is caracter positon keys
\r
10922 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
\r
10923 if (t.undoManager.typing)
\r
10929 if (!t.undoManager.typing) {
\r
10930 t.undoManager.add();
\r
10931 t.undoManager.typing = 1;
\r
10935 t.onMouseDown.add(function() {
\r
10936 if (t.undoManager.typing)
\r
10942 _isHidden : function() {
\r
10948 // Weird, wheres that cursor selection?
\r
10949 s = this.selection.getSel();
\r
10950 return (!s || !s.rangeCount || s.rangeCount == 0);
\r
10953 // Fix for bug #1867292
\r
10954 _fixNesting : function(s) {
\r
10957 s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
\r
10960 // Handle end element
\r
10965 if (c !== d[d.length - 1].tag) {
\r
10966 for (i=d.length - 1; i>=0; i--) {
\r
10967 if (d[i].tag === c) {
\r
10977 if (d.length && d[d.length - 1].close) {
\r
10978 a = a + '</' + d[d.length - 1].tag + '>';
\r
10984 if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
\r
10987 // Ignore closed ones
\r
10988 if (/\/>$/.test(a))
\r
10991 d.push({tag : c}); // Push start element
\r
10997 // End all open tags
\r
10998 for (i=d.length - 1; i>=0; i--)
\r
10999 s += '</' + d[i].tag + '>';
\r
11006 (function(tinymce) {
\r
11007 // Added for compression purposes
\r
11008 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
\r
11010 tinymce.EditorCommands = function(editor) {
\r
11011 var dom = editor.dom,
\r
11012 selection = editor.selection,
\r
11013 commands = {state: {}, exec : {}, value : {}},
\r
11014 settings = editor.settings,
\r
11017 function execCommand(command, ui, value) {
\r
11020 command = command.toLowerCase();
\r
11021 if (func = commands.exec[command]) {
\r
11022 func(command, ui, value);
\r
11029 function queryCommandState(command) {
\r
11032 command = command.toLowerCase();
\r
11033 if (func = commands.state[command])
\r
11034 return func(command);
\r
11039 function queryCommandValue(command) {
\r
11042 command = command.toLowerCase();
\r
11043 if (func = commands.value[command])
\r
11044 return func(command);
\r
11049 function addCommands(command_list, type) {
\r
11050 type = type || 'exec';
\r
11052 each(command_list, function(callback, command) {
\r
11053 each(command.toLowerCase().split(','), function(command) {
\r
11054 commands[type][command] = callback;
\r
11059 // Expose public methods
\r
11060 tinymce.extend(this, {
\r
11061 execCommand : execCommand,
\r
11062 queryCommandState : queryCommandState,
\r
11063 queryCommandValue : queryCommandValue,
\r
11064 addCommands : addCommands
\r
11067 // Private methods
\r
11069 function execNativeCommand(command, ui, value) {
\r
11070 if (ui === undefined)
\r
11073 if (value === undefined)
\r
11076 return editor.getDoc().execCommand(command, ui, value);
\r
11079 function isFormatMatch(name) {
\r
11080 return editor.formatter.match(name);
\r
11083 function toggleFormat(name, value) {
\r
11084 editor.formatter.toggle(name, value ? {value : value} : undefined);
\r
11087 function storeSelection(type) {
\r
11088 bookmark = selection.getBookmark(type);
\r
11091 function restoreSelection() {
\r
11092 selection.moveToBookmark(bookmark);
\r
11095 // Add execCommand overrides
\r
11097 // Ignore these, added for compatibility
\r
11098 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
\r
11100 // Add undo manager logic
\r
11101 'mceEndUndoLevel,mceAddUndoLevel' : function() {
\r
11102 editor.undoManager.add();
\r
11105 'Cut,Copy,Paste' : function(command) {
\r
11106 var doc = editor.getDoc(), failed;
\r
11108 // Try executing the native command
\r
11110 execNativeCommand(command);
\r
11112 // Command failed
\r
11116 // Present alert message about clipboard access not being available
\r
11117 if (failed || !doc.queryCommandSupported(command)) {
\r
11118 if (tinymce.isGecko) {
\r
11119 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
\r
11121 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
\r
11124 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
\r
11128 // Override unlink command
\r
11129 unlink : function(command) {
\r
11130 if (selection.isCollapsed())
\r
11131 selection.select(selection.getNode());
\r
11133 execNativeCommand(command);
\r
11134 selection.collapse(FALSE);
\r
11137 // Override justify commands to use the text formatter engine
\r
11138 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
11139 var align = command.substring(7);
\r
11141 // Remove all other alignments first
\r
11142 each('left,center,right,full'.split(','), function(name) {
\r
11143 if (align != name)
\r
11144 editor.formatter.remove('align' + name);
\r
11147 toggleFormat('align' + align);
\r
11150 // Override list commands to fix WebKit bug
\r
11151 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
11152 var listElm, listParent;
\r
11154 execNativeCommand(command);
\r
11156 // WebKit produces lists within block elements so we need to split them
\r
11157 // we will replace the native list creation logic to custom logic later on
\r
11158 // TODO: Remove this when the list creation logic is removed
\r
11159 listElm = dom.getParent(selection.getNode(), 'ol,ul');
\r
11161 listParent = listElm.parentNode;
\r
11163 // If list is within a text block then split that block
\r
11164 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
\r
11165 storeSelection();
\r
11166 dom.split(listParent, listElm);
\r
11167 restoreSelection();
\r
11172 // Override commands to use the text formatter engine
\r
11173 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
11174 toggleFormat(command);
\r
11177 // Override commands to use the text formatter engine
\r
11178 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
\r
11179 toggleFormat(command, value);
\r
11182 FontSize : function(command, ui, value) {
\r
11183 var fontClasses, fontSizes;
\r
11185 // Convert font size 1-7 to styles
\r
11186 if (value >= 1 && value <= 7) {
\r
11187 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
11188 fontClasses = tinymce.explode(settings.font_size_classes);
\r
11191 value = fontClasses[value - 1] || value;
\r
11193 value = fontSizes[value - 1] || value;
\r
11196 toggleFormat(command, value);
\r
11199 RemoveFormat : function(command) {
\r
11200 editor.formatter.remove(command);
\r
11203 mceBlockQuote : function(command) {
\r
11204 toggleFormat('blockquote');
\r
11207 FormatBlock : function(command, ui, value) {
\r
11208 return toggleFormat(value || 'p');
\r
11211 mceCleanup : function() {
\r
11212 var bookmark = selection.getBookmark();
\r
11214 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
\r
11216 selection.moveToBookmark(bookmark);
\r
11219 mceRemoveNode : function(command, ui, value) {
\r
11220 var node = value || selection.getNode();
\r
11222 // Make sure that the body node isn't removed
\r
11223 if (node != editor.getBody()) {
\r
11224 storeSelection();
\r
11225 editor.dom.remove(node, TRUE);
\r
11226 restoreSelection();
\r
11230 mceSelectNodeDepth : function(command, ui, value) {
\r
11233 dom.getParent(selection.getNode(), function(node) {
\r
11234 if (node.nodeType == 1 && counter++ == value) {
\r
11235 selection.select(node);
\r
11238 }, editor.getBody());
\r
11241 mceSelectNode : function(command, ui, value) {
\r
11242 selection.select(value);
\r
11245 mceInsertContent : function(command, ui, value) {
\r
11246 selection.setContent(value);
\r
11249 mceInsertRawHTML : function(command, ui, value) {
\r
11250 selection.setContent('tiny_mce_marker');
\r
11251 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value));
\r
11254 mceSetContent : function(command, ui, value) {
\r
11255 editor.setContent(value);
\r
11258 'Indent,Outdent' : function(command) {
\r
11259 var intentValue, indentUnit, value;
\r
11261 // Setup indent level
\r
11262 intentValue = settings.indentation;
\r
11263 indentUnit = /[a-z%]+$/i.exec(intentValue);
\r
11264 intentValue = parseInt(intentValue);
\r
11266 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
\r
11267 each(selection.getSelectedBlocks(), function(element) {
\r
11268 if (command == 'outdent') {
\r
11269 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
\r
11270 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
\r
11272 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
\r
11275 execNativeCommand(command);
\r
11278 mceRepaint : function() {
\r
11281 if (tinymce.isGecko) {
\r
11283 storeSelection(TRUE);
\r
11285 if (selection.getSel())
\r
11286 selection.getSel().selectAllChildren(editor.getBody());
\r
11288 selection.collapse(TRUE);
\r
11289 restoreSelection();
\r
11296 mceToggleFormat : function(command, ui, value) {
\r
11297 editor.formatter.toggle(value);
\r
11300 InsertHorizontalRule : function() {
\r
11301 selection.setContent('<hr />');
\r
11304 mceToggleVisualAid : function() {
\r
11305 editor.hasVisual = !editor.hasVisual;
\r
11306 editor.addVisual();
\r
11309 mceReplaceContent : function(command, ui, value) {
\r
11310 selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
\r
11313 mceInsertLink : function(command, ui, value) {
\r
11314 var link = dom.getParent(selection.getNode(), 'a');
\r
11316 if (tinymce.is(value, 'string'))
\r
11317 value = {href : value};
\r
11320 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
\r
11321 each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
\r
11322 dom.setAttribs(link, value);
\r
11326 dom.setAttribs(link, value);
\r
11328 editor.dom.remove(link, TRUE);
\r
11332 selectAll : function() {
\r
11333 var root = dom.getRoot(), rng = dom.createRng();
\r
11335 rng.setStart(root, 0);
\r
11336 rng.setEnd(root, root.childNodes.length);
\r
11338 editor.selection.setRng(rng);
\r
11342 // Add queryCommandState overrides
\r
11344 // Override justify commands
\r
11345 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
11346 return isFormatMatch('align' + command.substring(7));
\r
11349 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
11350 return isFormatMatch(command);
\r
11353 mceBlockQuote : function() {
\r
11354 return isFormatMatch('blockquote');
\r
11357 Outdent : function() {
\r
11360 if (settings.inline_styles) {
\r
11361 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
11364 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
11368 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
\r
11371 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
11372 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
\r
11376 // Add queryCommandValue overrides
\r
11378 'FontSize,FontName' : function(command) {
\r
11379 var value = 0, parent;
\r
11381 if (parent = dom.getParent(selection.getNode(), 'span')) {
\r
11382 if (command == 'fontsize')
\r
11383 value = parent.style.fontSize;
\r
11385 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
\r
11392 // Add undo manager logic
\r
11393 if (settings.custom_undo_redo) {
\r
11395 Undo : function() {
\r
11396 editor.undoManager.undo();
\r
11399 Redo : function() {
\r
11400 editor.undoManager.redo();
\r
11406 (function(tinymce) {
\r
11407 var Dispatcher = tinymce.util.Dispatcher;
\r
11409 tinymce.UndoManager = function(editor) {
\r
11410 var self, index = 0, data = [];
\r
11412 function getContent() {
\r
11413 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
\r
11419 onAdd : new Dispatcher(self),
\r
11420 onUndo : new Dispatcher(self),
\r
11421 onRedo : new Dispatcher(self),
\r
11423 add : function(level) {
\r
11424 var i, settings = editor.settings, lastLevel;
\r
11426 level = level || {};
\r
11427 level.content = getContent();
\r
11429 // Add undo level if needed
\r
11430 lastLevel = data[index];
\r
11431 if (lastLevel && lastLevel.content == level.content) {
\r
11432 if (index > 0 || data.length == 1)
\r
11436 // Time to compress
\r
11437 if (settings.custom_undo_redo_levels) {
\r
11438 if (data.length > settings.custom_undo_redo_levels) {
\r
11439 for (i = 0; i < data.length - 1; i++)
\r
11440 data[i] = data[i + 1];
\r
11443 index = data.length;
\r
11447 // Get a non intrusive normalized bookmark
\r
11448 level.bookmark = editor.selection.getBookmark(2, true);
\r
11450 // Crop array if needed
\r
11451 if (index < data.length - 1) {
\r
11452 // Treat first level as initial
\r
11456 data.length = index + 1;
\r
11459 data.push(level);
\r
11460 index = data.length - 1;
\r
11462 self.onAdd.dispatch(self, level);
\r
11463 editor.isNotDirty = 0;
\r
11468 undo : function() {
\r
11471 if (self.typing) {
\r
11477 level = data[--index];
\r
11479 editor.setContent(level.content, {format : 'raw'});
\r
11480 editor.selection.moveToBookmark(level.bookmark);
\r
11482 self.onUndo.dispatch(self, level);
\r
11488 redo : function() {
\r
11491 if (index < data.length - 1) {
\r
11492 level = data[++index];
\r
11494 editor.setContent(level.content, {format : 'raw'});
\r
11495 editor.selection.moveToBookmark(level.bookmark);
\r
11497 self.onRedo.dispatch(self, level);
\r
11503 clear : function() {
\r
11505 index = self.typing = 0;
\r
11508 hasUndo : function() {
\r
11509 return index > 0 || self.typing;
\r
11512 hasRedo : function() {
\r
11513 return index < data.length - 1;
\r
11519 (function(tinymce) {
\r
11521 var Event = tinymce.dom.Event,
\r
11522 isIE = tinymce.isIE,
\r
11523 isGecko = tinymce.isGecko,
\r
11524 isOpera = tinymce.isOpera,
\r
11525 each = tinymce.each,
\r
11526 extend = tinymce.extend,
\r
11530 function cloneFormats(node) {
\r
11531 var clone, temp, inner;
\r
11534 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
\r
11536 temp = node.cloneNode(false);
\r
11537 temp.appendChild(clone);
\r
11540 clone = inner = node.cloneNode(false);
\r
11543 clone.removeAttribute('id');
\r
11545 } while (node = node.parentNode);
\r
11548 return {wrapper : clone, inner : inner};
\r
11551 // Checks if the selection/caret is at the end of the specified block element
\r
11552 function isAtEnd(rng, par) {
\r
11553 var rng2 = par.ownerDocument.createRange();
\r
11555 rng2.setStart(rng.endContainer, rng.endOffset);
\r
11556 rng2.setEndAfter(par);
\r
11558 // 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
11559 return rng2.cloneContents().textContent.length == 0;
\r
11562 function isEmpty(n) {
\r
11565 n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
\r
11566 n = n.replace(/<[^>]+>/g, ''); // Remove all tags
\r
11568 return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
\r
11571 function splitList(selection, dom, li) {
\r
11572 var listBlock, block;
\r
11574 if (isEmpty(li)) {
\r
11575 listBlock = dom.getParent(li, 'ul,ol');
\r
11577 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
\r
11578 dom.split(listBlock, li);
\r
11579 block = dom.create('p', 0, '<br _mce_bogus="1" />');
\r
11580 dom.replace(block, li);
\r
11581 selection.select(block, 1);
\r
11590 tinymce.create('tinymce.ForceBlocks', {
\r
11591 ForceBlocks : function(ed) {
\r
11592 var t = this, s = ed.settings, elm;
\r
11596 elm = (s.forced_root_block || 'p').toLowerCase();
\r
11597 s.element = elm.toUpperCase();
\r
11599 ed.onPreInit.add(t.setup, t);
\r
11601 t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi');
\r
11602 t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11603 t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11604 t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi');
\r
11605 t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11607 function padd(ed, o) {
\r
11609 o.content = o.content.replace(t.reOpera, '</' + elm + '>');
\r
11611 o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
\r
11613 if (!isIE && !isOpera && o.set) {
\r
11614 // Use instead of BR in padded paragraphs
\r
11615 o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
\r
11616 o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
\r
11618 o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
\r
11621 ed.onBeforeSetContent.add(padd);
\r
11622 ed.onPostProcess.add(padd);
\r
11624 if (s.forced_root_block) {
\r
11625 ed.onInit.add(t.forceRoots, t);
\r
11626 ed.onSetContent.add(t.forceRoots, t);
\r
11627 ed.onBeforeGetContent.add(t.forceRoots, t);
\r
11631 setup : function() {
\r
11632 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
\r
11634 // Force root blocks when typing and when getting output
\r
11635 if (s.forced_root_block) {
\r
11636 ed.onBeforeExecCommand.add(t.forceRoots, t);
\r
11637 ed.onKeyUp.add(t.forceRoots, t);
\r
11638 ed.onPreProcess.add(t.forceRoots, t);
\r
11641 if (s.force_br_newlines) {
\r
11642 // Force IE to produce BRs on enter
\r
11644 ed.onKeyPress.add(function(ed, e) {
\r
11647 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
\r
11648 selection.setContent('<br id="__" /> ', {format : 'raw'});
\r
11649 n = dom.get('__');
\r
11650 n.removeAttribute('id');
\r
11651 selection.select(n);
\r
11652 selection.collapse();
\r
11653 return Event.cancel(e);
\r
11659 if (s.force_p_newlines) {
\r
11661 ed.onKeyPress.add(function(ed, e) {
\r
11662 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
\r
11666 // Ungly hack to for IE to preserve the formatting when you press
\r
11667 // enter at the end of a block element with formatted contents
\r
11668 // This logic overrides the browsers default logic with
\r
11669 // custom logic that enables us to control the output
\r
11670 tinymce.addUnload(function() {
\r
11671 t._previousFormats = 0; // Fix IE leak
\r
11674 ed.onKeyPress.add(function(ed, e) {
\r
11675 t._previousFormats = 0;
\r
11677 // Clone the current formats, this will later be applied to the new block contents
\r
11678 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
\r
11679 t._previousFormats = cloneFormats(ed.selection.getStart());
\r
11682 ed.onKeyUp.add(function(ed, e) {
\r
11683 // Let IE break the element and the wrap the new caret location in the previous formats
\r
11684 if (e.keyCode == 13 && !e.shiftKey) {
\r
11685 var parent = ed.selection.getStart(), fmt = t._previousFormats;
\r
11687 // Parent is an empty block
\r
11688 if (!parent.hasChildNodes()) {
\r
11689 parent = dom.getParent(parent, dom.isBlock);
\r
11692 parent.innerHTML = '';
\r
11694 if (t._previousFormats) {
\r
11695 parent.appendChild(fmt.wrapper);
\r
11696 fmt.inner.innerHTML = '\uFEFF';
\r
11698 parent.innerHTML = '\uFEFF';
\r
11700 selection.select(parent, 1);
\r
11701 ed.getDoc().execCommand('Delete', false, null);
\r
11709 ed.onKeyDown.add(function(ed, e) {
\r
11710 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
\r
11711 t.backspaceDelete(e, e.keyCode == 8);
\r
11716 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
\r
11717 if (tinymce.isWebKit) {
\r
11718 function insertBr(ed) {
\r
11719 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
\r
11721 // Insert BR element
\r
11722 rng.insertNode(br = dom.create('br'));
\r
11724 // Place caret after BR
\r
11725 rng.setStartAfter(br);
\r
11726 rng.setEndAfter(br);
\r
11727 selection.setRng(rng);
\r
11729 // Could not place caret after BR then insert an nbsp entity and move the caret
\r
11730 if (selection.getSel().focusNode == br.previousSibling) {
\r
11731 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
\r
11732 selection.collapse(TRUE);
\r
11735 // Create a temporary DIV after the BR and get the position as it
\r
11736 // seems like getPos() returns 0 for text nodes and BR elements.
\r
11737 dom.insertAfter(div, br);
\r
11738 divYPos = dom.getPos(div).y;
\r
11741 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
\r
11742 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
\r
11743 ed.getWin().scrollTo(0, divYPos);
\r
11746 ed.onKeyPress.add(function(ed, e) {
\r
11747 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
\r
11754 // Padd empty inline elements within block elements
\r
11755 // For example: <p><strong><em></em></strong></p> becomes <p><strong><em> </em></strong></p>
\r
11756 ed.onPreProcess.add(function(ed, o) {
\r
11757 each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
\r
11758 if (isEmpty(p)) {
\r
11759 each(dom.select('span,em,strong,b,i', o.node), function(n) {
\r
11760 if (!n.hasChildNodes()) {
\r
11761 n.appendChild(ed.getDoc().createTextNode('\u00a0'));
\r
11762 return FALSE; // Break the loop one padding is enough
\r
11769 // IE specific fixes
\r
11771 // Replaces IE:s auto generated paragraphs with the specified element name
\r
11772 if (s.element != 'P') {
\r
11773 ed.onKeyPress.add(function(ed, e) {
\r
11774 t.lastElm = selection.getNode().nodeName;
\r
11777 ed.onKeyUp.add(function(ed, e) {
\r
11778 var bl, n = selection.getNode(), b = ed.getBody();
\r
11780 if (b.childNodes.length === 1 && n.nodeName == 'P') {
\r
11781 n = dom.rename(n, s.element);
\r
11782 selection.select(n);
\r
11783 selection.collapse();
\r
11784 ed.nodeChanged();
\r
11785 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
\r
11786 bl = dom.getParent(n, 'p');
\r
11789 dom.rename(bl, s.element);
\r
11790 ed.nodeChanged();
\r
11798 find : function(n, t, s) {
\r
11799 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
\r
11801 while (n = w.nextNode()) {
\r
11805 if (t == 0 && n == s)
\r
11809 if (t == 1 && c == s)
\r
11816 forceRoots : function(ed, e) {
\r
11817 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
11818 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
\r
11820 // Fix for bug #1863847
\r
11821 //if (e && e.keyCode == 13)
\r
11824 // Wrap non blocks into blocks
\r
11825 for (i = nl.length - 1; i >= 0; i--) {
\r
11828 // Ignore internal elements
\r
11829 if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
\r
11834 // Is text or non block element
\r
11835 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
\r
11837 // Create new block but ignore whitespace
\r
11838 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
\r
11839 // Store selection
\r
11840 if (si == -2 && r) {
\r
11842 // If selection is element then mark it
\r
11843 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
\r
11844 // Save the id of the selected element
\r
11845 eid = n.getAttribute("id");
\r
11846 n.setAttribute("id", "__mce");
\r
11848 // If element is inside body, might not be the case in contentEdiable mode
\r
11849 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
\r
11850 so = r.startOffset;
\r
11851 eo = r.endOffset;
\r
11852 si = t.find(b, 0, r.startContainer);
\r
11853 ei = t.find(b, 0, r.endContainer);
\r
11857 // Force control range into text range
\r
11859 tr = d.body.createTextRange();
\r
11860 tr.moveToElementText(r.item(0));
\r
11864 tr = d.body.createTextRange();
\r
11865 tr.moveToElementText(b);
\r
11867 bp = tr.move('character', c) * -1;
\r
11869 tr = r.duplicate();
\r
11871 sp = tr.move('character', c) * -1;
\r
11873 tr = r.duplicate();
\r
11875 le = (tr.move('character', c) * -1) - sp;
\r
11882 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
\r
11883 // See: http://support.microsoft.com/kb/829907
\r
11884 bl = ed.dom.create(ed.settings.forced_root_block);
\r
11885 nx.parentNode.replaceChild(bl, nx);
\r
11886 bl.appendChild(nx);
\r
11889 if (bl.hasChildNodes())
\r
11890 bl.insertBefore(nx, bl.firstChild);
\r
11892 bl.appendChild(nx);
\r
11895 bl = null; // Time to create new block
\r
11898 // Restore selection
\r
11901 bl = b.getElementsByTagName(ed.settings.element)[0];
\r
11902 r = d.createRange();
\r
11904 // Select last location or generated block
\r
11906 r.setStart(t.find(b, 1, si), so);
\r
11908 r.setStart(bl, 0);
\r
11910 // Select last location or generated block
\r
11912 r.setEnd(t.find(b, 1, ei), eo);
\r
11917 s.removeAllRanges();
\r
11922 r = s.createRange();
\r
11923 r.moveToElementText(b);
\r
11925 r.moveStart('character', si);
\r
11926 r.moveEnd('character', ei);
\r
11932 } else if (!isIE && (n = ed.dom.get('__mce'))) {
\r
11933 // Restore the id of the selected element
\r
11935 n.setAttribute('id', eid);
\r
11937 n.removeAttribute('id');
\r
11939 // Move caret before selected element
\r
11940 r = d.createRange();
\r
11941 r.setStartBefore(n);
\r
11942 r.setEndBefore(n);
\r
11947 getParentBlock : function(n) {
\r
11948 var d = this.dom;
\r
11950 return d.getParent(n, d.isBlock);
\r
11953 insertPara : function(e) {
\r
11954 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
11955 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
11957 // If root blocks are forced then use Operas default behavior since it's really good
\r
11958 // Removed due to bug: #1853816
\r
11959 // if (se.forced_root_block && isOpera)
\r
11962 // Setup before range
\r
11963 rb = d.createRange();
\r
11965 // If is before the first block element and in body, then move it into first block element
\r
11966 rb.setStart(s.anchorNode, s.anchorOffset);
\r
11967 rb.collapse(TRUE);
\r
11969 // Setup after range
\r
11970 ra = d.createRange();
\r
11972 // If is before the first block element and in body, then move it into first block element
\r
11973 ra.setStart(s.focusNode, s.focusOffset);
\r
11974 ra.collapse(TRUE);
\r
11976 // Setup start/end points
\r
11977 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
\r
11978 sn = dir ? s.anchorNode : s.focusNode;
\r
11979 so = dir ? s.anchorOffset : s.focusOffset;
\r
11980 en = dir ? s.focusNode : s.anchorNode;
\r
11981 eo = dir ? s.focusOffset : s.anchorOffset;
\r
11983 // If selection is in empty table cell
\r
11984 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
\r
11985 if (sn.firstChild.nodeName == 'BR')
\r
11986 dom.remove(sn.firstChild); // Remove BR
\r
11988 // Create two new block elements
\r
11989 if (sn.childNodes.length == 0) {
\r
11990 ed.dom.add(sn, se.element, null, '<br />');
\r
11991 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
11993 n = sn.innerHTML;
\r
11994 sn.innerHTML = '';
\r
11995 ed.dom.add(sn, se.element, null, n);
\r
11996 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
11999 // Move caret into the last one
\r
12000 r = d.createRange();
\r
12001 r.selectNodeContents(aft);
\r
12003 ed.selection.setRng(r);
\r
12008 // If the caret is in an invalid location in FF we need to move it into the first block
\r
12009 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
\r
12010 sn = en = sn.firstChild;
\r
12012 rb = d.createRange();
\r
12013 rb.setStart(sn, 0);
\r
12014 ra = d.createRange();
\r
12015 ra.setStart(en, 0);
\r
12018 // Never use body as start or end node
\r
12019 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
12020 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
\r
12021 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
12022 en = en.nodeName == "BODY" ? en.firstChild : en;
\r
12024 // Get start and end blocks
\r
12025 sb = t.getParentBlock(sn);
\r
12026 eb = t.getParentBlock(en);
\r
12027 bn = sb ? sb.nodeName : se.element; // Get block name to create
\r
12029 // Return inside list use default browser behavior
\r
12030 if (n = t.dom.getParent(sb, 'li,pre')) {
\r
12031 if (n.nodeName == 'LI')
\r
12032 return splitList(ed.selection, t.dom, n);
\r
12037 // If caption or absolute layers then always generate new blocks within
\r
12038 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
12043 // If caption or absolute layers then always generate new blocks within
\r
12044 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
12050 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
\r
12055 // Setup new before and after blocks
\r
12056 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
\r
12057 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
\r
12059 // Remove id from after clone
\r
12060 aft.removeAttribute('id');
\r
12062 // Is header and cursor is at the end, then force paragraph under
\r
12063 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
\r
12064 aft = ed.dom.create(se.element);
\r
12066 // Find start chop node
\r
12069 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
12073 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
\r
12075 // Find end chop node
\r
12078 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
12082 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
\r
12084 // Place first chop part into before block element
\r
12085 if (sc.nodeName == bn)
\r
12086 rb.setStart(sc, 0);
\r
12088 rb.setStartBefore(sc);
\r
12090 rb.setEnd(sn, so);
\r
12091 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
12093 // Place secnd chop part within new block element
\r
12095 ra.setEndAfter(ec);
\r
12097 //console.debug(s.focusNode, s.focusOffset);
\r
12100 ra.setStart(en, eo);
\r
12101 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
12103 // Create range around everything
\r
12104 r = d.createRange();
\r
12105 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
\r
12106 r.setStartBefore(sc.parentNode);
\r
12108 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
\r
12109 r.setStartBefore(rb.startContainer);
\r
12111 r.setStart(rb.startContainer, rb.startOffset);
\r
12114 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
\r
12115 r.setEndAfter(ec.parentNode);
\r
12117 r.setEnd(ra.endContainer, ra.endOffset);
\r
12119 // Delete and replace it with new block elements
\r
12120 r.deleteContents();
\r
12123 ed.getWin().scrollTo(0, vp.y);
\r
12125 // Never wrap blocks in blocks
\r
12126 if (bef.firstChild && bef.firstChild.nodeName == bn)
\r
12127 bef.innerHTML = bef.firstChild.innerHTML;
\r
12129 if (aft.firstChild && aft.firstChild.nodeName == bn)
\r
12130 aft.innerHTML = aft.firstChild.innerHTML;
\r
12132 // Padd empty blocks
\r
12133 if (isEmpty(bef))
\r
12134 bef.innerHTML = '<br />';
\r
12136 function appendStyles(e, en) {
\r
12137 var nl = [], nn, n, i;
\r
12139 e.innerHTML = '';
\r
12141 // Make clones of style elements
\r
12142 if (se.keep_styles) {
\r
12145 // We only want style specific elements
\r
12146 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
\r
12147 nn = n.cloneNode(FALSE);
\r
12148 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
\r
12151 } while (n = n.parentNode);
\r
12154 // Append style elements to aft
\r
12155 if (nl.length > 0) {
\r
12156 for (i = nl.length - 1, nn = e; i >= 0; i--)
\r
12157 nn = nn.appendChild(nl[i]);
\r
12159 // Padd most inner style element
\r
12160 nl[0].innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
12161 return nl[0]; // Move caret to most inner element
\r
12163 e.innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
12166 // Fill empty afterblook with current style
\r
12167 if (isEmpty(aft))
\r
12168 car = appendStyles(aft, en);
\r
12170 // Opera needs this one backwards for older versions
\r
12171 if (isOpera && parseFloat(opera.version()) < 9.5) {
\r
12172 r.insertNode(bef);
\r
12173 r.insertNode(aft);
\r
12175 r.insertNode(aft);
\r
12176 r.insertNode(bef);
\r
12183 function first(n) {
\r
12184 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
\r
12187 // Move cursor and scroll into view
\r
12188 r = d.createRange();
\r
12189 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
\r
12191 s.removeAllRanges();
\r
12194 // 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
12195 y = ed.dom.getPos(aft).y;
\r
12196 ch = aft.clientHeight;
\r
12198 // Is element within viewport
\r
12199 if (y < vp.y || y + ch > vp.y + vp.h) {
\r
12200 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
12201 //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
12207 backspaceDelete : function(e, bs) {
\r
12208 var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;
\r
12210 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
\r
12211 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
\r
12212 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
\r
12214 // Walk the dom backwards until we find a text node
\r
12215 for (n = sc.lastChild; n; n = walker.prev()) {
\r
12216 if (n.nodeType == 3) {
\r
12217 r.setStart(n, n.nodeValue.length);
\r
12218 r.collapse(true);
\r
12225 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
\r
12226 // This workaround removes the element by hand and moves the caret to the previous element
\r
12227 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
\r
12228 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
\r
12229 // Find previous block element
\r
12231 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
\r
12234 if (sc != b.firstChild) {
\r
12235 // Find last text node
\r
12236 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
\r
12237 while (tn = w.nextNode())
\r
12240 // Place caret at the end of last text node
\r
12241 r = ed.getDoc().createRange();
\r
12242 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
\r
12243 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
\r
12246 // Remove the target container
\r
12247 ed.dom.remove(sc);
\r
12250 return Event.cancel(e);
\r
12258 (function(tinymce) {
\r
12260 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
\r
12262 tinymce.create('tinymce.ControlManager', {
\r
12263 ControlManager : function(ed, s) {
\r
12269 t.onAdd = new tinymce.util.Dispatcher(t);
\r
12270 t.onPostRender = new tinymce.util.Dispatcher(t);
\r
12271 t.prefix = s.prefix || ed.id + '_';
\r
12274 t.onPostRender.add(function() {
\r
12275 each(t.controls, function(c) {
\r
12281 get : function(id) {
\r
12282 return this.controls[this.prefix + id] || this.controls[id];
\r
12285 setActive : function(id, s) {
\r
12288 if (c = this.get(id))
\r
12294 setDisabled : function(id, s) {
\r
12297 if (c = this.get(id))
\r
12298 c.setDisabled(s);
\r
12303 add : function(c) {
\r
12307 t.controls[c.id] = c;
\r
12308 t.onAdd.dispatch(c, t);
\r
12314 createControl : function(n) {
\r
12315 var c, t = this, ed = t.editor;
\r
12317 each(ed.plugins, function(p) {
\r
12318 if (p.createControl) {
\r
12319 c = p.createControl(n, t);
\r
12328 case "separator":
\r
12329 return t.createSeparator();
\r
12332 if (!c && ed.buttons && (c = ed.buttons[n]))
\r
12333 return t.createButton(n, c);
\r
12338 createDropMenu : function(id, s, cc) {
\r
12339 var t = this, ed = t.editor, c, bm, v, cls;
\r
12342 'class' : 'mceDropDown',
\r
12343 constrain : ed.settings.constrain_menus
\r
12346 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
\r
12347 if (v = ed.getParam('skin_variant'))
\r
12348 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
\r
12350 id = t.prefix + id;
\r
12351 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
\r
12352 c = t.controls[id] = new cls(id, s);
\r
12353 c.onAddItem.add(function(c, o) {
\r
12354 var s = o.settings;
\r
12356 s.title = ed.getLang(s.title, s.title);
\r
12358 if (!s.onclick) {
\r
12359 s.onclick = function(v) {
\r
12361 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
12366 ed.onRemove.add(function() {
\r
12370 // Fix for bug #1897785, #1898007
\r
12371 if (tinymce.isIE) {
\r
12372 c.onShowMenu.add(function() {
\r
12373 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
12376 bm = ed.selection.getBookmark(1);
\r
12379 c.onHideMenu.add(function() {
\r
12381 ed.selection.moveToBookmark(bm);
\r
12390 createListBox : function(id, s, cc) {
\r
12391 var t = this, ed = t.editor, cmd, c, cls;
\r
12396 s.title = ed.translate(s.title);
\r
12397 s.scope = s.scope || ed;
\r
12399 if (!s.onselect) {
\r
12400 s.onselect = function(v) {
\r
12401 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12407 'class' : 'mce_' + id,
\r
12409 control_manager : t
\r
12412 id = t.prefix + id;
\r
12414 if (ed.settings.use_native_selects)
\r
12415 c = new tinymce.ui.NativeListBox(id, s);
\r
12417 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
\r
12418 c = new cls(id, s);
\r
12421 t.controls[id] = c;
\r
12423 // Fix focus problem in Safari
\r
12424 if (tinymce.isWebKit) {
\r
12425 c.onPostRender.add(function(c, n) {
\r
12426 // Store bookmark on mousedown
\r
12427 Event.add(n, 'mousedown', function() {
\r
12428 ed.bookmark = ed.selection.getBookmark(1);
\r
12431 // Restore on focus, since it might be lost
\r
12432 Event.add(n, 'focus', function() {
\r
12433 ed.selection.moveToBookmark(ed.bookmark);
\r
12434 ed.bookmark = null;
\r
12440 ed.onMouseDown.add(c.hideMenu, c);
\r
12445 createButton : function(id, s, cc) {
\r
12446 var t = this, ed = t.editor, o, c, cls;
\r
12451 s.title = ed.translate(s.title);
\r
12452 s.label = ed.translate(s.label);
\r
12453 s.scope = s.scope || ed;
\r
12455 if (!s.onclick && !s.menu_button) {
\r
12456 s.onclick = function() {
\r
12457 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
12463 'class' : 'mce_' + id,
\r
12464 unavailable_prefix : ed.getLang('unavailable', ''),
\r
12466 control_manager : t
\r
12469 id = t.prefix + id;
\r
12471 if (s.menu_button) {
\r
12472 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
\r
12473 c = new cls(id, s);
\r
12474 ed.onMouseDown.add(c.hideMenu, c);
\r
12476 cls = t._cls.button || tinymce.ui.Button;
\r
12477 c = new cls(id, s);
\r
12483 createMenuButton : function(id, s, cc) {
\r
12485 s.menu_button = 1;
\r
12487 return this.createButton(id, s, cc);
\r
12490 createSplitButton : function(id, s, cc) {
\r
12491 var t = this, ed = t.editor, cmd, c, cls;
\r
12496 s.title = ed.translate(s.title);
\r
12497 s.scope = s.scope || ed;
\r
12499 if (!s.onclick) {
\r
12500 s.onclick = function(v) {
\r
12501 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12505 if (!s.onselect) {
\r
12506 s.onselect = function(v) {
\r
12507 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12513 'class' : 'mce_' + id,
\r
12515 control_manager : t
\r
12518 id = t.prefix + id;
\r
12519 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
\r
12520 c = t.add(new cls(id, s));
\r
12521 ed.onMouseDown.add(c.hideMenu, c);
\r
12526 createColorSplitButton : function(id, s, cc) {
\r
12527 var t = this, ed = t.editor, cmd, c, cls, bm;
\r
12532 s.title = ed.translate(s.title);
\r
12533 s.scope = s.scope || ed;
\r
12535 if (!s.onclick) {
\r
12536 s.onclick = function(v) {
\r
12537 if (tinymce.isIE)
\r
12538 bm = ed.selection.getBookmark(1);
\r
12540 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12544 if (!s.onselect) {
\r
12545 s.onselect = function(v) {
\r
12546 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12552 'class' : 'mce_' + id,
\r
12553 'menu_class' : ed.getParam('skin') + 'Skin',
\r
12555 more_colors_title : ed.getLang('more_colors')
\r
12558 id = t.prefix + id;
\r
12559 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
\r
12560 c = new cls(id, s);
\r
12561 ed.onMouseDown.add(c.hideMenu, c);
\r
12563 // Remove the menu element when the editor is removed
\r
12564 ed.onRemove.add(function() {
\r
12568 // Fix for bug #1897785, #1898007
\r
12569 if (tinymce.isIE) {
\r
12570 c.onShowMenu.add(function() {
\r
12571 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
12573 bm = ed.selection.getBookmark(1);
\r
12576 c.onHideMenu.add(function() {
\r
12578 ed.selection.moveToBookmark(bm);
\r
12587 createToolbar : function(id, s, cc) {
\r
12588 var c, t = this, cls;
\r
12590 id = t.prefix + id;
\r
12591 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
\r
12592 c = new cls(id, s);
\r
12600 createSeparator : function(cc) {
\r
12601 var cls = cc || this._cls.separator || tinymce.ui.Separator;
\r
12603 return new cls();
\r
12606 setControlType : function(n, c) {
\r
12607 return this._cls[n.toLowerCase()] = c;
\r
12610 destroy : function() {
\r
12611 each(this.controls, function(c) {
\r
12615 this.controls = null;
\r
12620 (function(tinymce) {
\r
12621 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
\r
12623 tinymce.create('tinymce.WindowManager', {
\r
12624 WindowManager : function(ed) {
\r
12628 t.onOpen = new Dispatcher(t);
\r
12629 t.onClose = new Dispatcher(t);
\r
12634 open : function(s, p) {
\r
12635 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
\r
12637 // Default some options
\r
12640 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
\r
12641 sh = isOpera ? vp.h : screen.height;
\r
12642 s.name = s.name || 'mc_' + new Date().getTime();
\r
12643 s.width = parseInt(s.width || 320);
\r
12644 s.height = parseInt(s.height || 240);
\r
12645 s.resizable = true;
\r
12646 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
\r
12647 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
\r
12648 p.inline = false;
\r
12649 p.mce_width = s.width;
\r
12650 p.mce_height = s.height;
\r
12651 p.mce_auto_focus = s.auto_focus;
\r
12657 s.dialogWidth = s.width + 'px';
\r
12658 s.dialogHeight = s.height + 'px';
\r
12659 s.scroll = s.scrollbars || false;
\r
12663 // Build features string
\r
12664 each(s, function(v, k) {
\r
12665 if (tinymce.is(v, 'boolean'))
\r
12666 v = v ? 'yes' : 'no';
\r
12668 if (!/^(name|url)$/.test(k)) {
\r
12670 f += (f ? ';' : '') + k + ':' + v;
\r
12672 f += (f ? ',' : '') + k + '=' + v;
\r
12678 t.onOpen.dispatch(t, s, p);
\r
12680 u = s.url || s.file;
\r
12681 u = tinymce._addVer(u);
\r
12684 if (isIE && mo) {
\r
12686 window.showModalDialog(u, window, f);
\r
12688 w = window.open(u, s.name, f);
\r
12694 alert(t.editor.getLang('popup_blocked'));
\r
12697 close : function(w) {
\r
12699 this.onClose.dispatch(this);
\r
12702 createInstance : function(cl, a, b, c, d, e) {
\r
12703 var f = tinymce.resolve(cl);
\r
12705 return new f(a, b, c, d, e);
\r
12708 confirm : function(t, cb, s, w) {
\r
12711 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
\r
12714 alert : function(tx, cb, s, w) {
\r
12718 w.alert(t._decode(t.editor.getLang(tx, tx)));
\r
12724 resizeBy : function(dw, dh, win) {
\r
12725 win.resizeBy(dw, dh);
\r
12728 // Internal functions
\r
12730 _decode : function(s) {
\r
12731 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
\r
12735 (function(tinymce) {
\r
12736 function CommandManager() {
\r
12737 var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
\r
12739 function add(collection, cmd, func, scope) {
\r
12740 if (typeof(cmd) == 'string')
\r
12743 tinymce.each(cmd, function(cmd) {
\r
12744 collection[cmd.toLowerCase()] = {func : func, scope : scope};
\r
12748 tinymce.extend(this, {
\r
12749 add : function(cmd, func, scope) {
\r
12750 add(execCommands, cmd, func, scope);
\r
12753 addQueryStateHandler : function(cmd, func, scope) {
\r
12754 add(queryStateCommands, cmd, func, scope);
\r
12757 addQueryValueHandler : function(cmd, func, scope) {
\r
12758 add(queryValueCommands, cmd, func, scope);
\r
12761 execCommand : function(scope, cmd, ui, value, args) {
\r
12762 if (cmd = execCommands[cmd.toLowerCase()]) {
\r
12763 if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
\r
12768 queryCommandValue : function() {
\r
12769 if (cmd = queryValueCommands[cmd.toLowerCase()])
\r
12770 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
12773 queryCommandState : function() {
\r
12774 if (cmd = queryStateCommands[cmd.toLowerCase()])
\r
12775 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
12780 tinymce.GlobalCommands = new CommandManager();
\r
12782 (function(tinymce) {
\r
12783 tinymce.Formatter = function(ed) {
\r
12784 var formats = {},
\r
12785 each = tinymce.each,
\r
12787 selection = ed.selection,
\r
12788 TreeWalker = tinymce.dom.TreeWalker,
\r
12789 rangeUtils = new tinymce.dom.RangeUtils(dom),
\r
12790 isValid = ed.schema.isValid,
\r
12791 isBlock = dom.isBlock,
\r
12792 forcedRootBlock = ed.settings.forced_root_block,
\r
12793 nodeIndex = dom.nodeIndex,
\r
12794 INVISIBLE_CHAR = '\uFEFF',
\r
12795 MCE_ATTR_RE = /^(src|href|style)$/,
\r
12799 pendingFormats = {apply : [], remove : []};
\r
12801 function isArray(obj) {
\r
12802 return obj instanceof Array;
\r
12805 function getParents(node, selector) {
\r
12806 return dom.getParents(node, selector, dom.getRoot());
\r
12809 function isCaretNode(node) {
\r
12810 return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
\r
12813 // Public functions
\r
12815 function get(name) {
\r
12816 return name ? formats[name] : formats;
\r
12819 function register(name, format) {
\r
12821 if (typeof(name) !== 'string') {
\r
12822 each(name, function(format, name) {
\r
12823 register(name, format);
\r
12826 // Force format into array and add it to internal collection
\r
12827 format = format.length ? format : [format];
\r
12829 each(format, function(format) {
\r
12830 // Set deep to false by default on selector formats this to avoid removing
\r
12831 // alignment on images inside paragraphs when alignment is changed on paragraphs
\r
12832 if (format.deep === undefined)
\r
12833 format.deep = !format.selector;
\r
12835 // Default to true
\r
12836 if (format.split === undefined)
\r
12837 format.split = !format.selector || format.inline;
\r
12839 // Default to true
\r
12840 if (format.remove === undefined && format.selector && !format.inline)
\r
12841 format.remove = 'none';
\r
12843 // Mark format as a mixed format inline + block level
\r
12844 if (format.selector && format.inline) {
\r
12845 format.mixed = true;
\r
12846 format.block_expand = true;
\r
12849 // Split classes if needed
\r
12850 if (typeof(format.classes) === 'string')
\r
12851 format.classes = format.classes.split(/\s+/);
\r
12854 formats[name] = format;
\r
12859 function apply(name, vars, node) {
\r
12860 var formatList = get(name), format = formatList[0], bookmark, rng, i;
\r
12862 function moveStart(rng) {
\r
12863 var container = rng.startContainer,
\r
12864 offset = rng.startOffset,
\r
12867 // Move startContainer/startOffset in to a suitable node
\r
12868 if (container.nodeType == 1 || container.nodeValue === "") {
\r
12869 container = container.nodeType == 1 ? container.childNodes[offset] : container;
\r
12871 // Might fail if the offset is behind the last element in it's container
\r
12873 walker = new TreeWalker(container, container.parentNode);
\r
12874 for (node = walker.current(); node; node = walker.next()) {
\r
12875 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
\r
12876 rng.setStart(node, 0);
\r
12886 function setElementFormat(elm, fmt) {
\r
12887 fmt = fmt || format;
\r
12890 each(fmt.styles, function(value, name) {
\r
12891 dom.setStyle(elm, name, replaceVars(value, vars));
\r
12894 each(fmt.attributes, function(value, name) {
\r
12895 dom.setAttrib(elm, name, replaceVars(value, vars));
\r
12898 each(fmt.classes, function(value) {
\r
12899 value = replaceVars(value, vars);
\r
12901 if (!dom.hasClass(elm, value))
\r
12902 dom.addClass(elm, value);
\r
12907 function applyRngStyle(rng) {
\r
12908 var newWrappers = [], wrapName, wrapElm;
\r
12910 // Setup wrapper element
\r
12911 wrapName = format.inline || format.block;
\r
12912 wrapElm = dom.create(wrapName);
\r
12913 setElementFormat(wrapElm);
\r
12915 rangeUtils.walk(rng, function(nodes) {
\r
12916 var currentWrapElm;
\r
12918 function process(node) {
\r
12919 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
\r
12921 // Stop wrapping on br elements
\r
12922 if (isEq(nodeName, 'br')) {
\r
12923 currentWrapElm = 0;
\r
12925 // Remove any br elements when we wrap things
\r
12926 if (format.block)
\r
12927 dom.remove(node);
\r
12932 // If node is wrapper type
\r
12933 if (format.wrapper && matchNode(node, name, vars)) {
\r
12934 currentWrapElm = 0;
\r
12938 // Can we rename the block
\r
12939 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
\r
12940 node = dom.rename(node, wrapName);
\r
12941 setElementFormat(node);
\r
12942 newWrappers.push(node);
\r
12943 currentWrapElm = 0;
\r
12947 // Handle selector patterns
\r
12948 if (format.selector) {
\r
12949 // Look for matching formats
\r
12950 each(formatList, function(format) {
\r
12951 if (dom.is(node, format.selector) && !isCaretNode(node)) {
\r
12952 setElementFormat(node, format);
\r
12957 // Continue processing if a selector match wasn't found and a inline element is defined
\r
12958 if (!format.inline || found) {
\r
12959 currentWrapElm = 0;
\r
12964 // Is it valid to wrap this item
\r
12965 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
\r
12966 // Start wrapping
\r
12967 if (!currentWrapElm) {
\r
12969 currentWrapElm = wrapElm.cloneNode(FALSE);
\r
12970 node.parentNode.insertBefore(currentWrapElm, node);
\r
12971 newWrappers.push(currentWrapElm);
\r
12974 currentWrapElm.appendChild(node);
\r
12976 // Start a new wrapper for possible children
\r
12977 currentWrapElm = 0;
\r
12979 each(tinymce.grep(node.childNodes), process);
\r
12981 // End the last wrapper
\r
12982 currentWrapElm = 0;
\r
12986 // Process siblings from range
\r
12987 each(nodes, process);
\r
12991 each(newWrappers, function(node) {
\r
12994 function getChildCount(node) {
\r
12997 each(node.childNodes, function(node) {
\r
12998 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
\r
13005 function mergeStyles(node) {
\r
13006 var child, clone;
\r
13008 each(node.childNodes, function(node) {
\r
13009 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
\r
13011 return FALSE; // break loop
\r
13015 // If child was found and of the same type as the current node
\r
13016 if (child && matchName(child, format)) {
\r
13017 clone = child.cloneNode(FALSE);
\r
13018 setElementFormat(clone);
\r
13020 dom.replace(clone, node, TRUE);
\r
13021 dom.remove(child, 1);
\r
13024 return clone || node;
\r
13027 childCount = getChildCount(node);
\r
13029 // Remove empty nodes
\r
13030 if (childCount === 0) {
\r
13031 dom.remove(node, 1);
\r
13035 if (format.inline || format.wrapper) {
\r
13036 // Merges the current node with it's children of similar type to reduce the number of elements
\r
13037 if (!format.exact && childCount === 1)
\r
13038 node = mergeStyles(node);
\r
13040 // Remove/merge children
\r
13041 each(formatList, function(format) {
\r
13042 // Merge all children of similar type will move styles from child to parent
\r
13043 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
\r
13044 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
\r
13045 each(dom.select(format.inline, node), function(child) {
\r
13046 removeFormat(format, vars, child, format.exact ? child : null);
\r
13050 // Remove child if direct parent is of same type
\r
13051 if (matchNode(node.parentNode, name, vars)) {
\r
13052 dom.remove(node, 1);
\r
13057 // Look for parent with similar style format
\r
13058 if (format.merge_with_parents) {
\r
13059 dom.getParent(node.parentNode, function(parent) {
\r
13060 if (matchNode(parent, name, vars)) {
\r
13061 dom.remove(node, 1);
\r
13068 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
\r
13070 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
\r
13071 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
\r
13079 rng = dom.createRng();
\r
13081 rng.setStartBefore(node);
\r
13082 rng.setEndAfter(node);
\r
13084 applyRngStyle(expandRng(rng, formatList));
\r
13086 if (!selection.isCollapsed() || !format.inline) {
\r
13087 // Apply formatting to selection
\r
13088 bookmark = selection.getBookmark();
\r
13089 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
\r
13091 selection.moveToBookmark(bookmark);
\r
13092 selection.setRng(moveStart(selection.getRng(TRUE)));
\r
13093 ed.nodeChanged();
\r
13095 performCaretAction('apply', name, vars);
\r
13100 function remove(name, vars, node) {
\r
13101 var formatList = get(name), format = formatList[0], bookmark, i, rng;
\r
13103 function moveStart(rng) {
\r
13104 var container = rng.startContainer,
\r
13105 offset = rng.startOffset,
\r
13106 walker, node, nodes, tmpNode;
\r
13108 // Convert text node into index if possible
\r
13109 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
\r
13110 container = container.parentNode;
\r
13111 offset = nodeIndex(container) + 1;
\r
13114 // Move startContainer/startOffset in to a suitable node
\r
13115 if (container.nodeType == 1) {
\r
13116 nodes = container.childNodes;
\r
13117 container = nodes[Math.min(offset, nodes.length - 1)];
\r
13118 walker = new TreeWalker(container);
\r
13120 // If offset is at end of the parent node walk to the next one
\r
13121 if (offset > nodes.length - 1)
\r
13124 for (node = walker.current(); node; node = walker.next()) {
\r
13125 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
\r
13126 // IE has a "neat" feature where it moves the start node into the closest element
\r
13127 // we can avoid this by inserting an element before it and then remove it after we set the selection
\r
13128 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
\r
13129 node.parentNode.insertBefore(tmpNode, node);
\r
13131 // Set selection and remove tmpNode
\r
13132 rng.setStart(node, 0);
\r
13133 selection.setRng(rng);
\r
13134 dom.remove(tmpNode);
\r
13142 // Merges the styles for each node
\r
13143 function process(node) {
\r
13144 var children, i, l;
\r
13146 // Grab the children first since the nodelist might be changed
\r
13147 children = tinymce.grep(node.childNodes);
\r
13149 // Process current node
\r
13150 for (i = 0, l = formatList.length; i < l; i++) {
\r
13151 if (removeFormat(formatList[i], vars, node, node))
\r
13155 // Process the children
\r
13156 if (format.deep) {
\r
13157 for (i = 0, l = children.length; i < l; i++)
\r
13158 process(children[i]);
\r
13162 function findFormatRoot(container) {
\r
13165 // Find format root
\r
13166 each(getParents(container.parentNode).reverse(), function(parent) {
\r
13169 // Find format root element
\r
13170 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
\r
13171 // Is the node matching the format we are looking for
\r
13172 format = matchNode(parent, name, vars);
\r
13173 if (format && format.split !== false)
\r
13174 formatRoot = parent;
\r
13178 return formatRoot;
\r
13181 function wrapAndSplit(format_root, container, target, split) {
\r
13182 var parent, clone, lastClone, firstClone, i, formatRootParent;
\r
13184 // Format root found then clone formats and split it
\r
13185 if (format_root) {
\r
13186 formatRootParent = format_root.parentNode;
\r
13188 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
\r
13189 clone = parent.cloneNode(FALSE);
\r
13191 for (i = 0; i < formatList.length; i++) {
\r
13192 if (removeFormat(formatList[i], vars, clone, clone)) {
\r
13198 // Build wrapper node
\r
13201 clone.appendChild(lastClone);
\r
13204 firstClone = clone;
\r
13206 lastClone = clone;
\r
13210 // Never split block elements if the format is mixed
\r
13211 if (split && (!format.mixed || !isBlock(format_root)))
\r
13212 container = dom.split(format_root, container);
\r
13214 // Wrap container in cloned formats
\r
13216 target.parentNode.insertBefore(lastClone, target);
\r
13217 firstClone.appendChild(target);
\r
13221 return container;
\r
13224 function splitToFormatRoot(container) {
\r
13225 return wrapAndSplit(findFormatRoot(container), container, container, true);
\r
13228 function unwrap(start) {
\r
13229 var node = dom.get(start ? '_start' : '_end'),
\r
13230 out = node[start ? 'firstChild' : 'lastChild'];
\r
13232 // If the end is placed within the start the result will be removed
\r
13233 // So this checks if the out node is a bookmark node if it is it
\r
13234 // checks for another more suitable node
\r
13235 if (isBookmarkNode(out))
\r
13236 out = out[start ? 'firstChild' : 'lastChild'];
\r
13238 dom.remove(node, true);
\r
13243 function removeRngStyle(rng) {
\r
13244 var startContainer, endContainer;
\r
13246 rng = expandRng(rng, formatList, TRUE);
\r
13248 if (format.split) {
\r
13249 startContainer = getContainer(rng, TRUE);
\r
13250 endContainer = getContainer(rng);
\r
13252 if (startContainer != endContainer) {
\r
13253 // Wrap start/end nodes in span element since these might be cloned/moved
\r
13254 startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
\r
13255 endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
\r
13257 // Split start/end
\r
13258 splitToFormatRoot(startContainer);
\r
13259 splitToFormatRoot(endContainer);
\r
13261 // Unwrap start/end to get real elements again
\r
13262 startContainer = unwrap(TRUE);
\r
13263 endContainer = unwrap();
\r
13265 startContainer = endContainer = splitToFormatRoot(startContainer);
\r
13267 // Update range positions since they might have changed after the split operations
\r
13268 rng.startContainer = startContainer.parentNode;
\r
13269 rng.startOffset = nodeIndex(startContainer);
\r
13270 rng.endContainer = endContainer.parentNode;
\r
13271 rng.endOffset = nodeIndex(endContainer) + 1;
\r
13274 // Remove items between start/end
\r
13275 rangeUtils.walk(rng, function(nodes) {
\r
13276 each(nodes, function(node) {
\r
13284 rng = dom.createRng();
\r
13285 rng.setStartBefore(node);
\r
13286 rng.setEndAfter(node);
\r
13287 removeRngStyle(rng);
\r
13291 if (!selection.isCollapsed() || !format.inline) {
\r
13292 bookmark = selection.getBookmark();
\r
13293 removeRngStyle(selection.getRng(TRUE));
\r
13294 selection.moveToBookmark(bookmark);
\r
13296 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
\r
13297 if (match(name, vars, selection.getStart())) {
\r
13298 moveStart(selection.getRng(true));
\r
13301 ed.nodeChanged();
\r
13303 performCaretAction('remove', name, vars);
\r
13306 function toggle(name, vars, node) {
\r
13307 if (match(name, vars, node))
\r
13308 remove(name, vars, node);
\r
13310 apply(name, vars, node);
\r
13313 function matchNode(node, name, vars, similar) {
\r
13314 var formatList = get(name), format, i, classes;
\r
13316 function matchItems(node, format, item_name) {
\r
13317 var key, value, items = format[item_name], i;
\r
13319 // Check all items
\r
13321 // Non indexed object
\r
13322 if (items.length === undefined) {
\r
13323 for (key in items) {
\r
13324 if (items.hasOwnProperty(key)) {
\r
13325 if (item_name === 'attributes')
\r
13326 value = dom.getAttrib(node, key);
\r
13328 value = getStyle(node, key);
\r
13330 if (similar && !value && !format.exact)
\r
13333 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
\r
13338 // Only one match needed for indexed arrays
\r
13339 for (i = 0; i < items.length; i++) {
\r
13340 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
\r
13349 if (formatList && node) {
\r
13350 // Check each format in list
\r
13351 for (i = 0; i < formatList.length; i++) {
\r
13352 format = formatList[i];
\r
13354 // Name name, attributes, styles and classes
\r
13355 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
\r
13357 if (classes = format.classes) {
\r
13358 for (i = 0; i < classes.length; i++) {
\r
13359 if (!dom.hasClass(node, classes[i]))
\r
13370 function match(name, vars, node) {
\r
13371 var startNode, i;
\r
13373 function matchParents(node) {
\r
13374 // Find first node with similar format settings
\r
13375 node = dom.getParent(node, function(node) {
\r
13376 return !!matchNode(node, name, vars, true);
\r
13379 // Do an exact check on the similar format element
\r
13380 return matchNode(node, name, vars);
\r
13383 // Check specified node
\r
13385 return matchParents(node);
\r
13387 // Check pending formats
\r
13388 if (selection.isCollapsed()) {
\r
13389 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
13390 if (pendingFormats.apply[i].name == name)
\r
13394 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
13395 if (pendingFormats.remove[i].name == name)
\r
13399 return matchParents(selection.getNode());
\r
13402 // Check selected node
\r
13403 node = selection.getNode();
\r
13404 if (matchParents(node))
\r
13407 // Check start node if it's different
\r
13408 startNode = selection.getStart();
\r
13409 if (startNode != node) {
\r
13410 if (matchParents(startNode))
\r
13417 function matchAll(names, vars) {
\r
13418 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
\r
13420 // If the selection is collapsed then check pending formats
\r
13421 if (selection.isCollapsed()) {
\r
13422 for (ni = 0; ni < names.length; ni++) {
\r
13423 // If the name is to be removed, then stop it from being added
\r
13424 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
13425 name = names[ni];
\r
13427 if (pendingFormats.remove[i].name == name) {
\r
13428 checkedMap[name] = true;
\r
13434 // If the format is to be applied
\r
13435 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
13436 for (ni = 0; ni < names.length; ni++) {
\r
13437 name = names[ni];
\r
13439 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
\r
13440 checkedMap[name] = true;
\r
13441 matchedFormatNames.push(name);
\r
13447 // Check start of selection for formats
\r
13448 startElement = selection.getStart();
\r
13449 dom.getParent(startElement, function(node) {
\r
13452 for (i = 0; i < names.length; i++) {
\r
13455 if (!checkedMap[name] && matchNode(node, name, vars)) {
\r
13456 checkedMap[name] = true;
\r
13457 matchedFormatNames.push(name);
\r
13462 return matchedFormatNames;
\r
13465 function canApply(name) {
\r
13466 var formatList = get(name), startNode, parents, i, x, selector;
\r
13468 if (formatList) {
\r
13469 startNode = selection.getStart();
\r
13470 parents = getParents(startNode);
\r
13472 for (x = formatList.length - 1; x >= 0; x--) {
\r
13473 selector = formatList[x].selector;
\r
13475 // Format is not selector based, then always return TRUE
\r
13479 for (i = parents.length - 1; i >= 0; i--) {
\r
13480 if (dom.is(parents[i], selector))
\r
13489 // Expose to public
\r
13490 tinymce.extend(this, {
\r
13492 register : register,
\r
13497 matchAll : matchAll,
\r
13498 matchNode : matchNode,
\r
13499 canApply : canApply
\r
13502 // Private functions
\r
13504 function matchName(node, format) {
\r
13505 // Check for inline match
\r
13506 if (isEq(node, format.inline))
\r
13509 // Check for block match
\r
13510 if (isEq(node, format.block))
\r
13513 // Check for selector match
\r
13514 if (format.selector)
\r
13515 return dom.is(node, format.selector);
\r
13518 function isEq(str1, str2) {
\r
13519 str1 = str1 || '';
\r
13520 str2 = str2 || '';
\r
13522 str1 = '' + (str1.nodeName || str1);
\r
13523 str2 = '' + (str2.nodeName || str2);
\r
13525 return str1.toLowerCase() == str2.toLowerCase();
\r
13528 function getStyle(node, name) {
\r
13529 var styleVal = dom.getStyle(node, name);
\r
13531 // Force the format to hex
\r
13532 if (name == 'color' || name == 'backgroundColor')
\r
13533 styleVal = dom.toHex(styleVal);
\r
13535 // Opera will return bold as 700
\r
13536 if (name == 'fontWeight' && styleVal == 700)
\r
13537 styleVal = 'bold';
\r
13539 return '' + styleVal;
\r
13542 function replaceVars(value, vars) {
\r
13543 if (typeof(value) != "string")
\r
13544 value = value(vars);
\r
13546 value = value.replace(/%(\w+)/g, function(str, name) {
\r
13547 return vars[name] || str;
\r
13554 function isWhiteSpaceNode(node) {
\r
13555 return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
\r
13558 function wrap(node, name, attrs) {
\r
13559 var wrapper = dom.create(name, attrs);
\r
13561 node.parentNode.insertBefore(wrapper, node);
\r
13562 wrapper.appendChild(node);
\r
13567 function expandRng(rng, format, remove) {
\r
13568 var startContainer = rng.startContainer,
\r
13569 startOffset = rng.startOffset,
\r
13570 endContainer = rng.endContainer,
\r
13571 endOffset = rng.endOffset, sibling, lastIdx;
\r
13573 // This function walks up the tree if there is no siblings before/after the node
\r
13574 function findParentContainer(container, child_name, sibling_name, root) {
\r
13575 var parent, child;
\r
13577 root = root || dom.getRoot();
\r
13580 // Check if we can move up are we at root level or body level
\r
13581 parent = container.parentNode;
\r
13583 // Stop expanding on block elements or root depending on format
\r
13584 if (parent == root || (!format[0].block_expand && isBlock(parent)))
\r
13585 return container;
\r
13587 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
\r
13588 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
13589 return container;
\r
13591 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
13592 return container;
\r
13595 container = container.parentNode;
\r
13598 return container;
\r
13601 // If index based start position then resolve it
\r
13602 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
\r
13603 lastIdx = startContainer.childNodes.length - 1;
\r
13604 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
\r
13606 if (startContainer.nodeType == 3)
\r
13610 // If index based end position then resolve it
\r
13611 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
\r
13612 lastIdx = endContainer.childNodes.length - 1;
\r
13613 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
\r
13615 if (endContainer.nodeType == 3)
\r
13616 endOffset = endContainer.nodeValue.length;
\r
13619 // Exclude bookmark nodes if possible
\r
13620 if (isBookmarkNode(startContainer.parentNode))
\r
13621 startContainer = startContainer.parentNode;
\r
13623 if (isBookmarkNode(startContainer))
\r
13624 startContainer = startContainer.nextSibling || startContainer;
\r
13626 if (isBookmarkNode(endContainer.parentNode))
\r
13627 endContainer = endContainer.parentNode;
\r
13629 if (isBookmarkNode(endContainer))
\r
13630 endContainer = endContainer.previousSibling || endContainer;
\r
13632 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
\r
13633 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
\r
13634 // This will reduce the number of wrapper elements that needs to be created
\r
13635 // Move start point up the tree
\r
13636 if (format[0].inline || format[0].block_expand) {
\r
13637 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
13638 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
13641 // Expand start/end container to matching selector
\r
13642 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
\r
13643 function findSelectorEndPoint(container, sibling_name) {
\r
13644 var parents, i, y;
\r
13646 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
\r
13647 container = container[sibling_name];
\r
13649 parents = getParents(container);
\r
13650 for (i = 0; i < parents.length; i++) {
\r
13651 for (y = 0; y < format.length; y++) {
\r
13652 if (dom.is(parents[i], format[y].selector))
\r
13653 return parents[i];
\r
13657 return container;
\r
13660 // Find new startContainer/endContainer if there is better one
\r
13661 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
\r
13662 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
\r
13665 // Expand start/end container to matching block element or text node
\r
13666 if (format[0].block || format[0].selector) {
\r
13667 function findBlockEndPoint(container, sibling_name, sibling_name2) {
\r
13670 // Expand to block of similar type
\r
13671 if (!format[0].wrapper)
\r
13672 node = dom.getParent(container, format[0].block);
\r
13674 // Expand to first wrappable block element or any block element
\r
13676 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
\r
13678 // Exclude inner lists from wrapping
\r
13679 if (node && format[0].wrapper)
\r
13680 node = getParents(node, 'ul,ol').reverse()[0] || node;
\r
13682 // Didn't find a block element look for first/last wrappable element
\r
13684 node = container;
\r
13686 while (node[sibling_name] && !isBlock(node[sibling_name])) {
\r
13687 node = node[sibling_name];
\r
13689 // Break on BR but include it will be removed later on
\r
13690 // we can't remove it now since we need to check if it can be wrapped
\r
13691 if (isEq(node, 'br'))
\r
13696 return node || container;
\r
13699 // Find new startContainer/endContainer if there is better one
\r
13700 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
\r
13701 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
\r
13703 // Non block element then try to expand up the leaf
\r
13704 if (format[0].block) {
\r
13705 if (!isBlock(startContainer))
\r
13706 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
13708 if (!isBlock(endContainer))
\r
13709 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
13713 // Setup index for startContainer
\r
13714 if (startContainer.nodeType == 1) {
\r
13715 startOffset = nodeIndex(startContainer);
\r
13716 startContainer = startContainer.parentNode;
\r
13719 // Setup index for endContainer
\r
13720 if (endContainer.nodeType == 1) {
\r
13721 endOffset = nodeIndex(endContainer) + 1;
\r
13722 endContainer = endContainer.parentNode;
\r
13725 // Return new range like object
\r
13727 startContainer : startContainer,
\r
13728 startOffset : startOffset,
\r
13729 endContainer : endContainer,
\r
13730 endOffset : endOffset
\r
13734 function removeFormat(format, vars, node, compare_node) {
\r
13735 var i, attrs, stylesModified;
\r
13737 // Check if node matches format
\r
13738 if (!matchName(node, format))
\r
13741 // Should we compare with format attribs and styles
\r
13742 if (format.remove != 'all') {
\r
13744 each(format.styles, function(value, name) {
\r
13745 value = replaceVars(value, vars);
\r
13748 if (typeof(name) === 'number') {
\r
13750 compare_node = 0;
\r
13753 if (!compare_node || isEq(getStyle(compare_node, name), value))
\r
13754 dom.setStyle(node, name, '');
\r
13756 stylesModified = 1;
\r
13759 // Remove style attribute if it's empty
\r
13760 if (stylesModified && dom.getAttrib(node, 'style') == '') {
\r
13761 node.removeAttribute('style');
\r
13762 node.removeAttribute('_mce_style');
\r
13765 // Remove attributes
\r
13766 each(format.attributes, function(value, name) {
\r
13769 value = replaceVars(value, vars);
\r
13772 if (typeof(name) === 'number') {
\r
13774 compare_node = 0;
\r
13777 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
\r
13778 // Keep internal classes
\r
13779 if (name == 'class') {
\r
13780 value = dom.getAttrib(node, name);
\r
13782 // Build new class value where everything is removed except the internal prefixed classes
\r
13784 each(value.split(/\s+/), function(cls) {
\r
13785 if (/mce\w+/.test(cls))
\r
13786 valueOut += (valueOut ? ' ' : '') + cls;
\r
13789 // We got some internal classes left
\r
13791 dom.setAttrib(node, name, valueOut);
\r
13797 // IE6 has a bug where the attribute doesn't get removed correctly
\r
13798 if (name == "class")
\r
13799 node.removeAttribute('className');
\r
13801 // Remove mce prefixed attributes
\r
13802 if (MCE_ATTR_RE.test(name))
\r
13803 node.removeAttribute('_mce_' + name);
\r
13805 node.removeAttribute(name);
\r
13809 // Remove classes
\r
13810 each(format.classes, function(value) {
\r
13811 value = replaceVars(value, vars);
\r
13813 if (!compare_node || dom.hasClass(compare_node, value))
\r
13814 dom.removeClass(node, value);
\r
13817 // Check for non internal attributes
\r
13818 attrs = dom.getAttribs(node);
\r
13819 for (i = 0; i < attrs.length; i++) {
\r
13820 if (attrs[i].nodeName.indexOf('_') !== 0)
\r
13825 // Remove the inline child if it's empty for example <b> or <span>
\r
13826 if (format.remove != 'none') {
\r
13827 removeNode(node, format);
\r
13832 function removeNode(node, format) {
\r
13833 var parentNode = node.parentNode, rootBlockElm;
\r
13835 if (format.block) {
\r
13836 if (!forcedRootBlock) {
\r
13837 function find(node, next, inc) {
\r
13838 node = getNonWhiteSpaceSibling(node, next, inc);
\r
13840 return !node || (node.nodeName == 'BR' || isBlock(node));
\r
13843 // Append BR elements if needed before we remove the block
\r
13844 if (isBlock(node) && !isBlock(parentNode)) {
\r
13845 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
\r
13846 node.insertBefore(dom.create('br'), node.firstChild);
\r
13848 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
\r
13849 node.appendChild(dom.create('br'));
\r
13852 // Wrap the block in a forcedRootBlock if we are at the root of document
\r
13853 if (parentNode == dom.getRoot()) {
\r
13854 if (!format.list_block || !isEq(node, format.list_block)) {
\r
13855 each(tinymce.grep(node.childNodes), function(node) {
\r
13856 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
\r
13857 if (!rootBlockElm)
\r
13858 rootBlockElm = wrap(node, forcedRootBlock);
\r
13860 rootBlockElm.appendChild(node);
\r
13862 rootBlockElm = 0;
\r
13869 // Never remove nodes that isn't the specified inline element if a selector is specified too
\r
13870 if (format.selector && format.inline && !isEq(format.inline, node))
\r
13873 dom.remove(node, 1);
\r
13876 function getNonWhiteSpaceSibling(node, next, inc) {
\r
13878 next = next ? 'nextSibling' : 'previousSibling';
\r
13880 for (node = inc ? node : node[next]; node; node = node[next]) {
\r
13881 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
\r
13887 function isBookmarkNode(node) {
\r
13888 return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
\r
13891 function mergeSiblings(prev, next) {
\r
13892 var marker, sibling, tmpSibling;
\r
13894 function compareElements(node1, node2) {
\r
13895 // Not the same name
\r
13896 if (node1.nodeName != node2.nodeName)
\r
13899 function getAttribs(node) {
\r
13900 var attribs = {};
\r
13902 each(dom.getAttribs(node), function(attr) {
\r
13903 var name = attr.nodeName.toLowerCase();
\r
13905 // Don't compare internal attributes or style
\r
13906 if (name.indexOf('_') !== 0 && name !== 'style')
\r
13907 attribs[name] = dom.getAttrib(node, name);
\r
13913 function compareObjects(obj1, obj2) {
\r
13916 for (name in obj1) {
\r
13917 // Obj1 has item obj2 doesn't have
\r
13918 if (obj1.hasOwnProperty(name)) {
\r
13919 value = obj2[name];
\r
13921 // Obj2 doesn't have obj1 item
\r
13922 if (value === undefined)
\r
13925 // Obj2 item has a different value
\r
13926 if (obj1[name] != value)
\r
13929 // Delete similar value
\r
13930 delete obj2[name];
\r
13934 // Check if obj 2 has something obj 1 doesn't have
\r
13935 for (name in obj2) {
\r
13936 // Obj2 has item obj1 doesn't have
\r
13937 if (obj2.hasOwnProperty(name))
\r
13944 // Attribs are not the same
\r
13945 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
\r
13948 // Styles are not the same
\r
13949 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
\r
13955 // Check if next/prev exists and that they are elements
\r
13956 if (prev && next) {
\r
13957 function findElementSibling(node, sibling_name) {
\r
13958 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
\r
13959 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
13962 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
13969 // If previous sibling is empty then jump over it
\r
13970 prev = findElementSibling(prev, 'previousSibling');
\r
13971 next = findElementSibling(next, 'nextSibling');
\r
13973 // Compare next and previous nodes
\r
13974 if (compareElements(prev, next)) {
\r
13975 // Append nodes between
\r
13976 for (sibling = prev.nextSibling; sibling && sibling != next;) {
\r
13977 tmpSibling = sibling;
\r
13978 sibling = sibling.nextSibling;
\r
13979 prev.appendChild(tmpSibling);
\r
13982 // Remove next node
\r
13983 dom.remove(next);
\r
13985 // Move children into prev node
\r
13986 each(tinymce.grep(next.childNodes), function(node) {
\r
13987 prev.appendChild(node);
\r
13997 function isTextBlock(name) {
\r
13998 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
\r
14001 function getContainer(rng, start) {
\r
14002 var container, offset, lastIdx;
\r
14004 container = rng[start ? 'startContainer' : 'endContainer'];
\r
14005 offset = rng[start ? 'startOffset' : 'endOffset'];
\r
14007 if (container.nodeType == 1) {
\r
14008 lastIdx = container.childNodes.length - 1;
\r
14010 if (!start && offset)
\r
14013 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
\r
14016 return container;
\r
14019 function performCaretAction(type, name, vars) {
\r
14020 var i, currentPendingFormats = pendingFormats[type],
\r
14021 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
\r
14023 function hasPending() {
\r
14024 return pendingFormats.apply.length || pendingFormats.remove.length;
\r
14027 function resetPending() {
\r
14028 pendingFormats.apply = [];
\r
14029 pendingFormats.remove = [];
\r
14032 function perform(caret_node) {
\r
14033 // Apply pending formats
\r
14034 each(pendingFormats.apply.reverse(), function(item) {
\r
14035 apply(item.name, item.vars, caret_node);
\r
14038 // Remove pending formats
\r
14039 each(pendingFormats.remove.reverse(), function(item) {
\r
14040 remove(item.name, item.vars, caret_node);
\r
14043 dom.remove(caret_node, 1);
\r
14047 // Check if it already exists then ignore it
\r
14048 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
\r
14049 if (currentPendingFormats[i].name == name)
\r
14053 currentPendingFormats.push({name : name, vars : vars});
\r
14055 // Check if it's in the other type, then remove it
\r
14056 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
\r
14057 if (otherPendingFormats[i].name == name)
\r
14058 otherPendingFormats.splice(i, 1);
\r
14061 // Pending apply or remove formats
\r
14062 if (hasPending()) {
\r
14063 ed.getDoc().execCommand('FontName', false, 'mceinline');
\r
14064 pendingFormats.lastRng = selection.getRng();
\r
14066 // IE will convert the current word
\r
14067 each(dom.select('font,span'), function(node) {
\r
14070 if (isCaretNode(node)) {
\r
14071 bookmark = selection.getBookmark();
\r
14073 selection.moveToBookmark(bookmark);
\r
14074 ed.nodeChanged();
\r
14078 // Only register listeners once if we need to
\r
14079 if (!pendingFormats.isListening && hasPending()) {
\r
14080 pendingFormats.isListening = true;
\r
14082 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
\r
14083 ed[event].addToTop(function(ed, e) {
\r
14084 // Do we have pending formats and is the selection moved has moved
\r
14085 if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
\r
14086 each(dom.select('font,span'), function(node) {
\r
14087 var textNode, rng;
\r
14089 // Look for marker
\r
14090 if (isCaretNode(node)) {
\r
14091 textNode = node.firstChild;
\r
14096 rng = dom.createRng();
\r
14097 rng.setStart(textNode, textNode.nodeValue.length);
\r
14098 rng.setEnd(textNode, textNode.nodeValue.length);
\r
14099 selection.setRng(rng);
\r
14100 ed.nodeChanged();
\r
14102 dom.remove(node);
\r
14106 // Always unbind and clear pending styles on keyup
\r
14107 if (e.type == 'keyup' || e.type == 'mouseup')
\r
14118 tinymce.onAddEditor.add(function(tinymce, ed) {
\r
14119 var filters, fontSizes, dom, settings = ed.settings;
\r
14121 if (settings.inline_styles) {
\r
14122 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
14124 function replaceWithSpan(node, styles) {
\r
14125 dom.replace(dom.create('span', {
\r
14131 font : function(dom, node) {
\r
14132 replaceWithSpan(node, {
\r
14133 backgroundColor : node.style.backgroundColor,
\r
14134 color : node.color,
\r
14135 fontFamily : node.face,
\r
14136 fontSize : fontSizes[parseInt(node.size) - 1]
\r
14140 u : function(dom, node) {
\r
14141 replaceWithSpan(node, {
\r
14142 textDecoration : 'underline'
\r
14146 strike : function(dom, node) {
\r
14147 replaceWithSpan(node, {
\r
14148 textDecoration : 'line-through'
\r
14153 function convert(editor, params) {
\r
14154 dom = editor.dom;
\r
14156 if (settings.convert_fonts_to_spans) {
\r
14157 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
\r
14158 filters[node.nodeName.toLowerCase()](ed.dom, node);
\r
14163 ed.onPreProcess.add(convert);
\r
14165 ed.onInit.add(function() {
\r
14166 ed.selection.onSetContent.add(convert);
\r