2 var whiteSpaceRe = /^\s*|\s*$/g,
\r
8 minorVersion : '3.7',
\r
10 releaseDate : '2010-06-10',
\r
12 _init : function() {
\r
13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
\r
15 t.isOpera = win.opera && opera.buildNumber;
\r
17 t.isWebKit = /WebKit/.test(ua);
\r
19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
\r
21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
\r
23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
\r
25 t.isMac = ua.indexOf('Mac') != -1;
\r
27 t.isAir = /adobeair/i.test(ua);
\r
29 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
1581 n.top += t.win.self != t.win.top ? 2 : 0; // IE adds some strange extra cord if used in a frameset
\r
1583 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
\r
1587 while (r && r != ro && r.nodeType) {
\r
1588 x += r.offsetLeft || 0;
\r
1589 y += r.offsetTop || 0;
\r
1590 r = r.offsetParent;
\r
1594 while (r && r != ro && r.nodeType) {
\r
1595 x -= r.scrollLeft || 0;
\r
1596 y -= r.scrollTop || 0;
\r
1601 return {x : x, y : y};
\r
1604 parseStyle : function(st) {
\r
1605 var t = this, s = t.settings, o = {};
\r
1610 function compress(p, s, ot) {
\r
1613 // Get values and check it it needs compressing
\r
1614 t = o[p + '-top' + s];
\r
1618 r = o[p + '-right' + s];
\r
1622 b = o[p + '-bottom' + s];
\r
1626 l = o[p + '-left' + s];
\r
1632 delete o[p + '-top' + s];
\r
1633 delete o[p + '-right' + s];
\r
1634 delete o[p + '-bottom' + s];
\r
1635 delete o[p + '-left' + s];
\r
1638 function compress2(ta, a, b, c) {
\r
1654 o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
\r
1660 st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
\r
1662 each(st.split(';'), function(v) {
\r
1666 v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
\r
1667 v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
\r
1669 sv = tinymce.trim(v[1]);
\r
1670 sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
\r
1672 sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
\r
1673 return t.toHex(v);
\r
1676 if (s.url_converter) {
\r
1677 sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
\r
1678 return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
\r
1682 o[tinymce.trim(v[0]).toLowerCase()] = sv;
\r
1686 compress("border", "", "border");
\r
1687 compress("border", "-width", "border-width");
\r
1688 compress("border", "-color", "border-color");
\r
1689 compress("border", "-style", "border-style");
\r
1690 compress("padding", "", "padding");
\r
1691 compress("margin", "", "margin");
\r
1692 compress2('border', 'border-width', 'border-style', 'border-color');
\r
1695 // Remove pointless border
\r
1696 if (o.border == 'medium none')
\r
1703 serializeStyle : function(o, name) {
\r
1704 var t = this, s = '';
\r
1706 function add(v, k) {
\r
1708 // Remove browser specific styles like -moz- or -webkit-
\r
1709 if (k.indexOf('-') === 0)
\r
1713 case 'font-weight':
\r
1714 // Opera will output bold as 700
\r
1721 case 'background-color':
\r
1722 v = v.toLowerCase();
\r
1726 s += (s ? ' ' : '') + k + ': ' + v + ';';
\r
1730 // Validate style output
\r
1731 if (name && t._styles) {
\r
1732 each(t._styles['*'], function(name) {
\r
1733 add(o[name], name);
\r
1736 each(t._styles[name.toLowerCase()], function(name) {
\r
1737 add(o[name], name);
\r
1745 loadCSS : function(u) {
\r
1746 var t = this, d = t.doc, head;
\r
1751 head = t.select('head')[0];
\r
1753 each(u.split(','), function(u) {
\r
1759 t.files[u] = true;
\r
1760 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
\r
1762 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
\r
1763 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
\r
1764 // It's ugly but it seems to work fine.
\r
1765 if (isIE && d.documentMode) {
\r
1766 link.onload = function() {
\r
1768 link.onload = null;
\r
1772 head.appendChild(link);
\r
1776 addClass : function(e, c) {
\r
1777 return this.run(e, function(e) {
\r
1783 if (this.hasClass(e, c))
\r
1784 return e.className;
\r
1786 o = this.removeClass(e, c);
\r
1788 return e.className = (o != '' ? (o + ' ') : '') + c;
\r
1792 removeClass : function(e, c) {
\r
1795 return t.run(e, function(e) {
\r
1798 if (t.hasClass(e, c)) {
\r
1800 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
\r
1802 v = e.className.replace(re, ' ');
\r
1803 v = tinymce.trim(v != ' ' ? v : '');
\r
1807 // Empty class attr
\r
1809 e.removeAttribute('class');
\r
1810 e.removeAttribute('className');
\r
1816 return e.className;
\r
1820 hasClass : function(n, c) {
\r
1826 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
\r
1829 show : function(e) {
\r
1830 return this.setStyle(e, 'display', 'block');
\r
1833 hide : function(e) {
\r
1834 return this.setStyle(e, 'display', 'none');
\r
1837 isHidden : function(e) {
\r
1840 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
\r
1843 uniqueId : function(p) {
\r
1844 return (!p ? 'mce_' : p) + (this.counter++);
\r
1847 setHTML : function(e, h) {
\r
1850 return this.run(e, function(e) {
\r
1851 var x, i, nl, n, p, x;
\r
1853 h = t.processHTML(h);
\r
1857 // Remove all child nodes
\r
1858 while (e.firstChild)
\r
1859 e.firstChild.removeNode();
\r
1862 // IE will remove comments from the beginning
\r
1863 // unless you padd the contents with something
\r
1864 e.innerHTML = '<br />' + h;
\r
1865 e.removeChild(e.firstChild);
\r
1867 // 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
1868 // This seems to fix this problem
\r
1870 // Create new div with HTML contents and a BR infront to keep comments
\r
1871 x = t.create('div');
\r
1872 x.innerHTML = '<br />' + h;
\r
1874 // Add all children from div to target
\r
1875 each (x.childNodes, function(n, i) {
\r
1876 // Skip br element
\r
1883 // IE has a serious bug when it comes to paragraphs it can produce an invalid
\r
1884 // DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
\r
1885 // It seems to be that IE doesn't like a root block element placed inside another root block element
\r
1886 if (t.settings.fix_ie_paragraphs)
\r
1887 h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true"> </p>');
\r
1891 if (t.settings.fix_ie_paragraphs) {
\r
1892 // Check for odd paragraphs this is a sign of a broken DOM
\r
1893 nl = e.getElementsByTagName("p");
\r
1894 for (i = nl.length - 1, x = 0; i >= 0; i--) {
\r
1897 if (!n.hasChildNodes()) {
\r
1898 if (!n._mce_keep) {
\r
1899 x = 1; // Is broken
\r
1903 n.removeAttribute('_mce_keep');
\r
1908 // Time to fix the madness IE left us
\r
1910 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
\r
1911 // after we use innerHTML we can fix the DOM tree
\r
1912 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
\r
1913 h = h.replace(/<\/p>/gi, '</div>');
\r
1915 // Set the new HTML with DIVs
\r
1918 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
\r
1919 // This is needed since IE has a annoying bug see above for details
\r
1920 // This is a slow process but it has to be done. :(
\r
1921 if (t.settings.fix_ie_paragraphs) {
\r
1922 nl = e.getElementsByTagName("DIV");
\r
1923 for (i = nl.length - 1; i >= 0; i--) {
\r
1926 // Is it a temp div
\r
1928 // Create new paragraph
\r
1929 p = t.doc.createElement('p');
\r
1931 // Copy all attributes
\r
1932 n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
\r
1935 if (b !== '_mce_tmp') {
\r
1936 v = n.getAttribute(b);
\r
1938 if (!v && b === 'class')
\r
1941 p.setAttribute(b, v);
\r
1945 // Append all children to new paragraph
\r
1946 for (x = 0; x<n.childNodes.length; x++)
\r
1947 p.appendChild(n.childNodes[x].cloneNode(true));
\r
1949 // Replace div with new paragraph
\r
1962 processHTML : function(h) {
\r
1963 var t = this, s = t.settings, codeBlocks = [];
\r
1965 if (!s.process_html)
\r
1969 h = h.replace(/'/g, '''); // IE can't handle apos
\r
1970 h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
\r
1973 // Fix some issues
\r
1974 h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
\r
1976 // Store away src and href in _mce_src and mce_href since browsers mess them up
\r
1977 if (s.keep_values) {
\r
1978 // Wrap scripts and styles in comments for serialization purposes
\r
1979 if (/<script|noscript|style/i.test(h)) {
\r
1980 function trim(s) {
\r
1981 // Remove prefix and suffix code for element
\r
1982 s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
\r
1983 s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
\r
1984 s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
\r
1985 s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
\r
1990 // Wrap the script contents in CDATA and keep them from executing
\r
1991 h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
\r
1992 // Force type attribute
\r
1994 attribs = ' type="text/javascript"';
\r
1996 // Convert the src attribute of the scripts
\r
1997 attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
\r
1998 if (s.url_converter)
\r
1999 url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
\r
2001 return '_mce_src="' + url + '"';
\r
2004 // Wrap text contents
\r
2005 if (tinymce.trim(text)) {
\r
2006 codeBlocks.push(trim(text));
\r
2007 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
\r
2010 return '<mce:script' + attribs + '>' + text + '</mce:script>';
\r
2013 // Wrap style elements
\r
2014 h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
\r
2015 // Wrap text contents
\r
2017 codeBlocks.push(trim(text));
\r
2018 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
\r
2021 return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
\r
2024 // Wrap noscript elements
\r
2025 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
2026 return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '--') + '--></mce:noscript>';
\r
2030 h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
\r
2032 // This function processes the attributes in the HTML string to force boolean
\r
2033 // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
\r
2034 function processTags(html) {
\r
2035 return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
\r
2036 return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
\r
2039 name = name.toLowerCase();
\r
2040 value = value || val2 || val3 || "";
\r
2042 // Treat boolean attributes
\r
2043 if (boolAttrs[name]) {
\r
2044 // false or 0 is treated as a missing attribute
\r
2045 if (value === 'false' || value === '0')
\r
2048 return name + '="' + name + '"';
\r
2051 // Is attribute one that needs special treatment
\r
2052 if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
\r
2053 mceValue = t.decode(value);
\r
2055 // Convert URLs to relative/absolute ones
\r
2056 if (s.url_converter && (name == "src" || name == "href"))
\r
2057 mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
\r
2059 // Process styles lowercases them and compresses them
\r
2060 if (name == 'style')
\r
2061 mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
\r
2063 return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
\r
2071 h = processTags(h);
\r
2073 // Restore script blocks
\r
2074 h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
\r
2075 return codeBlocks[idx];
\r
2082 getOuterHTML : function(e) {
\r
2090 if (e.outerHTML !== undefined)
\r
2091 return e.outerHTML;
\r
2093 d = (e.ownerDocument || this.doc).createElement("body");
\r
2094 d.appendChild(e.cloneNode(true));
\r
2096 return d.innerHTML;
\r
2099 setOuterHTML : function(e, h, d) {
\r
2102 function setHTML(e, h, d) {
\r
2105 tp = d.createElement("body");
\r
2110 t.insertAfter(n.cloneNode(true), e);
\r
2111 n = n.previousSibling;
\r
2117 return this.run(e, function(e) {
\r
2120 // Only set HTML on elements
\r
2121 if (e.nodeType == 1) {
\r
2122 d = d || e.ownerDocument || t.doc;
\r
2126 // Try outerHTML for IE it sometimes produces an unknown runtime error
\r
2127 if (isIE && e.nodeType == 1)
\r
2132 // Fix for unknown runtime error
\r
2141 decode : function(s) {
\r
2144 // Look for entities to decode
\r
2145 if (/&[\w#]+;/.test(s)) {
\r
2146 // Decode the entities using a div element not super efficient but less code
\r
2147 e = this.doc.createElement("div");
\r
2155 } while (n = n.nextSibling);
\r
2164 encode : function(str) {
\r
2165 return ('' + str).replace(encodeCharsRe, function(chr) {
\r
2166 return encodedChars[chr];
\r
2170 insertAfter : function(node, reference_node) {
\r
2171 reference_node = this.get(reference_node);
\r
2173 return this.run(node, function(node) {
\r
2174 var parent, nextSibling;
\r
2176 parent = reference_node.parentNode;
\r
2177 nextSibling = reference_node.nextSibling;
\r
2180 parent.insertBefore(node, nextSibling);
\r
2182 parent.appendChild(node);
\r
2188 isBlock : function(n) {
\r
2189 if (n.nodeType && n.nodeType !== 1)
\r
2192 n = n.nodeName || n;
\r
2194 return blockRe.test(n);
\r
2197 replace : function(n, o, k) {
\r
2200 if (is(o, 'array'))
\r
2201 n = n.cloneNode(true);
\r
2203 return t.run(o, function(o) {
\r
2205 each(tinymce.grep(o.childNodes), function(c) {
\r
2210 return o.parentNode.replaceChild(n, o);
\r
2214 rename : function(elm, name) {
\r
2215 var t = this, newElm;
\r
2217 if (elm.nodeName != name.toUpperCase()) {
\r
2218 // Rename block element
\r
2219 newElm = t.create(name);
\r
2221 // Copy attribs to new block
\r
2222 each(t.getAttribs(elm), function(attr_node) {
\r
2223 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
\r
2227 t.replace(newElm, elm, 1);
\r
2230 return newElm || elm;
\r
2233 findCommonAncestor : function(a, b) {
\r
2239 while (pe && ps != pe)
\r
2240 pe = pe.parentNode;
\r
2245 ps = ps.parentNode;
\r
2248 if (!ps && a.ownerDocument)
\r
2249 return a.ownerDocument.documentElement;
\r
2254 toHex : function(s) {
\r
2255 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
\r
2258 s = parseInt(s).toString(16);
\r
2260 return s.length > 1 ? s : '0' + s; // 0 -> 00
\r
2264 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
\r
2272 getClasses : function() {
\r
2273 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
\r
2278 function addClasses(s) {
\r
2279 // IE style imports
\r
2280 each(s.imports, function(r) {
\r
2284 each(s.cssRules || s.rules, function(r) {
\r
2285 // Real type or fake it on IE
\r
2286 switch (r.type || 1) {
\r
2289 if (r.selectorText) {
\r
2290 each(r.selectorText.split(','), function(v) {
\r
2291 v = v.replace(/^\s*|\s*$|^\s\./g, "");
\r
2293 // Is internal or it doesn't contain a class
\r
2294 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
\r
2297 // Remove everything but class name
\r
2299 v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
\r
2302 if (f && !(v = f(v, ov)))
\r
2306 cl.push({'class' : v});
\r
2315 addClasses(r.styleSheet);
\r
2322 each(t.doc.styleSheets, addClasses);
\r
2327 if (cl.length > 0)
\r
2333 run : function(e, f, s) {
\r
2336 if (t.doc && typeof(e) === 'string')
\r
2343 if (!e.nodeType && (e.length || e.length === 0)) {
\r
2346 each(e, function(e, i) {
\r
2348 if (typeof(e) == 'string')
\r
2349 e = t.doc.getElementById(e);
\r
2351 o.push(f.call(s, e, i));
\r
2358 return f.call(s, e);
\r
2361 getAttribs : function(n) {
\r
2372 // Object will throw exception in IE
\r
2373 if (n.nodeName == 'OBJECT')
\r
2374 return n.attributes;
\r
2376 // IE doesn't keep the selected attribute if you clone option elements
\r
2377 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
\r
2378 o.push({specified : 1, nodeName : 'selected'});
\r
2380 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
\r
2381 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
\r
2382 o.push({specified : 1, nodeName : a});
\r
2388 return n.attributes;
\r
2391 destroy : function(s) {
\r
2395 t.events.destroy();
\r
2397 t.win = t.doc = t.root = t.events = null;
\r
2399 // Manual destroy then remove unload handler
\r
2401 tinymce.removeUnload(t.destroy);
\r
2404 createRng : function() {
\r
2407 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
\r
2410 nodeIndex : function(node, normalized) {
\r
2411 var idx = 0, lastNodeType, lastNode, nodeType;
\r
2414 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
\r
2415 nodeType = node.nodeType;
\r
2417 // Normalize text nodes
\r
2418 if (normalized && nodeType == 3) {
\r
2419 if (nodeType == lastNodeType || !node.nodeValue.length)
\r
2424 lastNodeType = nodeType;
\r
2431 split : function(pe, e, re) {
\r
2432 var t = this, r = t.createRng(), bef, aft, pa;
\r
2434 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
\r
2435 // but we don't want that in our code since it serves no purpose for the end user
\r
2436 // For example if this is chopped:
\r
2437 // <p>text 1<span><b>CHOP</b></span>text 2</p>
\r
2439 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
\r
2440 // this function will then trim of empty edges and produce:
\r
2441 // <p>text 1</p><b>CHOP</b><p>text 2</p>
\r
2442 function trim(node) {
\r
2443 var i, children = node.childNodes;
\r
2445 if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
\r
2448 for (i = children.length - 1; i >= 0; i--)
\r
2449 trim(children[i]);
\r
2451 if (node.nodeType != 9) {
\r
2452 // Keep non whitespace text nodes
\r
2453 if (node.nodeType == 3 && node.nodeValue.length > 0)
\r
2456 if (node.nodeType == 1) {
\r
2457 // If the only child is a bookmark then move it up
\r
2458 children = node.childNodes;
\r
2459 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
\r
2460 node.parentNode.insertBefore(children[0], node);
\r
2462 // Keep non empty elements or img, hr etc
\r
2463 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
\r
2474 // Get before chunk
\r
2475 r.setStart(pe.parentNode, t.nodeIndex(pe));
\r
2476 r.setEnd(e.parentNode, t.nodeIndex(e));
\r
2477 bef = r.extractContents();
\r
2479 // Get after chunk
\r
2480 r = t.createRng();
\r
2481 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
\r
2482 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
\r
2483 aft = r.extractContents();
\r
2485 // Insert before chunk
\r
2486 pa = pe.parentNode;
\r
2487 pa.insertBefore(trim(bef), pe);
\r
2489 // Insert middle chunk
\r
2491 pa.replaceChild(re, e);
\r
2493 pa.insertBefore(e, pe);
\r
2495 // Insert after chunk
\r
2496 pa.insertBefore(trim(aft), pe);
\r
2503 bind : function(target, name, func, scope) {
\r
2507 t.events = new tinymce.dom.EventUtils();
\r
2509 return t.events.add(target, name, func, scope || this);
\r
2512 unbind : function(target, name, func) {
\r
2516 t.events = new tinymce.dom.EventUtils();
\r
2518 return t.events.remove(target, name, func);
\r
2522 _findSib : function(node, selector, name) {
\r
2523 var t = this, f = selector;
\r
2526 // If expression make a function of it using is
\r
2527 if (is(f, 'string')) {
\r
2528 f = function(node) {
\r
2529 return t.is(node, selector);
\r
2533 // Loop all siblings
\r
2534 for (node = node[name]; node; node = node[name]) {
\r
2543 _isRes : function(c) {
\r
2544 // Is live resizble element
\r
2545 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
\r
2549 walk : function(n, f, s) {
\r
2550 var d = this.doc, w;
\r
2552 if (d.createTreeWalker) {
\r
2553 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
\r
2555 while ((n = w.nextNode()) != null)
\r
2556 f.call(s || this, n);
\r
2558 tinymce.walk(n, f, 'childNodes', s);
\r
2563 toRGB : function(s) {
\r
2564 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
\r
2567 // #FFF -> #FFFFFF
\r
2569 c[3] = c[2] = c[1];
\r
2571 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
\r
2579 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
\r
2583 // Range constructor
\r
2584 function Range(dom) {
\r
2592 START_OFFSET = 'startOffset',
\r
2593 START_CONTAINER = 'startContainer',
\r
2594 END_CONTAINER = 'endContainer',
\r
2595 END_OFFSET = 'endOffset',
\r
2596 extend = tinymce.extend,
\r
2597 nodeIndex = dom.nodeIndex;
\r
2601 startContainer : doc,
\r
2603 endContainer : doc,
\r
2606 commonAncestorContainer : doc,
\r
2608 // Range constants
\r
2609 START_TO_START : 0,
\r
2615 setStart : setStart,
\r
2617 setStartBefore : setStartBefore,
\r
2618 setStartAfter : setStartAfter,
\r
2619 setEndBefore : setEndBefore,
\r
2620 setEndAfter : setEndAfter,
\r
2621 collapse : collapse,
\r
2622 selectNode : selectNode,
\r
2623 selectNodeContents : selectNodeContents,
\r
2624 compareBoundaryPoints : compareBoundaryPoints,
\r
2625 deleteContents : deleteContents,
\r
2626 extractContents : extractContents,
\r
2627 cloneContents : cloneContents,
\r
2628 insertNode : insertNode,
\r
2629 surroundContents : surroundContents,
\r
2630 cloneRange : cloneRange
\r
2633 function setStart(n, o) {
\r
2634 _setEndPoint(TRUE, n, o);
\r
2637 function setEnd(n, o) {
\r
2638 _setEndPoint(FALSE, n, o);
\r
2641 function setStartBefore(n) {
\r
2642 setStart(n.parentNode, nodeIndex(n));
\r
2645 function setStartAfter(n) {
\r
2646 setStart(n.parentNode, nodeIndex(n) + 1);
\r
2649 function setEndBefore(n) {
\r
2650 setEnd(n.parentNode, nodeIndex(n));
\r
2653 function setEndAfter(n) {
\r
2654 setEnd(n.parentNode, nodeIndex(n) + 1);
\r
2657 function collapse(ts) {
\r
2659 t[END_CONTAINER] = t[START_CONTAINER];
\r
2660 t[END_OFFSET] = t[START_OFFSET];
\r
2662 t[START_CONTAINER] = t[END_CONTAINER];
\r
2663 t[START_OFFSET] = t[END_OFFSET];
\r
2666 t.collapsed = TRUE;
\r
2669 function selectNode(n) {
\r
2670 setStartBefore(n);
\r
2674 function selectNodeContents(n) {
\r
2676 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
\r
2679 function compareBoundaryPoints(h, r) {
\r
2680 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
\r
2682 // Check START_TO_START
\r
2684 return _compareBoundaryPoints(sc, so, sc, so);
\r
2686 // Check START_TO_END
\r
2688 return _compareBoundaryPoints(sc, so, ec, eo);
\r
2690 // Check END_TO_END
\r
2692 return _compareBoundaryPoints(ec, eo, ec, eo);
\r
2694 // Check END_TO_START
\r
2696 return _compareBoundaryPoints(ec, eo, sc, so);
\r
2699 function deleteContents() {
\r
2700 _traverse(DELETE);
\r
2703 function extractContents() {
\r
2704 return _traverse(EXTRACT);
\r
2707 function cloneContents() {
\r
2708 return _traverse(CLONE);
\r
2711 function insertNode(n) {
\r
2712 var startContainer = this[START_CONTAINER],
\r
2713 startOffset = this[START_OFFSET], nn, o;
\r
2715 // Node is TEXT_NODE or CDATA
\r
2716 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
\r
2717 if (!startOffset) {
\r
2718 // At the start of text
\r
2719 startContainer.parentNode.insertBefore(n, startContainer);
\r
2720 } else if (startOffset >= startContainer.nodeValue.length) {
\r
2721 // At the end of text
\r
2722 dom.insertAfter(n, startContainer);
\r
2724 // Middle, need to split
\r
2725 nn = startContainer.splitText(startOffset);
\r
2726 startContainer.parentNode.insertBefore(n, nn);
\r
2729 // Insert element node
\r
2730 if (startContainer.childNodes.length > 0)
\r
2731 o = startContainer.childNodes[startOffset];
\r
2734 startContainer.insertBefore(n, o);
\r
2736 startContainer.appendChild(n);
\r
2740 function surroundContents(n) {
\r
2741 var f = t.extractContents();
\r
2748 function cloneRange() {
\r
2749 return extend(new Range(dom), {
\r
2750 startContainer : t[START_CONTAINER],
\r
2751 startOffset : t[START_OFFSET],
\r
2752 endContainer : t[END_CONTAINER],
\r
2753 endOffset : t[END_OFFSET],
\r
2754 collapsed : t.collapsed,
\r
2755 commonAncestorContainer : t.commonAncestorContainer
\r
2759 // Private methods
\r
2761 function _getSelectedNode(container, offset) {
\r
2764 if (container.nodeType == 3 /* TEXT_NODE */)
\r
2770 child = container.firstChild;
\r
2771 while (child && offset > 0) {
\r
2773 child = child.nextSibling;
\r
2782 function _isCollapsed() {
\r
2783 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
\r
2786 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
\r
2787 var c, offsetC, n, cmnRoot, childA, childB;
\r
2789 // In the first case the boundary-points have the same container. A is before B
\r
2790 // if its offset is less than the offset of B, A is equal to B if its offset is
\r
2791 // equal to the offset of B, and A is after B if its offset is greater than the
\r
2793 if (containerA == containerB) {
\r
2794 if (offsetA == offsetB)
\r
2795 return 0; // equal
\r
2797 if (offsetA < offsetB)
\r
2798 return -1; // before
\r
2800 return 1; // after
\r
2803 // In the second case a child node C of the container of A is an ancestor
\r
2804 // container of B. In this case, A is before B if the offset of A is less than or
\r
2805 // equal to the index of the child node C and A is after B otherwise.
\r
2807 while (c && c.parentNode != containerA)
\r
2812 n = containerA.firstChild;
\r
2814 while (n != c && offsetC < offsetA) {
\r
2816 n = n.nextSibling;
\r
2819 if (offsetA <= offsetC)
\r
2820 return -1; // before
\r
2822 return 1; // after
\r
2825 // In the third case a child node C of the container of B is an ancestor container
\r
2826 // of A. In this case, A is before B if the index of the child node C is less than
\r
2827 // the offset of B and A is after B otherwise.
\r
2829 while (c && c.parentNode != containerB) {
\r
2835 n = containerB.firstChild;
\r
2837 while (n != c && offsetC < offsetB) {
\r
2839 n = n.nextSibling;
\r
2842 if (offsetC < offsetB)
\r
2843 return -1; // before
\r
2845 return 1; // after
\r
2848 // In the fourth case, none of three other cases hold: the containers of A and B
\r
2849 // are siblings or descendants of sibling nodes. In this case, A is before B if
\r
2850 // the container of A is before the container of B in a pre-order traversal of the
\r
2851 // Ranges' context tree and A is after B otherwise.
\r
2852 cmnRoot = dom.findCommonAncestor(containerA, containerB);
\r
2853 childA = containerA;
\r
2855 while (childA && childA.parentNode != cmnRoot)
\r
2856 childA = childA.parentNode;
\r
2861 childB = containerB;
\r
2862 while (childB && childB.parentNode != cmnRoot)
\r
2863 childB = childB.parentNode;
\r
2868 if (childA == childB)
\r
2869 return 0; // equal
\r
2871 n = cmnRoot.firstChild;
\r
2874 return -1; // before
\r
2877 return 1; // after
\r
2879 n = n.nextSibling;
\r
2883 function _setEndPoint(st, n, o) {
\r
2887 t[START_CONTAINER] = n;
\r
2888 t[START_OFFSET] = o;
\r
2890 t[END_CONTAINER] = n;
\r
2891 t[END_OFFSET] = o;
\r
2894 // If one boundary-point of a Range is set to have a root container
\r
2895 // other than the current one for the Range, the Range is collapsed to
\r
2896 // the new position. This enforces the restriction that both boundary-
\r
2897 // points of a Range must have the same root container.
\r
2898 ec = t[END_CONTAINER];
\r
2899 while (ec.parentNode)
\r
2900 ec = ec.parentNode;
\r
2902 sc = t[START_CONTAINER];
\r
2903 while (sc.parentNode)
\r
2904 sc = sc.parentNode;
\r
2907 // The start position of a Range is guaranteed to never be after the
\r
2908 // end position. To enforce this restriction, if the start is set to
\r
2909 // be at a position after the end, the Range is collapsed to that
\r
2911 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
\r
2916 t.collapsed = _isCollapsed();
\r
2917 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
\r
2920 function _traverse(how) {
\r
2921 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
\r
2923 if (t[START_CONTAINER] == t[END_CONTAINER])
\r
2924 return _traverseSameContainer(how);
\r
2926 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
2927 if (p == t[START_CONTAINER])
\r
2928 return _traverseCommonStartContainer(c, how);
\r
2930 ++endContainerDepth;
\r
2933 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
2934 if (p == t[END_CONTAINER])
\r
2935 return _traverseCommonEndContainer(c, how);
\r
2937 ++startContainerDepth;
\r
2940 depthDiff = startContainerDepth - endContainerDepth;
\r
2942 startNode = t[START_CONTAINER];
\r
2943 while (depthDiff > 0) {
\r
2944 startNode = startNode.parentNode;
\r
2948 endNode = t[END_CONTAINER];
\r
2949 while (depthDiff < 0) {
\r
2950 endNode = endNode.parentNode;
\r
2954 // ascend the ancestor hierarchy until we have a common parent.
\r
2955 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
\r
2960 return _traverseCommonAncestors(startNode, endNode, how);
\r
2963 function _traverseSameContainer(how) {
\r
2964 var frag, s, sub, n, cnt, sibling, xferNode;
\r
2966 if (how != DELETE)
\r
2967 frag = doc.createDocumentFragment();
\r
2969 // If selection is empty, just return the fragment
\r
2970 if (t[START_OFFSET] == t[END_OFFSET])
\r
2973 // Text node needs special case handling
\r
2974 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
\r
2975 // get the substring
\r
2976 s = t[START_CONTAINER].nodeValue;
\r
2977 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
\r
2979 // set the original text node to its new value
\r
2980 if (how != CLONE) {
\r
2981 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
\r
2983 // Nothing is partially selected, so collapse to start point
\r
2987 if (how == DELETE)
\r
2990 frag.appendChild(doc.createTextNode(sub));
\r
2994 // Copy nodes between the start/end offsets.
\r
2995 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
\r
2996 cnt = t[END_OFFSET] - t[START_OFFSET];
\r
2999 sibling = n.nextSibling;
\r
3000 xferNode = _traverseFullySelected(n, how);
\r
3003 frag.appendChild( xferNode );
\r
3009 // Nothing is partially selected, so collapse to start point
\r
3016 function _traverseCommonStartContainer(endAncestor, how) {
\r
3017 var frag, n, endIdx, cnt, sibling, xferNode;
\r
3019 if (how != DELETE)
\r
3020 frag = doc.createDocumentFragment();
\r
3022 n = _traverseRightBoundary(endAncestor, how);
\r
3025 frag.appendChild(n);
\r
3027 endIdx = nodeIndex(endAncestor);
\r
3028 cnt = endIdx - t[START_OFFSET];
\r
3031 // Collapse to just before the endAncestor, which
\r
3032 // is partially selected.
\r
3033 if (how != CLONE) {
\r
3034 t.setEndBefore(endAncestor);
\r
3035 t.collapse(FALSE);
\r
3041 n = endAncestor.previousSibling;
\r
3043 sibling = n.previousSibling;
\r
3044 xferNode = _traverseFullySelected(n, how);
\r
3047 frag.insertBefore(xferNode, frag.firstChild);
\r
3053 // Collapse to just before the endAncestor, which
\r
3054 // is partially selected.
\r
3055 if (how != CLONE) {
\r
3056 t.setEndBefore(endAncestor);
\r
3057 t.collapse(FALSE);
\r
3063 function _traverseCommonEndContainer(startAncestor, how) {
\r
3064 var frag, startIdx, n, cnt, sibling, xferNode;
\r
3066 if (how != DELETE)
\r
3067 frag = doc.createDocumentFragment();
\r
3069 n = _traverseLeftBoundary(startAncestor, how);
\r
3071 frag.appendChild(n);
\r
3073 startIdx = nodeIndex(startAncestor);
\r
3074 ++startIdx; // Because we already traversed it....
\r
3076 cnt = t[END_OFFSET] - startIdx;
\r
3077 n = startAncestor.nextSibling;
\r
3079 sibling = n.nextSibling;
\r
3080 xferNode = _traverseFullySelected(n, how);
\r
3083 frag.appendChild(xferNode);
\r
3089 if (how != CLONE) {
\r
3090 t.setStartAfter(startAncestor);
\r
3097 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
\r
3098 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
\r
3100 if (how != DELETE)
\r
3101 frag = doc.createDocumentFragment();
\r
3103 n = _traverseLeftBoundary(startAncestor, how);
\r
3105 frag.appendChild(n);
\r
3107 commonParent = startAncestor.parentNode;
\r
3108 startOffset = nodeIndex(startAncestor);
\r
3109 endOffset = nodeIndex(endAncestor);
\r
3112 cnt = endOffset - startOffset;
\r
3113 sibling = startAncestor.nextSibling;
\r
3116 nextSibling = sibling.nextSibling;
\r
3117 n = _traverseFullySelected(sibling, how);
\r
3120 frag.appendChild(n);
\r
3122 sibling = nextSibling;
\r
3126 n = _traverseRightBoundary(endAncestor, how);
\r
3129 frag.appendChild(n);
\r
3131 if (how != CLONE) {
\r
3132 t.setStartAfter(startAncestor);
\r
3139 function _traverseRightBoundary(root, how) {
\r
3140 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
\r
3143 return _traverseNode(next, isFullySelected, FALSE, how);
\r
3145 parent = next.parentNode;
\r
3146 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3150 prevSibling = next.previousSibling;
\r
3151 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
\r
3153 if (how != DELETE)
\r
3154 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
\r
3156 isFullySelected = TRUE;
\r
3157 next = prevSibling;
\r
3160 if (parent == root)
\r
3161 return clonedParent;
\r
3163 next = parent.previousSibling;
\r
3164 parent = parent.parentNode;
\r
3166 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3168 if (how != DELETE)
\r
3169 clonedGrandParent.appendChild(clonedParent);
\r
3171 clonedParent = clonedGrandParent;
\r
3175 function _traverseLeftBoundary(root, how) {
\r
3176 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
\r
3179 return _traverseNode(next, isFullySelected, TRUE, how);
\r
3181 parent = next.parentNode;
\r
3182 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3186 nextSibling = next.nextSibling;
\r
3187 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
\r
3189 if (how != DELETE)
\r
3190 clonedParent.appendChild(clonedChild);
\r
3192 isFullySelected = TRUE;
\r
3193 next = nextSibling;
\r
3196 if (parent == root)
\r
3197 return clonedParent;
\r
3199 next = parent.nextSibling;
\r
3200 parent = parent.parentNode;
\r
3202 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3204 if (how != DELETE)
\r
3205 clonedGrandParent.appendChild(clonedParent);
\r
3207 clonedParent = clonedGrandParent;
\r
3211 function _traverseNode(n, isFullySelected, isLeft, how) {
\r
3212 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
\r
3214 if (isFullySelected)
\r
3215 return _traverseFullySelected(n, how);
\r
3217 if (n.nodeType == 3 /* TEXT_NODE */) {
\r
3218 txtValue = n.nodeValue;
\r
3221 offset = t[START_OFFSET];
\r
3222 newNodeValue = txtValue.substring(offset);
\r
3223 oldNodeValue = txtValue.substring(0, offset);
\r
3225 offset = t[END_OFFSET];
\r
3226 newNodeValue = txtValue.substring(0, offset);
\r
3227 oldNodeValue = txtValue.substring(offset);
\r
3231 n.nodeValue = oldNodeValue;
\r
3233 if (how == DELETE)
\r
3236 newNode = n.cloneNode(FALSE);
\r
3237 newNode.nodeValue = newNodeValue;
\r
3242 if (how == DELETE)
\r
3245 return n.cloneNode(FALSE);
\r
3248 function _traverseFullySelected(n, how) {
\r
3249 if (how != DELETE)
\r
3250 return how == CLONE ? n.cloneNode(TRUE) : n;
\r
3252 n.parentNode.removeChild(n);
\r
3260 function Selection(selection) {
\r
3261 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
\r
3263 // Returns a W3C DOM compatible range object by using the IE Range API
\r
3264 function getRange() {
\r
3265 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
\r
3267 // If selection is outside the current document just return an empty range
\r
3268 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
\r
3269 if (element.ownerDocument != dom.doc)
\r
3272 // Handle control selection or text selection of a image
\r
3273 if (ieRange.item || !element.hasChildNodes()) {
\r
3274 domRange.setStart(element.parentNode, dom.nodeIndex(element));
\r
3275 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
\r
3280 collapsed = selection.isCollapsed();
\r
3282 function findEndPoint(start) {
\r
3283 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
\r
3285 // Setup temp range and collapse it
\r
3286 checkRng = ieRange.duplicate();
\r
3287 checkRng.collapse(start);
\r
3289 // Create marker and insert it at the end of the endpoints parent
\r
3290 marker = dom.create('a');
\r
3291 parent = checkRng.parentElement();
\r
3293 // If parent doesn't have any children then set the container to that parent and the index to 0
\r
3294 if (!parent.hasChildNodes()) {
\r
3295 domRange[start ? 'setStart' : 'setEnd'](parent, 0);
\r
3299 parent.appendChild(marker);
\r
3300 checkRng.moveToElementText(marker);
\r
3301 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
\r
3302 if (position > 0) {
\r
3303 // The position is after the end of the parent element.
\r
3304 // This is the case where IE puts the caret to the left edge of a table.
\r
3305 domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
\r
3306 dom.remove(marker);
\r
3310 // Setup node list and endIndex
\r
3311 nodes = tinymce.grep(parent.childNodes);
\r
3312 endIndex = nodes.length - 1;
\r
3313 // Perform a binary search for the position
\r
3314 while (startIndex <= endIndex) {
\r
3315 index = Math.floor((startIndex + endIndex) / 2);
\r
3317 // Insert marker and check it's position relative to the selection
\r
3318 parent.insertBefore(marker, nodes[index]);
\r
3319 checkRng.moveToElementText(marker);
\r
3320 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
\r
3321 if (position > 0) {
\r
3322 // Marker is to the right
\r
3323 startIndex = index + 1;
\r
3324 } else if (position < 0) {
\r
3325 // Marker is to the left
\r
3326 endIndex = index - 1;
\r
3328 // Maker is where we are
\r
3334 // Setup container
\r
3335 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
\r
3337 // Handle element selection
\r
3338 if (container.nodeType == 1) {
\r
3339 dom.remove(marker);
\r
3341 // Find offset and container
\r
3342 offset = dom.nodeIndex(container);
\r
3343 container = container.parentNode;
\r
3345 // Move the offset if we are setting the end or the position is after an element
\r
3346 if (!start || index > 0)
\r
3349 // Calculate offset within text node
\r
3350 if (position > 0 || index == 0) {
\r
3351 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
\r
3352 offset = checkRng.text.length;
\r
3354 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
\r
3355 offset = container.nodeValue.length - checkRng.text.length;
\r
3358 dom.remove(marker);
\r
3361 domRange[start ? 'setStart' : 'setEnd'](container, offset);
\r
3364 // Find start point
\r
3365 findEndPoint(true);
\r
3367 // Find end point if needed
\r
3374 this.addRange = function(rng) {
\r
3375 var ieRng, ieRng2, doc = selection.dom.doc, body = doc.body, startPos, endPos, sc, so, ec, eo, marker, lastIndex, skipStart, skipEnd;
\r
3379 // Setup some shorter versions
\r
3380 sc = rng.startContainer;
\r
3381 so = rng.startOffset;
\r
3382 ec = rng.endContainer;
\r
3383 eo = rng.endOffset;
\r
3384 ieRng = body.createTextRange();
\r
3386 // If document selection move caret to first node in document
\r
3387 if (sc == doc || ec == doc) {
\r
3388 ieRng = body.createTextRange();
\r
3394 // If child index resolve it
\r
3395 if (sc.nodeType == 1 && sc.hasChildNodes()) {
\r
3396 lastIndex = sc.childNodes.length - 1;
\r
3398 // Index is higher that the child count then we need to jump over the start container
\r
3399 if (so > lastIndex) {
\r
3401 sc = sc.childNodes[lastIndex];
\r
3403 sc = sc.childNodes[so];
\r
3405 // Child was text node then move offset to start of it
\r
3406 if (sc.nodeType == 3)
\r
3410 // If child index resolve it
\r
3411 if (ec.nodeType == 1 && ec.hasChildNodes()) {
\r
3412 lastIndex = ec.childNodes.length - 1;
\r
3416 ec = ec.childNodes[0];
\r
3418 ec = ec.childNodes[Math.min(lastIndex, eo - 1)];
\r
3420 // Child was text node then move offset to end of text node
\r
3421 if (ec.nodeType == 3)
\r
3422 eo = ec.nodeValue.length;
\r
3426 // Single element selection
\r
3427 if (sc == ec && sc.nodeType == 1) {
\r
3428 // Make control selection for some elements
\r
3429 if (/^(IMG|TABLE)$/.test(sc.nodeName) && so != eo) {
\r
3430 ieRng = body.createControlRange();
\r
3431 ieRng.addElement(sc);
\r
3433 ieRng = body.createTextRange();
\r
3435 // Padd empty elements with invisible character
\r
3436 if (!sc.hasChildNodes() && sc.canHaveHTML)
\r
3437 sc.innerHTML = invisibleChar;
\r
3439 // Select element contents
\r
3440 ieRng.moveToElementText(sc);
\r
3442 // If it's only containing a padding remove it so the caret remains
\r
3443 if (sc.innerHTML == invisibleChar) {
\r
3444 ieRng.collapse(TRUE);
\r
3445 sc.removeChild(sc.firstChild);
\r
3450 ieRng.collapse(eo <= rng.endContainer.childNodes.length - 1);
\r
3453 ieRng.scrollIntoView();
\r
3457 // Create range and marker
\r
3458 ieRng = body.createTextRange();
\r
3459 marker = doc.createElement('span');
\r
3460 marker.innerHTML = ' ';
\r
3462 // Set start of range to startContainer/startOffset
\r
3463 if (sc.nodeType == 3) {
\r
3464 // Insert marker after/before startContainer
\r
3466 dom.insertAfter(marker, sc);
\r
3468 sc.parentNode.insertBefore(marker, sc);
\r
3470 // Select marker the caret to offset position
\r
3471 ieRng.moveToElementText(marker);
\r
3472 marker.parentNode.removeChild(marker);
\r
3474 // Move if we need to, moving it 0 characters actually moves it!
\r
3476 ieRng.move('character', so);
\r
3478 ieRng.moveToElementText(sc);
\r
3481 ieRng.collapse(FALSE);
\r
3484 // If same text container then we can do a more simple move
\r
3485 if (sc == ec && sc.nodeType == 3) {
\r
3487 ieRng.moveEnd('character', eo - so);
\r
3489 ieRng.scrollIntoView();
\r
3491 // Some times a Runtime error of the 800a025e type gets thrown
\r
3492 // especially when the caret is placed before a table.
\r
3493 // This is a somewhat strange location for the caret.
\r
3494 // TODO: Find a better solution for this would possible require a rewrite of the setRng method
\r
3500 // Set end of range to endContainer/endOffset
\r
3501 ieRng2 = body.createTextRange();
\r
3502 if (ec.nodeType == 3) {
\r
3503 // Insert marker after/before startContainer
\r
3504 ec.parentNode.insertBefore(marker, ec);
\r
3506 // Move selection to end marker and move caret to end offset
\r
3507 ieRng2.moveToElementText(marker);
\r
3508 marker.parentNode.removeChild(marker);
\r
3509 ieRng2.move('character', eo);
\r
3510 ieRng.setEndPoint('EndToStart', ieRng2);
\r
3512 ieRng2.moveToElementText(ec);
\r
3513 ieRng2.collapse(!!skipEnd);
\r
3514 ieRng.setEndPoint('EndToEnd', ieRng2);
\r
3518 ieRng.scrollIntoView();
\r
3521 this.getRangeAt = function() {
\r
3522 // Setup new range if the cache is empty
\r
3523 if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
\r
3524 range = getRange();
\r
3526 // Store away text range for next call
\r
3527 lastIERng = selection.getRng();
\r
3530 // IE will say that the range is equal then produce an invalid argument exception
\r
3531 // if you perform specific operations in a keyup event. For example Ctrl+Del.
\r
3532 // This hack will invalidate the range cache if the exception occurs
\r
3534 range.startContainer.nextSibling;
\r
3536 range = getRange();
\r
3540 // Return cached range
\r
3544 this.destroy = function() {
\r
3545 // Destroy cached range and last IE range to avoid memory leaks
\r
3546 lastIERng = range = null;
\r
3549 // 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
3550 if (selection.dom.boxModel) {
\r
3552 var doc = dom.doc, body = doc.body, started, startRng;
\r
3554 // Make HTML element unselectable since we are going to handle selection by hand
\r
3555 doc.documentElement.unselectable = TRUE;
\r
3557 // Return range from point or null if it failed
\r
3558 function rngFromPoint(x, y) {
\r
3559 var rng = body.createTextRange();
\r
3562 rng.moveToPoint(x, y);
\r
3564 // IE sometimes throws and exception, so lets just ignore it
\r
3571 // Fires while the selection is changing
\r
3572 function selectionChange(e) {
\r
3575 // Check if the button is down or not
\r
3577 // Create range from mouse position
\r
3578 pointRng = rngFromPoint(e.x, e.y);
\r
3581 // Check if pointRange is before/after selection then change the endPoint
\r
3582 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
\r
3583 pointRng.setEndPoint('StartToStart', startRng);
\r
3585 pointRng.setEndPoint('EndToEnd', startRng);
\r
3587 pointRng.select();
\r
3593 // Removes listeners
\r
3594 function endSelection() {
\r
3595 dom.unbind(doc, 'mouseup', endSelection);
\r
3596 dom.unbind(doc, 'mousemove', selectionChange);
\r
3600 // Detect when user selects outside BODY
\r
3601 dom.bind(doc, 'mousedown', function(e) {
\r
3602 if (e.target.nodeName === 'HTML') {
\r
3608 // Setup start position
\r
3609 startRng = rngFromPoint(e.x, e.y);
\r
3611 // Listen for selection change events
\r
3612 dom.bind(doc, 'mouseup', endSelection);
\r
3613 dom.bind(doc, 'mousemove', selectionChange);
\r
3615 startRng.select();
\r
3623 // Expose the selection object
\r
3624 tinymce.dom.TridentSelection = Selection;
\r
3629 * Sizzle CSS Selector Engine - v1.0
\r
3630 * Copyright 2009, The Dojo Foundation
\r
3631 * Released under the MIT, BSD, and GPL Licenses.
\r
3632 * More information: http://sizzlejs.com/
\r
3636 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
\r
3638 toString = Object.prototype.toString,
\r
3639 hasDuplicate = false,
\r
3640 baseHasDuplicate = true;
\r
3642 // Here we check if the JavaScript engine is using some sort of
\r
3643 // optimization where it does not always call our comparision
\r
3644 // function. If that is the case, discard the hasDuplicate value.
\r
3645 // Thus far that includes Google Chrome.
\r
3646 [0, 0].sort(function(){
\r
3647 baseHasDuplicate = false;
\r
3651 var Sizzle = function(selector, context, results, seed) {
\r
3652 results = results || [];
\r
3653 context = context || document;
\r
3655 var origContext = context;
\r
3657 if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
\r
3661 if ( !selector || typeof selector !== "string" ) {
\r
3665 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
\r
3666 soFar = selector, ret, cur, pop, i;
\r
3668 // Reset the position of the chunker regexp (start from head)
\r
3671 m = chunker.exec(soFar);
\r
3676 parts.push( m[1] );
\r
3685 if ( parts.length > 1 && origPOS.exec( selector ) ) {
\r
3686 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
\r
3687 set = posProcess( parts[0] + parts[1], context );
\r
3689 set = Expr.relative[ parts[0] ] ?
\r
3691 Sizzle( parts.shift(), context );
\r
3693 while ( parts.length ) {
\r
3694 selector = parts.shift();
\r
3696 if ( Expr.relative[ selector ] ) {
\r
3697 selector += parts.shift();
\r
3700 set = posProcess( selector, set );
\r
3704 // Take a shortcut and set the context if the root selector is an ID
\r
3705 // (but not if it'll be faster if the inner selector is an ID)
\r
3706 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
\r
3707 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
\r
3708 ret = Sizzle.find( parts.shift(), context, contextXML );
\r
3709 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
\r
3714 { expr: parts.pop(), set: makeArray(seed) } :
\r
3715 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
\r
3716 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
\r
3718 if ( parts.length > 0 ) {
\r
3719 checkSet = makeArray(set);
\r
3724 while ( parts.length ) {
\r
3725 cur = parts.pop();
\r
3728 if ( !Expr.relative[ cur ] ) {
\r
3731 pop = parts.pop();
\r
3734 if ( pop == null ) {
\r
3738 Expr.relative[ cur ]( checkSet, pop, contextXML );
\r
3741 checkSet = parts = [];
\r
3745 if ( !checkSet ) {
\r
3749 if ( !checkSet ) {
\r
3750 Sizzle.error( cur || selector );
\r
3753 if ( toString.call(checkSet) === "[object Array]" ) {
\r
3755 results.push.apply( results, checkSet );
\r
3756 } else if ( context && context.nodeType === 1 ) {
\r
3757 for ( i = 0; checkSet[i] != null; i++ ) {
\r
3758 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
\r
3759 results.push( set[i] );
\r
3763 for ( i = 0; checkSet[i] != null; i++ ) {
\r
3764 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
\r
3765 results.push( set[i] );
\r
3770 makeArray( checkSet, results );
\r
3774 Sizzle( extra, origContext, results, seed );
\r
3775 Sizzle.uniqueSort( results );
\r
3781 Sizzle.uniqueSort = function(results){
\r
3782 if ( sortOrder ) {
\r
3783 hasDuplicate = baseHasDuplicate;
\r
3784 results.sort(sortOrder);
\r
3786 if ( hasDuplicate ) {
\r
3787 for ( var i = 1; i < results.length; i++ ) {
\r
3788 if ( results[i] === results[i-1] ) {
\r
3789 results.splice(i--, 1);
\r
3798 Sizzle.matches = function(expr, set){
\r
3799 return Sizzle(expr, null, null, set);
\r
3802 Sizzle.find = function(expr, context, isXML){
\r
3809 for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
\r
3810 var type = Expr.order[i], match;
\r
3812 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
\r
3813 var left = match[1];
\r
3814 match.splice(1,1);
\r
3816 if ( left.substr( left.length - 1 ) !== "\\" ) {
\r
3817 match[1] = (match[1] || "").replace(/\\/g, "");
\r
3818 set = Expr.find[ type ]( match, context, isXML );
\r
3819 if ( set != null ) {
\r
3820 expr = expr.replace( Expr.match[ type ], "" );
\r
3828 set = context.getElementsByTagName("*");
\r
3831 return {set: set, expr: expr};
\r
3834 Sizzle.filter = function(expr, set, inplace, not){
\r
3835 var old = expr, result = [], curLoop = set, match, anyFound,
\r
3836 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
\r
3838 while ( expr && set.length ) {
\r
3839 for ( var type in Expr.filter ) {
\r
3840 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
\r
3841 var filter = Expr.filter[ type ], found, item, left = match[1];
\r
3844 match.splice(1,1);
\r
3846 if ( left.substr( left.length - 1 ) === "\\" ) {
\r
3850 if ( curLoop === result ) {
\r
3854 if ( Expr.preFilter[ type ] ) {
\r
3855 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
\r
3858 anyFound = found = true;
\r
3859 } else if ( match === true ) {
\r
3865 for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
\r
3867 found = filter( item, match, i, curLoop );
\r
3868 var pass = not ^ !!found;
\r
3870 if ( inplace && found != null ) {
\r
3874 curLoop[i] = false;
\r
3876 } else if ( pass ) {
\r
3877 result.push( item );
\r
3884 if ( found !== undefined ) {
\r
3889 expr = expr.replace( Expr.match[ type ], "" );
\r
3891 if ( !anyFound ) {
\r
3900 // Improper expression
\r
3901 if ( expr === old ) {
\r
3902 if ( anyFound == null ) {
\r
3903 Sizzle.error( expr );
\r
3915 Sizzle.error = function( msg ) {
\r
3916 throw "Syntax error, unrecognized expression: " + msg;
\r
3919 var Expr = Sizzle.selectors = {
\r
3920 order: [ "ID", "NAME", "TAG" ],
\r
3922 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
\r
3923 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
\r
3924 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
\r
3925 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
\r
3926 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
\r
3927 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
\r
3928 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
\r
3929 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
\r
3933 "class": "className",
\r
3937 href: function(elem){
\r
3938 return elem.getAttribute("href");
\r
3942 "+": function(checkSet, part){
\r
3943 var isPartStr = typeof part === "string",
\r
3944 isTag = isPartStr && !/\W/.test(part),
\r
3945 isPartStrNotTag = isPartStr && !isTag;
\r
3948 part = part.toLowerCase();
\r
3951 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
\r
3952 if ( (elem = checkSet[i]) ) {
\r
3953 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
\r
3955 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
\r
3961 if ( isPartStrNotTag ) {
\r
3962 Sizzle.filter( part, checkSet, true );
\r
3965 ">": function(checkSet, part){
\r
3966 var isPartStr = typeof part === "string",
\r
3967 elem, i = 0, l = checkSet.length;
\r
3969 if ( isPartStr && !/\W/.test(part) ) {
\r
3970 part = part.toLowerCase();
\r
3972 for ( ; i < l; i++ ) {
\r
3973 elem = checkSet[i];
\r
3975 var parent = elem.parentNode;
\r
3976 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
\r
3980 for ( ; i < l; i++ ) {
\r
3981 elem = checkSet[i];
\r
3983 checkSet[i] = isPartStr ?
\r
3985 elem.parentNode === part;
\r
3989 if ( isPartStr ) {
\r
3990 Sizzle.filter( part, checkSet, true );
\r
3994 "": function(checkSet, part, isXML){
\r
3995 var doneName = done++, checkFn = dirCheck, nodeCheck;
\r
3997 if ( typeof part === "string" && !/\W/.test(part) ) {
\r
3998 part = part.toLowerCase();
\r
4000 checkFn = dirNodeCheck;
\r
4003 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
\r
4005 "~": function(checkSet, part, isXML){
\r
4006 var doneName = done++, checkFn = dirCheck, nodeCheck;
\r
4008 if ( typeof part === "string" && !/\W/.test(part) ) {
\r
4009 part = part.toLowerCase();
\r
4011 checkFn = dirNodeCheck;
\r
4014 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
\r
4018 ID: function(match, context, isXML){
\r
4019 if ( typeof context.getElementById !== "undefined" && !isXML ) {
\r
4020 var m = context.getElementById(match[1]);
\r
4021 return m ? [m] : [];
\r
4024 NAME: function(match, context){
\r
4025 if ( typeof context.getElementsByName !== "undefined" ) {
\r
4026 var ret = [], results = context.getElementsByName(match[1]);
\r
4028 for ( var i = 0, l = results.length; i < l; i++ ) {
\r
4029 if ( results[i].getAttribute("name") === match[1] ) {
\r
4030 ret.push( results[i] );
\r
4034 return ret.length === 0 ? null : ret;
\r
4037 TAG: function(match, context){
\r
4038 return context.getElementsByTagName(match[1]);
\r
4042 CLASS: function(match, curLoop, inplace, result, not, isXML){
\r
4043 match = " " + match[1].replace(/\\/g, "") + " ";
\r
4049 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
\r
4051 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
\r
4053 result.push( elem );
\r
4055 } else if ( inplace ) {
\r
4056 curLoop[i] = false;
\r
4063 ID: function(match){
\r
4064 return match[1].replace(/\\/g, "");
\r
4066 TAG: function(match, curLoop){
\r
4067 return match[1].toLowerCase();
\r
4069 CHILD: function(match){
\r
4070 if ( match[1] === "nth" ) {
\r
4071 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
\r
4072 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
\r
4073 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
\r
4074 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
\r
4076 // calculate the numbers (first)n+(last) including if they are negative
\r
4077 match[2] = (test[1] + (test[2] || 1)) - 0;
\r
4078 match[3] = test[3] - 0;
\r
4081 // TODO: Move to normal caching system
\r
4082 match[0] = done++;
\r
4086 ATTR: function(match, curLoop, inplace, result, not, isXML){
\r
4087 var name = match[1].replace(/\\/g, "");
\r
4089 if ( !isXML && Expr.attrMap[name] ) {
\r
4090 match[1] = Expr.attrMap[name];
\r
4093 if ( match[2] === "~=" ) {
\r
4094 match[4] = " " + match[4] + " ";
\r
4099 PSEUDO: function(match, curLoop, inplace, result, not){
\r
4100 if ( match[1] === "not" ) {
\r
4101 // If we're dealing with a complex expression, or a simple one
\r
4102 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
\r
4103 match[3] = Sizzle(match[3], null, null, curLoop);
\r
4105 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
\r
4107 result.push.apply( result, ret );
\r
4111 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
\r
4117 POS: function(match){
\r
4118 match.unshift( true );
\r
4123 enabled: function(elem){
\r
4124 return elem.disabled === false && elem.type !== "hidden";
\r
4126 disabled: function(elem){
\r
4127 return elem.disabled === true;
\r
4129 checked: function(elem){
\r
4130 return elem.checked === true;
\r
4132 selected: function(elem){
\r
4133 // Accessing this property makes selected-by-default
\r
4134 // options in Safari work properly
\r
4135 elem.parentNode.selectedIndex;
\r
4136 return elem.selected === true;
\r
4138 parent: function(elem){
\r
4139 return !!elem.firstChild;
\r
4141 empty: function(elem){
\r
4142 return !elem.firstChild;
\r
4144 has: function(elem, i, match){
\r
4145 return !!Sizzle( match[3], elem ).length;
\r
4147 header: function(elem){
\r
4148 return (/h\d/i).test( elem.nodeName );
\r
4150 text: function(elem){
\r
4151 return "text" === elem.type;
\r
4153 radio: function(elem){
\r
4154 return "radio" === elem.type;
\r
4156 checkbox: function(elem){
\r
4157 return "checkbox" === elem.type;
\r
4159 file: function(elem){
\r
4160 return "file" === elem.type;
\r
4162 password: function(elem){
\r
4163 return "password" === elem.type;
\r
4165 submit: function(elem){
\r
4166 return "submit" === elem.type;
\r
4168 image: function(elem){
\r
4169 return "image" === elem.type;
\r
4171 reset: function(elem){
\r
4172 return "reset" === elem.type;
\r
4174 button: function(elem){
\r
4175 return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
\r
4177 input: function(elem){
\r
4178 return (/input|select|textarea|button/i).test(elem.nodeName);
\r
4182 first: function(elem, i){
\r
4185 last: function(elem, i, match, array){
\r
4186 return i === array.length - 1;
\r
4188 even: function(elem, i){
\r
4189 return i % 2 === 0;
\r
4191 odd: function(elem, i){
\r
4192 return i % 2 === 1;
\r
4194 lt: function(elem, i, match){
\r
4195 return i < match[3] - 0;
\r
4197 gt: function(elem, i, match){
\r
4198 return i > match[3] - 0;
\r
4200 nth: function(elem, i, match){
\r
4201 return match[3] - 0 === i;
\r
4203 eq: function(elem, i, match){
\r
4204 return match[3] - 0 === i;
\r
4208 PSEUDO: function(elem, match, i, array){
\r
4209 var name = match[1], filter = Expr.filters[ name ];
\r
4212 return filter( elem, i, match, array );
\r
4213 } else if ( name === "contains" ) {
\r
4214 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
\r
4215 } else if ( name === "not" ) {
\r
4216 var not = match[3];
\r
4218 for ( var j = 0, l = not.length; j < l; j++ ) {
\r
4219 if ( not[j] === elem ) {
\r
4226 Sizzle.error( "Syntax error, unrecognized expression: " + name );
\r
4229 CHILD: function(elem, match){
\r
4230 var type = match[1], node = elem;
\r
4234 while ( (node = node.previousSibling) ) {
\r
4235 if ( node.nodeType === 1 ) {
\r
4239 if ( type === "first" ) {
\r
4244 while ( (node = node.nextSibling) ) {
\r
4245 if ( node.nodeType === 1 ) {
\r
4251 var first = match[2], last = match[3];
\r
4253 if ( first === 1 && last === 0 ) {
\r
4257 var doneName = match[0],
\r
4258 parent = elem.parentNode;
\r
4260 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
\r
4262 for ( node = parent.firstChild; node; node = node.nextSibling ) {
\r
4263 if ( node.nodeType === 1 ) {
\r
4264 node.nodeIndex = ++count;
\r
4267 parent.sizcache = doneName;
\r
4270 var diff = elem.nodeIndex - last;
\r
4271 if ( first === 0 ) {
\r
4272 return diff === 0;
\r
4274 return ( diff % first === 0 && diff / first >= 0 );
\r
4278 ID: function(elem, match){
\r
4279 return elem.nodeType === 1 && elem.getAttribute("id") === match;
\r
4281 TAG: function(elem, match){
\r
4282 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
\r
4284 CLASS: function(elem, match){
\r
4285 return (" " + (elem.className || elem.getAttribute("class")) + " ")
\r
4286 .indexOf( match ) > -1;
\r
4288 ATTR: function(elem, match){
\r
4289 var name = match[1],
\r
4290 result = Expr.attrHandle[ name ] ?
\r
4291 Expr.attrHandle[ name ]( elem ) :
\r
4292 elem[ name ] != null ?
\r
4294 elem.getAttribute( name ),
\r
4295 value = result + "",
\r
4299 return result == null ?
\r
4304 value.indexOf(check) >= 0 :
\r
4306 (" " + value + " ").indexOf(check) >= 0 :
\r
4308 value && result !== false :
\r
4312 value.indexOf(check) === 0 :
\r
4314 value.substr(value.length - check.length) === check :
\r
4316 value === check || value.substr(0, check.length + 1) === check + "-" :
\r
4319 POS: function(elem, match, i, array){
\r
4320 var name = match[2], filter = Expr.setFilters[ name ];
\r
4323 return filter( elem, i, match, array );
\r
4329 var origPOS = Expr.match.POS,
\r
4330 fescape = function(all, num){
\r
4331 return "\\" + (num - 0 + 1);
\r
4334 for ( var type in Expr.match ) {
\r
4335 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
\r
4336 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
\r
4339 var makeArray = function(array, results) {
\r
4340 array = Array.prototype.slice.call( array, 0 );
\r
4343 results.push.apply( results, array );
\r
4350 // Perform a simple check to determine if the browser is capable of
\r
4351 // converting a NodeList to an array using builtin methods.
\r
4352 // Also verifies that the returned array holds DOM nodes
\r
4353 // (which is not the case in the Blackberry browser)
\r
4355 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
\r
4357 // Provide a fallback method if it does not work
\r
4359 makeArray = function(array, results) {
\r
4360 var ret = results || [], i = 0;
\r
4362 if ( toString.call(array) === "[object Array]" ) {
\r
4363 Array.prototype.push.apply( ret, array );
\r
4365 if ( typeof array.length === "number" ) {
\r
4366 for ( var l = array.length; i < l; i++ ) {
\r
4367 ret.push( array[i] );
\r
4370 for ( ; array[i]; i++ ) {
\r
4371 ret.push( array[i] );
\r
4382 if ( document.documentElement.compareDocumentPosition ) {
\r
4383 sortOrder = function( a, b ) {
\r
4384 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
\r
4386 hasDuplicate = true;
\r
4388 return a.compareDocumentPosition ? -1 : 1;
\r
4391 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
\r
4392 if ( ret === 0 ) {
\r
4393 hasDuplicate = true;
\r
4397 } else if ( "sourceIndex" in document.documentElement ) {
\r
4398 sortOrder = function( a, b ) {
\r
4399 if ( !a.sourceIndex || !b.sourceIndex ) {
\r
4401 hasDuplicate = true;
\r
4403 return a.sourceIndex ? -1 : 1;
\r
4406 var ret = a.sourceIndex - b.sourceIndex;
\r
4407 if ( ret === 0 ) {
\r
4408 hasDuplicate = true;
\r
4412 } else if ( document.createRange ) {
\r
4413 sortOrder = function( a, b ) {
\r
4414 if ( !a.ownerDocument || !b.ownerDocument ) {
\r
4416 hasDuplicate = true;
\r
4418 return a.ownerDocument ? -1 : 1;
\r
4421 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
\r
4422 aRange.setStart(a, 0);
\r
4423 aRange.setEnd(a, 0);
\r
4424 bRange.setStart(b, 0);
\r
4425 bRange.setEnd(b, 0);
\r
4426 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
\r
4427 if ( ret === 0 ) {
\r
4428 hasDuplicate = true;
\r
4434 // Utility function for retreiving the text value of an array of DOM nodes
\r
4435 Sizzle.getText = function( elems ) {
\r
4436 var ret = "", elem;
\r
4438 for ( var i = 0; elems[i]; i++ ) {
\r
4441 // Get the text from text nodes and CDATA nodes
\r
4442 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
\r
4443 ret += elem.nodeValue;
\r
4445 // Traverse everything else, except comment nodes
\r
4446 } else if ( elem.nodeType !== 8 ) {
\r
4447 ret += Sizzle.getText( elem.childNodes );
\r
4454 // Check to see if the browser returns elements by name when
\r
4455 // querying by getElementById (and provide a workaround)
\r
4457 // We're going to inject a fake input element with a specified name
\r
4458 var form = document.createElement("div"),
\r
4459 id = "script" + (new Date()).getTime();
\r
4460 form.innerHTML = "<a name='" + id + "'/>";
\r
4462 // Inject it into the root element, check its status, and remove it quickly
\r
4463 var root = document.documentElement;
\r
4464 root.insertBefore( form, root.firstChild );
\r
4466 // The workaround has to do additional checks after a getElementById
\r
4467 // Which slows things down for other browsers (hence the branching)
\r
4468 if ( document.getElementById( id ) ) {
\r
4469 Expr.find.ID = function(match, context, isXML){
\r
4470 if ( typeof context.getElementById !== "undefined" && !isXML ) {
\r
4471 var m = context.getElementById(match[1]);
\r
4472 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
\r
4476 Expr.filter.ID = function(elem, match){
\r
4477 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
\r
4478 return elem.nodeType === 1 && node && node.nodeValue === match;
\r
4482 root.removeChild( form );
\r
4483 root = form = null; // release memory in IE
\r
4487 // Check to see if the browser returns only elements
\r
4488 // when doing getElementsByTagName("*")
\r
4490 // Create a fake element
\r
4491 var div = document.createElement("div");
\r
4492 div.appendChild( document.createComment("") );
\r
4494 // Make sure no comments are found
\r
4495 if ( div.getElementsByTagName("*").length > 0 ) {
\r
4496 Expr.find.TAG = function(match, context){
\r
4497 var results = context.getElementsByTagName(match[1]);
\r
4499 // Filter out possible comments
\r
4500 if ( match[1] === "*" ) {
\r
4503 for ( var i = 0; results[i]; i++ ) {
\r
4504 if ( results[i].nodeType === 1 ) {
\r
4505 tmp.push( results[i] );
\r
4516 // Check to see if an attribute returns normalized href attributes
\r
4517 div.innerHTML = "<a href='#'></a>";
\r
4518 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
\r
4519 div.firstChild.getAttribute("href") !== "#" ) {
\r
4520 Expr.attrHandle.href = function(elem){
\r
4521 return elem.getAttribute("href", 2);
\r
4525 div = null; // release memory in IE
\r
4528 if ( document.querySelectorAll ) {
\r
4530 var oldSizzle = Sizzle, div = document.createElement("div");
\r
4531 div.innerHTML = "<p class='TEST'></p>";
\r
4533 // Safari can't handle uppercase or unicode characters when
\r
4534 // in quirks mode.
\r
4535 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
\r
4539 Sizzle = function(query, context, extra, seed){
\r
4540 context = context || document;
\r
4542 // Only use querySelectorAll on non-XML documents
\r
4543 // (ID selectors don't work in non-HTML documents)
\r
4544 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
\r
4546 return makeArray( context.querySelectorAll(query), extra );
\r
4550 return oldSizzle(query, context, extra, seed);
\r
4553 for ( var prop in oldSizzle ) {
\r
4554 Sizzle[ prop ] = oldSizzle[ prop ];
\r
4557 div = null; // release memory in IE
\r
4562 var div = document.createElement("div");
\r
4564 div.innerHTML = "<div class='test e'></div><div class='test'></div>";
\r
4566 // Opera can't find a second classname (in 9.6)
\r
4567 // Also, make sure that getElementsByClassName actually exists
\r
4568 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
\r
4572 // Safari caches class attributes, doesn't catch changes (in 3.2)
\r
4573 div.lastChild.className = "e";
\r
4575 if ( div.getElementsByClassName("e").length === 1 ) {
\r
4579 Expr.order.splice(1, 0, "CLASS");
\r
4580 Expr.find.CLASS = function(match, context, isXML) {
\r
4581 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
\r
4582 return context.getElementsByClassName(match[1]);
\r
4586 div = null; // release memory in IE
\r
4589 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
\r
4590 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
4591 var elem = checkSet[i];
\r
4594 var match = false;
\r
4597 if ( elem.sizcache === doneName ) {
\r
4598 match = checkSet[elem.sizset];
\r
4602 if ( elem.nodeType === 1 && !isXML ){
\r
4603 elem.sizcache = doneName;
\r
4607 if ( elem.nodeName.toLowerCase() === cur ) {
\r
4615 checkSet[i] = match;
\r
4620 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
\r
4621 for ( var i = 0, l = checkSet.length; i < l; i++ ) {
\r
4622 var elem = checkSet[i];
\r
4625 var match = false;
\r
4628 if ( elem.sizcache === doneName ) {
\r
4629 match = checkSet[elem.sizset];
\r
4633 if ( elem.nodeType === 1 ) {
\r
4635 elem.sizcache = doneName;
\r
4638 if ( typeof cur !== "string" ) {
\r
4639 if ( elem === cur ) {
\r
4644 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
\r
4653 checkSet[i] = match;
\r
4658 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
\r
4659 return !!(a.compareDocumentPosition(b) & 16);
\r
4660 } : function(a, b){
\r
4661 return a !== b && (a.contains ? a.contains(b) : true);
\r
4664 Sizzle.isXML = function(elem){
\r
4665 // documentElement is verified for cases where it doesn't yet exist
\r
4666 // (such as loading iframes in IE - #4833)
\r
4667 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
\r
4668 return documentElement ? documentElement.nodeName !== "HTML" : false;
\r
4671 var posProcess = function(selector, context){
\r
4672 var tmpSet = [], later = "", match,
\r
4673 root = context.nodeType ? [context] : context;
\r
4675 // Position selectors must be done after the filter
\r
4676 // And so must :not(positional) so we move all PSEUDOs to the end
\r
4677 while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
\r
4678 later += match[0];
\r
4679 selector = selector.replace( Expr.match.PSEUDO, "" );
\r
4682 selector = Expr.relative[selector] ? selector + "*" : selector;
\r
4684 for ( var i = 0, l = root.length; i < l; i++ ) {
\r
4685 Sizzle( selector, root[i], tmpSet );
\r
4688 return Sizzle.filter( later, tmpSet );
\r
4693 window.tinymce.dom.Sizzle = Sizzle;
\r
4698 (function(tinymce) {
\r
4700 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
\r
4702 tinymce.create('tinymce.dom.EventUtils', {
\r
4703 EventUtils : function() {
\r
4708 add : function(o, n, f, s) {
\r
4709 var cb, t = this, el = t.events, r;
\r
4711 if (n instanceof Array) {
\r
4714 each(n, function(n) {
\r
4715 r.push(t.add(o, n, f, s));
\r
4722 if (o && o.hasOwnProperty && o instanceof Array) {
\r
4725 each(o, function(o) {
\r
4727 r.push(t.add(o, n, f, s));
\r
4738 // Setup event callback
\r
4739 cb = function(e) {
\r
4740 // Is all events disabled
\r
4744 e = e || window.event;
\r
4746 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
\r
4749 e.target = e.srcElement;
\r
4751 // Patch in preventDefault, stopPropagation methods for W3C compatibility
\r
4752 tinymce.extend(e, t._stoppers);
\r
4758 return f.call(s, e);
\r
4761 if (n == 'unload') {
\r
4762 tinymce.unloads.unshift({func : cb});
\r
4766 if (n == 'init') {
\r
4775 // Store away listener reference
\r
4789 remove : function(o, n, f) {
\r
4790 var t = this, a = t.events, s = false, r;
\r
4793 if (o && o.hasOwnProperty && o instanceof Array) {
\r
4796 each(o, function(o) {
\r
4798 r.push(t.remove(o, n, f));
\r
4806 each(a, function(e, i) {
\r
4807 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
\r
4809 t._remove(o, n, e.cfunc);
\r
4818 clear : function(o) {
\r
4819 var t = this, a = t.events, i, e;
\r
4824 for (i = a.length - 1; i >= 0; i--) {
\r
4827 if (e.obj === o) {
\r
4828 t._remove(e.obj, e.name, e.cfunc);
\r
4829 e.obj = e.cfunc = null;
\r
4836 cancel : function(e) {
\r
4842 return this.prevent(e);
\r
4845 stop : function(e) {
\r
4846 if (e.stopPropagation)
\r
4847 e.stopPropagation();
\r
4849 e.cancelBubble = true;
\r
4854 prevent : function(e) {
\r
4855 if (e.preventDefault)
\r
4856 e.preventDefault();
\r
4858 e.returnValue = false;
\r
4863 destroy : function() {
\r
4866 each(t.events, function(e, i) {
\r
4867 t._remove(e.obj, e.name, e.cfunc);
\r
4868 e.obj = e.cfunc = null;
\r
4875 _add : function(o, n, f) {
\r
4876 if (o.attachEvent)
\r
4877 o.attachEvent('on' + n, f);
\r
4878 else if (o.addEventListener)
\r
4879 o.addEventListener(n, f, false);
\r
4884 _remove : function(o, n, f) {
\r
4887 if (o.detachEvent)
\r
4888 o.detachEvent('on' + n, f);
\r
4889 else if (o.removeEventListener)
\r
4890 o.removeEventListener(n, f, false);
\r
4892 o['on' + n] = null;
\r
4894 // Might fail with permission denined on IE so we just ignore that
\r
4899 _pageInit : function(win) {
\r
4902 // Keep it from running more than once
\r
4906 t.domLoaded = true;
\r
4908 each(t.inits, function(c) {
\r
4915 _wait : function(win) {
\r
4916 var t = this, doc = win.document;
\r
4918 // No need since the document is already loaded
\r
4919 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
\r
4925 if (doc.attachEvent) {
\r
4926 doc.attachEvent("onreadystatechange", function() {
\r
4927 if (doc.readyState === "complete") {
\r
4928 doc.detachEvent("onreadystatechange", arguments.callee);
\r
4933 if (doc.documentElement.doScroll && win == win.top) {
\r
4939 // If IE is used, use the trick by Diego Perini
\r
4940 // http://javascript.nwbox.com/IEContentLoaded/
\r
4941 doc.documentElement.doScroll("left");
\r
4943 setTimeout(arguments.callee, 0);
\r
4950 } else if (doc.addEventListener) {
\r
4951 t._add(win, 'DOMContentLoaded', function() {
\r
4956 t._add(win, 'load', function() {
\r
4962 preventDefault : function() {
\r
4963 this.returnValue = false;
\r
4966 stopPropagation : function() {
\r
4967 this.cancelBubble = true;
\r
4972 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
\r
4974 // Dispatch DOM content loaded event for IE and Safari
\r
4975 Event._wait(window);
\r
4977 tinymce.addUnload(function() {
\r
4982 (function(tinymce) {
\r
4983 tinymce.dom.Element = function(id, settings) {
\r
4984 var t = this, dom, el;
\r
4986 t.settings = settings = settings || {};
\r
4988 t.dom = dom = settings.dom || tinymce.DOM;
\r
4990 // Only IE leaks DOM references, this is a lot faster
\r
4991 if (!tinymce.isIE)
\r
4992 el = dom.get(t.id);
\r
4995 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
\r
4996 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
\r
4997 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
\r
4998 'isHidden,setHTML,get').split(/,/)
\r
5000 t[k] = function() {
\r
5003 for (i = 0; i < arguments.length; i++)
\r
5004 a.push(arguments[i]);
\r
5006 a = dom[k].apply(dom, a);
\r
5013 tinymce.extend(t, {
\r
5014 on : function(n, f, s) {
\r
5015 return tinymce.dom.Event.add(t.id, n, f, s);
\r
5018 getXY : function() {
\r
5020 x : parseInt(t.getStyle('left')),
\r
5021 y : parseInt(t.getStyle('top'))
\r
5025 getSize : function() {
\r
5026 var n = dom.get(t.id);
\r
5029 w : parseInt(t.getStyle('width') || n.clientWidth),
\r
5030 h : parseInt(t.getStyle('height') || n.clientHeight)
\r
5034 moveTo : function(x, y) {
\r
5035 t.setStyles({left : x, top : y});
\r
5038 moveBy : function(x, y) {
\r
5039 var p = t.getXY();
\r
5041 t.moveTo(p.x + x, p.y + y);
\r
5044 resizeTo : function(w, h) {
\r
5045 t.setStyles({width : w, height : h});
\r
5048 resizeBy : function(w, h) {
\r
5049 var s = t.getSize();
\r
5051 t.resizeTo(s.w + w, s.h + h);
\r
5054 update : function(k) {
\r
5057 if (tinymce.isIE6 && settings.blocker) {
\r
5061 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
\r
5064 // Remove blocker on remove
\r
5065 if (k == 'remove') {
\r
5066 dom.remove(t.blocker);
\r
5071 t.blocker = dom.uniqueId();
\r
5072 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
\r
5073 dom.setStyle(b, 'opacity', 0);
\r
5075 b = dom.get(t.blocker);
\r
5077 dom.setStyles(b, {
\r
5078 left : t.getStyle('left', 1),
\r
5079 top : t.getStyle('top', 1),
\r
5080 width : t.getStyle('width', 1),
\r
5081 height : t.getStyle('height', 1),
\r
5082 display : t.getStyle('display', 1),
\r
5083 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
\r
5091 (function(tinymce) {
\r
5092 function trimNl(s) {
\r
5093 return s.replace(/[\n\r]+/g, '');
\r
5097 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
\r
5099 tinymce.create('tinymce.dom.Selection', {
\r
5100 Selection : function(dom, win, serializer) {
\r
5105 t.serializer = serializer;
\r
5109 'onBeforeSetContent',
\r
5110 'onBeforeGetContent',
\r
5114 t[e] = new tinymce.util.Dispatcher(t);
\r
5117 // No W3C Range support
\r
5118 if (!t.win.getSelection)
\r
5119 t.tridentSel = new tinymce.dom.TridentSelection(t);
\r
5122 tinymce.addUnload(t.destroy, t);
\r
5125 getContent : function(s) {
\r
5126 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
\r
5131 s.format = s.format || 'html';
\r
5132 t.onBeforeGetContent.dispatch(t, s);
\r
5134 if (s.format == 'text')
\r
5135 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
\r
5137 if (r.cloneContents) {
\r
5138 n = r.cloneContents();
\r
5142 } else if (is(r.item) || is(r.htmlText))
\r
5143 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
\r
5145 e.innerHTML = r.toString();
\r
5147 // Keep whitespace before and after
\r
5148 if (/^\s/.test(e.innerHTML))
\r
5151 if (/\s+$/.test(e.innerHTML))
\r
5154 s.getInner = true;
\r
5156 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
\r
5157 t.onGetContent.dispatch(t, s);
\r
5162 setContent : function(h, s) {
\r
5163 var t = this, r = t.getRng(), c, d = t.win.document;
\r
5165 s = s || {format : 'html'};
\r
5167 h = s.content = t.dom.processHTML(h);
\r
5169 // Dispatch before set content event
\r
5170 t.onBeforeSetContent.dispatch(t, s);
\r
5173 if (r.insertNode) {
\r
5174 // Make caret marker since insertNode places the caret in the beginning of text after insert
\r
5175 h += '<span id="__caret">_</span>';
\r
5177 // Delete and insert new node
\r
5179 if (r.startContainer == d && r.endContainer == d) {
\r
5180 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
\r
5181 d.body.innerHTML = h;
\r
5183 r.deleteContents();
\r
5184 if (d.body.childNodes.length == 0) {
\r
5185 d.body.innerHTML = h;
\r
5187 r.insertNode(r.createContextualFragment(h));
\r
5191 // Move to caret marker
\r
5192 c = t.dom.get('__caret');
\r
5193 // Make sure we wrap it compleatly, Opera fails with a simple select call
\r
5194 r = d.createRange();
\r
5195 r.setStartBefore(c);
\r
5196 r.setEndBefore(c);
\r
5199 // Remove the caret position
\r
5200 t.dom.remove('__caret');
\r
5203 // Delete content and get caret text selection
\r
5204 d.execCommand('Delete', false, null);
\r
5211 // Dispatch set content event
\r
5212 t.onSetContent.dispatch(t, s);
\r
5215 getStart : function() {
\r
5216 var rng = this.getRng(), startElement, parentElement, checkRng, node;
\r
5218 if (rng.duplicate || rng.item) {
\r
5219 // Control selection, return first item
\r
5221 return rng.item(0);
\r
5223 // Get start element
\r
5224 checkRng = rng.duplicate();
\r
5225 checkRng.collapse(1);
\r
5226 startElement = checkRng.parentElement();
\r
5228 // Check if range parent is inside the start element, then return the inner parent element
\r
5229 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
\r
5230 parentElement = node = rng.parentElement();
\r
5231 while (node = node.parentNode) {
\r
5232 if (node == startElement) {
\r
5233 startElement = parentElement;
\r
5238 // If start element is body element try to move to the first child if it exists
\r
5239 if (startElement && startElement.nodeName == 'BODY')
\r
5240 return startElement.firstChild || startElement;
\r
5242 return startElement;
\r
5244 startElement = rng.startContainer;
\r
5246 if (startElement.nodeType == 1 && startElement.hasChildNodes())
\r
5247 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
\r
5249 if (startElement && startElement.nodeType == 3)
\r
5250 return startElement.parentNode;
\r
5252 return startElement;
\r
5256 getEnd : function() {
\r
5257 var t = this, r = t.getRng(), e, eo;
\r
5259 if (r.duplicate || r.item) {
\r
5263 r = r.duplicate();
\r
5265 e = r.parentElement();
\r
5267 if (e && e.nodeName == 'BODY')
\r
5268 return e.lastChild || e;
\r
5272 e = r.endContainer;
\r
5275 if (e.nodeType == 1 && e.hasChildNodes())
\r
5276 e = e.childNodes[eo > 0 ? eo - 1 : eo];
\r
5278 if (e && e.nodeType == 3)
\r
5279 return e.parentNode;
\r
5285 getBookmark : function(type, normalized) {
\r
5286 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
\r
5288 function findIndex(name, element) {
\r
5291 each(dom.select(name), function(node, i) {
\r
5292 if (node == element)
\r
5300 function getLocation() {
\r
5301 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
\r
5303 function getPoint(rng, start) {
\r
5304 var container = rng[start ? 'startContainer' : 'endContainer'],
\r
5305 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
\r
5307 if (container.nodeType == 3) {
\r
5309 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
\r
5310 offset += node.nodeValue.length;
\r
5313 point.push(offset);
\r
5315 childNodes = container.childNodes;
\r
5317 if (offset >= childNodes.length && childNodes.length) {
\r
5319 offset = Math.max(0, childNodes.length - 1);
\r
5322 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
\r
5325 for (; container && container != root; container = container.parentNode)
\r
5326 point.push(t.dom.nodeIndex(container, normalized));
\r
5331 bookmark.start = getPoint(rng, true);
\r
5333 if (!t.isCollapsed())
\r
5334 bookmark.end = getPoint(rng);
\r
5339 return getLocation();
\r
5342 // Handle simple range
\r
5344 return {rng : t.getRng()};
\r
5347 id = dom.uniqueId();
\r
5348 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
\r
5349 styles = 'overflow:hidden;line-height:0px';
\r
5351 // Explorer method
\r
5352 if (rng.duplicate || rng.item) {
\r
5355 rng2 = rng.duplicate();
\r
5357 // Insert start marker
\r
5359 rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
\r
5361 // Insert end marker
\r
5363 rng2.collapse(false);
\r
5364 rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
\r
5367 // Control selection
\r
5368 element = rng.item(0);
\r
5369 name = element.nodeName;
\r
5371 return {name : name, index : findIndex(name, element)};
\r
5374 element = t.getNode();
\r
5375 name = element.nodeName;
\r
5376 if (name == 'IMG')
\r
5377 return {name : name, index : findIndex(name, element)};
\r
5380 rng2 = rng.cloneRange();
\r
5382 // Insert end marker
\r
5384 rng2.collapse(false);
\r
5385 rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
\r
5388 rng.collapse(true);
\r
5389 rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
\r
5392 t.moveToBookmark({id : id, keep : 1});
\r
5397 moveToBookmark : function(bookmark) {
\r
5398 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
\r
5400 // Clear selection cache
\r
5402 t.tridentSel.destroy();
\r
5405 if (bookmark.start) {
\r
5406 rng = dom.createRng();
\r
5407 root = dom.getRoot();
\r
5409 function setEndPoint(start) {
\r
5410 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
\r
5413 // Find container node
\r
5414 for (node = root, i = point.length - 1; i >= 1; i--) {
\r
5415 children = node.childNodes;
\r
5417 if (children.length)
\r
5418 node = children[point[i]];
\r
5421 // Set offset within container node
\r
5423 rng.setStart(node, point[0]);
\r
5425 rng.setEnd(node, point[0]);
\r
5429 setEndPoint(true);
\r
5433 } else if (bookmark.id) {
\r
5434 function restoreEndPoint(suffix) {
\r
5435 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
\r
5438 node = marker.parentNode;
\r
5440 if (suffix == 'start') {
\r
5442 idx = dom.nodeIndex(marker);
\r
5444 node = marker.firstChild;
\r
5448 startContainer = endContainer = node;
\r
5449 startOffset = endOffset = idx;
\r
5452 idx = dom.nodeIndex(marker);
\r
5454 node = marker.firstChild;
\r
5458 endContainer = node;
\r
5463 prev = marker.previousSibling;
\r
5464 next = marker.nextSibling;
\r
5466 // Remove all marker text nodes
\r
5467 each(tinymce.grep(marker.childNodes), function(node) {
\r
5468 if (node.nodeType == 3)
\r
5469 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
\r
5472 // Remove marker but keep children if for example contents where inserted into the marker
\r
5473 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
\r
5474 while (marker = dom.get(bookmark.id + '_' + suffix))
\r
5475 dom.remove(marker, 1);
\r
5477 // If siblings are text nodes then merge them
\r
5478 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
\r
5479 idx = prev.nodeValue.length;
\r
5480 prev.appendData(next.nodeValue);
\r
5483 if (suffix == 'start') {
\r
5484 startContainer = endContainer = prev;
\r
5485 startOffset = endOffset = idx;
\r
5487 endContainer = prev;
\r
5495 function addBogus(node) {
\r
5496 // Adds a bogus BR element for empty block elements
\r
5497 // on non IE browsers just to have a place to put the caret
\r
5498 if (!isIE && dom.isBlock(node) && !node.innerHTML)
\r
5499 node.innerHTML = '<br _mce_bogus="1" />';
\r
5504 // Restore start/end points
\r
5505 restoreEndPoint('start');
\r
5506 restoreEndPoint('end');
\r
5508 rng = dom.createRng();
\r
5509 rng.setStart(addBogus(startContainer), startOffset);
\r
5510 rng.setEnd(addBogus(endContainer), endOffset);
\r
5512 } else if (bookmark.name) {
\r
5513 t.select(dom.select(bookmark.name)[bookmark.index]);
\r
5514 } else if (bookmark.rng)
\r
5515 t.setRng(bookmark.rng);
\r
5519 select : function(node, content) {
\r
5520 var t = this, dom = t.dom, rng = dom.createRng(), idx;
\r
5522 idx = dom.nodeIndex(node);
\r
5523 rng.setStart(node.parentNode, idx);
\r
5524 rng.setEnd(node.parentNode, idx + 1);
\r
5526 // Find first/last text node or BR element
\r
5528 function setPoint(node, start) {
\r
5529 var walker = new tinymce.dom.TreeWalker(node, node);
\r
5533 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
\r
5535 rng.setStart(node, 0);
\r
5537 rng.setEnd(node, node.nodeValue.length);
\r
5543 if (node.nodeName == 'BR') {
\r
5545 rng.setStartBefore(node);
\r
5547 rng.setEndBefore(node);
\r
5551 } while (node = (start ? walker.next() : walker.prev()));
\r
5554 setPoint(node, 1);
\r
5563 isCollapsed : function() {
\r
5564 var t = this, r = t.getRng(), s = t.getSel();
\r
5569 if (r.compareEndPoints)
\r
5570 return r.compareEndPoints('StartToEnd', r) === 0;
\r
5572 return !s || r.collapsed;
\r
5575 collapse : function(b) {
\r
5576 var t = this, r = t.getRng(), n;
\r
5578 // Control range on IE
\r
5581 r = this.win.document.body.createTextRange();
\r
5582 r.moveToElementText(n);
\r
5589 getSel : function() {
\r
5590 var t = this, w = this.win;
\r
5592 return w.getSelection ? w.getSelection() : w.document.selection;
\r
5595 getRng : function(w3c) {
\r
5596 var t = this, s, r;
\r
5598 // Found tridentSel object then we need to use that one
\r
5599 if (w3c && t.tridentSel)
\r
5600 return t.tridentSel.getRangeAt(0);
\r
5603 if (s = t.getSel())
\r
5604 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
\r
5606 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
\r
5609 // No range found then create an empty one
\r
5610 // This can occur when the editor is placed in a hidden container element on Gecko
\r
5611 // Or on IE when there was an exception
\r
5613 r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange();
\r
5615 if (t.selectedRange && t.explicitRange) {
\r
5616 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
\r
5617 // Safari, Opera and Chrome only ever select text which causes the range to change.
\r
5618 // This lets us use the originally set range if the selection hasn't been changed by the user.
\r
5619 r = t.explicitRange;
\r
5621 t.selectedRange = null;
\r
5622 t.explicitRange = null;
\r
5628 setRng : function(r) {
\r
5631 if (!t.tridentSel) {
\r
5635 t.explicitRange = r;
\r
5636 s.removeAllRanges();
\r
5638 t.selectedRange = s.getRangeAt(0);
\r
5642 if (r.cloneRange) {
\r
5643 t.tridentSel.addRange(r);
\r
5647 // Is IE specific range
\r
5651 // Needed for some odd IE bug #1843306
\r
5656 setNode : function(n) {
\r
5659 t.setContent(t.dom.getOuterHTML(n));
\r
5664 getNode : function() {
\r
5665 var t = this, rng = t.getRng(), sel = t.getSel(), elm;
\r
5667 if (rng.setStart) {
\r
5668 // Range maybe lost after the editor is made visible again
\r
5670 return t.dom.getRoot();
\r
5672 elm = rng.commonAncestorContainer;
\r
5674 // Handle selection a image or other control like element such as anchors
\r
5675 if (!rng.collapsed) {
\r
5676 if (rng.startContainer == rng.endContainer) {
\r
5677 if (rng.startOffset - rng.endOffset < 2) {
\r
5678 if (rng.startContainer.hasChildNodes())
\r
5679 elm = rng.startContainer.childNodes[rng.startOffset];
\r
5683 // If the anchor node is a element instead of a text node then return this element
\r
5684 if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
\r
5685 return sel.anchorNode.childNodes[sel.anchorOffset];
\r
5688 if (elm && elm.nodeType == 3)
\r
5689 return elm.parentNode;
\r
5694 return rng.item ? rng.item(0) : rng.parentElement();
\r
5697 getSelectedBlocks : function(st, en) {
\r
5698 var t = this, dom = t.dom, sb, eb, n, bl = [];
\r
5700 sb = dom.getParent(st || t.getStart(), dom.isBlock);
\r
5701 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
\r
5706 if (sb && eb && sb != eb) {
\r
5709 while ((n = n.nextSibling) && n != eb) {
\r
5710 if (dom.isBlock(n))
\r
5715 if (eb && sb != eb)
\r
5721 destroy : function(s) {
\r
5727 t.tridentSel.destroy();
\r
5729 // Manual destroy then remove unload handler
\r
5731 tinymce.removeUnload(t.destroy);
\r
5736 (function(tinymce) {
\r
5737 tinymce.create('tinymce.dom.XMLWriter', {
\r
5740 XMLWriter : function(s) {
\r
5741 // Get XML document
\r
5742 function getXML() {
\r
5743 var i = document.implementation;
\r
5745 if (!i || !i.createDocument) {
\r
5747 try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
\r
5748 try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
\r
5750 return i.createDocument('', '', null);
\r
5753 this.doc = getXML();
\r
5755 // Since Opera and WebKit doesn't escape > into > we need to do it our self to normalize the output for all browsers
\r
5756 this.valid = tinymce.isOpera || tinymce.isWebKit;
\r
5761 reset : function() {
\r
5762 var t = this, d = t.doc;
\r
5765 d.removeChild(d.firstChild);
\r
5767 t.node = d.appendChild(d.createElement("html"));
\r
5770 writeStartElement : function(n) {
\r
5773 t.node = t.node.appendChild(t.doc.createElement(n));
\r
5776 writeAttribute : function(n, v) {
\r
5778 v = v.replace(/>/g, '%MCGT%');
\r
5780 this.node.setAttribute(n, v);
\r
5783 writeEndElement : function() {
\r
5784 this.node = this.node.parentNode;
\r
5787 writeFullEndElement : function() {
\r
5788 var t = this, n = t.node;
\r
5790 n.appendChild(t.doc.createTextNode(""));
\r
5791 t.node = n.parentNode;
\r
5794 writeText : function(v) {
\r
5796 v = v.replace(/>/g, '%MCGT%');
\r
5798 this.node.appendChild(this.doc.createTextNode(v));
\r
5801 writeCDATA : function(v) {
\r
5802 this.node.appendChild(this.doc.createCDATASection(v));
\r
5805 writeComment : function(v) {
\r
5806 // Fix for bug #2035694
\r
5808 v = v.replace(/^\-|\-$/g, ' ');
\r
5810 this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
\r
5813 getContent : function() {
\r
5816 h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
\r
5817 h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
\r
5818 h = h.replace(/ ?\/>/g, ' />');
\r
5821 h = h.replace(/\%MCGT%/g, '>');
\r
5828 (function(tinymce) {
\r
5829 tinymce.create('tinymce.dom.StringWriter', {
\r
5836 StringWriter : function(s) {
\r
5837 this.settings = tinymce.extend({
\r
5838 indent_char : ' ',
\r
5845 reset : function() {
\r
5852 writeStartElement : function(n) {
\r
5853 this._writeAttributesEnd();
\r
5854 this.writeRaw('<' + n);
\r
5855 this.tags.push(n);
\r
5856 this.inAttr = true;
\r
5858 this.elementCount = this.count;
\r
5861 writeAttribute : function(n, v) {
\r
5864 t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
\r
5867 writeEndElement : function() {
\r
5870 if (this.tags.length > 0) {
\r
5871 n = this.tags.pop();
\r
5873 if (this._writeAttributesEnd(1))
\r
5874 this.writeRaw('</' + n + '>');
\r
5876 if (this.settings.indentation > 0)
\r
5877 this.writeRaw('\n');
\r
5881 writeFullEndElement : function() {
\r
5882 if (this.tags.length > 0) {
\r
5883 this._writeAttributesEnd();
\r
5884 this.writeRaw('</' + this.tags.pop() + '>');
\r
5886 if (this.settings.indentation > 0)
\r
5887 this.writeRaw('\n');
\r
5891 writeText : function(v) {
\r
5892 this._writeAttributesEnd();
\r
5893 this.writeRaw(this.encode(v));
\r
5897 writeCDATA : function(v) {
\r
5898 this._writeAttributesEnd();
\r
5899 this.writeRaw('<![CDATA[' + v + ']]>');
\r
5903 writeComment : function(v) {
\r
5904 this._writeAttributesEnd();
\r
5905 this.writeRaw('<!-- ' + v + '-->');
\r
5909 writeRaw : function(v) {
\r
5913 encode : function(s) {
\r
5914 return s.replace(/[<>&"]/g, function(v) {
\r
5933 getContent : function() {
\r
5937 _writeAttributesEnd : function(s) {
\r
5941 this.inAttr = false;
\r
5943 if (s && this.elementCount == this.count) {
\r
5944 this.writeRaw(' />');
\r
5948 this.writeRaw('>');
\r
5955 (function(tinymce) {
\r
5957 var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
\r
5959 function wildcardToRE(s) {
\r
5960 return s.replace(/([?+*])/g, '.$1');
\r
5963 tinymce.create('tinymce.dom.Serializer', {
\r
5964 Serializer : function(s) {
\r
5968 t.onPreProcess = new Dispatcher(t);
\r
5969 t.onPostProcess = new Dispatcher(t);
\r
5972 t.writer = new tinymce.dom.XMLWriter();
\r
5974 // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
\r
5975 t.writer = new tinymce.dom.StringWriter();
\r
5978 // Default settings
\r
5979 t.settings = s = extend({
\r
5980 dom : tinymce.DOM,
\r
5984 invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
\r
5985 closed : /^(br|hr|input|meta|img|link|param|area)$/,
\r
5986 entity_encoding : 'named',
\r
5987 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
5988 valid_elements : '*[*]',
\r
5989 extended_valid_elements : 0,
\r
5990 invalid_elements : 0,
\r
5991 fix_table_elements : 1,
\r
5992 fix_list_elements : true,
\r
5993 fix_content_duplication : true,
\r
5994 convert_fonts_to_spans : false,
\r
5995 font_size_classes : 0,
\r
5996 apply_source_formatting : 0,
\r
5997 indent_mode : 'simple',
\r
5998 indent_char : '\t',
\r
5999 indent_levels : 1,
\r
6000 remove_linebreaks : 1,
\r
6001 remove_redundant_brs : 1,
\r
6002 element_format : 'xhtml'
\r
6006 t.schema = s.schema;
\r
6008 // Use raw entities if no entities are defined
\r
6009 if (s.entity_encoding == 'named' && !s.entities)
\r
6010 s.entity_encoding = 'raw';
\r
6012 if (s.remove_redundant_brs) {
\r
6013 t.onPostProcess.add(function(se, o) {
\r
6014 // Remove single BR at end of block elements since they get rendered
\r
6015 o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
\r
6016 // Check if it's a single element
\r
6017 if (/^<br \/>\s*<\//.test(a))
\r
6018 return '</' + c + '>';
\r
6025 // Remove XHTML element endings i.e. produce crap :) XHTML is better
\r
6026 if (s.element_format == 'html') {
\r
6027 t.onPostProcess.add(function(se, o) {
\r
6028 o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
\r
6032 if (s.fix_list_elements) {
\r
6033 t.onPreProcess.add(function(se, o) {
\r
6034 var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
\r
6036 function prevNode(e, n) {
\r
6037 var a = n.split(','), i;
\r
6039 while ((e = e.previousSibling) != null) {
\r
6040 for (i=0; i<a.length; i++) {
\r
6041 if (e.nodeName == a[i])
\r
6049 for (x=0; x<a.length; x++) {
\r
6050 nl = t.dom.select(a[x], o.node);
\r
6052 for (i=0; i<nl.length; i++) {
\r
6056 if (r.test(p.nodeName)) {
\r
6057 np = prevNode(n, 'LI');
\r
6060 np = t.dom.create('li');
\r
6061 np.innerHTML = ' ';
\r
6062 np.appendChild(n);
\r
6063 p.insertBefore(np, p.firstChild);
\r
6065 np.appendChild(n);
\r
6072 if (s.fix_table_elements) {
\r
6073 t.onPreProcess.add(function(se, o) {
\r
6074 // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
\r
6075 // so Opera users with an older version will have to live with less compaible output not much we can do here
\r
6076 if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
\r
6077 each(t.dom.select('p table', o.node).reverse(), function(n) {
\r
6078 var parent = t.dom.getParent(n.parentNode, 'table,p');
\r
6080 if (parent.nodeName != 'TABLE') {
\r
6082 t.dom.split(parent, n);
\r
6084 // IE can sometimes fire an unknown runtime error so we just ignore it
\r
6093 setEntities : function(s) {
\r
6094 var t = this, a, i, l = {}, v;
\r
6096 // No need to setup more than once
\r
6097 if (t.entityLookup)
\r
6100 // Build regex and lookup array
\r
6102 for (i = 0; i < a.length; i += 2) {
\r
6105 // Don't add default & " etc.
\r
6106 if (v == 34 || v == 38 || v == 60 || v == 62)
\r
6109 l[String.fromCharCode(a[i])] = a[i + 1];
\r
6111 v = parseInt(a[i]).toString(16);
\r
6114 t.entityLookup = l;
\r
6117 setRules : function(s) {
\r
6123 t.validElements = {};
\r
6125 return t.addRules(s);
\r
6128 addRules : function(s) {
\r
6136 each(s.split(','), function(s) {
\r
6137 var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
\r
6139 // Extend with default rules
\r
6141 at = tinymce.extend([], dr.attribs);
\r
6143 // Parse attributes
\r
6144 if (p.length > 1) {
\r
6145 each(p[1].split('|'), function(s) {
\r
6150 // Parse attribute rule
\r
6151 s = s.replace(/::/g, '~');
\r
6152 s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
\r
6153 s[2] = s[2].replace(/~/g, ':');
\r
6155 // Add required attributes
\r
6156 if (s[1] == '!') {
\r
6161 // Remove inherited attributes
\r
6162 if (s[1] == '-') {
\r
6163 for (i = 0; i <at.length; i++) {
\r
6164 if (at[i].name == s[2]) {
\r
6172 // Add default attrib values
\r
6174 ar.defaultVal = s[4] || '';
\r
6177 // Add forced attrib values
\r
6179 ar.forcedVal = s[4];
\r
6182 // Add validation values
\r
6184 ar.validVals = s[4].split('?');
\r
6188 if (/[*.?]/.test(s[2])) {
\r
6190 ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
\r
6201 // Handle element names
\r
6202 each(tn, function(s, i) {
\r
6203 var pr = s.charAt(0), x = 1, ru = {};
\r
6205 // Extend with default rule data
\r
6208 ru.noEmpty = dr.noEmpty;
\r
6211 ru.fullEnd = dr.fullEnd;
\r
6214 ru.padd = dr.padd;
\r
6217 // Handle prefixes
\r
6220 ru.noEmpty = true;
\r
6224 ru.fullEnd = true;
\r
6235 tn[i] = s = s.substring(x);
\r
6236 t.validElements[s] = 1;
\r
6238 // Add element name or element regex
\r
6239 if (/[*.?]/.test(tn[0])) {
\r
6240 ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
\r
6241 t.wildRules = t.wildRules || {};
\r
6242 t.wildRules.push(ru);
\r
6246 // Store away default rule
\r
6256 ru.requiredAttribs = ra;
\r
6259 // Build valid attributes regexp
\r
6261 each(va, function(v) {
\r
6265 s += '(' + wildcardToRE(v) + ')';
\r
6267 ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
\r
6268 ru.wildAttribs = wat;
\r
6273 // Build valid elements regexp
\r
6275 each(t.validElements, function(v, k) {
\r
6282 t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
\r
6284 //console.debug(t.validElementsRE.toString());
\r
6285 //console.dir(t.rules);
\r
6286 //console.dir(t.wildRules);
\r
6289 findRule : function(n) {
\r
6290 var t = this, rl = t.rules, i, r;
\r
6301 for (i = 0; i < rl.length; i++) {
\r
6302 if (rl[i].nameRE.test(n))
\r
6309 findAttribRule : function(ru, n) {
\r
6310 var i, wa = ru.wildAttribs;
\r
6312 for (i = 0; i < wa.length; i++) {
\r
6313 if (wa[i].nameRE.test(n))
\r
6320 serialize : function(n, o) {
\r
6321 var h, t = this, doc, oldDoc, impl, selected;
\r
6325 o.format = o.format || 'html';
\r
6328 // IE looses the selected attribute on option elements so we need to store it
\r
6329 // See: http://support.microsoft.com/kb/829907
\r
6332 each(n.getElementsByTagName('option'), function(n) {
\r
6333 var v = t.dom.getAttrib(n, 'selected');
\r
6335 selected.push(v ? v : null);
\r
6339 n = n.cloneNode(true);
\r
6341 // IE looses the selected attribute on option elements so we need to restore it
\r
6343 each(n.getElementsByTagName('option'), function(n, i) {
\r
6344 t.dom.setAttrib(n, 'selected', selected[i]);
\r
6348 // Nodes needs to be attached to something in WebKit/Opera
\r
6349 // Older builds of Opera crashes if you attach the node to an document created dynamically
\r
6350 // and since we can't feature detect a crash we need to sniff the acutal build number
\r
6351 // This fix will make DOM ranges and make Sizzle happy!
\r
6352 impl = n.ownerDocument.implementation;
\r
6353 if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
\r
6354 // Create an empty HTML document
\r
6355 doc = impl.createHTMLDocument("");
\r
6357 // Add the element or it's children if it's a body element to the new document
\r
6358 each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
\r
6359 doc.body.appendChild(doc.importNode(node, true));
\r
6362 // Grab first child or body element for serialization
\r
6363 if (n.nodeName != 'BODY')
\r
6364 n = doc.body.firstChild;
\r
6368 // set the new document in DOMUtils so createElement etc works
\r
6369 oldDoc = t.dom.doc;
\r
6373 t.key = '' + (parseInt(t.key) + 1);
\r
6376 if (!o.no_events) {
\r
6378 t.onPreProcess.dispatch(t, o);
\r
6381 // Serialize HTML DOM into a string
\r
6384 t._serializeNode(n, o.getInner);
\r
6387 o.content = t.writer.getContent();
\r
6389 // Restore the old document if it was changed
\r
6391 t.dom.doc = oldDoc;
\r
6394 t.onPostProcess.dispatch(t, o);
\r
6396 t._postProcess(o);
\r
6399 return tinymce.trim(o.content);
\r
6402 // Internal functions
\r
6404 _postProcess : function(o) {
\r
6405 var t = this, s = t.settings, h = o.content, sc = [], p;
\r
6407 if (o.format == 'html') {
\r
6408 // Protect some elements
\r
6412 {pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
\r
6413 {pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
\r
6414 {pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
\r
6415 {pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
\r
6416 {pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
\r
6423 if (s.entity_encoding !== 'raw')
\r
6426 // Use BR instead of padded P elements inside editor and use <p> </p> outside editor
\r
6428 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
\r
6430 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
\r
6432 // Since Gecko and Safari keeps whitespace in the DOM we need to
\r
6433 // remove it inorder to match other browsers. But I think Gecko and Safari is right.
\r
6434 // This process is only done when getting contents out from the editor.
\r
6436 // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
\r
6437 h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1> </p>' : '<p$1> </p>');
\r
6439 if (s.remove_linebreaks) {
\r
6440 h = h.replace(/\r?\n|\r/g, ' ');
\r
6441 h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
\r
6442 h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
\r
6443 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
6444 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
6445 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
6448 // Simple indentation
\r
6449 if (s.apply_source_formatting && s.indent_mode == 'simple') {
\r
6450 // Add line breaks before and after block elements
\r
6451 h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
\r
6452 h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
\r
6453 h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
\r
6454 h = h.replace(/\n\n/g, '\n');
\r
6458 h = t._unprotect(h, p);
\r
6460 // Restore CDATA sections
\r
6461 h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
\r
6463 // Restore the \u00a0 character if raw mode is enabled
\r
6464 if (s.entity_encoding == 'raw')
\r
6465 h = h.replace(/<p> <\/p>|<p([^>]+)> <\/p>/g, '<p$1>\u00a0</p>');
\r
6467 // Restore noscript elements
\r
6468 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
6469 return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
\r
6476 _serializeNode : function(n, inner) {
\r
6477 var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type;
\r
6479 if (!s.node_filter || s.node_filter(n)) {
\r
6480 switch (n.nodeType) {
\r
6481 case 1: // Element
\r
6482 if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
\r
6485 iv = keep = false;
\r
6486 hc = n.hasChildNodes();
\r
6487 nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
\r
6489 // Get internal type
\r
6490 type = n.getAttribute('_mce_type');
\r
6492 if (!t._info.cleanup) {
\r
6499 // Add correct prefix on IE
\r
6501 if (n.scopeName !== 'HTML' && n.scopeName !== 'html')
\r
6502 nn = n.scopeName + ':' + nn;
\r
6505 // Remove mce prefix on IE needed for the abbr element
\r
6506 if (nn.indexOf('mce:') === 0)
\r
6507 nn = nn.substring(4);
\r
6511 if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
\r
6518 // Fix IE content duplication (DOM can have multiple copies of the same node)
\r
6519 if (s.fix_content_duplication) {
\r
6520 if (n._mce_serialized == t.key)
\r
6523 n._mce_serialized = t.key;
\r
6526 // IE sometimes adds a / infront of the node name
\r
6527 if (nn.charAt(0) == '/')
\r
6528 nn = nn.substring(1);
\r
6529 } else if (isGecko) {
\r
6530 // Ignore br elements
\r
6531 if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
\r
6535 // Check if valid child
\r
6536 if (s.validate_children) {
\r
6537 if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
\r
6542 t.elementName = nn;
\r
6545 ru = t.findRule(nn);
\r
6547 // No valid rule for this element could be found then skip it
\r
6553 nn = ru.name || nn;
\r
6554 closed = s.closed.test(nn);
\r
6556 // Skip empty nodes or empty node name in IE
\r
6557 if ((!hc && ru.noEmpty) || (isIE && !nn)) {
\r
6563 if (ru.requiredAttribs) {
\r
6564 a = ru.requiredAttribs;
\r
6566 for (i = a.length - 1; i >= 0; i--) {
\r
6567 if (this.dom.getAttrib(n, a[i]) !== '')
\r
6571 // None of the required was there
\r
6578 w.writeStartElement(nn);
\r
6580 // Add ordered attributes
\r
6582 for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
\r
6584 v = t._getAttrib(n, a);
\r
6587 w.writeAttribute(a.name, v);
\r
6591 // Add wild attributes
\r
6592 if (ru.validAttribsRE) {
\r
6593 at = t.dom.getAttribs(n);
\r
6594 for (i=at.length-1; i>-1; i--) {
\r
6597 if (no.specified) {
\r
6598 a = no.nodeName.toLowerCase();
\r
6600 if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
\r
6603 ar = t.findAttribRule(ru, a);
\r
6604 v = t._getAttrib(n, ar, a);
\r
6607 w.writeAttribute(a, v);
\r
6612 // Keep type attribute
\r
6614 w.writeAttribute('_mce_type', type);
\r
6616 // Write text from script
\r
6617 if (nn === 'script' && tinymce.trim(n.innerHTML)) {
\r
6618 w.writeText('// '); // Padd it with a comment so it will parse on older browsers
\r
6619 w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
\r
6624 // Padd empty nodes with a
\r
6626 // If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
\r
6627 if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
\r
6628 if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
\r
6629 w.writeText('\u00a0');
\r
6631 w.writeText('\u00a0'); // No children then padd it
\r
6637 // Check if valid child
\r
6638 if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
\r
6641 return w.writeText(n.nodeValue);
\r
6644 return w.writeCDATA(n.nodeValue);
\r
6646 case 8: // Comment
\r
6647 return w.writeComment(n.nodeValue);
\r
6649 } else if (n.nodeType == 1)
\r
6650 hc = n.hasChildNodes();
\r
6652 if (hc && !closed) {
\r
6653 cn = n.firstChild;
\r
6656 t._serializeNode(cn);
\r
6657 t.elementName = nn;
\r
6658 cn = cn.nextSibling;
\r
6662 // Write element end
\r
6665 w.writeFullEndElement();
\r
6667 w.writeEndElement();
\r
6671 _protect : function(o) {
\r
6674 o.items = o.items || [];
\r
6677 return s.replace(/[\r\n\\]/g, function(c) {
\r
6680 else if (c === '\\')
\r
6688 return s.replace(/\\[\\rn]/g, function(c) {
\r
6691 else if (c === '\\\\')
\r
6698 each(o.patterns, function(p) {
\r
6699 o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
\r
6706 return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
\r
6713 _unprotect : function(h, o) {
\r
6714 h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
\r
6715 return o.items[parseInt(b)];
\r
6723 _encode : function(h) {
\r
6724 var t = this, s = t.settings, l;
\r
6727 if (s.entity_encoding !== 'raw') {
\r
6728 if (s.entity_encoding.indexOf('named') != -1) {
\r
6729 t.setEntities(s.entities);
\r
6730 l = t.entityLookup;
\r
6732 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
6736 a = '&' + v + ';';
\r
6742 if (s.entity_encoding.indexOf('numeric') != -1) {
\r
6743 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
6744 return '&#' + a.charCodeAt(0) + ';';
\r
6752 _setup : function() {
\r
6753 var t = this, s = this.settings;
\r
6760 t.setRules(s.valid_elements);
\r
6761 t.addRules(s.extended_valid_elements);
\r
6763 if (s.invalid_elements)
\r
6764 t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
\r
6766 if (s.attrib_value_filter)
\r
6767 t.attribValueFilter = s.attribValueFilter;
\r
6770 _getAttrib : function(n, a, na) {
\r
6773 na = na || a.name;
\r
6775 if (a.forcedVal && (v = a.forcedVal)) {
\r
6776 if (v === '{$uid}')
\r
6777 return this.dom.uniqueId();
\r
6782 v = this.dom.getAttrib(n, na);
\r
6787 // Whats the point? Remove usless attribute value
\r
6794 if (this.attribValueFilter)
\r
6795 v = this.attribValueFilter(na, v, n);
\r
6797 if (a.validVals) {
\r
6798 for (i = a.validVals.length - 1; i >= 0; i--) {
\r
6799 if (v == a.validVals[i])
\r
6807 if (v === '' && typeof(a.defaultVal) != 'undefined') {
\r
6810 if (v === '{$uid}')
\r
6811 return this.dom.uniqueId();
\r
6815 // Remove internal mceItemXX classes when content is extracted from editor
\r
6816 if (na == 'class' && this.processObj.get)
\r
6817 v = v.replace(/\s?mceItem\w+\s?/g, '');
\r
6829 (function(tinymce) {
\r
6830 tinymce.dom.ScriptLoader = function(settings) {
\r
6836 scriptLoadedCallbacks = {},
\r
6837 queueLoadedCallbacks = [],
\r
6841 function loadScript(url, callback) {
\r
6842 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
\r
6844 // Execute callback when script is loaded
\r
6849 elm.onreadystatechange = elm.onload = elm = null;
\r
6854 id = dom.uniqueId();
\r
6856 if (tinymce.isIE6) {
\r
6857 uri = new tinymce.util.URI(url);
\r
6860 // If script is from same domain and we
\r
6861 // use IE 6 then use XHR since it's more reliable
\r
6862 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
\r
6863 tinymce.util.XHR.send({
\r
6864 url : tinymce._addVer(uri.getURI()),
\r
6865 success : function(content) {
\r
6866 // Create new temp script element
\r
6867 var script = dom.create('script', {
\r
6868 type : 'text/javascript'
\r
6871 // Evaluate script in global scope
\r
6872 script.text = content;
\r
6873 document.getElementsByTagName('head')[0].appendChild(script);
\r
6874 dom.remove(script);
\r
6884 // Create new script element
\r
6885 elm = dom.create('script', {
\r
6887 type : 'text/javascript',
\r
6888 src : tinymce._addVer(url)
\r
6891 // Add onload and readystate listeners
\r
6892 elm.onload = done;
\r
6893 elm.onreadystatechange = function() {
\r
6894 var state = elm.readyState;
\r
6896 // Loaded state is passed on IE 6 however there
\r
6897 // are known issues with this method but we can't use
\r
6898 // XHR in a cross domain loading
\r
6899 if (state == 'complete' || state == 'loaded')
\r
6903 // Most browsers support this feature so we report errors
\r
6904 // for those at least to help users track their missing plugins etc
\r
6905 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
\r
6906 /*elm.onerror = function() {
\r
6907 alert('Failed to load: ' + url);
\r
6910 // Add script to document
\r
6911 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
\r
6914 this.isDone = function(url) {
\r
6915 return states[url] == LOADED;
\r
6918 this.markDone = function(url) {
\r
6919 states[url] = LOADED;
\r
6922 this.add = this.load = function(url, callback, scope) {
\r
6923 var item, state = states[url];
\r
6925 // Add url to load queue
\r
6926 if (state == undefined) {
\r
6928 states[url] = QUEUED;
\r
6932 // Store away callback for later execution
\r
6933 if (!scriptLoadedCallbacks[url])
\r
6934 scriptLoadedCallbacks[url] = [];
\r
6936 scriptLoadedCallbacks[url].push({
\r
6938 scope : scope || this
\r
6943 this.loadQueue = function(callback, scope) {
\r
6944 this.loadScripts(queue, callback, scope);
\r
6947 this.loadScripts = function(scripts, callback, scope) {
\r
6950 function execScriptLoadedCallbacks(url) {
\r
6951 // Execute URL callback functions
\r
6952 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
\r
6953 callback.func.call(callback.scope);
\r
6956 scriptLoadedCallbacks[url] = undefined;
\r
6959 queueLoadedCallbacks.push({
\r
6961 scope : scope || this
\r
6964 loadScripts = function() {
\r
6965 var loadingScripts = tinymce.grep(scripts);
\r
6967 // Current scripts has been handled
\r
6968 scripts.length = 0;
\r
6970 // Load scripts that needs to be loaded
\r
6971 tinymce.each(loadingScripts, function(url) {
\r
6972 // Script is already loaded then execute script callbacks directly
\r
6973 if (states[url] == LOADED) {
\r
6974 execScriptLoadedCallbacks(url);
\r
6978 // Is script not loading then start loading it
\r
6979 if (states[url] != LOADING) {
\r
6980 states[url] = LOADING;
\r
6983 loadScript(url, function() {
\r
6984 states[url] = LOADED;
\r
6987 execScriptLoadedCallbacks(url);
\r
6989 // Load more scripts if they where added by the recently loaded script
\r
6995 // No scripts are currently loading then execute all pending queue loaded callbacks
\r
6997 tinymce.each(queueLoadedCallbacks, function(callback) {
\r
6998 callback.func.call(callback.scope);
\r
7001 queueLoadedCallbacks.length = 0;
\r
7009 // Global script loader
\r
7010 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
\r
7013 tinymce.dom.TreeWalker = function(start_node, root_node) {
\r
7014 var node = start_node;
\r
7016 function findSibling(node, start_name, sibling_name, shallow) {
\r
7017 var sibling, parent;
\r
7020 // Walk into nodes if it has a start
\r
7021 if (!shallow && node[start_name])
\r
7022 return node[start_name];
\r
7024 // Return the sibling if it has one
\r
7025 if (node != root_node) {
\r
7026 sibling = node[sibling_name];
\r
7030 // Walk up the parents to look for siblings
\r
7031 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
\r
7032 sibling = parent[sibling_name];
\r
7040 this.current = function() {
\r
7044 this.next = function(shallow) {
\r
7045 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
\r
7048 this.prev = function(shallow) {
\r
7049 return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
\r
7054 var transitional = {};
\r
7056 function unpack(lookup, data) {
\r
7059 function replace(value) {
\r
7060 return value.replace(/[A-Z]+/g, function(key) {
\r
7061 return replace(lookup[key]);
\r
7066 for (key in lookup) {
\r
7067 if (lookup.hasOwnProperty(key))
\r
7068 lookup[key] = replace(lookup[key]);
\r
7071 // Unpack and parse data into object map
\r
7072 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
\r
7075 children = children.split(/\|/);
\r
7077 for (i = children.length - 1; i >= 0; i--)
\r
7078 map[children[i]] = 1;
\r
7080 transitional[name] = map;
\r
7084 // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
\r
7085 // we will later include the attributes here and use it as a default for valid elements but it
\r
7086 // requires us to rewrite the serializer engine
\r
7088 Z : '#|H|K|N|O|P',
\r
7089 Y : '#|X|form|R|Q',
\r
7090 X : 'p|T|div|U|W|isindex|fieldset|table',
\r
7091 W : 'pre|hr|blockquote|address|center|noframes',
\r
7092 U : 'ul|ol|dl|menu|dir',
\r
7093 ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
\r
7094 T : 'h1|h2|h3|h4|h5|h6',
\r
7097 ZA : '#|a|G|J|M|O|P',
\r
7098 R : '#|a|H|K|N|O',
\r
7100 P : 'ins|del|script',
\r
7101 O : 'input|select|textarea|label|button',
\r
7103 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
\r
7106 J : 'tt|i|b|u|s|strike',
\r
7107 I : 'big|small|font|basefont',
\r
7109 G : 'br|span|bdo',
\r
7110 F : 'object|applet|img|map|iframe'
\r
7113 'object[#|param|X|form|a|H|K|N|O|Q]' +
\r
7120 'applet[#|param|X|form|a|H|K|N|O|Q]' +
\r
7123 'map[X|form|Q|area]' +
\r
7125 'iframe[#|X|form|a|H|K|N|O|Q]' +
\r
7151 'select[optgroup|option]' +
\r
7152 'optgroup[option]' +
\r
7156 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
\r
7158 'ins[#|X|form|a|H|K|N|O|Q]' +
\r
7160 'del[#|X|form|a|H|K|N|O|Q]' +
\r
7162 'div[#|X|form|a|H|K|N|O|Q]' +
\r
7164 'li[#|X|form|a|H|K|N|O|Q]' +
\r
7168 'dd[#|X|form|a|H|K|N|O|Q]' +
\r
7173 'blockquote[#|X|form|a|H|K|N|O|Q]' +
\r
7175 'center[#|X|form|a|H|K|N|O|Q]' +
\r
7176 'noframes[#|X|form|a|H|K|N|O|Q]' +
\r
7178 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
\r
7180 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
\r
7183 'colgroup[col]' +
\r
7186 'th[#|X|form|a|H|K|N|O|Q]' +
\r
7187 'form[#|X|a|H|K|N|O|Q]' +
\r
7188 'noscript[#|X|form|a|H|K|N|O|Q]' +
\r
7189 'td[#|X|form|a|H|K|N|O|Q]' +
\r
7194 'body[#|X|form|a|H|K|N|O|Q]'
\r
7197 tinymce.dom.Schema = function() {
\r
7198 var t = this, elements = transitional;
\r
7200 t.isValid = function(name, child_name) {
\r
7201 var element = elements[name];
\r
7203 return !!(element && (!child_name || element[child_name]));
\r
7207 (function(tinymce) {
\r
7208 tinymce.dom.RangeUtils = function(dom) {
\r
7209 var INVISIBLE_CHAR = '\uFEFF';
\r
7211 this.walk = function(rng, callback) {
\r
7212 var startContainer = rng.startContainer,
\r
7213 startOffset = rng.startOffset,
\r
7214 endContainer = rng.endContainer,
\r
7215 endOffset = rng.endOffset,
\r
7216 ancestor, startPoint,
\r
7217 endPoint, node, parent, siblings, nodes;
\r
7219 // Handle table cell selection the table plugin enables
\r
7220 // you to fake select table cells and perform formatting actions on them
\r
7221 nodes = dom.select('td.mceSelected,th.mceSelected');
\r
7222 if (nodes.length > 0) {
\r
7223 tinymce.each(nodes, function(node) {
\r
7230 function collectSiblings(node, name, end_node) {
\r
7231 var siblings = [];
\r
7233 for (; node && node != end_node; node = node[name])
\r
7234 siblings.push(node);
\r
7239 function findEndPoint(node, root) {
\r
7241 if (node.parentNode == root)
\r
7244 node = node.parentNode;
\r
7248 function walkBoundary(start_node, end_node, next) {
\r
7249 var siblingName = next ? 'nextSibling' : 'previousSibling';
\r
7251 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
\r
7252 parent = node.parentNode;
\r
7253 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
\r
7255 if (siblings.length) {
\r
7257 siblings.reverse();
\r
7259 callback(siblings);
\r
7264 // If index based start position then resolve it
\r
7265 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
\r
7266 startContainer = startContainer.childNodes[startOffset];
\r
7268 // If index based end position then resolve it
\r
7269 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
\r
7270 endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
\r
7272 // Find common ancestor and end points
\r
7273 ancestor = dom.findCommonAncestor(startContainer, endContainer);
\r
7276 if (startContainer == endContainer)
\r
7277 return callback([startContainer]);
\r
7279 // Process left side
\r
7280 for (node = startContainer; node; node = node.parentNode) {
\r
7281 if (node == endContainer)
\r
7282 return walkBoundary(startContainer, ancestor, true);
\r
7284 if (node == ancestor)
\r
7288 // Process right side
\r
7289 for (node = endContainer; node; node = node.parentNode) {
\r
7290 if (node == startContainer)
\r
7291 return walkBoundary(endContainer, ancestor);
\r
7293 if (node == ancestor)
\r
7297 // Find start/end point
\r
7298 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
\r
7299 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
\r
7302 walkBoundary(startContainer, startPoint, true);
\r
7304 // Walk the middle from start to end point
\r
7305 siblings = collectSiblings(
\r
7306 startPoint == startContainer ? startPoint : startPoint.nextSibling,
\r
7308 endPoint == endContainer ? endPoint.nextSibling : endPoint
\r
7311 if (siblings.length)
\r
7312 callback(siblings);
\r
7314 // Walk right leaf
\r
7315 walkBoundary(endContainer, endPoint);
\r
7318 /* this.split = function(rng) {
\r
7319 var startContainer = rng.startContainer,
\r
7320 startOffset = rng.startOffset,
\r
7321 endContainer = rng.endContainer,
\r
7322 endOffset = rng.endOffset;
\r
7324 function splitText(node, offset) {
\r
7325 if (offset == node.nodeValue.length)
\r
7326 node.appendData(INVISIBLE_CHAR);
\r
7328 node = node.splitText(offset);
\r
7330 if (node.nodeValue === INVISIBLE_CHAR)
\r
7331 node.nodeValue = '';
\r
7336 // Handle single text node
\r
7337 if (startContainer == endContainer) {
\r
7338 if (startContainer.nodeType == 3) {
\r
7339 if (startOffset != 0)
\r
7340 startContainer = endContainer = splitText(startContainer, startOffset);
\r
7342 if (endOffset - startOffset != startContainer.nodeValue.length)
\r
7343 splitText(startContainer, endOffset - startOffset);
\r
7346 // Split startContainer text node if needed
\r
7347 if (startContainer.nodeType == 3 && startOffset != 0) {
\r
7348 startContainer = splitText(startContainer, startOffset);
\r
7352 // Split endContainer text node if needed
\r
7353 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
\r
7354 endContainer = splitText(endContainer, endOffset).previousSibling;
\r
7355 endOffset = endContainer.nodeValue.length;
\r
7360 startContainer : startContainer,
\r
7361 startOffset : startOffset,
\r
7362 endContainer : endContainer,
\r
7363 endOffset : endOffset
\r
7369 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
\r
7370 if (rng1 && rng2) {
\r
7371 // Compare native IE ranges
\r
7372 if (rng1.item || rng1.duplicate) {
\r
7373 // Both are control ranges and the selected element matches
\r
7374 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
\r
7377 // Both are text ranges and the range matches
\r
7378 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
\r
7381 // Compare w3c ranges
\r
7382 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
\r
7390 (function(tinymce) {
\r
7391 // Shorten class names
\r
7392 var DOM = tinymce.DOM, is = tinymce.is;
\r
7394 tinymce.create('tinymce.ui.Control', {
\r
7395 Control : function(id, s) {
\r
7397 this.settings = s = s || {};
\r
7398 this.rendered = false;
\r
7399 this.onRender = new tinymce.util.Dispatcher(this);
\r
7400 this.classPrefix = '';
\r
7401 this.scope = s.scope || this;
\r
7402 this.disabled = 0;
\r
7406 setDisabled : function(s) {
\r
7409 if (s != this.disabled) {
\r
7410 e = DOM.get(this.id);
\r
7412 // Add accessibility title for unavailable actions
\r
7413 if (e && this.settings.unavailable_prefix) {
\r
7415 this.prevTitle = e.title;
\r
7416 e.title = this.settings.unavailable_prefix + ": " + e.title;
\r
7418 e.title = this.prevTitle;
\r
7421 this.setState('Disabled', s);
\r
7422 this.setState('Enabled', !s);
\r
7423 this.disabled = s;
\r
7427 isDisabled : function() {
\r
7428 return this.disabled;
\r
7431 setActive : function(s) {
\r
7432 if (s != this.active) {
\r
7433 this.setState('Active', s);
\r
7438 isActive : function() {
\r
7439 return this.active;
\r
7442 setState : function(c, s) {
\r
7443 var n = DOM.get(this.id);
\r
7445 c = this.classPrefix + c;
\r
7448 DOM.addClass(n, c);
\r
7450 DOM.removeClass(n, c);
\r
7453 isRendered : function() {
\r
7454 return this.rendered;
\r
7457 renderHTML : function() {
\r
7460 renderTo : function(n) {
\r
7461 DOM.setHTML(n, this.renderHTML());
\r
7464 postRender : function() {
\r
7467 // Set pending states
\r
7468 if (is(t.disabled)) {
\r
7474 if (is(t.active)) {
\r
7481 remove : function() {
\r
7482 DOM.remove(this.id);
\r
7486 destroy : function() {
\r
7487 tinymce.dom.Event.clear(this.id);
\r
7491 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
\r
7492 Container : function(id, s) {
\r
7493 this.parent(id, s);
\r
7495 this.controls = [];
\r
7500 add : function(c) {
\r
7501 this.lookup[c.id] = c;
\r
7502 this.controls.push(c);
\r
7507 get : function(n) {
\r
7508 return this.lookup[n];
\r
7513 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
\r
7514 Separator : function(id, s) {
\r
7515 this.parent(id, s);
\r
7516 this.classPrefix = 'mceSeparator';
\r
7519 renderHTML : function() {
\r
7520 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
\r
7524 (function(tinymce) {
\r
7525 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
7527 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
\r
7528 MenuItem : function(id, s) {
\r
7529 this.parent(id, s);
\r
7530 this.classPrefix = 'mceMenuItem';
\r
7533 setSelected : function(s) {
\r
7534 this.setState('Selected', s);
\r
7535 this.selected = s;
\r
7538 isSelected : function() {
\r
7539 return this.selected;
\r
7542 postRender : function() {
\r
7547 // Set pending state
\r
7548 if (is(t.selected))
\r
7549 t.setSelected(t.selected);
\r
7554 (function(tinymce) {
\r
7555 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
7557 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
\r
7558 Menu : function(id, s) {
\r
7563 t.collapsed = false;
\r
7565 t.onAddItem = new tinymce.util.Dispatcher(this);
\r
7568 expand : function(d) {
\r
7572 walk(t, function(o) {
\r
7578 t.collapsed = false;
\r
7581 collapse : function(d) {
\r
7585 walk(t, function(o) {
\r
7591 t.collapsed = true;
\r
7594 isCollapsed : function() {
\r
7595 return this.collapsed;
\r
7598 add : function(o) {
\r
7600 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
\r
7602 this.onAddItem.dispatch(this, o);
\r
7604 return this.items[o.id] = o;
\r
7607 addSeparator : function() {
\r
7608 return this.add({separator : true});
\r
7611 addMenu : function(o) {
\r
7613 o = this.createMenu(o);
\r
7617 return this.add(o);
\r
7620 hasMenus : function() {
\r
7621 return this.menuCount !== 0;
\r
7624 remove : function(o) {
\r
7625 delete this.items[o.id];
\r
7628 removeAll : function() {
\r
7631 walk(t, function(o) {
\r
7643 createMenu : function(o) {
\r
7644 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
\r
7646 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
\r
7652 (function(tinymce) {
\r
7653 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
\r
7655 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
\r
7656 DropMenu : function(id, s) {
\r
7658 s.container = s.container || DOM.doc.body;
\r
7659 s.offset_x = s.offset_x || 0;
\r
7660 s.offset_y = s.offset_y || 0;
\r
7661 s.vp_offset_x = s.vp_offset_x || 0;
\r
7662 s.vp_offset_y = s.vp_offset_y || 0;
\r
7664 if (is(s.icons) && !s.icons)
\r
7665 s['class'] += ' mceNoIcons';
\r
7667 this.parent(id, s);
\r
7668 this.onShowMenu = new tinymce.util.Dispatcher(this);
\r
7669 this.onHideMenu = new tinymce.util.Dispatcher(this);
\r
7670 this.classPrefix = 'mceMenu';
\r
7673 createMenu : function(s) {
\r
7674 var t = this, cs = t.settings, m;
\r
7676 s.container = s.container || cs.container;
\r
7678 s.constrain = s.constrain || cs.constrain;
\r
7679 s['class'] = s['class'] || cs['class'];
\r
7680 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
\r
7681 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
\r
7682 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
\r
7684 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
\r
7689 update : function() {
\r
7690 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
\r
7692 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
\r
7693 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
\r
7695 if (!DOM.boxModel)
\r
7696 t.element.setStyles({width : tw + 2, height : th + 2});
\r
7698 t.element.setStyles({width : tw, height : th});
\r
7701 DOM.setStyle(co, 'width', tw);
\r
7703 if (s.max_height) {
\r
7704 DOM.setStyle(co, 'height', th);
\r
7706 if (tb.clientHeight < s.max_height)
\r
7707 DOM.setStyle(co, 'overflow', 'hidden');
\r
7711 showMenu : function(x, y, px) {
\r
7712 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
\r
7716 if (t.isMenuVisible)
\r
7719 if (!t.rendered) {
\r
7720 co = DOM.add(t.settings.container, t.renderNode());
\r
7722 each(t.items, function(o) {
\r
7726 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
7728 co = DOM.get('menu_' + t.id);
\r
7730 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
\r
7731 if (!tinymce.isOpera)
\r
7732 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
\r
7737 x += s.offset_x || 0;
\r
7738 y += s.offset_y || 0;
\r
7742 // Move inside viewport if not submenu
\r
7743 if (s.constrain) {
\r
7744 w = co.clientWidth - ot;
\r
7745 h = co.clientHeight - ot;
\r
7749 if ((x + s.vp_offset_x + w) > mx)
\r
7750 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
\r
7752 if ((y + s.vp_offset_y + h) > my)
\r
7753 y = Math.max(0, (my - s.vp_offset_y) - h);
\r
7756 DOM.setStyles(co, {left : x , top : y});
\r
7757 t.element.update();
\r
7759 t.isMenuVisible = 1;
\r
7760 t.mouseClickFunc = Event.add(co, 'click', function(e) {
\r
7765 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
\r
7766 m = t.items[e.id];
\r
7768 if (m.isDisabled())
\r
7777 dm = dm.settings.parent;
\r
7780 if (m.settings.onclick)
\r
7781 m.settings.onclick(e);
\r
7783 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
7787 if (t.hasMenus()) {
\r
7788 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
\r
7792 if (e && (e = DOM.getParent(e, 'tr'))) {
\r
7793 m = t.items[e.id];
\r
7796 t.lastMenu.collapse(1);
\r
7798 if (m.isDisabled())
\r
7801 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
\r
7802 //p = DOM.getPos(s.container);
\r
7803 r = DOM.getRect(e);
\r
7804 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
\r
7806 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
\r
7812 t.onShowMenu.dispatch(t);
\r
7814 if (s.keyboard_focus) {
\r
7815 Event.add(co, 'keydown', t._keyHandler, t);
\r
7816 DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
\r
7821 hideMenu : function(c) {
\r
7822 var t = this, co = DOM.get('menu_' + t.id), e;
\r
7824 if (!t.isMenuVisible)
\r
7827 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
7828 Event.remove(co, 'click', t.mouseClickFunc);
\r
7829 Event.remove(co, 'keydown', t._keyHandler);
\r
7831 t.isMenuVisible = 0;
\r
7839 if (e = DOM.get(t.id))
\r
7840 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
\r
7842 t.onHideMenu.dispatch(t);
\r
7845 add : function(o) {
\r
7850 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
\r
7851 t._add(DOM.select('tbody', co)[0], o);
\r
7856 collapse : function(d) {
\r
7861 remove : function(o) {
\r
7865 return this.parent(o);
\r
7868 destroy : function() {
\r
7869 var t = this, co = DOM.get('menu_' + t.id);
\r
7871 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
7872 Event.remove(co, 'click', t.mouseClickFunc);
\r
7875 t.element.remove();
\r
7880 renderNode : function() {
\r
7881 var t = this, s = t.settings, n, tb, co, w;
\r
7883 w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
\r
7884 co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
\r
7885 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
7888 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
\r
7890 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
\r
7891 n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
\r
7892 tb = DOM.add(n, 'tbody');
\r
7894 each(t.items, function(o) {
\r
7898 t.rendered = true;
\r
7903 // Internal functions
\r
7905 _keyHandler : function(e) {
\r
7906 var t = this, kc = e.keyCode;
\r
7908 function focus(d) {
\r
7909 var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
\r
7919 focus(-1); // Select first link
\r
7930 return this.hideMenu();
\r
7934 _add : function(tb, o) {
\r
7935 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
\r
7937 if (s.separator) {
\r
7938 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
\r
7939 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
\r
7941 if (n = ro.previousSibling)
\r
7942 DOM.addClass(n, 'mceLast');
\r
7947 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
\r
7948 n = it = DOM.add(n, 'td');
\r
7949 n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
\r
7951 DOM.addClass(it, s['class']);
\r
7952 // n = DOM.add(n, 'span', {'class' : 'item'});
\r
7954 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
\r
7957 DOM.add(ic, 'img', {src : s.icon_src});
\r
7959 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
\r
7961 if (o.settings.style)
\r
7962 DOM.setAttrib(n, 'style', o.settings.style);
\r
7964 if (tb.childNodes.length == 1)
\r
7965 DOM.addClass(ro, 'mceFirst');
\r
7967 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
\r
7968 DOM.addClass(ro, 'mceFirst');
\r
7971 DOM.addClass(ro, cp + 'ItemSub');
\r
7973 if (n = ro.previousSibling)
\r
7974 DOM.removeClass(n, 'mceLast');
\r
7976 DOM.addClass(ro, 'mceLast');
\r
7980 (function(tinymce) {
\r
7981 var DOM = tinymce.DOM;
\r
7983 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
\r
7984 Button : function(id, s) {
\r
7985 this.parent(id, s);
\r
7986 this.classPrefix = 'mceButton';
\r
7989 renderHTML : function() {
\r
7990 var cp = this.classPrefix, s = this.settings, h, l;
\r
7992 l = DOM.encode(s.label || '');
\r
7993 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
7996 h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
\r
7998 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
\r
8003 postRender : function() {
\r
8004 var t = this, s = t.settings;
\r
8006 tinymce.dom.Event.add(t.id, 'click', function(e) {
\r
8007 if (!t.isDisabled())
\r
8008 return s.onclick.call(s.scope, e);
\r
8014 (function(tinymce) {
\r
8015 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
8017 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
\r
8018 ListBox : function(id, s) {
\r
8025 t.onChange = new Dispatcher(t);
\r
8027 t.onPostRender = new Dispatcher(t);
\r
8029 t.onAdd = new Dispatcher(t);
\r
8031 t.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
8033 t.classPrefix = 'mceListBox';
\r
8036 select : function(va) {
\r
8037 var t = this, fv, f;
\r
8039 if (va == undefined)
\r
8040 return t.selectByIndex(-1);
\r
8042 // Is string or number make function selector
\r
8043 if (va && va.call)
\r
8051 // Do we need to do something?
\r
8052 if (va != t.selectedValue) {
\r
8054 each(t.items, function(o, i) {
\r
8057 t.selectByIndex(i);
\r
8063 t.selectByIndex(-1);
\r
8067 selectByIndex : function(idx) {
\r
8068 var t = this, e, o;
\r
8070 if (idx != t.selectedIndex) {
\r
8071 e = DOM.get(t.id + '_text');
\r
8075 t.selectedValue = o.value;
\r
8076 t.selectedIndex = idx;
\r
8077 DOM.setHTML(e, DOM.encode(o.title));
\r
8078 DOM.removeClass(e, 'mceTitle');
\r
8080 DOM.setHTML(e, DOM.encode(t.settings.title));
\r
8081 DOM.addClass(e, 'mceTitle');
\r
8082 t.selectedValue = t.selectedIndex = null;
\r
8089 add : function(n, v, o) {
\r
8093 o = tinymce.extend(o, {
\r
8099 t.onAdd.dispatch(t, o);
\r
8102 getLength : function() {
\r
8103 return this.items.length;
\r
8106 renderHTML : function() {
\r
8107 var h = '', t = this, s = t.settings, cp = t.classPrefix;
\r
8109 h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
\r
8110 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
8111 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
8112 h += '</tr></tbody></table>';
\r
8117 showMenu : function() {
\r
8118 var t = this, p1, p2, e = DOM.get(this.id), m;
\r
8120 if (t.isDisabled() || t.items.length == 0)
\r
8123 if (t.menu && t.menu.isMenuVisible)
\r
8124 return t.hideMenu();
\r
8126 if (!t.isMenuRendered) {
\r
8128 t.isMenuRendered = true;
\r
8131 p1 = DOM.getPos(this.settings.menu_container);
\r
8132 p2 = DOM.getPos(e);
\r
8135 m.settings.offset_x = p2.x;
\r
8136 m.settings.offset_y = p2.y;
\r
8137 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
\r
8141 m.items[t.oldID].setSelected(0);
\r
8143 each(t.items, function(o) {
\r
8144 if (o.value === t.selectedValue) {
\r
8145 m.items[o.id].setSelected(1);
\r
8150 m.showMenu(0, e.clientHeight);
\r
8152 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8153 DOM.addClass(t.id, t.classPrefix + 'Selected');
\r
8155 //DOM.get(t.id + '_text').focus();
\r
8158 hideMenu : function(e) {
\r
8161 if (t.menu && t.menu.isMenuVisible) {
\r
8162 // Prevent double toogles by canceling the mouse click event to the button
\r
8163 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
\r
8166 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
8167 DOM.removeClass(t.id, t.classPrefix + 'Selected');
\r
8168 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8169 t.menu.hideMenu();
\r
8174 renderMenu : function() {
\r
8177 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
8179 'class' : t.classPrefix + 'Menu mceNoIcons',
\r
8184 m.onHideMenu.add(t.hideMenu, t);
\r
8187 title : t.settings.title,
\r
8188 'class' : 'mceMenuItemTitle',
\r
8189 onclick : function() {
\r
8190 if (t.settings.onselect('') !== false)
\r
8191 t.select(''); // Must be runned after
\r
8195 each(t.items, function(o) {
\r
8196 // No value then treat it as a title
\r
8197 if (o.value === undefined) {
\r
8200 'class' : 'mceMenuItemTitle',
\r
8201 onclick : function() {
\r
8202 if (t.settings.onselect('') !== false)
\r
8203 t.select(''); // Must be runned after
\r
8207 o.id = DOM.uniqueId();
\r
8208 o.onclick = function() {
\r
8209 if (t.settings.onselect(o.value) !== false)
\r
8210 t.select(o.value); // Must be runned after
\r
8217 t.onRenderMenu.dispatch(t, m);
\r
8221 postRender : function() {
\r
8222 var t = this, cp = t.classPrefix;
\r
8224 Event.add(t.id, 'click', t.showMenu, t);
\r
8225 Event.add(t.id + '_text', 'focus', function() {
\r
8226 if (!t._focused) {
\r
8227 t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
\r
8228 var idx = -1, v, kc = e.keyCode;
\r
8230 // Find current index
\r
8231 each(t.items, function(v, i) {
\r
8232 if (t.selectedValue == v.value)
\r
8238 v = t.items[idx - 1];
\r
8239 else if (kc == 40)
\r
8240 v = t.items[idx + 1];
\r
8241 else if (kc == 13) {
\r
8242 // Fake select on enter
\r
8243 v = t.selectedValue;
\r
8244 t.selectedValue = null; // Needs to be null to fake change
\r
8245 t.settings.onselect(v);
\r
8246 return Event.cancel(e);
\r
8251 t.select(v.value);
\r
8258 Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
\r
8260 // Old IE doesn't have hover on all elements
\r
8261 if (tinymce.isIE6 || !DOM.boxModel) {
\r
8262 Event.add(t.id, 'mouseover', function() {
\r
8263 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
8264 DOM.addClass(t.id, cp + 'Hover');
\r
8267 Event.add(t.id, 'mouseout', function() {
\r
8268 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
8269 DOM.removeClass(t.id, cp + 'Hover');
\r
8273 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
8276 destroy : function() {
\r
8279 Event.clear(this.id + '_text');
\r
8280 Event.clear(this.id + '_open');
\r
8284 (function(tinymce) {
\r
8285 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
8287 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
\r
8288 NativeListBox : function(id, s) {
\r
8289 this.parent(id, s);
\r
8290 this.classPrefix = 'mceNativeListBox';
\r
8293 setDisabled : function(s) {
\r
8294 DOM.get(this.id).disabled = s;
\r
8297 isDisabled : function() {
\r
8298 return DOM.get(this.id).disabled;
\r
8301 select : function(va) {
\r
8302 var t = this, fv, f;
\r
8304 if (va == undefined)
\r
8305 return t.selectByIndex(-1);
\r
8307 // Is string or number make function selector
\r
8308 if (va && va.call)
\r
8316 // Do we need to do something?
\r
8317 if (va != t.selectedValue) {
\r
8319 each(t.items, function(o, i) {
\r
8322 t.selectByIndex(i);
\r
8328 t.selectByIndex(-1);
\r
8332 selectByIndex : function(idx) {
\r
8333 DOM.get(this.id).selectedIndex = idx + 1;
\r
8334 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
\r
8337 add : function(n, v, a) {
\r
8343 if (t.isRendered())
\r
8344 DOM.add(DOM.get(this.id), 'option', a, n);
\r
8353 t.onAdd.dispatch(t, o);
\r
8356 getLength : function() {
\r
8357 return this.items.length;
\r
8360 renderHTML : function() {
\r
8363 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
\r
8365 each(t.items, function(it) {
\r
8366 h += DOM.createHTML('option', {value : it.value}, it.title);
\r
8369 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
\r
8374 postRender : function() {
\r
8377 t.rendered = true;
\r
8379 function onChange(e) {
\r
8380 var v = t.items[e.target.selectedIndex - 1];
\r
8382 if (v && (v = v.value)) {
\r
8383 t.onChange.dispatch(t, v);
\r
8385 if (t.settings.onselect)
\r
8386 t.settings.onselect(v);
\r
8390 Event.add(t.id, 'change', onChange);
\r
8392 // Accessibility keyhandler
\r
8393 Event.add(t.id, 'keydown', function(e) {
\r
8396 Event.remove(t.id, 'change', ch);
\r
8398 bf = Event.add(t.id, 'blur', function() {
\r
8399 Event.add(t.id, 'change', onChange);
\r
8400 Event.remove(t.id, 'blur', bf);
\r
8403 if (e.keyCode == 13 || e.keyCode == 32) {
\r
8405 return Event.cancel(e);
\r
8409 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
8413 (function(tinymce) {
\r
8414 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
8416 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
\r
8417 MenuButton : function(id, s) {
\r
8418 this.parent(id, s);
\r
8420 this.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
8422 s.menu_container = s.menu_container || DOM.doc.body;
\r
8425 showMenu : function() {
\r
8426 var t = this, p1, p2, e = DOM.get(t.id), m;
\r
8428 if (t.isDisabled())
\r
8431 if (!t.isMenuRendered) {
\r
8433 t.isMenuRendered = true;
\r
8436 if (t.isMenuVisible)
\r
8437 return t.hideMenu();
\r
8439 p1 = DOM.getPos(t.settings.menu_container);
\r
8440 p2 = DOM.getPos(e);
\r
8443 m.settings.offset_x = p2.x;
\r
8444 m.settings.offset_y = p2.y;
\r
8445 m.settings.vp_offset_x = p2.x;
\r
8446 m.settings.vp_offset_y = p2.y;
\r
8447 m.settings.keyboard_focus = t._focused;
\r
8448 m.showMenu(0, e.clientHeight);
\r
8450 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8451 t.setState('Selected', 1);
\r
8453 t.isMenuVisible = 1;
\r
8456 renderMenu : function() {
\r
8459 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
8461 'class' : this.classPrefix + 'Menu',
\r
8462 icons : t.settings.icons
\r
8465 m.onHideMenu.add(t.hideMenu, t);
\r
8467 t.onRenderMenu.dispatch(t, m);
\r
8471 hideMenu : function(e) {
\r
8474 // Prevent double toogles by canceling the mouse click event to the button
\r
8475 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
\r
8478 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
8479 t.setState('Selected', 0);
\r
8480 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8482 t.menu.hideMenu();
\r
8485 t.isMenuVisible = 0;
\r
8488 postRender : function() {
\r
8489 var t = this, s = t.settings;
\r
8491 Event.add(t.id, 'click', function() {
\r
8492 if (!t.isDisabled()) {
\r
8494 s.onclick(t.value);
\r
8503 (function(tinymce) {
\r
8504 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
8506 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
\r
8507 SplitButton : function(id, s) {
\r
8508 this.parent(id, s);
\r
8509 this.classPrefix = 'mceSplitButton';
\r
8512 renderHTML : function() {
\r
8513 var h, t = this, s = t.settings, h1;
\r
8515 h = '<tbody><tr>';
\r
8518 h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
\r
8520 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
\r
8522 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
8524 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
\r
8525 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
8527 h += '</tr></tbody>';
\r
8529 return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
\r
8532 postRender : function() {
\r
8533 var t = this, s = t.settings;
\r
8536 Event.add(t.id + '_action', 'click', function() {
\r
8537 if (!t.isDisabled())
\r
8538 s.onclick(t.value);
\r
8542 Event.add(t.id + '_open', 'click', t.showMenu, t);
\r
8543 Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
\r
8544 Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
\r
8546 // Old IE doesn't have hover on all elements
\r
8547 if (tinymce.isIE6 || !DOM.boxModel) {
\r
8548 Event.add(t.id, 'mouseover', function() {
\r
8549 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
8550 DOM.addClass(t.id, 'mceSplitButtonHover');
\r
8553 Event.add(t.id, 'mouseout', function() {
\r
8554 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
8555 DOM.removeClass(t.id, 'mceSplitButtonHover');
\r
8560 destroy : function() {
\r
8563 Event.clear(this.id + '_action');
\r
8564 Event.clear(this.id + '_open');
\r
8569 (function(tinymce) {
\r
8570 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
\r
8572 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
\r
8573 ColorSplitButton : function(id, s) {
\r
8578 t.settings = s = tinymce.extend({
\r
8579 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
8581 default_color : '#888888'
\r
8584 t.onShowMenu = new tinymce.util.Dispatcher(t);
\r
8586 t.onHideMenu = new tinymce.util.Dispatcher(t);
\r
8588 t.value = s.default_color;
\r
8591 showMenu : function() {
\r
8592 var t = this, r, p, e, p2;
\r
8594 if (t.isDisabled())
\r
8597 if (!t.isMenuRendered) {
\r
8599 t.isMenuRendered = true;
\r
8602 if (t.isMenuVisible)
\r
8603 return t.hideMenu();
\r
8605 e = DOM.get(t.id);
\r
8606 DOM.show(t.id + '_menu');
\r
8607 DOM.addClass(e, 'mceSplitButtonSelected');
\r
8608 p2 = DOM.getPos(e);
\r
8609 DOM.setStyles(t.id + '_menu', {
\r
8611 top : p2.y + e.clientHeight,
\r
8616 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8617 t.onShowMenu.dispatch(t);
\r
8620 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
\r
8621 if (e.keyCode == 27)
\r
8625 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
\r
8628 t.isMenuVisible = 1;
\r
8631 hideMenu : function(e) {
\r
8634 // Prevent double toogles by canceling the mouse click event to the button
\r
8635 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
\r
8638 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
\r
8639 DOM.removeClass(t.id, 'mceSplitButtonSelected');
\r
8640 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
8641 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
\r
8642 DOM.hide(t.id + '_menu');
\r
8645 t.onHideMenu.dispatch(t);
\r
8647 t.isMenuVisible = 0;
\r
8650 renderMenu : function() {
\r
8651 var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
\r
8653 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
8654 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
\r
8655 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
\r
8657 n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
\r
8658 tb = DOM.add(n, 'tbody');
\r
8660 // Generate color grid
\r
8662 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
\r
8663 c = c.replace(/^#/, '');
\r
8666 tr = DOM.add(tb, 'tr');
\r
8667 i = s.grid_width - 1;
\r
8670 n = DOM.add(tr, 'td');
\r
8672 n = DOM.add(n, 'a', {
\r
8673 href : 'javascript:;',
\r
8675 backgroundColor : '#' + c
\r
8677 _mce_color : '#' + c
\r
8681 if (s.more_colors_func) {
\r
8682 n = DOM.add(tb, 'tr');
\r
8683 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
\r
8684 n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
\r
8686 Event.add(n, 'click', function(e) {
\r
8687 s.more_colors_func.call(s.more_colors_scope || this);
\r
8688 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
8692 DOM.addClass(m, 'mceColorSplitMenu');
\r
8694 Event.add(t.id + '_menu', 'click', function(e) {
\r
8699 if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
\r
8702 return Event.cancel(e); // Prevent IE auto save warning
\r
8708 setColor : function(c) {
\r
8711 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
\r
8715 t.settings.onselect(c);
\r
8718 postRender : function() {
\r
8719 var t = this, id = t.id;
\r
8722 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
\r
8723 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
\r
8726 destroy : function() {
\r
8729 Event.clear(this.id + '_menu');
\r
8730 Event.clear(this.id + '_more');
\r
8731 DOM.remove(this.id + '_menu');
\r
8736 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
\r
8737 renderHTML : function() {
\r
8738 var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
\r
8741 for (i=0; i<cl.length; i++) {
\r
8742 // Get current control, prev control, next control and if the control is a list box or not
\r
8747 // Add toolbar start
\r
8749 c = 'mceToolbarStart';
\r
8752 c += ' mceToolbarStartButton';
\r
8753 else if (co.SplitButton)
\r
8754 c += ' mceToolbarStartSplitButton';
\r
8755 else if (co.ListBox)
\r
8756 c += ' mceToolbarStartListBox';
\r
8758 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8761 // Add toolbar end before list box and after the previous button
\r
8762 // This is to fix the o2k7 editor skins
\r
8763 if (pr && co.ListBox) {
\r
8764 if (pr.Button || pr.SplitButton)
\r
8765 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8768 // Render control HTML
\r
8770 // IE 8 quick fix, needed to propertly generate a hit area for anchors
\r
8772 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
\r
8774 h += '<td>' + co.renderHTML() + '</td>';
\r
8776 // Add toolbar start after list box and before the next button
\r
8777 // This is to fix the o2k7 editor skins
\r
8778 if (nx && co.ListBox) {
\r
8779 if (nx.Button || nx.SplitButton)
\r
8780 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8784 c = 'mceToolbarEnd';
\r
8787 c += ' mceToolbarEndButton';
\r
8788 else if (co.SplitButton)
\r
8789 c += ' mceToolbarEndSplitButton';
\r
8790 else if (co.ListBox)
\r
8791 c += ' mceToolbarEndListBox';
\r
8793 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
8795 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
8799 (function(tinymce) {
\r
8800 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
\r
8802 tinymce.create('tinymce.AddOnManager', {
\r
8807 onAdd : new Dispatcher(this),
\r
8809 get : function(n) {
\r
8810 return this.lookup[n];
\r
8813 requireLangPack : function(n) {
\r
8814 var s = tinymce.settings;
\r
8816 if (s && s.language)
\r
8817 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
\r
8820 add : function(id, o) {
\r
8821 this.items.push(o);
\r
8822 this.lookup[id] = o;
\r
8823 this.onAdd.dispatch(this, id, o);
\r
8828 load : function(n, u, cb, s) {
\r
8834 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
\r
8835 u = tinymce.baseURL + '/' + u;
\r
8837 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
\r
8838 tinymce.ScriptLoader.add(u, cb, s);
\r
8842 // Create plugin and theme managers
\r
8843 tinymce.PluginManager = new tinymce.AddOnManager();
\r
8844 tinymce.ThemeManager = new tinymce.AddOnManager();
\r
8847 (function(tinymce) {
\r
8849 var each = tinymce.each, extend = tinymce.extend,
\r
8850 DOM = tinymce.DOM, Event = tinymce.dom.Event,
\r
8851 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
8852 explode = tinymce.explode,
\r
8853 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
\r
8855 // Setup some URLs where the editor API is located and where the document is
\r
8856 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
\r
8857 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
\r
8858 tinymce.documentBaseURL += '/';
\r
8860 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
\r
8862 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
\r
8864 // Add before unload listener
\r
8865 // This was required since IE was leaking memory if you added and removed beforeunload listeners
\r
8866 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
\r
8867 tinymce.onBeforeUnload = new Dispatcher(tinymce);
\r
8869 // Must be on window or IE will leak if the editor is placed in frame or iframe
\r
8870 Event.add(window, 'beforeunload', function(e) {
\r
8871 tinymce.onBeforeUnload.dispatch(tinymce, e);
\r
8874 tinymce.onAddEditor = new Dispatcher(tinymce);
\r
8876 tinymce.onRemoveEditor = new Dispatcher(tinymce);
\r
8878 tinymce.EditorManager = extend(tinymce, {
\r
8883 activeEditor : null,
\r
8885 init : function(s) {
\r
8886 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
\r
8888 function execCallback(se, n, s) {
\r
8894 if (tinymce.is(f, 'string')) {
\r
8895 s = f.replace(/\.\w+$/, '');
\r
8896 s = s ? tinymce.resolve(s) : 0;
\r
8897 f = tinymce.resolve(f);
\r
8900 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
\r
8911 Event.add(document, 'init', function() {
\r
8914 execCallback(s, 'onpageload');
\r
8918 l = s.elements || '';
\r
8920 if(l.length > 0) {
\r
8921 each(explode(l), function(v) {
\r
8923 ed = new tinymce.Editor(v, s);
\r
8927 each(document.forms, function(f) {
\r
8928 each(f.elements, function(e) {
\r
8929 if (e.name === v) {
\r
8930 v = 'mce_editor_' + instanceCounter++;
\r
8931 DOM.setAttrib(e, 'id', v);
\r
8933 ed = new tinymce.Editor(v, s);
\r
8945 case "specific_textareas":
\r
8946 function hasClass(n, c) {
\r
8947 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
\r
8950 each(DOM.select('textarea'), function(v) {
\r
8951 if (s.editor_deselector && hasClass(v, s.editor_deselector))
\r
8954 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
\r
8955 // Can we use the name
\r
8956 e = DOM.get(v.name);
\r
8960 // Generate unique name if missing or already exists
\r
8961 if (!v.id || t.get(v.id))
\r
8962 v.id = DOM.uniqueId();
\r
8964 ed = new tinymce.Editor(v.id, s);
\r
8972 // Call onInit when all editors are initialized
\r
8976 each(el, function(ed) {
\r
8979 if (!ed.initialized) {
\r
8981 ed.onInit.add(function() {
\r
8986 execCallback(s, 'oninit');
\r
8993 execCallback(s, 'oninit');
\r
8999 get : function(id) {
\r
9000 if (id === undefined)
\r
9001 return this.editors;
\r
9003 return this.editors[id];
\r
9006 getInstanceById : function(id) {
\r
9007 return this.get(id);
\r
9010 add : function(editor) {
\r
9011 var self = this, editors = self.editors;
\r
9013 // Add named and index editor instance
\r
9014 editors[editor.id] = editor;
\r
9015 editors.push(editor);
\r
9017 self._setActive(editor);
\r
9018 self.onAddEditor.dispatch(self, editor);
\r
9024 remove : function(editor) {
\r
9025 var t = this, i, editors = t.editors;
\r
9027 // Not in the collection
\r
9028 if (!editors[editor.id])
\r
9031 delete editors[editor.id];
\r
9033 for (i = 0; i < editors.length; i++) {
\r
9034 if (editors[i] == editor) {
\r
9035 editors.splice(i, 1);
\r
9040 // Select another editor since the active one was removed
\r
9041 if (t.activeEditor == editor)
\r
9042 t._setActive(editors[0]);
\r
9045 t.onRemoveEditor.dispatch(t, editor);
\r
9050 execCommand : function(c, u, v) {
\r
9051 var t = this, ed = t.get(v), w;
\r
9053 // Manager commands
\r
9059 case "mceAddEditor":
\r
9060 case "mceAddControl":
\r
9062 new tinymce.Editor(v, t.settings).render();
\r
9066 case "mceAddFrameControl":
\r
9069 // Add tinyMCE global instance and tinymce namespace to specified window
\r
9070 w.tinyMCE = tinyMCE;
\r
9071 w.tinymce = tinymce;
\r
9073 tinymce.DOM.doc = w.document;
\r
9074 tinymce.DOM.win = w;
\r
9076 ed = new tinymce.Editor(v.element_id, v);
\r
9079 // Fix IE memory leaks
\r
9080 if (tinymce.isIE) {
\r
9083 w.detachEvent('onunload', clr);
\r
9084 w = w.tinyMCE = w.tinymce = null; // IE leak
\r
9087 w.attachEvent('onunload', clr);
\r
9090 v.page_window = null;
\r
9094 case "mceRemoveEditor":
\r
9095 case "mceRemoveControl":
\r
9101 case 'mceToggleEditor':
\r
9103 t.execCommand('mceAddControl', 0, v);
\r
9107 if (ed.isHidden())
\r
9115 // Run command on active editor
\r
9116 if (t.activeEditor)
\r
9117 return t.activeEditor.execCommand(c, u, v);
\r
9122 execInstanceCommand : function(id, c, u, v) {
\r
9123 var ed = this.get(id);
\r
9126 return ed.execCommand(c, u, v);
\r
9131 triggerSave : function() {
\r
9132 each(this.editors, function(e) {
\r
9137 addI18n : function(p, o) {
\r
9138 var lo, i18n = this.i18n;
\r
9140 if (!tinymce.is(p, 'string')) {
\r
9141 each(p, function(o, lc) {
\r
9142 each(o, function(o, g) {
\r
9143 each(o, function(o, k) {
\r
9144 if (g === 'common')
\r
9145 i18n[lc + '.' + k] = o;
\r
9147 i18n[lc + '.' + g + '.' + k] = o;
\r
9152 each(o, function(o, k) {
\r
9153 i18n[p + '.' + k] = o;
\r
9158 // Private methods
\r
9160 _setActive : function(editor) {
\r
9161 this.selectedInstance = this.activeEditor = editor;
\r
9166 (function(tinymce) {
\r
9167 // Shorten these names
\r
9168 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
\r
9169 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
\r
9170 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
\r
9171 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
9172 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
\r
9174 tinymce.create('tinymce.Editor', {
\r
9175 Editor : function(id, s) {
\r
9178 t.id = t.editorId = id;
\r
9180 t.execCommands = {};
\r
9181 t.queryStateCommands = {};
\r
9182 t.queryValueCommands = {};
\r
9184 t.isNotDirty = false;
\r
9188 // Add events to the editor
\r
9192 'onBeforeRenderUI',
\r
9232 'onBeforeSetContent',
\r
9234 'onBeforeGetContent',
\r
9248 'onBeforeExecCommand',
\r
9258 'onSetProgressState'
\r
9260 t[e] = new Dispatcher(t);
\r
9263 t.settings = s = extend({
\r
9266 docs_language : 'en',
\r
9273 document_base_url : tinymce.documentBaseURL,
\r
9274 add_form_submit_trigger : 1,
\r
9276 add_unload_trigger : 1,
\r
9278 relative_urls : 1,
\r
9279 remove_script_host : 1,
\r
9280 table_inline_editing : 0,
\r
9281 object_resizing : 1,
\r
9283 accessibility_focus : 1,
\r
9284 custom_shortcuts : 1,
\r
9285 custom_undo_redo_keyboard_shortcuts : 1,
\r
9286 custom_undo_redo_restore_selection : 1,
\r
9287 custom_undo_redo : 1,
\r
9288 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
9289 visual_table_class : 'mceItemTable',
\r
9291 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
\r
9292 apply_source_formatting : 1,
\r
9293 directionality : 'ltr',
\r
9294 forced_root_block : 'p',
\r
9295 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
9297 padd_empty_editor : 1,
\r
9300 force_p_newlines : 1,
\r
9301 indentation : '30px',
\r
9303 fix_table_elements : 1,
\r
9304 inline_styles : 1,
\r
9305 convert_fonts_to_spans : true
\r
9308 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
\r
9309 base_uri : tinyMCE.baseURI
\r
9312 t.baseURI = tinymce.baseURI;
\r
9315 t.execCallback('setup', t);
\r
9318 render : function(nst) {
\r
9319 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
\r
9321 // Page is not loaded yet, wait for it
\r
9322 if (!Event.domLoaded) {
\r
9323 Event.add(document, 'init', function() {
\r
9329 tinyMCE.settings = s;
\r
9331 // Element not found, then skip initialization
\r
9332 if (!t.getElement())
\r
9335 // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
\r
9336 // browser says it has contentEditable support but there is no visible caret
\r
9337 // We will remove this check ones Apple implements full contentEditable support
\r
9338 if (tinymce.isIDevice)
\r
9341 // Add hidden input for non input elements inside form elements
\r
9342 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
\r
9343 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
\r
9345 if (tinymce.WindowManager)
\r
9346 t.windowManager = new tinymce.WindowManager(t);
\r
9348 if (s.encoding == 'xml') {
\r
9349 t.onGetContent.add(function(ed, o) {
\r
9351 o.content = DOM.encode(o.content);
\r
9355 if (s.add_form_submit_trigger) {
\r
9356 t.onSubmit.addToTop(function() {
\r
9357 if (t.initialized) {
\r
9364 if (s.add_unload_trigger) {
\r
9365 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
\r
9366 if (t.initialized && !t.destroyed && !t.isHidden())
\r
9367 t.save({format : 'raw', no_events : true});
\r
9371 tinymce.addUnload(t.destroy, t);
\r
9373 if (s.submit_patch) {
\r
9374 t.onBeforeRenderUI.add(function() {
\r
9375 var n = t.getElement().form;
\r
9380 // Already patched
\r
9381 if (n._mceOldSubmit)
\r
9384 // Check page uses id="submit" or name="submit" for it's submit button
\r
9385 if (!n.submit.nodeType && !n.submit.length) {
\r
9386 t.formElement = n;
\r
9387 n._mceOldSubmit = n.submit;
\r
9388 n.submit = function() {
\r
9389 // Save all instances
\r
9390 tinymce.triggerSave();
\r
9393 return t.formElement._mceOldSubmit(t.formElement);
\r
9402 function loadScripts() {
\r
9404 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
\r
9406 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
\r
9407 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
\r
9409 each(explode(s.plugins), function(p) {
\r
9410 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
\r
9411 // Skip safari plugin, since it is removed as of 3.3b1
\r
9412 if (p == 'safari')
\r
9415 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
\r
9419 // Init when que is loaded
\r
9420 sl.loadQueue(function() {
\r
9429 init : function() {
\r
9430 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
\r
9435 s.theme = s.theme.replace(/-/, '');
\r
9436 o = ThemeManager.get(s.theme);
\r
9437 t.theme = new o();
\r
9439 if (t.theme.init && s.init_theme)
\r
9440 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
\r
9443 // Create all plugins
\r
9444 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
\r
9445 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
\r
9450 t.plugins[p] = po;
\r
9457 // Setup popup CSS path(s)
\r
9458 if (s.popup_css !== false) {
\r
9460 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
\r
9462 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
\r
9465 if (s.popup_css_add)
\r
9466 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
\r
9468 t.controlManager = new tinymce.ControlManager(t);
\r
9470 if (s.custom_undo_redo) {
\r
9471 // Add initial undo level
\r
9472 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
\r
9473 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
\r
9474 if (!t.undoManager.hasUndo())
\r
9475 t.undoManager.add();
\r
9479 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
\r
9480 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
\r
9481 t.undoManager.add();
\r
9485 t.onExecCommand.add(function(ed, c) {
\r
9486 // Don't refresh the select lists until caret move
\r
9487 if (!/^(FontName|FontSize)$/.test(c))
\r
9491 // Remove ghost selections on images and tables in Gecko
\r
9493 function repaint(a, o) {
\r
9494 if (!o || !o.initial)
\r
9495 t.execCommand('mceRepaint');
\r
9498 t.onUndo.add(repaint);
\r
9499 t.onRedo.add(repaint);
\r
9500 t.onSetContent.add(repaint);
\r
9503 // Enables users to override the control factory
\r
9504 t.onBeforeRenderUI.dispatch(t, t.controlManager);
\r
9507 if (s.render_ui) {
\r
9508 w = s.width || e.style.width || e.offsetWidth;
\r
9509 h = s.height || e.style.height || e.offsetHeight;
\r
9510 t.orgDisplay = e.style.display;
\r
9511 re = /^[0-9\.]+(|px)$/i;
\r
9513 if (re.test('' + w))
\r
9514 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
\r
9516 if (re.test('' + h))
\r
9517 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
\r
9520 o = t.theme.renderUI({
\r
9524 deltaWidth : s.delta_width,
\r
9525 deltaHeight : s.delta_height
\r
9528 t.editorContainer = o.editorContainer;
\r
9532 // User specified a document.domain value
\r
9533 if (document.domain && location.hostname != document.domain)
\r
9534 tinymce.relaxedDomain = document.domain;
\r
9537 DOM.setStyles(o.sizeContainer || o.editorContainer, {
\r
9542 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
\r
9546 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
\r
9548 // We only need to override paths if we have to
\r
9549 // IE has a bug where it remove site absolute urls to relative ones if this is specified
\r
9550 if (s.document_base_url != tinymce.documentBaseURL)
\r
9551 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
\r
9553 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
\r
9555 if (tinymce.relaxedDomain)
\r
9556 t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
\r
9558 bi = s.body_id || 'tinymce';
\r
9559 if (bi.indexOf('=') != -1) {
\r
9560 bi = t.getParam('body_id', '', 'hash');
\r
9561 bi = bi[t.id] || bi;
\r
9564 bc = s.body_class || '';
\r
9565 if (bc.indexOf('=') != -1) {
\r
9566 bc = t.getParam('body_class', '', 'hash');
\r
9567 bc = bc[t.id] || '';
\r
9570 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
\r
9572 // Domain relaxing enabled, then set document domain
\r
9573 if (tinymce.relaxedDomain) {
\r
9574 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
\r
9575 if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
\r
9576 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
9577 else if (tinymce.isOpera)
\r
9578 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
\r
9582 n = DOM.add(o.iframeContainer, 'iframe', {
\r
9583 id : t.id + "_ifr",
\r
9584 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
\r
9585 frameBorder : '0',
\r
9592 t.contentAreaContainer = o.iframeContainer;
\r
9593 DOM.get(o.editorContainer).style.display = t.orgDisplay;
\r
9594 DOM.get(t.id).style.display = 'none';
\r
9596 if (!isIE || !tinymce.relaxedDomain)
\r
9599 e = n = o = null; // Cleanup
\r
9602 setupIframe : function() {
\r
9603 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
\r
9605 // Setup iframe body
\r
9606 if (!isIE || !tinymce.relaxedDomain) {
\r
9608 d.write(t.iframeHTML);
\r
9612 // Design mode needs to be added here Ctrl+A will fail otherwise
\r
9616 d.designMode = 'On';
\r
9618 // Will fail on Gecko if the editor is placed in an hidden container element
\r
9619 // The design mode will be set ones the editor is focused
\r
9623 // IE needs to use contentEditable or it will display non secure items for HTTPS
\r
9625 // It will not steal focus if we hide it while setting contentEditable
\r
9630 b.contentEditable = true;
\r
9635 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
\r
9636 keep_values : true,
\r
9637 url_converter : t.convertURL,
\r
9638 url_converter_scope : t,
\r
9639 hex_colors : s.force_hex_style_colors,
\r
9640 class_filter : s.class_filter,
\r
9641 update_styles : 1,
\r
9642 fix_ie_paragraphs : 1,
\r
9643 valid_styles : s.valid_styles
\r
9646 t.schema = new tinymce.dom.Schema();
\r
9648 t.serializer = new tinymce.dom.Serializer(extend(s, {
\r
9649 valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
\r
9654 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
\r
9656 t.formatter = new tinymce.Formatter(this);
\r
9658 // Register default formats
\r
9659 t.formatter.register({
\r
9661 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
\r
9662 {selector : 'img,table', styles : {'float' : 'left'}}
\r
9666 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
\r
9667 {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
\r
9668 {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
\r
9672 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
\r
9673 {selector : 'img,table', styles : {'float' : 'right'}}
\r
9677 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
\r
9681 {inline : 'strong'},
\r
9682 {inline : 'span', styles : {fontWeight : 'bold'}},
\r
9688 {inline : 'span', styles : {fontStyle : 'italic'}},
\r
9693 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
\r
9698 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
\r
9702 forecolor : {inline : 'span', styles : {color : '%value'}},
\r
9703 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
\r
9704 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
\r
9705 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
\r
9706 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
\r
9707 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
\r
9710 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
\r
9711 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
\r
9712 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
\r
9716 // Register default block formats
\r
9717 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
\r
9718 t.formatter.register(name, {block : name, remove : 'all'});
\r
9721 // Register user defined formats
\r
9722 t.formatter.register(t.settings.formats);
\r
9724 t.undoManager = new tinymce.UndoManager(t);
\r
9727 t.undoManager.onAdd.add(function(um, l) {
\r
9729 return t.onChange.dispatch(t, l, um);
\r
9732 t.undoManager.onUndo.add(function(um, l) {
\r
9733 return t.onUndo.dispatch(t, l, um);
\r
9736 t.undoManager.onRedo.add(function(um, l) {
\r
9737 return t.onRedo.dispatch(t, l, um);
\r
9740 t.forceBlocks = new tinymce.ForceBlocks(t, {
\r
9741 forced_root_block : s.forced_root_block
\r
9744 t.editorCommands = new tinymce.EditorCommands(t);
\r
9747 t.serializer.onPreProcess.add(function(se, o) {
\r
9748 return t.onPreProcess.dispatch(t, o, se);
\r
9751 t.serializer.onPostProcess.add(function(se, o) {
\r
9752 return t.onPostProcess.dispatch(t, o, se);
\r
9755 t.onPreInit.dispatch(t);
\r
9757 if (!s.gecko_spellcheck)
\r
9758 t.getBody().spellcheck = 0;
\r
9763 t.controlManager.onPostRender.dispatch(t, t.controlManager);
\r
9764 t.onPostRender.dispatch(t);
\r
9766 if (s.directionality)
\r
9767 t.getBody().dir = s.directionality;
\r
9770 t.getBody().style.whiteSpace = "nowrap";
\r
9772 if (s.custom_elements) {
\r
9773 function handleCustom(ed, o) {
\r
9774 each(explode(s.custom_elements), function(v) {
\r
9777 if (v.indexOf('~') === 0) {
\r
9778 v = v.substring(1);
\r
9783 o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
\r
9784 o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
\r
9788 t.onBeforeSetContent.add(handleCustom);
\r
9789 t.onPostProcess.add(function(ed, o) {
\r
9791 handleCustom(ed, o);
\r
9795 if (s.handle_node_change_callback) {
\r
9796 t.onNodeChange.add(function(ed, cm, n) {
\r
9797 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
\r
9801 if (s.save_callback) {
\r
9802 t.onSaveContent.add(function(ed, o) {
\r
9803 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
9810 if (s.onchange_callback) {
\r
9811 t.onChange.add(function(ed, l) {
\r
9812 t.execCallback('onchange_callback', t, l);
\r
9816 if (s.convert_newlines_to_brs) {
\r
9817 t.onBeforeSetContent.add(function(ed, o) {
\r
9819 o.content = o.content.replace(/\r?\n/g, '<br />');
\r
9823 if (s.fix_nesting && isIE) {
\r
9824 t.onBeforeSetContent.add(function(ed, o) {
\r
9825 o.content = t._fixNesting(o.content);
\r
9829 if (s.preformatted) {
\r
9830 t.onPostProcess.add(function(ed, o) {
\r
9831 o.content = o.content.replace(/^\s*<pre.*?>/, '');
\r
9832 o.content = o.content.replace(/<\/pre>\s*$/, '');
\r
9835 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
\r
9839 if (s.verify_css_classes) {
\r
9840 t.serializer.attribValueFilter = function(n, v) {
\r
9843 if (n == 'class') {
\r
9844 // Build regexp for classes
\r
9845 if (!t.classesRE) {
\r
9846 cl = t.dom.getClasses();
\r
9848 if (cl.length > 0) {
\r
9851 each (cl, function(o) {
\r
9852 s += (s ? '|' : '') + o['class'];
\r
9855 t.classesRE = new RegExp('(' + s + ')', 'gi');
\r
9859 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
\r
9866 if (s.cleanup_callback) {
\r
9867 t.onBeforeSetContent.add(function(ed, o) {
\r
9868 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
9871 t.onPreProcess.add(function(ed, o) {
\r
9873 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
\r
9876 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
\r
9879 t.onPostProcess.add(function(ed, o) {
\r
9881 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
9884 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
\r
9888 if (s.save_callback) {
\r
9889 t.onGetContent.add(function(ed, o) {
\r
9891 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
9895 if (s.handle_event_callback) {
\r
9896 t.onEvent.add(function(ed, e, o) {
\r
9897 if (t.execCallback('handle_event_callback', e, ed, o) === false)
\r
9902 // Add visual aids when new contents is added
\r
9903 t.onSetContent.add(function() {
\r
9904 t.addVisual(t.getBody());
\r
9907 // Remove empty contents
\r
9908 if (s.padd_empty_editor) {
\r
9909 t.onPostProcess.add(function(ed, o) {
\r
9910 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
\r
9915 // Fix gecko link bug, when a link is placed at the end of block elements there is
\r
9916 // no way to move the caret behind the link. This fix adds a bogus br element after the link
\r
9917 function fixLinks(ed, o) {
\r
9918 each(ed.dom.select('a'), function(n) {
\r
9919 var pn = n.parentNode;
\r
9921 if (ed.dom.isBlock(pn) && pn.lastChild === n)
\r
9922 ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
\r
9926 t.onExecCommand.add(function(ed, cmd) {
\r
9927 if (cmd === 'CreateLink')
\r
9931 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
\r
9933 if (!s.readonly) {
\r
9935 // Design mode must be set here once again to fix a bug where
\r
9936 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
\r
9937 d.designMode = 'Off';
\r
9938 d.designMode = 'On';
\r
9940 // Will fail on Gecko if the editor is placed in an hidden container element
\r
9941 // The design mode will be set ones the editor is focused
\r
9946 // A small timeout was needed since firefox will remove. Bug: #1838304
\r
9947 setTimeout(function () {
\r
9951 t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
\r
9952 t.startContent = t.getContent({format : 'raw'});
\r
9953 t.initialized = true;
\r
9955 t.onInit.dispatch(t);
\r
9956 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
\r
9957 t.execCallback('init_instance_callback', t);
\r
9959 t.nodeChanged({initial : 1});
\r
9961 // Load specified content CSS last
\r
9962 if (s.content_css) {
\r
9963 tinymce.each(explode(s.content_css), function(u) {
\r
9964 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
\r
9968 // Handle auto focus
\r
9969 if (s.auto_focus) {
\r
9970 setTimeout(function () {
\r
9971 var ed = tinymce.get(s.auto_focus);
\r
9973 ed.selection.select(ed.getBody(), 1);
\r
9974 ed.selection.collapse(1);
\r
9975 ed.getWin().focus();
\r
9984 focus : function(sf) {
\r
9985 var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
\r
9988 // Get selected control element
\r
9989 ieRng = t.selection.getRng();
\r
9991 controlElm = ieRng.item(0);
\r
9994 // Is not content editable
\r
9996 t.getWin().focus();
\r
9998 // Restore selected control element
\r
9999 // This is needed when for example an image is selected within a
\r
10000 // layer a call to focus will then remove the control selection
\r
10001 if (controlElm && controlElm.ownerDocument == doc) {
\r
10002 ieRng = doc.body.createControlRange();
\r
10003 ieRng.addElement(controlElm);
\r
10009 if (tinymce.activeEditor != t) {
\r
10010 if ((oed = tinymce.activeEditor) != null)
\r
10011 oed.onDeactivate.dispatch(oed, t);
\r
10013 t.onActivate.dispatch(t, oed);
\r
10016 tinymce._setActive(t);
\r
10019 execCallback : function(n) {
\r
10020 var t = this, f = t.settings[n], s;
\r
10025 // Look through lookup
\r
10026 if (t.callbackLookup && (s = t.callbackLookup[n])) {
\r
10031 if (is(f, 'string')) {
\r
10032 s = f.replace(/\.\w+$/, '');
\r
10033 s = s ? tinymce.resolve(s) : 0;
\r
10034 f = tinymce.resolve(f);
\r
10035 t.callbackLookup = t.callbackLookup || {};
\r
10036 t.callbackLookup[n] = {func : f, scope : s};
\r
10039 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
\r
10042 translate : function(s) {
\r
10043 var c = this.settings.language || 'en', i18n = tinymce.i18n;
\r
10048 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
\r
10049 return i18n[c + '.' + b] || '{#' + b + '}';
\r
10053 getLang : function(n, dv) {
\r
10054 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
\r
10057 getParam : function(n, dv, ty) {
\r
10058 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
\r
10060 if (ty === 'hash') {
\r
10063 if (is(v, 'string')) {
\r
10064 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
\r
10065 v = v.split('=');
\r
10067 if (v.length > 1)
\r
10068 o[tr(v[0])] = tr(v[1]);
\r
10070 o[tr(v[0])] = tr(v);
\r
10081 nodeChanged : function(o) {
\r
10082 var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody();
\r
10084 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
\r
10085 if (t.initialized) {
\r
10087 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
\r
10089 // Get parents and add them to object
\r
10091 t.dom.getParent(n, function(node) {
\r
10092 if (node.nodeName == 'BODY')
\r
10095 o.parents.push(node);
\r
10098 t.onNodeChange.dispatch(
\r
10100 o ? o.controlManager || t.controlManager : t.controlManager,
\r
10108 addButton : function(n, s) {
\r
10111 t.buttons = t.buttons || {};
\r
10112 t.buttons[n] = s;
\r
10115 addCommand : function(n, f, s) {
\r
10116 this.execCommands[n] = {func : f, scope : s || this};
\r
10119 addQueryStateHandler : function(n, f, s) {
\r
10120 this.queryStateCommands[n] = {func : f, scope : s || this};
\r
10123 addQueryValueHandler : function(n, f, s) {
\r
10124 this.queryValueCommands[n] = {func : f, scope : s || this};
\r
10127 addShortcut : function(pa, desc, cmd_func, sc) {
\r
10130 if (!t.settings.custom_shortcuts)
\r
10133 t.shortcuts = t.shortcuts || {};
\r
10135 if (is(cmd_func, 'string')) {
\r
10138 cmd_func = function() {
\r
10139 t.execCommand(c, false, null);
\r
10143 if (is(cmd_func, 'object')) {
\r
10146 cmd_func = function() {
\r
10147 t.execCommand(c[0], c[1], c[2]);
\r
10151 each(explode(pa), function(pa) {
\r
10154 scope : sc || this,
\r
10161 each(explode(pa, '+'), function(v) {
\r
10170 o.charCode = v.charCodeAt(0);
\r
10171 o.keyCode = v.toUpperCase().charCodeAt(0);
\r
10175 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
\r
10181 execCommand : function(cmd, ui, val, a) {
\r
10182 var t = this, s = 0, o, st;
\r
10184 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
\r
10188 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
\r
10192 // Command callback
\r
10193 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
\r
10194 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10198 // Registred commands
\r
10199 if (o = t.execCommands[cmd]) {
\r
10200 st = o.func.call(o.scope, ui, val);
\r
10202 // Fall through on true
\r
10203 if (st !== true) {
\r
10204 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10209 // Plugin commands
\r
10210 each(t.plugins, function(p) {
\r
10211 if (p.execCommand && p.execCommand(cmd, ui, val)) {
\r
10212 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10221 // Theme commands
\r
10222 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
\r
10223 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10227 // Execute global commands
\r
10228 if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
\r
10229 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10233 // Editor commands
\r
10234 if (t.editorCommands.execCommand(cmd, ui, val)) {
\r
10235 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10239 // Browser commands
\r
10240 t.getDoc().execCommand(cmd, ui, val);
\r
10241 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
10244 queryCommandState : function(cmd) {
\r
10245 var t = this, o, s;
\r
10247 // Is hidden then return undefined
\r
10248 if (t._isHidden())
\r
10251 // Registred commands
\r
10252 if (o = t.queryStateCommands[cmd]) {
\r
10253 s = o.func.call(o.scope);
\r
10255 // Fall though on true
\r
10260 // Registred commands
\r
10261 o = t.editorCommands.queryCommandState(cmd);
\r
10265 // Browser commands
\r
10267 return this.getDoc().queryCommandState(cmd);
\r
10269 // Fails sometimes see bug: 1896577
\r
10273 queryCommandValue : function(c) {
\r
10274 var t = this, o, s;
\r
10276 // Is hidden then return undefined
\r
10277 if (t._isHidden())
\r
10280 // Registred commands
\r
10281 if (o = t.queryValueCommands[c]) {
\r
10282 s = o.func.call(o.scope);
\r
10284 // Fall though on true
\r
10289 // Registred commands
\r
10290 o = t.editorCommands.queryCommandValue(c);
\r
10294 // Browser commands
\r
10296 return this.getDoc().queryCommandValue(c);
\r
10298 // Fails sometimes see bug: 1896577
\r
10302 show : function() {
\r
10305 DOM.show(t.getContainer());
\r
10310 hide : function() {
\r
10311 var t = this, d = t.getDoc();
\r
10313 // Fixed bug where IE has a blinking cursor left from the editor
\r
10315 d.execCommand('SelectAll');
\r
10317 // We must save before we hide so Safari doesn't crash
\r
10319 DOM.hide(t.getContainer());
\r
10320 DOM.setStyle(t.id, 'display', t.orgDisplay);
\r
10323 isHidden : function() {
\r
10324 return !DOM.isHidden(this.id);
\r
10327 setProgressState : function(b, ti, o) {
\r
10328 this.onSetProgressState.dispatch(this, b, ti, o);
\r
10333 load : function(o) {
\r
10334 var t = this, e = t.getElement(), h;
\r
10340 // Double encode existing entities in the value
\r
10341 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
\r
10344 if (!o.no_events)
\r
10345 t.onLoadContent.dispatch(t, o);
\r
10347 o.element = e = null;
\r
10353 save : function(o) {
\r
10354 var t = this, e = t.getElement(), h, f;
\r
10356 if (!e || !t.initialized)
\r
10362 // Add undo level will trigger onchange event
\r
10363 if (!o.no_events) {
\r
10364 t.undoManager.typing = 0;
\r
10365 t.undoManager.add();
\r
10369 h = o.content = t.getContent(o);
\r
10371 if (!o.no_events)
\r
10372 t.onSaveContent.dispatch(t, o);
\r
10376 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
\r
10379 // Update hidden form element
\r
10380 if (f = DOM.getParent(t.id, 'form')) {
\r
10381 each(f.elements, function(e) {
\r
10382 if (e.name == t.id) {
\r
10391 o.element = e = null;
\r
10396 setContent : function(h, o) {
\r
10400 o.format = o.format || 'html';
\r
10404 if (!o.no_events)
\r
10405 t.onBeforeSetContent.dispatch(t, o);
\r
10407 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
\r
10408 // It will also be impossible to place the caret in the editor unless there is a BR element present
\r
10409 if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
\r
10410 o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
\r
10411 o.format = 'raw';
\r
10414 o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
\r
10416 if (o.format != 'raw' && t.settings.cleanup) {
\r
10417 o.getInner = true;
\r
10418 o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
\r
10421 if (!o.no_events)
\r
10422 t.onSetContent.dispatch(t, o);
\r
10424 return o.content;
\r
10427 getContent : function(o) {
\r
10431 o.format = o.format || 'html';
\r
10434 if (!o.no_events)
\r
10435 t.onBeforeGetContent.dispatch(t, o);
\r
10437 if (o.format != 'raw' && t.settings.cleanup) {
\r
10438 o.getInner = true;
\r
10439 h = t.serializer.serialize(t.getBody(), o);
\r
10441 h = t.getBody().innerHTML;
\r
10443 h = h.replace(/^\s*|\s*$/g, '');
\r
10446 if (!o.no_events)
\r
10447 t.onGetContent.dispatch(t, o);
\r
10449 return o.content;
\r
10452 isDirty : function() {
\r
10455 return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
\r
10458 getContainer : function() {
\r
10461 if (!t.container)
\r
10462 t.container = DOM.get(t.editorContainer || t.id + '_parent');
\r
10464 return t.container;
\r
10467 getContentAreaContainer : function() {
\r
10468 return this.contentAreaContainer;
\r
10471 getElement : function() {
\r
10472 return DOM.get(this.settings.content_element || this.id);
\r
10475 getWin : function() {
\r
10478 if (!t.contentWindow) {
\r
10479 e = DOM.get(t.id + "_ifr");
\r
10482 t.contentWindow = e.contentWindow;
\r
10485 return t.contentWindow;
\r
10488 getDoc : function() {
\r
10491 if (!t.contentDocument) {
\r
10495 t.contentDocument = w.document;
\r
10498 return t.contentDocument;
\r
10501 getBody : function() {
\r
10502 return this.bodyElement || this.getDoc().body;
\r
10505 convertURL : function(u, n, e) {
\r
10506 var t = this, s = t.settings;
\r
10508 // Use callback instead
\r
10509 if (s.urlconverter_callback)
\r
10510 return t.execCallback('urlconverter_callback', u, e, true, n);
\r
10512 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
\r
10513 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
\r
10516 // Convert to relative
\r
10517 if (s.relative_urls)
\r
10518 return t.documentBaseURI.toRelative(u);
\r
10520 // Convert to absolute
\r
10521 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
\r
10526 addVisual : function(e) {
\r
10527 var t = this, s = t.settings;
\r
10529 e = e || t.getBody();
\r
10531 if (!is(t.hasVisual))
\r
10532 t.hasVisual = s.visual;
\r
10534 each(t.dom.select('table,a', e), function(e) {
\r
10537 switch (e.nodeName) {
\r
10539 v = t.dom.getAttrib(e, 'border');
\r
10541 if (!v || v == '0') {
\r
10543 t.dom.addClass(e, s.visual_table_class);
\r
10545 t.dom.removeClass(e, s.visual_table_class);
\r
10551 v = t.dom.getAttrib(e, 'name');
\r
10555 t.dom.addClass(e, 'mceItemAnchor');
\r
10557 t.dom.removeClass(e, 'mceItemAnchor');
\r
10564 t.onVisualAid.dispatch(t, e, t.hasVisual);
\r
10567 remove : function() {
\r
10568 var t = this, e = t.getContainer();
\r
10570 t.removed = 1; // Cancels post remove event execution
\r
10573 t.execCallback('remove_instance_callback', t);
\r
10574 t.onRemove.dispatch(t);
\r
10576 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
\r
10577 t.onExecCommand.listeners = [];
\r
10579 tinymce.remove(t);
\r
10583 destroy : function(s) {
\r
10586 // One time is enough
\r
10591 tinymce.removeUnload(t.destroy);
\r
10592 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
\r
10594 // Manual destroy
\r
10595 if (t.theme && t.theme.destroy)
\r
10596 t.theme.destroy();
\r
10598 // Destroy controls, selection and dom
\r
10599 t.controlManager.destroy();
\r
10600 t.selection.destroy();
\r
10603 // Remove all events
\r
10605 // Don't clear the window or document if content editable
\r
10606 // is enabled since other instances might still be present
\r
10607 if (!t.settings.content_editable) {
\r
10608 Event.clear(t.getWin());
\r
10609 Event.clear(t.getDoc());
\r
10612 Event.clear(t.getBody());
\r
10613 Event.clear(t.formElement);
\r
10616 if (t.formElement) {
\r
10617 t.formElement.submit = t.formElement._mceOldSubmit;
\r
10618 t.formElement._mceOldSubmit = null;
\r
10621 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
\r
10624 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
\r
10629 // Internal functions
\r
10631 _addEvents : function() {
\r
10632 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
\r
10633 var t = this, i, s = t.settings, lo = {
\r
10634 mouseup : 'onMouseUp',
\r
10635 mousedown : 'onMouseDown',
\r
10636 click : 'onClick',
\r
10637 keyup : 'onKeyUp',
\r
10638 keydown : 'onKeyDown',
\r
10639 keypress : 'onKeyPress',
\r
10640 submit : 'onSubmit',
\r
10641 reset : 'onReset',
\r
10642 contextmenu : 'onContextMenu',
\r
10643 dblclick : 'onDblClick',
\r
10644 paste : 'onPaste' // Doesn't work in all browsers yet
\r
10647 function eventHandler(e, o) {
\r
10650 // Don't fire events when it's removed
\r
10654 // Generic event handler
\r
10655 if (t.onEvent.dispatch(t, e, o) !== false) {
\r
10656 // Specific event handler
\r
10657 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
\r
10661 // Add DOM events
\r
10662 each(lo, function(v, k) {
\r
10664 case 'contextmenu':
\r
10665 if (tinymce.isOpera) {
\r
10666 // Fake contextmenu on Opera
\r
10667 t.dom.bind(t.getBody(), 'mousedown', function(e) {
\r
10669 e.fakeType = 'contextmenu';
\r
10674 t.dom.bind(t.getBody(), k, eventHandler);
\r
10678 t.dom.bind(t.getBody(), k, function(e) {
\r
10685 t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
\r
10689 t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
\r
10693 t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
\r
10698 // Fixes bug where a specified document_base_uri could result in broken images
\r
10699 // This will also fix drag drop of images in Gecko
\r
10700 if (tinymce.isGecko) {
\r
10701 // Convert all images to absolute URLs
\r
10702 /* t.onSetContent.add(function(ed, o) {
\r
10703 each(ed.dom.select('img'), function(e) {
\r
10706 if (v = e.getAttribute('_mce_src'))
\r
10707 e.src = t.documentBaseURI.toAbsolute(v);
\r
10711 t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
\r
10716 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
\r
10717 e.src = t.documentBaseURI.toAbsolute(v);
\r
10721 // Set various midas options in Gecko
\r
10723 function setOpts() {
\r
10724 var t = this, d = t.getDoc(), s = t.settings;
\r
10726 if (isGecko && !s.readonly) {
\r
10727 if (t._isHidden()) {
\r
10729 if (!s.content_editable)
\r
10730 d.designMode = 'On';
\r
10732 // Fails if it's hidden
\r
10737 // Try new Gecko method
\r
10738 d.execCommand("styleWithCSS", 0, false);
\r
10740 // Use old method
\r
10741 if (!t._isHidden())
\r
10742 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
\r
10745 if (!s.table_inline_editing)
\r
10746 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
\r
10748 if (!s.object_resizing)
\r
10749 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
\r
10753 t.onBeforeExecCommand.add(setOpts);
\r
10754 t.onMouseDown.add(setOpts);
\r
10757 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
\r
10758 // WebKit can't even do simple things like selecting an image
\r
10759 // This also fixes so it's possible to select mceItemAnchors
\r
10760 if (tinymce.isWebKit) {
\r
10761 t.onClick.add(function(ed, e) {
\r
10764 // Needs tobe the setBaseAndExtend or it will fail to select floated images
\r
10765 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor')))
\r
10766 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
\r
10770 // Add node change handlers
\r
10771 t.onMouseUp.add(t.nodeChanged);
\r
10772 //t.onClick.add(t.nodeChanged);
\r
10773 t.onKeyUp.add(function(ed, e) {
\r
10774 var c = e.keyCode;
\r
10776 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
10780 // Add reset handler
\r
10781 t.onReset.add(function() {
\r
10782 t.setContent(t.startContent, {format : 'raw'});
\r
10786 if (s.custom_shortcuts) {
\r
10787 if (s.custom_undo_redo_keyboard_shortcuts) {
\r
10788 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
\r
10789 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
\r
10792 // Add default shortcuts for gecko
\r
10793 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
\r
10794 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
\r
10795 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
\r
10797 // BlockFormat shortcuts keys
\r
10798 for (i=1; i<=6; i++)
\r
10799 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
\r
10801 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
\r
10802 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
\r
10803 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
\r
10805 function find(e) {
\r
10808 if (!e.altKey && !e.ctrlKey && !e.metaKey)
\r
10811 each(t.shortcuts, function(o) {
\r
10812 if (tinymce.isMac && o.ctrl != e.metaKey)
\r
10814 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
\r
10817 if (o.alt != e.altKey)
\r
10820 if (o.shift != e.shiftKey)
\r
10823 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
\r
10832 t.onKeyUp.add(function(ed, e) {
\r
10836 return Event.cancel(e);
\r
10839 t.onKeyPress.add(function(ed, e) {
\r
10843 return Event.cancel(e);
\r
10846 t.onKeyDown.add(function(ed, e) {
\r
10850 o.func.call(o.scope);
\r
10851 return Event.cancel(e);
\r
10856 if (tinymce.isIE) {
\r
10857 // Fix so resize will only update the width and height attributes not the styles of an image
\r
10858 // It will also block mceItemNoResize items
\r
10859 t.dom.bind(t.getDoc(), 'controlselect', function(e) {
\r
10860 var re = t.resizeInfo, cb;
\r
10864 // Don't do this action for non image elements
\r
10865 if (e.nodeName !== 'IMG')
\r
10869 t.dom.unbind(re.node, re.ev, re.cb);
\r
10871 if (!t.dom.hasClass(e, 'mceItemNoResize')) {
\r
10872 ev = 'resizeend';
\r
10873 cb = t.dom.bind(e, ev, function(e) {
\r
10878 if (v = t.dom.getStyle(e, 'width')) {
\r
10879 t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
\r
10880 t.dom.setStyle(e, 'width', '');
\r
10883 if (v = t.dom.getStyle(e, 'height')) {
\r
10884 t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
\r
10885 t.dom.setStyle(e, 'height', '');
\r
10889 ev = 'resizestart';
\r
10890 cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
\r
10893 re = t.resizeInfo = {
\r
10900 t.onKeyDown.add(function(ed, e) {
\r
10901 switch (e.keyCode) {
\r
10903 // Fix IE control + backspace browser bug
\r
10904 if (t.selection.getRng().item) {
\r
10905 ed.dom.remove(t.selection.getRng().item(0));
\r
10906 return Event.cancel(e);
\r
10911 /*if (t.dom.boxModel) {
\r
10912 t.getBody().style.height = '100%';
\r
10914 Event.add(t.getWin(), 'resize', function(e) {
\r
10915 var docElm = t.getDoc().documentElement;
\r
10917 docElm.style.height = (docElm.offsetHeight - 10) + 'px';
\r
10922 if (tinymce.isOpera) {
\r
10923 t.onClick.add(function(ed, e) {
\r
10924 Event.prevent(e);
\r
10928 // Add custom undo/redo handlers
\r
10929 if (s.custom_undo_redo) {
\r
10930 function addUndo() {
\r
10931 t.undoManager.typing = 0;
\r
10932 t.undoManager.add();
\r
10935 t.dom.bind(t.getDoc(), 'focusout', function(e) {
\r
10936 if (!t.removed && t.undoManager.typing)
\r
10940 t.onKeyUp.add(function(ed, e) {
\r
10941 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
\r
10945 t.onKeyDown.add(function(ed, e) {
\r
10946 var rng, tmpRng, parent, offset;
\r
10948 // IE has a really odd bug where the DOM might include an node that doesn't have
\r
10949 // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
\r
10950 // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
\r
10951 // after you delete contents from it. See: #3008923
\r
10952 if (isIE && e.keyCode == 46) {
\r
10953 rng = t.selection.getRng();
\r
10955 if (rng.parentElement) {
\r
10956 parent = rng.parentElement();
\r
10958 // Get the current caret position within the element
\r
10959 tmpRng = rng.duplicate();
\r
10960 tmpRng.moveToElementText(parent);
\r
10961 tmpRng.setEndPoint('EndToEnd', rng);
\r
10962 offset = tmpRng.text.length;
\r
10964 // Select next word when ctrl key is used in combo with delete
\r
10966 rng.moveEnd('word', 1);
\r
10970 // Delete contents
\r
10971 t.selection.getSel().clear();
\r
10973 // Check if we are within the same parent
\r
10974 if (rng.parentElement() == parent) {
\r
10976 // Update the HTML and hopefully it will remove the artifacts
\r
10977 parent.innerHTML = parent.innerHTML;
\r
10979 // And since it's IE it can sometimes produce an unknown runtime error
\r
10982 // Restore the caret position
\r
10983 tmpRng.moveToElementText(parent);
\r
10984 tmpRng.collapse();
\r
10985 tmpRng.move('character', offset);
\r
10989 // Block the default delete behavior since it might be broken
\r
10990 e.preventDefault();
\r
10995 // Is caracter positon keys
\r
10996 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
\r
10997 if (t.undoManager.typing)
\r
11003 if (!t.undoManager.typing) {
\r
11004 t.undoManager.add();
\r
11005 t.undoManager.typing = 1;
\r
11009 t.onMouseDown.add(function() {
\r
11010 if (t.undoManager.typing)
\r
11016 _isHidden : function() {
\r
11022 // Weird, wheres that cursor selection?
\r
11023 s = this.selection.getSel();
\r
11024 return (!s || !s.rangeCount || s.rangeCount == 0);
\r
11027 // Fix for bug #1867292
\r
11028 _fixNesting : function(s) {
\r
11031 s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
\r
11034 // Handle end element
\r
11039 if (c !== d[d.length - 1].tag) {
\r
11040 for (i=d.length - 1; i>=0; i--) {
\r
11041 if (d[i].tag === c) {
\r
11051 if (d.length && d[d.length - 1].close) {
\r
11052 a = a + '</' + d[d.length - 1].tag + '>';
\r
11058 if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
\r
11061 // Ignore closed ones
\r
11062 if (/\/>$/.test(a))
\r
11065 d.push({tag : c}); // Push start element
\r
11071 // End all open tags
\r
11072 for (i=d.length - 1; i>=0; i--)
\r
11073 s += '</' + d[i].tag + '>';
\r
11080 (function(tinymce) {
\r
11081 // Added for compression purposes
\r
11082 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
\r
11084 tinymce.EditorCommands = function(editor) {
\r
11085 var dom = editor.dom,
\r
11086 selection = editor.selection,
\r
11087 commands = {state: {}, exec : {}, value : {}},
\r
11088 settings = editor.settings,
\r
11091 function execCommand(command, ui, value) {
\r
11094 command = command.toLowerCase();
\r
11095 if (func = commands.exec[command]) {
\r
11096 func(command, ui, value);
\r
11103 function queryCommandState(command) {
\r
11106 command = command.toLowerCase();
\r
11107 if (func = commands.state[command])
\r
11108 return func(command);
\r
11113 function queryCommandValue(command) {
\r
11116 command = command.toLowerCase();
\r
11117 if (func = commands.value[command])
\r
11118 return func(command);
\r
11123 function addCommands(command_list, type) {
\r
11124 type = type || 'exec';
\r
11126 each(command_list, function(callback, command) {
\r
11127 each(command.toLowerCase().split(','), function(command) {
\r
11128 commands[type][command] = callback;
\r
11133 // Expose public methods
\r
11134 tinymce.extend(this, {
\r
11135 execCommand : execCommand,
\r
11136 queryCommandState : queryCommandState,
\r
11137 queryCommandValue : queryCommandValue,
\r
11138 addCommands : addCommands
\r
11141 // Private methods
\r
11143 function execNativeCommand(command, ui, value) {
\r
11144 if (ui === undefined)
\r
11147 if (value === undefined)
\r
11150 return editor.getDoc().execCommand(command, ui, value);
\r
11153 function isFormatMatch(name) {
\r
11154 return editor.formatter.match(name);
\r
11157 function toggleFormat(name, value) {
\r
11158 editor.formatter.toggle(name, value ? {value : value} : undefined);
\r
11161 function storeSelection(type) {
\r
11162 bookmark = selection.getBookmark(type);
\r
11165 function restoreSelection() {
\r
11166 selection.moveToBookmark(bookmark);
\r
11169 // Add execCommand overrides
\r
11171 // Ignore these, added for compatibility
\r
11172 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
\r
11174 // Add undo manager logic
\r
11175 'mceEndUndoLevel,mceAddUndoLevel' : function() {
\r
11176 editor.undoManager.add();
\r
11179 'Cut,Copy,Paste' : function(command) {
\r
11180 var doc = editor.getDoc(), failed;
\r
11182 // Try executing the native command
\r
11184 execNativeCommand(command);
\r
11186 // Command failed
\r
11190 // Present alert message about clipboard access not being available
\r
11191 if (failed || !doc.queryCommandSupported(command)) {
\r
11192 if (tinymce.isGecko) {
\r
11193 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
\r
11195 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
\r
11198 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
\r
11202 // Override unlink command
\r
11203 unlink : function(command) {
\r
11204 if (selection.isCollapsed())
\r
11205 selection.select(selection.getNode());
\r
11207 execNativeCommand(command);
\r
11208 selection.collapse(FALSE);
\r
11211 // Override justify commands to use the text formatter engine
\r
11212 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
11213 var align = command.substring(7);
\r
11215 // Remove all other alignments first
\r
11216 each('left,center,right,full'.split(','), function(name) {
\r
11217 if (align != name)
\r
11218 editor.formatter.remove('align' + name);
\r
11221 toggleFormat('align' + align);
\r
11224 // Override list commands to fix WebKit bug
\r
11225 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
11226 var listElm, listParent;
\r
11228 execNativeCommand(command);
\r
11230 // WebKit produces lists within block elements so we need to split them
\r
11231 // we will replace the native list creation logic to custom logic later on
\r
11232 // TODO: Remove this when the list creation logic is removed
\r
11233 listElm = dom.getParent(selection.getNode(), 'ol,ul');
\r
11235 listParent = listElm.parentNode;
\r
11237 // If list is within a text block then split that block
\r
11238 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
\r
11239 storeSelection();
\r
11240 dom.split(listParent, listElm);
\r
11241 restoreSelection();
\r
11246 // Override commands to use the text formatter engine
\r
11247 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
11248 toggleFormat(command);
\r
11251 // Override commands to use the text formatter engine
\r
11252 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
\r
11253 toggleFormat(command, value);
\r
11256 FontSize : function(command, ui, value) {
\r
11257 var fontClasses, fontSizes;
\r
11259 // Convert font size 1-7 to styles
\r
11260 if (value >= 1 && value <= 7) {
\r
11261 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
11262 fontClasses = tinymce.explode(settings.font_size_classes);
\r
11265 value = fontClasses[value - 1] || value;
\r
11267 value = fontSizes[value - 1] || value;
\r
11270 toggleFormat(command, value);
\r
11273 RemoveFormat : function(command) {
\r
11274 editor.formatter.remove(command);
\r
11277 mceBlockQuote : function(command) {
\r
11278 toggleFormat('blockquote');
\r
11281 FormatBlock : function(command, ui, value) {
\r
11282 return toggleFormat(value);
\r
11285 mceCleanup : function() {
\r
11286 var bookmark = selection.getBookmark();
\r
11288 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
\r
11290 selection.moveToBookmark(bookmark);
\r
11293 mceRemoveNode : function(command, ui, value) {
\r
11294 var node = value || selection.getNode();
\r
11296 // Make sure that the body node isn't removed
\r
11297 if (node != editor.getBody()) {
\r
11298 storeSelection();
\r
11299 editor.dom.remove(node, TRUE);
\r
11300 restoreSelection();
\r
11304 mceSelectNodeDepth : function(command, ui, value) {
\r
11307 dom.getParent(selection.getNode(), function(node) {
\r
11308 if (node.nodeType == 1 && counter++ == value) {
\r
11309 selection.select(node);
\r
11312 }, editor.getBody());
\r
11315 mceSelectNode : function(command, ui, value) {
\r
11316 selection.select(value);
\r
11319 mceInsertContent : function(command, ui, value) {
\r
11320 selection.setContent(value);
\r
11323 mceInsertRawHTML : function(command, ui, value) {
\r
11324 selection.setContent('tiny_mce_marker');
\r
11325 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value));
\r
11328 mceSetContent : function(command, ui, value) {
\r
11329 editor.setContent(value);
\r
11332 'Indent,Outdent' : function(command) {
\r
11333 var intentValue, indentUnit, value;
\r
11335 // Setup indent level
\r
11336 intentValue = settings.indentation;
\r
11337 indentUnit = /[a-z%]+$/i.exec(intentValue);
\r
11338 intentValue = parseInt(intentValue);
\r
11340 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
\r
11341 each(selection.getSelectedBlocks(), function(element) {
\r
11342 if (command == 'outdent') {
\r
11343 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
\r
11344 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
\r
11346 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
\r
11349 execNativeCommand(command);
\r
11352 mceRepaint : function() {
\r
11355 if (tinymce.isGecko) {
\r
11357 storeSelection(TRUE);
\r
11359 if (selection.getSel())
\r
11360 selection.getSel().selectAllChildren(editor.getBody());
\r
11362 selection.collapse(TRUE);
\r
11363 restoreSelection();
\r
11370 mceToggleFormat : function(command, ui, value) {
\r
11371 editor.formatter.toggle(value);
\r
11374 InsertHorizontalRule : function() {
\r
11375 selection.setContent('<hr />');
\r
11378 mceToggleVisualAid : function() {
\r
11379 editor.hasVisual = !editor.hasVisual;
\r
11380 editor.addVisual();
\r
11383 mceReplaceContent : function(command, ui, value) {
\r
11384 selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
\r
11387 mceInsertLink : function(command, ui, value) {
\r
11388 var link = dom.getParent(selection.getNode(), 'a');
\r
11390 if (tinymce.is(value, 'string'))
\r
11391 value = {href : value};
\r
11394 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
\r
11395 each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
\r
11396 dom.setAttribs(link, value);
\r
11400 dom.setAttribs(link, value);
\r
11402 editor.dom.remove(link, TRUE);
\r
11406 selectAll : function() {
\r
11407 var root = dom.getRoot();
\r
11408 var rng = dom.createRng();
\r
11409 rng.setStart(root, 0);
\r
11410 rng.setEnd(root, root.childNodes.length);
\r
11411 editor.selection.setRng(rng);
\r
11415 // Add queryCommandState overrides
\r
11417 // Override justify commands
\r
11418 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
11419 return isFormatMatch('align' + command.substring(7));
\r
11422 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
11423 return isFormatMatch(command);
\r
11426 mceBlockQuote : function() {
\r
11427 return isFormatMatch('blockquote');
\r
11430 Outdent : function() {
\r
11433 if (settings.inline_styles) {
\r
11434 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
11437 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
11441 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
\r
11444 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
11445 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
\r
11449 // Add queryCommandValue overrides
\r
11451 'FontSize,FontName' : function(command) {
\r
11452 var value = 0, parent;
\r
11454 if (parent = dom.getParent(selection.getNode(), 'span')) {
\r
11455 if (command == 'fontsize')
\r
11456 value = parent.style.fontSize;
\r
11458 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
\r
11465 // Add undo manager logic
\r
11466 if (settings.custom_undo_redo) {
\r
11468 Undo : function() {
\r
11469 editor.undoManager.undo();
\r
11472 Redo : function() {
\r
11473 editor.undoManager.redo();
\r
11479 (function(tinymce) {
\r
11480 var Dispatcher = tinymce.util.Dispatcher;
\r
11482 tinymce.UndoManager = function(editor) {
\r
11483 var self, index = 0, data = [];
\r
11485 function getContent() {
\r
11486 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
\r
11492 onAdd : new Dispatcher(self),
\r
11493 onUndo : new Dispatcher(self),
\r
11494 onRedo : new Dispatcher(self),
\r
11496 add : function(level) {
\r
11497 var i, settings = editor.settings, lastLevel;
\r
11499 level = level || {};
\r
11500 level.content = getContent();
\r
11502 // Add undo level if needed
\r
11503 lastLevel = data[index];
\r
11504 if (lastLevel && lastLevel.content == level.content) {
\r
11505 if (index > 0 || data.length == 1)
\r
11509 // Time to compress
\r
11510 if (settings.custom_undo_redo_levels) {
\r
11511 if (data.length > settings.custom_undo_redo_levels) {
\r
11512 for (i = 0; i < data.length - 1; i++)
\r
11513 data[i] = data[i + 1];
\r
11516 index = data.length;
\r
11520 // Get a non intrusive normalized bookmark
\r
11521 level.bookmark = editor.selection.getBookmark(2, true);
\r
11523 // Crop array if needed
\r
11524 if (index < data.length - 1) {
\r
11525 // Treat first level as initial
\r
11529 data.length = index + 1;
\r
11532 data.push(level);
\r
11533 index = data.length - 1;
\r
11535 self.onAdd.dispatch(self, level);
\r
11536 editor.isNotDirty = 0;
\r
11541 undo : function() {
\r
11544 if (self.typing) {
\r
11550 level = data[--index];
\r
11552 editor.setContent(level.content, {format : 'raw'});
\r
11553 editor.selection.moveToBookmark(level.bookmark);
\r
11555 self.onUndo.dispatch(self, level);
\r
11561 redo : function() {
\r
11564 if (index < data.length - 1) {
\r
11565 level = data[++index];
\r
11567 editor.setContent(level.content, {format : 'raw'});
\r
11568 editor.selection.moveToBookmark(level.bookmark);
\r
11570 self.onRedo.dispatch(self, level);
\r
11576 clear : function() {
\r
11578 index = self.typing = 0;
\r
11581 hasUndo : function() {
\r
11582 return index > 0 || self.typing;
\r
11585 hasRedo : function() {
\r
11586 return index < data.length - 1;
\r
11592 (function(tinymce) {
\r
11594 var Event = tinymce.dom.Event,
\r
11595 isIE = tinymce.isIE,
\r
11596 isGecko = tinymce.isGecko,
\r
11597 isOpera = tinymce.isOpera,
\r
11598 each = tinymce.each,
\r
11599 extend = tinymce.extend,
\r
11603 function cloneFormats(node) {
\r
11604 var clone, temp, inner;
\r
11607 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
\r
11609 temp = node.cloneNode(false);
\r
11610 temp.appendChild(clone);
\r
11613 clone = inner = node.cloneNode(false);
\r
11616 clone.removeAttribute('id');
\r
11618 } while (node = node.parentNode);
\r
11621 return {wrapper : clone, inner : inner};
\r
11624 // Checks if the selection/caret is at the end of the specified block element
\r
11625 function isAtEnd(rng, par) {
\r
11626 var rng2 = par.ownerDocument.createRange();
\r
11628 rng2.setStart(rng.endContainer, rng.endOffset);
\r
11629 rng2.setEndAfter(par);
\r
11631 // 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
11632 return rng2.cloneContents().textContent.length == 0;
\r
11635 function isEmpty(n) {
\r
11638 n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
\r
11639 n = n.replace(/<[^>]+>/g, ''); // Remove all tags
\r
11641 return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
\r
11644 function splitList(selection, dom, li) {
\r
11645 var listBlock, block;
\r
11647 if (isEmpty(li)) {
\r
11648 listBlock = dom.getParent(li, 'ul,ol');
\r
11650 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
\r
11651 dom.split(listBlock, li);
\r
11652 block = dom.create('p', 0, '<br _mce_bogus="1" />');
\r
11653 dom.replace(block, li);
\r
11654 selection.select(block, 1);
\r
11663 tinymce.create('tinymce.ForceBlocks', {
\r
11664 ForceBlocks : function(ed) {
\r
11665 var t = this, s = ed.settings, elm;
\r
11669 elm = (s.forced_root_block || 'p').toLowerCase();
\r
11670 s.element = elm.toUpperCase();
\r
11672 ed.onPreInit.add(t.setup, t);
\r
11674 t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi');
\r
11675 t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11676 t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11677 t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi');
\r
11678 t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
\r
11680 function padd(ed, o) {
\r
11682 o.content = o.content.replace(t.reOpera, '</' + elm + '>');
\r
11684 o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
\r
11686 if (!isIE && !isOpera && o.set) {
\r
11687 // Use instead of BR in padded paragraphs
\r
11688 o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
\r
11689 o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
\r
11691 o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
\r
11694 ed.onBeforeSetContent.add(padd);
\r
11695 ed.onPostProcess.add(padd);
\r
11697 if (s.forced_root_block) {
\r
11698 ed.onInit.add(t.forceRoots, t);
\r
11699 ed.onSetContent.add(t.forceRoots, t);
\r
11700 ed.onBeforeGetContent.add(t.forceRoots, t);
\r
11704 setup : function() {
\r
11705 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
\r
11707 // Force root blocks when typing and when getting output
\r
11708 if (s.forced_root_block) {
\r
11709 ed.onBeforeExecCommand.add(t.forceRoots, t);
\r
11710 ed.onKeyUp.add(t.forceRoots, t);
\r
11711 ed.onPreProcess.add(t.forceRoots, t);
\r
11714 if (s.force_br_newlines) {
\r
11715 // Force IE to produce BRs on enter
\r
11717 ed.onKeyPress.add(function(ed, e) {
\r
11720 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
\r
11721 selection.setContent('<br id="__" /> ', {format : 'raw'});
\r
11722 n = dom.get('__');
\r
11723 n.removeAttribute('id');
\r
11724 selection.select(n);
\r
11725 selection.collapse();
\r
11726 return Event.cancel(e);
\r
11732 if (s.force_p_newlines) {
\r
11734 ed.onKeyPress.add(function(ed, e) {
\r
11735 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
\r
11739 // Ungly hack to for IE to preserve the formatting when you press
\r
11740 // enter at the end of a block element with formatted contents
\r
11741 // This logic overrides the browsers default logic with
\r
11742 // custom logic that enables us to control the output
\r
11743 tinymce.addUnload(function() {
\r
11744 t._previousFormats = 0; // Fix IE leak
\r
11747 ed.onKeyPress.add(function(ed, e) {
\r
11748 t._previousFormats = 0;
\r
11750 // Clone the current formats, this will later be applied to the new block contents
\r
11751 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
\r
11752 t._previousFormats = cloneFormats(ed.selection.getStart());
\r
11755 ed.onKeyUp.add(function(ed, e) {
\r
11756 // Let IE break the element and the wrap the new caret location in the previous formats
\r
11757 if (e.keyCode == 13 && !e.shiftKey) {
\r
11758 var parent = ed.selection.getStart(), fmt = t._previousFormats;
\r
11760 // Parent is an empty block
\r
11761 if (!parent.hasChildNodes()) {
\r
11762 parent = dom.getParent(parent, dom.isBlock);
\r
11765 parent.innerHTML = '';
\r
11767 if (t._previousFormats) {
\r
11768 parent.appendChild(fmt.wrapper);
\r
11769 fmt.inner.innerHTML = '\uFEFF';
\r
11771 parent.innerHTML = '\uFEFF';
\r
11773 selection.select(parent, 1);
\r
11774 ed.getDoc().execCommand('Delete', false, null);
\r
11782 ed.onKeyDown.add(function(ed, e) {
\r
11783 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
\r
11784 t.backspaceDelete(e, e.keyCode == 8);
\r
11789 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
\r
11790 if (tinymce.isWebKit) {
\r
11791 function insertBr(ed) {
\r
11792 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
\r
11794 // Insert BR element
\r
11795 rng.insertNode(br = dom.create('br'));
\r
11797 // Place caret after BR
\r
11798 rng.setStartAfter(br);
\r
11799 rng.setEndAfter(br);
\r
11800 selection.setRng(rng);
\r
11802 // Could not place caret after BR then insert an nbsp entity and move the caret
\r
11803 if (selection.getSel().focusNode == br.previousSibling) {
\r
11804 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
\r
11805 selection.collapse(TRUE);
\r
11808 // Create a temporary DIV after the BR and get the position as it
\r
11809 // seems like getPos() returns 0 for text nodes and BR elements.
\r
11810 dom.insertAfter(div, br);
\r
11811 divYPos = dom.getPos(div).y;
\r
11814 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
\r
11815 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
\r
11816 ed.getWin().scrollTo(0, divYPos);
\r
11819 ed.onKeyPress.add(function(ed, e) {
\r
11820 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
\r
11827 // Padd empty inline elements within block elements
\r
11828 // For example: <p><strong><em></em></strong></p> becomes <p><strong><em> </em></strong></p>
\r
11829 ed.onPreProcess.add(function(ed, o) {
\r
11830 each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
\r
11831 if (isEmpty(p)) {
\r
11832 each(dom.select('span,em,strong,b,i', o.node), function(n) {
\r
11833 if (!n.hasChildNodes()) {
\r
11834 n.appendChild(ed.getDoc().createTextNode('\u00a0'));
\r
11835 return FALSE; // Break the loop one padding is enough
\r
11842 // IE specific fixes
\r
11844 // Replaces IE:s auto generated paragraphs with the specified element name
\r
11845 if (s.element != 'P') {
\r
11846 ed.onKeyPress.add(function(ed, e) {
\r
11847 t.lastElm = selection.getNode().nodeName;
\r
11850 ed.onKeyUp.add(function(ed, e) {
\r
11851 var bl, n = selection.getNode(), b = ed.getBody();
\r
11853 if (b.childNodes.length === 1 && n.nodeName == 'P') {
\r
11854 n = dom.rename(n, s.element);
\r
11855 selection.select(n);
\r
11856 selection.collapse();
\r
11857 ed.nodeChanged();
\r
11858 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
\r
11859 bl = dom.getParent(n, 'p');
\r
11862 dom.rename(bl, s.element);
\r
11863 ed.nodeChanged();
\r
11871 find : function(n, t, s) {
\r
11872 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
\r
11874 while (n = w.nextNode()) {
\r
11878 if (t == 0 && n == s)
\r
11882 if (t == 1 && c == s)
\r
11889 forceRoots : function(ed, e) {
\r
11890 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
11891 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
\r
11893 // Fix for bug #1863847
\r
11894 //if (e && e.keyCode == 13)
\r
11897 // Wrap non blocks into blocks
\r
11898 for (i = nl.length - 1; i >= 0; i--) {
\r
11901 // Ignore internal elements
\r
11902 if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
\r
11907 // Is text or non block element
\r
11908 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
\r
11910 // Create new block but ignore whitespace
\r
11911 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
\r
11912 // Store selection
\r
11913 if (si == -2 && r) {
\r
11915 // If selection is element then mark it
\r
11916 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
\r
11917 // Save the id of the selected element
\r
11918 eid = n.getAttribute("id");
\r
11919 n.setAttribute("id", "__mce");
\r
11921 // If element is inside body, might not be the case in contentEdiable mode
\r
11922 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
\r
11923 so = r.startOffset;
\r
11924 eo = r.endOffset;
\r
11925 si = t.find(b, 0, r.startContainer);
\r
11926 ei = t.find(b, 0, r.endContainer);
\r
11930 // Force control range into text range
\r
11932 tr = d.body.createTextRange();
\r
11933 tr.moveToElementText(r.item(0));
\r
11937 tr = d.body.createTextRange();
\r
11938 tr.moveToElementText(b);
\r
11940 bp = tr.move('character', c) * -1;
\r
11942 tr = r.duplicate();
\r
11944 sp = tr.move('character', c) * -1;
\r
11946 tr = r.duplicate();
\r
11948 le = (tr.move('character', c) * -1) - sp;
\r
11955 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
\r
11956 // See: http://support.microsoft.com/kb/829907
\r
11957 bl = ed.dom.create(ed.settings.forced_root_block);
\r
11958 nx.parentNode.replaceChild(bl, nx);
\r
11959 bl.appendChild(nx);
\r
11962 if (bl.hasChildNodes())
\r
11963 bl.insertBefore(nx, bl.firstChild);
\r
11965 bl.appendChild(nx);
\r
11968 bl = null; // Time to create new block
\r
11971 // Restore selection
\r
11974 bl = b.getElementsByTagName(ed.settings.element)[0];
\r
11975 r = d.createRange();
\r
11977 // Select last location or generated block
\r
11979 r.setStart(t.find(b, 1, si), so);
\r
11981 r.setStart(bl, 0);
\r
11983 // Select last location or generated block
\r
11985 r.setEnd(t.find(b, 1, ei), eo);
\r
11990 s.removeAllRanges();
\r
11995 r = s.createRange();
\r
11996 r.moveToElementText(b);
\r
11998 r.moveStart('character', si);
\r
11999 r.moveEnd('character', ei);
\r
12005 } else if (!isIE && (n = ed.dom.get('__mce'))) {
\r
12006 // Restore the id of the selected element
\r
12008 n.setAttribute('id', eid);
\r
12010 n.removeAttribute('id');
\r
12012 // Move caret before selected element
\r
12013 r = d.createRange();
\r
12014 r.setStartBefore(n);
\r
12015 r.setEndBefore(n);
\r
12020 getParentBlock : function(n) {
\r
12021 var d = this.dom;
\r
12023 return d.getParent(n, d.isBlock);
\r
12026 insertPara : function(e) {
\r
12027 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
12028 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
12030 // If root blocks are forced then use Operas default behavior since it's really good
\r
12031 // Removed due to bug: #1853816
\r
12032 // if (se.forced_root_block && isOpera)
\r
12035 // Setup before range
\r
12036 rb = d.createRange();
\r
12038 // If is before the first block element and in body, then move it into first block element
\r
12039 rb.setStart(s.anchorNode, s.anchorOffset);
\r
12040 rb.collapse(TRUE);
\r
12042 // Setup after range
\r
12043 ra = d.createRange();
\r
12045 // If is before the first block element and in body, then move it into first block element
\r
12046 ra.setStart(s.focusNode, s.focusOffset);
\r
12047 ra.collapse(TRUE);
\r
12049 // Setup start/end points
\r
12050 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
\r
12051 sn = dir ? s.anchorNode : s.focusNode;
\r
12052 so = dir ? s.anchorOffset : s.focusOffset;
\r
12053 en = dir ? s.focusNode : s.anchorNode;
\r
12054 eo = dir ? s.focusOffset : s.anchorOffset;
\r
12056 // If selection is in empty table cell
\r
12057 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
\r
12058 if (sn.firstChild.nodeName == 'BR')
\r
12059 dom.remove(sn.firstChild); // Remove BR
\r
12061 // Create two new block elements
\r
12062 if (sn.childNodes.length == 0) {
\r
12063 ed.dom.add(sn, se.element, null, '<br />');
\r
12064 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
12066 n = sn.innerHTML;
\r
12067 sn.innerHTML = '';
\r
12068 ed.dom.add(sn, se.element, null, n);
\r
12069 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
12072 // Move caret into the last one
\r
12073 r = d.createRange();
\r
12074 r.selectNodeContents(aft);
\r
12076 ed.selection.setRng(r);
\r
12081 // If the caret is in an invalid location in FF we need to move it into the first block
\r
12082 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
\r
12083 sn = en = sn.firstChild;
\r
12085 rb = d.createRange();
\r
12086 rb.setStart(sn, 0);
\r
12087 ra = d.createRange();
\r
12088 ra.setStart(en, 0);
\r
12091 // Never use body as start or end node
\r
12092 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
12093 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
\r
12094 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
12095 en = en.nodeName == "BODY" ? en.firstChild : en;
\r
12097 // Get start and end blocks
\r
12098 sb = t.getParentBlock(sn);
\r
12099 eb = t.getParentBlock(en);
\r
12100 bn = sb ? sb.nodeName : se.element; // Get block name to create
\r
12102 // Return inside list use default browser behavior
\r
12103 if (n = t.dom.getParent(sb, 'li,pre')) {
\r
12104 if (n.nodeName == 'LI')
\r
12105 return splitList(ed.selection, t.dom, n);
\r
12110 // If caption or absolute layers then always generate new blocks within
\r
12111 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
12116 // If caption or absolute layers then always generate new blocks within
\r
12117 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
12123 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
\r
12128 // Setup new before and after blocks
\r
12129 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
\r
12130 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
\r
12132 // Remove id from after clone
\r
12133 aft.removeAttribute('id');
\r
12135 // Is header and cursor is at the end, then force paragraph under
\r
12136 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
\r
12137 aft = ed.dom.create(se.element);
\r
12139 // Find start chop node
\r
12142 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
12146 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
\r
12148 // Find end chop node
\r
12151 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
12155 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
\r
12157 // Place first chop part into before block element
\r
12158 if (sc.nodeName == bn)
\r
12159 rb.setStart(sc, 0);
\r
12161 rb.setStartBefore(sc);
\r
12163 rb.setEnd(sn, so);
\r
12164 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
12166 // Place secnd chop part within new block element
\r
12168 ra.setEndAfter(ec);
\r
12170 //console.debug(s.focusNode, s.focusOffset);
\r
12173 ra.setStart(en, eo);
\r
12174 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
12176 // Create range around everything
\r
12177 r = d.createRange();
\r
12178 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
\r
12179 r.setStartBefore(sc.parentNode);
\r
12181 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
\r
12182 r.setStartBefore(rb.startContainer);
\r
12184 r.setStart(rb.startContainer, rb.startOffset);
\r
12187 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
\r
12188 r.setEndAfter(ec.parentNode);
\r
12190 r.setEnd(ra.endContainer, ra.endOffset);
\r
12192 // Delete and replace it with new block elements
\r
12193 r.deleteContents();
\r
12196 ed.getWin().scrollTo(0, vp.y);
\r
12198 // Never wrap blocks in blocks
\r
12199 if (bef.firstChild && bef.firstChild.nodeName == bn)
\r
12200 bef.innerHTML = bef.firstChild.innerHTML;
\r
12202 if (aft.firstChild && aft.firstChild.nodeName == bn)
\r
12203 aft.innerHTML = aft.firstChild.innerHTML;
\r
12205 // Padd empty blocks
\r
12206 if (isEmpty(bef))
\r
12207 bef.innerHTML = '<br />';
\r
12209 function appendStyles(e, en) {
\r
12210 var nl = [], nn, n, i;
\r
12212 e.innerHTML = '';
\r
12214 // Make clones of style elements
\r
12215 if (se.keep_styles) {
\r
12218 // We only want style specific elements
\r
12219 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
\r
12220 nn = n.cloneNode(FALSE);
\r
12221 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
\r
12224 } while (n = n.parentNode);
\r
12227 // Append style elements to aft
\r
12228 if (nl.length > 0) {
\r
12229 for (i = nl.length - 1, nn = e; i >= 0; i--)
\r
12230 nn = nn.appendChild(nl[i]);
\r
12232 // Padd most inner style element
\r
12233 nl[0].innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
12234 return nl[0]; // Move caret to most inner element
\r
12236 e.innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
12239 // Fill empty afterblook with current style
\r
12240 if (isEmpty(aft))
\r
12241 car = appendStyles(aft, en);
\r
12243 // Opera needs this one backwards for older versions
\r
12244 if (isOpera && parseFloat(opera.version()) < 9.5) {
\r
12245 r.insertNode(bef);
\r
12246 r.insertNode(aft);
\r
12248 r.insertNode(aft);
\r
12249 r.insertNode(bef);
\r
12256 function first(n) {
\r
12257 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
\r
12260 // Move cursor and scroll into view
\r
12261 r = d.createRange();
\r
12262 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
\r
12264 s.removeAllRanges();
\r
12267 // 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
12268 y = ed.dom.getPos(aft).y;
\r
12269 ch = aft.clientHeight;
\r
12271 // Is element within viewport
\r
12272 if (y < vp.y || y + ch > vp.y + vp.h) {
\r
12273 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
12274 //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
12280 backspaceDelete : function(e, bs) {
\r
12281 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
12283 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
\r
12284 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
\r
12285 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
\r
12287 // Walk the dom backwards until we find a text node
\r
12288 for (n = sc.lastChild; n; n = walker.prev()) {
\r
12289 if (n.nodeType == 3) {
\r
12290 r.setStart(n, n.nodeValue.length);
\r
12291 r.collapse(true);
\r
12298 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
\r
12299 // This workaround removes the element by hand and moves the caret to the previous element
\r
12300 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
\r
12301 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
\r
12302 // Find previous block element
\r
12304 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
\r
12307 if (sc != b.firstChild) {
\r
12308 // Find last text node
\r
12309 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
\r
12310 while (tn = w.nextNode())
\r
12313 // Place caret at the end of last text node
\r
12314 r = ed.getDoc().createRange();
\r
12315 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
\r
12316 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
\r
12319 // Remove the target container
\r
12320 ed.dom.remove(sc);
\r
12323 return Event.cancel(e);
\r
12331 (function(tinymce) {
\r
12333 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
\r
12335 tinymce.create('tinymce.ControlManager', {
\r
12336 ControlManager : function(ed, s) {
\r
12342 t.onAdd = new tinymce.util.Dispatcher(t);
\r
12343 t.onPostRender = new tinymce.util.Dispatcher(t);
\r
12344 t.prefix = s.prefix || ed.id + '_';
\r
12347 t.onPostRender.add(function() {
\r
12348 each(t.controls, function(c) {
\r
12354 get : function(id) {
\r
12355 return this.controls[this.prefix + id] || this.controls[id];
\r
12358 setActive : function(id, s) {
\r
12361 if (c = this.get(id))
\r
12367 setDisabled : function(id, s) {
\r
12370 if (c = this.get(id))
\r
12371 c.setDisabled(s);
\r
12376 add : function(c) {
\r
12380 t.controls[c.id] = c;
\r
12381 t.onAdd.dispatch(c, t);
\r
12387 createControl : function(n) {
\r
12388 var c, t = this, ed = t.editor;
\r
12390 each(ed.plugins, function(p) {
\r
12391 if (p.createControl) {
\r
12392 c = p.createControl(n, t);
\r
12401 case "separator":
\r
12402 return t.createSeparator();
\r
12405 if (!c && ed.buttons && (c = ed.buttons[n]))
\r
12406 return t.createButton(n, c);
\r
12411 createDropMenu : function(id, s, cc) {
\r
12412 var t = this, ed = t.editor, c, bm, v, cls;
\r
12415 'class' : 'mceDropDown',
\r
12416 constrain : ed.settings.constrain_menus
\r
12419 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
\r
12420 if (v = ed.getParam('skin_variant'))
\r
12421 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
\r
12423 id = t.prefix + id;
\r
12424 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
\r
12425 c = t.controls[id] = new cls(id, s);
\r
12426 c.onAddItem.add(function(c, o) {
\r
12427 var s = o.settings;
\r
12429 s.title = ed.getLang(s.title, s.title);
\r
12431 if (!s.onclick) {
\r
12432 s.onclick = function(v) {
\r
12434 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
12439 ed.onRemove.add(function() {
\r
12443 // Fix for bug #1897785, #1898007
\r
12444 if (tinymce.isIE) {
\r
12445 c.onShowMenu.add(function() {
\r
12446 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
12449 bm = ed.selection.getBookmark(1);
\r
12452 c.onHideMenu.add(function() {
\r
12454 ed.selection.moveToBookmark(bm);
\r
12463 createListBox : function(id, s, cc) {
\r
12464 var t = this, ed = t.editor, cmd, c, cls;
\r
12469 s.title = ed.translate(s.title);
\r
12470 s.scope = s.scope || ed;
\r
12472 if (!s.onselect) {
\r
12473 s.onselect = function(v) {
\r
12474 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12480 'class' : 'mce_' + id,
\r
12482 control_manager : t
\r
12485 id = t.prefix + id;
\r
12487 if (ed.settings.use_native_selects)
\r
12488 c = new tinymce.ui.NativeListBox(id, s);
\r
12490 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
\r
12491 c = new cls(id, s);
\r
12494 t.controls[id] = c;
\r
12496 // Fix focus problem in Safari
\r
12497 if (tinymce.isWebKit) {
\r
12498 c.onPostRender.add(function(c, n) {
\r
12499 // Store bookmark on mousedown
\r
12500 Event.add(n, 'mousedown', function() {
\r
12501 ed.bookmark = ed.selection.getBookmark(1);
\r
12504 // Restore on focus, since it might be lost
\r
12505 Event.add(n, 'focus', function() {
\r
12506 ed.selection.moveToBookmark(ed.bookmark);
\r
12507 ed.bookmark = null;
\r
12513 ed.onMouseDown.add(c.hideMenu, c);
\r
12518 createButton : function(id, s, cc) {
\r
12519 var t = this, ed = t.editor, o, c, cls;
\r
12524 s.title = ed.translate(s.title);
\r
12525 s.label = ed.translate(s.label);
\r
12526 s.scope = s.scope || ed;
\r
12528 if (!s.onclick && !s.menu_button) {
\r
12529 s.onclick = function() {
\r
12530 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
12536 'class' : 'mce_' + id,
\r
12537 unavailable_prefix : ed.getLang('unavailable', ''),
\r
12539 control_manager : t
\r
12542 id = t.prefix + id;
\r
12544 if (s.menu_button) {
\r
12545 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
\r
12546 c = new cls(id, s);
\r
12547 ed.onMouseDown.add(c.hideMenu, c);
\r
12549 cls = t._cls.button || tinymce.ui.Button;
\r
12550 c = new cls(id, s);
\r
12556 createMenuButton : function(id, s, cc) {
\r
12558 s.menu_button = 1;
\r
12560 return this.createButton(id, s, cc);
\r
12563 createSplitButton : function(id, s, cc) {
\r
12564 var t = this, ed = t.editor, cmd, c, cls;
\r
12569 s.title = ed.translate(s.title);
\r
12570 s.scope = s.scope || ed;
\r
12572 if (!s.onclick) {
\r
12573 s.onclick = function(v) {
\r
12574 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12578 if (!s.onselect) {
\r
12579 s.onselect = function(v) {
\r
12580 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12586 'class' : 'mce_' + id,
\r
12588 control_manager : t
\r
12591 id = t.prefix + id;
\r
12592 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
\r
12593 c = t.add(new cls(id, s));
\r
12594 ed.onMouseDown.add(c.hideMenu, c);
\r
12599 createColorSplitButton : function(id, s, cc) {
\r
12600 var t = this, ed = t.editor, cmd, c, cls, bm;
\r
12605 s.title = ed.translate(s.title);
\r
12606 s.scope = s.scope || ed;
\r
12608 if (!s.onclick) {
\r
12609 s.onclick = function(v) {
\r
12610 if (tinymce.isIE)
\r
12611 bm = ed.selection.getBookmark(1);
\r
12613 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12617 if (!s.onselect) {
\r
12618 s.onselect = function(v) {
\r
12619 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
12625 'class' : 'mce_' + id,
\r
12626 'menu_class' : ed.getParam('skin') + 'Skin',
\r
12628 more_colors_title : ed.getLang('more_colors')
\r
12631 id = t.prefix + id;
\r
12632 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
\r
12633 c = new cls(id, s);
\r
12634 ed.onMouseDown.add(c.hideMenu, c);
\r
12636 // Remove the menu element when the editor is removed
\r
12637 ed.onRemove.add(function() {
\r
12641 // Fix for bug #1897785, #1898007
\r
12642 if (tinymce.isIE) {
\r
12643 c.onShowMenu.add(function() {
\r
12644 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
12646 bm = ed.selection.getBookmark(1);
\r
12649 c.onHideMenu.add(function() {
\r
12651 ed.selection.moveToBookmark(bm);
\r
12660 createToolbar : function(id, s, cc) {
\r
12661 var c, t = this, cls;
\r
12663 id = t.prefix + id;
\r
12664 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
\r
12665 c = new cls(id, s);
\r
12673 createSeparator : function(cc) {
\r
12674 var cls = cc || this._cls.separator || tinymce.ui.Separator;
\r
12676 return new cls();
\r
12679 setControlType : function(n, c) {
\r
12680 return this._cls[n.toLowerCase()] = c;
\r
12683 destroy : function() {
\r
12684 each(this.controls, function(c) {
\r
12688 this.controls = null;
\r
12693 (function(tinymce) {
\r
12694 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
\r
12696 tinymce.create('tinymce.WindowManager', {
\r
12697 WindowManager : function(ed) {
\r
12701 t.onOpen = new Dispatcher(t);
\r
12702 t.onClose = new Dispatcher(t);
\r
12707 open : function(s, p) {
\r
12708 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
\r
12710 // Default some options
\r
12713 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
\r
12714 sh = isOpera ? vp.h : screen.height;
\r
12715 s.name = s.name || 'mc_' + new Date().getTime();
\r
12716 s.width = parseInt(s.width || 320);
\r
12717 s.height = parseInt(s.height || 240);
\r
12718 s.resizable = true;
\r
12719 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
\r
12720 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
\r
12721 p.inline = false;
\r
12722 p.mce_width = s.width;
\r
12723 p.mce_height = s.height;
\r
12724 p.mce_auto_focus = s.auto_focus;
\r
12730 s.dialogWidth = s.width + 'px';
\r
12731 s.dialogHeight = s.height + 'px';
\r
12732 s.scroll = s.scrollbars || false;
\r
12736 // Build features string
\r
12737 each(s, function(v, k) {
\r
12738 if (tinymce.is(v, 'boolean'))
\r
12739 v = v ? 'yes' : 'no';
\r
12741 if (!/^(name|url)$/.test(k)) {
\r
12743 f += (f ? ';' : '') + k + ':' + v;
\r
12745 f += (f ? ',' : '') + k + '=' + v;
\r
12751 t.onOpen.dispatch(t, s, p);
\r
12753 u = s.url || s.file;
\r
12754 u = tinymce._addVer(u);
\r
12757 if (isIE && mo) {
\r
12759 window.showModalDialog(u, window, f);
\r
12761 w = window.open(u, s.name, f);
\r
12767 alert(t.editor.getLang('popup_blocked'));
\r
12770 close : function(w) {
\r
12772 this.onClose.dispatch(this);
\r
12775 createInstance : function(cl, a, b, c, d, e) {
\r
12776 var f = tinymce.resolve(cl);
\r
12778 return new f(a, b, c, d, e);
\r
12781 confirm : function(t, cb, s, w) {
\r
12784 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
\r
12787 alert : function(tx, cb, s, w) {
\r
12791 w.alert(t._decode(t.editor.getLang(tx, tx)));
\r
12797 resizeBy : function(dw, dh, win) {
\r
12798 win.resizeBy(dw, dh);
\r
12801 // Internal functions
\r
12803 _decode : function(s) {
\r
12804 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
\r
12808 (function(tinymce) {
\r
12809 function CommandManager() {
\r
12810 var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
\r
12812 function add(collection, cmd, func, scope) {
\r
12813 if (typeof(cmd) == 'string')
\r
12816 tinymce.each(cmd, function(cmd) {
\r
12817 collection[cmd.toLowerCase()] = {func : func, scope : scope};
\r
12821 tinymce.extend(this, {
\r
12822 add : function(cmd, func, scope) {
\r
12823 add(execCommands, cmd, func, scope);
\r
12826 addQueryStateHandler : function(cmd, func, scope) {
\r
12827 add(queryStateCommands, cmd, func, scope);
\r
12830 addQueryValueHandler : function(cmd, func, scope) {
\r
12831 add(queryValueCommands, cmd, func, scope);
\r
12834 execCommand : function(scope, cmd, ui, value, args) {
\r
12835 if (cmd = execCommands[cmd.toLowerCase()]) {
\r
12836 if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
\r
12841 queryCommandValue : function() {
\r
12842 if (cmd = queryValueCommands[cmd.toLowerCase()])
\r
12843 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
12846 queryCommandState : function() {
\r
12847 if (cmd = queryStateCommands[cmd.toLowerCase()])
\r
12848 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
12853 tinymce.GlobalCommands = new CommandManager();
\r
12855 (function(tinymce) {
\r
12856 tinymce.Formatter = function(ed) {
\r
12857 var formats = {},
\r
12858 each = tinymce.each,
\r
12860 selection = ed.selection,
\r
12861 TreeWalker = tinymce.dom.TreeWalker,
\r
12862 rangeUtils = new tinymce.dom.RangeUtils(dom),
\r
12863 isValid = ed.schema.isValid,
\r
12864 isBlock = dom.isBlock,
\r
12865 forcedRootBlock = ed.settings.forced_root_block,
\r
12866 nodeIndex = dom.nodeIndex,
\r
12867 INVISIBLE_CHAR = '\uFEFF',
\r
12868 MCE_ATTR_RE = /^(src|href|style)$/,
\r
12872 pendingFormats = {apply : [], remove : []};
\r
12874 function isArray(obj) {
\r
12875 return obj instanceof Array;
\r
12878 function getParents(node, selector) {
\r
12879 return dom.getParents(node, selector, dom.getRoot());
\r
12882 function isCaretNode(node) {
\r
12883 return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
\r
12886 // Public functions
\r
12888 function get(name) {
\r
12889 return name ? formats[name] : formats;
\r
12892 function register(name, format) {
\r
12894 if (typeof(name) !== 'string') {
\r
12895 each(name, function(format, name) {
\r
12896 register(name, format);
\r
12899 // Force format into array and add it to internal collection
\r
12900 format = format.length ? format : [format];
\r
12902 each(format, function(format) {
\r
12903 // Set deep to false by default on selector formats this to avoid removing
\r
12904 // alignment on images inside paragraphs when alignment is changed on paragraphs
\r
12905 if (format.deep === undefined)
\r
12906 format.deep = !format.selector;
\r
12908 // Default to true
\r
12909 if (format.split === undefined)
\r
12910 format.split = !format.selector || format.inline;
\r
12912 // Default to true
\r
12913 if (format.remove === undefined && format.selector && !format.inline)
\r
12914 format.remove = 'none';
\r
12916 // Mark format as a mixed format inline + block level
\r
12917 if (format.selector && format.inline) {
\r
12918 format.mixed = true;
\r
12919 format.block_expand = true;
\r
12922 // Split classes if needed
\r
12923 if (typeof(format.classes) === 'string')
\r
12924 format.classes = format.classes.split(/\s+/);
\r
12927 formats[name] = format;
\r
12932 function apply(name, vars, node) {
\r
12933 var formatList = get(name), format = formatList[0], bookmark, rng, i;
\r
12935 function moveStart(rng) {
\r
12936 var container = rng.startContainer,
\r
12937 offset = rng.startOffset,
\r
12940 // Move startContainer/startOffset in to a suitable node
\r
12941 if (container.nodeType == 1 || container.nodeValue === "") {
\r
12942 container = container.nodeType == 1 ? container.childNodes[offset] : container;
\r
12944 // Might fail if the offset is behind the last element in it's container
\r
12946 walker = new TreeWalker(container, container.parentNode);
\r
12947 for (node = walker.current(); node; node = walker.next()) {
\r
12948 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
\r
12949 rng.setStart(node, 0);
\r
12959 function setElementFormat(elm, fmt) {
\r
12960 fmt = fmt || format;
\r
12963 each(fmt.styles, function(value, name) {
\r
12964 dom.setStyle(elm, name, replaceVars(value, vars));
\r
12967 each(fmt.attributes, function(value, name) {
\r
12968 dom.setAttrib(elm, name, replaceVars(value, vars));
\r
12971 each(fmt.classes, function(value) {
\r
12972 value = replaceVars(value, vars);
\r
12974 if (!dom.hasClass(elm, value))
\r
12975 dom.addClass(elm, value);
\r
12980 function applyRngStyle(rng) {
\r
12981 var newWrappers = [], wrapName, wrapElm;
\r
12983 // Setup wrapper element
\r
12984 wrapName = format.inline || format.block;
\r
12985 wrapElm = dom.create(wrapName);
\r
12986 setElementFormat(wrapElm);
\r
12988 rangeUtils.walk(rng, function(nodes) {
\r
12989 var currentWrapElm;
\r
12991 function process(node) {
\r
12992 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
\r
12994 // Stop wrapping on br elements
\r
12995 if (isEq(nodeName, 'br')) {
\r
12996 currentWrapElm = 0;
\r
12998 // Remove any br elements when we wrap things
\r
12999 if (format.block)
\r
13000 dom.remove(node);
\r
13005 // If node is wrapper type
\r
13006 if (format.wrapper && matchNode(node, name, vars)) {
\r
13007 currentWrapElm = 0;
\r
13011 // Can we rename the block
\r
13012 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
\r
13013 node = dom.rename(node, wrapName);
\r
13014 setElementFormat(node);
\r
13015 newWrappers.push(node);
\r
13016 currentWrapElm = 0;
\r
13020 // Handle selector patterns
\r
13021 if (format.selector) {
\r
13022 // Look for matching formats
\r
13023 each(formatList, function(format) {
\r
13024 if (dom.is(node, format.selector) && !isCaretNode(node)) {
\r
13025 setElementFormat(node, format);
\r
13030 // Continue processing if a selector match wasn't found and a inline element is defined
\r
13031 if (!format.inline || found) {
\r
13032 currentWrapElm = 0;
\r
13037 // Is it valid to wrap this item
\r
13038 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
\r
13039 // Start wrapping
\r
13040 if (!currentWrapElm) {
\r
13042 currentWrapElm = wrapElm.cloneNode(FALSE);
\r
13043 node.parentNode.insertBefore(currentWrapElm, node);
\r
13044 newWrappers.push(currentWrapElm);
\r
13047 currentWrapElm.appendChild(node);
\r
13049 // Start a new wrapper for possible children
\r
13050 currentWrapElm = 0;
\r
13052 each(tinymce.grep(node.childNodes), process);
\r
13054 // End the last wrapper
\r
13055 currentWrapElm = 0;
\r
13059 // Process siblings from range
\r
13060 each(nodes, process);
\r
13064 each(newWrappers, function(node) {
\r
13067 function getChildCount(node) {
\r
13070 each(node.childNodes, function(node) {
\r
13071 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
\r
13078 function mergeStyles(node) {
\r
13079 var child, clone;
\r
13081 each(node.childNodes, function(node) {
\r
13082 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
\r
13084 return FALSE; // break loop
\r
13088 // If child was found and of the same type as the current node
\r
13089 if (child && matchName(child, format)) {
\r
13090 clone = child.cloneNode(FALSE);
\r
13091 setElementFormat(clone);
\r
13093 dom.replace(clone, node, TRUE);
\r
13094 dom.remove(child, 1);
\r
13097 return clone || node;
\r
13100 childCount = getChildCount(node);
\r
13102 // Remove empty nodes
\r
13103 if (childCount === 0) {
\r
13104 dom.remove(node, 1);
\r
13108 if (format.inline || format.wrapper) {
\r
13109 // Merges the current node with it's children of similar type to reduce the number of elements
\r
13110 if (!format.exact && childCount === 1)
\r
13111 node = mergeStyles(node);
\r
13113 // Remove/merge children
\r
13114 each(formatList, function(format) {
\r
13115 // Merge all children of similar type will move styles from child to parent
\r
13116 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
\r
13117 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
\r
13118 each(dom.select(format.inline, node), function(child) {
\r
13119 removeFormat(format, vars, child, format.exact ? child : null);
\r
13123 // Remove child if direct parent is of same type
\r
13124 if (matchNode(node.parentNode, name, vars)) {
\r
13125 dom.remove(node, 1);
\r
13130 // Look for parent with similar style format
\r
13131 if (format.merge_with_parents) {
\r
13132 dom.getParent(node.parentNode, function(parent) {
\r
13133 if (matchNode(parent, name, vars)) {
\r
13134 dom.remove(node, 1);
\r
13141 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
\r
13143 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
\r
13144 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
\r
13152 rng = dom.createRng();
\r
13154 rng.setStartBefore(node);
\r
13155 rng.setEndAfter(node);
\r
13157 applyRngStyle(expandRng(rng, formatList));
\r
13159 if (!selection.isCollapsed() || !format.inline) {
\r
13160 // Apply formatting to selection
\r
13161 bookmark = selection.getBookmark();
\r
13162 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
\r
13164 selection.moveToBookmark(bookmark);
\r
13165 selection.setRng(moveStart(selection.getRng(TRUE)));
\r
13166 ed.nodeChanged();
\r
13168 performCaretAction('apply', name, vars);
\r
13173 function remove(name, vars, node) {
\r
13174 var formatList = get(name), format = formatList[0], bookmark, i, rng;
\r
13176 // Merges the styles for each node
\r
13177 function process(node) {
\r
13178 var children, i, l;
\r
13180 // Grab the children first since the nodelist might be changed
\r
13181 children = tinymce.grep(node.childNodes);
\r
13183 // Process current node
\r
13184 for (i = 0, l = formatList.length; i < l; i++) {
\r
13185 if (removeFormat(formatList[i], vars, node, node))
\r
13189 // Process the children
\r
13190 if (format.deep) {
\r
13191 for (i = 0, l = children.length; i < l; i++)
\r
13192 process(children[i]);
\r
13196 function findFormatRoot(container) {
\r
13199 // Find format root
\r
13200 each(getParents(container.parentNode).reverse(), function(parent) {
\r
13203 // Find format root element
\r
13204 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
\r
13205 // Is the node matching the format we are looking for
\r
13206 format = matchNode(parent, name, vars);
\r
13207 if (format && format.split !== false)
\r
13208 formatRoot = parent;
\r
13212 return formatRoot;
\r
13215 function wrapAndSplit(format_root, container, target, split) {
\r
13216 var parent, clone, lastClone, firstClone, i, formatRootParent;
\r
13218 // Format root found then clone formats and split it
\r
13219 if (format_root) {
\r
13220 formatRootParent = format_root.parentNode;
\r
13222 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
\r
13223 clone = parent.cloneNode(FALSE);
\r
13225 for (i = 0; i < formatList.length; i++) {
\r
13226 if (removeFormat(formatList[i], vars, clone, clone)) {
\r
13232 // Build wrapper node
\r
13235 clone.appendChild(lastClone);
\r
13238 firstClone = clone;
\r
13240 lastClone = clone;
\r
13244 // Never split block elements if the format is mixed
\r
13245 if (split && (!format.mixed || !isBlock(format_root)))
\r
13246 container = dom.split(format_root, container);
\r
13248 // Wrap container in cloned formats
\r
13250 target.parentNode.insertBefore(lastClone, target);
\r
13251 firstClone.appendChild(target);
\r
13255 return container;
\r
13258 function splitToFormatRoot(container) {
\r
13259 return wrapAndSplit(findFormatRoot(container), container, container, true);
\r
13262 function unwrap(start) {
\r
13263 var node = dom.get(start ? '_start' : '_end'),
\r
13264 out = node[start ? 'firstChild' : 'lastChild'];
\r
13266 // If the end is placed within the start the result will be removed
\r
13267 // So this checks if the out node is a bookmark node if it is it
\r
13268 // checks for another more suitable node
\r
13269 if (isBookmarkNode(out))
\r
13270 out = out[start ? 'firstChild' : 'lastChild'];
\r
13272 dom.remove(node, true);
\r
13277 function removeRngStyle(rng) {
\r
13278 var startContainer, endContainer;
\r
13280 rng = expandRng(rng, formatList, TRUE);
\r
13282 if (format.split) {
\r
13283 startContainer = getContainer(rng, TRUE);
\r
13284 endContainer = getContainer(rng);
\r
13286 if (startContainer != endContainer) {
\r
13287 // Wrap start/end nodes in span element since these might be cloned/moved
\r
13288 startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
\r
13289 endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
\r
13291 // Split start/end
\r
13292 splitToFormatRoot(startContainer);
\r
13293 splitToFormatRoot(endContainer);
\r
13295 // Unwrap start/end to get real elements again
\r
13296 startContainer = unwrap(TRUE);
\r
13297 endContainer = unwrap();
\r
13299 startContainer = endContainer = splitToFormatRoot(startContainer);
\r
13301 // Update range positions since they might have changed after the split operations
\r
13302 rng.startContainer = startContainer.parentNode;
\r
13303 rng.startOffset = nodeIndex(startContainer);
\r
13304 rng.endContainer = endContainer.parentNode;
\r
13305 rng.endOffset = nodeIndex(endContainer) + 1;
\r
13308 // Remove items between start/end
\r
13309 rangeUtils.walk(rng, function(nodes) {
\r
13310 each(nodes, function(node) {
\r
13318 rng = dom.createRng();
\r
13319 rng.setStartBefore(node);
\r
13320 rng.setEndAfter(node);
\r
13321 removeRngStyle(rng);
\r
13325 if (!selection.isCollapsed() || !format.inline) {
\r
13326 bookmark = selection.getBookmark();
\r
13327 removeRngStyle(selection.getRng(TRUE));
\r
13328 selection.moveToBookmark(bookmark);
\r
13329 ed.nodeChanged();
\r
13331 performCaretAction('remove', name, vars);
\r
13334 function toggle(name, vars, node) {
\r
13335 if (match(name, vars, node))
\r
13336 remove(name, vars, node);
\r
13338 apply(name, vars, node);
\r
13341 function matchNode(node, name, vars, similar) {
\r
13342 var formatList = get(name), format, i, classes;
\r
13344 function matchItems(node, format, item_name) {
\r
13345 var key, value, items = format[item_name], i;
\r
13347 // Check all items
\r
13349 // Non indexed object
\r
13350 if (items.length === undefined) {
\r
13351 for (key in items) {
\r
13352 if (items.hasOwnProperty(key)) {
\r
13353 if (item_name === 'attributes')
\r
13354 value = dom.getAttrib(node, key);
\r
13356 value = getStyle(node, key);
\r
13358 if (similar && !value && !format.exact)
\r
13361 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
\r
13366 // Only one match needed for indexed arrays
\r
13367 for (i = 0; i < items.length; i++) {
\r
13368 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
\r
13377 if (formatList && node) {
\r
13378 // Check each format in list
\r
13379 for (i = 0; i < formatList.length; i++) {
\r
13380 format = formatList[i];
\r
13382 // Name name, attributes, styles and classes
\r
13383 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
\r
13385 if (classes = format.classes) {
\r
13386 for (i = 0; i < classes.length; i++) {
\r
13387 if (!dom.hasClass(node, classes[i]))
\r
13398 function match(name, vars, node) {
\r
13399 var startNode, i;
\r
13401 function matchParents(node) {
\r
13402 // Find first node with similar format settings
\r
13403 node = dom.getParent(node, function(node) {
\r
13404 return !!matchNode(node, name, vars, true);
\r
13407 // Do an exact check on the similar format element
\r
13408 return matchNode(node, name, vars);
\r
13411 // Check specified node
\r
13413 return matchParents(node);
\r
13415 // Check pending formats
\r
13416 if (selection.isCollapsed()) {
\r
13417 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
13418 if (pendingFormats.apply[i].name == name)
\r
13422 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
13423 if (pendingFormats.remove[i].name == name)
\r
13427 return matchParents(selection.getNode());
\r
13430 // Check selected node
\r
13431 node = selection.getNode();
\r
13432 if (matchParents(node))
\r
13435 // Check start node if it's different
\r
13436 startNode = selection.getStart();
\r
13437 if (startNode != node) {
\r
13438 if (matchParents(startNode))
\r
13445 function matchAll(names, vars) {
\r
13446 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
\r
13448 // If the selection is collapsed then check pending formats
\r
13449 if (selection.isCollapsed()) {
\r
13450 for (ni = 0; ni < names.length; ni++) {
\r
13451 // If the name is to be removed, then stop it from being added
\r
13452 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
13453 name = names[ni];
\r
13455 if (pendingFormats.remove[i].name == name) {
\r
13456 checkedMap[name] = true;
\r
13462 // If the format is to be applied
\r
13463 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
13464 for (ni = 0; ni < names.length; ni++) {
\r
13465 name = names[ni];
\r
13467 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
\r
13468 checkedMap[name] = true;
\r
13469 matchedFormatNames.push(name);
\r
13475 // Check start of selection for formats
\r
13476 startElement = selection.getStart();
\r
13477 dom.getParent(startElement, function(node) {
\r
13480 for (i = 0; i < names.length; i++) {
\r
13483 if (!checkedMap[name] && matchNode(node, name, vars)) {
\r
13484 checkedMap[name] = true;
\r
13485 matchedFormatNames.push(name);
\r
13490 return matchedFormatNames;
\r
13493 function canApply(name) {
\r
13494 var formatList = get(name), startNode, parents, i, x, selector;
\r
13496 if (formatList) {
\r
13497 startNode = selection.getStart();
\r
13498 parents = getParents(startNode);
\r
13500 for (x = formatList.length - 1; x >= 0; x--) {
\r
13501 selector = formatList[x].selector;
\r
13503 // Format is not selector based, then always return TRUE
\r
13507 for (i = parents.length - 1; i >= 0; i--) {
\r
13508 if (dom.is(parents[i], selector))
\r
13517 // Expose to public
\r
13518 tinymce.extend(this, {
\r
13520 register : register,
\r
13525 matchAll : matchAll,
\r
13526 matchNode : matchNode,
\r
13527 canApply : canApply
\r
13530 // Private functions
\r
13532 function matchName(node, format) {
\r
13533 // Check for inline match
\r
13534 if (isEq(node, format.inline))
\r
13537 // Check for block match
\r
13538 if (isEq(node, format.block))
\r
13541 // Check for selector match
\r
13542 if (format.selector)
\r
13543 return dom.is(node, format.selector);
\r
13546 function isEq(str1, str2) {
\r
13547 str1 = str1 || '';
\r
13548 str2 = str2 || '';
\r
13550 str1 = '' + (str1.nodeName || str1);
\r
13551 str2 = '' + (str2.nodeName || str2);
\r
13553 return str1.toLowerCase() == str2.toLowerCase();
\r
13556 function getStyle(node, name) {
\r
13557 var styleVal = dom.getStyle(node, name);
\r
13559 // Force the format to hex
\r
13560 if (name == 'color' || name == 'backgroundColor')
\r
13561 styleVal = dom.toHex(styleVal);
\r
13563 // Opera will return bold as 700
\r
13564 if (name == 'fontWeight' && styleVal == 700)
\r
13565 styleVal = 'bold';
\r
13567 return '' + styleVal;
\r
13570 function replaceVars(value, vars) {
\r
13571 if (typeof(value) != "string")
\r
13572 value = value(vars);
\r
13574 value = value.replace(/%(\w+)/g, function(str, name) {
\r
13575 return vars[name] || str;
\r
13582 function isWhiteSpaceNode(node) {
\r
13583 return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
\r
13586 function wrap(node, name, attrs) {
\r
13587 var wrapper = dom.create(name, attrs);
\r
13589 node.parentNode.insertBefore(wrapper, node);
\r
13590 wrapper.appendChild(node);
\r
13595 function expandRng(rng, format, remove) {
\r
13596 var startContainer = rng.startContainer,
\r
13597 startOffset = rng.startOffset,
\r
13598 endContainer = rng.endContainer,
\r
13599 endOffset = rng.endOffset, sibling, lastIdx;
\r
13601 // This function walks up the tree if there is no siblings before/after the node
\r
13602 function findParentContainer(container, child_name, sibling_name, root) {
\r
13603 var parent, child;
\r
13605 root = root || dom.getRoot();
\r
13608 // Check if we can move up are we at root level or body level
\r
13609 parent = container.parentNode;
\r
13611 // Stop expanding on block elements or root depending on format
\r
13612 if (parent == root || (!format[0].block_expand && isBlock(parent)))
\r
13613 return container;
\r
13615 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
\r
13616 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
13617 return container;
\r
13619 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
13620 return container;
\r
13623 container = container.parentNode;
\r
13626 return container;
\r
13629 // If index based start position then resolve it
\r
13630 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
\r
13631 lastIdx = startContainer.childNodes.length - 1;
\r
13632 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
\r
13634 if (startContainer.nodeType == 3)
\r
13638 // If index based end position then resolve it
\r
13639 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
\r
13640 lastIdx = endContainer.childNodes.length - 1;
\r
13641 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
\r
13643 if (endContainer.nodeType == 3)
\r
13644 endOffset = endContainer.nodeValue.length;
\r
13647 // Exclude bookmark nodes if possible
\r
13648 if (isBookmarkNode(startContainer.parentNode))
\r
13649 startContainer = startContainer.parentNode;
\r
13651 if (isBookmarkNode(startContainer))
\r
13652 startContainer = startContainer.nextSibling || startContainer;
\r
13654 if (isBookmarkNode(endContainer.parentNode))
\r
13655 endContainer = endContainer.parentNode;
\r
13657 if (isBookmarkNode(endContainer))
\r
13658 endContainer = endContainer.previousSibling || endContainer;
\r
13660 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
\r
13661 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
\r
13662 // This will reduce the number of wrapper elements that needs to be created
\r
13663 // Move start point up the tree
\r
13664 if (format[0].inline || format[0].block_expand) {
\r
13665 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
13666 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
13669 // Expand start/end container to matching selector
\r
13670 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
\r
13671 function findSelectorEndPoint(container, sibling_name) {
\r
13672 var parents, i, y;
\r
13674 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
\r
13675 container = container[sibling_name];
\r
13677 parents = getParents(container);
\r
13678 for (i = 0; i < parents.length; i++) {
\r
13679 for (y = 0; y < format.length; y++) {
\r
13680 if (dom.is(parents[i], format[y].selector))
\r
13681 return parents[i];
\r
13685 return container;
\r
13688 // Find new startContainer/endContainer if there is better one
\r
13689 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
\r
13690 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
\r
13693 // Expand start/end container to matching block element or text node
\r
13694 if (format[0].block || format[0].selector) {
\r
13695 function findBlockEndPoint(container, sibling_name, sibling_name2) {
\r
13698 // Expand to block of similar type
\r
13699 if (!format[0].wrapper)
\r
13700 node = dom.getParent(container, format[0].block);
\r
13702 // Expand to first wrappable block element or any block element
\r
13704 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
\r
13706 // Exclude inner lists from wrapping
\r
13707 if (node && format[0].wrapper)
\r
13708 node = getParents(node, 'ul,ol').reverse()[0] || node;
\r
13710 // Didn't find a block element look for first/last wrappable element
\r
13712 node = container;
\r
13714 while (node[sibling_name] && !isBlock(node[sibling_name])) {
\r
13715 node = node[sibling_name];
\r
13717 // Break on BR but include it will be removed later on
\r
13718 // we can't remove it now since we need to check if it can be wrapped
\r
13719 if (isEq(node, 'br'))
\r
13724 return node || container;
\r
13727 // Find new startContainer/endContainer if there is better one
\r
13728 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
\r
13729 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
\r
13731 // Non block element then try to expand up the leaf
\r
13732 if (format[0].block) {
\r
13733 if (!isBlock(startContainer))
\r
13734 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
13736 if (!isBlock(endContainer))
\r
13737 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
13741 // Setup index for startContainer
\r
13742 if (startContainer.nodeType == 1) {
\r
13743 startOffset = nodeIndex(startContainer);
\r
13744 startContainer = startContainer.parentNode;
\r
13747 // Setup index for endContainer
\r
13748 if (endContainer.nodeType == 1) {
\r
13749 endOffset = nodeIndex(endContainer) + 1;
\r
13750 endContainer = endContainer.parentNode;
\r
13753 // Return new range like object
\r
13755 startContainer : startContainer,
\r
13756 startOffset : startOffset,
\r
13757 endContainer : endContainer,
\r
13758 endOffset : endOffset
\r
13762 function removeFormat(format, vars, node, compare_node) {
\r
13763 var i, attrs, stylesModified;
\r
13765 // Check if node matches format
\r
13766 if (!matchName(node, format))
\r
13769 // Should we compare with format attribs and styles
\r
13770 if (format.remove != 'all') {
\r
13772 each(format.styles, function(value, name) {
\r
13773 value = replaceVars(value, vars);
\r
13776 if (typeof(name) === 'number') {
\r
13778 compare_node = 0;
\r
13781 if (!compare_node || isEq(getStyle(compare_node, name), value))
\r
13782 dom.setStyle(node, name, '');
\r
13784 stylesModified = 1;
\r
13787 // Remove style attribute if it's empty
\r
13788 if (stylesModified && dom.getAttrib(node, 'style') == '') {
\r
13789 node.removeAttribute('style');
\r
13790 node.removeAttribute('_mce_style');
\r
13793 // Remove attributes
\r
13794 each(format.attributes, function(value, name) {
\r
13797 value = replaceVars(value, vars);
\r
13800 if (typeof(name) === 'number') {
\r
13802 compare_node = 0;
\r
13805 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
\r
13806 // Keep internal classes
\r
13807 if (name == 'class') {
\r
13808 value = dom.getAttrib(node, name);
\r
13810 // Build new class value where everything is removed except the internal prefixed classes
\r
13812 each(value.split(/\s+/), function(cls) {
\r
13813 if (/mce\w+/.test(cls))
\r
13814 valueOut += (valueOut ? ' ' : '') + cls;
\r
13817 // We got some internal classes left
\r
13819 dom.setAttrib(node, name, valueOut);
\r
13825 // IE6 has a bug where the attribute doesn't get removed correctly
\r
13826 if (name == "class")
\r
13827 node.removeAttribute('className');
\r
13829 // Remove mce prefixed attributes
\r
13830 if (MCE_ATTR_RE.test(name))
\r
13831 node.removeAttribute('_mce_' + name);
\r
13833 node.removeAttribute(name);
\r
13837 // Remove classes
\r
13838 each(format.classes, function(value) {
\r
13839 value = replaceVars(value, vars);
\r
13841 if (!compare_node || dom.hasClass(compare_node, value))
\r
13842 dom.removeClass(node, value);
\r
13845 // Check for non internal attributes
\r
13846 attrs = dom.getAttribs(node);
\r
13847 for (i = 0; i < attrs.length; i++) {
\r
13848 if (attrs[i].nodeName.indexOf('_') !== 0)
\r
13853 // Remove the inline child if it's empty for example <b> or <span>
\r
13854 if (format.remove != 'none') {
\r
13855 removeNode(node, format);
\r
13860 function removeNode(node, format) {
\r
13861 var parentNode = node.parentNode, rootBlockElm;
\r
13863 if (format.block) {
\r
13864 if (!forcedRootBlock) {
\r
13865 function find(node, next, inc) {
\r
13866 node = getNonWhiteSpaceSibling(node, next, inc);
\r
13868 return !node || (node.nodeName == 'BR' || isBlock(node));
\r
13871 // Append BR elements if needed before we remove the block
\r
13872 if (isBlock(node) && !isBlock(parentNode)) {
\r
13873 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
\r
13874 node.insertBefore(dom.create('br'), node.firstChild);
\r
13876 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
\r
13877 node.appendChild(dom.create('br'));
\r
13880 // Wrap the block in a forcedRootBlock if we are at the root of document
\r
13881 if (parentNode == dom.getRoot()) {
\r
13882 if (!format.list_block || !isEq(node, format.list_block)) {
\r
13883 each(tinymce.grep(node.childNodes), function(node) {
\r
13884 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
\r
13885 if (!rootBlockElm)
\r
13886 rootBlockElm = wrap(node, forcedRootBlock);
\r
13888 rootBlockElm.appendChild(node);
\r
13890 rootBlockElm = 0;
\r
13897 // Never remove nodes that isn't the specified inline element if a selector is specified too
\r
13898 if (format.selector && format.inline && !isEq(format.inline, node))
\r
13901 dom.remove(node, 1);
\r
13904 function getNonWhiteSpaceSibling(node, next, inc) {
\r
13906 next = next ? 'nextSibling' : 'previousSibling';
\r
13908 for (node = inc ? node : node[next]; node; node = node[next]) {
\r
13909 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
\r
13915 function isBookmarkNode(node) {
\r
13916 return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
\r
13919 function mergeSiblings(prev, next) {
\r
13920 var marker, sibling, tmpSibling;
\r
13922 function compareElements(node1, node2) {
\r
13923 // Not the same name
\r
13924 if (node1.nodeName != node2.nodeName)
\r
13927 function getAttribs(node) {
\r
13928 var attribs = {};
\r
13930 each(dom.getAttribs(node), function(attr) {
\r
13931 var name = attr.nodeName.toLowerCase();
\r
13933 // Don't compare internal attributes or style
\r
13934 if (name.indexOf('_') !== 0 && name !== 'style')
\r
13935 attribs[name] = dom.getAttrib(node, name);
\r
13941 function compareObjects(obj1, obj2) {
\r
13944 for (name in obj1) {
\r
13945 // Obj1 has item obj2 doesn't have
\r
13946 if (obj1.hasOwnProperty(name)) {
\r
13947 value = obj2[name];
\r
13949 // Obj2 doesn't have obj1 item
\r
13950 if (value === undefined)
\r
13953 // Obj2 item has a different value
\r
13954 if (obj1[name] != value)
\r
13957 // Delete similar value
\r
13958 delete obj2[name];
\r
13962 // Check if obj 2 has something obj 1 doesn't have
\r
13963 for (name in obj2) {
\r
13964 // Obj2 has item obj1 doesn't have
\r
13965 if (obj2.hasOwnProperty(name))
\r
13972 // Attribs are not the same
\r
13973 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
\r
13976 // Styles are not the same
\r
13977 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
\r
13983 // Check if next/prev exists and that they are elements
\r
13984 if (prev && next) {
\r
13985 function findElementSibling(node, sibling_name) {
\r
13986 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
\r
13987 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
13990 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
13997 // If previous sibling is empty then jump over it
\r
13998 prev = findElementSibling(prev, 'previousSibling');
\r
13999 next = findElementSibling(next, 'nextSibling');
\r
14001 // Compare next and previous nodes
\r
14002 if (compareElements(prev, next)) {
\r
14003 // Append nodes between
\r
14004 for (sibling = prev.nextSibling; sibling && sibling != next;) {
\r
14005 tmpSibling = sibling;
\r
14006 sibling = sibling.nextSibling;
\r
14007 prev.appendChild(tmpSibling);
\r
14010 // Remove next node
\r
14011 dom.remove(next);
\r
14013 // Move children into prev node
\r
14014 each(tinymce.grep(next.childNodes), function(node) {
\r
14015 prev.appendChild(node);
\r
14025 function isTextBlock(name) {
\r
14026 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
\r
14029 function getContainer(rng, start) {
\r
14030 var container, offset, lastIdx;
\r
14032 container = rng[start ? 'startContainer' : 'endContainer'];
\r
14033 offset = rng[start ? 'startOffset' : 'endOffset'];
\r
14035 if (container.nodeType == 1) {
\r
14036 lastIdx = container.childNodes.length - 1;
\r
14038 if (!start && offset)
\r
14041 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
\r
14044 return container;
\r
14047 function performCaretAction(type, name, vars) {
\r
14048 var i, currentPendingFormats = pendingFormats[type],
\r
14049 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
\r
14051 function hasPending() {
\r
14052 return pendingFormats.apply.length || pendingFormats.remove.length;
\r
14055 function resetPending() {
\r
14056 pendingFormats.apply = [];
\r
14057 pendingFormats.remove = [];
\r
14060 function perform(caret_node) {
\r
14061 // Apply pending formats
\r
14062 each(pendingFormats.apply.reverse(), function(item) {
\r
14063 apply(item.name, item.vars, caret_node);
\r
14066 // Remove pending formats
\r
14067 each(pendingFormats.remove.reverse(), function(item) {
\r
14068 remove(item.name, item.vars, caret_node);
\r
14071 dom.remove(caret_node, 1);
\r
14075 // Check if it already exists then ignore it
\r
14076 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
\r
14077 if (currentPendingFormats[i].name == name)
\r
14081 currentPendingFormats.push({name : name, vars : vars});
\r
14083 // Check if it's in the other type, then remove it
\r
14084 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
\r
14085 if (otherPendingFormats[i].name == name)
\r
14086 otherPendingFormats.splice(i, 1);
\r
14089 // Pending apply or remove formats
\r
14090 if (hasPending()) {
\r
14091 ed.getDoc().execCommand('FontName', false, 'mceinline');
\r
14092 pendingFormats.lastRng = selection.getRng();
\r
14094 // IE will convert the current word
\r
14095 each(dom.select('font,span'), function(node) {
\r
14098 if (isCaretNode(node)) {
\r
14099 bookmark = selection.getBookmark();
\r
14101 selection.moveToBookmark(bookmark);
\r
14102 ed.nodeChanged();
\r
14106 // Only register listeners once if we need to
\r
14107 if (!pendingFormats.isListening && hasPending()) {
\r
14108 pendingFormats.isListening = true;
\r
14110 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
\r
14111 ed[event].addToTop(function(ed, e) {
\r
14112 // Do we have pending formats and is the selection moved has moved
\r
14113 if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
\r
14114 each(dom.select('font,span'), function(node) {
\r
14115 var textNode, rng;
\r
14117 // Look for marker
\r
14118 if (isCaretNode(node)) {
\r
14119 textNode = node.firstChild;
\r
14124 rng = dom.createRng();
\r
14125 rng.setStart(textNode, textNode.nodeValue.length);
\r
14126 rng.setEnd(textNode, textNode.nodeValue.length);
\r
14127 selection.setRng(rng);
\r
14128 ed.nodeChanged();
\r
14130 dom.remove(node);
\r
14134 // Always unbind and clear pending styles on keyup
\r
14135 if (e.type == 'keyup' || e.type == 'mouseup')
\r
14146 tinymce.onAddEditor.add(function(tinymce, ed) {
\r
14147 var filters, fontSizes, dom, settings = ed.settings;
\r
14149 if (settings.inline_styles) {
\r
14150 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
14152 function replaceWithSpan(node, styles) {
\r
14153 dom.replace(dom.create('span', {
\r
14159 font : function(dom, node) {
\r
14160 replaceWithSpan(node, {
\r
14161 backgroundColor : node.style.backgroundColor,
\r
14162 color : node.color,
\r
14163 fontFamily : node.face,
\r
14164 fontSize : fontSizes[parseInt(node.size) - 1]
\r
14168 u : function(dom, node) {
\r
14169 replaceWithSpan(node, {
\r
14170 textDecoration : 'underline'
\r
14174 strike : function(dom, node) {
\r
14175 replaceWithSpan(node, {
\r
14176 textDecoration : 'line-through'
\r
14181 function convert(editor, params) {
\r
14182 dom = editor.dom;
\r
14184 if (settings.convert_fonts_to_spans) {
\r
14185 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
\r
14186 filters[node.nodeName.toLowerCase()](ed.dom, node);
\r
14191 ed.onPreProcess.add(convert);
\r
14193 ed.onInit.add(function() {
\r
14194 ed.selection.onSetContent.add(convert);
\r