2 var whiteSpaceRe = /^\s*|\s*$/g,
\r
5 win.tinymce = win.tinyMCE = {
\r
8 minorVersion : '3rc1',
\r
10 releaseDate : '2010-02-23',
\r
12 _init : function() {
\r
13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
\r
15 t.isOpera = win.opera && opera.buildNumber;
\r
17 t.isWebKit = /WebKit/.test(ua);
\r
19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
\r
21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
\r
23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
\r
25 t.isMac = ua.indexOf('Mac') != -1;
\r
27 t.isAir = /adobeair/i.test(ua);
\r
29 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
\r
30 if (win.tinyMCEPreInit) {
\r
31 t.suffix = tinyMCEPreInit.suffix;
\r
32 t.baseURL = tinyMCEPreInit.base;
\r
33 t.query = tinyMCEPreInit.query;
\r
37 // Get suffix and base
\r
40 // If base element found, add that infront of baseURL
\r
41 nl = d.getElementsByTagName('base');
\r
42 for (i=0; i<nl.length; i++) {
\r
43 if (v = nl[i].href) {
\r
44 // Host only value like http://site.com or http://site.com:8008
\r
45 if (/^https?:\/\/[^\/]+$/.test(v))
\r
48 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
\r
52 function getBase(n) {
\r
53 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
\r
54 if (/_(src|dev)\.js/g.test(n.src))
\r
57 if ((p = n.src.indexOf('?')) != -1)
\r
58 t.query = n.src.substring(p + 1);
\r
60 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
\r
62 // If path to script is relative and a base href was found add that one infront
\r
63 // the src property will always be an absolute one on non IE browsers and IE 8
\r
64 // so this logic will basically only be executed on older IE versions
\r
65 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
\r
66 t.baseURL = base + t.baseURL;
\r
75 nl = d.getElementsByTagName('script');
\r
76 for (i=0; i<nl.length; i++) {
\r
82 n = d.getElementsByTagName('head')[0];
\r
84 nl = n.getElementsByTagName('script');
\r
85 for (i=0; i<nl.length; i++) {
\r
94 is : function(o, t) {
\r
96 return o !== undefined;
\r
98 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
\r
101 return typeof(o) == t;
\r
104 each : function(o, cb, s) {
\r
112 if (o.length !== undefined) {
\r
113 // Indexed arrays, needed for Safari
\r
114 for (n=0, l = o.length; n < l; n++) {
\r
115 if (cb.call(s, o[n], n, o) === false)
\r
121 if (o.hasOwnProperty(n)) {
\r
122 if (cb.call(s, o[n], n, o) === false)
\r
132 trim : function(s) {
\r
133 return (s ? '' + s : '').replace(whiteSpaceRe, '');
\r
136 create : function(s, p) {
\r
137 var t = this, sp, ns, cn, scn, c, de = 0;
\r
139 // Parse : <prefix> <class>:<super class>
\r
140 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
\r
141 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
\r
143 // Create namespace for new class
\r
144 ns = t.createNS(s[3].replace(/\.\w+$/, ''));
\r
146 // Class already exists
\r
150 // Make pure static class
\r
151 if (s[2] == 'static') {
\r
155 this.onCreate(s[2], s[3], ns[cn]);
\r
160 // Create default constructor
\r
162 p[cn] = function() {};
\r
166 // Add constructor and methods
\r
168 t.extend(ns[cn].prototype, p);
\r
172 sp = t.resolve(s[5]).prototype;
\r
173 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
\r
175 // Extend constructor
\r
178 // Add passthrough constructor
\r
179 ns[cn] = function() {
\r
180 return sp[scn].apply(this, arguments);
\r
183 // Add inherit constructor
\r
184 ns[cn] = function() {
\r
185 this.parent = sp[scn];
\r
186 return c.apply(this, arguments);
\r
189 ns[cn].prototype[cn] = ns[cn];
\r
191 // Add super methods
\r
192 t.each(sp, function(f, n) {
\r
193 ns[cn].prototype[n] = sp[n];
\r
196 // Add overridden methods
\r
197 t.each(p, function(f, n) {
\r
198 // Extend methods if needed
\r
200 ns[cn].prototype[n] = function() {
\r
201 this.parent = sp[n];
\r
202 return f.apply(this, arguments);
\r
206 ns[cn].prototype[n] = f;
\r
211 // Add static methods
\r
212 t.each(p['static'], function(f, n) {
\r
217 this.onCreate(s[2], s[3], ns[cn].prototype);
\r
220 walk : function(o, f, n, s) {
\r
227 tinymce.each(o, function(o, i) {
\r
228 if (f.call(s, o, i, n) === false)
\r
231 tinymce.walk(o, f, n, s);
\r
236 createNS : function(n, o) {
\r
242 for (i=0; i<n.length; i++) {
\r
254 resolve : function(n, o) {
\r
260 for (i = 0, l = n.length; i < l; i++) {
\r
270 addUnload : function(f, s) {
\r
273 f = {func : f, scope : s || this};
\r
276 function unload() {
\r
277 var li = t.unloads, o, n;
\r
280 // Call unload handlers
\r
285 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
\r
288 // Detach unload function
\r
289 if (win.detachEvent) {
\r
290 win.detachEvent('onbeforeunload', fakeUnload);
\r
291 win.detachEvent('onunload', unload);
\r
292 } else if (win.removeEventListener)
\r
293 win.removeEventListener('unload', unload, false);
\r
295 // Destroy references
\r
296 t.unloads = o = li = w = unload = 0;
\r
298 // Run garbarge collector on IE
\r
299 if (win.CollectGarbage)
\r
304 function fakeUnload() {
\r
307 // Is there things still loading, then do some magic
\r
308 if (d.readyState == 'interactive') {
\r
310 // Prevent memory leak
\r
311 d.detachEvent('onstop', stop);
\r
313 // Call unload handler
\r
320 // Fire unload when the currently loading page is stopped
\r
322 d.attachEvent('onstop', stop);
\r
324 // Remove onstop listener after a while to prevent the unload function
\r
325 // to execute if the user presses cancel in an onbeforeunload
\r
326 // confirm dialog and then presses the browser stop button
\r
327 win.setTimeout(function() {
\r
329 d.detachEvent('onstop', stop);
\r
334 // Attach unload handler
\r
335 if (win.attachEvent) {
\r
336 win.attachEvent('onunload', unload);
\r
337 win.attachEvent('onbeforeunload', fakeUnload);
\r
338 } else if (win.addEventListener)
\r
339 win.addEventListener('unload', unload, false);
\r
341 // Setup initial unload handler array
\r
349 removeUnload : function(f) {
\r
350 var u = this.unloads, r = null;
\r
352 tinymce.each(u, function(o, i) {
\r
353 if (o && o.func == f) {
\r
363 explode : function(s, d) {
\r
364 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
\r
367 _addVer : function(u) {
\r
373 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
\r
375 if (u.indexOf('#') == -1)
\r
378 return u.replace('#', v + '#');
\r
383 // Initialize the API
\r
387 (function($, tinymce) {
\r
388 var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undefined;
\r
390 // jQuery is undefined
\r
392 return alert("Load jQuery first!");
\r
394 // Stick jQuery into the tinymce namespace
\r
398 tinymce.adapter = {
\r
399 patchEditor : function(editor) {
\r
402 // Adapt the css function to make sure that the _mce_style
\r
403 // attribute gets updated with the new style information
\r
404 function css(name, value) {
\r
407 // Remove _mce_style when set operation occurs
\r
409 self.removeAttr('_mce_style');
\r
411 return fn.css.apply(self, arguments);
\r
414 // Apapt the attr function to make sure that it uses the _mce_ prefixed variants
\r
415 function attr(name, value) {
\r
418 // Update/retrive _mce_ attribute variants
\r
419 if (attrRegExp.test(name)) {
\r
420 if (value !== undefined) {
\r
421 // Use TinyMCE behavior when setting the specifc attributes
\r
422 self.each(function(i, node) {
\r
423 editor.dom.setAttrib(node, name, value);
\r
428 return self.attr('_mce_' + name);
\r
431 // Default behavior
\r
432 return fn.attr.apply(self, arguments);
\r
435 function htmlPatchFunc(func) {
\r
436 // Returns a modified function that processes
\r
437 // the HTML before executing the action this makes sure
\r
438 // that href/src etc gets moved into the _mce_ variants
\r
439 return function(content) {
\r
441 content = editor.dom.processHTML(content);
\r
443 return func.call(this, content);
\r
447 // Patch various jQuery functions to handle tinymce specific attribute and content behavior
\r
448 // we don't patch the jQuery.fn directly since it will most likely break compatibility
\r
449 // with other jQuery logic on the page. Only instances created by TinyMCE should be patched.
\r
450 function patch(jq) {
\r
451 // Patch some functions, only patch the object once
\r
452 if (jq.css !== css) {
\r
453 // Patch css/attr to use the _mce_ prefixed attribute variants
\r
457 // Patch HTML functions to use the DOMUtils.processHTML filter logic
\r
458 jq.html = htmlPatchFunc(fn.html);
\r
459 jq.append = htmlPatchFunc(fn.append);
\r
460 jq.prepend = htmlPatchFunc(fn.prepend);
\r
461 jq.after = htmlPatchFunc(fn.after);
\r
462 jq.before = htmlPatchFunc(fn.before);
\r
463 jq.replaceWith = htmlPatchFunc(fn.replaceWith);
\r
464 jq.tinymce = editor;
\r
466 // Each pushed jQuery instance needs to be patched
\r
467 // as well for example when traversing the DOM
\r
468 jq.pushStack = function() {
\r
469 return patch(fn.pushStack.apply(this, arguments));
\r
476 // Add a $ function on each editor instance this one is scoped for the editor document object
\r
477 // this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red');
\r
478 editor.$ = function(selector, scope) {
\r
479 var doc = editor.getDoc();
\r
481 return patch($(selector || doc, doc || scope));
\r
486 // Patch in core NS functions
\r
487 tinymce.extend = $.extend;
\r
488 tinymce.extend(tinymce, {
\r
490 grep : function(a, f) {return $.grep(a, f || function(){return 1;});},
\r
491 inArray : function(a, v) {return $.inArray(v, a || []);}
\r
493 /* Didn't iterate stylesheets
\r
494 each : function(o, cb, s) {
\r
500 $.each(o, function(nr, el){
\r
501 if (cb.call(s, el, nr, o) === false) {
\r
511 // Patch in functions in various clases
\r
512 // Add a "#ifndefjquery" statement around each core API function you add below
\r
514 'tinymce.dom.DOMUtils' : {
\r
516 addClass : function(e, c) {
\r
517 if (is(e, 'array') && is(e[0], 'string'))
\r
519 return (e && $(is(e, 'string') ? '#' + e : e)
\r
521 .attr('class')) || false;
\r
524 hasClass : function(n, c) {
\r
525 return $(is(n, 'string') ? '#' + n : n).hasClass(c);
\r
528 removeClass : function(e, c) {
\r
534 $(is(e, 'string') ? '#' + e : e)
\r
537 r.push(this.className);
\r
540 return r.length == 1 ? r[0] : r;
\r
544 select : function(pattern, scope) {
\r
547 return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []);
\r
550 is : function(n, patt) {
\r
551 return $(this.get(n)).is(patt);
\r
555 show : function(e) {
\r
556 if (is(e, 'array') && is(e[0], 'string'))
\r
559 $(is(e, 'string') ? '#' + e : e).css('display', 'block');
\r
562 hide : function(e) {
\r
563 if (is(e, 'array') && is(e[0], 'string'))
\r
566 $(is(e, 'string') ? '#' + e : e).css('display', 'none');
\r
569 isHidden : function(e) {
\r
570 return $(is(e, 'string') ? '#' + e : e).is(':hidden');
\r
573 insertAfter : function(n, e) {
\r
574 return $(is(e, 'string') ? '#' + e : e).after(n);
\r
577 replace : function(o, n, k) {
\r
578 n = $(is(n, 'string') ? '#' + n : n);
\r
581 n.children().appendTo(o);
\r
586 setStyle : function(n, na, v) {
\r
587 if (is(n, 'array') && is(n[0], 'string'))
\r
590 $(is(n, 'string') ? '#' + n : n).css(na, v);
\r
593 getStyle : function(n, na, c) {
\r
594 return $(is(n, 'string') ? '#' + n : n).css(na);
\r
597 setStyles : function(e, o) {
\r
598 if (is(e, 'array') && is(e[0], 'string'))
\r
600 $(is(e, 'string') ? '#' + e : e).css(o);
\r
603 setAttrib : function(e, n, v) {
\r
604 var t = this, s = t.settings;
\r
606 if (is(e, 'array') && is(e[0], 'string'))
\r
609 e = $(is(e, 'string') ? '#' + e : e);
\r
613 e.each(function(i, v){
\r
615 $(v).attr('_mce_style', v);
\r
617 v.style.cssText = v;
\r
623 this.className = v;
\r
629 e.each(function(i, v){
\r
630 if (s.keep_values) {
\r
631 if (s.url_converter)
\r
632 v = s.url_converter.call(s.url_converter_scope || t, v, n, v);
\r
634 t.setAttrib(v, '_mce_' + n, v);
\r
641 if (v !== null && v.length !== 0)
\r
647 setAttribs : function(e, o) {
\r
650 $.each(o, function(n, v){
\r
651 t.setAttrib(e,n,v);
\r
658 'tinymce.dom.Event' : {
\r
659 add : function (o, n, f, s) {
\r
663 e.target = e.target || this;
\r
664 f.call(s || this, e);
\r
667 if (is(o, 'array') && is(o[0], 'string'))
\r
669 o = $(is(o, 'string') ? '#' + o : o);
\r
680 lo = this._jqLookup || (this._jqLookup = []);
\r
681 lo.push({func : f, cfunc : cb});
\r
686 remove : function(o, n, f) {
\r
688 $(this._jqLookup).each(function() {
\r
689 if (this.func === f)
\r
693 if (is(o, 'array') && is(o[0], 'string'))
\r
696 $(is(o, 'string') ? '#' + o : o).unbind(n,f);
\r
704 // Patch functions after a class is created
\r
705 tinymce.onCreate = function(ty, c, p) {
\r
706 tinymce.extend(p, patches[c]);
\r
708 })(jQuery, tinymce);
\r
710 tinymce.create('tinymce.util.Dispatcher', {
\r
714 Dispatcher : function(s) {
\r
715 this.scope = s || this;
\r
716 this.listeners = [];
\r
719 add : function(cb, s) {
\r
720 this.listeners.push({cb : cb, scope : s || this.scope});
\r
725 addToTop : function(cb, s) {
\r
726 this.listeners.unshift({cb : cb, scope : s || this.scope});
\r
731 remove : function(cb) {
\r
732 var l = this.listeners, o = null;
\r
734 tinymce.each(l, function(c, i) {
\r
745 dispatch : function() {
\r
746 var s, a = arguments, i, li = this.listeners, c;
\r
748 // Needs to be a real loop since the listener count might change while looping
\r
749 // And this is also more efficient
\r
750 for (i = 0; i<li.length; i++) {
\r
752 s = c.cb.apply(c.scope, a);
\r
763 var each = tinymce.each;
\r
765 tinymce.create('tinymce.util.URI', {
\r
766 URI : function(u, s) {
\r
767 var t = this, o, a, b;
\r
770 u = tinymce.trim(u);
\r
772 // Default settings
\r
773 s = t.settings = s || {};
\r
775 // Strange app protocol or local anchor
\r
776 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
\r
781 // Absolute path with no host, fake host and protocol
\r
782 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
\r
783 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
\r
785 // Relative path http:// or protocol relative //path
\r
786 if (!/^\w*:?\/\//.test(u))
\r
787 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
\r
789 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
\r
790 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
\r
791 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
\r
792 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
\r
795 // Zope 3 workaround, they use @@something
\r
797 s = s.replace(/\(mce_at\)/g, '@@');
\r
802 if (b = s.base_uri) {
\r
804 t.protocol = b.protocol;
\r
807 t.userInfo = b.userInfo;
\r
809 if (!t.port && t.host == 'mce_host')
\r
812 if (!t.host || t.host == 'mce_host')
\r
818 //t.path = t.path || '/';
\r
821 setPath : function(p) {
\r
824 p = /^(.*?)\/?(\w+)?$/.exec(p);
\r
826 // Update path parts
\r
828 t.directory = p[1];
\r
836 toRelative : function(u) {
\r
842 u = new tinymce.util.URI(u, {base_uri : t});
\r
844 // Not on same domain/port or protocol
\r
845 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
\r
848 o = t.toRelPath(t.path, u.path);
\r
852 o += '?' + u.query;
\r
856 o += '#' + u.anchor;
\r
861 toAbsolute : function(u, nh) {
\r
862 var u = new tinymce.util.URI(u, {base_uri : this});
\r
864 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
\r
867 toRelPath : function(base, path) {
\r
868 var items, bp = 0, out = '', i, l;
\r
871 base = base.substring(0, base.lastIndexOf('/'));
\r
872 base = base.split('/');
\r
873 items = path.split('/');
\r
875 if (base.length >= items.length) {
\r
876 for (i = 0, l = base.length; i < l; i++) {
\r
877 if (i >= items.length || base[i] != items[i]) {
\r
884 if (base.length < items.length) {
\r
885 for (i = 0, l = items.length; i < l; i++) {
\r
886 if (i >= base.length || base[i] != items[i]) {
\r
896 for (i = 0, l = base.length - (bp - 1); i < l; i++)
\r
899 for (i = bp - 1, l = items.length; i < l; i++) {
\r
901 out += "/" + items[i];
\r
909 toAbsPath : function(base, path) {
\r
910 var i, nb = 0, o = [], tr, outPath;
\r
913 tr = /\/$/.test(path) ? '/' : '';
\r
914 base = base.split('/');
\r
915 path = path.split('/');
\r
917 // Remove empty chunks
\r
918 each(base, function(k) {
\r
925 // Merge relURLParts chunks
\r
926 for (i = path.length - 1, o = []; i >= 0; i--) {
\r
927 // Ignore empty or .
\r
928 if (path[i].length == 0 || path[i] == ".")
\r
932 if (path[i] == '..') {
\r
946 i = base.length - nb;
\r
950 outPath = o.reverse().join('/');
\r
952 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
\r
954 // Add front / if it's needed
\r
955 if (outPath.indexOf('/') !== 0)
\r
956 outPath = '/' + outPath;
\r
958 // Add traling / if it's needed
\r
959 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
\r
965 getURI : function(nh) {
\r
969 if (!t.source || nh) {
\r
974 s += t.protocol + '://';
\r
977 s += t.userInfo + '@';
\r
990 s += '?' + t.query;
\r
993 s += '#' + t.anchor;
\r
1003 var each = tinymce.each;
\r
1005 tinymce.create('static tinymce.util.Cookie', {
\r
1006 getHash : function(n) {
\r
1007 var v = this.get(n), h;
\r
1010 each(v.split('&'), function(v) {
\r
1013 h[unescape(v[0])] = unescape(v[1]);
\r
1020 setHash : function(n, v, e, p, d, s) {
\r
1023 each(v, function(v, k) {
\r
1024 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
\r
1027 this.set(n, o, e, p, d, s);
\r
1030 get : function(n) {
\r
1031 var c = document.cookie, e, p = n + "=", b;
\r
1037 b = c.indexOf("; " + p);
\r
1047 e = c.indexOf(";", b);
\r
1052 return unescape(c.substring(b + p.length, e));
\r
1055 set : function(n, v, e, p, d, s) {
\r
1056 document.cookie = n + "=" + escape(v) +
\r
1057 ((e) ? "; expires=" + e.toGMTString() : "") +
\r
1058 ((p) ? "; path=" + escape(p) : "") +
\r
1059 ((d) ? "; domain=" + d : "") +
\r
1060 ((s) ? "; secure" : "");
\r
1063 remove : function(n, p) {
\r
1064 var d = new Date();
\r
1066 d.setTime(d.getTime() - 1000);
\r
1068 this.set(n, '', d, p, d);
\r
1072 tinymce.create('static tinymce.util.JSON', {
\r
1073 serialize : function(o) {
\r
1074 var i, v, s = tinymce.util.JSON.serialize, t;
\r
1081 if (t == 'string') {
\r
1082 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
\r
1084 return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
\r
1088 return '\\' + v.charAt(i + 1);
\r
1090 a = b.charCodeAt().toString(16);
\r
1092 return '\\u' + '0000'.substring(a.length) + a;
\r
1096 if (t == 'object') {
\r
1097 if (o.hasOwnProperty && o instanceof Array) {
\r
1098 for (i=0, v = '['; i<o.length; i++)
\r
1099 v += (i > 0 ? ',' : '') + s(o[i]);
\r
1107 v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
\r
1115 parse : function(s) {
\r
1117 return eval('(' + s + ')');
\r
1124 tinymce.create('static tinymce.util.XHR', {
\r
1125 send : function(o) {
\r
1126 var x, t, w = window, c = 0;
\r
1128 // Default settings
\r
1129 o.scope = o.scope || this;
\r
1130 o.success_scope = o.success_scope || o.scope;
\r
1131 o.error_scope = o.error_scope || o.scope;
\r
1132 o.async = o.async === false ? false : true;
\r
1133 o.data = o.data || '';
\r
1139 x = new ActiveXObject(s);
\r
1146 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
\r
1149 if (x.overrideMimeType)
\r
1150 x.overrideMimeType(o.content_type);
\r
1152 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
\r
1154 if (o.content_type)
\r
1155 x.setRequestHeader('Content-Type', o.content_type);
\r
1157 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
\r
1161 function ready() {
\r
1162 if (!o.async || x.readyState == 4 || c++ > 10000) {
\r
1163 if (o.success && c < 10000 && x.status == 200)
\r
1164 o.success.call(o.success_scope, '' + x.responseText, x, o);
\r
1166 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
\r
1170 w.setTimeout(ready, 10);
\r
1173 // Syncronous request
\r
1177 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
\r
1178 t = w.setTimeout(ready, 10);
\r
1183 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
\r
1185 tinymce.create('tinymce.util.JSONRequest', {
\r
1186 JSONRequest : function(s) {
\r
1187 this.settings = extend({
\r
1192 send : function(o) {
\r
1193 var ecb = o.error, scb = o.success;
\r
1195 o = extend(this.settings, o);
\r
1197 o.success = function(c, x) {
\r
1198 c = JSON.parse(c);
\r
1200 if (typeof(c) == 'undefined') {
\r
1202 error : 'JSON Parse error.'
\r
1207 ecb.call(o.error_scope || o.scope, c.error, x);
\r
1209 scb.call(o.success_scope || o.scope, c.result);
\r
1212 o.error = function(ty, x) {
\r
1213 ecb.call(o.error_scope || o.scope, ty, x);
\r
1216 o.data = JSON.serialize({
\r
1217 id : o.id || 'c' + (this.count++),
\r
1218 method : o.method,
\r
1222 // JSON content type for Ruby on rails. Bug: #1883287
\r
1223 o.content_type = 'application/json';
\r
1229 sendRPC : function(o) {
\r
1230 return new tinymce.util.JSONRequest().send(o);
\r
1234 }());(function(tinymce) {
\r
1236 var each = tinymce.each,
\r
1238 isWebKit = tinymce.isWebKit,
\r
1239 isIE = tinymce.isIE,
\r
1240 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
1241 boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
\r
1242 mceAttribs = makeMap('src,href,style,coords,shape'),
\r
1243 encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'},
\r
1244 encodeCharsRe = /[<>&\"]/g,
\r
1245 simpleSelectorRe = /^([a-z0-9],?)+$/i,
\r
1246 tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
\r
1247 attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
\r
1249 function makeMap(str) {
\r
1252 str = str.split(',');
\r
1253 for (i = str.length; i >= 0; i--)
\r
1259 tinymce.create('tinymce.dom.DOMUtils', {
\r
1263 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
\r
1265 "for" : "htmlFor",
\r
1266 "class" : "className",
\r
1267 className : "className",
\r
1268 checked : "checked",
\r
1269 disabled : "disabled",
\r
1270 maxlength : "maxLength",
\r
1271 readonly : "readOnly",
\r
1272 selected : "selected",
\r
1279 DOMUtils : function(d, s) {
\r
1280 var t = this, globalStyle;
\r
1285 t.cssFlicker = false;
\r
1287 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat";
\r
1288 t.stdMode = d.documentMode === 8;
\r
1290 t.settings = s = tinymce.extend({
\r
1291 keep_values : false,
\r
1296 // Fix IE6SP2 flicker and check it failed for pre SP2
\r
1297 if (tinymce.isIE6) {
\r
1299 d.execCommand('BackgroundImageCache', false, true);
\r
1301 t.cssFlicker = true;
\r
1305 // Build styles list
\r
1306 if (s.valid_styles) {
\r
1309 // Convert styles into a rule list
\r
1310 each(s.valid_styles, function(value, key) {
\r
1311 t._styles[key] = tinymce.explode(value);
\r
1315 tinymce.addUnload(t.destroy, t);
\r
1318 getRoot : function() {
\r
1319 var t = this, s = t.settings;
\r
1321 return (s && t.get(s.root_element)) || t.doc.body;
\r
1324 getViewPort : function(w) {
\r
1327 w = !w ? this.win : w;
\r
1329 b = this.boxModel ? d.documentElement : d.body;
\r
1331 // Returns viewport size excluding scrollbars
\r
1333 x : w.pageXOffset || b.scrollLeft,
\r
1334 y : w.pageYOffset || b.scrollTop,
\r
1335 w : w.innerWidth || b.clientWidth,
\r
1336 h : w.innerHeight || b.clientHeight
\r
1340 getRect : function(e) {
\r
1341 var p, t = this, sr;
\r
1345 sr = t.getSize(e);
\r
1355 getSize : function(e) {
\r
1356 var t = this, w, h;
\r
1359 w = t.getStyle(e, 'width');
\r
1360 h = t.getStyle(e, 'height');
\r
1362 // Non pixel value, then force offset/clientWidth
\r
1363 if (w.indexOf('px') === -1)
\r
1366 // Non pixel value, then force offset/clientWidth
\r
1367 if (h.indexOf('px') === -1)
\r
1371 w : parseInt(w) || e.offsetWidth || e.clientWidth,
\r
1372 h : parseInt(h) || e.offsetHeight || e.clientHeight
\r
1376 getParent : function(n, f, r) {
\r
1377 return this.getParents(n, f, r, false);
\r
1380 getParents : function(n, f, r, c) {
\r
1381 var t = this, na, se = t.settings, o = [];
\r
1384 c = c === undefined;
\r
1386 if (se.strict_root)
\r
1387 r = r || t.getRoot();
\r
1389 // Wrap node name as func
\r
1390 if (is(f, 'string')) {
\r
1394 f = function(n) {return n.nodeType == 1;};
\r
1397 return t.is(n, na);
\r
1403 if (n == r || !n.nodeType || n.nodeType === 9)
\r
1416 return c ? o : null;
\r
1419 get : function(e) {
\r
1422 if (e && this.doc && typeof(e) == 'string') {
\r
1424 e = this.doc.getElementById(e);
\r
1426 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
\r
1427 if (e && e.id !== n)
\r
1428 return this.doc.getElementsByName(n)[1];
\r
1434 getNext : function(node, selector) {
\r
1435 return this._findSib(node, selector, 'nextSibling');
\r
1438 getPrev : function(node, selector) {
\r
1439 return this._findSib(node, selector, 'previousSibling');
\r
1443 add : function(p, n, a, h, c) {
\r
1446 return this.run(p, function(p) {
\r
1449 e = is(n, 'string') ? t.doc.createElement(n) : n;
\r
1450 t.setAttribs(e, a);
\r
1459 return !c ? p.appendChild(e) : e;
\r
1463 create : function(n, a, h) {
\r
1464 return this.add(this.doc.createElement(n), n, a, h, 1);
\r
1467 createHTML : function(n, a, h) {
\r
1468 var o = '', t = this, k;
\r
1473 if (a.hasOwnProperty(k))
\r
1474 o += ' ' + k + '="' + t.encode(a[k]) + '"';
\r
1477 if (tinymce.is(h))
\r
1478 return o + '>' + h + '</' + n + '>';
\r
1483 remove : function(n, k) {
\r
1486 return this.run(n, function(n) {
\r
1495 for (i = n.childNodes.length - 1; i >= 0; i--)
\r
1496 t.insertAfter(n.childNodes[i], n);
\r
1498 //each(n.childNodes, function(c) {
\r
1499 // p.insertBefore(c.cloneNode(true), n);
\r
1503 // Fix IE psuedo leak
\r
1504 if (t.fixPsuedoLeaks) {
\r
1505 p = n.cloneNode(true);
\r
1506 k = 'IELeakGarbageBin';
\r
1507 g = t.get(k) || t.add(t.doc.body, 'div', {id : k, style : 'display:none'});
\r
1514 return p.removeChild(n);
\r
1518 setStyle : function(n, na, v) {
\r
1521 return t.run(n, function(e) {
\r
1526 // Camelcase it, if needed
\r
1527 na = na.replace(/-(\D)/g, function(a, b){
\r
1528 return b.toUpperCase();
\r
1531 // Default px suffix on these
\r
1532 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
\r
1537 // IE specific opacity
\r
1539 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
\r
1541 if (!n.currentStyle || !n.currentStyle.hasLayout)
\r
1542 s.display = 'inline-block';
\r
1545 // Fix for older browsers
\r
1546 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
\r
1550 isIE ? s.styleFloat = v : s.cssFloat = v;
\r
1557 // Force update of the style data
\r
1558 if (t.settings.update_styles)
\r
1559 t.setAttrib(e, '_mce_style');
\r
1563 getStyle : function(n, na, c) {
\r
1570 if (this.doc.defaultView && c) {
\r
1571 // Remove camelcase
\r
1572 na = na.replace(/[A-Z]/g, function(a){
\r
1577 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
\r
1579 // Old safari might fail
\r
1584 // Camelcase it, if needed
\r
1585 na = na.replace(/-(\D)/g, function(a, b){
\r
1586 return b.toUpperCase();
\r
1589 if (na == 'float')
\r
1590 na = isIE ? 'styleFloat' : 'cssFloat';
\r
1593 if (n.currentStyle && c)
\r
1594 return n.currentStyle[na];
\r
1596 return n.style[na];
\r
1599 setStyles : function(e, o) {
\r
1600 var t = this, s = t.settings, ol;
\r
1602 ol = s.update_styles;
\r
1603 s.update_styles = 0;
\r
1605 each(o, function(v, n) {
\r
1606 t.setStyle(e, n, v);
\r
1609 // Update style info
\r
1610 s.update_styles = ol;
\r
1611 if (s.update_styles)
\r
1612 t.setAttrib(e, s.cssText);
\r
1615 setAttrib : function(e, n, v) {
\r
1618 // Whats the point
\r
1622 // Strict XML mode
\r
1623 if (t.settings.strict)
\r
1624 n = n.toLowerCase();
\r
1626 return this.run(e, function(e) {
\r
1627 var s = t.settings;
\r
1631 if (!is(v, 'string')) {
\r
1632 each(v, function(v, n) {
\r
1633 t.setStyle(e, n, v);
\r
1639 // No mce_style for elements with these since they might get resized by the user
\r
1640 if (s.keep_values) {
\r
1641 if (v && !t._isRes(v))
\r
1642 e.setAttribute('_mce_style', v, 2);
\r
1644 e.removeAttribute('_mce_style', 2);
\r
1647 e.style.cssText = v;
\r
1651 e.className = v || ''; // Fix IE null bug
\r
1656 if (s.keep_values) {
\r
1657 if (s.url_converter)
\r
1658 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
\r
1660 t.setAttrib(e, '_mce_' + n, v, 2);
\r
1666 e.setAttribute('_mce_style', v);
\r
1670 if (is(v) && v !== null && v.length !== 0)
\r
1671 e.setAttribute(n, '' + v, 2);
\r
1673 e.removeAttribute(n, 2);
\r
1677 setAttribs : function(e, o) {
\r
1680 return this.run(e, function(e) {
\r
1681 each(o, function(v, n) {
\r
1682 t.setAttrib(e, n, v);
\r
1687 getAttrib : function(e, n, dv) {
\r
1692 if (!e || e.nodeType !== 1)
\r
1698 // Try the mce variant for these
\r
1699 if (/^(src|href|style|coords|shape)$/.test(n)) {
\r
1700 v = e.getAttribute("_mce_" + n);
\r
1706 if (isIE && t.props[n]) {
\r
1707 v = e[t.props[n]];
\r
1708 v = v && v.nodeValue ? v.nodeValue : v;
\r
1712 v = e.getAttribute(n, 2);
\r
1714 // Check boolean attribs
\r
1715 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
\r
1716 if (e[t.props[n]] === true && v === '')
\r
1719 return v ? n : '';
\r
1722 // Inner input elements will override attributes on form elements
\r
1723 if (e.nodeName === "FORM" && e.getAttributeNode(n))
\r
1724 return e.getAttributeNode(n).nodeValue;
\r
1726 if (n === 'style') {
\r
1727 v = v || e.style.cssText;
\r
1730 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
\r
1732 if (t.settings.keep_values && !t._isRes(v))
\r
1733 e.setAttribute('_mce_style', v);
\r
1737 // Remove Apple and WebKit stuff
\r
1738 if (isWebKit && n === "class" && v)
\r
1739 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
\r
1741 // Handle IE issues
\r
1746 // IE returns 1 as default value
\r
1753 // IE returns +0 as default value for size
\r
1754 if (v === '+0' || v === 20 || v === 0)
\r
1771 // IE returns -1 as default value
\r
1779 // IE returns default value
\r
1780 if (v === 32768 || v === 2147483647 || v === '32768')
\r
1795 v = v.toLowerCase();
\r
1799 // IE has odd anonymous function for event attributes
\r
1800 if (n.indexOf('on') === 0 && v)
\r
1801 v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
\r
1805 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
\r
1808 getPos : function(n, ro) {
\r
1809 var t = this, x = 0, y = 0, e, d = t.doc, r;
\r
1812 ro = ro || d.body;
\r
1815 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
\r
1816 if (isIE && !t.stdMode) {
\r
1817 n = n.getBoundingClientRect();
\r
1818 e = t.boxModel ? d.documentElement : d.body;
\r
1819 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
\r
1820 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
\r
1821 n.top += t.win.self != t.win.top ? 2 : 0; // IE adds some strange extra cord if used in a frameset
\r
1823 return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
\r
1827 while (r && r != ro && r.nodeType) {
\r
1828 x += r.offsetLeft || 0;
\r
1829 y += r.offsetTop || 0;
\r
1830 r = r.offsetParent;
\r
1834 while (r && r != ro && r.nodeType) {
\r
1835 x -= r.scrollLeft || 0;
\r
1836 y -= r.scrollTop || 0;
\r
1841 return {x : x, y : y};
\r
1844 parseStyle : function(st) {
\r
1845 var t = this, s = t.settings, o = {};
\r
1850 function compress(p, s, ot) {
\r
1853 // Get values and check it it needs compressing
\r
1854 t = o[p + '-top' + s];
\r
1858 r = o[p + '-right' + s];
\r
1862 b = o[p + '-bottom' + s];
\r
1866 l = o[p + '-left' + s];
\r
1872 delete o[p + '-top' + s];
\r
1873 delete o[p + '-right' + s];
\r
1874 delete o[p + '-bottom' + s];
\r
1875 delete o[p + '-left' + s];
\r
1878 function compress2(ta, a, b, c) {
\r
1894 o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
\r
1900 st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
\r
1902 each(st.split(';'), function(v) {
\r
1906 v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
\r
1907 v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
\r
1909 sv = tinymce.trim(v[1]);
\r
1910 sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
\r
1912 sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
\r
1913 return t.toHex(v);
\r
1916 if (s.url_converter) {
\r
1917 sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
\r
1918 return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
\r
1922 o[tinymce.trim(v[0]).toLowerCase()] = sv;
\r
1926 compress("border", "", "border");
\r
1927 compress("border", "-width", "border-width");
\r
1928 compress("border", "-color", "border-color");
\r
1929 compress("border", "-style", "border-style");
\r
1930 compress("padding", "", "padding");
\r
1931 compress("margin", "", "margin");
\r
1932 compress2('border', 'border-width', 'border-style', 'border-color');
\r
1935 // Remove pointless border
\r
1936 if (o.border == 'medium none')
\r
1943 serializeStyle : function(o, name) {
\r
1944 var t = this, s = '';
\r
1946 function add(v, k) {
\r
1948 // Remove browser specific styles like -moz- or -webkit-
\r
1949 if (k.indexOf('-') === 0)
\r
1953 case 'font-weight':
\r
1954 // Opera will output bold as 700
\r
1961 case 'background-color':
\r
1962 v = v.toLowerCase();
\r
1966 s += (s ? ' ' : '') + k + ': ' + v + ';';
\r
1970 // Validate style output
\r
1971 if (name && t._styles) {
\r
1972 each(t._styles['*'], function(name) {
\r
1973 add(o[name], name);
\r
1976 each(t._styles[name.toLowerCase()], function(name) {
\r
1977 add(o[name], name);
\r
1985 loadCSS : function(u) {
\r
1986 var t = this, d = t.doc, head;
\r
1991 head = t.select('head')[0];
\r
1993 each(u.split(','), function(u) {
\r
1999 t.files[u] = true;
\r
2000 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
\r
2002 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
\r
2003 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
\r
2004 // It's ugly but it seems to work fine.
\r
2005 if (isIE && d.documentMode) {
\r
2006 link.onload = function() {
\r
2008 link.onload = null;
\r
2012 head.appendChild(link);
\r
2016 addClass : function(e, c) {
\r
2017 return this.run(e, function(e) {
\r
2023 if (this.hasClass(e, c))
\r
2024 return e.className;
\r
2026 o = this.removeClass(e, c);
\r
2028 return e.className = (o != '' ? (o + ' ') : '') + c;
\r
2032 removeClass : function(e, c) {
\r
2035 return t.run(e, function(e) {
\r
2038 if (t.hasClass(e, c)) {
\r
2040 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
\r
2042 v = e.className.replace(re, ' ');
\r
2043 v = tinymce.trim(v != ' ' ? v : '');
\r
2047 // Empty class attr
\r
2049 e.removeAttribute('class');
\r
2054 return e.className;
\r
2058 hasClass : function(n, c) {
\r
2064 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
\r
2067 show : function(e) {
\r
2068 return this.setStyle(e, 'display', 'block');
\r
2071 hide : function(e) {
\r
2072 return this.setStyle(e, 'display', 'none');
\r
2075 isHidden : function(e) {
\r
2078 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
\r
2081 uniqueId : function(p) {
\r
2082 return (!p ? 'mce_' : p) + (this.counter++);
\r
2085 setHTML : function(e, h) {
\r
2088 return this.run(e, function(e) {
\r
2089 var x, i, nl, n, p, x;
\r
2091 h = t.processHTML(h);
\r
2095 // Remove all child nodes
\r
2096 while (e.firstChild)
\r
2097 e.firstChild.removeNode();
\r
2100 // IE will remove comments from the beginning
\r
2101 // unless you padd the contents with something
\r
2102 e.innerHTML = '<br />' + h;
\r
2103 e.removeChild(e.firstChild);
\r
2105 // 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
2106 // This seems to fix this problem
\r
2108 // Create new div with HTML contents and a BR infront to keep comments
\r
2109 x = t.create('div');
\r
2110 x.innerHTML = '<br />' + h;
\r
2112 // Add all children from div to target
\r
2113 each (x.childNodes, function(n, i) {
\r
2114 // Skip br element
\r
2121 // IE has a serious bug when it comes to paragraphs it can produce an invalid
\r
2122 // DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
\r
2123 // It seems to be that IE doesn't like a root block element placed inside another root block element
\r
2124 if (t.settings.fix_ie_paragraphs)
\r
2125 h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true"> </p>');
\r
2129 if (t.settings.fix_ie_paragraphs) {
\r
2130 // Check for odd paragraphs this is a sign of a broken DOM
\r
2131 nl = e.getElementsByTagName("p");
\r
2132 for (i = nl.length - 1, x = 0; i >= 0; i--) {
\r
2135 if (!n.hasChildNodes()) {
\r
2136 if (!n._mce_keep) {
\r
2137 x = 1; // Is broken
\r
2141 n.removeAttribute('_mce_keep');
\r
2146 // Time to fix the madness IE left us
\r
2148 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
\r
2149 // after we use innerHTML we can fix the DOM tree
\r
2150 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
\r
2151 h = h.replace(/<\/p>/g, '</div>');
\r
2153 // Set the new HTML with DIVs
\r
2156 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
\r
2157 // This is needed since IE has a annoying bug see above for details
\r
2158 // This is a slow process but it has to be done. :(
\r
2159 if (t.settings.fix_ie_paragraphs) {
\r
2160 nl = e.getElementsByTagName("DIV");
\r
2161 for (i = nl.length - 1; i >= 0; i--) {
\r
2164 // Is it a temp div
\r
2166 // Create new paragraph
\r
2167 p = t.doc.createElement('p');
\r
2169 // Copy all attributes
\r
2170 n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
\r
2173 if (b !== '_mce_tmp') {
\r
2174 v = n.getAttribute(b);
\r
2176 if (!v && b === 'class')
\r
2179 p.setAttribute(b, v);
\r
2183 // Append all children to new paragraph
\r
2184 for (x = 0; x<n.childNodes.length; x++)
\r
2185 p.appendChild(n.childNodes[x].cloneNode(true));
\r
2187 // Replace div with new paragraph
\r
2200 processHTML : function(h) {
\r
2201 var t = this, s = t.settings, codeBlocks = [];
\r
2203 if (!s.process_html)
\r
2207 h = h.replace(/'/g, '''); // IE can't handle apos
\r
2208 h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
\r
2211 // Fix some issues
\r
2212 h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
\r
2214 // Store away src and href in _mce_src and mce_href since browsers mess them up
\r
2215 if (s.keep_values) {
\r
2216 // Wrap scripts and styles in comments for serialization purposes
\r
2217 if (/<script|noscript|style/i.test(h)) {
\r
2218 function trim(s) {
\r
2219 // Remove prefix and suffix code for element
\r
2220 s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
\r
2221 s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
\r
2222 s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
\r
2223 s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
\r
2228 // Wrap the script contents in CDATA and keep them from executing
\r
2229 h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
\r
2230 // Force type attribute
\r
2232 attribs = ' type="text/javascript"';
\r
2234 // Convert the src attribute of the scripts
\r
2235 attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
\r
2236 if (s.url_converter)
\r
2237 url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
\r
2239 return '_mce_src="' + url + '"';
\r
2242 // Wrap text contents
\r
2243 if (tinymce.trim(text)) {
\r
2244 codeBlocks.push(trim(text));
\r
2245 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
\r
2248 return '<mce:script' + attribs + '>' + text + '</mce:script>';
\r
2251 // Wrap style elements
\r
2252 h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
\r
2253 // Wrap text contents
\r
2255 codeBlocks.push(trim(text));
\r
2256 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
\r
2259 return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
\r
2262 // Wrap noscript elements
\r
2263 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
2264 return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '--') + '--></mce:noscript>';
\r
2268 h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
\r
2270 // This function processes the attributes in the HTML string to force boolean
\r
2271 // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
\r
2272 function processTags(html) {
\r
2273 return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
\r
2274 return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
\r
2277 name = name.toLowerCase();
\r
2278 value = value || val2 || val3 || "";
\r
2280 // Treat boolean attributes
\r
2281 if (boolAttrs[name]) {
\r
2282 // false or 0 is treated as a missing attribute
\r
2283 if (value === 'false' || value === '0')
\r
2286 return name + '="' + name + '"';
\r
2289 // Is attribute one that needs special treatment
\r
2290 if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
\r
2291 mceValue = t.decode(value);
\r
2293 // Convert URLs to relative/absolute ones
\r
2294 if (s.url_converter && (name == "src" || name == "href"))
\r
2295 mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
\r
2297 // Process styles lowercases them and compresses them
\r
2298 if (name == 'style')
\r
2299 mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
\r
2301 return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
\r
2309 h = processTags(h);
\r
2311 // Restore script blocks
\r
2312 h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
\r
2313 return codeBlocks[idx];
\r
2320 getOuterHTML : function(e) {
\r
2328 if (e.outerHTML !== undefined)
\r
2329 return e.outerHTML;
\r
2331 d = (e.ownerDocument || this.doc).createElement("body");
\r
2332 d.appendChild(e.cloneNode(true));
\r
2334 return d.innerHTML;
\r
2337 setOuterHTML : function(e, h, d) {
\r
2340 function setHTML(e, h, d) {
\r
2343 tp = d.createElement("body");
\r
2348 t.insertAfter(n.cloneNode(true), e);
\r
2349 n = n.previousSibling;
\r
2355 return this.run(e, function(e) {
\r
2358 // Only set HTML on elements
\r
2359 if (e.nodeType == 1) {
\r
2360 d = d || e.ownerDocument || t.doc;
\r
2364 // Try outerHTML for IE it sometimes produces an unknown runtime error
\r
2365 if (isIE && e.nodeType == 1)
\r
2370 // Fix for unknown runtime error
\r
2379 decode : function(s) {
\r
2382 // Look for entities to decode
\r
2383 if (/&[\w#]+;/.test(s)) {
\r
2384 // Decode the entities using a div element not super efficient but less code
\r
2385 e = this.doc.createElement("div");
\r
2393 } while (n = n.nextSibling);
\r
2402 encode : function(str) {
\r
2403 return ('' + str).replace(encodeCharsRe, function(chr) {
\r
2404 return encodedChars[chr];
\r
2408 insertAfter : function(n, r) {
\r
2413 return this.run(n, function(n) {
\r
2417 ns = r.nextSibling;
\r
2420 p.insertBefore(n, ns);
\r
2428 isBlock : function(n) {
\r
2429 if (n.nodeType && n.nodeType !== 1)
\r
2432 n = n.nodeName || n;
\r
2434 return blockRe.test(n);
\r
2437 replace : function(n, o, k) {
\r
2440 if (is(o, 'array'))
\r
2441 n = n.cloneNode(true);
\r
2443 return t.run(o, function(o) {
\r
2445 each(tinymce.grep(o.childNodes), function(c) {
\r
2450 // Fix IE psuedo leak for elements since replacing elements if fairly common
\r
2451 // Will break parentNode for some unknown reason
\r
2452 if (t.fixPsuedoLeaks && o.nodeType === 1) {
\r
2453 o.parentNode.insertBefore(n, o);
\r
2458 return o.parentNode.replaceChild(n, o);
\r
2462 rename : function(elm, name) {
\r
2463 var t = this, newElm;
\r
2465 if (elm.nodeName != name.toUpperCase()) {
\r
2466 // Rename block element
\r
2467 newElm = t.create(name);
\r
2469 // Copy attribs to new block
\r
2470 each(t.getAttribs(elm), function(attr_node) {
\r
2471 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
\r
2475 t.replace(newElm, elm, 1);
\r
2478 return newElm || elm;
\r
2481 findCommonAncestor : function(a, b) {
\r
2487 while (pe && ps != pe)
\r
2488 pe = pe.parentNode;
\r
2493 ps = ps.parentNode;
\r
2496 if (!ps && a.ownerDocument)
\r
2497 return a.ownerDocument.documentElement;
\r
2502 toHex : function(s) {
\r
2503 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
\r
2506 s = parseInt(s).toString(16);
\r
2508 return s.length > 1 ? s : '0' + s; // 0 -> 00
\r
2512 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
\r
2520 getClasses : function() {
\r
2521 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
\r
2526 function addClasses(s) {
\r
2527 // IE style imports
\r
2528 each(s.imports, function(r) {
\r
2532 each(s.cssRules || s.rules, function(r) {
\r
2533 // Real type or fake it on IE
\r
2534 switch (r.type || 1) {
\r
2537 if (r.selectorText) {
\r
2538 each(r.selectorText.split(','), function(v) {
\r
2539 v = v.replace(/^\s*|\s*$|^\s\./g, "");
\r
2541 // Is internal or it doesn't contain a class
\r
2542 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
\r
2545 // Remove everything but class name
\r
2547 v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
\r
2550 if (f && !(v = f(v, ov)))
\r
2554 cl.push({'class' : v});
\r
2563 addClasses(r.styleSheet);
\r
2570 each(t.doc.styleSheets, addClasses);
\r
2575 if (cl.length > 0)
\r
2581 run : function(e, f, s) {
\r
2584 if (t.doc && typeof(e) === 'string')
\r
2591 if (!e.nodeType && (e.length || e.length === 0)) {
\r
2594 each(e, function(e, i) {
\r
2596 if (typeof(e) == 'string')
\r
2597 e = t.doc.getElementById(e);
\r
2599 o.push(f.call(s, e, i));
\r
2606 return f.call(s, e);
\r
2609 getAttribs : function(n) {
\r
2620 // Object will throw exception in IE
\r
2621 if (n.nodeName == 'OBJECT')
\r
2622 return n.attributes;
\r
2624 // IE doesn't keep the selected attribute if you clone option elements
\r
2625 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
\r
2626 o.push({specified : 1, nodeName : 'selected'});
\r
2628 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
\r
2629 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
\r
2630 o.push({specified : 1, nodeName : a});
\r
2636 return n.attributes;
\r
2639 destroy : function(s) {
\r
2643 t.events.destroy();
\r
2645 t.win = t.doc = t.root = t.events = null;
\r
2647 // Manual destroy then remove unload handler
\r
2649 tinymce.removeUnload(t.destroy);
\r
2652 createRng : function() {
\r
2655 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
\r
2658 nodeIndex : function(node, normalized) {
\r
2659 var idx = 0, lastNode, nodeType;
\r
2662 for (node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
\r
2663 nodeType = node.nodeType;
\r
2665 // Text nodes needs special treatment if the normalized argument is specified
\r
2666 if (normalized && nodeType == 3) {
\r
2667 // Checks if the current node has contents and that the last node is a non text node or empty
\r
2668 if (node.nodeValue.length > 0 && (lastNode.nodeType != nodeType || lastNode.nodeValue.length === 0))
\r
2680 split : function(pe, e, re) {
\r
2681 var t = this, r = t.createRng(), bef, aft, pa;
\r
2683 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
\r
2684 // but we don't want that in our code since it serves no purpose for the end user
\r
2685 // For example if this is chopped:
\r
2686 // <p>text 1<span><b>CHOP</b></span>text 2</p>
\r
2688 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
\r
2689 // this function will then trim of empty edges and produce:
\r
2690 // <p>text 1</p><b>CHOP</b><p>text 2</p>
\r
2691 function trim(node) {
\r
2692 var i, children = node.childNodes;
\r
2694 if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
\r
2697 for (i = children.length - 1; i >= 0; i--)
\r
2698 trim(children[i]);
\r
2700 if (node.nodeType != 9) {
\r
2701 // Keep non whitespace text nodes
\r
2702 if (node.nodeType == 3 && node.nodeValue.length > 0)
\r
2705 if (node.nodeType == 1) {
\r
2706 // If the only child is a bookmark then move it up
\r
2707 children = node.childNodes;
\r
2708 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
\r
2709 node.parentNode.insertBefore(children[0], node);
\r
2711 // Keep non empty elements or img, hr etc
\r
2712 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
\r
2723 // Get before chunk
\r
2724 r.setStart(pe.parentNode, t.nodeIndex(pe));
\r
2725 r.setEnd(e.parentNode, t.nodeIndex(e));
\r
2726 bef = r.extractContents();
\r
2728 // Get after chunk
\r
2729 r = t.createRng();
\r
2730 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
\r
2731 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
\r
2732 aft = r.extractContents();
\r
2734 // Insert before chunk
\r
2735 pa = pe.parentNode;
\r
2736 pa.insertBefore(trim(bef), pe);
\r
2738 // Insert middle chunk
\r
2740 pa.replaceChild(re, e);
\r
2742 pa.insertBefore(e, pe);
\r
2744 // Insert after chunk
\r
2745 pa.insertBefore(trim(aft), pe);
\r
2752 bind : function(target, name, func, scope) {
\r
2756 t.events = new tinymce.dom.EventUtils();
\r
2758 return t.events.add(target, name, func, scope || this);
\r
2761 unbind : function(target, name, func) {
\r
2765 t.events = new tinymce.dom.EventUtils();
\r
2767 return t.events.remove(target, name, func);
\r
2771 _findSib : function(node, selector, name) {
\r
2772 var t = this, f = selector;
\r
2775 // If expression make a function of it using is
\r
2776 if (is(f, 'string')) {
\r
2777 f = function(node) {
\r
2778 return t.is(node, selector);
\r
2782 // Loop all siblings
\r
2783 for (node = node[name]; node; node = node[name]) {
\r
2792 _isRes : function(c) {
\r
2793 // Is live resizble element
\r
2794 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
\r
2798 walk : function(n, f, s) {
\r
2799 var d = this.doc, w;
\r
2801 if (d.createTreeWalker) {
\r
2802 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
\r
2804 while ((n = w.nextNode()) != null)
\r
2805 f.call(s || this, n);
\r
2807 tinymce.walk(n, f, 'childNodes', s);
\r
2812 toRGB : function(s) {
\r
2813 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
\r
2816 // #FFF -> #FFFFFF
\r
2818 c[3] = c[2] = c[1];
\r
2820 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
\r
2828 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
\r
2831 // Range constructor
\r
2832 function Range(dom) {
\r
2840 START_OFFSET = 'startOffset',
\r
2841 START_CONTAINER = 'startContainer',
\r
2842 END_CONTAINER = 'endContainer',
\r
2843 END_OFFSET = 'endOffset',
\r
2844 extend = tinymce.extend,
\r
2845 nodeIndex = dom.nodeIndex;
\r
2849 startContainer : doc,
\r
2851 endContainer : doc,
\r
2854 commonAncestorContainer : doc,
\r
2856 // Range constants
\r
2857 START_TO_START : 0,
\r
2863 setStart : setStart,
\r
2865 setStartBefore : setStartBefore,
\r
2866 setStartAfter : setStartAfter,
\r
2867 setEndBefore : setEndBefore,
\r
2868 setEndAfter : setEndAfter,
\r
2869 collapse : collapse,
\r
2870 selectNode : selectNode,
\r
2871 selectNodeContents : selectNodeContents,
\r
2872 compareBoundaryPoints : compareBoundaryPoints,
\r
2873 deleteContents : deleteContents,
\r
2874 extractContents : extractContents,
\r
2875 cloneContents : cloneContents,
\r
2876 insertNode : insertNode,
\r
2877 surroundContents : surroundContents,
\r
2878 cloneRange : cloneRange
\r
2881 function setStart(n, o) {
\r
2882 _setEndPoint(TRUE, n, o);
\r
2885 function setEnd(n, o) {
\r
2886 _setEndPoint(FALSE, n, o);
\r
2889 function setStartBefore(n) {
\r
2890 setStart(n.parentNode, nodeIndex(n));
\r
2893 function setStartAfter(n) {
\r
2894 setStart(n.parentNode, nodeIndex(n) + 1);
\r
2897 function setEndBefore(n) {
\r
2898 setEnd(n.parentNode, nodeIndex(n));
\r
2901 function setEndAfter(n) {
\r
2902 setEnd(n.parentNode, nodeIndex(n) + 1);
\r
2905 function collapse(ts) {
\r
2907 t[END_CONTAINER] = t[START_CONTAINER];
\r
2908 t[END_OFFSET] = t[START_OFFSET];
\r
2910 t[START_CONTAINER] = t[END_CONTAINER];
\r
2911 t[START_OFFSET] = t[END_OFFSET];
\r
2914 t.collapsed = TRUE;
\r
2917 function selectNode(n) {
\r
2918 setStartBefore(n);
\r
2922 function selectNodeContents(n) {
\r
2924 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
\r
2927 function compareBoundaryPoints(h, r) {
\r
2928 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
\r
2930 // Check START_TO_START
\r
2932 return _compareBoundaryPoints(sc, so, sc, so);
\r
2934 // Check START_TO_END
\r
2936 return _compareBoundaryPoints(sc, so, ec, eo);
\r
2938 // Check END_TO_END
\r
2940 return _compareBoundaryPoints(ec, eo, ec, eo);
\r
2942 // Check END_TO_START
\r
2944 return _compareBoundaryPoints(ec, eo, sc, so);
\r
2947 function deleteContents() {
\r
2948 _traverse(DELETE);
\r
2951 function extractContents() {
\r
2952 return _traverse(EXTRACT);
\r
2955 function cloneContents() {
\r
2956 return _traverse(CLONE);
\r
2959 function insertNode(n) {
\r
2960 var startContainer = this[START_CONTAINER],
\r
2961 startOffset = this[START_OFFSET], nn, o;
\r
2963 // Node is TEXT_NODE or CDATA
\r
2964 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
\r
2965 if (!startOffset) {
\r
2966 // At the start of text
\r
2967 startContainer.parentNode.insertBefore(n, startContainer);
\r
2968 } else if (startOffset >= startContainer.nodeValue.length) {
\r
2969 // At the end of text
\r
2970 dom.insertAfter(n, startContainer);
\r
2972 // Middle, need to split
\r
2973 nn = startContainer.splitText(startOffset);
\r
2974 startContainer.parentNode.insertBefore(n, nn);
\r
2977 // Insert element node
\r
2978 if (startContainer.childNodes.length > 0)
\r
2979 o = startContainer.childNodes[startOffset];
\r
2982 startContainer.insertBefore(n, o);
\r
2984 startContainer.appendChild(n);
\r
2988 function surroundContents(n) {
\r
2989 var f = t.extractContents();
\r
2996 function cloneRange() {
\r
2997 return extend(new Range(dom), {
\r
2998 startContainer : t[START_CONTAINER],
\r
2999 startOffset : t[START_OFFSET],
\r
3000 endContainer : t[END_CONTAINER],
\r
3001 endOffset : t[END_OFFSET],
\r
3002 collapsed : t.collapsed,
\r
3003 commonAncestorContainer : t.commonAncestorContainer
\r
3007 // Private methods
\r
3009 function _getSelectedNode(container, offset) {
\r
3012 if (container.nodeType == 3 /* TEXT_NODE */)
\r
3018 child = container.firstChild;
\r
3019 while (child && offset > 0) {
\r
3021 child = child.nextSibling;
\r
3030 function _isCollapsed() {
\r
3031 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
\r
3034 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
\r
3035 var c, offsetC, n, cmnRoot, childA, childB;
\r
3037 // In the first case the boundary-points have the same container. A is before B
\r
3038 // if its offset is less than the offset of B, A is equal to B if its offset is
\r
3039 // equal to the offset of B, and A is after B if its offset is greater than the
\r
3041 if (containerA == containerB) {
\r
3042 if (offsetA == offsetB)
\r
3043 return 0; // equal
\r
3045 if (offsetA < offsetB)
\r
3046 return -1; // before
\r
3048 return 1; // after
\r
3051 // In the second case a child node C of the container of A is an ancestor
\r
3052 // container of B. In this case, A is before B if the offset of A is less than or
\r
3053 // equal to the index of the child node C and A is after B otherwise.
\r
3055 while (c && c.parentNode != containerA)
\r
3060 n = containerA.firstChild;
\r
3062 while (n != c && offsetC < offsetA) {
\r
3064 n = n.nextSibling;
\r
3067 if (offsetA <= offsetC)
\r
3068 return -1; // before
\r
3070 return 1; // after
\r
3073 // In the third case a child node C of the container of B is an ancestor container
\r
3074 // of A. In this case, A is before B if the index of the child node C is less than
\r
3075 // the offset of B and A is after B otherwise.
\r
3077 while (c && c.parentNode != containerB) {
\r
3083 n = containerB.firstChild;
\r
3085 while (n != c && offsetC < offsetB) {
\r
3087 n = n.nextSibling;
\r
3090 if (offsetC < offsetB)
\r
3091 return -1; // before
\r
3093 return 1; // after
\r
3096 // In the fourth case, none of three other cases hold: the containers of A and B
\r
3097 // are siblings or descendants of sibling nodes. In this case, A is before B if
\r
3098 // the container of A is before the container of B in a pre-order traversal of the
\r
3099 // Ranges' context tree and A is after B otherwise.
\r
3100 cmnRoot = dom.findCommonAncestor(containerA, containerB);
\r
3101 childA = containerA;
\r
3103 while (childA && childA.parentNode != cmnRoot)
\r
3104 childA = childA.parentNode;
\r
3109 childB = containerB;
\r
3110 while (childB && childB.parentNode != cmnRoot)
\r
3111 childB = childB.parentNode;
\r
3116 if (childA == childB)
\r
3117 return 0; // equal
\r
3119 n = cmnRoot.firstChild;
\r
3122 return -1; // before
\r
3125 return 1; // after
\r
3127 n = n.nextSibling;
\r
3131 function _setEndPoint(st, n, o) {
\r
3135 t[START_CONTAINER] = n;
\r
3136 t[START_OFFSET] = o;
\r
3138 t[END_CONTAINER] = n;
\r
3139 t[END_OFFSET] = o;
\r
3142 // If one boundary-point of a Range is set to have a root container
\r
3143 // other than the current one for the Range, the Range is collapsed to
\r
3144 // the new position. This enforces the restriction that both boundary-
\r
3145 // points of a Range must have the same root container.
\r
3146 ec = t[END_CONTAINER];
\r
3147 while (ec.parentNode)
\r
3148 ec = ec.parentNode;
\r
3150 sc = t[START_CONTAINER];
\r
3151 while (sc.parentNode)
\r
3152 sc = sc.parentNode;
\r
3155 // The start position of a Range is guaranteed to never be after the
\r
3156 // end position. To enforce this restriction, if the start is set to
\r
3157 // be at a position after the end, the Range is collapsed to that
\r
3159 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
\r
3164 t.collapsed = _isCollapsed();
\r
3165 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
\r
3168 function _traverse(how) {
\r
3169 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
\r
3171 if (t[START_CONTAINER] == t[END_CONTAINER])
\r
3172 return _traverseSameContainer(how);
\r
3174 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
3175 if (p == t[START_CONTAINER])
\r
3176 return _traverseCommonStartContainer(c, how);
\r
3178 ++endContainerDepth;
\r
3181 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
3182 if (p == t[END_CONTAINER])
\r
3183 return _traverseCommonEndContainer(c, how);
\r
3185 ++startContainerDepth;
\r
3188 depthDiff = startContainerDepth - endContainerDepth;
\r
3190 startNode = t[START_CONTAINER];
\r
3191 while (depthDiff > 0) {
\r
3192 startNode = startNode.parentNode;
\r
3196 endNode = t[END_CONTAINER];
\r
3197 while (depthDiff < 0) {
\r
3198 endNode = endNode.parentNode;
\r
3202 // ascend the ancestor hierarchy until we have a common parent.
\r
3203 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
\r
3208 return _traverseCommonAncestors(startNode, endNode, how);
\r
3211 function _traverseSameContainer(how) {
\r
3212 var frag, s, sub, n, cnt, sibling, xferNode;
\r
3214 if (how != DELETE)
\r
3215 frag = doc.createDocumentFragment();
\r
3217 // If selection is empty, just return the fragment
\r
3218 if (t[START_OFFSET] == t[END_OFFSET])
\r
3221 // Text node needs special case handling
\r
3222 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
\r
3223 // get the substring
\r
3224 s = t[START_CONTAINER].nodeValue;
\r
3225 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
\r
3227 // set the original text node to its new value
\r
3228 if (how != CLONE) {
\r
3229 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
\r
3231 // Nothing is partially selected, so collapse to start point
\r
3235 if (how == DELETE)
\r
3238 frag.appendChild(doc.createTextNode(sub));
\r
3242 // Copy nodes between the start/end offsets.
\r
3243 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
\r
3244 cnt = t[END_OFFSET] - t[START_OFFSET];
\r
3247 sibling = n.nextSibling;
\r
3248 xferNode = _traverseFullySelected(n, how);
\r
3251 frag.appendChild( xferNode );
\r
3257 // Nothing is partially selected, so collapse to start point
\r
3264 function _traverseCommonStartContainer(endAncestor, how) {
\r
3265 var frag, n, endIdx, cnt, sibling, xferNode;
\r
3267 if (how != DELETE)
\r
3268 frag = doc.createDocumentFragment();
\r
3270 n = _traverseRightBoundary(endAncestor, how);
\r
3273 frag.appendChild(n);
\r
3275 endIdx = nodeIndex(endAncestor);
\r
3276 cnt = endIdx - t[START_OFFSET];
\r
3279 // Collapse to just before the endAncestor, which
\r
3280 // is partially selected.
\r
3281 if (how != CLONE) {
\r
3282 t.setEndBefore(endAncestor);
\r
3283 t.collapse(FALSE);
\r
3289 n = endAncestor.previousSibling;
\r
3291 sibling = n.previousSibling;
\r
3292 xferNode = _traverseFullySelected(n, how);
\r
3295 frag.insertBefore(xferNode, frag.firstChild);
\r
3301 // Collapse to just before the endAncestor, which
\r
3302 // is partially selected.
\r
3303 if (how != CLONE) {
\r
3304 t.setEndBefore(endAncestor);
\r
3305 t.collapse(FALSE);
\r
3311 function _traverseCommonEndContainer(startAncestor, how) {
\r
3312 var frag, startIdx, n, cnt, sibling, xferNode;
\r
3314 if (how != DELETE)
\r
3315 frag = doc.createDocumentFragment();
\r
3317 n = _traverseLeftBoundary(startAncestor, how);
\r
3319 frag.appendChild(n);
\r
3321 startIdx = nodeIndex(startAncestor);
\r
3322 ++startIdx; // Because we already traversed it....
\r
3324 cnt = t[END_OFFSET] - startIdx;
\r
3325 n = startAncestor.nextSibling;
\r
3327 sibling = n.nextSibling;
\r
3328 xferNode = _traverseFullySelected(n, how);
\r
3331 frag.appendChild(xferNode);
\r
3337 if (how != CLONE) {
\r
3338 t.setStartAfter(startAncestor);
\r
3345 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
\r
3346 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
\r
3348 if (how != DELETE)
\r
3349 frag = doc.createDocumentFragment();
\r
3351 n = _traverseLeftBoundary(startAncestor, how);
\r
3353 frag.appendChild(n);
\r
3355 commonParent = startAncestor.parentNode;
\r
3356 startOffset = nodeIndex(startAncestor);
\r
3357 endOffset = nodeIndex(endAncestor);
\r
3360 cnt = endOffset - startOffset;
\r
3361 sibling = startAncestor.nextSibling;
\r
3364 nextSibling = sibling.nextSibling;
\r
3365 n = _traverseFullySelected(sibling, how);
\r
3368 frag.appendChild(n);
\r
3370 sibling = nextSibling;
\r
3374 n = _traverseRightBoundary(endAncestor, how);
\r
3377 frag.appendChild(n);
\r
3379 if (how != CLONE) {
\r
3380 t.setStartAfter(startAncestor);
\r
3387 function _traverseRightBoundary(root, how) {
\r
3388 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
\r
3391 return _traverseNode(next, isFullySelected, FALSE, how);
\r
3393 parent = next.parentNode;
\r
3394 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3398 prevSibling = next.previousSibling;
\r
3399 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
\r
3401 if (how != DELETE)
\r
3402 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
\r
3404 isFullySelected = TRUE;
\r
3405 next = prevSibling;
\r
3408 if (parent == root)
\r
3409 return clonedParent;
\r
3411 next = parent.previousSibling;
\r
3412 parent = parent.parentNode;
\r
3414 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3416 if (how != DELETE)
\r
3417 clonedGrandParent.appendChild(clonedParent);
\r
3419 clonedParent = clonedGrandParent;
\r
3423 function _traverseLeftBoundary(root, how) {
\r
3424 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
\r
3427 return _traverseNode(next, isFullySelected, TRUE, how);
\r
3429 parent = next.parentNode;
\r
3430 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3434 nextSibling = next.nextSibling;
\r
3435 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
\r
3437 if (how != DELETE)
\r
3438 clonedParent.appendChild(clonedChild);
\r
3440 isFullySelected = TRUE;
\r
3441 next = nextSibling;
\r
3444 if (parent == root)
\r
3445 return clonedParent;
\r
3447 next = parent.nextSibling;
\r
3448 parent = parent.parentNode;
\r
3450 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3452 if (how != DELETE)
\r
3453 clonedGrandParent.appendChild(clonedParent);
\r
3455 clonedParent = clonedGrandParent;
\r
3459 function _traverseNode(n, isFullySelected, isLeft, how) {
\r
3460 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
\r
3462 if (isFullySelected)
\r
3463 return _traverseFullySelected(n, how);
\r
3465 if (n.nodeType == 3 /* TEXT_NODE */) {
\r
3466 txtValue = n.nodeValue;
\r
3469 offset = t[START_OFFSET];
\r
3470 newNodeValue = txtValue.substring(offset);
\r
3471 oldNodeValue = txtValue.substring(0, offset);
\r
3473 offset = t[END_OFFSET];
\r
3474 newNodeValue = txtValue.substring(0, offset);
\r
3475 oldNodeValue = txtValue.substring(offset);
\r
3479 n.nodeValue = oldNodeValue;
\r
3481 if (how == DELETE)
\r
3484 newNode = n.cloneNode(FALSE);
\r
3485 newNode.nodeValue = newNodeValue;
\r
3490 if (how == DELETE)
\r
3493 return n.cloneNode(FALSE);
\r
3496 function _traverseFullySelected(n, how) {
\r
3497 if (how != DELETE)
\r
3498 return how == CLONE ? n.cloneNode(TRUE) : n;
\r
3500 n.parentNode.removeChild(n);
\r
3507 function Selection(selection) {
\r
3508 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
\r
3510 // Compares two IE specific ranges to see if they are different
\r
3511 // this method is useful when invalidating the cached selection range
\r
3512 function compareRanges(rng1, rng2) {
\r
3513 if (rng1 && rng2) {
\r
3514 // Both are control ranges and the selected element matches
\r
3515 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
\r
3518 // Both are text ranges and the range matches
\r
3519 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
\r
3520 // IE will say that the range is equal then produce an invalid argument exception
\r
3521 // if you perform specific operations in a keyup event. For example Ctrl+Del.
\r
3522 // This hack will invalidate the range cache if the exception occurs
\r
3524 // Try accessing nextSibling will producer an invalid argument some times
\r
3525 range.startContainer.nextSibling;
\r
3536 // Returns a W3C DOM compatible range object by using the IE Range API
\r
3537 function getRange() {
\r
3538 var ieRange = selection.getRng(), domRange = dom.createRng(), ieRange2, element, collapsed, isMerged;
\r
3540 // If selection is outside the current document just return an empty range
\r
3541 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
\r
3542 if (element.ownerDocument != dom.doc)
\r
3545 // Handle control selection or text selection of a image
\r
3546 if (ieRange.item || !element.hasChildNodes()) {
\r
3547 domRange.setStart(element.parentNode, dom.nodeIndex(element));
\r
3548 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
\r
3553 // Duplicare IE selection range and check if the range is collapsed
\r
3554 ieRange2 = ieRange.duplicate();
\r
3555 collapsed = selection.isCollapsed();
\r
3557 // Insert invisible start marker
\r
3558 ieRange.collapse();
\r
3559 ieRange.pasteHTML('<span id="_mce_start" style="display:none;line-height:0">' + invisibleChar + '</span>');
\r
3561 // Insert invisible end marker
\r
3563 ieRange2.collapse(FALSE);
\r
3564 ieRange2.pasteHTML('<span id="_mce_end" style="display:none;line-height:0">' + invisibleChar + '</span>');
\r
3567 // Sets the end point of the range by looking for the marker
\r
3568 // This method also merges the text nodes it splits so that
\r
3569 // the DOM doesn't get fragmented.
\r
3570 function setEndPoint(start) {
\r
3571 var container, offset, marker, sibling;
\r
3573 // Look for endpoint marker
\r
3574 marker = dom.get('_mce_' + (start ? 'start' : 'end'));
\r
3575 sibling = marker.previousSibling;
\r
3577 // Is marker after a text node
\r
3578 if (sibling && sibling.nodeType == 3) {
\r
3579 // Get container node and calc offset
\r
3580 container = sibling;
\r
3581 offset = container.nodeValue.length;
\r
3582 dom.remove(marker);
\r
3584 // Merge text nodes to reduce DOM fragmentation
\r
3585 sibling = container.nextSibling;
\r
3586 if (sibling && sibling.nodeType == 3) {
\r
3588 container.appendData(sibling.nodeValue);
\r
3589 dom.remove(sibling);
\r
3592 sibling = marker.nextSibling;
\r
3594 // Is marker before a text node
\r
3595 if (sibling && sibling.nodeType == 3) {
\r
3596 container = sibling;
\r
3599 // Is marker before an element
\r
3601 offset = dom.nodeIndex(sibling) - 1;
\r
3603 offset = dom.nodeIndex(marker);
\r
3605 container = marker.parentNode;
\r
3608 dom.remove(marker);
\r
3611 // Set start of range
\r
3613 domRange.setStart(container, offset);
\r
3615 // Set end of range or automatically if it's collapsed to increase performance
\r
3616 if (!start || collapsed)
\r
3617 domRange.setEnd(container, offset);
\r
3620 // Set start of range
\r
3621 setEndPoint(TRUE);
\r
3623 // Set end of range if needed
\r
3625 setEndPoint(FALSE);
\r
3627 // Restore selection if the range contents was merged
\r
3628 // since the selection was then moved since the text nodes got changed
\r
3630 t.addRange(domRange);
\r
3635 this.addRange = function(rng) {
\r
3636 var ieRng, ieRng2, doc = selection.dom.doc, body = doc.body, startPos, endPos, sc, so, ec, eo, marker, lastIndex, skipStart, skipEnd;
\r
3640 // Setup some shorter versions
\r
3641 sc = rng.startContainer;
\r
3642 so = rng.startOffset;
\r
3643 ec = rng.endContainer;
\r
3644 eo = rng.endOffset;
\r
3645 ieRng = body.createTextRange();
\r
3647 // If document selection move caret to first node in document
\r
3648 if (sc == doc || ec == doc) {
\r
3649 ieRng = body.createTextRange();
\r
3655 // If child index resolve it
\r
3656 if (sc.nodeType == 1 && sc.hasChildNodes()) {
\r
3657 lastIndex = sc.childNodes.length - 1;
\r
3659 // Index is higher that the child count then we need to jump over the start container
\r
3660 if (so > lastIndex) {
\r
3662 sc = sc.childNodes[lastIndex];
\r
3664 sc = sc.childNodes[so];
\r
3666 // Child was text node then move offset to start of it
\r
3667 if (sc.nodeType == 3)
\r
3671 // If child index resolve it
\r
3672 if (ec.nodeType == 1 && ec.hasChildNodes()) {
\r
3673 lastIndex = ec.childNodes.length - 1;
\r
3677 ec = ec.childNodes[0];
\r
3679 ec = ec.childNodes[Math.min(lastIndex, eo - 1)];
\r
3681 // Child was text node then move offset to end of text node
\r
3682 if (ec.nodeType == 3)
\r
3683 eo = ec.nodeValue.length;
\r
3687 // Single element selection
\r
3688 if (sc == ec && sc.nodeType == 1) {
\r
3689 // Make control selection for some elements
\r
3690 if (/^(IMG|TABLE)$/.test(sc.nodeName) && so != eo) {
\r
3691 ieRng = body.createControlRange();
\r
3692 ieRng.addElement(sc);
\r
3694 ieRng = body.createTextRange();
\r
3696 // Padd empty elements with invisible character
\r
3697 if (!sc.hasChildNodes() && sc.canHaveHTML)
\r
3698 sc.innerHTML = invisibleChar;
\r
3700 // Select element contents
\r
3701 ieRng.moveToElementText(sc);
\r
3703 // If it's only containing a padding remove it so the caret remains
\r
3704 if (sc.innerHTML == invisibleChar) {
\r
3705 ieRng.collapse(TRUE);
\r
3706 sc.removeChild(sc.firstChild);
\r
3711 ieRng.collapse(eo <= rng.endContainer.childNodes.length - 1);
\r
3714 ieRng.scrollIntoView();
\r
3718 // Create range and marker
\r
3719 ieRng = body.createTextRange();
\r
3720 marker = doc.createElement('span');
\r
3721 marker.innerHTML = ' ';
\r
3723 // Set start of range to startContainer/startOffset
\r
3724 if (sc.nodeType == 3) {
\r
3725 // Insert marker after/before startContainer
\r
3727 dom.insertAfter(marker, sc);
\r
3729 sc.parentNode.insertBefore(marker, sc);
\r
3731 // Select marker the caret to offset position
\r
3732 ieRng.moveToElementText(marker);
\r
3733 marker.parentNode.removeChild(marker);
\r
3734 ieRng.move('character', so);
\r
3736 ieRng.moveToElementText(sc);
\r
3739 ieRng.collapse(FALSE);
\r
3742 // If same text container then we can do a more simple move
\r
3743 if (sc == ec && sc.nodeType == 3) {
\r
3744 ieRng.moveEnd('character', eo - so);
\r
3746 ieRng.scrollIntoView();
\r
3750 // Set end of range to endContainer/endOffset
\r
3751 ieRng2 = body.createTextRange();
\r
3752 if (ec.nodeType == 3) {
\r
3753 // Insert marker after/before startContainer
\r
3754 ec.parentNode.insertBefore(marker, ec);
\r
3756 // Move selection to end marker and move caret to end offset
\r
3757 ieRng2.moveToElementText(marker);
\r
3758 marker.parentNode.removeChild(marker);
\r
3759 ieRng2.move('character', eo);
\r
3760 ieRng.setEndPoint('EndToStart', ieRng2);
\r
3762 ieRng2.moveToElementText(ec);
\r
3763 ieRng2.collapse(!!skipEnd);
\r
3764 ieRng.setEndPoint('EndToEnd', ieRng2);
\r
3768 ieRng.scrollIntoView();
\r
3771 this.getRangeAt = function() {
\r
3772 // Setup new range if the cache is empty
\r
3773 if (!range || !compareRanges(lastIERng, selection.getRng())) {
\r
3774 range = getRange();
\r
3776 // Store away text range for next call
\r
3777 lastIERng = selection.getRng();
\r
3780 // Return cached range
\r
3784 this.destroy = function() {
\r
3785 // Destroy cached range and last IE range to avoid memory leaks
\r
3786 lastIERng = range = null;
\r
3789 // 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
3790 if (selection.dom.boxModel) {
\r
3792 var doc = dom.doc, body = doc.body, started, startRng;
\r
3794 // Make HTML element unselectable since we are going to handle selection by hand
\r
3795 doc.documentElement.unselectable = TRUE;
\r
3797 // Return range from point or null if it failed
\r
3798 function rngFromPoint(x, y) {
\r
3799 var rng = body.createTextRange();
\r
3802 rng.moveToPoint(x, y);
\r
3804 // IE sometimes throws and exception, so lets just ignore it
\r
3811 // Fires while the selection is changing
\r
3812 function selectionChange(e) {
\r
3815 // Check if the button is down or not
\r
3817 // Create range from mouse position
\r
3818 pointRng = rngFromPoint(e.x, e.y);
\r
3821 // Check if pointRange is before/after selection then change the endPoint
\r
3822 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
\r
3823 pointRng.setEndPoint('StartToStart', startRng);
\r
3825 pointRng.setEndPoint('EndToEnd', startRng);
\r
3827 pointRng.select();
\r
3833 // Removes listeners
\r
3834 function endSelection() {
\r
3835 dom.unbind(doc, 'mouseup', endSelection);
\r
3836 dom.unbind(doc, 'mousemove', selectionChange);
\r
3840 // Detect when user selects outside BODY
\r
3841 dom.bind(doc, 'mousedown', function(e) {
\r
3842 if (e.target.nodeName === 'HTML') {
\r
3848 // Setup start position
\r
3849 startRng = rngFromPoint(e.x, e.y);
\r
3851 // Listen for selection change events
\r
3852 dom.bind(doc, 'mouseup', endSelection);
\r
3853 dom.bind(doc, 'mousemove', selectionChange);
\r
3855 startRng.select();
\r
3863 // Expose the selection object
\r
3864 tinymce.dom.TridentSelection = Selection;
\r
3866 (function(tinymce) {
\r
3868 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
\r
3870 tinymce.create('tinymce.dom.EventUtils', {
\r
3871 EventUtils : function() {
\r
3876 add : function(o, n, f, s) {
\r
3877 var cb, t = this, el = t.events, r;
\r
3879 if (n instanceof Array) {
\r
3882 each(n, function(n) {
\r
3883 r.push(t.add(o, n, f, s));
\r
3890 if (o && o.hasOwnProperty && o instanceof Array) {
\r
3893 each(o, function(o) {
\r
3895 r.push(t.add(o, n, f, s));
\r
3906 // Setup event callback
\r
3907 cb = function(e) {
\r
3908 // Is all events disabled
\r
3912 e = e || window.event;
\r
3914 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
\r
3917 e.target = e.srcElement;
\r
3919 // Patch in preventDefault, stopPropagation methods for W3C compatibility
\r
3920 tinymce.extend(e, t._stoppers);
\r
3926 return f.call(s, e);
\r
3929 if (n == 'unload') {
\r
3930 tinymce.unloads.unshift({func : cb});
\r
3934 if (n == 'init') {
\r
3943 // Store away listener reference
\r
3957 remove : function(o, n, f) {
\r
3958 var t = this, a = t.events, s = false, r;
\r
3961 if (o && o.hasOwnProperty && o instanceof Array) {
\r
3964 each(o, function(o) {
\r
3966 r.push(t.remove(o, n, f));
\r
3974 each(a, function(e, i) {
\r
3975 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
\r
3977 t._remove(o, n, e.cfunc);
\r
3986 clear : function(o) {
\r
3987 var t = this, a = t.events, i, e;
\r
3992 for (i = a.length - 1; i >= 0; i--) {
\r
3995 if (e.obj === o) {
\r
3996 t._remove(e.obj, e.name, e.cfunc);
\r
3997 e.obj = e.cfunc = null;
\r
4004 cancel : function(e) {
\r
4010 return this.prevent(e);
\r
4013 stop : function(e) {
\r
4014 if (e.stopPropagation)
\r
4015 e.stopPropagation();
\r
4017 e.cancelBubble = true;
\r
4022 prevent : function(e) {
\r
4023 if (e.preventDefault)
\r
4024 e.preventDefault();
\r
4026 e.returnValue = false;
\r
4031 destroy : function() {
\r
4034 each(t.events, function(e, i) {
\r
4035 t._remove(e.obj, e.name, e.cfunc);
\r
4036 e.obj = e.cfunc = null;
\r
4043 _add : function(o, n, f) {
\r
4044 if (o.attachEvent)
\r
4045 o.attachEvent('on' + n, f);
\r
4046 else if (o.addEventListener)
\r
4047 o.addEventListener(n, f, false);
\r
4052 _remove : function(o, n, f) {
\r
4055 if (o.detachEvent)
\r
4056 o.detachEvent('on' + n, f);
\r
4057 else if (o.removeEventListener)
\r
4058 o.removeEventListener(n, f, false);
\r
4060 o['on' + n] = null;
\r
4062 // Might fail with permission denined on IE so we just ignore that
\r
4067 _pageInit : function(win) {
\r
4070 // Keep it from running more than once
\r
4074 t.domLoaded = true;
\r
4076 each(t.inits, function(c) {
\r
4083 _wait : function(win) {
\r
4084 var t = this, doc = win.document;
\r
4086 // No need since the document is already loaded
\r
4087 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
\r
4093 if (doc.attachEvent) {
\r
4094 doc.attachEvent("onreadystatechange", function() {
\r
4095 if (doc.readyState === "complete") {
\r
4096 doc.detachEvent("onreadystatechange", arguments.callee);
\r
4101 if (doc.documentElement.doScroll && win == win.top) {
\r
4107 // If IE is used, use the trick by Diego Perini
\r
4108 // http://javascript.nwbox.com/IEContentLoaded/
\r
4109 doc.documentElement.doScroll("left");
\r
4111 setTimeout(arguments.callee, 0);
\r
4118 } else if (doc.addEventListener) {
\r
4119 t._add(win, 'DOMContentLoaded', function() {
\r
4124 t._add(win, 'load', function() {
\r
4130 preventDefault : function() {
\r
4131 this.returnValue = false;
\r
4134 stopPropagation : function() {
\r
4135 this.cancelBubble = true;
\r
4140 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
\r
4142 // Dispatch DOM content loaded event for IE and Safari
\r
4143 Event._wait(window);
\r
4145 tinymce.addUnload(function() {
\r
4149 (function(tinymce) {
\r
4150 tinymce.dom.Element = function(id, settings) {
\r
4151 var t = this, dom, el;
\r
4153 t.settings = settings = settings || {};
\r
4155 t.dom = dom = settings.dom || tinymce.DOM;
\r
4157 // Only IE leaks DOM references, this is a lot faster
\r
4158 if (!tinymce.isIE)
\r
4159 el = dom.get(t.id);
\r
4162 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
\r
4163 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
\r
4164 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
\r
4165 'isHidden,setHTML,get').split(/,/)
\r
4167 t[k] = function() {
\r
4170 for (i = 0; i < arguments.length; i++)
\r
4171 a.push(arguments[i]);
\r
4173 a = dom[k].apply(dom, a);
\r
4180 tinymce.extend(t, {
\r
4181 on : function(n, f, s) {
\r
4182 return tinymce.dom.Event.add(t.id, n, f, s);
\r
4185 getXY : function() {
\r
4187 x : parseInt(t.getStyle('left')),
\r
4188 y : parseInt(t.getStyle('top'))
\r
4192 getSize : function() {
\r
4193 var n = dom.get(t.id);
\r
4196 w : parseInt(t.getStyle('width') || n.clientWidth),
\r
4197 h : parseInt(t.getStyle('height') || n.clientHeight)
\r
4201 moveTo : function(x, y) {
\r
4202 t.setStyles({left : x, top : y});
\r
4205 moveBy : function(x, y) {
\r
4206 var p = t.getXY();
\r
4208 t.moveTo(p.x + x, p.y + y);
\r
4211 resizeTo : function(w, h) {
\r
4212 t.setStyles({width : w, height : h});
\r
4215 resizeBy : function(w, h) {
\r
4216 var s = t.getSize();
\r
4218 t.resizeTo(s.w + w, s.h + h);
\r
4221 update : function(k) {
\r
4224 if (tinymce.isIE6 && settings.blocker) {
\r
4228 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
\r
4231 // Remove blocker on remove
\r
4232 if (k == 'remove') {
\r
4233 dom.remove(t.blocker);
\r
4238 t.blocker = dom.uniqueId();
\r
4239 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
\r
4240 dom.setStyle(b, 'opacity', 0);
\r
4242 b = dom.get(t.blocker);
\r
4244 dom.setStyles(b, {
\r
4245 left : t.getStyle('left', 1),
\r
4246 top : t.getStyle('top', 1),
\r
4247 width : t.getStyle('width', 1),
\r
4248 height : t.getStyle('height', 1),
\r
4249 display : t.getStyle('display', 1),
\r
4250 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
\r
4257 (function(tinymce) {
\r
4258 function trimNl(s) {
\r
4259 return s.replace(/[\n\r]+/g, '');
\r
4263 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
\r
4265 tinymce.create('tinymce.dom.Selection', {
\r
4266 Selection : function(dom, win, serializer) {
\r
4271 t.serializer = serializer;
\r
4275 'onBeforeSetContent',
\r
4276 'onBeforeGetContent',
\r
4280 t[e] = new tinymce.util.Dispatcher(t);
\r
4283 // No W3C Range support
\r
4284 if (!t.win.getSelection)
\r
4285 t.tridentSel = new tinymce.dom.TridentSelection(t);
\r
4288 tinymce.addUnload(t.destroy, t);
\r
4291 getContent : function(s) {
\r
4292 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
\r
4297 s.format = s.format || 'html';
\r
4298 t.onBeforeGetContent.dispatch(t, s);
\r
4300 if (s.format == 'text')
\r
4301 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
\r
4303 if (r.cloneContents) {
\r
4304 n = r.cloneContents();
\r
4308 } else if (is(r.item) || is(r.htmlText))
\r
4309 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
\r
4311 e.innerHTML = r.toString();
\r
4313 // Keep whitespace before and after
\r
4314 if (/^\s/.test(e.innerHTML))
\r
4317 if (/\s+$/.test(e.innerHTML))
\r
4320 s.getInner = true;
\r
4322 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
\r
4323 t.onGetContent.dispatch(t, s);
\r
4328 setContent : function(h, s) {
\r
4329 var t = this, r = t.getRng(), c, d = t.win.document;
\r
4331 s = s || {format : 'html'};
\r
4333 h = s.content = t.dom.processHTML(h);
\r
4335 // Dispatch before set content event
\r
4336 t.onBeforeSetContent.dispatch(t, s);
\r
4339 if (r.insertNode) {
\r
4340 // Make caret marker since insertNode places the caret in the beginning of text after insert
\r
4341 h += '<span id="__caret">_</span>';
\r
4343 // Delete and insert new node
\r
4344 if (r.startContainer == d && r.endContainer == d) {
\r
4345 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
\r
4346 d.body.innerHTML = h;
\r
4348 r.deleteContents();
\r
4349 r.insertNode(t.getRng().createContextualFragment(h));
\r
4352 // Move to caret marker
\r
4353 c = t.dom.get('__caret');
\r
4355 // Make sure we wrap it compleatly, Opera fails with a simple select call
\r
4356 r = d.createRange();
\r
4357 r.setStartBefore(c);
\r
4358 r.setEndBefore(c);
\r
4361 // Remove the caret position
\r
4362 t.dom.remove('__caret');
\r
4365 // Delete content and get caret text selection
\r
4366 d.execCommand('Delete', false, null);
\r
4373 // Dispatch set content event
\r
4374 t.onSetContent.dispatch(t, s);
\r
4377 getStart : function() {
\r
4378 var t = this, r = t.getRng(), e;
\r
4384 r = r.duplicate();
\r
4386 e = r.parentElement();
\r
4388 if (e && e.nodeName == 'BODY')
\r
4389 return e.firstChild || e;
\r
4393 e = r.startContainer;
\r
4395 if (e.nodeType == 1 && e.hasChildNodes())
\r
4396 e = e.childNodes[Math.min(e.childNodes.length - 1, r.startOffset)];
\r
4398 if (e && e.nodeType == 3)
\r
4399 return e.parentNode;
\r
4405 getEnd : function() {
\r
4406 var t = this, r = t.getRng(), e, eo;
\r
4412 r = r.duplicate();
\r
4414 e = r.parentElement();
\r
4416 if (e && e.nodeName == 'BODY')
\r
4417 return e.lastChild || e;
\r
4421 e = r.endContainer;
\r
4424 if (e.nodeType == 1 && e.hasChildNodes())
\r
4425 e = e.childNodes[eo > 0 ? eo - 1 : eo];
\r
4427 if (e && e.nodeType == 3)
\r
4428 return e.parentNode;
\r
4434 getBookmark : function(type, normalized) {
\r
4435 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
\r
4437 function findIndex(name, element) {
\r
4440 each(dom.select(name), function(node, i) {
\r
4441 if (node == element)
\r
4449 function getLocation() {
\r
4450 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
\r
4452 function getPoint(rng, start) {
\r
4453 var indexes = [], node, lastIdx,
\r
4454 container = rng[start ? 'startContainer' : 'endContainer'],
\r
4455 offset = rng[start ? 'startOffset' : 'endOffset'], exclude, point = {};
\r
4457 // Resolve element index
\r
4458 if (container.nodeType == 1 && container.hasChildNodes()) {
\r
4459 lastIdx = container.childNodes.length - 1;
\r
4460 point.exclude = (start && offset > lastIdx) || (!start && offset == 0);
\r
4462 if (!start && offset)
\r
4465 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
\r
4467 if (container.nodeType == 3)
\r
4468 offset = start ? 0 : container.nodeValue.length;
\r
4471 if (container.nodeType == 3) {
\r
4473 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
\r
4474 offset += node.nodeValue.length;
\r
4477 point.offset = offset;
\r
4480 for (; container && container != root; container = container.parentNode)
\r
4481 indexes.push(t.dom.nodeIndex(container, normalized));
\r
4483 point.indexes = indexes;
\r
4488 bookmark.start = getPoint(rng, true);
\r
4490 if (!t.isCollapsed())
\r
4491 bookmark.end = getPoint(rng);
\r
4496 return getLocation();
\r
4499 // Handle simple range
\r
4501 return {rng : t.getRng()};
\r
4504 id = dom.uniqueId();
\r
4505 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
\r
4506 styles = 'overflow:hidden;line-height:0px';
\r
4508 // Explorer method
\r
4509 if (rng.duplicate || rng.item) {
\r
4512 rng2 = rng.duplicate();
\r
4514 // Insert start marker
\r
4516 rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
\r
4518 // Insert end marker
\r
4520 rng2.collapse(false);
\r
4521 rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
\r
4524 // Control selection
\r
4525 element = rng.item(0);
\r
4526 name = element.nodeName;
\r
4528 return {name : name, index : findIndex(name, element)};
\r
4531 element = t.getNode();
\r
4532 name = element.nodeName;
\r
4533 if (name == 'IMG')
\r
4534 return {name : name, index : findIndex(name, element)};
\r
4537 rng2 = rng.cloneRange();
\r
4539 // Insert end marker
\r
4541 rng2.collapse(false);
\r
4542 rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
\r
4545 rng.collapse(true);
\r
4546 rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
\r
4549 t.moveToBookmark({id : id, keep : 1});
\r
4554 moveToBookmark : function(bookmark) {
\r
4555 var t = this, dom = t.dom, marker1, marker2, rng, root;
\r
4557 // Clear selection cache
\r
4559 t.tridentSel.destroy();
\r
4562 if (bookmark.start) {
\r
4563 rng = dom.createRng();
\r
4564 root = dom.getRoot();
\r
4566 function setEndPoint(start) {
\r
4567 var point = bookmark[start ? 'start' : 'end'], i, node, offset;
\r
4570 for (node = root, i = point.indexes.length - 1; i >= 0; i--)
\r
4571 node = node.childNodes[point.indexes[i]] || node;
\r
4574 if (node.nodeType == 3 && point.offset)
\r
4575 rng.setStart(node, point.offset);
\r
4577 if (point.exclude)
\r
4578 rng.setStartAfter(node);
\r
4580 rng.setStartBefore(node);
\r
4583 if (node.nodeType == 3 && point.offset)
\r
4584 rng.setEnd(node, point.offset);
\r
4586 if (point.exclude)
\r
4587 rng.setEndBefore(node);
\r
4589 rng.setEndAfter(node);
\r
4595 setEndPoint(true);
\r
4599 } else if (bookmark.id) {
\r
4600 rng = dom.createRng();
\r
4602 function restoreEndPoint(suffix) {
\r
4603 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
\r
4606 node = marker.parentNode;
\r
4608 if (suffix == 'start') {
\r
4610 idx = dom.nodeIndex(marker);
\r
4619 rng.setStart(node, idx);
\r
4620 rng.setEnd(node, idx);
\r
4623 idx = dom.nodeIndex(marker);
\r
4629 rng.setEnd(node, idx);
\r
4633 prev = marker.previousSibling;
\r
4634 next = marker.nextSibling;
\r
4636 // Remove all marker text nodes
\r
4637 each(tinymce.grep(marker.childNodes), function(node) {
\r
4638 if (node.nodeType == 3)
\r
4639 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
\r
4642 // Remove marker but keep children if for example contents where inserted into the marker
\r
4643 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
\r
4644 while (marker = dom.get(bookmark.id + '_' + suffix))
\r
4645 dom.remove(marker, 1);
\r
4647 // If siblings are text nodes then merge them
\r
4648 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
\r
4649 idx = prev.nodeValue.length;
\r
4650 prev.appendData(next.nodeValue);
\r
4653 if (suffix == 'start') {
\r
4654 rng.setStart(prev, idx);
\r
4655 rng.setEnd(prev, idx);
\r
4657 rng.setEnd(prev, idx);
\r
4663 // Restore start/end points
\r
4664 restoreEndPoint('start');
\r
4665 restoreEndPoint('end');
\r
4668 } else if (bookmark.name) {
\r
4669 t.select(dom.select(bookmark.name)[bookmark.index]);
\r
4670 } else if (bookmark.rng)
\r
4671 t.setRng(bookmark.rng);
\r
4675 select : function(node, content) {
\r
4676 var t = this, dom = t.dom, rng = dom.createRng(), idx;
\r
4678 idx = dom.nodeIndex(node);
\r
4679 rng.setStart(node.parentNode, idx);
\r
4680 rng.setEnd(node.parentNode, idx + 1);
\r
4682 // Find first/last text node or BR element
\r
4684 function setPoint(node, start) {
\r
4685 var walker = new tinymce.dom.TreeWalker(node, node);
\r
4689 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
\r
4691 rng.setStart(node, 0);
\r
4693 rng.setEnd(node, node.nodeValue.length);
\r
4699 if (node.nodeName == 'BR') {
\r
4701 rng.setStartBefore(node);
\r
4703 rng.setEndBefore(node);
\r
4707 } while (node = (start ? walker.next() : walker.prev()));
\r
4710 setPoint(node, 1);
\r
4719 isCollapsed : function() {
\r
4720 var t = this, r = t.getRng(), s = t.getSel();
\r
4725 if (r.compareEndPoints)
\r
4726 return r.compareEndPoints('StartToEnd', r) === 0;
\r
4728 return !s || r.collapsed;
\r
4731 collapse : function(b) {
\r
4732 var t = this, r = t.getRng(), n;
\r
4734 // Control range on IE
\r
4737 r = this.win.document.body.createTextRange();
\r
4738 r.moveToElementText(n);
\r
4745 getSel : function() {
\r
4746 var t = this, w = this.win;
\r
4748 return w.getSelection ? w.getSelection() : w.document.selection;
\r
4751 getRng : function(w3c) {
\r
4752 var t = this, s, r;
\r
4754 // Found tridentSel object then we need to use that one
\r
4755 if (w3c && t.tridentSel)
\r
4756 return t.tridentSel.getRangeAt(0);
\r
4759 if (s = t.getSel())
\r
4760 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
\r
4762 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
\r
4765 // No range found then create an empty one
\r
4766 // This can occur when the editor is placed in a hidden container element on Gecko
\r
4767 // Or on IE when there was an exception
\r
4769 r = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange();
\r
4774 setRng : function(r) {
\r
4777 if (!t.tridentSel) {
\r
4781 s.removeAllRanges();
\r
4786 if (r.cloneRange) {
\r
4787 t.tridentSel.addRange(r);
\r
4791 // Is IE specific range
\r
4795 // Needed for some odd IE bug #1843306
\r
4800 setNode : function(n) {
\r
4803 t.setContent(t.dom.getOuterHTML(n));
\r
4808 getNode : function() {
\r
4809 var t = this, rng = t.getRng(), sel = t.getSel(), elm;
\r
4812 // Range maybe lost after the editor is made visible again
\r
4814 return t.dom.getRoot();
\r
4816 elm = rng.commonAncestorContainer;
\r
4818 // Handle selection a image or other control like element such as anchors
\r
4819 if (!rng.collapsed) {
\r
4820 if (rng.startContainer == rng.endContainer) {
\r
4821 if (rng.startOffset - rng.endOffset < 2) {
\r
4822 if (rng.startContainer.hasChildNodes())
\r
4823 elm = rng.startContainer.childNodes[rng.startOffset];
\r
4827 // If the anchor node is a element instead of a text node then return this element
\r
4828 if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
\r
4829 return sel.anchorNode.childNodes[sel.anchorOffset];
\r
4832 if (elm && elm.nodeType == 3)
\r
4833 return elm.parentNode;
\r
4838 return rng.item ? rng.item(0) : rng.parentElement();
\r
4841 getSelectedBlocks : function(st, en) {
\r
4842 var t = this, dom = t.dom, sb, eb, n, bl = [];
\r
4844 sb = dom.getParent(st || t.getStart(), dom.isBlock);
\r
4845 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
\r
4850 if (sb && eb && sb != eb) {
\r
4853 while ((n = n.nextSibling) && n != eb) {
\r
4854 if (dom.isBlock(n))
\r
4859 if (eb && sb != eb)
\r
4865 destroy : function(s) {
\r
4871 t.tridentSel.destroy();
\r
4873 // Manual destroy then remove unload handler
\r
4875 tinymce.removeUnload(t.destroy);
\r
4879 (function(tinymce) {
\r
4880 tinymce.create('tinymce.dom.XMLWriter', {
\r
4883 XMLWriter : function(s) {
\r
4884 // Get XML document
\r
4885 function getXML() {
\r
4886 var i = document.implementation;
\r
4888 if (!i || !i.createDocument) {
\r
4890 try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
\r
4891 try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
\r
4893 return i.createDocument('', '', null);
\r
4896 this.doc = getXML();
\r
4898 // Since Opera and WebKit doesn't escape > into > we need to do it our self to normalize the output for all browsers
\r
4899 this.valid = tinymce.isOpera || tinymce.isWebKit;
\r
4904 reset : function() {
\r
4905 var t = this, d = t.doc;
\r
4908 d.removeChild(d.firstChild);
\r
4910 t.node = d.appendChild(d.createElement("html"));
\r
4913 writeStartElement : function(n) {
\r
4916 t.node = t.node.appendChild(t.doc.createElement(n));
\r
4919 writeAttribute : function(n, v) {
\r
4921 v = v.replace(/>/g, '%MCGT%');
\r
4923 this.node.setAttribute(n, v);
\r
4926 writeEndElement : function() {
\r
4927 this.node = this.node.parentNode;
\r
4930 writeFullEndElement : function() {
\r
4931 var t = this, n = t.node;
\r
4933 n.appendChild(t.doc.createTextNode(""));
\r
4934 t.node = n.parentNode;
\r
4937 writeText : function(v) {
\r
4939 v = v.replace(/>/g, '%MCGT%');
\r
4941 this.node.appendChild(this.doc.createTextNode(v));
\r
4944 writeCDATA : function(v) {
\r
4945 this.node.appendChild(this.doc.createCDATASection(v));
\r
4948 writeComment : function(v) {
\r
4949 // Fix for bug #2035694
\r
4951 v = v.replace(/^\-|\-$/g, ' ');
\r
4953 this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
\r
4956 getContent : function() {
\r
4959 h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
\r
4960 h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
\r
4961 h = h.replace(/ ?\/>/g, ' />');
\r
4964 h = h.replace(/\%MCGT%/g, '>');
\r
4970 (function(tinymce) {
\r
4971 tinymce.create('tinymce.dom.StringWriter', {
\r
4978 StringWriter : function(s) {
\r
4979 this.settings = tinymce.extend({
\r
4980 indent_char : ' ',
\r
4987 reset : function() {
\r
4994 writeStartElement : function(n) {
\r
4995 this._writeAttributesEnd();
\r
4996 this.writeRaw('<' + n);
\r
4997 this.tags.push(n);
\r
4998 this.inAttr = true;
\r
5000 this.elementCount = this.count;
\r
5003 writeAttribute : function(n, v) {
\r
5006 t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
\r
5009 writeEndElement : function() {
\r
5012 if (this.tags.length > 0) {
\r
5013 n = this.tags.pop();
\r
5015 if (this._writeAttributesEnd(1))
\r
5016 this.writeRaw('</' + n + '>');
\r
5018 if (this.settings.indentation > 0)
\r
5019 this.writeRaw('\n');
\r
5023 writeFullEndElement : function() {
\r
5024 if (this.tags.length > 0) {
\r
5025 this._writeAttributesEnd();
\r
5026 this.writeRaw('</' + this.tags.pop() + '>');
\r
5028 if (this.settings.indentation > 0)
\r
5029 this.writeRaw('\n');
\r
5033 writeText : function(v) {
\r
5034 this._writeAttributesEnd();
\r
5035 this.writeRaw(this.encode(v));
\r
5039 writeCDATA : function(v) {
\r
5040 this._writeAttributesEnd();
\r
5041 this.writeRaw('<![CDATA[' + v + ']]>');
\r
5045 writeComment : function(v) {
\r
5046 this._writeAttributesEnd();
\r
5047 this.writeRaw('<!-- ' + v + '-->');
\r
5051 writeRaw : function(v) {
\r
5055 encode : function(s) {
\r
5056 return s.replace(/[<>&"]/g, function(v) {
\r
5075 getContent : function() {
\r
5079 _writeAttributesEnd : function(s) {
\r
5083 this.inAttr = false;
\r
5085 if (s && this.elementCount == this.count) {
\r
5086 this.writeRaw(' />');
\r
5090 this.writeRaw('>');
\r
5096 (function(tinymce) {
\r
5098 var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
\r
5100 function wildcardToRE(s) {
\r
5101 return s.replace(/([?+*])/g, '.$1');
\r
5104 tinymce.create('tinymce.dom.Serializer', {
\r
5105 Serializer : function(s) {
\r
5109 t.onPreProcess = new Dispatcher(t);
\r
5110 t.onPostProcess = new Dispatcher(t);
\r
5113 t.writer = new tinymce.dom.XMLWriter();
\r
5115 // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
\r
5116 t.writer = new tinymce.dom.StringWriter();
\r
5119 // Default settings
\r
5120 t.settings = s = extend({
\r
5121 dom : tinymce.DOM,
\r
5125 invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
\r
5126 closed : /^(br|hr|input|meta|img|link|param|area)$/,
\r
5127 entity_encoding : 'named',
\r
5128 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
5129 valid_elements : '*[*]',
\r
5130 extended_valid_elements : 0,
\r
5131 invalid_elements : 0,
\r
5132 fix_table_elements : 1,
\r
5133 fix_list_elements : true,
\r
5134 fix_content_duplication : true,
\r
5135 convert_fonts_to_spans : false,
\r
5136 font_size_classes : 0,
\r
5137 apply_source_formatting : 0,
\r
5138 indent_mode : 'simple',
\r
5139 indent_char : '\t',
\r
5140 indent_levels : 1,
\r
5141 remove_linebreaks : 1,
\r
5142 remove_redundant_brs : 1,
\r
5143 element_format : 'xhtml'
\r
5147 t.schema = s.schema;
\r
5149 // Use raw entities if no entities are defined
\r
5150 if (s.entity_encoding == 'named' && !s.entities)
\r
5151 s.entity_encoding = 'raw';
\r
5153 if (s.remove_redundant_brs) {
\r
5154 t.onPostProcess.add(function(se, o) {
\r
5155 // Remove single BR at end of block elements since they get rendered
\r
5156 o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
\r
5157 // Check if it's a single element
\r
5158 if (/^<br \/>\s*<\//.test(a))
\r
5159 return '</' + c + '>';
\r
5166 // Remove XHTML element endings i.e. produce crap :) XHTML is better
\r
5167 if (s.element_format == 'html') {
\r
5168 t.onPostProcess.add(function(se, o) {
\r
5169 o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
\r
5173 if (s.fix_list_elements) {
\r
5174 t.onPreProcess.add(function(se, o) {
\r
5175 var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
\r
5177 function prevNode(e, n) {
\r
5178 var a = n.split(','), i;
\r
5180 while ((e = e.previousSibling) != null) {
\r
5181 for (i=0; i<a.length; i++) {
\r
5182 if (e.nodeName == a[i])
\r
5190 for (x=0; x<a.length; x++) {
\r
5191 nl = t.dom.select(a[x], o.node);
\r
5193 for (i=0; i<nl.length; i++) {
\r
5197 if (r.test(p.nodeName)) {
\r
5198 np = prevNode(n, 'LI');
\r
5201 np = t.dom.create('li');
\r
5202 np.innerHTML = ' ';
\r
5203 np.appendChild(n);
\r
5204 p.insertBefore(np, p.firstChild);
\r
5206 np.appendChild(n);
\r
5213 if (s.fix_table_elements) {
\r
5214 t.onPreProcess.add(function(se, o) {
\r
5215 // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
\r
5216 // so Opera users with an older version will have to live with less compaible output not much we can do here
\r
5217 if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
\r
5218 each(t.dom.select('p table', o.node).reverse(), function(n) {
\r
5219 var parent = t.dom.getParent(n.parentNode, 'table,p');
\r
5221 if (parent.nodeName != 'TABLE') {
\r
5223 t.dom.split(parent, n);
\r
5225 // IE can sometimes fire an unknown runtime error so we just ignore it
\r
5234 setEntities : function(s) {
\r
5235 var t = this, a, i, l = {}, v;
\r
5237 // No need to setup more than once
\r
5238 if (t.entityLookup)
\r
5241 // Build regex and lookup array
\r
5243 for (i = 0; i < a.length; i += 2) {
\r
5246 // Don't add default & " etc.
\r
5247 if (v == 34 || v == 38 || v == 60 || v == 62)
\r
5250 l[String.fromCharCode(a[i])] = a[i + 1];
\r
5252 v = parseInt(a[i]).toString(16);
\r
5255 t.entityLookup = l;
\r
5258 setRules : function(s) {
\r
5264 t.validElements = {};
\r
5266 return t.addRules(s);
\r
5269 addRules : function(s) {
\r
5277 each(s.split(','), function(s) {
\r
5278 var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
\r
5280 // Extend with default rules
\r
5282 at = tinymce.extend([], dr.attribs);
\r
5284 // Parse attributes
\r
5285 if (p.length > 1) {
\r
5286 each(p[1].split('|'), function(s) {
\r
5291 // Parse attribute rule
\r
5292 s = s.replace(/::/g, '~');
\r
5293 s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
\r
5294 s[2] = s[2].replace(/~/g, ':');
\r
5296 // Add required attributes
\r
5297 if (s[1] == '!') {
\r
5302 // Remove inherited attributes
\r
5303 if (s[1] == '-') {
\r
5304 for (i = 0; i <at.length; i++) {
\r
5305 if (at[i].name == s[2]) {
\r
5313 // Add default attrib values
\r
5315 ar.defaultVal = s[4] || '';
\r
5318 // Add forced attrib values
\r
5320 ar.forcedVal = s[4];
\r
5323 // Add validation values
\r
5325 ar.validVals = s[4].split('?');
\r
5329 if (/[*.?]/.test(s[2])) {
\r
5331 ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
\r
5342 // Handle element names
\r
5343 each(tn, function(s, i) {
\r
5344 var pr = s.charAt(0), x = 1, ru = {};
\r
5346 // Extend with default rule data
\r
5349 ru.noEmpty = dr.noEmpty;
\r
5352 ru.fullEnd = dr.fullEnd;
\r
5355 ru.padd = dr.padd;
\r
5358 // Handle prefixes
\r
5361 ru.noEmpty = true;
\r
5365 ru.fullEnd = true;
\r
5376 tn[i] = s = s.substring(x);
\r
5377 t.validElements[s] = 1;
\r
5379 // Add element name or element regex
\r
5380 if (/[*.?]/.test(tn[0])) {
\r
5381 ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
\r
5382 t.wildRules = t.wildRules || {};
\r
5383 t.wildRules.push(ru);
\r
5387 // Store away default rule
\r
5397 ru.requiredAttribs = ra;
\r
5400 // Build valid attributes regexp
\r
5402 each(va, function(v) {
\r
5406 s += '(' + wildcardToRE(v) + ')';
\r
5408 ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
\r
5409 ru.wildAttribs = wat;
\r
5414 // Build valid elements regexp
\r
5416 each(t.validElements, function(v, k) {
\r
5423 t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
\r
5425 //console.debug(t.validElementsRE.toString());
\r
5426 //console.dir(t.rules);
\r
5427 //console.dir(t.wildRules);
\r
5430 findRule : function(n) {
\r
5431 var t = this, rl = t.rules, i, r;
\r
5442 for (i = 0; i < rl.length; i++) {
\r
5443 if (rl[i].nameRE.test(n))
\r
5450 findAttribRule : function(ru, n) {
\r
5451 var i, wa = ru.wildAttribs;
\r
5453 for (i = 0; i < wa.length; i++) {
\r
5454 if (wa[i].nameRE.test(n))
\r
5461 serialize : function(n, o) {
\r
5462 var h, t = this, doc, oldDoc, impl, selected;
\r
5466 o.format = o.format || 'html';
\r
5469 // IE looses the selected attribute on option elements so we need to store it
\r
5470 // See: http://support.microsoft.com/kb/829907
\r
5473 each(n.getElementsByTagName('option'), function(n) {
\r
5474 var v = t.dom.getAttrib(n, 'selected');
\r
5476 selected.push(v ? v : null);
\r
5480 n = n.cloneNode(true);
\r
5482 // IE looses the selected attribute on option elements so we need to restore it
\r
5484 each(n.getElementsByTagName('option'), function(n, i) {
\r
5485 t.dom.setAttrib(n, 'selected', selected[i]);
\r
5489 // Nodes needs to be attached to something in WebKit/Opera
\r
5490 // Older builds of Opera crashes if you attach the node to an document created dynamically
\r
5491 // and since we can't feature detect a crash we need to sniff the acutal build number
\r
5492 // This fix will make DOM ranges and make Sizzle happy!
\r
5493 impl = n.ownerDocument.implementation;
\r
5494 if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
\r
5495 // Create an empty HTML document
\r
5496 doc = impl.createHTMLDocument("");
\r
5498 // Add the element or it's children if it's a body element to the new document
\r
5499 each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
\r
5500 doc.body.appendChild(doc.importNode(node, true));
\r
5503 // Grab first child or body element for serialization
\r
5504 if (n.nodeName != 'BODY')
\r
5505 n = doc.body.firstChild;
\r
5509 // set the new document in DOMUtils so createElement etc works
\r
5510 oldDoc = t.dom.doc;
\r
5514 t.key = '' + (parseInt(t.key) + 1);
\r
5517 if (!o.no_events) {
\r
5519 t.onPreProcess.dispatch(t, o);
\r
5522 // Serialize HTML DOM into a string
\r
5525 t._serializeNode(n, o.getInner);
\r
5528 o.content = t.writer.getContent();
\r
5530 // Restore the old document if it was changed
\r
5532 t.dom.doc = oldDoc;
\r
5535 t.onPostProcess.dispatch(t, o);
\r
5537 t._postProcess(o);
\r
5540 return tinymce.trim(o.content);
\r
5543 // Internal functions
\r
5545 _postProcess : function(o) {
\r
5546 var t = this, s = t.settings, h = o.content, sc = [], p;
\r
5548 if (o.format == 'html') {
\r
5549 // Protect some elements
\r
5553 {pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
\r
5554 {pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
\r
5555 {pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
\r
5556 {pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
\r
5557 {pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
\r
5564 if (s.entity_encoding !== 'raw')
\r
5567 // Use BR instead of padded P elements inside editor and use <p> </p> outside editor
\r
5569 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
\r
5571 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
\r
5573 // Since Gecko and Safari keeps whitespace in the DOM we need to
\r
5574 // remove it inorder to match other browsers. But I think Gecko and Safari is right.
\r
5575 // This process is only done when getting contents out from the editor.
\r
5577 // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
\r
5578 h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1> </p>' : '<p$1> </p>');
\r
5580 if (s.remove_linebreaks) {
\r
5581 h = h.replace(/\r?\n|\r/g, ' ');
\r
5582 h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
\r
5583 h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
\r
5584 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
5585 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
5586 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
5589 // Simple indentation
\r
5590 if (s.apply_source_formatting && s.indent_mode == 'simple') {
\r
5591 // Add line breaks before and after block elements
\r
5592 h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
\r
5593 h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
\r
5594 h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
\r
5595 h = h.replace(/\n\n/g, '\n');
\r
5599 h = t._unprotect(h, p);
\r
5601 // Restore CDATA sections
\r
5602 h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
\r
5604 // Restore the \u00a0 character if raw mode is enabled
\r
5605 if (s.entity_encoding == 'raw')
\r
5606 h = h.replace(/<p> <\/p>|<p([^>]+)> <\/p>/g, '<p$1>\u00a0</p>');
\r
5608 // Restore noscript elements
\r
5609 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
5610 return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
\r
5617 _serializeNode : function(n, inner) {
\r
5618 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
5620 if (!s.node_filter || s.node_filter(n)) {
\r
5621 switch (n.nodeType) {
\r
5622 case 1: // Element
\r
5623 if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
\r
5626 iv = keep = false;
\r
5627 hc = n.hasChildNodes();
\r
5628 nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
\r
5630 // Get internal type
\r
5631 type = n.getAttribute('_mce_type');
\r
5633 if (!t._info.cleanup) {
\r
5640 // Add correct prefix on IE
\r
5642 if (n.scopeName !== 'HTML' && n.scopeName !== 'html')
\r
5643 nn = n.scopeName + ':' + nn;
\r
5646 // Remove mce prefix on IE needed for the abbr element
\r
5647 if (nn.indexOf('mce:') === 0)
\r
5648 nn = nn.substring(4);
\r
5652 if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
\r
5659 // Fix IE content duplication (DOM can have multiple copies of the same node)
\r
5660 if (s.fix_content_duplication) {
\r
5661 if (n._mce_serialized == t.key)
\r
5664 n._mce_serialized = t.key;
\r
5667 // IE sometimes adds a / infront of the node name
\r
5668 if (nn.charAt(0) == '/')
\r
5669 nn = nn.substring(1);
\r
5670 } else if (isGecko) {
\r
5671 // Ignore br elements
\r
5672 if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
\r
5676 // Check if valid child
\r
5677 if (s.validate_children) {
\r
5678 if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
\r
5683 t.elementName = nn;
\r
5686 ru = t.findRule(nn);
\r
5687 nn = ru.name || nn;
\r
5688 closed = s.closed.test(nn);
\r
5690 // Skip empty nodes or empty node name in IE
\r
5691 if ((!hc && ru.noEmpty) || (isIE && !nn)) {
\r
5697 if (ru.requiredAttribs) {
\r
5698 a = ru.requiredAttribs;
\r
5700 for (i = a.length - 1; i >= 0; i--) {
\r
5701 if (this.dom.getAttrib(n, a[i]) !== '')
\r
5705 // None of the required was there
\r
5712 w.writeStartElement(nn);
\r
5714 // Add ordered attributes
\r
5716 for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
\r
5718 v = t._getAttrib(n, a);
\r
5721 w.writeAttribute(a.name, v);
\r
5725 // Add wild attributes
\r
5726 if (ru.validAttribsRE) {
\r
5727 at = t.dom.getAttribs(n);
\r
5728 for (i=at.length-1; i>-1; i--) {
\r
5731 if (no.specified) {
\r
5732 a = no.nodeName.toLowerCase();
\r
5734 if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
\r
5737 ar = t.findAttribRule(ru, a);
\r
5738 v = t._getAttrib(n, ar, a);
\r
5741 w.writeAttribute(a, v);
\r
5746 // Keep type attribute
\r
5748 w.writeAttribute('_mce_type', type);
\r
5750 // Write text from script
\r
5751 if (nn === 'script' && tinymce.trim(n.innerHTML)) {
\r
5752 w.writeText('// '); // Padd it with a comment so it will parse on older browsers
\r
5753 w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
\r
5758 // Padd empty nodes with a
\r
5760 // If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
\r
5761 if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
\r
5762 if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
\r
5763 w.writeText('\u00a0');
\r
5765 w.writeText('\u00a0'); // No children then padd it
\r
5771 // Check if valid child
\r
5772 if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
\r
5775 return w.writeText(n.nodeValue);
\r
5778 return w.writeCDATA(n.nodeValue);
\r
5780 case 8: // Comment
\r
5781 return w.writeComment(n.nodeValue);
\r
5783 } else if (n.nodeType == 1)
\r
5784 hc = n.hasChildNodes();
\r
5786 if (hc && !closed) {
\r
5787 cn = n.firstChild;
\r
5790 t._serializeNode(cn);
\r
5791 t.elementName = nn;
\r
5792 cn = cn.nextSibling;
\r
5796 // Write element end
\r
5799 w.writeFullEndElement();
\r
5801 w.writeEndElement();
\r
5805 _protect : function(o) {
\r
5808 o.items = o.items || [];
\r
5811 return s.replace(/[\r\n\\]/g, function(c) {
\r
5814 else if (c === '\\')
\r
5822 return s.replace(/\\[\\rn]/g, function(c) {
\r
5825 else if (c === '\\\\')
\r
5832 each(o.patterns, function(p) {
\r
5833 o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
\r
5840 return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
\r
5847 _unprotect : function(h, o) {
\r
5848 h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
\r
5849 return o.items[parseInt(b)];
\r
5857 _encode : function(h) {
\r
5858 var t = this, s = t.settings, l;
\r
5861 if (s.entity_encoding !== 'raw') {
\r
5862 if (s.entity_encoding.indexOf('named') != -1) {
\r
5863 t.setEntities(s.entities);
\r
5864 l = t.entityLookup;
\r
5866 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
5870 a = '&' + v + ';';
\r
5876 if (s.entity_encoding.indexOf('numeric') != -1) {
\r
5877 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
5878 return '&#' + a.charCodeAt(0) + ';';
\r
5886 _setup : function() {
\r
5887 var t = this, s = this.settings;
\r
5894 t.setRules(s.valid_elements);
\r
5895 t.addRules(s.extended_valid_elements);
\r
5897 if (s.invalid_elements)
\r
5898 t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
\r
5900 if (s.attrib_value_filter)
\r
5901 t.attribValueFilter = s.attribValueFilter;
\r
5904 _getAttrib : function(n, a, na) {
\r
5907 na = na || a.name;
\r
5909 if (a.forcedVal && (v = a.forcedVal)) {
\r
5910 if (v === '{$uid}')
\r
5911 return this.dom.uniqueId();
\r
5916 v = this.dom.getAttrib(n, na);
\r
5921 // Whats the point? Remove usless attribute value
\r
5928 if (this.attribValueFilter)
\r
5929 v = this.attribValueFilter(na, v, n);
\r
5931 if (a.validVals) {
\r
5932 for (i = a.validVals.length - 1; i >= 0; i--) {
\r
5933 if (v == a.validVals[i])
\r
5941 if (v === '' && typeof(a.defaultVal) != 'undefined') {
\r
5944 if (v === '{$uid}')
\r
5945 return this.dom.uniqueId();
\r
5949 // Remove internal mceItemXX classes when content is extracted from editor
\r
5950 if (na == 'class' && this.processObj.get)
\r
5951 v = v.replace(/\s?mceItem\w+\s?/g, '');
\r
5962 (function(tinymce) {
\r
5963 tinymce.dom.ScriptLoader = function(settings) {
\r
5969 scriptLoadedCallbacks = {},
\r
5970 queueLoadedCallbacks = [],
\r
5974 function loadScript(url, callback) {
\r
5975 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
\r
5977 // Execute callback when script is loaded
\r
5982 elm.onreadystatechange = elm.onload = elm = null;
\r
5987 id = dom.uniqueId();
\r
5989 if (tinymce.isIE6) {
\r
5990 uri = new tinymce.util.URI(url);
\r
5993 // If script is from same domain and we
\r
5994 // use IE 6 then use XHR since it's more reliable
\r
5995 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
\r
5996 tinymce.util.XHR.send({
\r
5997 url : tinymce._addVer(uri.getURI()),
\r
5998 success : function(content) {
\r
5999 // Create new temp script element
\r
6000 var script = dom.create('script', {
\r
6001 type : 'text/javascript'
\r
6004 // Evaluate script in global scope
\r
6005 script.text = content;
\r
6006 document.getElementsByTagName('head')[0].appendChild(script);
\r
6007 dom.remove(script);
\r
6017 // Create new script element
\r
6018 elm = dom.create('script', {
\r
6020 type : 'text/javascript',
\r
6021 src : tinymce._addVer(url)
\r
6024 // Add onload and readystate listeners
\r
6025 elm.onload = done;
\r
6026 elm.onreadystatechange = function() {
\r
6027 var state = elm.readyState;
\r
6029 // Loaded state is passed on IE 6 however there
\r
6030 // are known issues with this method but we can't use
\r
6031 // XHR in a cross domain loading
\r
6032 if (state == 'complete' || state == 'loaded')
\r
6036 // Most browsers support this feature so we report errors
\r
6037 // for those at least to help users track their missing plugins etc
\r
6038 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
\r
6039 /*elm.onerror = function() {
\r
6040 alert('Failed to load: ' + url);
\r
6043 // Add script to document
\r
6044 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
\r
6047 this.isDone = function(url) {
\r
6048 return states[url] == LOADED;
\r
6051 this.markDone = function(url) {
\r
6052 states[url] = LOADED;
\r
6055 this.add = this.load = function(url, callback, scope) {
\r
6056 var item, state = states[url];
\r
6058 // Add url to load queue
\r
6059 if (state == undefined) {
\r
6061 states[url] = QUEUED;
\r
6065 // Store away callback for later execution
\r
6066 if (!scriptLoadedCallbacks[url])
\r
6067 scriptLoadedCallbacks[url] = [];
\r
6069 scriptLoadedCallbacks[url].push({
\r
6071 scope : scope || this
\r
6076 this.loadQueue = function(callback, scope) {
\r
6077 this.loadScripts(queue, callback, scope);
\r
6080 this.loadScripts = function(scripts, callback, scope) {
\r
6083 function execScriptLoadedCallbacks(url) {
\r
6084 // Execute URL callback functions
\r
6085 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
\r
6086 callback.func.call(callback.scope);
\r
6089 scriptLoadedCallbacks[url] = undefined;
\r
6092 queueLoadedCallbacks.push({
\r
6094 scope : scope || this
\r
6097 loadScripts = function() {
\r
6098 var loadingScripts = tinymce.grep(scripts);
\r
6100 // Current scripts has been handled
\r
6101 scripts.length = 0;
\r
6103 // Load scripts that needs to be loaded
\r
6104 tinymce.each(loadingScripts, function(url) {
\r
6105 // Script is already loaded then execute script callbacks directly
\r
6106 if (states[url] == LOADED) {
\r
6107 execScriptLoadedCallbacks(url);
\r
6111 // Is script not loading then start loading it
\r
6112 if (states[url] != LOADING) {
\r
6113 states[url] = LOADING;
\r
6116 loadScript(url, function() {
\r
6117 states[url] = LOADED;
\r
6120 execScriptLoadedCallbacks(url);
\r
6122 // Load more scripts if they where added by the recently loaded script
\r
6128 // No scripts are currently loading then execute all pending queue loaded callbacks
\r
6130 tinymce.each(queueLoadedCallbacks, function(callback) {
\r
6131 callback.func.call(callback.scope);
\r
6134 queueLoadedCallbacks.length = 0;
\r
6142 // Global script loader
\r
6143 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
\r
6145 tinymce.dom.TreeWalker = function(start_node, root_node) {
\r
6146 var node = start_node;
\r
6148 function findSibling(node, start_name, sibling_name, shallow) {
\r
6149 var sibling, parent;
\r
6152 // Walk into nodes if it has a start
\r
6153 if (!shallow && node[start_name])
\r
6154 return node[start_name];
\r
6156 // Return the sibling if it has one
\r
6157 if (node != root_node) {
\r
6158 sibling = node[sibling_name];
\r
6162 // Walk up the parents to look for siblings
\r
6163 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
\r
6164 sibling = parent[sibling_name];
\r
6172 this.current = function() {
\r
6176 this.next = function(shallow) {
\r
6177 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
\r
6180 this.prev = function(shallow) {
\r
6181 return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
\r
6185 var transitional = {};
\r
6187 function unpack(lookup, data) {
\r
6190 function replace(value) {
\r
6191 return value.replace(/[A-Z]+/g, function(key) {
\r
6192 return replace(lookup[key]);
\r
6197 for (key in lookup) {
\r
6198 if (lookup.hasOwnProperty(key))
\r
6199 lookup[key] = replace(lookup[key]);
\r
6202 // Unpack and parse data into object map
\r
6203 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
\r
6206 children = children.split(/\|/);
\r
6208 for (i = children.length - 1; i >= 0; i--)
\r
6209 map[children[i]] = 1;
\r
6211 transitional[name] = map;
\r
6215 // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
\r
6216 // we will later include the attributes here and use it as a default for valid elements but it
\r
6217 // requires us to rewrite the serializer engine
\r
6219 Z : '#|H|K|N|O|P',
\r
6220 Y : '#|X|form|R|Q',
\r
6221 X : 'p|T|div|U|W|isindex|fieldset|table',
\r
6222 W : 'pre|hr|blockquote|address|center|noframes',
\r
6223 U : 'ul|ol|dl|menu|dir',
\r
6224 ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
\r
6225 T : 'h1|h2|h3|h4|h5|h6',
\r
6228 ZA : '#|a|G|J|M|O|P',
\r
6229 R : '#|a|H|K|N|O',
\r
6231 P : 'ins|del|script',
\r
6232 O : 'input|select|textarea|label|button',
\r
6234 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
\r
6237 J : 'tt|i|b|u|s|strike',
\r
6238 I : 'big|small|font|basefont',
\r
6240 G : 'br|span|bdo',
\r
6241 F : 'object|applet|img|map|iframe'
\r
6244 'object[#|param|X|form|a|H|K|N|O|Q]' +
\r
6251 'applet[#|param|X|form|a|H|K|N|O|Q]' +
\r
6254 'map[X|form|Q|area]' +
\r
6256 'iframe[#|X|form|a|H|K|N|O|Q]' +
\r
6282 'select[optgroup|option]' +
\r
6283 'optgroup[option]' +
\r
6287 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
\r
6289 'ins[#|X|form|a|H|K|N|O|Q]' +
\r
6291 'del[#|X|form|a|H|K|N|O|Q]' +
\r
6293 'div[#|X|form|a|H|K|N|O|Q]' +
\r
6295 'li[#|X|form|a|H|K|N|O|Q]' +
\r
6299 'dd[#|X|form|a|H|K|N|O|Q]' +
\r
6304 'blockquote[#|X|form|a|H|K|N|O|Q]' +
\r
6306 'center[#|X|form|a|H|K|N|O|Q]' +
\r
6307 'noframes[#|X|form|a|H|K|N|O|Q]' +
\r
6309 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
\r
6311 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
\r
6314 'colgroup[col]' +
\r
6317 'th[#|X|form|a|H|K|N|O|Q]' +
\r
6318 'form[#|X|a|H|K|N|O|Q]' +
\r
6319 'noscript[#|X|form|a|H|K|N|O|Q]' +
\r
6320 'td[#|X|form|a|H|K|N|O|Q]' +
\r
6325 'body[#|X|form|a|H|K|N|O|Q]'
\r
6328 tinymce.dom.Schema = function() {
\r
6329 var t = this, elements = transitional;
\r
6331 t.isValid = function(name, child_name) {
\r
6332 var element = elements[name];
\r
6334 return !!(element && (!child_name || element[child_name]));
\r
6337 })();(function(tinymce) {
\r
6338 tinymce.dom.RangeUtils = function(dom) {
\r
6339 var INVISIBLE_CHAR = '\uFEFF';
\r
6341 this.walk = function(rng, callback) {
\r
6342 var startContainer = rng.startContainer,
\r
6343 startOffset = rng.startOffset,
\r
6344 endContainer = rng.endContainer,
\r
6345 endOffset = rng.endOffset,
\r
6346 ancestor, startPoint,
\r
6347 endPoint, node, parent, siblings, nodes;
\r
6349 // Handle table cell selection the table plugin enables
\r
6350 // you to fake select table cells and perform formatting actions on them
\r
6351 nodes = dom.select('td.mceSelected,th.mceSelected');
\r
6352 if (nodes.length > 0) {
\r
6353 tinymce.each(nodes, function(node) {
\r
6360 function collectSiblings(node, name, end_node) {
\r
6361 var siblings = [];
\r
6363 for (; node && node != end_node; node = node[name])
\r
6364 siblings.push(node);
\r
6369 function findEndPoint(node, root) {
\r
6371 if (node.parentNode == root)
\r
6374 node = node.parentNode;
\r
6378 function walkBoundary(start_node, end_node, next) {
\r
6379 var siblingName = next ? 'nextSibling' : 'previousSibling';
\r
6381 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
\r
6382 parent = node.parentNode;
\r
6383 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
\r
6385 if (siblings.length) {
\r
6387 siblings.reverse();
\r
6389 callback(siblings);
\r
6394 // If index based start position then resolve it
\r
6395 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
\r
6396 startContainer = startContainer.childNodes[startOffset];
\r
6398 // If index based end position then resolve it
\r
6399 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
\r
6400 endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
\r
6402 // Find common ancestor and end points
\r
6403 ancestor = dom.findCommonAncestor(startContainer, endContainer);
\r
6406 if (startContainer == endContainer)
\r
6407 return callback([startContainer]);
\r
6409 // Process left side
\r
6410 for (node = startContainer; node; node = node.parentNode) {
\r
6411 if (node == endContainer)
\r
6412 return walkBoundary(startContainer, ancestor, true);
\r
6414 if (node == ancestor)
\r
6418 // Process right side
\r
6419 for (node = endContainer; node; node = node.parentNode) {
\r
6420 if (node == startContainer)
\r
6421 return walkBoundary(endContainer, ancestor);
\r
6423 if (node == ancestor)
\r
6427 // Find start/end point
\r
6428 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
\r
6429 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
\r
6432 walkBoundary(startContainer, startPoint, true);
\r
6434 // Walk the middle from start to end point
\r
6435 siblings = collectSiblings(
\r
6436 startPoint == startContainer ? startPoint : startPoint.nextSibling,
\r
6438 endPoint == endContainer ? endPoint.nextSibling : endPoint
\r
6441 if (siblings.length)
\r
6442 callback(siblings);
\r
6444 // Walk right leaf
\r
6445 walkBoundary(endContainer, endPoint);
\r
6448 /* this.split = function(rng) {
\r
6449 var startContainer = rng.startContainer,
\r
6450 startOffset = rng.startOffset,
\r
6451 endContainer = rng.endContainer,
\r
6452 endOffset = rng.endOffset;
\r
6454 function splitText(node, offset) {
\r
6455 if (offset == node.nodeValue.length)
\r
6456 node.appendData(INVISIBLE_CHAR);
\r
6458 node = node.splitText(offset);
\r
6460 if (node.nodeValue === INVISIBLE_CHAR)
\r
6461 node.nodeValue = '';
\r
6466 // Handle single text node
\r
6467 if (startContainer == endContainer) {
\r
6468 if (startContainer.nodeType == 3) {
\r
6469 if (startOffset != 0)
\r
6470 startContainer = endContainer = splitText(startContainer, startOffset);
\r
6472 if (endOffset - startOffset != startContainer.nodeValue.length)
\r
6473 splitText(startContainer, endOffset - startOffset);
\r
6476 // Split startContainer text node if needed
\r
6477 if (startContainer.nodeType == 3 && startOffset != 0) {
\r
6478 startContainer = splitText(startContainer, startOffset);
\r
6482 // Split endContainer text node if needed
\r
6483 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
\r
6484 endContainer = splitText(endContainer, endOffset).previousSibling;
\r
6485 endOffset = endContainer.nodeValue.length;
\r
6490 startContainer : startContainer,
\r
6491 startOffset : startOffset,
\r
6492 endContainer : endContainer,
\r
6493 endOffset : endOffset
\r
6499 (function(tinymce) {
\r
6500 // Shorten class names
\r
6501 var DOM = tinymce.DOM, is = tinymce.is;
\r
6503 tinymce.create('tinymce.ui.Control', {
\r
6504 Control : function(id, s) {
\r
6506 this.settings = s = s || {};
\r
6507 this.rendered = false;
\r
6508 this.onRender = new tinymce.util.Dispatcher(this);
\r
6509 this.classPrefix = '';
\r
6510 this.scope = s.scope || this;
\r
6511 this.disabled = 0;
\r
6515 setDisabled : function(s) {
\r
6518 if (s != this.disabled) {
\r
6519 e = DOM.get(this.id);
\r
6521 // Add accessibility title for unavailable actions
\r
6522 if (e && this.settings.unavailable_prefix) {
\r
6524 this.prevTitle = e.title;
\r
6525 e.title = this.settings.unavailable_prefix + ": " + e.title;
\r
6527 e.title = this.prevTitle;
\r
6530 this.setState('Disabled', s);
\r
6531 this.setState('Enabled', !s);
\r
6532 this.disabled = s;
\r
6536 isDisabled : function() {
\r
6537 return this.disabled;
\r
6540 setActive : function(s) {
\r
6541 if (s != this.active) {
\r
6542 this.setState('Active', s);
\r
6547 isActive : function() {
\r
6548 return this.active;
\r
6551 setState : function(c, s) {
\r
6552 var n = DOM.get(this.id);
\r
6554 c = this.classPrefix + c;
\r
6557 DOM.addClass(n, c);
\r
6559 DOM.removeClass(n, c);
\r
6562 isRendered : function() {
\r
6563 return this.rendered;
\r
6566 renderHTML : function() {
\r
6569 renderTo : function(n) {
\r
6570 DOM.setHTML(n, this.renderHTML());
\r
6573 postRender : function() {
\r
6576 // Set pending states
\r
6577 if (is(t.disabled)) {
\r
6583 if (is(t.active)) {
\r
6590 remove : function() {
\r
6591 DOM.remove(this.id);
\r
6595 destroy : function() {
\r
6596 tinymce.dom.Event.clear(this.id);
\r
6599 })(tinymce);tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
\r
6600 Container : function(id, s) {
\r
6601 this.parent(id, s);
\r
6603 this.controls = [];
\r
6608 add : function(c) {
\r
6609 this.lookup[c.id] = c;
\r
6610 this.controls.push(c);
\r
6615 get : function(n) {
\r
6616 return this.lookup[n];
\r
6620 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
\r
6621 Separator : function(id, s) {
\r
6622 this.parent(id, s);
\r
6623 this.classPrefix = 'mceSeparator';
\r
6626 renderHTML : function() {
\r
6627 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
\r
6630 (function(tinymce) {
\r
6631 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
6633 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
\r
6634 MenuItem : function(id, s) {
\r
6635 this.parent(id, s);
\r
6636 this.classPrefix = 'mceMenuItem';
\r
6639 setSelected : function(s) {
\r
6640 this.setState('Selected', s);
\r
6641 this.selected = s;
\r
6644 isSelected : function() {
\r
6645 return this.selected;
\r
6648 postRender : function() {
\r
6653 // Set pending state
\r
6654 if (is(t.selected))
\r
6655 t.setSelected(t.selected);
\r
6659 (function(tinymce) {
\r
6660 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
6662 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
\r
6663 Menu : function(id, s) {
\r
6668 t.collapsed = false;
\r
6670 t.onAddItem = new tinymce.util.Dispatcher(this);
\r
6673 expand : function(d) {
\r
6677 walk(t, function(o) {
\r
6683 t.collapsed = false;
\r
6686 collapse : function(d) {
\r
6690 walk(t, function(o) {
\r
6696 t.collapsed = true;
\r
6699 isCollapsed : function() {
\r
6700 return this.collapsed;
\r
6703 add : function(o) {
\r
6705 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
\r
6707 this.onAddItem.dispatch(this, o);
\r
6709 return this.items[o.id] = o;
\r
6712 addSeparator : function() {
\r
6713 return this.add({separator : true});
\r
6716 addMenu : function(o) {
\r
6718 o = this.createMenu(o);
\r
6722 return this.add(o);
\r
6725 hasMenus : function() {
\r
6726 return this.menuCount !== 0;
\r
6729 remove : function(o) {
\r
6730 delete this.items[o.id];
\r
6733 removeAll : function() {
\r
6736 walk(t, function(o) {
\r
6748 createMenu : function(o) {
\r
6749 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
\r
6751 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
\r
6756 })(tinymce);(function(tinymce) {
\r
6757 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
\r
6759 tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
\r
6760 DropMenu : function(id, s) {
\r
6762 s.container = s.container || DOM.doc.body;
\r
6763 s.offset_x = s.offset_x || 0;
\r
6764 s.offset_y = s.offset_y || 0;
\r
6765 s.vp_offset_x = s.vp_offset_x || 0;
\r
6766 s.vp_offset_y = s.vp_offset_y || 0;
\r
6768 if (is(s.icons) && !s.icons)
\r
6769 s['class'] += ' mceNoIcons';
\r
6771 this.parent(id, s);
\r
6772 this.onShowMenu = new tinymce.util.Dispatcher(this);
\r
6773 this.onHideMenu = new tinymce.util.Dispatcher(this);
\r
6774 this.classPrefix = 'mceMenu';
\r
6777 createMenu : function(s) {
\r
6778 var t = this, cs = t.settings, m;
\r
6780 s.container = s.container || cs.container;
\r
6782 s.constrain = s.constrain || cs.constrain;
\r
6783 s['class'] = s['class'] || cs['class'];
\r
6784 s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
\r
6785 s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
\r
6786 m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
\r
6788 m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
\r
6793 update : function() {
\r
6794 var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
\r
6796 tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
\r
6797 th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
\r
6799 if (!DOM.boxModel)
\r
6800 t.element.setStyles({width : tw + 2, height : th + 2});
\r
6802 t.element.setStyles({width : tw, height : th});
\r
6805 DOM.setStyle(co, 'width', tw);
\r
6807 if (s.max_height) {
\r
6808 DOM.setStyle(co, 'height', th);
\r
6810 if (tb.clientHeight < s.max_height)
\r
6811 DOM.setStyle(co, 'overflow', 'hidden');
\r
6815 showMenu : function(x, y, px) {
\r
6816 var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
\r
6820 if (t.isMenuVisible)
\r
6823 if (!t.rendered) {
\r
6824 co = DOM.add(t.settings.container, t.renderNode());
\r
6826 each(t.items, function(o) {
\r
6830 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
6832 co = DOM.get('menu_' + t.id);
\r
6834 // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
\r
6835 if (!tinymce.isOpera)
\r
6836 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
\r
6841 x += s.offset_x || 0;
\r
6842 y += s.offset_y || 0;
\r
6846 // Move inside viewport if not submenu
\r
6847 if (s.constrain) {
\r
6848 w = co.clientWidth - ot;
\r
6849 h = co.clientHeight - ot;
\r
6853 if ((x + s.vp_offset_x + w) > mx)
\r
6854 x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
\r
6856 if ((y + s.vp_offset_y + h) > my)
\r
6857 y = Math.max(0, (my - s.vp_offset_y) - h);
\r
6860 DOM.setStyles(co, {left : x , top : y});
\r
6861 t.element.update();
\r
6863 t.isMenuVisible = 1;
\r
6864 t.mouseClickFunc = Event.add(co, 'click', function(e) {
\r
6869 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
\r
6870 m = t.items[e.id];
\r
6872 if (m.isDisabled())
\r
6881 dm = dm.settings.parent;
\r
6884 if (m.settings.onclick)
\r
6885 m.settings.onclick(e);
\r
6887 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
6891 if (t.hasMenus()) {
\r
6892 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
\r
6896 if (e && (e = DOM.getParent(e, 'tr'))) {
\r
6897 m = t.items[e.id];
\r
6900 t.lastMenu.collapse(1);
\r
6902 if (m.isDisabled())
\r
6905 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
\r
6906 //p = DOM.getPos(s.container);
\r
6907 r = DOM.getRect(e);
\r
6908 m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
\r
6910 DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
\r
6916 t.onShowMenu.dispatch(t);
\r
6918 if (s.keyboard_focus) {
\r
6919 Event.add(co, 'keydown', t._keyHandler, t);
\r
6920 DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
\r
6925 hideMenu : function(c) {
\r
6926 var t = this, co = DOM.get('menu_' + t.id), e;
\r
6928 if (!t.isMenuVisible)
\r
6931 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
6932 Event.remove(co, 'click', t.mouseClickFunc);
\r
6933 Event.remove(co, 'keydown', t._keyHandler);
\r
6935 t.isMenuVisible = 0;
\r
6943 if (e = DOM.get(t.id))
\r
6944 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
\r
6946 t.onHideMenu.dispatch(t);
\r
6949 add : function(o) {
\r
6954 if (t.isRendered && (co = DOM.get('menu_' + t.id)))
\r
6955 t._add(DOM.select('tbody', co)[0], o);
\r
6960 collapse : function(d) {
\r
6965 remove : function(o) {
\r
6969 return this.parent(o);
\r
6972 destroy : function() {
\r
6973 var t = this, co = DOM.get('menu_' + t.id);
\r
6975 Event.remove(co, 'mouseover', t.mouseOverFunc);
\r
6976 Event.remove(co, 'click', t.mouseClickFunc);
\r
6979 t.element.remove();
\r
6984 renderNode : function() {
\r
6985 var t = this, s = t.settings, n, tb, co, w;
\r
6987 w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
\r
6988 co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
\r
6989 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
\r
6992 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
\r
6994 // n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
\r
6995 n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
\r
6996 tb = DOM.add(n, 'tbody');
\r
6998 each(t.items, function(o) {
\r
7002 t.rendered = true;
\r
7007 // Internal functions
\r
7009 _keyHandler : function(e) {
\r
7010 var t = this, kc = e.keyCode;
\r
7012 function focus(d) {
\r
7013 var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
\r
7023 focus(-1); // Select first link
\r
7034 return this.hideMenu();
\r
7038 _add : function(tb, o) {
\r
7039 var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
\r
7041 if (s.separator) {
\r
7042 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
\r
7043 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
\r
7045 if (n = ro.previousSibling)
\r
7046 DOM.addClass(n, 'mceLast');
\r
7051 n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
\r
7052 n = it = DOM.add(n, 'td');
\r
7053 n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
\r
7055 DOM.addClass(it, s['class']);
\r
7056 // n = DOM.add(n, 'span', {'class' : 'item'});
\r
7058 ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
\r
7061 DOM.add(ic, 'img', {src : s.icon_src});
\r
7063 n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
\r
7065 if (o.settings.style)
\r
7066 DOM.setAttrib(n, 'style', o.settings.style);
\r
7068 if (tb.childNodes.length == 1)
\r
7069 DOM.addClass(ro, 'mceFirst');
\r
7071 if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
\r
7072 DOM.addClass(ro, 'mceFirst');
\r
7075 DOM.addClass(ro, cp + 'ItemSub');
\r
7077 if (n = ro.previousSibling)
\r
7078 DOM.removeClass(n, 'mceLast');
\r
7080 DOM.addClass(ro, 'mceLast');
\r
7083 })(tinymce);(function(tinymce) {
\r
7084 var DOM = tinymce.DOM;
\r
7086 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
\r
7087 Button : function(id, s) {
\r
7088 this.parent(id, s);
\r
7089 this.classPrefix = 'mceButton';
\r
7092 renderHTML : function() {
\r
7093 var cp = this.classPrefix, s = this.settings, h, l;
\r
7095 l = DOM.encode(s.label || '');
\r
7096 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
7099 h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
\r
7101 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
\r
7106 postRender : function() {
\r
7107 var t = this, s = t.settings;
\r
7109 tinymce.dom.Event.add(t.id, 'click', function(e) {
\r
7110 if (!t.isDisabled())
\r
7111 return s.onclick.call(s.scope, e);
\r
7116 (function(tinymce) {
\r
7117 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
7119 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
\r
7120 ListBox : function(id, s) {
\r
7127 t.onChange = new Dispatcher(t);
\r
7129 t.onPostRender = new Dispatcher(t);
\r
7131 t.onAdd = new Dispatcher(t);
\r
7133 t.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
7135 t.classPrefix = 'mceListBox';
\r
7138 select : function(va) {
\r
7139 var t = this, fv, f;
\r
7141 if (va == undefined)
\r
7142 return t.selectByIndex(-1);
\r
7144 // Is string or number make function selector
\r
7145 if (va && va.call)
\r
7153 // Do we need to do something?
\r
7154 if (va != t.selectedValue) {
\r
7156 each(t.items, function(o, i) {
\r
7159 t.selectByIndex(i);
\r
7165 t.selectByIndex(-1);
\r
7169 selectByIndex : function(idx) {
\r
7170 var t = this, e, o;
\r
7172 if (idx != t.selectedIndex) {
\r
7173 e = DOM.get(t.id + '_text');
\r
7177 t.selectedValue = o.value;
\r
7178 t.selectedIndex = idx;
\r
7179 DOM.setHTML(e, DOM.encode(o.title));
\r
7180 DOM.removeClass(e, 'mceTitle');
\r
7182 DOM.setHTML(e, DOM.encode(t.settings.title));
\r
7183 DOM.addClass(e, 'mceTitle');
\r
7184 t.selectedValue = t.selectedIndex = null;
\r
7191 add : function(n, v, o) {
\r
7195 o = tinymce.extend(o, {
\r
7201 t.onAdd.dispatch(t, o);
\r
7204 getLength : function() {
\r
7205 return this.items.length;
\r
7208 renderHTML : function() {
\r
7209 var h = '', t = this, s = t.settings, cp = t.classPrefix;
\r
7211 h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
\r
7212 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
7213 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
7214 h += '</tr></tbody></table>';
\r
7219 showMenu : function() {
\r
7220 var t = this, p1, p2, e = DOM.get(this.id), m;
\r
7222 if (t.isDisabled() || t.items.length == 0)
\r
7225 if (t.menu && t.menu.isMenuVisible)
\r
7226 return t.hideMenu();
\r
7228 if (!t.isMenuRendered) {
\r
7230 t.isMenuRendered = true;
\r
7233 p1 = DOM.getPos(this.settings.menu_container);
\r
7234 p2 = DOM.getPos(e);
\r
7237 m.settings.offset_x = p2.x;
\r
7238 m.settings.offset_y = p2.y;
\r
7239 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
\r
7243 m.items[t.oldID].setSelected(0);
\r
7245 each(t.items, function(o) {
\r
7246 if (o.value === t.selectedValue) {
\r
7247 m.items[o.id].setSelected(1);
\r
7252 m.showMenu(0, e.clientHeight);
\r
7254 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7255 DOM.addClass(t.id, t.classPrefix + 'Selected');
\r
7257 //DOM.get(t.id + '_text').focus();
\r
7260 hideMenu : function(e) {
\r
7263 if (t.menu && t.menu.isMenuVisible) {
\r
7264 // Prevent double toogles by canceling the mouse click event to the button
\r
7265 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
\r
7268 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
7269 DOM.removeClass(t.id, t.classPrefix + 'Selected');
\r
7270 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7271 t.menu.hideMenu();
\r
7276 renderMenu : function() {
\r
7279 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
7281 'class' : t.classPrefix + 'Menu mceNoIcons',
\r
7286 m.onHideMenu.add(t.hideMenu, t);
\r
7289 title : t.settings.title,
\r
7290 'class' : 'mceMenuItemTitle',
\r
7291 onclick : function() {
\r
7292 if (t.settings.onselect('') !== false)
\r
7293 t.select(''); // Must be runned after
\r
7297 each(t.items, function(o) {
\r
7298 // No value then treat it as a title
\r
7299 if (o.value === undefined) {
\r
7302 'class' : 'mceMenuItemTitle',
\r
7303 onclick : function() {
\r
7304 if (t.settings.onselect('') !== false)
\r
7305 t.select(''); // Must be runned after
\r
7309 o.id = DOM.uniqueId();
\r
7310 o.onclick = function() {
\r
7311 if (t.settings.onselect(o.value) !== false)
\r
7312 t.select(o.value); // Must be runned after
\r
7319 t.onRenderMenu.dispatch(t, m);
\r
7323 postRender : function() {
\r
7324 var t = this, cp = t.classPrefix;
\r
7326 Event.add(t.id, 'click', t.showMenu, t);
\r
7327 Event.add(t.id + '_text', 'focus', function(e) {
\r
7328 if (!t._focused) {
\r
7329 t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
\r
7330 var idx = -1, v, kc = e.keyCode;
\r
7332 // Find current index
\r
7333 each(t.items, function(v, i) {
\r
7334 if (t.selectedValue == v.value)
\r
7340 v = t.items[idx - 1];
\r
7341 else if (kc == 40)
\r
7342 v = t.items[idx + 1];
\r
7343 else if (kc == 13) {
\r
7344 // Fake select on enter
\r
7345 v = t.selectedValue;
\r
7346 t.selectedValue = null; // Needs to be null to fake change
\r
7347 t.settings.onselect(v);
\r
7348 return Event.cancel(e);
\r
7353 t.select(v.value);
\r
7360 Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
\r
7362 // Old IE doesn't have hover on all elements
\r
7363 if (tinymce.isIE6 || !DOM.boxModel) {
\r
7364 Event.add(t.id, 'mouseover', function() {
\r
7365 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
7366 DOM.addClass(t.id, cp + 'Hover');
\r
7369 Event.add(t.id, 'mouseout', function() {
\r
7370 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
7371 DOM.removeClass(t.id, cp + 'Hover');
\r
7375 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
7378 destroy : function() {
\r
7381 Event.clear(this.id + '_text');
\r
7382 Event.clear(this.id + '_open');
\r
7385 })(tinymce);(function(tinymce) {
\r
7386 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
7388 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
\r
7389 NativeListBox : function(id, s) {
\r
7390 this.parent(id, s);
\r
7391 this.classPrefix = 'mceNativeListBox';
\r
7394 setDisabled : function(s) {
\r
7395 DOM.get(this.id).disabled = s;
\r
7398 isDisabled : function() {
\r
7399 return DOM.get(this.id).disabled;
\r
7402 select : function(va) {
\r
7403 var t = this, fv, f;
\r
7405 if (va == undefined)
\r
7406 return t.selectByIndex(-1);
\r
7408 // Is string or number make function selector
\r
7409 if (va && va.call)
\r
7417 // Do we need to do something?
\r
7418 if (va != t.selectedValue) {
\r
7420 each(t.items, function(o, i) {
\r
7423 t.selectByIndex(i);
\r
7429 t.selectByIndex(-1);
\r
7433 selectByIndex : function(idx) {
\r
7434 DOM.get(this.id).selectedIndex = idx + 1;
\r
7435 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
\r
7438 add : function(n, v, a) {
\r
7444 if (t.isRendered())
\r
7445 DOM.add(DOM.get(this.id), 'option', a, n);
\r
7454 t.onAdd.dispatch(t, o);
\r
7457 getLength : function() {
\r
7458 return DOM.get(this.id).options.length - 1;
\r
7461 renderHTML : function() {
\r
7464 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
\r
7466 each(t.items, function(it) {
\r
7467 h += DOM.createHTML('option', {value : it.value}, it.title);
\r
7470 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
\r
7475 postRender : function() {
\r
7478 t.rendered = true;
\r
7480 function onChange(e) {
\r
7481 var v = t.items[e.target.selectedIndex - 1];
\r
7483 if (v && (v = v.value)) {
\r
7484 t.onChange.dispatch(t, v);
\r
7486 if (t.settings.onselect)
\r
7487 t.settings.onselect(v);
\r
7491 Event.add(t.id, 'change', onChange);
\r
7493 // Accessibility keyhandler
\r
7494 Event.add(t.id, 'keydown', function(e) {
\r
7497 Event.remove(t.id, 'change', ch);
\r
7499 bf = Event.add(t.id, 'blur', function() {
\r
7500 Event.add(t.id, 'change', onChange);
\r
7501 Event.remove(t.id, 'blur', bf);
\r
7504 if (e.keyCode == 13 || e.keyCode == 32) {
\r
7506 return Event.cancel(e);
\r
7510 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
7513 })(tinymce);(function(tinymce) {
\r
7514 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
7516 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
\r
7517 MenuButton : function(id, s) {
\r
7518 this.parent(id, s);
\r
7520 this.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
7522 s.menu_container = s.menu_container || DOM.doc.body;
\r
7525 showMenu : function() {
\r
7526 var t = this, p1, p2, e = DOM.get(t.id), m;
\r
7528 if (t.isDisabled())
\r
7531 if (!t.isMenuRendered) {
\r
7533 t.isMenuRendered = true;
\r
7536 if (t.isMenuVisible)
\r
7537 return t.hideMenu();
\r
7539 p1 = DOM.getPos(t.settings.menu_container);
\r
7540 p2 = DOM.getPos(e);
\r
7543 m.settings.offset_x = p2.x;
\r
7544 m.settings.offset_y = p2.y;
\r
7545 m.settings.vp_offset_x = p2.x;
\r
7546 m.settings.vp_offset_y = p2.y;
\r
7547 m.settings.keyboard_focus = t._focused;
\r
7548 m.showMenu(0, e.clientHeight);
\r
7550 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7551 t.setState('Selected', 1);
\r
7553 t.isMenuVisible = 1;
\r
7556 renderMenu : function() {
\r
7559 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
7561 'class' : this.classPrefix + 'Menu',
\r
7562 icons : t.settings.icons
\r
7565 m.onHideMenu.add(t.hideMenu, t);
\r
7567 t.onRenderMenu.dispatch(t, m);
\r
7571 hideMenu : function(e) {
\r
7574 // Prevent double toogles by canceling the mouse click event to the button
\r
7575 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
\r
7578 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
7579 t.setState('Selected', 0);
\r
7580 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7582 t.menu.hideMenu();
\r
7585 t.isMenuVisible = 0;
\r
7588 postRender : function() {
\r
7589 var t = this, s = t.settings;
\r
7591 Event.add(t.id, 'click', function() {
\r
7592 if (!t.isDisabled()) {
\r
7594 s.onclick(t.value);
\r
7602 (function(tinymce) {
\r
7603 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
7605 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
\r
7606 SplitButton : function(id, s) {
\r
7607 this.parent(id, s);
\r
7608 this.classPrefix = 'mceSplitButton';
\r
7611 renderHTML : function() {
\r
7612 var h, t = this, s = t.settings, h1;
\r
7614 h = '<tbody><tr>';
\r
7617 h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
\r
7619 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
\r
7621 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
7623 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
\r
7624 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
7626 h += '</tr></tbody>';
\r
7628 return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
\r
7631 postRender : function() {
\r
7632 var t = this, s = t.settings;
\r
7635 Event.add(t.id + '_action', 'click', function() {
\r
7636 if (!t.isDisabled())
\r
7637 s.onclick(t.value);
\r
7641 Event.add(t.id + '_open', 'click', t.showMenu, t);
\r
7642 Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
\r
7643 Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
\r
7645 // Old IE doesn't have hover on all elements
\r
7646 if (tinymce.isIE6 || !DOM.boxModel) {
\r
7647 Event.add(t.id, 'mouseover', function() {
\r
7648 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
7649 DOM.addClass(t.id, 'mceSplitButtonHover');
\r
7652 Event.add(t.id, 'mouseout', function() {
\r
7653 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
7654 DOM.removeClass(t.id, 'mceSplitButtonHover');
\r
7659 destroy : function() {
\r
7662 Event.clear(this.id + '_action');
\r
7663 Event.clear(this.id + '_open');
\r
7667 (function(tinymce) {
\r
7668 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
\r
7670 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
\r
7671 ColorSplitButton : function(id, s) {
\r
7676 t.settings = s = tinymce.extend({
\r
7677 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
7679 default_color : '#888888'
\r
7682 t.onShowMenu = new tinymce.util.Dispatcher(t);
\r
7684 t.onHideMenu = new tinymce.util.Dispatcher(t);
\r
7686 t.value = s.default_color;
\r
7689 showMenu : function() {
\r
7690 var t = this, r, p, e, p2;
\r
7692 if (t.isDisabled())
\r
7695 if (!t.isMenuRendered) {
\r
7697 t.isMenuRendered = true;
\r
7700 if (t.isMenuVisible)
\r
7701 return t.hideMenu();
\r
7703 e = DOM.get(t.id);
\r
7704 DOM.show(t.id + '_menu');
\r
7705 DOM.addClass(e, 'mceSplitButtonSelected');
\r
7706 p2 = DOM.getPos(e);
\r
7707 DOM.setStyles(t.id + '_menu', {
\r
7709 top : p2.y + e.clientHeight,
\r
7714 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7715 t.onShowMenu.dispatch(t);
\r
7718 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
\r
7719 if (e.keyCode == 27)
\r
7723 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
\r
7726 t.isMenuVisible = 1;
\r
7729 hideMenu : function(e) {
\r
7732 // Prevent double toogles by canceling the mouse click event to the button
\r
7733 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
\r
7736 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
\r
7737 DOM.removeClass(t.id, 'mceSplitButtonSelected');
\r
7738 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7739 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
\r
7740 DOM.hide(t.id + '_menu');
\r
7743 t.onHideMenu.dispatch(t);
\r
7745 t.isMenuVisible = 0;
\r
7748 renderMenu : function() {
\r
7749 var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
\r
7751 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
7752 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
\r
7753 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
\r
7755 n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
\r
7756 tb = DOM.add(n, 'tbody');
\r
7758 // Generate color grid
\r
7760 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
\r
7761 c = c.replace(/^#/, '');
\r
7764 tr = DOM.add(tb, 'tr');
\r
7765 i = s.grid_width - 1;
\r
7768 n = DOM.add(tr, 'td');
\r
7770 n = DOM.add(n, 'a', {
\r
7771 href : 'javascript:;',
\r
7773 backgroundColor : '#' + c
\r
7775 _mce_color : '#' + c
\r
7779 if (s.more_colors_func) {
\r
7780 n = DOM.add(tb, 'tr');
\r
7781 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
\r
7782 n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
\r
7784 Event.add(n, 'click', function(e) {
\r
7785 s.more_colors_func.call(s.more_colors_scope || this);
\r
7786 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
7790 DOM.addClass(m, 'mceColorSplitMenu');
\r
7792 Event.add(t.id + '_menu', 'click', function(e) {
\r
7797 if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
\r
7800 return Event.cancel(e); // Prevent IE auto save warning
\r
7806 setColor : function(c) {
\r
7809 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
\r
7813 t.settings.onselect(c);
\r
7816 postRender : function() {
\r
7817 var t = this, id = t.id;
\r
7820 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
\r
7821 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
\r
7824 destroy : function() {
\r
7827 Event.clear(this.id + '_menu');
\r
7828 Event.clear(this.id + '_more');
\r
7829 DOM.remove(this.id + '_menu');
\r
7833 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
\r
7834 renderHTML : function() {
\r
7835 var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
\r
7838 for (i=0; i<cl.length; i++) {
\r
7839 // Get current control, prev control, next control and if the control is a list box or not
\r
7844 // Add toolbar start
\r
7846 c = 'mceToolbarStart';
\r
7849 c += ' mceToolbarStartButton';
\r
7850 else if (co.SplitButton)
\r
7851 c += ' mceToolbarStartSplitButton';
\r
7852 else if (co.ListBox)
\r
7853 c += ' mceToolbarStartListBox';
\r
7855 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
7858 // Add toolbar end before list box and after the previous button
\r
7859 // This is to fix the o2k7 editor skins
\r
7860 if (pr && co.ListBox) {
\r
7861 if (pr.Button || pr.SplitButton)
\r
7862 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
7865 // Render control HTML
\r
7867 // IE 8 quick fix, needed to propertly generate a hit area for anchors
\r
7869 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
\r
7871 h += '<td>' + co.renderHTML() + '</td>';
\r
7873 // Add toolbar start after list box and before the next button
\r
7874 // This is to fix the o2k7 editor skins
\r
7875 if (nx && co.ListBox) {
\r
7876 if (nx.Button || nx.SplitButton)
\r
7877 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
7881 c = 'mceToolbarEnd';
\r
7884 c += ' mceToolbarEndButton';
\r
7885 else if (co.SplitButton)
\r
7886 c += ' mceToolbarEndSplitButton';
\r
7887 else if (co.ListBox)
\r
7888 c += ' mceToolbarEndListBox';
\r
7890 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
7892 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
7895 (function(tinymce) {
\r
7896 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
\r
7898 tinymce.create('tinymce.AddOnManager', {
\r
7903 onAdd : new Dispatcher(this),
\r
7905 get : function(n) {
\r
7906 return this.lookup[n];
\r
7909 requireLangPack : function(n) {
\r
7910 var s = tinymce.settings;
\r
7912 if (s && s.language)
\r
7913 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
\r
7916 add : function(id, o) {
\r
7917 this.items.push(o);
\r
7918 this.lookup[id] = o;
\r
7919 this.onAdd.dispatch(this, id, o);
\r
7924 load : function(n, u, cb, s) {
\r
7930 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
\r
7931 u = tinymce.baseURL + '/' + u;
\r
7933 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
\r
7934 tinymce.ScriptLoader.add(u, cb, s);
\r
7938 // Create plugin and theme managers
\r
7939 tinymce.PluginManager = new tinymce.AddOnManager();
\r
7940 tinymce.ThemeManager = new tinymce.AddOnManager();
\r
7943 (function(tinymce) {
\r
7945 var each = tinymce.each, extend = tinymce.extend,
\r
7946 DOM = tinymce.DOM, Event = tinymce.dom.Event,
\r
7947 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
7948 explode = tinymce.explode,
\r
7949 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
\r
7951 // Setup some URLs where the editor API is located and where the document is
\r
7952 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
\r
7953 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
\r
7954 tinymce.documentBaseURL += '/';
\r
7956 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
\r
7958 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
\r
7960 // Add before unload listener
\r
7961 // This was required since IE was leaking memory if you added and removed beforeunload listeners
\r
7962 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
\r
7963 tinymce.onBeforeUnload = new Dispatcher(tinymce);
\r
7965 // Must be on window or IE will leak if the editor is placed in frame or iframe
\r
7966 Event.add(window, 'beforeunload', function(e) {
\r
7967 tinymce.onBeforeUnload.dispatch(tinymce, e);
\r
7970 tinymce.onAddEditor = new Dispatcher(tinymce);
\r
7972 tinymce.onRemoveEditor = new Dispatcher(tinymce);
\r
7974 tinymce.EditorManager = extend(tinymce, {
\r
7979 activeEditor : null,
\r
7981 init : function(s) {
\r
7982 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
\r
7984 function execCallback(se, n, s) {
\r
7990 if (tinymce.is(f, 'string')) {
\r
7991 s = f.replace(/\.\w+$/, '');
\r
7992 s = s ? tinymce.resolve(s) : 0;
\r
7993 f = tinymce.resolve(f);
\r
7996 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
\r
8007 Event.add(document, 'init', function() {
\r
8010 execCallback(s, 'onpageload');
\r
8014 l = s.elements || '';
\r
8016 if(l.length > 0) {
\r
8017 each(explode(l), function(v) {
\r
8019 ed = new tinymce.Editor(v, s);
\r
8023 each(document.forms, function(f) {
\r
8024 each(f.elements, function(e) {
\r
8025 if (e.name === v) {
\r
8026 v = 'mce_editor_' + instanceCounter++;
\r
8027 DOM.setAttrib(e, 'id', v);
\r
8029 ed = new tinymce.Editor(v, s);
\r
8041 case "specific_textareas":
\r
8042 function hasClass(n, c) {
\r
8043 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
\r
8046 each(DOM.select('textarea'), function(v) {
\r
8047 if (s.editor_deselector && hasClass(v, s.editor_deselector))
\r
8050 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
\r
8051 // Can we use the name
\r
8052 e = DOM.get(v.name);
\r
8056 // Generate unique name if missing or already exists
\r
8057 if (!v.id || t.get(v.id))
\r
8058 v.id = DOM.uniqueId();
\r
8060 ed = new tinymce.Editor(v.id, s);
\r
8068 // Call onInit when all editors are initialized
\r
8072 each(el, function(ed) {
\r
8075 if (!ed.initialized) {
\r
8077 ed.onInit.add(function() {
\r
8082 execCallback(s, 'oninit');
\r
8089 execCallback(s, 'oninit');
\r
8095 get : function(id) {
\r
8096 if (id === undefined)
\r
8097 return this.editors;
\r
8099 return this.editors[id];
\r
8102 getInstanceById : function(id) {
\r
8103 return this.get(id);
\r
8106 add : function(editor) {
\r
8107 var self = this, editors = self.editors;
\r
8109 // Add named and index editor instance
\r
8110 editors[editor.id] = editor;
\r
8111 editors.push(editor);
\r
8113 self._setActive(editor);
\r
8114 self.onAddEditor.dispatch(self, editor);
\r
8117 // Patch the tinymce.Editor instance with jQuery adapter logic
\r
8118 if (tinymce.adapter)
\r
8119 tinymce.adapter.patchEditor(editor);
\r
8125 remove : function(editor) {
\r
8126 var t = this, i, editors = t.editors;
\r
8128 // Not in the collection
\r
8129 if (!editors[editor.id])
\r
8132 delete editors[editor.id];
\r
8134 for (i = 0; i < editors.length; i++) {
\r
8135 if (editors[i] == editor) {
\r
8136 editors.splice(i, 1);
\r
8141 // Select another editor since the active one was removed
\r
8142 if (t.activeEditor == editor)
\r
8143 t._setActive(editors[0]);
\r
8146 t.onRemoveEditor.dispatch(t, editor);
\r
8151 execCommand : function(c, u, v) {
\r
8152 var t = this, ed = t.get(v), w;
\r
8154 // Manager commands
\r
8160 case "mceAddEditor":
\r
8161 case "mceAddControl":
\r
8163 new tinymce.Editor(v, t.settings).render();
\r
8167 case "mceAddFrameControl":
\r
8170 // Add tinyMCE global instance and tinymce namespace to specified window
\r
8171 w.tinyMCE = tinyMCE;
\r
8172 w.tinymce = tinymce;
\r
8174 tinymce.DOM.doc = w.document;
\r
8175 tinymce.DOM.win = w;
\r
8177 ed = new tinymce.Editor(v.element_id, v);
\r
8180 // Fix IE memory leaks
\r
8181 if (tinymce.isIE) {
\r
8184 w.detachEvent('onunload', clr);
\r
8185 w = w.tinyMCE = w.tinymce = null; // IE leak
\r
8188 w.attachEvent('onunload', clr);
\r
8191 v.page_window = null;
\r
8195 case "mceRemoveEditor":
\r
8196 case "mceRemoveControl":
\r
8202 case 'mceToggleEditor':
\r
8204 t.execCommand('mceAddControl', 0, v);
\r
8208 if (ed.isHidden())
\r
8216 // Run command on active editor
\r
8217 if (t.activeEditor)
\r
8218 return t.activeEditor.execCommand(c, u, v);
\r
8223 execInstanceCommand : function(id, c, u, v) {
\r
8224 var ed = this.get(id);
\r
8227 return ed.execCommand(c, u, v);
\r
8232 triggerSave : function() {
\r
8233 each(this.editors, function(e) {
\r
8238 addI18n : function(p, o) {
\r
8239 var lo, i18n = this.i18n;
\r
8241 if (!tinymce.is(p, 'string')) {
\r
8242 each(p, function(o, lc) {
\r
8243 each(o, function(o, g) {
\r
8244 each(o, function(o, k) {
\r
8245 if (g === 'common')
\r
8246 i18n[lc + '.' + k] = o;
\r
8248 i18n[lc + '.' + g + '.' + k] = o;
\r
8253 each(o, function(o, k) {
\r
8254 i18n[p + '.' + k] = o;
\r
8259 // Private methods
\r
8261 _setActive : function(editor) {
\r
8262 this.selectedInstance = this.activeEditor = editor;
\r
8267 (function(tinymce) {
\r
8268 // Shorten these names
\r
8269 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
\r
8270 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
\r
8271 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
\r
8272 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
8273 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
\r
8275 tinymce.create('tinymce.Editor', {
\r
8276 Editor : function(id, s) {
\r
8279 t.id = t.editorId = id;
\r
8281 t.execCommands = {};
\r
8282 t.queryStateCommands = {};
\r
8283 t.queryValueCommands = {};
\r
8285 t.isNotDirty = false;
\r
8289 // Add events to the editor
\r
8293 'onBeforeRenderUI',
\r
8333 'onBeforeSetContent',
\r
8335 'onBeforeGetContent',
\r
8349 'onBeforeExecCommand',
\r
8359 'onSetProgressState'
\r
8361 t[e] = new Dispatcher(t);
\r
8364 t.settings = s = extend({
\r
8367 docs_language : 'en',
\r
8374 document_base_url : tinymce.documentBaseURL,
\r
8375 add_form_submit_trigger : 1,
\r
8377 add_unload_trigger : 1,
\r
8379 relative_urls : 1,
\r
8380 remove_script_host : 1,
\r
8381 table_inline_editing : 0,
\r
8382 object_resizing : 1,
\r
8384 accessibility_focus : 1,
\r
8385 custom_shortcuts : 1,
\r
8386 custom_undo_redo_keyboard_shortcuts : 1,
\r
8387 custom_undo_redo_restore_selection : 1,
\r
8388 custom_undo_redo : 1,
\r
8389 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
8390 visual_table_class : 'mceItemTable',
\r
8392 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
\r
8393 apply_source_formatting : 1,
\r
8394 directionality : 'ltr',
\r
8395 forced_root_block : 'p',
\r
8396 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
8398 padd_empty_editor : 1,
\r
8401 force_p_newlines : 1,
\r
8402 indentation : '30px',
\r
8404 fix_table_elements : 1,
\r
8405 inline_styles : 1,
\r
8406 convert_fonts_to_spans : true
\r
8409 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
\r
8410 base_uri : tinyMCE.baseURI
\r
8413 t.baseURI = tinymce.baseURI;
\r
8416 t.execCallback('setup', t);
\r
8419 render : function(nst) {
\r
8420 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
\r
8422 // Page is not loaded yet, wait for it
\r
8423 if (!Event.domLoaded) {
\r
8424 Event.add(document, 'init', function() {
\r
8430 tinyMCE.settings = s;
\r
8432 // Element not found, then skip initialization
\r
8433 if (!t.getElement())
\r
8436 // Add hidden input for non input elements inside form elements
\r
8437 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
\r
8438 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
\r
8440 if (tinymce.WindowManager)
\r
8441 t.windowManager = new tinymce.WindowManager(t);
\r
8443 if (s.encoding == 'xml') {
\r
8444 t.onGetContent.add(function(ed, o) {
\r
8446 o.content = DOM.encode(o.content);
\r
8450 if (s.add_form_submit_trigger) {
\r
8451 t.onSubmit.addToTop(function() {
\r
8452 if (t.initialized) {
\r
8459 if (s.add_unload_trigger) {
\r
8460 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
\r
8461 if (t.initialized && !t.destroyed && !t.isHidden())
\r
8462 t.save({format : 'raw', no_events : true});
\r
8466 tinymce.addUnload(t.destroy, t);
\r
8468 if (s.submit_patch) {
\r
8469 t.onBeforeRenderUI.add(function() {
\r
8470 var n = t.getElement().form;
\r
8475 // Already patched
\r
8476 if (n._mceOldSubmit)
\r
8479 // Check page uses id="submit" or name="submit" for it's submit button
\r
8480 if (!n.submit.nodeType && !n.submit.length) {
\r
8481 t.formElement = n;
\r
8482 n._mceOldSubmit = n.submit;
\r
8483 n.submit = function() {
\r
8484 // Save all instances
\r
8485 tinymce.triggerSave();
\r
8488 return t.formElement._mceOldSubmit(t.formElement);
\r
8497 function loadScripts() {
\r
8499 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
\r
8501 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
\r
8502 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
\r
8504 each(explode(s.plugins), function(p) {
\r
8505 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
\r
8506 // Skip safari plugin, since it is removed as of 3.3b1
\r
8507 if (p == 'safari')
\r
8510 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
\r
8514 // Init when que is loaded
\r
8515 sl.loadQueue(function() {
\r
8524 init : function() {
\r
8525 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
\r
8530 s.theme = s.theme.replace(/-/, '');
\r
8531 o = ThemeManager.get(s.theme);
\r
8532 t.theme = new o();
\r
8534 if (t.theme.init && s.init_theme)
\r
8535 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
\r
8538 // Create all plugins
\r
8539 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
\r
8540 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
\r
8545 t.plugins[p] = po;
\r
8552 // Setup popup CSS path(s)
\r
8553 if (s.popup_css !== false) {
\r
8555 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
\r
8557 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
\r
8560 if (s.popup_css_add)
\r
8561 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
\r
8563 t.controlManager = new tinymce.ControlManager(t);
\r
8565 if (s.custom_undo_redo) {
\r
8566 // Add initial undo level
\r
8567 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
\r
8568 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
\r
8569 if (!t.undoManager.hasUndo())
\r
8570 t.undoManager.add();
\r
8574 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
\r
8575 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
\r
8576 t.undoManager.add();
\r
8580 t.onExecCommand.add(function(ed, c) {
\r
8581 // Don't refresh the select lists until caret move
\r
8582 if (!/^(FontName|FontSize)$/.test(c))
\r
8586 // Remove ghost selections on images and tables in Gecko
\r
8588 function repaint(a, o) {
\r
8589 if (!o || !o.initial)
\r
8590 t.execCommand('mceRepaint');
\r
8593 t.onUndo.add(repaint);
\r
8594 t.onRedo.add(repaint);
\r
8595 t.onSetContent.add(repaint);
\r
8598 // Enables users to override the control factory
\r
8599 t.onBeforeRenderUI.dispatch(t, t.controlManager);
\r
8602 if (s.render_ui) {
\r
8603 w = s.width || e.style.width || e.offsetWidth;
\r
8604 h = s.height || e.style.height || e.offsetHeight;
\r
8605 t.orgDisplay = e.style.display;
\r
8606 re = /^[0-9\.]+(|px)$/i;
\r
8608 if (re.test('' + w))
\r
8609 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
\r
8611 if (re.test('' + h))
\r
8612 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
\r
8615 o = t.theme.renderUI({
\r
8619 deltaWidth : s.delta_width,
\r
8620 deltaHeight : s.delta_height
\r
8623 t.editorContainer = o.editorContainer;
\r
8627 // User specified a document.domain value
\r
8628 if (document.domain && location.hostname != document.domain)
\r
8629 tinymce.relaxedDomain = document.domain;
\r
8632 DOM.setStyles(o.sizeContainer || o.editorContainer, {
\r
8637 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
\r
8641 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
\r
8643 // We only need to override paths if we have to
\r
8644 // IE has a bug where it remove site absolute urls to relative ones if this is specified
\r
8645 if (s.document_base_url != tinymce.documentBaseURL)
\r
8646 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
\r
8648 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
\r
8650 if (tinymce.relaxedDomain)
\r
8651 t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
\r
8653 bi = s.body_id || 'tinymce';
\r
8654 if (bi.indexOf('=') != -1) {
\r
8655 bi = t.getParam('body_id', '', 'hash');
\r
8656 bi = bi[t.id] || bi;
\r
8659 bc = s.body_class || '';
\r
8660 if (bc.indexOf('=') != -1) {
\r
8661 bc = t.getParam('body_class', '', 'hash');
\r
8662 bc = bc[t.id] || '';
\r
8665 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
\r
8667 // Domain relaxing enabled, then set document domain
\r
8668 if (tinymce.relaxedDomain) {
\r
8669 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
\r
8670 if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
\r
8671 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
8672 else if (tinymce.isOpera)
\r
8673 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
\r
8677 n = DOM.add(o.iframeContainer, 'iframe', {
\r
8678 id : t.id + "_ifr",
\r
8679 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
\r
8680 frameBorder : '0',
\r
8687 t.contentAreaContainer = o.iframeContainer;
\r
8688 DOM.get(o.editorContainer).style.display = t.orgDisplay;
\r
8689 DOM.get(t.id).style.display = 'none';
\r
8691 if (!isIE || !tinymce.relaxedDomain)
\r
8694 e = n = o = null; // Cleanup
\r
8697 setupIframe : function() {
\r
8698 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
\r
8700 // Setup iframe body
\r
8701 if (!isIE || !tinymce.relaxedDomain) {
\r
8703 d.write(t.iframeHTML);
\r
8707 // Design mode needs to be added here Ctrl+A will fail otherwise
\r
8711 d.designMode = 'On';
\r
8713 // Will fail on Gecko if the editor is placed in an hidden container element
\r
8714 // The design mode will be set ones the editor is focused
\r
8718 // IE needs to use contentEditable or it will display non secure items for HTTPS
\r
8720 // It will not steal focus if we hide it while setting contentEditable
\r
8725 b.contentEditable = true;
\r
8730 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
\r
8731 keep_values : true,
\r
8732 url_converter : t.convertURL,
\r
8733 url_converter_scope : t,
\r
8734 hex_colors : s.force_hex_style_colors,
\r
8735 class_filter : s.class_filter,
\r
8736 update_styles : 1,
\r
8737 fix_ie_paragraphs : 1,
\r
8738 valid_styles : s.valid_styles
\r
8741 t.schema = new tinymce.dom.Schema();
\r
8743 t.serializer = new tinymce.dom.Serializer(extend(s, {
\r
8744 valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
\r
8749 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
\r
8751 t.formatter = new tinymce.Formatter(this);
\r
8753 // Register default formats
\r
8754 t.formatter.register({
\r
8756 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
\r
8757 {selector : 'img,table', styles : {'float' : 'left'}}
\r
8761 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
\r
8762 {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
\r
8763 {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
\r
8767 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
\r
8768 {selector : 'img,table', styles : {'float' : 'right'}}
\r
8772 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
\r
8776 {inline : 'strong'},
\r
8777 {inline : 'span', styles : {fontWeight : 'bold'}},
\r
8783 {inline : 'span', styles : {fontStyle : 'italic'}},
\r
8788 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
\r
8793 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
\r
8797 forecolor : {inline : 'span', styles : {color : '%value'}},
\r
8798 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
\r
8799 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
\r
8800 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
\r
8801 blockquote : {block : 'blockquote', wrapper : 1},
\r
8804 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
\r
8805 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
\r
8806 {selector : '*', attributes : ['style', 'class'], expand : false, deep : true}
\r
8810 // Register default block formats
\r
8811 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
\r
8812 t.formatter.register(name, {block : name});
\r
8815 // Register user defined formats
\r
8816 t.formatter.register(t.settings.formats);
\r
8818 t.undoManager = new tinymce.UndoManager(t);
\r
8821 t.undoManager.onAdd.add(function(um, l) {
\r
8823 return t.onChange.dispatch(t, l, um);
\r
8826 t.undoManager.onUndo.add(function(um, l) {
\r
8827 return t.onUndo.dispatch(t, l, um);
\r
8830 t.undoManager.onRedo.add(function(um, l) {
\r
8831 return t.onRedo.dispatch(t, l, um);
\r
8834 t.forceBlocks = new tinymce.ForceBlocks(t, {
\r
8835 forced_root_block : s.forced_root_block
\r
8838 t.editorCommands = new tinymce.EditorCommands(t);
\r
8841 t.serializer.onPreProcess.add(function(se, o) {
\r
8842 return t.onPreProcess.dispatch(t, o, se);
\r
8845 t.serializer.onPostProcess.add(function(se, o) {
\r
8846 return t.onPostProcess.dispatch(t, o, se);
\r
8849 t.onPreInit.dispatch(t);
\r
8851 if (!s.gecko_spellcheck)
\r
8852 t.getBody().spellcheck = 0;
\r
8857 t.controlManager.onPostRender.dispatch(t, t.controlManager);
\r
8858 t.onPostRender.dispatch(t);
\r
8860 if (s.directionality)
\r
8861 t.getBody().dir = s.directionality;
\r
8864 t.getBody().style.whiteSpace = "nowrap";
\r
8866 if (s.custom_elements) {
\r
8867 function handleCustom(ed, o) {
\r
8868 each(explode(s.custom_elements), function(v) {
\r
8871 if (v.indexOf('~') === 0) {
\r
8872 v = v.substring(1);
\r
8877 o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
\r
8878 o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
\r
8882 t.onBeforeSetContent.add(handleCustom);
\r
8883 t.onPostProcess.add(function(ed, o) {
\r
8885 handleCustom(ed, o);
\r
8889 if (s.handle_node_change_callback) {
\r
8890 t.onNodeChange.add(function(ed, cm, n) {
\r
8891 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
\r
8895 if (s.save_callback) {
\r
8896 t.onSaveContent.add(function(ed, o) {
\r
8897 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
8904 if (s.onchange_callback) {
\r
8905 t.onChange.add(function(ed, l) {
\r
8906 t.execCallback('onchange_callback', t, l);
\r
8910 if (s.convert_newlines_to_brs) {
\r
8911 t.onBeforeSetContent.add(function(ed, o) {
\r
8913 o.content = o.content.replace(/\r?\n/g, '<br />');
\r
8917 if (s.fix_nesting && isIE) {
\r
8918 t.onBeforeSetContent.add(function(ed, o) {
\r
8919 o.content = t._fixNesting(o.content);
\r
8923 if (s.preformatted) {
\r
8924 t.onPostProcess.add(function(ed, o) {
\r
8925 o.content = o.content.replace(/^\s*<pre.*?>/, '');
\r
8926 o.content = o.content.replace(/<\/pre>\s*$/, '');
\r
8929 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
\r
8933 if (s.verify_css_classes) {
\r
8934 t.serializer.attribValueFilter = function(n, v) {
\r
8937 if (n == 'class') {
\r
8938 // Build regexp for classes
\r
8939 if (!t.classesRE) {
\r
8940 cl = t.dom.getClasses();
\r
8942 if (cl.length > 0) {
\r
8945 each (cl, function(o) {
\r
8946 s += (s ? '|' : '') + o['class'];
\r
8949 t.classesRE = new RegExp('(' + s + ')', 'gi');
\r
8953 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
\r
8960 if (s.cleanup_callback) {
\r
8961 t.onBeforeSetContent.add(function(ed, o) {
\r
8962 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
8965 t.onPreProcess.add(function(ed, o) {
\r
8967 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
\r
8970 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
\r
8973 t.onPostProcess.add(function(ed, o) {
\r
8975 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
8978 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
\r
8982 if (s.save_callback) {
\r
8983 t.onGetContent.add(function(ed, o) {
\r
8985 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
8989 if (s.handle_event_callback) {
\r
8990 t.onEvent.add(function(ed, e, o) {
\r
8991 if (t.execCallback('handle_event_callback', e, ed, o) === false)
\r
8996 // Add visual aids when new contents is added
\r
8997 t.onSetContent.add(function() {
\r
8998 t.addVisual(t.getBody());
\r
9001 // Remove empty contents
\r
9002 if (s.padd_empty_editor) {
\r
9003 t.onPostProcess.add(function(ed, o) {
\r
9004 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
\r
9009 // Fix gecko link bug, when a link is placed at the end of block elements there is
\r
9010 // no way to move the caret behind the link. This fix adds a bogus br element after the link
\r
9011 function fixLinks(ed, o) {
\r
9012 each(ed.dom.select('a'), function(n) {
\r
9013 var pn = n.parentNode;
\r
9015 if (ed.dom.isBlock(pn) && pn.lastChild === n)
\r
9016 ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
\r
9020 t.onExecCommand.add(function(ed, cmd) {
\r
9021 if (cmd === 'CreateLink')
\r
9025 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
\r
9027 if (!s.readonly) {
\r
9029 // Design mode must be set here once again to fix a bug where
\r
9030 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
\r
9031 d.designMode = 'Off';
\r
9032 d.designMode = 'On';
\r
9034 // Will fail on Gecko if the editor is placed in an hidden container element
\r
9035 // The design mode will be set ones the editor is focused
\r
9040 // A small timeout was needed since firefox will remove. Bug: #1838304
\r
9041 setTimeout(function () {
\r
9045 t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
\r
9046 t.startContent = t.getContent({format : 'raw'});
\r
9047 t.initialized = true;
\r
9049 t.onInit.dispatch(t);
\r
9050 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
\r
9051 t.execCallback('init_instance_callback', t);
\r
9053 t.nodeChanged({initial : 1});
\r
9055 // Load specified content CSS last
\r
9056 if (s.content_css) {
\r
9057 tinymce.each(explode(s.content_css), function(u) {
\r
9058 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
\r
9062 // Handle auto focus
\r
9063 if (s.auto_focus) {
\r
9064 setTimeout(function () {
\r
9065 var ed = tinymce.get(s.auto_focus);
\r
9067 ed.selection.select(ed.getBody(), 1);
\r
9068 ed.selection.collapse(1);
\r
9069 ed.getWin().focus();
\r
9078 focus : function(sf) {
\r
9079 var oed, t = this, ce = t.settings.content_editable;
\r
9082 // Is not content editable or the selection is outside the area in IE
\r
9083 // the IE statement is needed to avoid bluring if element selections inside layers since
\r
9084 // the layer is like it's own document in IE
\r
9085 if (!ce && (!isIE || t.selection.getNode().ownerDocument != t.getDoc()))
\r
9086 t.getWin().focus();
\r
9090 if (tinymce.activeEditor != t) {
\r
9091 if ((oed = tinymce.activeEditor) != null)
\r
9092 oed.onDeactivate.dispatch(oed, t);
\r
9094 t.onActivate.dispatch(t, oed);
\r
9097 tinymce._setActive(t);
\r
9100 execCallback : function(n) {
\r
9101 var t = this, f = t.settings[n], s;
\r
9106 // Look through lookup
\r
9107 if (t.callbackLookup && (s = t.callbackLookup[n])) {
\r
9112 if (is(f, 'string')) {
\r
9113 s = f.replace(/\.\w+$/, '');
\r
9114 s = s ? tinymce.resolve(s) : 0;
\r
9115 f = tinymce.resolve(f);
\r
9116 t.callbackLookup = t.callbackLookup || {};
\r
9117 t.callbackLookup[n] = {func : f, scope : s};
\r
9120 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
\r
9123 translate : function(s) {
\r
9124 var c = this.settings.language || 'en', i18n = tinymce.i18n;
\r
9129 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
\r
9130 return i18n[c + '.' + b] || '{#' + b + '}';
\r
9134 getLang : function(n, dv) {
\r
9135 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
\r
9138 getParam : function(n, dv, ty) {
\r
9139 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
\r
9141 if (ty === 'hash') {
\r
9144 if (is(v, 'string')) {
\r
9145 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
\r
9149 o[tr(v[0])] = tr(v[1]);
\r
9151 o[tr(v[0])] = tr(v);
\r
9162 nodeChanged : function(o) {
\r
9163 var t = this, s = t.selection, n = s.getNode() || t.getBody();
\r
9165 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
\r
9166 if (t.initialized) {
\r
9168 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
\r
9170 // Get parents and add them to object
\r
9172 t.dom.getParent(n, function(node) {
\r
9173 if (node.nodeName == 'BODY')
\r
9176 o.parents.push(node);
\r
9179 t.onNodeChange.dispatch(
\r
9181 o ? o.controlManager || t.controlManager : t.controlManager,
\r
9189 addButton : function(n, s) {
\r
9192 t.buttons = t.buttons || {};
\r
9196 addCommand : function(n, f, s) {
\r
9197 this.execCommands[n] = {func : f, scope : s || this};
\r
9200 addQueryStateHandler : function(n, f, s) {
\r
9201 this.queryStateCommands[n] = {func : f, scope : s || this};
\r
9204 addQueryValueHandler : function(n, f, s) {
\r
9205 this.queryValueCommands[n] = {func : f, scope : s || this};
\r
9208 addShortcut : function(pa, desc, cmd_func, sc) {
\r
9211 if (!t.settings.custom_shortcuts)
\r
9214 t.shortcuts = t.shortcuts || {};
\r
9216 if (is(cmd_func, 'string')) {
\r
9219 cmd_func = function() {
\r
9220 t.execCommand(c, false, null);
\r
9224 if (is(cmd_func, 'object')) {
\r
9227 cmd_func = function() {
\r
9228 t.execCommand(c[0], c[1], c[2]);
\r
9232 each(explode(pa), function(pa) {
\r
9235 scope : sc || this,
\r
9242 each(explode(pa, '+'), function(v) {
\r
9251 o.charCode = v.charCodeAt(0);
\r
9252 o.keyCode = v.toUpperCase().charCodeAt(0);
\r
9256 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
\r
9262 execCommand : function(cmd, ui, val, a) {
\r
9263 var t = this, s = 0, o, st;
\r
9265 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
\r
9269 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
\r
9273 // Command callback
\r
9274 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
\r
9275 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9279 // Registred commands
\r
9280 if (o = t.execCommands[cmd]) {
\r
9281 st = o.func.call(o.scope, ui, val);
\r
9283 // Fall through on true
\r
9284 if (st !== true) {
\r
9285 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9290 // Plugin commands
\r
9291 each(t.plugins, function(p) {
\r
9292 if (p.execCommand && p.execCommand(cmd, ui, val)) {
\r
9293 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9303 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
\r
9304 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9308 // Execute global commands
\r
9309 if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
\r
9310 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9314 // Editor commands
\r
9315 if (t.editorCommands.execCommand(cmd, ui, val)) {
\r
9316 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9320 // Browser commands
\r
9321 t.getDoc().execCommand(cmd, ui, val);
\r
9322 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9325 queryCommandState : function(cmd) {
\r
9326 var t = this, o, s;
\r
9328 // Is hidden then return undefined
\r
9329 if (t._isHidden())
\r
9332 // Registred commands
\r
9333 if (o = t.queryStateCommands[cmd]) {
\r
9334 s = o.func.call(o.scope);
\r
9336 // Fall though on true
\r
9341 // Registred commands
\r
9342 o = t.editorCommands.queryCommandState(cmd);
\r
9346 // Browser commands
\r
9348 return this.getDoc().queryCommandState(cmd);
\r
9350 // Fails sometimes see bug: 1896577
\r
9354 queryCommandValue : function(c) {
\r
9355 var t = this, o, s;
\r
9357 // Is hidden then return undefined
\r
9358 if (t._isHidden())
\r
9361 // Registred commands
\r
9362 if (o = t.queryValueCommands[c]) {
\r
9363 s = o.func.call(o.scope);
\r
9365 // Fall though on true
\r
9370 // Registred commands
\r
9371 o = t.editorCommands.queryCommandValue(c);
\r
9375 // Browser commands
\r
9377 return this.getDoc().queryCommandValue(c);
\r
9379 // Fails sometimes see bug: 1896577
\r
9383 show : function() {
\r
9386 DOM.show(t.getContainer());
\r
9391 hide : function() {
\r
9392 var t = this, d = t.getDoc();
\r
9394 // Fixed bug where IE has a blinking cursor left from the editor
\r
9396 d.execCommand('SelectAll');
\r
9398 // We must save before we hide so Safari doesn't crash
\r
9400 DOM.hide(t.getContainer());
\r
9401 DOM.setStyle(t.id, 'display', t.orgDisplay);
\r
9404 isHidden : function() {
\r
9405 return !DOM.isHidden(this.id);
\r
9408 setProgressState : function(b, ti, o) {
\r
9409 this.onSetProgressState.dispatch(this, b, ti, o);
\r
9414 load : function(o) {
\r
9415 var t = this, e = t.getElement(), h;
\r
9421 // Double encode existing entities in the value
\r
9422 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
\r
9426 t.onLoadContent.dispatch(t, o);
\r
9428 o.element = e = null;
\r
9434 save : function(o) {
\r
9435 var t = this, e = t.getElement(), h, f;
\r
9437 if (!e || !t.initialized)
\r
9443 // Add undo level will trigger onchange event
\r
9444 if (!o.no_events) {
\r
9445 t.undoManager.typing = 0;
\r
9446 t.undoManager.add();
\r
9450 h = o.content = t.getContent(o);
\r
9453 t.onSaveContent.dispatch(t, o);
\r
9457 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
\r
9460 // Update hidden form element
\r
9461 if (f = DOM.getParent(t.id, 'form')) {
\r
9462 each(f.elements, function(e) {
\r
9463 if (e.name == t.id) {
\r
9472 o.element = e = null;
\r
9477 setContent : function(h, o) {
\r
9481 o.format = o.format || 'html';
\r
9486 t.onBeforeSetContent.dispatch(t, o);
\r
9488 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
\r
9489 // It will also be impossible to place the caret in the editor unless there is a BR element present
\r
9490 if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
\r
9491 o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
\r
9495 o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
\r
9497 if (o.format != 'raw' && t.settings.cleanup) {
\r
9498 o.getInner = true;
\r
9499 o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
\r
9503 t.onSetContent.dispatch(t, o);
\r
9508 getContent : function(o) {
\r
9512 o.format = o.format || 'html';
\r
9516 t.onBeforeGetContent.dispatch(t, o);
\r
9518 if (o.format != 'raw' && t.settings.cleanup) {
\r
9519 o.getInner = true;
\r
9520 h = t.serializer.serialize(t.getBody(), o);
\r
9522 h = t.getBody().innerHTML;
\r
9524 h = h.replace(/^\s*|\s*$/g, '');
\r
9528 t.onGetContent.dispatch(t, o);
\r
9533 isDirty : function() {
\r
9536 return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
\r
9539 getContainer : function() {
\r
9543 t.container = DOM.get(t.editorContainer || t.id + '_parent');
\r
9545 return t.container;
\r
9548 getContentAreaContainer : function() {
\r
9549 return this.contentAreaContainer;
\r
9552 getElement : function() {
\r
9553 return DOM.get(this.settings.content_element || this.id);
\r
9556 getWin : function() {
\r
9559 if (!t.contentWindow) {
\r
9560 e = DOM.get(t.id + "_ifr");
\r
9563 t.contentWindow = e.contentWindow;
\r
9566 return t.contentWindow;
\r
9569 getDoc : function() {
\r
9572 if (!t.contentDocument) {
\r
9576 t.contentDocument = w.document;
\r
9579 return t.contentDocument;
\r
9582 getBody : function() {
\r
9583 return this.bodyElement || this.getDoc().body;
\r
9586 convertURL : function(u, n, e) {
\r
9587 var t = this, s = t.settings;
\r
9589 // Use callback instead
\r
9590 if (s.urlconverter_callback)
\r
9591 return t.execCallback('urlconverter_callback', u, e, true, n);
\r
9593 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
\r
9594 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
\r
9597 // Convert to relative
\r
9598 if (s.relative_urls)
\r
9599 return t.documentBaseURI.toRelative(u);
\r
9601 // Convert to absolute
\r
9602 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
\r
9607 addVisual : function(e) {
\r
9608 var t = this, s = t.settings;
\r
9610 e = e || t.getBody();
\r
9612 if (!is(t.hasVisual))
\r
9613 t.hasVisual = s.visual;
\r
9615 each(t.dom.select('table,a', e), function(e) {
\r
9618 switch (e.nodeName) {
\r
9620 v = t.dom.getAttrib(e, 'border');
\r
9622 if (!v || v == '0') {
\r
9624 t.dom.addClass(e, s.visual_table_class);
\r
9626 t.dom.removeClass(e, s.visual_table_class);
\r
9632 v = t.dom.getAttrib(e, 'name');
\r
9636 t.dom.addClass(e, 'mceItemAnchor');
\r
9638 t.dom.removeClass(e, 'mceItemAnchor');
\r
9645 t.onVisualAid.dispatch(t, e, t.hasVisual);
\r
9648 remove : function() {
\r
9649 var t = this, e = t.getContainer();
\r
9651 t.removed = 1; // Cancels post remove event execution
\r
9654 t.execCallback('remove_instance_callback', t);
\r
9655 t.onRemove.dispatch(t);
\r
9657 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
\r
9658 t.onExecCommand.listeners = [];
\r
9660 tinymce.remove(t);
\r
9664 destroy : function(s) {
\r
9667 // One time is enough
\r
9672 tinymce.removeUnload(t.destroy);
\r
9673 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
\r
9676 if (t.theme && t.theme.destroy)
\r
9677 t.theme.destroy();
\r
9679 // Destroy controls, selection and dom
\r
9680 t.controlManager.destroy();
\r
9681 t.selection.destroy();
\r
9684 // Remove all events
\r
9686 // Don't clear the window or document if content editable
\r
9687 // is enabled since other instances might still be present
\r
9688 if (!t.settings.content_editable) {
\r
9689 Event.clear(t.getWin());
\r
9690 Event.clear(t.getDoc());
\r
9693 Event.clear(t.getBody());
\r
9694 Event.clear(t.formElement);
\r
9697 if (t.formElement) {
\r
9698 t.formElement.submit = t.formElement._mceOldSubmit;
\r
9699 t.formElement._mceOldSubmit = null;
\r
9702 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
\r
9705 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
\r
9710 // Internal functions
\r
9712 _addEvents : function() {
\r
9713 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
\r
9714 var t = this, i, s = t.settings, lo = {
\r
9715 mouseup : 'onMouseUp',
\r
9716 mousedown : 'onMouseDown',
\r
9717 click : 'onClick',
\r
9718 keyup : 'onKeyUp',
\r
9719 keydown : 'onKeyDown',
\r
9720 keypress : 'onKeyPress',
\r
9721 submit : 'onSubmit',
\r
9722 reset : 'onReset',
\r
9723 contextmenu : 'onContextMenu',
\r
9724 dblclick : 'onDblClick',
\r
9725 paste : 'onPaste' // Doesn't work in all browsers yet
\r
9728 function eventHandler(e, o) {
\r
9731 // Don't fire events when it's removed
\r
9735 // Generic event handler
\r
9736 if (t.onEvent.dispatch(t, e, o) !== false) {
\r
9737 // Specific event handler
\r
9738 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
\r
9743 each(lo, function(v, k) {
\r
9745 case 'contextmenu':
\r
9746 if (tinymce.isOpera) {
\r
9747 // Fake contextmenu on Opera
\r
9748 t.dom.bind(t.getBody(), 'mousedown', function(e) {
\r
9750 e.fakeType = 'contextmenu';
\r
9755 t.dom.bind(t.getBody(), k, eventHandler);
\r
9759 t.dom.bind(t.getBody(), k, function(e) {
\r
9766 t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
\r
9770 t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
\r
9774 t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
\r
9779 // Fixes bug where a specified document_base_uri could result in broken images
\r
9780 // This will also fix drag drop of images in Gecko
\r
9781 if (tinymce.isGecko) {
\r
9782 // Convert all images to absolute URLs
\r
9783 /* t.onSetContent.add(function(ed, o) {
\r
9784 each(ed.dom.select('img'), function(e) {
\r
9787 if (v = e.getAttribute('_mce_src'))
\r
9788 e.src = t.documentBaseURI.toAbsolute(v);
\r
9792 t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
\r
9797 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
\r
9798 e.src = t.documentBaseURI.toAbsolute(v);
\r
9802 // Set various midas options in Gecko
\r
9804 function setOpts() {
\r
9805 var t = this, d = t.getDoc(), s = t.settings;
\r
9807 if (isGecko && !s.readonly) {
\r
9808 if (t._isHidden()) {
\r
9810 if (!s.content_editable)
\r
9811 d.designMode = 'On';
\r
9813 // Fails if it's hidden
\r
9818 // Try new Gecko method
\r
9819 d.execCommand("styleWithCSS", 0, false);
\r
9822 if (!t._isHidden())
\r
9823 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
\r
9826 if (!s.table_inline_editing)
\r
9827 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
\r
9829 if (!s.object_resizing)
\r
9830 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
\r
9834 t.onBeforeExecCommand.add(setOpts);
\r
9835 t.onMouseDown.add(setOpts);
\r
9838 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
\r
9839 // WebKit can't even do simple things like selecting an image
\r
9840 // This also fixes so it's possible to select mceItemAnchors
\r
9841 if (tinymce.isWebKit) {
\r
9842 t.onClick.add(function(ed, e) {
\r
9845 // Needs tobe the setBaseAndExtend or it will fail to select floated images
\r
9846 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor')))
\r
9847 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
\r
9851 // Add node change handlers
\r
9852 t.onMouseUp.add(t.nodeChanged);
\r
9853 t.onClick.add(t.nodeChanged);
\r
9854 t.onKeyUp.add(function(ed, e) {
\r
9855 var c = e.keyCode;
\r
9857 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
9861 // Add reset handler
\r
9862 t.onReset.add(function() {
\r
9863 t.setContent(t.startContent, {format : 'raw'});
\r
9867 if (s.custom_shortcuts) {
\r
9868 if (s.custom_undo_redo_keyboard_shortcuts) {
\r
9869 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
\r
9870 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
\r
9873 // Add default shortcuts for gecko
\r
9875 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
\r
9876 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
\r
9877 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
\r
9880 // BlockFormat shortcuts keys
\r
9881 for (i=1; i<=6; i++)
\r
9882 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
\r
9884 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
\r
9885 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
\r
9886 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
\r
9888 function find(e) {
\r
9891 if (!e.altKey && !e.ctrlKey && !e.metaKey)
\r
9894 each(t.shortcuts, function(o) {
\r
9895 if (tinymce.isMac && o.ctrl != e.metaKey)
\r
9897 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
\r
9900 if (o.alt != e.altKey)
\r
9903 if (o.shift != e.shiftKey)
\r
9906 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
\r
9915 t.onKeyUp.add(function(ed, e) {
\r
9919 return Event.cancel(e);
\r
9922 t.onKeyPress.add(function(ed, e) {
\r
9926 return Event.cancel(e);
\r
9929 t.onKeyDown.add(function(ed, e) {
\r
9933 o.func.call(o.scope);
\r
9934 return Event.cancel(e);
\r
9939 if (tinymce.isIE) {
\r
9940 // Fix so resize will only update the width and height attributes not the styles of an image
\r
9941 // It will also block mceItemNoResize items
\r
9942 t.dom.bind(t.getDoc(), 'controlselect', function(e) {
\r
9943 var re = t.resizeInfo, cb;
\r
9947 // Don't do this action for non image elements
\r
9948 if (e.nodeName !== 'IMG')
\r
9952 t.dom.unbind(re.node, re.ev, re.cb);
\r
9954 if (!t.dom.hasClass(e, 'mceItemNoResize')) {
\r
9956 cb = t.dom.bind(e, ev, function(e) {
\r
9961 if (v = t.dom.getStyle(e, 'width')) {
\r
9962 t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
\r
9963 t.dom.setStyle(e, 'width', '');
\r
9966 if (v = t.dom.getStyle(e, 'height')) {
\r
9967 t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
\r
9968 t.dom.setStyle(e, 'height', '');
\r
9972 ev = 'resizestart';
\r
9973 cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
\r
9976 re = t.resizeInfo = {
\r
9983 t.onKeyDown.add(function(ed, e) {
\r
9984 switch (e.keyCode) {
\r
9986 // Fix IE control + backspace browser bug
\r
9987 if (t.selection.getRng().item) {
\r
9988 t.selection.getRng().item(0).removeNode();
\r
9989 return Event.cancel(e);
\r
9994 /*if (t.dom.boxModel) {
\r
9995 t.getBody().style.height = '100%';
\r
9997 Event.add(t.getWin(), 'resize', function(e) {
\r
9998 var docElm = t.getDoc().documentElement;
\r
10000 docElm.style.height = (docElm.offsetHeight - 10) + 'px';
\r
10005 if (tinymce.isOpera) {
\r
10006 t.onClick.add(function(ed, e) {
\r
10007 Event.prevent(e);
\r
10011 // Add custom undo/redo handlers
\r
10012 if (s.custom_undo_redo) {
\r
10013 function addUndo() {
\r
10014 t.undoManager.typing = 0;
\r
10015 t.undoManager.add();
\r
10018 t.dom.bind(t.getDoc(), 'focusout', function(e) {
\r
10019 if (!t.removed && t.undoManager.typing)
\r
10023 t.onKeyUp.add(function(ed, e) {
\r
10024 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
\r
10028 t.onKeyDown.add(function(ed, e) {
\r
10029 // Is caracter positon keys
\r
10030 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
\r
10031 if (t.undoManager.typing)
\r
10037 if (!t.undoManager.typing) {
\r
10038 t.undoManager.add();
\r
10039 t.undoManager.typing = 1;
\r
10043 t.onMouseDown.add(function() {
\r
10044 if (t.undoManager.typing)
\r
10050 _isHidden : function() {
\r
10056 // Weird, wheres that cursor selection?
\r
10057 s = this.selection.getSel();
\r
10058 return (!s || !s.rangeCount || s.rangeCount == 0);
\r
10061 // Fix for bug #1867292
\r
10062 _fixNesting : function(s) {
\r
10065 s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
\r
10068 // Handle end element
\r
10073 if (c !== d[d.length - 1].tag) {
\r
10074 for (i=d.length - 1; i>=0; i--) {
\r
10075 if (d[i].tag === c) {
\r
10085 if (d.length && d[d.length - 1].close) {
\r
10086 a = a + '</' + d[d.length - 1].tag + '>';
\r
10092 if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
\r
10095 // Ignore closed ones
\r
10096 if (/\/>$/.test(a))
\r
10099 d.push({tag : c}); // Push start element
\r
10105 // End all open tags
\r
10106 for (i=d.length - 1; i>=0; i--)
\r
10107 s += '</' + d[i].tag + '>';
\r
10113 (function(tinymce) {
\r
10114 // Added for compression purposes
\r
10115 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
\r
10117 tinymce.EditorCommands = function(editor) {
\r
10118 var dom = editor.dom,
\r
10119 selection = editor.selection,
\r
10120 commands = {state: {}, exec : {}, value : {}},
\r
10121 settings = editor.settings,
\r
10124 function execCommand(command, ui, value) {
\r
10127 command = command.toLowerCase();
\r
10128 if (func = commands.exec[command]) {
\r
10129 func(command, ui, value);
\r
10136 function queryCommandState(command) {
\r
10139 command = command.toLowerCase();
\r
10140 if (func = commands.state[command])
\r
10141 return func(command);
\r
10146 function queryCommandValue(command) {
\r
10149 command = command.toLowerCase();
\r
10150 if (func = commands.value[command])
\r
10151 return func(command);
\r
10156 function addCommands(command_list, type) {
\r
10157 type = type || 'exec';
\r
10159 each(command_list, function(callback, command) {
\r
10160 each(command.toLowerCase().split(','), function(command) {
\r
10161 commands[type][command] = callback;
\r
10166 // Expose public methods
\r
10167 tinymce.extend(this, {
\r
10168 execCommand : execCommand,
\r
10169 queryCommandState : queryCommandState,
\r
10170 queryCommandValue : queryCommandValue,
\r
10171 addCommands : addCommands
\r
10174 // Private methods
\r
10176 function execNativeCommand(command, ui, value) {
\r
10177 if (ui === undefined)
\r
10180 if (value === undefined)
\r
10183 return editor.getDoc().execCommand(command, ui, value);
\r
10186 function isFormatMatch(name) {
\r
10187 return editor.formatter.match(name);
\r
10190 function toggleFormat(name, value) {
\r
10191 editor.formatter.toggle(name, value ? {value : value} : undefined);
\r
10194 function storeSelection(type) {
\r
10195 bookmark = selection.getBookmark(type);
\r
10198 function restoreSelection() {
\r
10199 selection.moveToBookmark(bookmark);
\r
10202 // Add execCommand overrides
\r
10204 // Ignore these, added for compatibility
\r
10205 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
\r
10207 // Add undo manager logic
\r
10208 'mceEndUndoLevel,mceAddUndoLevel' : function() {
\r
10209 editor.undoManager.add();
\r
10212 'Cut,Copy,Paste' : function(command) {
\r
10213 var doc = editor.getDoc(), failed;
\r
10215 // Try executing the native command
\r
10217 execNativeCommand(command);
\r
10219 // Command failed
\r
10223 // Present alert message about clipboard access not being available
\r
10224 if (failed || !doc.queryCommandEnabled(command)) {
\r
10225 if (tinymce.isGecko) {
\r
10226 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
\r
10228 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
\r
10231 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
\r
10235 // Override unlink command
\r
10236 unlink : function(command) {
\r
10237 if (selection.isCollapsed())
\r
10238 selection.select(selection.getNode());
\r
10240 execNativeCommand(command);
\r
10241 selection.collapse(FALSE);
\r
10244 // Override justify commands to use the text formatter engine
\r
10245 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
10246 var align = command.substring(7);
\r
10248 // Remove all other alignments first
\r
10249 each('left,center,right,full'.split(','), function(name) {
\r
10250 if (align != name)
\r
10251 editor.formatter.remove('align' + name);
\r
10254 toggleFormat('align' + align);
\r
10257 // Override list commands to fix WebKit bug
\r
10258 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
10259 var listElm, listParent;
\r
10261 execNativeCommand(command);
\r
10263 // WebKit produces lists within block elements so we need to split them
\r
10264 // we will replace the native list creation logic to custom logic later on
\r
10265 // TODO: Remove this when the list creation logic is removed
\r
10266 listElm = dom.getParent(selection.getNode(), 'ol,ul');
\r
10268 listParent = listElm.parentNode;
\r
10270 // If list is within a text block then split that block
\r
10271 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
\r
10272 storeSelection();
\r
10273 dom.split(listParent, listElm);
\r
10274 restoreSelection();
\r
10279 // Override commands to use the text formatter engine
\r
10280 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
10281 toggleFormat(command);
\r
10284 // Override commands to use the text formatter engine
\r
10285 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
\r
10286 toggleFormat(command, value);
\r
10289 FontSize : function(command, ui, value) {
\r
10290 var fontClasses, fontSizes;
\r
10292 // Convert font size 1-7 to styles
\r
10293 if (value >= 1 && value <= 7) {
\r
10294 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
10295 fontClasses = tinymce.explode(settings.font_size_classes);
\r
10298 value = fontClasses[value - 1] || value;
\r
10300 value = fontSizes[value - 1] || value;
\r
10303 toggleFormat(command, value);
\r
10306 RemoveFormat : function(command) {
\r
10307 editor.formatter.remove(command);
\r
10310 mceBlockQuote : function(command) {
\r
10311 toggleFormat('blockquote');
\r
10314 FormatBlock : function(command, ui, value) {
\r
10315 return toggleFormat(value);
\r
10318 mceCleanup : function() {
\r
10319 storeSelection();
\r
10320 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
\r
10321 restoreSelection();
\r
10324 mceRemoveNode : function(command, ui, value) {
\r
10325 var node = value || selection.getNode();
\r
10327 // Make sure that the body node isn't removed
\r
10328 if (node != ed.getBody()) {
\r
10329 storeSelection();
\r
10330 editor.dom.remove(node, TRUE);
\r
10331 restoreSelection();
\r
10335 mceSelectNodeDepth : function(command, ui, value) {
\r
10338 dom.getParent(selection.getNode(), function(node) {
\r
10339 if (node.nodeType == 1 && counter++ == value) {
\r
10340 selection.select(node);
\r
10343 }, editor.getBody());
\r
10346 mceSelectNode : function(command, ui, value) {
\r
10347 selection.select(value);
\r
10350 mceInsertContent : function(command, ui, value) {
\r
10351 selection.setContent(value);
\r
10354 mceInsertRawHTML : function(command, ui, value) {
\r
10355 selection.setContent('tiny_mce_marker');
\r
10356 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value));
\r
10359 mceSetContent : function(command, ui, value) {
\r
10360 editor.setContent(value);
\r
10363 'Indent,Outdent' : function(command) {
\r
10364 var intentValue, indentUnit, value;
\r
10366 // Setup indent level
\r
10367 intentValue = settings.indentation;
\r
10368 indentUnit = /[a-z%]+$/i.exec(intentValue);
\r
10369 intentValue = parseInt(intentValue);
\r
10371 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
\r
10372 each(selection.getSelectedBlocks(), function(element) {
\r
10373 if (command == 'outdent') {
\r
10374 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
\r
10375 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
\r
10377 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
\r
10380 execNativeCommand(command);
\r
10383 mceRepaint : function() {
\r
10386 if (tinymce.isGecko) {
\r
10388 storeSelection(TRUE);
\r
10390 if (selection.getSel())
\r
10391 selection.getSel().selectAllChildren(editor.getBody());
\r
10393 selection.collapse(TRUE);
\r
10394 restoreSelection();
\r
10401 InsertHorizontalRule : function() {
\r
10402 selection.setContent('<hr />');
\r
10405 mceToggleVisualAid : function() {
\r
10406 editor.hasVisual = !editor.hasVisual;
\r
10407 editor.addVisual();
\r
10410 mceReplaceContent : function(command, ui, value) {
\r
10411 selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
\r
10414 mceInsertLink : function(command, ui, value) {
\r
10415 var link = dom.getParent(selection.getNode(), 'a');
\r
10417 if (tinymce.is(value, 'string'))
\r
10418 value = {href : value};
\r
10421 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
\r
10422 each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
\r
10423 dom.setAttribs(link, value);
\r
10427 dom.setAttribs(link, value);
\r
10429 ed.dom.remove(link, TRUE);
\r
10434 // Add queryCommandState overrides
\r
10436 // Override justify commands
\r
10437 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
10438 return isFormatMatch('align' + command.substring(7));
\r
10441 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
10442 return isFormatMatch(command);
\r
10445 mceBlockQuote : function() {
\r
10446 return isFormatMatch('blockquote');
\r
10449 Outdent : function() {
\r
10452 if (settings.inline_styles) {
\r
10453 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
10456 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
10460 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
\r
10463 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
10464 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
\r
10468 // Add queryCommandValue overrides
\r
10470 'FontSize,FontName' : function(command) {
\r
10471 var value = 0, parent;
\r
10473 if (parent = dom.getParent(selection.getNode(), 'span')) {
\r
10474 if (command == 'fontsize')
\r
10475 value = parent.style.fontSize;
\r
10477 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
\r
10484 // Add undo manager logic
\r
10485 if (settings.custom_undo_redo) {
\r
10487 Undo : function() {
\r
10488 editor.undoManager.undo();
\r
10491 Redo : function() {
\r
10492 editor.undoManager.redo();
\r
10497 })(tinymce);(function(tinymce) {
\r
10498 tinymce.create('tinymce.UndoManager', {
\r
10503 UndoManager : function(ed) {
\r
10504 var t = this, Dispatcher = tinymce.util.Dispatcher;
\r
10508 t.onAdd = new Dispatcher(this);
\r
10509 t.onUndo = new Dispatcher(this);
\r
10510 t.onRedo = new Dispatcher(this);
\r
10513 add : function(l) {
\r
10514 var t = this, i, ed = t.editor, b, s = ed.settings, la;
\r
10517 l.content = l.content || ed.getContent({format : 'raw', no_events : 1});
\r
10518 l.content = l.content.replace(/^\s*|\s*$/g, '');
\r
10520 // Add undo level if needed
\r
10521 la = t.data[t.index];
\r
10522 if (la && la.content == l.content) {
\r
10523 if (t.index > 0 || t.data.length == 1)
\r
10527 // Time to compress
\r
10528 if (s.custom_undo_redo_levels) {
\r
10529 if (t.data.length > s.custom_undo_redo_levels) {
\r
10530 for (i = 0; i < t.data.length - 1; i++)
\r
10531 t.data[i] = t.data[i + 1];
\r
10534 t.index = t.data.length;
\r
10538 if (s.custom_undo_redo_restore_selection)
\r
10539 l.bookmark = b = l.bookmark || ed.selection.getBookmark(2, true);
\r
10541 // Crop array if needed
\r
10542 if (t.index < t.data.length - 1) {
\r
10543 // Treat first level as initial
\r
10544 if (t.index == 0)
\r
10547 t.data.length = t.index + 1;
\r
10551 t.index = t.data.length - 1;
\r
10553 t.onAdd.dispatch(t, l);
\r
10554 ed.isNotDirty = 0;
\r
10556 //console.log(t.index);
\r
10557 //console.dir(t.data);
\r
10562 undo : function() {
\r
10563 var t = this, ed = t.editor, l = l, i;
\r
10570 if (t.index > 0) {
\r
10571 l = t.data[--t.index];
\r
10573 ed.setContent(l.content, {format : 'raw'});
\r
10574 ed.selection.moveToBookmark(l.bookmark);
\r
10576 t.onUndo.dispatch(t, l);
\r
10582 redo : function() {
\r
10583 var t = this, ed = t.editor, l = null;
\r
10585 if (t.index < t.data.length - 1) {
\r
10586 l = t.data[++t.index];
\r
10587 ed.setContent(l.content, {format : 'raw'});
\r
10588 ed.selection.moveToBookmark(l.bookmark);
\r
10590 t.onRedo.dispatch(t, l);
\r
10596 clear : function() {
\r
10604 hasUndo : function() {
\r
10605 return this.index > 0 || this.typing;
\r
10608 hasRedo : function() {
\r
10609 return this.index < this.data.length - 1;
\r
10613 (function(tinymce) {
\r
10615 var Event = tinymce.dom.Event,
\r
10616 isIE = tinymce.isIE,
\r
10617 isGecko = tinymce.isGecko,
\r
10618 isOpera = tinymce.isOpera,
\r
10619 each = tinymce.each,
\r
10620 extend = tinymce.extend,
\r
10624 // Checks if the selection/caret is at the end of the specified block element
\r
10625 function isAtEnd(rng, par) {
\r
10626 var rng2 = par.ownerDocument.createRange();
\r
10628 rng2.setStart(rng.endContainer, rng.endOffset);
\r
10629 rng2.setEndAfter(par);
\r
10631 // 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
10632 return rng2.cloneContents().textContent.length == 0;
\r
10635 function isEmpty(n) {
\r
10638 n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
\r
10639 n = n.replace(/<[^>]+>/g, ''); // Remove all tags
\r
10641 return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
\r
10644 function splitList(selection, dom, li) {
\r
10645 var listBlock, block;
\r
10647 if (isEmpty(li)) {
\r
10648 listBlock = dom.getParent(li, 'ul,ol');
\r
10650 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
\r
10651 dom.split(listBlock, li);
\r
10652 block = dom.create('p', 0, '<br _mce_bogus="1" />');
\r
10653 dom.replace(block, li);
\r
10654 selection.select(block, 1);
\r
10663 tinymce.create('tinymce.ForceBlocks', {
\r
10664 ForceBlocks : function(ed) {
\r
10665 var t = this, s = ed.settings, elm;
\r
10669 elm = (s.forced_root_block || 'p').toLowerCase();
\r
10670 s.element = elm.toUpperCase();
\r
10672 ed.onPreInit.add(t.setup, t);
\r
10674 t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi');
\r
10675 t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
10676 t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
10677 t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi');
\r
10678 t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
\r
10680 function padd(ed, o) {
\r
10682 o.content = o.content.replace(t.reOpera, '</' + elm + '>');
\r
10684 o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
\r
10686 if (!isIE && !isOpera && o.set) {
\r
10687 // Use instead of BR in padded paragraphs
\r
10688 o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
\r
10689 o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
\r
10691 o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
\r
10694 ed.onBeforeSetContent.add(padd);
\r
10695 ed.onPostProcess.add(padd);
\r
10697 if (s.forced_root_block) {
\r
10698 ed.onInit.add(t.forceRoots, t);
\r
10699 ed.onSetContent.add(t.forceRoots, t);
\r
10700 ed.onBeforeGetContent.add(t.forceRoots, t);
\r
10704 setup : function() {
\r
10705 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
\r
10707 // Force root blocks when typing and when getting output
\r
10708 if (s.forced_root_block) {
\r
10709 ed.onBeforeExecCommand.add(t.forceRoots, t);
\r
10710 ed.onKeyUp.add(t.forceRoots, t);
\r
10711 ed.onPreProcess.add(t.forceRoots, t);
\r
10714 if (s.force_br_newlines) {
\r
10715 // Force IE to produce BRs on enter
\r
10717 ed.onKeyPress.add(function(ed, e) {
\r
10720 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
\r
10721 selection.setContent('<br id="__" /> ', {format : 'raw'});
\r
10722 n = dom.get('__');
\r
10723 n.removeAttribute('id');
\r
10724 selection.select(n);
\r
10725 selection.collapse();
\r
10726 return Event.cancel(e);
\r
10732 if (!isIE && s.force_p_newlines) {
\r
10733 ed.onKeyPress.add(function(ed, e) {
\r
10734 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
\r
10739 ed.onKeyDown.add(function(ed, e) {
\r
10740 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
\r
10741 t.backspaceDelete(e, e.keyCode == 8);
\r
10746 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
\r
10747 if (tinymce.isWebKit) {
\r
10748 function insertBr(ed) {
\r
10749 var rng = selection.getRng(), br;
\r
10751 // Insert BR element
\r
10752 rng.insertNode(br = dom.create('br'));
\r
10754 // Place caret after BR
\r
10755 rng.setStartAfter(br);
\r
10756 rng.setEndAfter(br);
\r
10757 selection.setRng(rng);
\r
10759 // Could not place caret after BR then insert an nbsp entity and move the caret
\r
10760 if (selection.getSel().focusNode == br.previousSibling) {
\r
10761 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
\r
10762 selection.collapse(TRUE);
\r
10765 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
\r
10766 ed.getWin().scrollTo(0, dom.getPos(selection.getRng().startContainer).y);
\r
10769 ed.onKeyPress.add(function(ed, e) {
\r
10770 if (e.keyCode == 13 && (e.shiftKey || s.force_br_newlines)) {
\r
10777 // Padd empty inline elements within block elements
\r
10778 // For example: <p><strong><em></em></strong></p> becomes <p><strong><em> </em></strong></p>
\r
10779 ed.onPreProcess.add(function(ed, o) {
\r
10780 each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
\r
10781 if (isEmpty(p)) {
\r
10782 each(dom.select('span,em,strong,b,i', o.node), function(n) {
\r
10783 if (!n.hasChildNodes()) {
\r
10784 n.appendChild(ed.getDoc().createTextNode('\u00a0'));
\r
10785 return FALSE; // Break the loop one padding is enough
\r
10792 // IE specific fixes
\r
10794 // Replaces IE:s auto generated paragraphs with the specified element name
\r
10795 if (s.element != 'P') {
\r
10796 ed.onKeyPress.add(function(ed, e) {
\r
10797 t.lastElm = selection.getNode().nodeName;
\r
10800 ed.onKeyUp.add(function(ed, e) {
\r
10801 var bl, n = selection.getNode(), b = ed.getBody();
\r
10803 if (b.childNodes.length === 1 && n.nodeName == 'P') {
\r
10804 n = dom.rename(n, s.element);
\r
10805 selection.select(n);
\r
10806 selection.collapse();
\r
10807 ed.nodeChanged();
\r
10808 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
\r
10809 bl = dom.getParent(n, 'p');
\r
10812 dom.rename(bl, s.element);
\r
10813 ed.nodeChanged();
\r
10821 find : function(n, t, s) {
\r
10822 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
\r
10824 while (n = w.nextNode()) {
\r
10828 if (t == 0 && n == s)
\r
10832 if (t == 1 && c == s)
\r
10839 forceRoots : function(ed, e) {
\r
10840 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
10841 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
\r
10843 // Fix for bug #1863847
\r
10844 //if (e && e.keyCode == 13)
\r
10847 // Wrap non blocks into blocks
\r
10848 for (i = nl.length - 1; i >= 0; i--) {
\r
10851 // Ignore internal elements
\r
10852 if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
\r
10857 // Is text or non block element
\r
10858 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
\r
10860 // Create new block but ignore whitespace
\r
10861 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
\r
10862 // Store selection
\r
10863 if (si == -2 && r) {
\r
10865 // If selection is element then mark it
\r
10866 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
\r
10867 // Save the id of the selected element
\r
10868 eid = n.getAttribute("id");
\r
10869 n.setAttribute("id", "__mce");
\r
10871 // If element is inside body, might not be the case in contentEdiable mode
\r
10872 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
\r
10873 so = r.startOffset;
\r
10874 eo = r.endOffset;
\r
10875 si = t.find(b, 0, r.startContainer);
\r
10876 ei = t.find(b, 0, r.endContainer);
\r
10880 tr = d.body.createTextRange();
\r
10881 tr.moveToElementText(b);
\r
10883 bp = tr.move('character', c) * -1;
\r
10885 tr = r.duplicate();
\r
10887 sp = tr.move('character', c) * -1;
\r
10889 tr = r.duplicate();
\r
10891 le = (tr.move('character', c) * -1) - sp;
\r
10898 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
\r
10899 // See: http://support.microsoft.com/kb/829907
\r
10900 bl = ed.dom.create(ed.settings.forced_root_block);
\r
10901 nx.parentNode.replaceChild(bl, nx);
\r
10902 bl.appendChild(nx);
\r
10905 if (bl.hasChildNodes())
\r
10906 bl.insertBefore(nx, bl.firstChild);
\r
10908 bl.appendChild(nx);
\r
10911 bl = null; // Time to create new block
\r
10914 // Restore selection
\r
10917 bl = b.getElementsByTagName(ed.settings.element)[0];
\r
10918 r = d.createRange();
\r
10920 // Select last location or generated block
\r
10922 r.setStart(t.find(b, 1, si), so);
\r
10924 r.setStart(bl, 0);
\r
10926 // Select last location or generated block
\r
10928 r.setEnd(t.find(b, 1, ei), eo);
\r
10933 s.removeAllRanges();
\r
10938 r = s.createRange();
\r
10939 r.moveToElementText(b);
\r
10941 r.moveStart('character', si);
\r
10942 r.moveEnd('character', ei);
\r
10948 } else if (!isIE && (n = ed.dom.get('__mce'))) {
\r
10949 // Restore the id of the selected element
\r
10951 n.setAttribute('id', eid);
\r
10953 n.removeAttribute('id');
\r
10955 // Move caret before selected element
\r
10956 r = d.createRange();
\r
10957 r.setStartBefore(n);
\r
10958 r.setEndBefore(n);
\r
10963 getParentBlock : function(n) {
\r
10964 var d = this.dom;
\r
10966 return d.getParent(n, d.isBlock);
\r
10969 insertPara : function(e) {
\r
10970 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
10971 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
10973 // If root blocks are forced then use Operas default behavior since it's really good
\r
10974 // Removed due to bug: #1853816
\r
10975 // if (se.forced_root_block && isOpera)
\r
10978 // Setup before range
\r
10979 rb = d.createRange();
\r
10981 // If is before the first block element and in body, then move it into first block element
\r
10982 rb.setStart(s.anchorNode, s.anchorOffset);
\r
10983 rb.collapse(TRUE);
\r
10985 // Setup after range
\r
10986 ra = d.createRange();
\r
10988 // If is before the first block element and in body, then move it into first block element
\r
10989 ra.setStart(s.focusNode, s.focusOffset);
\r
10990 ra.collapse(TRUE);
\r
10992 // Setup start/end points
\r
10993 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
\r
10994 sn = dir ? s.anchorNode : s.focusNode;
\r
10995 so = dir ? s.anchorOffset : s.focusOffset;
\r
10996 en = dir ? s.focusNode : s.anchorNode;
\r
10997 eo = dir ? s.focusOffset : s.anchorOffset;
\r
10999 // If selection is in empty table cell
\r
11000 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
\r
11001 if (sn.firstChild.nodeName == 'BR')
\r
11002 dom.remove(sn.firstChild); // Remove BR
\r
11004 // Create two new block elements
\r
11005 if (sn.childNodes.length == 0) {
\r
11006 ed.dom.add(sn, se.element, null, '<br />');
\r
11007 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
11009 n = sn.innerHTML;
\r
11010 sn.innerHTML = '';
\r
11011 ed.dom.add(sn, se.element, null, n);
\r
11012 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
11015 // Move caret into the last one
\r
11016 r = d.createRange();
\r
11017 r.selectNodeContents(aft);
\r
11019 ed.selection.setRng(r);
\r
11024 // If the caret is in an invalid location in FF we need to move it into the first block
\r
11025 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
\r
11026 sn = en = sn.firstChild;
\r
11028 rb = d.createRange();
\r
11029 rb.setStart(sn, 0);
\r
11030 ra = d.createRange();
\r
11031 ra.setStart(en, 0);
\r
11034 // Never use body as start or end node
\r
11035 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
11036 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
\r
11037 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
11038 en = en.nodeName == "BODY" ? en.firstChild : en;
\r
11040 // Get start and end blocks
\r
11041 sb = t.getParentBlock(sn);
\r
11042 eb = t.getParentBlock(en);
\r
11043 bn = sb ? sb.nodeName : se.element; // Get block name to create
\r
11045 // Return inside list use default browser behavior
\r
11046 if (n = t.dom.getParent(sb, 'li,pre')) {
\r
11047 if (n.nodeName == 'LI')
\r
11048 return splitList(ed.selection, t.dom, n);
\r
11053 // If caption or absolute layers then always generate new blocks within
\r
11054 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
11059 // If caption or absolute layers then always generate new blocks within
\r
11060 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
11066 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
\r
11071 // Setup new before and after blocks
\r
11072 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
\r
11073 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
\r
11075 // Remove id from after clone
\r
11076 aft.removeAttribute('id');
\r
11078 // Is header and cursor is at the end, then force paragraph under
\r
11079 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
\r
11080 aft = ed.dom.create(se.element);
\r
11082 // Find start chop node
\r
11085 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
11089 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
\r
11091 // Find end chop node
\r
11094 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
11098 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
\r
11100 // Place first chop part into before block element
\r
11101 if (sc.nodeName == bn)
\r
11102 rb.setStart(sc, 0);
\r
11104 rb.setStartBefore(sc);
\r
11106 rb.setEnd(sn, so);
\r
11107 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
11109 // Place secnd chop part within new block element
\r
11111 ra.setEndAfter(ec);
\r
11113 //console.debug(s.focusNode, s.focusOffset);
\r
11116 ra.setStart(en, eo);
\r
11117 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
11119 // Create range around everything
\r
11120 r = d.createRange();
\r
11121 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
\r
11122 r.setStartBefore(sc.parentNode);
\r
11124 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
\r
11125 r.setStartBefore(rb.startContainer);
\r
11127 r.setStart(rb.startContainer, rb.startOffset);
\r
11130 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
\r
11131 r.setEndAfter(ec.parentNode);
\r
11133 r.setEnd(ra.endContainer, ra.endOffset);
\r
11135 // Delete and replace it with new block elements
\r
11136 r.deleteContents();
\r
11139 ed.getWin().scrollTo(0, vp.y);
\r
11141 // Never wrap blocks in blocks
\r
11142 if (bef.firstChild && bef.firstChild.nodeName == bn)
\r
11143 bef.innerHTML = bef.firstChild.innerHTML;
\r
11145 if (aft.firstChild && aft.firstChild.nodeName == bn)
\r
11146 aft.innerHTML = aft.firstChild.innerHTML;
\r
11148 // Padd empty blocks
\r
11149 if (isEmpty(bef))
\r
11150 bef.innerHTML = '<br />';
\r
11152 function appendStyles(e, en) {
\r
11153 var nl = [], nn, n, i;
\r
11155 e.innerHTML = '';
\r
11157 // Make clones of style elements
\r
11158 if (se.keep_styles) {
\r
11161 // We only want style specific elements
\r
11162 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
\r
11163 nn = n.cloneNode(FALSE);
\r
11164 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
\r
11167 } while (n = n.parentNode);
\r
11170 // Append style elements to aft
\r
11171 if (nl.length > 0) {
\r
11172 for (i = nl.length - 1, nn = e; i >= 0; i--)
\r
11173 nn = nn.appendChild(nl[i]);
\r
11175 // Padd most inner style element
\r
11176 nl[0].innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
11177 return nl[0]; // Move caret to most inner element
\r
11179 e.innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
11182 // Fill empty afterblook with current style
\r
11183 if (isEmpty(aft))
\r
11184 car = appendStyles(aft, en);
\r
11186 // Opera needs this one backwards for older versions
\r
11187 if (isOpera && parseFloat(opera.version()) < 9.5) {
\r
11188 r.insertNode(bef);
\r
11189 r.insertNode(aft);
\r
11191 r.insertNode(aft);
\r
11192 r.insertNode(bef);
\r
11199 function first(n) {
\r
11200 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
\r
11203 // Move cursor and scroll into view
\r
11204 r = d.createRange();
\r
11205 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
\r
11207 s.removeAllRanges();
\r
11210 // 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
11211 y = ed.dom.getPos(aft).y;
\r
11212 ch = aft.clientHeight;
\r
11214 // Is element within viewport
\r
11215 if (y < vp.y || y + ch > vp.y + vp.h) {
\r
11216 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
11217 //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
11223 backspaceDelete : function(e, bs) {
\r
11224 var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn;
\r
11226 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
\r
11227 // This workaround removes the element by hand and moves the caret to the previous element
\r
11228 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
\r
11229 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
\r
11230 // Find previous block element
\r
11232 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
\r
11235 if (sc != b.firstChild) {
\r
11236 // Find last text node
\r
11237 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
\r
11238 while (tn = w.nextNode())
\r
11241 // Place caret at the end of last text node
\r
11242 r = ed.getDoc().createRange();
\r
11243 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
\r
11244 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
\r
11247 // Remove the target container
\r
11248 ed.dom.remove(sc);
\r
11251 return Event.cancel(e);
\r
11256 // Gecko generates BR elements here and there, we don't like those so lets remove them
\r
11257 function handler(e) {
\r
11262 // A new BR was created in a block element, remove it
\r
11263 if (e && e.parentNode && e.nodeName == 'BR' && (n = t.getParentBlock(e))) {
\r
11264 pr = e.previousSibling;
\r
11266 Event.remove(b, 'DOMNodeInserted', handler);
\r
11268 // Is there whitespace at the end of the node before then we might need the pesky BR
\r
11269 // to place the caret at a correct location see bug: #2013943
\r
11270 if (pr && pr.nodeType == 3 && /\s+$/.test(pr.nodeValue))
\r
11273 // Only remove BR elements that got inserted in the middle of the text
\r
11274 if (e.previousSibling || e.nextSibling)
\r
11275 ed.dom.remove(e);
\r
11279 // Listen for new nodes
\r
11280 Event._add(b, 'DOMNodeInserted', handler);
\r
11282 // Remove listener
\r
11283 window.setTimeout(function() {
\r
11284 Event._remove(b, 'DOMNodeInserted', handler);
\r
11289 (function(tinymce) {
\r
11291 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
\r
11293 tinymce.create('tinymce.ControlManager', {
\r
11294 ControlManager : function(ed, s) {
\r
11300 t.onAdd = new tinymce.util.Dispatcher(t);
\r
11301 t.onPostRender = new tinymce.util.Dispatcher(t);
\r
11302 t.prefix = s.prefix || ed.id + '_';
\r
11305 t.onPostRender.add(function() {
\r
11306 each(t.controls, function(c) {
\r
11312 get : function(id) {
\r
11313 return this.controls[this.prefix + id] || this.controls[id];
\r
11316 setActive : function(id, s) {
\r
11319 if (c = this.get(id))
\r
11325 setDisabled : function(id, s) {
\r
11328 if (c = this.get(id))
\r
11329 c.setDisabled(s);
\r
11334 add : function(c) {
\r
11338 t.controls[c.id] = c;
\r
11339 t.onAdd.dispatch(c, t);
\r
11345 createControl : function(n) {
\r
11346 var c, t = this, ed = t.editor;
\r
11348 each(ed.plugins, function(p) {
\r
11349 if (p.createControl) {
\r
11350 c = p.createControl(n, t);
\r
11359 case "separator":
\r
11360 return t.createSeparator();
\r
11363 if (!c && ed.buttons && (c = ed.buttons[n]))
\r
11364 return t.createButton(n, c);
\r
11369 createDropMenu : function(id, s, cc) {
\r
11370 var t = this, ed = t.editor, c, bm, v, cls;
\r
11373 'class' : 'mceDropDown',
\r
11374 constrain : ed.settings.constrain_menus
\r
11377 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
\r
11378 if (v = ed.getParam('skin_variant'))
\r
11379 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
\r
11381 id = t.prefix + id;
\r
11382 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
\r
11383 c = t.controls[id] = new cls(id, s);
\r
11384 c.onAddItem.add(function(c, o) {
\r
11385 var s = o.settings;
\r
11387 s.title = ed.getLang(s.title, s.title);
\r
11389 if (!s.onclick) {
\r
11390 s.onclick = function(v) {
\r
11392 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
11397 ed.onRemove.add(function() {
\r
11401 // Fix for bug #1897785, #1898007
\r
11402 if (tinymce.isIE) {
\r
11403 c.onShowMenu.add(function() {
\r
11404 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
11407 bm = ed.selection.getBookmark(1);
\r
11410 c.onHideMenu.add(function() {
\r
11412 ed.selection.moveToBookmark(bm);
\r
11421 createListBox : function(id, s, cc) {
\r
11422 var t = this, ed = t.editor, cmd, c, cls;
\r
11427 s.title = ed.translate(s.title);
\r
11428 s.scope = s.scope || ed;
\r
11430 if (!s.onselect) {
\r
11431 s.onselect = function(v) {
\r
11432 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
11438 'class' : 'mce_' + id,
\r
11440 control_manager : t
\r
11443 id = t.prefix + id;
\r
11445 if (ed.settings.use_native_selects)
\r
11446 c = new tinymce.ui.NativeListBox(id, s);
\r
11448 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
\r
11449 c = new cls(id, s);
\r
11452 t.controls[id] = c;
\r
11454 // Fix focus problem in Safari
\r
11455 if (tinymce.isWebKit) {
\r
11456 c.onPostRender.add(function(c, n) {
\r
11457 // Store bookmark on mousedown
\r
11458 Event.add(n, 'mousedown', function() {
\r
11459 ed.bookmark = ed.selection.getBookmark(1);
\r
11462 // Restore on focus, since it might be lost
\r
11463 Event.add(n, 'focus', function() {
\r
11464 ed.selection.moveToBookmark(ed.bookmark);
\r
11465 ed.bookmark = null;
\r
11471 ed.onMouseDown.add(c.hideMenu, c);
\r
11476 createButton : function(id, s, cc) {
\r
11477 var t = this, ed = t.editor, o, c, cls;
\r
11482 s.title = ed.translate(s.title);
\r
11483 s.label = ed.translate(s.label);
\r
11484 s.scope = s.scope || ed;
\r
11486 if (!s.onclick && !s.menu_button) {
\r
11487 s.onclick = function() {
\r
11488 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
11494 'class' : 'mce_' + id,
\r
11495 unavailable_prefix : ed.getLang('unavailable', ''),
\r
11497 control_manager : t
\r
11500 id = t.prefix + id;
\r
11502 if (s.menu_button) {
\r
11503 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
\r
11504 c = new cls(id, s);
\r
11505 ed.onMouseDown.add(c.hideMenu, c);
\r
11507 cls = t._cls.button || tinymce.ui.Button;
\r
11508 c = new cls(id, s);
\r
11514 createMenuButton : function(id, s, cc) {
\r
11516 s.menu_button = 1;
\r
11518 return this.createButton(id, s, cc);
\r
11521 createSplitButton : function(id, s, cc) {
\r
11522 var t = this, ed = t.editor, cmd, c, cls;
\r
11527 s.title = ed.translate(s.title);
\r
11528 s.scope = s.scope || ed;
\r
11530 if (!s.onclick) {
\r
11531 s.onclick = function(v) {
\r
11532 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
11536 if (!s.onselect) {
\r
11537 s.onselect = function(v) {
\r
11538 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
11544 'class' : 'mce_' + id,
\r
11546 control_manager : t
\r
11549 id = t.prefix + id;
\r
11550 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
\r
11551 c = t.add(new cls(id, s));
\r
11552 ed.onMouseDown.add(c.hideMenu, c);
\r
11557 createColorSplitButton : function(id, s, cc) {
\r
11558 var t = this, ed = t.editor, cmd, c, cls, bm;
\r
11563 s.title = ed.translate(s.title);
\r
11564 s.scope = s.scope || ed;
\r
11566 if (!s.onclick) {
\r
11567 s.onclick = function(v) {
\r
11568 if (tinymce.isIE)
\r
11569 bm = ed.selection.getBookmark(1);
\r
11571 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
11575 if (!s.onselect) {
\r
11576 s.onselect = function(v) {
\r
11577 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
11583 'class' : 'mce_' + id,
\r
11584 'menu_class' : ed.getParam('skin') + 'Skin',
\r
11586 more_colors_title : ed.getLang('more_colors')
\r
11589 id = t.prefix + id;
\r
11590 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
\r
11591 c = new cls(id, s);
\r
11592 ed.onMouseDown.add(c.hideMenu, c);
\r
11594 // Remove the menu element when the editor is removed
\r
11595 ed.onRemove.add(function() {
\r
11599 // Fix for bug #1897785, #1898007
\r
11600 if (tinymce.isIE) {
\r
11601 c.onShowMenu.add(function() {
\r
11602 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
11604 bm = ed.selection.getBookmark(1);
\r
11607 c.onHideMenu.add(function() {
\r
11609 ed.selection.moveToBookmark(bm);
\r
11618 createToolbar : function(id, s, cc) {
\r
11619 var c, t = this, cls;
\r
11621 id = t.prefix + id;
\r
11622 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
\r
11623 c = new cls(id, s);
\r
11631 createSeparator : function(cc) {
\r
11632 var cls = cc || this._cls.separator || tinymce.ui.Separator;
\r
11634 return new cls();
\r
11637 setControlType : function(n, c) {
\r
11638 return this._cls[n.toLowerCase()] = c;
\r
11641 destroy : function() {
\r
11642 each(this.controls, function(c) {
\r
11646 this.controls = null;
\r
11650 (function(tinymce) {
\r
11651 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
\r
11653 tinymce.create('tinymce.WindowManager', {
\r
11654 WindowManager : function(ed) {
\r
11658 t.onOpen = new Dispatcher(t);
\r
11659 t.onClose = new Dispatcher(t);
\r
11664 open : function(s, p) {
\r
11665 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
\r
11667 // Default some options
\r
11670 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
\r
11671 sh = isOpera ? vp.h : screen.height;
\r
11672 s.name = s.name || 'mc_' + new Date().getTime();
\r
11673 s.width = parseInt(s.width || 320);
\r
11674 s.height = parseInt(s.height || 240);
\r
11675 s.resizable = true;
\r
11676 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
\r
11677 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
\r
11678 p.inline = false;
\r
11679 p.mce_width = s.width;
\r
11680 p.mce_height = s.height;
\r
11681 p.mce_auto_focus = s.auto_focus;
\r
11687 s.dialogWidth = s.width + 'px';
\r
11688 s.dialogHeight = s.height + 'px';
\r
11689 s.scroll = s.scrollbars || false;
\r
11693 // Build features string
\r
11694 each(s, function(v, k) {
\r
11695 if (tinymce.is(v, 'boolean'))
\r
11696 v = v ? 'yes' : 'no';
\r
11698 if (!/^(name|url)$/.test(k)) {
\r
11700 f += (f ? ';' : '') + k + ':' + v;
\r
11702 f += (f ? ',' : '') + k + '=' + v;
\r
11708 t.onOpen.dispatch(t, s, p);
\r
11710 u = s.url || s.file;
\r
11711 u = tinymce._addVer(u);
\r
11714 if (isIE && mo) {
\r
11716 window.showModalDialog(u, window, f);
\r
11718 w = window.open(u, s.name, f);
\r
11724 alert(t.editor.getLang('popup_blocked'));
\r
11727 close : function(w) {
\r
11729 this.onClose.dispatch(this);
\r
11732 createInstance : function(cl, a, b, c, d, e) {
\r
11733 var f = tinymce.resolve(cl);
\r
11735 return new f(a, b, c, d, e);
\r
11738 confirm : function(t, cb, s, w) {
\r
11741 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
\r
11744 alert : function(tx, cb, s, w) {
\r
11748 w.alert(t._decode(t.editor.getLang(tx, tx)));
\r
11754 resizeBy : function(dw, dh, win) {
\r
11755 win.resizeBy(dw, dh);
\r
11758 // Internal functions
\r
11760 _decode : function(s) {
\r
11761 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
\r
11764 }(tinymce));(function(tinymce) {
\r
11765 function CommandManager() {
\r
11766 var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
\r
11768 function add(collection, cmd, func, scope) {
\r
11769 if (typeof(cmd) == 'string')
\r
11772 tinymce.each(cmd, function(cmd) {
\r
11773 collection[cmd.toLowerCase()] = {func : func, scope : scope};
\r
11777 tinymce.extend(this, {
\r
11778 add : function(cmd, func, scope) {
\r
11779 add(execCommands, cmd, func, scope);
\r
11782 addQueryStateHandler : function(cmd, func, scope) {
\r
11783 add(queryStateCommands, cmd, func, scope);
\r
11786 addQueryValueHandler : function(cmd, func, scope) {
\r
11787 add(queryValueCommands, cmd, func, scope);
\r
11790 execCommand : function(scope, cmd, ui, value, args) {
\r
11791 if (cmd = execCommands[cmd.toLowerCase()]) {
\r
11792 if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
\r
11797 queryCommandValue : function() {
\r
11798 if (cmd = queryValueCommands[cmd.toLowerCase()])
\r
11799 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
11802 queryCommandState : function() {
\r
11803 if (cmd = queryStateCommands[cmd.toLowerCase()])
\r
11804 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
11809 tinymce.GlobalCommands = new CommandManager();
\r
11810 })(tinymce);(function(tinymce) {
\r
11811 tinymce.Formatter = function(ed) {
\r
11812 var formats = {},
\r
11813 each = tinymce.each,
\r
11815 selection = ed.selection,
\r
11816 TreeWalker = tinymce.dom.TreeWalker,
\r
11817 rangeUtils = new tinymce.dom.RangeUtils(dom),
\r
11818 isValid = ed.schema.isValid,
\r
11819 isBlock = dom.isBlock,
\r
11820 forcedRootBlock = ed.settings.forced_root_block,
\r
11821 nodeIndex = dom.nodeIndex,
\r
11822 INVISIBLE_CHAR = '\uFEFF',
\r
11823 MCE_ATTR_RE = /^(src|href|style)$/,
\r
11830 function getParents(node, selector) {
\r
11831 return dom.getParents(node, selector, dom.getRoot());
\r
11834 function resetPending() {
\r
11836 if (!pendingFormats || pendingFormats.apply.length || pendingFormats.remove.length)
\r
11837 pendingFormats = {apply : [], remove : []};
\r
11840 ed.onMouseUp.add(resetPending);
\r
11843 // Public functions
\r
11845 function get(name) {
\r
11846 return name ? formats[name] : formats;
\r
11849 function register(name, format) {
\r
11851 if (typeof(name) !== 'string') {
\r
11852 each(name, function(format, name) {
\r
11853 register(name, format);
\r
11856 // Force format into array and add it to internal collection
\r
11857 format = format.length ? format : [format];
\r
11859 each(format, function(format) {
\r
11860 // Set deep to false by default on selector formats this to avoid removing
\r
11861 // alignment on images inside paragraphs when alignment is changed on paragraphs
\r
11862 if (format.deep === undefined)
\r
11863 format.deep = !format.selector;
\r
11865 // Default to true
\r
11866 if (format.split === undefined)
\r
11867 format.split = !format.selector;
\r
11869 // Default to true
\r
11870 if (format.remove === undefined && format.selector)
\r
11871 format.remove = 'none';
\r
11873 // Split classes if needed
\r
11874 if (typeof(format.classes) === 'string')
\r
11875 format.classes = format.classes.split(/\s+/);
\r
11878 formats[name] = format;
\r
11883 function apply(name, vars, node) {
\r
11884 var formatList = get(name), format = formatList[0], bookmark, rng, i;
\r
11886 function moveStart(rng) {
\r
11887 var container = rng.startContainer,
\r
11888 offset = rng.startOffset,
\r
11891 // Move startContainer/startOffset in to a suitable node
\r
11892 if (container.nodeType == 1 || container.nodeValue === "") {
\r
11893 walker = new TreeWalker(container.childNodes[offset]);
\r
11894 for (node = walker.current(); node; node = walker.next()) {
\r
11895 if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) {
\r
11896 rng.setStart(node, 0);
\r
11905 function setElementFormat(elm, fmt) {
\r
11906 fmt = fmt || format;
\r
11909 each(fmt.styles, function(value, name) {
\r
11910 dom.setStyle(elm, name, replaceVars(value, vars));
\r
11913 each(fmt.attributes, function(value, name) {
\r
11914 dom.setAttrib(elm, name, replaceVars(value, vars));
\r
11917 each(fmt.classes, function(value) {
\r
11918 value = replaceVars(value, vars);
\r
11920 if (!dom.hasClass(elm, value))
\r
11921 dom.addClass(elm, value);
\r
11926 function applyRngStyle(rng) {
\r
11927 var newWrappers = [], wrapName, wrapElm;
\r
11929 // Setup wrapper element
\r
11930 wrapName = format.inline || format.block;
\r
11931 wrapElm = dom.create(wrapName);
\r
11932 setElementFormat(wrapElm);
\r
11934 rangeUtils.walk(rng, function(nodes) {
\r
11935 var currentWrapElm;
\r
11937 function process(node) {
\r
11938 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase();
\r
11940 // Stop wrapping on br elements
\r
11941 if (isEq(nodeName, 'br')) {
\r
11942 currentWrapElm = 0;
\r
11944 // Remove any br elements when we wrap things
\r
11945 if (format.block)
\r
11946 dom.remove(node);
\r
11951 // If node is wrapper type
\r
11952 if (format.wrapper && matchNode(node, name, vars)) {
\r
11953 currentWrapElm = 0;
\r
11957 // Can we rename the block
\r
11958 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
\r
11959 node = dom.rename(node, wrapName);
\r
11960 setElementFormat(node);
\r
11961 newWrappers.push(node);
\r
11962 currentWrapElm = 0;
\r
11966 // Handle selector patterns
\r
11967 if (format.selector) {
\r
11968 // Look for matching formats
\r
11969 each(formatList, function(format) {
\r
11970 if (dom.is(node, format.selector))
\r
11971 setElementFormat(node, format);
\r
11977 // Is it valid to wrap this item
\r
11978 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
\r
11979 // Start wrapping
\r
11980 if (!currentWrapElm) {
\r
11982 currentWrapElm = wrapElm.cloneNode(FALSE);
\r
11983 node.parentNode.insertBefore(currentWrapElm, node);
\r
11984 newWrappers.push(currentWrapElm);
\r
11987 currentWrapElm.appendChild(node);
\r
11989 // Start a new wrapper for possible children
\r
11990 currentWrapElm = 0;
\r
11992 each(tinymce.grep(node.childNodes), process);
\r
11994 // End the last wrapper
\r
11995 currentWrapElm = 0;
\r
11999 // Process siblings from range
\r
12000 each(nodes, process);
\r
12004 each(newWrappers, function(node) {
\r
12007 function getChildCount(node) {
\r
12010 each(node.childNodes, function(node) {
\r
12011 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
\r
12018 function mergeStyles(node) {
\r
12019 var child, clone;
\r
12021 each(node.childNodes, function(node) {
\r
12022 if (node.nodeType == 1 && !isBookmarkNode(node)) {
\r
12024 return FALSE; // break loop
\r
12028 // If child was found and of the same type as the current node
\r
12029 if (child && matchName(child, format)) {
\r
12030 clone = child.cloneNode(FALSE);
\r
12031 setElementFormat(clone);
\r
12033 dom.replace(clone, node, TRUE);
\r
12034 dom.remove(child, 1);
\r
12040 childCount = getChildCount(node);
\r
12042 // Remove empty nodes
\r
12043 if (childCount === 0) {
\r
12044 dom.remove(node, 1);
\r
12048 if (format.inline || format.wrapper) {
\r
12049 // Merges the current node with it's children of similar type to reduce the number of elements
\r
12050 if (!format.exact && childCount === 1) {
\r
12051 if (mergeStyles(node))
\r
12055 // Remove/merge children
\r
12056 each(formatList, function(format) {
\r
12057 // Merge all children of similar type will move styles from child to parent
\r
12058 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
\r
12059 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
\r
12060 each(dom.select(format.inline, node), function(child) {
\r
12061 removeFormat(format, vars, child, format.exact ? child : null);
\r
12065 // Look for parent with similar style format
\r
12066 dom.getParent(node.parentNode, function(parent) {
\r
12067 if (matchNode(parent, name, vars)) {
\r
12068 dom.remove(node, 1);
\r
12074 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
\r
12076 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
\r
12077 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
\r
12085 rng = dom.createRng();
\r
12087 rng.setStartBefore(node);
\r
12088 rng.setEndAfter(node);
\r
12090 applyRngStyle(rng);
\r
12092 if (!selection.isCollapsed() || !format.inline) {
\r
12093 // Apply formatting to selection
\r
12094 bookmark = selection.getBookmark();
\r
12095 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
\r
12097 selection.moveToBookmark(bookmark);
\r
12098 selection.setRng(moveStart(selection.getRng(TRUE)));
\r
12099 ed.nodeChanged();
\r
12101 performCaretAction('apply', name, vars);
\r
12106 function remove(name, vars, node) {
\r
12107 var formatList = get(name), format = formatList[0], bookmark, i, rng;
\r
12109 // Merges the styles for each node
\r
12110 function process(node) {
\r
12111 var children, i, l;
\r
12113 // Grab the children first since the nodelist might be changed
\r
12114 children = tinymce.grep(node.childNodes);
\r
12116 // Process current node
\r
12117 for (i = 0, l = formatList.length; i < l; i++) {
\r
12118 if (removeFormat(formatList[i], vars, node, node))
\r
12122 // Process the children
\r
12123 if (format.deep) {
\r
12124 for (i = 0, l = children.length; i < l; i++)
\r
12125 process(children[i]);
\r
12129 function findFormatRoot(container) {
\r
12132 // Find format root
\r
12133 each(getParents(container.parentNode).reverse(), function(parent) {
\r
12134 // Find format root element
\r
12135 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
\r
12136 // If the matched format has a remove none flag we shouldn't split it
\r
12137 if (!isBlock(parent) && matchNode(parent, name, vars))
\r
12138 formatRoot = parent;
\r
12142 return formatRoot;
\r
12145 function wrapAndSplit(format_root, container, target, split) {
\r
12146 var parent, clone, lastClone, firstClone, i, formatRootParent;
\r
12148 // Format root found then clone formats and split it
\r
12149 if (format_root) {
\r
12150 formatRootParent = format_root.parentNode;
\r
12152 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
\r
12153 clone = parent.cloneNode(FALSE);
\r
12155 for (i = 0; i < formatList.length; i++) {
\r
12156 if (removeFormat(formatList[i], vars, clone, clone)) {
\r
12162 // Build wrapper node
\r
12165 clone.appendChild(lastClone);
\r
12168 firstClone = clone;
\r
12170 lastClone = clone;
\r
12175 container = dom.split(format_root, container);
\r
12177 // Wrap container in cloned formats
\r
12179 target.parentNode.insertBefore(lastClone, target);
\r
12180 firstClone.appendChild(target);
\r
12184 return container;
\r
12187 function splitToFormatRoot(container) {
\r
12188 return wrapAndSplit(findFormatRoot(container), container, container, true);
\r
12191 function unwrap(start) {
\r
12192 var node = dom.get(start ? '_start' : '_end'),
\r
12193 out = node[start ? 'firstChild' : 'lastChild'];
\r
12195 dom.remove(node, 1);
\r
12200 function removeRngStyle(rng) {
\r
12201 var startContainer, endContainer;
\r
12203 rng = expandRng(rng, formatList, TRUE);
\r
12205 if (format.split) {
\r
12206 startContainer = getContainer(rng, TRUE);
\r
12207 endContainer = getContainer(rng);
\r
12209 if (startContainer != endContainer) {
\r
12210 // Wrap start/end nodes in span element since these might be cloned/moved
\r
12211 startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
\r
12212 endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
\r
12214 // Split start/end
\r
12215 splitToFormatRoot(startContainer);
\r
12216 splitToFormatRoot(endContainer);
\r
12218 // Unwrap start/end to get real elements again
\r
12219 startContainer = unwrap(TRUE);
\r
12220 endContainer = unwrap();
\r
12222 startContainer = endContainer = splitToFormatRoot(startContainer);
\r
12224 // Update range positions since they might have changed after the split operations
\r
12225 rng.startContainer = startContainer.parentNode;
\r
12226 rng.startOffset = nodeIndex(startContainer);
\r
12227 rng.endContainer = endContainer.parentNode;
\r
12228 rng.endOffset = nodeIndex(endContainer) + 1;
\r
12231 // Remove items between start/end
\r
12232 rangeUtils.walk(rng, function(nodes) {
\r
12233 each(nodes, function(node) {
\r
12241 rng = dom.createRng();
\r
12242 rng.setStartBefore(node);
\r
12243 rng.setEndAfter(node);
\r
12244 removeRngStyle(rng);
\r
12248 if (!selection.isCollapsed() || !format.inline) {
\r
12249 bookmark = selection.getBookmark();
\r
12250 removeRngStyle(selection.getRng(TRUE));
\r
12251 selection.moveToBookmark(bookmark);
\r
12252 ed.nodeChanged();
\r
12254 performCaretAction('remove', name, vars);
\r
12257 function toggle(name, vars, node) {
\r
12258 if (match(name, vars, node))
\r
12259 remove(name, vars, node);
\r
12261 apply(name, vars, node);
\r
12264 function matchNode(node, name, vars) {
\r
12265 var formatList = get(name), format, i, classes;
\r
12267 function matchItems(node, format, item_name) {
\r
12268 var key, value, items = format[item_name], i;
\r
12270 // Check all items
\r
12272 // Non indexed object
\r
12273 if (items.length === undefined) {
\r
12274 for (key in items) {
\r
12275 if (items.hasOwnProperty(key)) {
\r
12276 if (item_name === 'attributes')
\r
12277 value = dom.getAttrib(node, key);
\r
12279 value = getStyle(node, key);
\r
12281 if (!isEq(value, replaceVars(items[key], vars)))
\r
12286 // Only one match needed for indexed arrays
\r
12287 for (i = 0; i < items.length; i++) {
\r
12288 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
\r
12297 if (formatList && node) {
\r
12298 // Check each format in list
\r
12299 for (i = 0; i < formatList.length; i++) {
\r
12300 format = formatList[i];
\r
12302 // Name name, attributes, styles and classes
\r
12303 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
\r
12305 if (classes = format.classes) {
\r
12306 for (i = 0; i < classes.length; i++) {
\r
12307 if (!dom.hasClass(node, classes[i]))
\r
12318 function match(name, vars, node) {
\r
12319 var startNode, i;
\r
12321 function matchParents(node) {
\r
12322 // Find first node with similar format settings
\r
12323 node = dom.getParent(node, function(node) {
\r
12324 return !!matchNode(node, name, vars);
\r
12327 // Do an exact check on the similar format element
\r
12328 return matchNode(node, name, vars);
\r
12331 // Check specified node
\r
12333 return matchParents(node);
\r
12335 // Check pending formats
\r
12336 if (selection.isCollapsed()) {
\r
12337 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
12338 if (pendingFormats.apply[i].name == name)
\r
12342 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
12343 if (pendingFormats.remove[i].name == name)
\r
12347 return matchParents(selection.getNode());
\r
12350 // Check selected node
\r
12351 node = selection.getNode();
\r
12352 if (matchParents(node))
\r
12355 // Check start node if it's different
\r
12356 startNode = selection.getStart();
\r
12357 if (startNode != node) {
\r
12358 if (matchParents(startNode))
\r
12365 function canApply(name) {
\r
12366 var formatList = get(name), startNode, parents, i, x, selector;
\r
12368 if (formatList) {
\r
12369 startNode = selection.getStart();
\r
12370 parents = getParents(startNode);
\r
12372 for (x = formatList.length - 1; x >= 0; x--) {
\r
12373 selector = formatList[x].selector;
\r
12375 // Format is not selector based, then always return TRUE
\r
12379 for (i = parents.length - 1; i >= 0; i--) {
\r
12380 if (dom.is(parents[i], selector))
\r
12389 // Expose to public
\r
12390 tinymce.extend(this, {
\r
12392 register : register,
\r
12397 matchNode : matchNode,
\r
12398 canApply : canApply
\r
12401 // Private functions
\r
12403 function matchName(node, format) {
\r
12404 // Check for inline match
\r
12405 if (isEq(node, format.inline))
\r
12408 // Check for block match
\r
12409 if (isEq(node, format.block))
\r
12412 // Check for selector match
\r
12413 if (format.selector)
\r
12414 return dom.is(node, format.selector);
\r
12417 function isEq(str1, str2) {
\r
12418 str1 = str1 || '';
\r
12419 str2 = str2 || '';
\r
12421 str1 = str1.nodeName || str1;
\r
12422 str2 = str2.nodeName || str2;
\r
12424 return str1.toLowerCase() == str2.toLowerCase();
\r
12427 function getStyle(node, name) {
\r
12428 var styleVal = dom.getStyle(node, name);
\r
12430 // Force the format to hex
\r
12431 if (name == 'color' || name == 'backgroundColor')
\r
12432 styleVal = dom.toHex(styleVal);
\r
12434 // Opera will return bold as 700
\r
12435 if (name == 'fontWeight' && styleVal == 700)
\r
12436 styleVal = 'bold';
\r
12438 return '' + styleVal;
\r
12441 function replaceVars(value, vars) {
\r
12442 if (typeof(value) != "string")
\r
12443 value = value(vars);
\r
12445 value = value.replace(/%(\w+)/g, function(str, name) {
\r
12446 return vars[name] || str;
\r
12453 function isWhiteSpaceNode(node) {
\r
12454 return node && node.nodeType === 3 && /^\s*$/.test(node.nodeValue);
\r
12457 function wrap(node, name, attrs) {
\r
12458 var wrapper = dom.create(name, attrs);
\r
12460 node.parentNode.insertBefore(wrapper, node);
\r
12461 wrapper.appendChild(node);
\r
12466 function expandRng(rng, format, remove) {
\r
12467 var startContainer = rng.startContainer,
\r
12468 startOffset = rng.startOffset,
\r
12469 endContainer = rng.endContainer,
\r
12470 endOffset = rng.endOffset, sibling, lastIdx;
\r
12472 // This function walks up the tree if there is no siblings before/after the node
\r
12473 function findParentContainer(container, child_name, sibling_name, root) {
\r
12474 var parent, child;
\r
12476 root = root || dom.getRoot();
\r
12479 // Check if we can move up are we at root level or body level
\r
12480 parent = container.parentNode;
\r
12482 // Stop expanding on block elements or root depending on format
\r
12483 if (parent == root || (!format[0].block_expand && isBlock(parent)))
\r
12484 return container;
\r
12486 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
\r
12487 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
12488 return container;
\r
12490 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
12491 return container;
\r
12494 container = container.parentNode;
\r
12497 return container;
\r
12500 // If index based start position then resolve it
\r
12501 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
\r
12502 lastIdx = startContainer.childNodes.length - 1;
\r
12503 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
\r
12505 if (startContainer.nodeType == 3)
\r
12509 // If index based end position then resolve it
\r
12510 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
\r
12511 lastIdx = endContainer.childNodes.length - 1;
\r
12512 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
\r
12514 if (endContainer.nodeType == 3)
\r
12515 endOffset = endContainer.nodeValue.length;
\r
12518 // Exclude bookmark nodes if possible
\r
12519 if (isBookmarkNode(startContainer.parentNode))
\r
12520 startContainer = startContainer.parentNode;
\r
12522 if (isBookmarkNode(startContainer))
\r
12523 startContainer = startContainer.nextSibling || startContainer;
\r
12525 if (isBookmarkNode(endContainer.parentNode))
\r
12526 endContainer = endContainer.parentNode;
\r
12528 if (isBookmarkNode(endContainer))
\r
12529 endContainer = endContainer.previousSibling || endContainer;
\r
12531 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
\r
12532 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
\r
12533 // This will reduce the number of wrapper elements that needs to be created
\r
12534 // Move start point up the tree
\r
12535 if (format[0].inline || format[0].block_expand) {
\r
12536 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
12537 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
12540 // Expand start/end container to matching selector
\r
12541 if (format[0].selector && format[0].expand !== FALSE) {
\r
12542 function findSelectorEndPoint(container, sibling_name) {
\r
12543 var parents, i, y;
\r
12545 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
\r
12546 container = container[sibling_name];
\r
12548 parents = getParents(container);
\r
12549 for (i = 0; i < parents.length; i++) {
\r
12550 for (y = 0; y < format.length; y++) {
\r
12551 if (dom.is(parents[i], format[y].selector))
\r
12552 return parents[i];
\r
12556 return container;
\r
12559 // Find new startContainer/endContainer if there is better one
\r
12560 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
\r
12561 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
\r
12564 // Expand start/end container to matching block element or text node
\r
12565 if (format[0].block || format[0].selector) {
\r
12566 function findBlockEndPoint(container, sibling_name, sibling_name2) {
\r
12569 // Expand to block of similar type
\r
12570 if (!format[0].wrapper)
\r
12571 node = dom.getParent(container, format[0].block);
\r
12573 // Expand to first wrappable block element or any block element
\r
12575 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
\r
12577 // Exclude inner lists from wrapping
\r
12578 if (node && format[0].wrapper)
\r
12579 node = getParents(node, 'ul,ol').reverse()[0] || node;
\r
12581 // Didn't find a block element look for first/last wrappable element
\r
12583 node = container;
\r
12585 while (node[sibling_name] && !isBlock(node[sibling_name])) {
\r
12586 node = node[sibling_name];
\r
12588 // Break on BR but include it will be removed later on
\r
12589 // we can't remove it now since we need to check if it can be wrapped
\r
12590 if (isEq(node, 'br'))
\r
12595 return node || container;
\r
12598 // Find new startContainer/endContainer if there is better one
\r
12599 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
\r
12600 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
\r
12602 // Non block element then try to expand up the leaf
\r
12603 if (format[0].block) {
\r
12604 if (!isBlock(startContainer))
\r
12605 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
12607 if (!isBlock(endContainer))
\r
12608 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
12612 // Setup index for startContainer
\r
12613 if (startContainer.nodeType == 1) {
\r
12614 startOffset = nodeIndex(startContainer);
\r
12615 startContainer = startContainer.parentNode;
\r
12618 // Setup index for endContainer
\r
12619 if (endContainer.nodeType == 1) {
\r
12620 endOffset = nodeIndex(endContainer) + 1;
\r
12621 endContainer = endContainer.parentNode;
\r
12624 // Return new range like object
\r
12626 startContainer : startContainer,
\r
12627 startOffset : startOffset,
\r
12628 endContainer : endContainer,
\r
12629 endOffset : endOffset
\r
12633 function removeFormat(format, vars, node, compare_node) {
\r
12634 var i, attrs, stylesModified;
\r
12636 // Check if node matches format
\r
12637 if (!matchName(node, format))
\r
12640 // Should we compare with format attribs and styles
\r
12641 if (format.remove != 'all') {
\r
12643 each(format.styles, function(value, name) {
\r
12644 value = replaceVars(value, vars);
\r
12647 if (typeof(name) === 'number') {
\r
12649 compare_node = 0;
\r
12652 if (!compare_node || isEq(getStyle(compare_node, name), value))
\r
12653 dom.setStyle(node, name, '');
\r
12655 stylesModified = 1;
\r
12658 // Remove style attribute if it's empty
\r
12659 if (stylesModified && dom.getAttrib(node, 'style') == '') {
\r
12660 node.removeAttribute('style');
\r
12661 node.removeAttribute('_mce_style');
\r
12664 // Remove attributes
\r
12665 each(format.attributes, function(value, name) {
\r
12668 value = replaceVars(value, vars);
\r
12671 if (typeof(name) === 'number') {
\r
12673 compare_node = 0;
\r
12676 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
\r
12677 // Keep internal classes
\r
12678 if (name == 'class') {
\r
12679 value = dom.getAttrib(node, name);
\r
12681 // Build new class value where everything is removed except the internal prefixed classes
\r
12683 each(value.split(/\s+/), function(cls) {
\r
12684 if (/mce\w+/.test(cls))
\r
12685 valueOut += (valueOut ? ' ' : '') + cls;
\r
12688 // We got some internal classes left
\r
12690 dom.setAttrib(node, name, valueOut);
\r
12696 // IE6 has a bug where the attribute doesn't get removed correctly
\r
12697 if (name == "class")
\r
12698 node.removeAttribute('className');
\r
12700 // Remove mce prefixed attributes
\r
12701 if (MCE_ATTR_RE.test(name))
\r
12702 node.removeAttribute('_mce_' + name);
\r
12704 node.removeAttribute(name);
\r
12708 // Remove classes
\r
12709 each(format.classes, function(value) {
\r
12710 value = replaceVars(value, vars);
\r
12712 if (!compare_node || dom.hasClass(compare_node, value))
\r
12713 dom.removeClass(node, value);
\r
12716 // Check for non internal attributes
\r
12717 attrs = dom.getAttribs(node);
\r
12718 for (i = 0; i < attrs.length; i++) {
\r
12719 if (attrs[i].nodeName.indexOf('_') !== 0)
\r
12724 // Remove the inline child if it's empty for example <b> or <span>
\r
12725 if (format.remove != 'none') {
\r
12726 removeNode(node, format);
\r
12731 function removeNode(node, format) {
\r
12732 var parentNode = node.parentNode, rootBlockElm;
\r
12734 if (format.block) {
\r
12735 if (!forcedRootBlock) {
\r
12736 function find(node, next, inc) {
\r
12737 node = getNonWhiteSpaceSibling(node, next, inc);
\r
12739 return !node || (node.nodeName == 'BR' || isBlock(node));
\r
12742 // Append BR elements if needed before we remove the block
\r
12743 if (isBlock(node) && !isBlock(parentNode)) {
\r
12744 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
\r
12745 node.insertBefore(dom.create('br'), node.firstChild);
\r
12747 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
\r
12748 node.appendChild(dom.create('br'));
\r
12751 // Wrap the block in a forcedRootBlock if we are at the root of document
\r
12752 if (parentNode == dom.getRoot()) {
\r
12753 if (!format.list_block || !isEq(node, format.list_block)) {
\r
12754 each(tinymce.grep(node.childNodes), function(node) {
\r
12755 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
\r
12756 if (!rootBlockElm)
\r
12757 rootBlockElm = wrap(node, forcedRootBlock);
\r
12759 rootBlockElm.appendChild(node);
\r
12761 rootBlockElm = 0;
\r
12768 dom.remove(node, 1);
\r
12771 function getNonWhiteSpaceSibling(node, next, inc) {
\r
12773 next = next ? 'nextSibling' : 'previousSibling';
\r
12775 for (node = inc ? node : node[next]; node; node = node[next]) {
\r
12776 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
\r
12782 function isBookmarkNode(node) {
\r
12783 return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
\r
12786 function mergeSiblings(prev, next) {
\r
12787 var marker, sibling, tmpSibling;
\r
12789 function compareElements(node1, node2) {
\r
12790 // Not the same name
\r
12791 if (node1.nodeName != node2.nodeName)
\r
12794 function getAttribs(node) {
\r
12795 var attribs = {};
\r
12797 each(dom.getAttribs(node), function(attr) {
\r
12798 var name = attr.nodeName.toLowerCase();
\r
12800 // Don't compare internal attributes or style/class
\r
12801 if (name.indexOf('_') !== 0 && name !== 'class' && name !== 'style')
\r
12802 attribs[name] = dom.getAttrib(node, name);
\r
12808 function compareObjects(obj1, obj2) {
\r
12811 for (name in obj1) {
\r
12812 // Obj1 has item obj2 doesn't have
\r
12813 if (obj1.hasOwnProperty(name)) {
\r
12814 value = obj2[name];
\r
12816 // Obj2 doesn't have obj1 item
\r
12817 if (value === undefined)
\r
12820 // Obj2 item has a different value
\r
12821 if (obj1[name] != value)
\r
12824 // Delete similar value
\r
12825 delete obj2[name];
\r
12829 // Check if obj 2 has something obj 1 doesn't have
\r
12830 for (name in obj2) {
\r
12831 // Obj2 has item obj1 doesn't have
\r
12832 if (obj2.hasOwnProperty(name))
\r
12839 // Attribs are not the same
\r
12840 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
\r
12843 // Styles are not the same
\r
12844 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
\r
12850 // Check if next/prev exists and that they are elements
\r
12851 if (prev && next) {
\r
12852 function findElementSibling(node, sibling_name) {
\r
12853 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
\r
12854 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
12857 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
12864 // If previous sibling is empty then jump over it
\r
12865 prev = findElementSibling(prev, 'previousSibling');
\r
12866 next = findElementSibling(next, 'nextSibling');
\r
12868 // Compare next and previous nodes
\r
12869 if (compareElements(prev, next)) {
\r
12870 // Append nodes between
\r
12871 for (sibling = prev.nextSibling; sibling && sibling != next;) {
\r
12872 tmpSibling = sibling;
\r
12873 sibling = sibling.nextSibling;
\r
12874 prev.appendChild(tmpSibling);
\r
12877 // Remove next node
\r
12878 dom.remove(next);
\r
12880 // Move children into prev node
\r
12881 each(tinymce.grep(next.childNodes), function(node) {
\r
12882 prev.appendChild(node);
\r
12892 function isTextBlock(name) {
\r
12893 return /^(h[1-6]|p|div|pre|address)$/.test(name);
\r
12896 function getContainer(rng, start) {
\r
12897 var container, offset, lastIdx;
\r
12899 container = rng[start ? 'startContainer' : 'endContainer'];
\r
12900 offset = rng[start ? 'startOffset' : 'endOffset'];
\r
12902 if (container.nodeType == 1) {
\r
12903 lastIdx = container.childNodes.length - 1;
\r
12905 if (!start && offset)
\r
12908 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
\r
12911 return container;
\r
12914 function performCaretAction(type, name, vars) {
\r
12915 var i, rng, selectedNode = selection.getNode().parentNode,
\r
12916 doc = ed.getDoc(), marker = 'mceinline',
\r
12917 events = ['onKeyDown', 'onKeyUp', 'onKeyPress'],
\r
12918 currentPendingFormats = pendingFormats[type],
\r
12919 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
\r
12921 // Check if it already exists
\r
12922 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
\r
12923 if (currentPendingFormats[i].name == name)
\r
12927 currentPendingFormats.push({name : name, vars : vars});
\r
12929 // Check if it's in the oter type
\r
12930 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
\r
12931 if (otherPendingFormats[i].name == name)
\r
12932 otherPendingFormats.splice(i, 1);
\r
12935 function unbind() {
\r
12936 if (caretHandler) {
\r
12937 each(events, function(event) {
\r
12938 ed[event].remove(caretHandler);
\r
12941 caretHandler = 0;
\r
12945 function perform(caret_node) {
\r
12946 // Apply pending formats
\r
12947 each(pendingFormats.apply.reverse(), function(item) {
\r
12948 apply(item.name, item.vars, caret_node);
\r
12951 // Remove pending formats
\r
12952 each(pendingFormats.remove.reverse(), function(item) {
\r
12953 remove(item.name, item.vars, caret_node);
\r
12956 dom.remove(caret_node, 1);
\r
12960 function isMarker(node) {
\r
12961 return node.face == marker || node.style.fontFamily == marker;
\r
12966 doc.execCommand('FontName', false, marker);
\r
12968 // IE will convert the current word
\r
12969 each(dom.select('font,span', selectedNode), function(node) {
\r
12972 if (isMarker(node)) {
\r
12973 bookmark = selection.getBookmark();
\r
12975 selection.moveToBookmark(bookmark);
\r
12976 ed.nodeChanged();
\r
12977 selectedNode = 0;
\r
12981 if (selectedNode) {
\r
12982 caretHandler = function(ed, e) {
\r
12983 each(dom.select('font,span', selectedNode), function(node) {
\r
12984 var bookmark, textNode;
\r
12986 // Look for marker
\r
12987 if (node.face == marker || node.style.fontFamily == marker) {
\r
12988 textNode = node.firstChild;
\r
12992 rng = dom.createRng();
\r
12993 rng.setStart(textNode, textNode.nodeValue.length);
\r
12994 rng.setEnd(textNode, textNode.nodeValue.length);
\r
12995 selection.setRng(rng);
\r
12996 ed.nodeChanged();
\r
13002 // Always unbind and clear pending styles on keyup
\r
13003 if (e.type == 'keyup') {
\r
13009 each(events, function(event) {
\r
13010 ed[event].addToTop(caretHandler);
\r
13016 tinymce.onAddEditor.add(function(tinymce, ed) {
\r
13017 var filters, fontSizes, dom, settings = ed.settings;
\r
13019 if (settings.inline_styles) {
\r
13020 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
13022 function replaceWithSpan(node, styles) {
\r
13023 dom.replace(dom.create('span', {
\r
13029 font : function(dom, node) {
\r
13030 replaceWithSpan(node, {
\r
13031 backgroundColor : node.style.backgroundColor,
\r
13032 color : node.color,
\r
13033 fontFamily : node.face,
\r
13034 fontSize : fontSizes[parseInt(node.size) - 1]
\r
13038 u : function(dom, node) {
\r
13039 replaceWithSpan(node, {
\r
13040 textDecoration : 'underline'
\r
13044 strike : function(dom, node) {
\r
13045 replaceWithSpan(node, {
\r
13046 textDecoration : 'line-through'
\r
13051 function convert(editor, params) {
\r
13052 dom = editor.dom;
\r
13054 if (settings.convert_fonts_to_spans) {
\r
13055 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
\r
13056 filters[node.nodeName.toLowerCase()](ed.dom, node);
\r
13061 ed.onPreProcess.add(convert);
\r
13063 ed.onInit.add(function() {
\r
13064 ed.selection.onSetContent.add(convert);
\r