2 var whiteSpaceRe = /^\s*|\s*$/g,
\r
8 minorVersion : '3.8',
\r
10 releaseDate : '2010-06-30',
\r
12 _init : function() {
\r
13 var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
\r
15 t.isOpera = win.opera && opera.buildNumber;
\r
17 t.isWebKit = /WebKit/.test(ua);
\r
19 t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
\r
21 t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
\r
23 t.isGecko = !t.isWebKit && /Gecko/.test(ua);
\r
25 t.isMac = ua.indexOf('Mac') != -1;
\r
27 t.isAir = /adobeair/i.test(ua);
\r
29 t.isIDevice = /(iPad|iPhone)/.test(ua);
\r
31 // TinyMCE .NET webcontrol might be setting the values for TinyMCE
\r
32 if (win.tinyMCEPreInit) {
\r
33 t.suffix = tinyMCEPreInit.suffix;
\r
34 t.baseURL = tinyMCEPreInit.base;
\r
35 t.query = tinyMCEPreInit.query;
\r
39 // Get suffix and base
\r
42 // If base element found, add that infront of baseURL
\r
43 nl = d.getElementsByTagName('base');
\r
44 for (i=0; i<nl.length; i++) {
\r
45 if (v = nl[i].href) {
\r
46 // Host only value like http://site.com or http://site.com:8008
\r
47 if (/^https?:\/\/[^\/]+$/.test(v))
\r
50 base = v ? v.match(/.*\//)[0] : ''; // Get only directory
\r
54 function getBase(n) {
\r
55 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
\r
56 if (/_(src|dev)\.js/g.test(n.src))
\r
59 if ((p = n.src.indexOf('?')) != -1)
\r
60 t.query = n.src.substring(p + 1);
\r
62 t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
\r
64 // If path to script is relative and a base href was found add that one infront
\r
65 // the src property will always be an absolute one on non IE browsers and IE 8
\r
66 // so this logic will basically only be executed on older IE versions
\r
67 if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
\r
68 t.baseURL = base + t.baseURL;
\r
77 nl = d.getElementsByTagName('script');
\r
78 for (i=0; i<nl.length; i++) {
\r
84 n = d.getElementsByTagName('head')[0];
\r
86 nl = n.getElementsByTagName('script');
\r
87 for (i=0; i<nl.length; i++) {
\r
96 is : function(o, t) {
\r
98 return o !== undefined;
\r
100 if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
\r
103 return typeof(o) == t;
\r
106 each : function(o, cb, s) {
\r
114 if (o.length !== undefined) {
\r
115 // Indexed arrays, needed for Safari
\r
116 for (n=0, l = o.length; n < l; n++) {
\r
117 if (cb.call(s, o[n], n, o) === false)
\r
123 if (o.hasOwnProperty(n)) {
\r
124 if (cb.call(s, o[n], n, o) === false)
\r
134 trim : function(s) {
\r
135 return (s ? '' + s : '').replace(whiteSpaceRe, '');
\r
138 create : function(s, p) {
\r
139 var t = this, sp, ns, cn, scn, c, de = 0;
\r
141 // Parse : <prefix> <class>:<super class>
\r
142 s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
\r
143 cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
\r
145 // Create namespace for new class
\r
146 ns = t.createNS(s[3].replace(/\.\w+$/, ''));
\r
148 // Class already exists
\r
152 // Make pure static class
\r
153 if (s[2] == 'static') {
\r
157 this.onCreate(s[2], s[3], ns[cn]);
\r
162 // Create default constructor
\r
164 p[cn] = function() {};
\r
168 // Add constructor and methods
\r
170 t.extend(ns[cn].prototype, p);
\r
174 sp = t.resolve(s[5]).prototype;
\r
175 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
\r
177 // Extend constructor
\r
180 // Add passthrough constructor
\r
181 ns[cn] = function() {
\r
182 return sp[scn].apply(this, arguments);
\r
185 // Add inherit constructor
\r
186 ns[cn] = function() {
\r
187 this.parent = sp[scn];
\r
188 return c.apply(this, arguments);
\r
191 ns[cn].prototype[cn] = ns[cn];
\r
193 // Add super methods
\r
194 t.each(sp, function(f, n) {
\r
195 ns[cn].prototype[n] = sp[n];
\r
198 // Add overridden methods
\r
199 t.each(p, function(f, n) {
\r
200 // Extend methods if needed
\r
202 ns[cn].prototype[n] = function() {
\r
203 this.parent = sp[n];
\r
204 return f.apply(this, arguments);
\r
208 ns[cn].prototype[n] = f;
\r
213 // Add static methods
\r
214 t.each(p['static'], function(f, n) {
\r
219 this.onCreate(s[2], s[3], ns[cn].prototype);
\r
222 walk : function(o, f, n, s) {
\r
229 tinymce.each(o, function(o, i) {
\r
230 if (f.call(s, o, i, n) === false)
\r
233 tinymce.walk(o, f, n, s);
\r
238 createNS : function(n, o) {
\r
244 for (i=0; i<n.length; i++) {
\r
256 resolve : function(n, o) {
\r
262 for (i = 0, l = n.length; i < l; i++) {
\r
272 addUnload : function(f, s) {
\r
275 f = {func : f, scope : s || this};
\r
278 function unload() {
\r
279 var li = t.unloads, o, n;
\r
282 // Call unload handlers
\r
287 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
\r
290 // Detach unload function
\r
291 if (win.detachEvent) {
\r
292 win.detachEvent('onbeforeunload', fakeUnload);
\r
293 win.detachEvent('onunload', unload);
\r
294 } else if (win.removeEventListener)
\r
295 win.removeEventListener('unload', unload, false);
\r
297 // Destroy references
\r
298 t.unloads = o = li = w = unload = 0;
\r
300 // Run garbarge collector on IE
\r
301 if (win.CollectGarbage)
\r
306 function fakeUnload() {
\r
309 // Is there things still loading, then do some magic
\r
310 if (d.readyState == 'interactive') {
\r
312 // Prevent memory leak
\r
313 d.detachEvent('onstop', stop);
\r
315 // Call unload handler
\r
322 // Fire unload when the currently loading page is stopped
\r
324 d.attachEvent('onstop', stop);
\r
326 // Remove onstop listener after a while to prevent the unload function
\r
327 // to execute if the user presses cancel in an onbeforeunload
\r
328 // confirm dialog and then presses the browser stop button
\r
329 win.setTimeout(function() {
\r
331 d.detachEvent('onstop', stop);
\r
336 // Attach unload handler
\r
337 if (win.attachEvent) {
\r
338 win.attachEvent('onunload', unload);
\r
339 win.attachEvent('onbeforeunload', fakeUnload);
\r
340 } else if (win.addEventListener)
\r
341 win.addEventListener('unload', unload, false);
\r
343 // Setup initial unload handler array
\r
351 removeUnload : function(f) {
\r
352 var u = this.unloads, r = null;
\r
354 tinymce.each(u, function(o, i) {
\r
355 if (o && o.func == f) {
\r
365 explode : function(s, d) {
\r
366 return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
\r
369 _addVer : function(u) {
\r
375 v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
\r
377 if (u.indexOf('#') == -1)
\r
380 return u.replace('#', v + '#');
\r
385 // Initialize the API
\r
388 // Expose tinymce namespace to the global namespace (window)
\r
389 win.tinymce = win.tinyMCE = tinymce;
\r
392 (function($, tinymce) {
\r
393 var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undefined;
\r
395 // jQuery is undefined
\r
397 return alert("Load jQuery first!");
\r
399 // Stick jQuery into the tinymce namespace
\r
403 tinymce.adapter = {
\r
404 patchEditor : function(editor) {
\r
407 // Adapt the css function to make sure that the _mce_style
\r
408 // attribute gets updated with the new style information
\r
409 function css(name, value) {
\r
412 // Remove _mce_style when set operation occurs
\r
414 self.removeAttr('_mce_style');
\r
416 return fn.css.apply(self, arguments);
\r
419 // Apapt the attr function to make sure that it uses the _mce_ prefixed variants
\r
420 function attr(name, value) {
\r
423 // Update/retrive _mce_ attribute variants
\r
424 if (attrRegExp.test(name)) {
\r
425 if (value !== undefined) {
\r
426 // Use TinyMCE behavior when setting the specifc attributes
\r
427 self.each(function(i, node) {
\r
428 editor.dom.setAttrib(node, name, value);
\r
433 return self.attr('_mce_' + name);
\r
436 // Default behavior
\r
437 return fn.attr.apply(self, arguments);
\r
440 function htmlPatchFunc(func) {
\r
441 // Returns a modified function that processes
\r
442 // the HTML before executing the action this makes sure
\r
443 // that href/src etc gets moved into the _mce_ variants
\r
444 return function(content) {
\r
446 content = editor.dom.processHTML(content);
\r
448 return func.call(this, content);
\r
452 // Patch various jQuery functions to handle tinymce specific attribute and content behavior
\r
453 // we don't patch the jQuery.fn directly since it will most likely break compatibility
\r
454 // with other jQuery logic on the page. Only instances created by TinyMCE should be patched.
\r
455 function patch(jq) {
\r
456 // Patch some functions, only patch the object once
\r
457 if (jq.css !== css) {
\r
458 // Patch css/attr to use the _mce_ prefixed attribute variants
\r
462 // Patch HTML functions to use the DOMUtils.processHTML filter logic
\r
463 jq.html = htmlPatchFunc(fn.html);
\r
464 jq.append = htmlPatchFunc(fn.append);
\r
465 jq.prepend = htmlPatchFunc(fn.prepend);
\r
466 jq.after = htmlPatchFunc(fn.after);
\r
467 jq.before = htmlPatchFunc(fn.before);
\r
468 jq.replaceWith = htmlPatchFunc(fn.replaceWith);
\r
469 jq.tinymce = editor;
\r
471 // Each pushed jQuery instance needs to be patched
\r
472 // as well for example when traversing the DOM
\r
473 jq.pushStack = function() {
\r
474 return patch(fn.pushStack.apply(this, arguments));
\r
481 // Add a $ function on each editor instance this one is scoped for the editor document object
\r
482 // this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red');
\r
483 editor.$ = function(selector, scope) {
\r
484 var doc = editor.getDoc();
\r
486 return patch($(selector || doc, doc || scope));
\r
491 // Patch in core NS functions
\r
492 tinymce.extend = $.extend;
\r
493 tinymce.extend(tinymce, {
\r
495 grep : function(a, f) {return $.grep(a, f || function(){return 1;});},
\r
496 inArray : function(a, v) {return $.inArray(v, a || []);}
\r
498 /* Didn't iterate stylesheets
\r
499 each : function(o, cb, s) {
\r
505 $.each(o, function(nr, el){
\r
506 if (cb.call(s, el, nr, o) === false) {
\r
516 // Patch in functions in various clases
\r
517 // Add a "#ifndefjquery" statement around each core API function you add below
\r
519 'tinymce.dom.DOMUtils' : {
\r
521 addClass : function(e, c) {
\r
522 if (is(e, 'array') && is(e[0], 'string'))
\r
524 return (e && $(is(e, 'string') ? '#' + e : e)
\r
526 .attr('class')) || false;
\r
529 hasClass : function(n, c) {
\r
530 return $(is(n, 'string') ? '#' + n : n).hasClass(c);
\r
533 removeClass : function(e, c) {
\r
539 $(is(e, 'string') ? '#' + e : e)
\r
542 r.push(this.className);
\r
545 return r.length == 1 ? r[0] : r;
\r
549 select : function(pattern, scope) {
\r
552 return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []);
\r
555 is : function(n, patt) {
\r
556 return $(this.get(n)).is(patt);
\r
560 show : function(e) {
\r
561 if (is(e, 'array') && is(e[0], 'string'))
\r
564 $(is(e, 'string') ? '#' + e : e).css('display', 'block');
\r
567 hide : function(e) {
\r
568 if (is(e, 'array') && is(e[0], 'string'))
\r
571 $(is(e, 'string') ? '#' + e : e).css('display', 'none');
\r
574 isHidden : function(e) {
\r
575 return $(is(e, 'string') ? '#' + e : e).is(':hidden');
\r
578 insertAfter : function(n, e) {
\r
579 return $(is(e, 'string') ? '#' + e : e).after(n);
\r
582 replace : function(o, n, k) {
\r
583 n = $(is(n, 'string') ? '#' + n : n);
\r
586 n.children().appendTo(o);
\r
591 setStyle : function(n, na, v) {
\r
592 if (is(n, 'array') && is(n[0], 'string'))
\r
595 $(is(n, 'string') ? '#' + n : n).css(na, v);
\r
598 getStyle : function(n, na, c) {
\r
599 return $(is(n, 'string') ? '#' + n : n).css(na);
\r
602 setStyles : function(e, o) {
\r
603 if (is(e, 'array') && is(e[0], 'string'))
\r
605 $(is(e, 'string') ? '#' + e : e).css(o);
\r
608 setAttrib : function(e, n, v) {
\r
609 var t = this, s = t.settings;
\r
611 if (is(e, 'array') && is(e[0], 'string'))
\r
614 e = $(is(e, 'string') ? '#' + e : e);
\r
618 e.each(function(i, v){
\r
620 $(v).attr('_mce_style', v);
\r
622 v.style.cssText = v;
\r
628 this.className = v;
\r
634 e.each(function(i, v){
\r
635 if (s.keep_values) {
\r
636 if (s.url_converter)
\r
637 v = s.url_converter.call(s.url_converter_scope || t, v, n, v);
\r
639 t.setAttrib(v, '_mce_' + n, v);
\r
646 if (v !== null && v.length !== 0)
\r
652 setAttribs : function(e, o) {
\r
655 $.each(o, function(n, v){
\r
656 t.setAttrib(e,n,v);
\r
663 'tinymce.dom.Event' : {
\r
664 add : function (o, n, f, s) {
\r
668 e.target = e.target || this;
\r
669 f.call(s || this, e);
\r
672 if (is(o, 'array') && is(o[0], 'string'))
\r
674 o = $(is(o, 'string') ? '#' + o : o);
\r
685 lo = this._jqLookup || (this._jqLookup = []);
\r
686 lo.push({func : f, cfunc : cb});
\r
691 remove : function(o, n, f) {
\r
693 $(this._jqLookup).each(function() {
\r
694 if (this.func === f)
\r
698 if (is(o, 'array') && is(o[0], 'string'))
\r
701 $(is(o, 'string') ? '#' + o : o).unbind(n,f);
\r
709 // Patch functions after a class is created
\r
710 tinymce.onCreate = function(ty, c, p) {
\r
711 tinymce.extend(p, patches[c]);
\r
713 })(window.jQuery, tinymce);
\r
717 tinymce.create('tinymce.util.Dispatcher', {
\r
721 Dispatcher : function(s) {
\r
722 this.scope = s || this;
\r
723 this.listeners = [];
\r
726 add : function(cb, s) {
\r
727 this.listeners.push({cb : cb, scope : s || this.scope});
\r
732 addToTop : function(cb, s) {
\r
733 this.listeners.unshift({cb : cb, scope : s || this.scope});
\r
738 remove : function(cb) {
\r
739 var l = this.listeners, o = null;
\r
741 tinymce.each(l, function(c, i) {
\r
752 dispatch : function() {
\r
753 var s, a = arguments, i, li = this.listeners, c;
\r
755 // Needs to be a real loop since the listener count might change while looping
\r
756 // And this is also more efficient
\r
757 for (i = 0; i<li.length; i++) {
\r
759 s = c.cb.apply(c.scope, a);
\r
771 var each = tinymce.each;
\r
773 tinymce.create('tinymce.util.URI', {
\r
774 URI : function(u, s) {
\r
775 var t = this, o, a, b;
\r
778 u = tinymce.trim(u);
\r
780 // Default settings
\r
781 s = t.settings = s || {};
\r
783 // Strange app protocol or local anchor
\r
784 if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
\r
789 // Absolute path with no host, fake host and protocol
\r
790 if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
\r
791 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
\r
793 // Relative path http:// or protocol relative //path
\r
794 if (!/^\w*:?\/\//.test(u))
\r
795 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
\r
797 // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
\r
798 u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
\r
799 u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
\r
800 each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
\r
803 // Zope 3 workaround, they use @@something
\r
805 s = s.replace(/\(mce_at\)/g, '@@');
\r
810 if (b = s.base_uri) {
\r
812 t.protocol = b.protocol;
\r
815 t.userInfo = b.userInfo;
\r
817 if (!t.port && t.host == 'mce_host')
\r
820 if (!t.host || t.host == 'mce_host')
\r
826 //t.path = t.path || '/';
\r
829 setPath : function(p) {
\r
832 p = /^(.*?)\/?(\w+)?$/.exec(p);
\r
834 // Update path parts
\r
836 t.directory = p[1];
\r
844 toRelative : function(u) {
\r
850 u = new tinymce.util.URI(u, {base_uri : t});
\r
852 // Not on same domain/port or protocol
\r
853 if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
\r
856 o = t.toRelPath(t.path, u.path);
\r
860 o += '?' + u.query;
\r
864 o += '#' + u.anchor;
\r
869 toAbsolute : function(u, nh) {
\r
870 var u = new tinymce.util.URI(u, {base_uri : this});
\r
872 return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
\r
875 toRelPath : function(base, path) {
\r
876 var items, bp = 0, out = '', i, l;
\r
879 base = base.substring(0, base.lastIndexOf('/'));
\r
880 base = base.split('/');
\r
881 items = path.split('/');
\r
883 if (base.length >= items.length) {
\r
884 for (i = 0, l = base.length; i < l; i++) {
\r
885 if (i >= items.length || base[i] != items[i]) {
\r
892 if (base.length < items.length) {
\r
893 for (i = 0, l = items.length; i < l; i++) {
\r
894 if (i >= base.length || base[i] != items[i]) {
\r
904 for (i = 0, l = base.length - (bp - 1); i < l; i++)
\r
907 for (i = bp - 1, l = items.length; i < l; i++) {
\r
909 out += "/" + items[i];
\r
917 toAbsPath : function(base, path) {
\r
918 var i, nb = 0, o = [], tr, outPath;
\r
921 tr = /\/$/.test(path) ? '/' : '';
\r
922 base = base.split('/');
\r
923 path = path.split('/');
\r
925 // Remove empty chunks
\r
926 each(base, function(k) {
\r
933 // Merge relURLParts chunks
\r
934 for (i = path.length - 1, o = []; i >= 0; i--) {
\r
935 // Ignore empty or .
\r
936 if (path[i].length == 0 || path[i] == ".")
\r
940 if (path[i] == '..') {
\r
954 i = base.length - nb;
\r
958 outPath = o.reverse().join('/');
\r
960 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
\r
962 // Add front / if it's needed
\r
963 if (outPath.indexOf('/') !== 0)
\r
964 outPath = '/' + outPath;
\r
966 // Add traling / if it's needed
\r
967 if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
\r
973 getURI : function(nh) {
\r
977 if (!t.source || nh) {
\r
982 s += t.protocol + '://';
\r
985 s += t.userInfo + '@';
\r
998 s += '?' + t.query;
\r
1001 s += '#' + t.anchor;
\r
1012 var each = tinymce.each;
\r
1014 tinymce.create('static tinymce.util.Cookie', {
\r
1015 getHash : function(n) {
\r
1016 var v = this.get(n), h;
\r
1019 each(v.split('&'), function(v) {
\r
1022 h[unescape(v[0])] = unescape(v[1]);
\r
1029 setHash : function(n, v, e, p, d, s) {
\r
1032 each(v, function(v, k) {
\r
1033 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
\r
1036 this.set(n, o, e, p, d, s);
\r
1039 get : function(n) {
\r
1040 var c = document.cookie, e, p = n + "=", b;
\r
1046 b = c.indexOf("; " + p);
\r
1056 e = c.indexOf(";", b);
\r
1061 return unescape(c.substring(b + p.length, e));
\r
1064 set : function(n, v, e, p, d, s) {
\r
1065 document.cookie = n + "=" + escape(v) +
\r
1066 ((e) ? "; expires=" + e.toGMTString() : "") +
\r
1067 ((p) ? "; path=" + escape(p) : "") +
\r
1068 ((d) ? "; domain=" + d : "") +
\r
1069 ((s) ? "; secure" : "");
\r
1072 remove : function(n, p) {
\r
1073 var d = new Date();
\r
1075 d.setTime(d.getTime() - 1000);
\r
1077 this.set(n, '', d, p, d);
\r
1082 tinymce.create('static tinymce.util.JSON', {
\r
1083 serialize : function(o) {
\r
1084 var i, v, s = tinymce.util.JSON.serialize, t;
\r
1091 if (t == 'string') {
\r
1092 v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
\r
1094 return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
\r
1098 return '\\' + v.charAt(i + 1);
\r
1100 a = b.charCodeAt().toString(16);
\r
1102 return '\\u' + '0000'.substring(a.length) + a;
\r
1106 if (t == 'object') {
\r
1107 if (o.hasOwnProperty && o instanceof Array) {
\r
1108 for (i=0, v = '['; i<o.length; i++)
\r
1109 v += (i > 0 ? ',' : '') + s(o[i]);
\r
1117 v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
\r
1125 parse : function(s) {
\r
1127 return eval('(' + s + ')');
\r
1135 tinymce.create('static tinymce.util.XHR', {
\r
1136 send : function(o) {
\r
1137 var x, t, w = window, c = 0;
\r
1139 // Default settings
\r
1140 o.scope = o.scope || this;
\r
1141 o.success_scope = o.success_scope || o.scope;
\r
1142 o.error_scope = o.error_scope || o.scope;
\r
1143 o.async = o.async === false ? false : true;
\r
1144 o.data = o.data || '';
\r
1150 x = new ActiveXObject(s);
\r
1157 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
\r
1160 if (x.overrideMimeType)
\r
1161 x.overrideMimeType(o.content_type);
\r
1163 x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
\r
1165 if (o.content_type)
\r
1166 x.setRequestHeader('Content-Type', o.content_type);
\r
1168 x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
\r
1172 function ready() {
\r
1173 if (!o.async || x.readyState == 4 || c++ > 10000) {
\r
1174 if (o.success && c < 10000 && x.status == 200)
\r
1175 o.success.call(o.success_scope, '' + x.responseText, x, o);
\r
1177 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
\r
1181 w.setTimeout(ready, 10);
\r
1184 // Syncronous request
\r
1188 // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
\r
1189 t = w.setTimeout(ready, 10);
\r
1195 var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
\r
1197 tinymce.create('tinymce.util.JSONRequest', {
\r
1198 JSONRequest : function(s) {
\r
1199 this.settings = extend({
\r
1204 send : function(o) {
\r
1205 var ecb = o.error, scb = o.success;
\r
1207 o = extend(this.settings, o);
\r
1209 o.success = function(c, x) {
\r
1210 c = JSON.parse(c);
\r
1212 if (typeof(c) == 'undefined') {
\r
1214 error : 'JSON Parse error.'
\r
1219 ecb.call(o.error_scope || o.scope, c.error, x);
\r
1221 scb.call(o.success_scope || o.scope, c.result);
\r
1224 o.error = function(ty, x) {
\r
1225 ecb.call(o.error_scope || o.scope, ty, x);
\r
1228 o.data = JSON.serialize({
\r
1229 id : o.id || 'c' + (this.count++),
\r
1230 method : o.method,
\r
1234 // JSON content type for Ruby on rails. Bug: #1883287
\r
1235 o.content_type = 'application/json';
\r
1241 sendRPC : function(o) {
\r
1242 return new tinymce.util.JSONRequest().send(o);
\r
1247 (function(tinymce) {
\r
1249 var each = tinymce.each,
\r
1251 isWebKit = tinymce.isWebKit,
\r
1252 isIE = tinymce.isIE,
\r
1253 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
1254 boolAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'),
\r
1255 mceAttribs = makeMap('src,href,style,coords,shape'),
\r
1256 encodedChars = {'&' : '&', '"' : '"', '<' : '<', '>' : '>'},
\r
1257 encodeCharsRe = /[<>&\"]/g,
\r
1258 simpleSelectorRe = /^([a-z0-9],?)+$/i,
\r
1259 tagRegExp = /<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)(\s*\/?)>/g,
\r
1260 attrRegExp = /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
\r
1262 function makeMap(str) {
\r
1265 str = str.split(',');
\r
1266 for (i = str.length; i >= 0; i--)
\r
1272 tinymce.create('tinymce.dom.DOMUtils', {
\r
1276 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
\r
1278 "for" : "htmlFor",
\r
1279 "class" : "className",
\r
1280 className : "className",
\r
1281 checked : "checked",
\r
1282 disabled : "disabled",
\r
1283 maxlength : "maxLength",
\r
1284 readonly : "readOnly",
\r
1285 selected : "selected",
\r
1292 DOMUtils : function(d, s) {
\r
1293 var t = this, globalStyle;
\r
1298 t.cssFlicker = false;
\r
1300 t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat";
\r
1301 t.stdMode = d.documentMode === 8;
\r
1303 t.settings = s = tinymce.extend({
\r
1304 keep_values : false,
\r
1309 // Fix IE6SP2 flicker and check it failed for pre SP2
\r
1310 if (tinymce.isIE6) {
\r
1312 d.execCommand('BackgroundImageCache', false, true);
\r
1314 t.cssFlicker = true;
\r
1318 // Build styles list
\r
1319 if (s.valid_styles) {
\r
1322 // Convert styles into a rule list
\r
1323 each(s.valid_styles, function(value, key) {
\r
1324 t._styles[key] = tinymce.explode(value);
\r
1328 tinymce.addUnload(t.destroy, t);
\r
1331 getRoot : function() {
\r
1332 var t = this, s = t.settings;
\r
1334 return (s && t.get(s.root_element)) || t.doc.body;
\r
1337 getViewPort : function(w) {
\r
1340 w = !w ? this.win : w;
\r
1342 b = this.boxModel ? d.documentElement : d.body;
\r
1344 // Returns viewport size excluding scrollbars
\r
1346 x : w.pageXOffset || b.scrollLeft,
\r
1347 y : w.pageYOffset || b.scrollTop,
\r
1348 w : w.innerWidth || b.clientWidth,
\r
1349 h : w.innerHeight || b.clientHeight
\r
1353 getRect : function(e) {
\r
1354 var p, t = this, sr;
\r
1358 sr = t.getSize(e);
\r
1368 getSize : function(e) {
\r
1369 var t = this, w, h;
\r
1372 w = t.getStyle(e, 'width');
\r
1373 h = t.getStyle(e, 'height');
\r
1375 // Non pixel value, then force offset/clientWidth
\r
1376 if (w.indexOf('px') === -1)
\r
1379 // Non pixel value, then force offset/clientWidth
\r
1380 if (h.indexOf('px') === -1)
\r
1384 w : parseInt(w) || e.offsetWidth || e.clientWidth,
\r
1385 h : parseInt(h) || e.offsetHeight || e.clientHeight
\r
1389 getParent : function(n, f, r) {
\r
1390 return this.getParents(n, f, r, false);
\r
1393 getParents : function(n, f, r, c) {
\r
1394 var t = this, na, se = t.settings, o = [];
\r
1397 c = c === undefined;
\r
1399 if (se.strict_root)
\r
1400 r = r || t.getRoot();
\r
1402 // Wrap node name as func
\r
1403 if (is(f, 'string')) {
\r
1407 f = function(n) {return n.nodeType == 1;};
\r
1410 return t.is(n, na);
\r
1416 if (n == r || !n.nodeType || n.nodeType === 9)
\r
1429 return c ? o : null;
\r
1432 get : function(e) {
\r
1435 if (e && this.doc && typeof(e) == 'string') {
\r
1437 e = this.doc.getElementById(e);
\r
1439 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
\r
1440 if (e && e.id !== n)
\r
1441 return this.doc.getElementsByName(n)[1];
\r
1447 getNext : function(node, selector) {
\r
1448 return this._findSib(node, selector, 'nextSibling');
\r
1451 getPrev : function(node, selector) {
\r
1452 return this._findSib(node, selector, 'previousSibling');
\r
1456 add : function(p, n, a, h, c) {
\r
1459 return this.run(p, function(p) {
\r
1462 e = is(n, 'string') ? t.doc.createElement(n) : n;
\r
1463 t.setAttribs(e, a);
\r
1472 return !c ? p.appendChild(e) : e;
\r
1476 create : function(n, a, h) {
\r
1477 return this.add(this.doc.createElement(n), n, a, h, 1);
\r
1480 createHTML : function(n, a, h) {
\r
1481 var o = '', t = this, k;
\r
1486 if (a.hasOwnProperty(k))
\r
1487 o += ' ' + k + '="' + t.encode(a[k]) + '"';
\r
1490 if (tinymce.is(h))
\r
1491 return o + '>' + h + '</' + n + '>';
\r
1496 remove : function(node, keep_children) {
\r
1497 return this.run(node, function(node) {
\r
1498 var parent, child;
\r
1500 parent = node.parentNode;
\r
1505 if (keep_children) {
\r
1506 while (child = node.firstChild) {
\r
1507 // IE 8 will crash if you don't remove completely empty text nodes
\r
1508 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
\r
1509 parent.insertBefore(child, node);
\r
1511 node.removeChild(child);
\r
1515 return parent.removeChild(node);
\r
1519 setStyle : function(n, na, v) {
\r
1522 return t.run(n, function(e) {
\r
1527 // Camelcase it, if needed
\r
1528 na = na.replace(/-(\D)/g, function(a, b){
\r
1529 return b.toUpperCase();
\r
1532 // Default px suffix on these
\r
1533 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
\r
1538 // IE specific opacity
\r
1540 s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
\r
1542 if (!n.currentStyle || !n.currentStyle.hasLayout)
\r
1543 s.display = 'inline-block';
\r
1546 // Fix for older browsers
\r
1547 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
\r
1551 isIE ? s.styleFloat = v : s.cssFloat = v;
\r
1558 // Force update of the style data
\r
1559 if (t.settings.update_styles)
\r
1560 t.setAttrib(e, '_mce_style');
\r
1564 getStyle : function(n, na, c) {
\r
1571 if (this.doc.defaultView && c) {
\r
1572 // Remove camelcase
\r
1573 na = na.replace(/[A-Z]/g, function(a){
\r
1578 return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
\r
1580 // Old safari might fail
\r
1585 // Camelcase it, if needed
\r
1586 na = na.replace(/-(\D)/g, function(a, b){
\r
1587 return b.toUpperCase();
\r
1590 if (na == 'float')
\r
1591 na = isIE ? 'styleFloat' : 'cssFloat';
\r
1594 if (n.currentStyle && c)
\r
1595 return n.currentStyle[na];
\r
1597 return n.style[na];
\r
1600 setStyles : function(e, o) {
\r
1601 var t = this, s = t.settings, ol;
\r
1603 ol = s.update_styles;
\r
1604 s.update_styles = 0;
\r
1606 each(o, function(v, n) {
\r
1607 t.setStyle(e, n, v);
\r
1610 // Update style info
\r
1611 s.update_styles = ol;
\r
1612 if (s.update_styles)
\r
1613 t.setAttrib(e, s.cssText);
\r
1616 setAttrib : function(e, n, v) {
\r
1619 // Whats the point
\r
1623 // Strict XML mode
\r
1624 if (t.settings.strict)
\r
1625 n = n.toLowerCase();
\r
1627 return this.run(e, function(e) {
\r
1628 var s = t.settings;
\r
1632 if (!is(v, 'string')) {
\r
1633 each(v, function(v, n) {
\r
1634 t.setStyle(e, n, v);
\r
1640 // No mce_style for elements with these since they might get resized by the user
\r
1641 if (s.keep_values) {
\r
1642 if (v && !t._isRes(v))
\r
1643 e.setAttribute('_mce_style', v, 2);
\r
1645 e.removeAttribute('_mce_style', 2);
\r
1648 e.style.cssText = v;
\r
1652 e.className = v || ''; // Fix IE null bug
\r
1657 if (s.keep_values) {
\r
1658 if (s.url_converter)
\r
1659 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
\r
1661 t.setAttrib(e, '_mce_' + n, v, 2);
\r
1667 e.setAttribute('_mce_style', v);
\r
1671 if (is(v) && v !== null && v.length !== 0)
\r
1672 e.setAttribute(n, '' + v, 2);
\r
1674 e.removeAttribute(n, 2);
\r
1678 setAttribs : function(e, o) {
\r
1681 return this.run(e, function(e) {
\r
1682 each(o, function(v, n) {
\r
1683 t.setAttrib(e, n, v);
\r
1688 getAttrib : function(e, n, dv) {
\r
1693 if (!e || e.nodeType !== 1)
\r
1699 // Try the mce variant for these
\r
1700 if (/^(src|href|style|coords|shape)$/.test(n)) {
\r
1701 v = e.getAttribute("_mce_" + n);
\r
1707 if (isIE && t.props[n]) {
\r
1708 v = e[t.props[n]];
\r
1709 v = v && v.nodeValue ? v.nodeValue : v;
\r
1713 v = e.getAttribute(n, 2);
\r
1715 // Check boolean attribs
\r
1716 if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
\r
1717 if (e[t.props[n]] === true && v === '')
\r
1720 return v ? n : '';
\r
1723 // Inner input elements will override attributes on form elements
\r
1724 if (e.nodeName === "FORM" && e.getAttributeNode(n))
\r
1725 return e.getAttributeNode(n).nodeValue;
\r
1727 if (n === 'style') {
\r
1728 v = v || e.style.cssText;
\r
1731 v = t.serializeStyle(t.parseStyle(v), e.nodeName);
\r
1733 if (t.settings.keep_values && !t._isRes(v))
\r
1734 e.setAttribute('_mce_style', v);
\r
1738 // Remove Apple and WebKit stuff
\r
1739 if (isWebKit && n === "class" && v)
\r
1740 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
\r
1742 // Handle IE issues
\r
1747 // IE returns 1 as default value
\r
1754 // IE returns +0 as default value for size
\r
1755 if (v === '+0' || v === 20 || v === 0)
\r
1772 // IE returns -1 as default value
\r
1780 // IE returns default value
\r
1781 if (v === 32768 || v === 2147483647 || v === '32768')
\r
1796 v = v.toLowerCase();
\r
1800 // IE has odd anonymous function for event attributes
\r
1801 if (n.indexOf('on') === 0 && v)
\r
1802 v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
\r
1806 return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
\r
1809 getPos : function(n, ro) {
\r
1810 var t = this, x = 0, y = 0, e, d = t.doc, r;
\r
1813 ro = ro || d.body;
\r
1816 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
\r
1817 if (isIE && !t.stdMode) {
\r
1818 n = n.getBoundingClientRect();
\r
1819 e = t.boxModel ? d.documentElement : d.body;
\r
1820 x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
\r
1821 x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
\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
2050 e.removeAttribute('className');
\r
2056 return e.className;
\r
2060 hasClass : function(n, c) {
\r
2066 return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
\r
2069 show : function(e) {
\r
2070 return this.setStyle(e, 'display', 'block');
\r
2073 hide : function(e) {
\r
2074 return this.setStyle(e, 'display', 'none');
\r
2077 isHidden : function(e) {
\r
2080 return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
\r
2083 uniqueId : function(p) {
\r
2084 return (!p ? 'mce_' : p) + (this.counter++);
\r
2087 setHTML : function(e, h) {
\r
2090 return this.run(e, function(e) {
\r
2091 var x, i, nl, n, p, x;
\r
2093 h = t.processHTML(h);
\r
2097 // Remove all child nodes
\r
2098 while (e.firstChild)
\r
2099 e.firstChild.removeNode();
\r
2102 // IE will remove comments from the beginning
\r
2103 // unless you padd the contents with something
\r
2104 e.innerHTML = '<br />' + h;
\r
2105 e.removeChild(e.firstChild);
\r
2107 // 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
2108 // This seems to fix this problem
\r
2110 // Create new div with HTML contents and a BR infront to keep comments
\r
2111 x = t.create('div');
\r
2112 x.innerHTML = '<br />' + h;
\r
2114 // Add all children from div to target
\r
2115 each (x.childNodes, function(n, i) {
\r
2116 // Skip br element
\r
2123 // IE has a serious bug when it comes to paragraphs it can produce an invalid
\r
2124 // DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
\r
2125 // It seems to be that IE doesn't like a root block element placed inside another root block element
\r
2126 if (t.settings.fix_ie_paragraphs)
\r
2127 h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 _mce_keep="true"> </p>');
\r
2131 if (t.settings.fix_ie_paragraphs) {
\r
2132 // Check for odd paragraphs this is a sign of a broken DOM
\r
2133 nl = e.getElementsByTagName("p");
\r
2134 for (i = nl.length - 1, x = 0; i >= 0; i--) {
\r
2137 if (!n.hasChildNodes()) {
\r
2138 if (!n._mce_keep) {
\r
2139 x = 1; // Is broken
\r
2143 n.removeAttribute('_mce_keep');
\r
2148 // Time to fix the madness IE left us
\r
2150 // So if we replace the p elements with divs and mark them and then replace them back to paragraphs
\r
2151 // after we use innerHTML we can fix the DOM tree
\r
2152 h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 _mce_tmp="1">');
\r
2153 h = h.replace(/<\/p>/gi, '</div>');
\r
2155 // Set the new HTML with DIVs
\r
2158 // Replace all DIV elements with the _mce_tmp attibute back to paragraphs
\r
2159 // This is needed since IE has a annoying bug see above for details
\r
2160 // This is a slow process but it has to be done. :(
\r
2161 if (t.settings.fix_ie_paragraphs) {
\r
2162 nl = e.getElementsByTagName("DIV");
\r
2163 for (i = nl.length - 1; i >= 0; i--) {
\r
2166 // Is it a temp div
\r
2168 // Create new paragraph
\r
2169 p = t.doc.createElement('p');
\r
2171 // Copy all attributes
\r
2172 n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
\r
2175 if (b !== '_mce_tmp') {
\r
2176 v = n.getAttribute(b);
\r
2178 if (!v && b === 'class')
\r
2181 p.setAttribute(b, v);
\r
2185 // Append all children to new paragraph
\r
2186 for (x = 0; x<n.childNodes.length; x++)
\r
2187 p.appendChild(n.childNodes[x].cloneNode(true));
\r
2189 // Replace div with new paragraph
\r
2202 processHTML : function(h) {
\r
2203 var t = this, s = t.settings, codeBlocks = [];
\r
2205 if (!s.process_html)
\r
2209 h = h.replace(/'/g, '''); // IE can't handle apos
\r
2210 h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
\r
2213 // Fix some issues
\r
2214 h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
\r
2216 // Store away src and href in _mce_src and mce_href since browsers mess them up
\r
2217 if (s.keep_values) {
\r
2218 // Wrap scripts and styles in comments for serialization purposes
\r
2219 if (/<script|noscript|style/i.test(h)) {
\r
2220 function trim(s) {
\r
2221 // Remove prefix and suffix code for element
\r
2222 s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
\r
2223 s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
\r
2224 s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
\r
2225 s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
\r
2230 // Wrap the script contents in CDATA and keep them from executing
\r
2231 h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
\r
2232 // Force type attribute
\r
2234 attribs = ' type="text/javascript"';
\r
2236 // Convert the src attribute of the scripts
\r
2237 attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
\r
2238 if (s.url_converter)
\r
2239 url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
\r
2241 return '_mce_src="' + url + '"';
\r
2244 // Wrap text contents
\r
2245 if (tinymce.trim(text)) {
\r
2246 codeBlocks.push(trim(text));
\r
2247 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
\r
2250 return '<mce:script' + attribs + '>' + text + '</mce:script>';
\r
2253 // Wrap style elements
\r
2254 h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
\r
2255 // Wrap text contents
\r
2257 codeBlocks.push(trim(text));
\r
2258 text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
\r
2261 return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' _mce_bogus="1">' + text + '</style>';
\r
2264 // Wrap noscript elements
\r
2265 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
2266 return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '--') + '--></mce:noscript>';
\r
2270 h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
\r
2272 // This function processes the attributes in the HTML string to force boolean
\r
2273 // attributes to the attr="attr" format and convert style, src and href to _mce_ versions
\r
2274 function processTags(html) {
\r
2275 return html.replace(tagRegExp, function(match, elm_name, attrs, end) {
\r
2276 return '<' + elm_name + attrs.replace(attrRegExp, function(match, name, value, val2, val3) {
\r
2279 name = name.toLowerCase();
\r
2280 value = value || val2 || val3 || "";
\r
2282 // Treat boolean attributes
\r
2283 if (boolAttrs[name]) {
\r
2284 // false or 0 is treated as a missing attribute
\r
2285 if (value === 'false' || value === '0')
\r
2288 return name + '="' + name + '"';
\r
2291 // Is attribute one that needs special treatment
\r
2292 if (mceAttribs[name] && attrs.indexOf('_mce_' + name) == -1) {
\r
2293 mceValue = t.decode(value);
\r
2295 // Convert URLs to relative/absolute ones
\r
2296 if (s.url_converter && (name == "src" || name == "href"))
\r
2297 mceValue = s.url_converter.call(s.url_converter_scope || t, mceValue, name, elm_name);
\r
2299 // Process styles lowercases them and compresses them
\r
2300 if (name == 'style')
\r
2301 mceValue = t.serializeStyle(t.parseStyle(mceValue), name);
\r
2303 return name + '="' + value + '"' + ' _mce_' + name + '="' + t.encode(mceValue) + '"';
\r
2311 h = processTags(h);
\r
2313 // Restore script blocks
\r
2314 h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
\r
2315 return codeBlocks[idx];
\r
2322 getOuterHTML : function(e) {
\r
2330 if (e.outerHTML !== undefined)
\r
2331 return e.outerHTML;
\r
2333 d = (e.ownerDocument || this.doc).createElement("body");
\r
2334 d.appendChild(e.cloneNode(true));
\r
2336 return d.innerHTML;
\r
2339 setOuterHTML : function(e, h, d) {
\r
2342 function setHTML(e, h, d) {
\r
2345 tp = d.createElement("body");
\r
2350 t.insertAfter(n.cloneNode(true), e);
\r
2351 n = n.previousSibling;
\r
2357 return this.run(e, function(e) {
\r
2360 // Only set HTML on elements
\r
2361 if (e.nodeType == 1) {
\r
2362 d = d || e.ownerDocument || t.doc;
\r
2366 // Try outerHTML for IE it sometimes produces an unknown runtime error
\r
2367 if (isIE && e.nodeType == 1)
\r
2372 // Fix for unknown runtime error
\r
2381 decode : function(s) {
\r
2384 // Look for entities to decode
\r
2385 if (/&[\w#]+;/.test(s)) {
\r
2386 // Decode the entities using a div element not super efficient but less code
\r
2387 e = this.doc.createElement("div");
\r
2395 } while (n = n.nextSibling);
\r
2404 encode : function(str) {
\r
2405 return ('' + str).replace(encodeCharsRe, function(chr) {
\r
2406 return encodedChars[chr];
\r
2410 insertAfter : function(node, reference_node) {
\r
2411 reference_node = this.get(reference_node);
\r
2413 return this.run(node, function(node) {
\r
2414 var parent, nextSibling;
\r
2416 parent = reference_node.parentNode;
\r
2417 nextSibling = reference_node.nextSibling;
\r
2420 parent.insertBefore(node, nextSibling);
\r
2422 parent.appendChild(node);
\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 return o.parentNode.replaceChild(n, o);
\r
2454 rename : function(elm, name) {
\r
2455 var t = this, newElm;
\r
2457 if (elm.nodeName != name.toUpperCase()) {
\r
2458 // Rename block element
\r
2459 newElm = t.create(name);
\r
2461 // Copy attribs to new block
\r
2462 each(t.getAttribs(elm), function(attr_node) {
\r
2463 t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
\r
2467 t.replace(newElm, elm, 1);
\r
2470 return newElm || elm;
\r
2473 findCommonAncestor : function(a, b) {
\r
2479 while (pe && ps != pe)
\r
2480 pe = pe.parentNode;
\r
2485 ps = ps.parentNode;
\r
2488 if (!ps && a.ownerDocument)
\r
2489 return a.ownerDocument.documentElement;
\r
2494 toHex : function(s) {
\r
2495 var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
\r
2498 s = parseInt(s).toString(16);
\r
2500 return s.length > 1 ? s : '0' + s; // 0 -> 00
\r
2504 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
\r
2512 getClasses : function() {
\r
2513 var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
\r
2518 function addClasses(s) {
\r
2519 // IE style imports
\r
2520 each(s.imports, function(r) {
\r
2524 each(s.cssRules || s.rules, function(r) {
\r
2525 // Real type or fake it on IE
\r
2526 switch (r.type || 1) {
\r
2529 if (r.selectorText) {
\r
2530 each(r.selectorText.split(','), function(v) {
\r
2531 v = v.replace(/^\s*|\s*$|^\s\./g, "");
\r
2533 // Is internal or it doesn't contain a class
\r
2534 if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
\r
2537 // Remove everything but class name
\r
2539 v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
\r
2542 if (f && !(v = f(v, ov)))
\r
2546 cl.push({'class' : v});
\r
2555 addClasses(r.styleSheet);
\r
2562 each(t.doc.styleSheets, addClasses);
\r
2567 if (cl.length > 0)
\r
2573 run : function(e, f, s) {
\r
2576 if (t.doc && typeof(e) === 'string')
\r
2583 if (!e.nodeType && (e.length || e.length === 0)) {
\r
2586 each(e, function(e, i) {
\r
2588 if (typeof(e) == 'string')
\r
2589 e = t.doc.getElementById(e);
\r
2591 o.push(f.call(s, e, i));
\r
2598 return f.call(s, e);
\r
2601 getAttribs : function(n) {
\r
2612 // Object will throw exception in IE
\r
2613 if (n.nodeName == 'OBJECT')
\r
2614 return n.attributes;
\r
2616 // IE doesn't keep the selected attribute if you clone option elements
\r
2617 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
\r
2618 o.push({specified : 1, nodeName : 'selected'});
\r
2620 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
\r
2621 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
\r
2622 o.push({specified : 1, nodeName : a});
\r
2628 return n.attributes;
\r
2631 destroy : function(s) {
\r
2635 t.events.destroy();
\r
2637 t.win = t.doc = t.root = t.events = null;
\r
2639 // Manual destroy then remove unload handler
\r
2641 tinymce.removeUnload(t.destroy);
\r
2644 createRng : function() {
\r
2647 return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
\r
2650 nodeIndex : function(node, normalized) {
\r
2651 var idx = 0, lastNodeType, lastNode, nodeType;
\r
2654 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
\r
2655 nodeType = node.nodeType;
\r
2657 // Normalize text nodes
\r
2658 if (normalized && nodeType == 3) {
\r
2659 if (nodeType == lastNodeType || !node.nodeValue.length)
\r
2664 lastNodeType = nodeType;
\r
2671 split : function(pe, e, re) {
\r
2672 var t = this, r = t.createRng(), bef, aft, pa;
\r
2674 // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
\r
2675 // but we don't want that in our code since it serves no purpose for the end user
\r
2676 // For example if this is chopped:
\r
2677 // <p>text 1<span><b>CHOP</b></span>text 2</p>
\r
2679 // <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
\r
2680 // this function will then trim of empty edges and produce:
\r
2681 // <p>text 1</p><b>CHOP</b><p>text 2</p>
\r
2682 function trim(node) {
\r
2683 var i, children = node.childNodes;
\r
2685 if (node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark')
\r
2688 for (i = children.length - 1; i >= 0; i--)
\r
2689 trim(children[i]);
\r
2691 if (node.nodeType != 9) {
\r
2692 // Keep non whitespace text nodes
\r
2693 if (node.nodeType == 3 && node.nodeValue.length > 0)
\r
2696 if (node.nodeType == 1) {
\r
2697 // If the only child is a bookmark then move it up
\r
2698 children = node.childNodes;
\r
2699 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('_mce_type') == 'bookmark')
\r
2700 node.parentNode.insertBefore(children[0], node);
\r
2702 // Keep non empty elements or img, hr etc
\r
2703 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
\r
2714 // Get before chunk
\r
2715 r.setStart(pe.parentNode, t.nodeIndex(pe));
\r
2716 r.setEnd(e.parentNode, t.nodeIndex(e));
\r
2717 bef = r.extractContents();
\r
2719 // Get after chunk
\r
2720 r = t.createRng();
\r
2721 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
\r
2722 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
\r
2723 aft = r.extractContents();
\r
2725 // Insert before chunk
\r
2726 pa = pe.parentNode;
\r
2727 pa.insertBefore(trim(bef), pe);
\r
2729 // Insert middle chunk
\r
2731 pa.replaceChild(re, e);
\r
2733 pa.insertBefore(e, pe);
\r
2735 // Insert after chunk
\r
2736 pa.insertBefore(trim(aft), pe);
\r
2743 bind : function(target, name, func, scope) {
\r
2747 t.events = new tinymce.dom.EventUtils();
\r
2749 return t.events.add(target, name, func, scope || this);
\r
2752 unbind : function(target, name, func) {
\r
2756 t.events = new tinymce.dom.EventUtils();
\r
2758 return t.events.remove(target, name, func);
\r
2762 _findSib : function(node, selector, name) {
\r
2763 var t = this, f = selector;
\r
2766 // If expression make a function of it using is
\r
2767 if (is(f, 'string')) {
\r
2768 f = function(node) {
\r
2769 return t.is(node, selector);
\r
2773 // Loop all siblings
\r
2774 for (node = node[name]; node; node = node[name]) {
\r
2783 _isRes : function(c) {
\r
2784 // Is live resizble element
\r
2785 return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
\r
2789 walk : function(n, f, s) {
\r
2790 var d = this.doc, w;
\r
2792 if (d.createTreeWalker) {
\r
2793 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
\r
2795 while ((n = w.nextNode()) != null)
\r
2796 f.call(s || this, n);
\r
2798 tinymce.walk(n, f, 'childNodes', s);
\r
2803 toRGB : function(s) {
\r
2804 var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
\r
2807 // #FFF -> #FFFFFF
\r
2809 c[3] = c[2] = c[1];
\r
2811 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
\r
2819 tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
\r
2823 // Range constructor
\r
2824 function Range(dom) {
\r
2832 START_OFFSET = 'startOffset',
\r
2833 START_CONTAINER = 'startContainer',
\r
2834 END_CONTAINER = 'endContainer',
\r
2835 END_OFFSET = 'endOffset',
\r
2836 extend = tinymce.extend,
\r
2837 nodeIndex = dom.nodeIndex;
\r
2841 startContainer : doc,
\r
2843 endContainer : doc,
\r
2846 commonAncestorContainer : doc,
\r
2848 // Range constants
\r
2849 START_TO_START : 0,
\r
2855 setStart : setStart,
\r
2857 setStartBefore : setStartBefore,
\r
2858 setStartAfter : setStartAfter,
\r
2859 setEndBefore : setEndBefore,
\r
2860 setEndAfter : setEndAfter,
\r
2861 collapse : collapse,
\r
2862 selectNode : selectNode,
\r
2863 selectNodeContents : selectNodeContents,
\r
2864 compareBoundaryPoints : compareBoundaryPoints,
\r
2865 deleteContents : deleteContents,
\r
2866 extractContents : extractContents,
\r
2867 cloneContents : cloneContents,
\r
2868 insertNode : insertNode,
\r
2869 surroundContents : surroundContents,
\r
2870 cloneRange : cloneRange
\r
2873 function setStart(n, o) {
\r
2874 _setEndPoint(TRUE, n, o);
\r
2877 function setEnd(n, o) {
\r
2878 _setEndPoint(FALSE, n, o);
\r
2881 function setStartBefore(n) {
\r
2882 setStart(n.parentNode, nodeIndex(n));
\r
2885 function setStartAfter(n) {
\r
2886 setStart(n.parentNode, nodeIndex(n) + 1);
\r
2889 function setEndBefore(n) {
\r
2890 setEnd(n.parentNode, nodeIndex(n));
\r
2893 function setEndAfter(n) {
\r
2894 setEnd(n.parentNode, nodeIndex(n) + 1);
\r
2897 function collapse(ts) {
\r
2899 t[END_CONTAINER] = t[START_CONTAINER];
\r
2900 t[END_OFFSET] = t[START_OFFSET];
\r
2902 t[START_CONTAINER] = t[END_CONTAINER];
\r
2903 t[START_OFFSET] = t[END_OFFSET];
\r
2906 t.collapsed = TRUE;
\r
2909 function selectNode(n) {
\r
2910 setStartBefore(n);
\r
2914 function selectNodeContents(n) {
\r
2916 setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
\r
2919 function compareBoundaryPoints(h, r) {
\r
2920 var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET];
\r
2922 // Check START_TO_START
\r
2924 return _compareBoundaryPoints(sc, so, sc, so);
\r
2926 // Check START_TO_END
\r
2928 return _compareBoundaryPoints(sc, so, ec, eo);
\r
2930 // Check END_TO_END
\r
2932 return _compareBoundaryPoints(ec, eo, ec, eo);
\r
2934 // Check END_TO_START
\r
2936 return _compareBoundaryPoints(ec, eo, sc, so);
\r
2939 function deleteContents() {
\r
2940 _traverse(DELETE);
\r
2943 function extractContents() {
\r
2944 return _traverse(EXTRACT);
\r
2947 function cloneContents() {
\r
2948 return _traverse(CLONE);
\r
2951 function insertNode(n) {
\r
2952 var startContainer = this[START_CONTAINER],
\r
2953 startOffset = this[START_OFFSET], nn, o;
\r
2955 // Node is TEXT_NODE or CDATA
\r
2956 if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
\r
2957 if (!startOffset) {
\r
2958 // At the start of text
\r
2959 startContainer.parentNode.insertBefore(n, startContainer);
\r
2960 } else if (startOffset >= startContainer.nodeValue.length) {
\r
2961 // At the end of text
\r
2962 dom.insertAfter(n, startContainer);
\r
2964 // Middle, need to split
\r
2965 nn = startContainer.splitText(startOffset);
\r
2966 startContainer.parentNode.insertBefore(n, nn);
\r
2969 // Insert element node
\r
2970 if (startContainer.childNodes.length > 0)
\r
2971 o = startContainer.childNodes[startOffset];
\r
2974 startContainer.insertBefore(n, o);
\r
2976 startContainer.appendChild(n);
\r
2980 function surroundContents(n) {
\r
2981 var f = t.extractContents();
\r
2988 function cloneRange() {
\r
2989 return extend(new Range(dom), {
\r
2990 startContainer : t[START_CONTAINER],
\r
2991 startOffset : t[START_OFFSET],
\r
2992 endContainer : t[END_CONTAINER],
\r
2993 endOffset : t[END_OFFSET],
\r
2994 collapsed : t.collapsed,
\r
2995 commonAncestorContainer : t.commonAncestorContainer
\r
2999 // Private methods
\r
3001 function _getSelectedNode(container, offset) {
\r
3004 if (container.nodeType == 3 /* TEXT_NODE */)
\r
3010 child = container.firstChild;
\r
3011 while (child && offset > 0) {
\r
3013 child = child.nextSibling;
\r
3022 function _isCollapsed() {
\r
3023 return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
\r
3026 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
\r
3027 var c, offsetC, n, cmnRoot, childA, childB;
\r
3029 // In the first case the boundary-points have the same container. A is before B
\r
3030 // if its offset is less than the offset of B, A is equal to B if its offset is
\r
3031 // equal to the offset of B, and A is after B if its offset is greater than the
\r
3033 if (containerA == containerB) {
\r
3034 if (offsetA == offsetB)
\r
3035 return 0; // equal
\r
3037 if (offsetA < offsetB)
\r
3038 return -1; // before
\r
3040 return 1; // after
\r
3043 // In the second case a child node C of the container of A is an ancestor
\r
3044 // container of B. In this case, A is before B if the offset of A is less than or
\r
3045 // equal to the index of the child node C and A is after B otherwise.
\r
3047 while (c && c.parentNode != containerA)
\r
3052 n = containerA.firstChild;
\r
3054 while (n != c && offsetC < offsetA) {
\r
3056 n = n.nextSibling;
\r
3059 if (offsetA <= offsetC)
\r
3060 return -1; // before
\r
3062 return 1; // after
\r
3065 // In the third case a child node C of the container of B is an ancestor container
\r
3066 // of A. In this case, A is before B if the index of the child node C is less than
\r
3067 // the offset of B and A is after B otherwise.
\r
3069 while (c && c.parentNode != containerB) {
\r
3075 n = containerB.firstChild;
\r
3077 while (n != c && offsetC < offsetB) {
\r
3079 n = n.nextSibling;
\r
3082 if (offsetC < offsetB)
\r
3083 return -1; // before
\r
3085 return 1; // after
\r
3088 // In the fourth case, none of three other cases hold: the containers of A and B
\r
3089 // are siblings or descendants of sibling nodes. In this case, A is before B if
\r
3090 // the container of A is before the container of B in a pre-order traversal of the
\r
3091 // Ranges' context tree and A is after B otherwise.
\r
3092 cmnRoot = dom.findCommonAncestor(containerA, containerB);
\r
3093 childA = containerA;
\r
3095 while (childA && childA.parentNode != cmnRoot)
\r
3096 childA = childA.parentNode;
\r
3101 childB = containerB;
\r
3102 while (childB && childB.parentNode != cmnRoot)
\r
3103 childB = childB.parentNode;
\r
3108 if (childA == childB)
\r
3109 return 0; // equal
\r
3111 n = cmnRoot.firstChild;
\r
3114 return -1; // before
\r
3117 return 1; // after
\r
3119 n = n.nextSibling;
\r
3123 function _setEndPoint(st, n, o) {
\r
3127 t[START_CONTAINER] = n;
\r
3128 t[START_OFFSET] = o;
\r
3130 t[END_CONTAINER] = n;
\r
3131 t[END_OFFSET] = o;
\r
3134 // If one boundary-point of a Range is set to have a root container
\r
3135 // other than the current one for the Range, the Range is collapsed to
\r
3136 // the new position. This enforces the restriction that both boundary-
\r
3137 // points of a Range must have the same root container.
\r
3138 ec = t[END_CONTAINER];
\r
3139 while (ec.parentNode)
\r
3140 ec = ec.parentNode;
\r
3142 sc = t[START_CONTAINER];
\r
3143 while (sc.parentNode)
\r
3144 sc = sc.parentNode;
\r
3147 // The start position of a Range is guaranteed to never be after the
\r
3148 // end position. To enforce this restriction, if the start is set to
\r
3149 // be at a position after the end, the Range is collapsed to that
\r
3151 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
\r
3156 t.collapsed = _isCollapsed();
\r
3157 t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
\r
3160 function _traverse(how) {
\r
3161 var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
\r
3163 if (t[START_CONTAINER] == t[END_CONTAINER])
\r
3164 return _traverseSameContainer(how);
\r
3166 for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
3167 if (p == t[START_CONTAINER])
\r
3168 return _traverseCommonStartContainer(c, how);
\r
3170 ++endContainerDepth;
\r
3173 for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
\r
3174 if (p == t[END_CONTAINER])
\r
3175 return _traverseCommonEndContainer(c, how);
\r
3177 ++startContainerDepth;
\r
3180 depthDiff = startContainerDepth - endContainerDepth;
\r
3182 startNode = t[START_CONTAINER];
\r
3183 while (depthDiff > 0) {
\r
3184 startNode = startNode.parentNode;
\r
3188 endNode = t[END_CONTAINER];
\r
3189 while (depthDiff < 0) {
\r
3190 endNode = endNode.parentNode;
\r
3194 // ascend the ancestor hierarchy until we have a common parent.
\r
3195 for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
\r
3200 return _traverseCommonAncestors(startNode, endNode, how);
\r
3203 function _traverseSameContainer(how) {
\r
3204 var frag, s, sub, n, cnt, sibling, xferNode;
\r
3206 if (how != DELETE)
\r
3207 frag = doc.createDocumentFragment();
\r
3209 // If selection is empty, just return the fragment
\r
3210 if (t[START_OFFSET] == t[END_OFFSET])
\r
3213 // Text node needs special case handling
\r
3214 if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
\r
3215 // get the substring
\r
3216 s = t[START_CONTAINER].nodeValue;
\r
3217 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
\r
3219 // set the original text node to its new value
\r
3220 if (how != CLONE) {
\r
3221 t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
\r
3223 // Nothing is partially selected, so collapse to start point
\r
3227 if (how == DELETE)
\r
3230 frag.appendChild(doc.createTextNode(sub));
\r
3234 // Copy nodes between the start/end offsets.
\r
3235 n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
\r
3236 cnt = t[END_OFFSET] - t[START_OFFSET];
\r
3239 sibling = n.nextSibling;
\r
3240 xferNode = _traverseFullySelected(n, how);
\r
3243 frag.appendChild( xferNode );
\r
3249 // Nothing is partially selected, so collapse to start point
\r
3256 function _traverseCommonStartContainer(endAncestor, how) {
\r
3257 var frag, n, endIdx, cnt, sibling, xferNode;
\r
3259 if (how != DELETE)
\r
3260 frag = doc.createDocumentFragment();
\r
3262 n = _traverseRightBoundary(endAncestor, how);
\r
3265 frag.appendChild(n);
\r
3267 endIdx = nodeIndex(endAncestor);
\r
3268 cnt = endIdx - t[START_OFFSET];
\r
3271 // Collapse to just before the endAncestor, which
\r
3272 // is partially selected.
\r
3273 if (how != CLONE) {
\r
3274 t.setEndBefore(endAncestor);
\r
3275 t.collapse(FALSE);
\r
3281 n = endAncestor.previousSibling;
\r
3283 sibling = n.previousSibling;
\r
3284 xferNode = _traverseFullySelected(n, how);
\r
3287 frag.insertBefore(xferNode, frag.firstChild);
\r
3293 // Collapse to just before the endAncestor, which
\r
3294 // is partially selected.
\r
3295 if (how != CLONE) {
\r
3296 t.setEndBefore(endAncestor);
\r
3297 t.collapse(FALSE);
\r
3303 function _traverseCommonEndContainer(startAncestor, how) {
\r
3304 var frag, startIdx, n, cnt, sibling, xferNode;
\r
3306 if (how != DELETE)
\r
3307 frag = doc.createDocumentFragment();
\r
3309 n = _traverseLeftBoundary(startAncestor, how);
\r
3311 frag.appendChild(n);
\r
3313 startIdx = nodeIndex(startAncestor);
\r
3314 ++startIdx; // Because we already traversed it....
\r
3316 cnt = t[END_OFFSET] - startIdx;
\r
3317 n = startAncestor.nextSibling;
\r
3319 sibling = n.nextSibling;
\r
3320 xferNode = _traverseFullySelected(n, how);
\r
3323 frag.appendChild(xferNode);
\r
3329 if (how != CLONE) {
\r
3330 t.setStartAfter(startAncestor);
\r
3337 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
\r
3338 var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
\r
3340 if (how != DELETE)
\r
3341 frag = doc.createDocumentFragment();
\r
3343 n = _traverseLeftBoundary(startAncestor, how);
\r
3345 frag.appendChild(n);
\r
3347 commonParent = startAncestor.parentNode;
\r
3348 startOffset = nodeIndex(startAncestor);
\r
3349 endOffset = nodeIndex(endAncestor);
\r
3352 cnt = endOffset - startOffset;
\r
3353 sibling = startAncestor.nextSibling;
\r
3356 nextSibling = sibling.nextSibling;
\r
3357 n = _traverseFullySelected(sibling, how);
\r
3360 frag.appendChild(n);
\r
3362 sibling = nextSibling;
\r
3366 n = _traverseRightBoundary(endAncestor, how);
\r
3369 frag.appendChild(n);
\r
3371 if (how != CLONE) {
\r
3372 t.setStartAfter(startAncestor);
\r
3379 function _traverseRightBoundary(root, how) {
\r
3380 var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
\r
3383 return _traverseNode(next, isFullySelected, FALSE, how);
\r
3385 parent = next.parentNode;
\r
3386 clonedParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3390 prevSibling = next.previousSibling;
\r
3391 clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
\r
3393 if (how != DELETE)
\r
3394 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
\r
3396 isFullySelected = TRUE;
\r
3397 next = prevSibling;
\r
3400 if (parent == root)
\r
3401 return clonedParent;
\r
3403 next = parent.previousSibling;
\r
3404 parent = parent.parentNode;
\r
3406 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
\r
3408 if (how != DELETE)
\r
3409 clonedGrandParent.appendChild(clonedParent);
\r
3411 clonedParent = clonedGrandParent;
\r
3415 function _traverseLeftBoundary(root, how) {
\r
3416 var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
\r
3419 return _traverseNode(next, isFullySelected, TRUE, how);
\r
3421 parent = next.parentNode;
\r
3422 clonedParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3426 nextSibling = next.nextSibling;
\r
3427 clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
\r
3429 if (how != DELETE)
\r
3430 clonedParent.appendChild(clonedChild);
\r
3432 isFullySelected = TRUE;
\r
3433 next = nextSibling;
\r
3436 if (parent == root)
\r
3437 return clonedParent;
\r
3439 next = parent.nextSibling;
\r
3440 parent = parent.parentNode;
\r
3442 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
\r
3444 if (how != DELETE)
\r
3445 clonedGrandParent.appendChild(clonedParent);
\r
3447 clonedParent = clonedGrandParent;
\r
3451 function _traverseNode(n, isFullySelected, isLeft, how) {
\r
3452 var txtValue, newNodeValue, oldNodeValue, offset, newNode;
\r
3454 if (isFullySelected)
\r
3455 return _traverseFullySelected(n, how);
\r
3457 if (n.nodeType == 3 /* TEXT_NODE */) {
\r
3458 txtValue = n.nodeValue;
\r
3461 offset = t[START_OFFSET];
\r
3462 newNodeValue = txtValue.substring(offset);
\r
3463 oldNodeValue = txtValue.substring(0, offset);
\r
3465 offset = t[END_OFFSET];
\r
3466 newNodeValue = txtValue.substring(0, offset);
\r
3467 oldNodeValue = txtValue.substring(offset);
\r
3471 n.nodeValue = oldNodeValue;
\r
3473 if (how == DELETE)
\r
3476 newNode = n.cloneNode(FALSE);
\r
3477 newNode.nodeValue = newNodeValue;
\r
3482 if (how == DELETE)
\r
3485 return n.cloneNode(FALSE);
\r
3488 function _traverseFullySelected(n, how) {
\r
3489 if (how != DELETE)
\r
3490 return how == CLONE ? n.cloneNode(TRUE) : n;
\r
3492 n.parentNode.removeChild(n);
\r
3500 function Selection(selection) {
\r
3501 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
\r
3503 // Returns a W3C DOM compatible range object by using the IE Range API
\r
3504 function getRange() {
\r
3505 var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
\r
3507 // If selection is outside the current document just return an empty range
\r
3508 element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
\r
3509 if (element.ownerDocument != dom.doc)
\r
3512 // Handle control selection or text selection of a image
\r
3513 if (ieRange.item || !element.hasChildNodes()) {
\r
3514 domRange.setStart(element.parentNode, dom.nodeIndex(element));
\r
3515 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
\r
3520 collapsed = selection.isCollapsed();
\r
3522 function findEndPoint(start) {
\r
3523 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
\r
3525 // Setup temp range and collapse it
\r
3526 checkRng = ieRange.duplicate();
\r
3527 checkRng.collapse(start);
\r
3529 // Create marker and insert it at the end of the endpoints parent
\r
3530 marker = dom.create('a');
\r
3531 parent = checkRng.parentElement();
\r
3533 // If parent doesn't have any children then set the container to that parent and the index to 0
\r
3534 if (!parent.hasChildNodes()) {
\r
3535 domRange[start ? 'setStart' : 'setEnd'](parent, 0);
\r
3539 parent.appendChild(marker);
\r
3540 checkRng.moveToElementText(marker);
\r
3541 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
\r
3542 if (position > 0) {
\r
3543 // The position is after the end of the parent element.
\r
3544 // This is the case where IE puts the caret to the left edge of a table.
\r
3545 domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
\r
3546 dom.remove(marker);
\r
3550 // Setup node list and endIndex
\r
3551 nodes = tinymce.grep(parent.childNodes);
\r
3552 endIndex = nodes.length - 1;
\r
3553 // Perform a binary search for the position
\r
3554 while (startIndex <= endIndex) {
\r
3555 index = Math.floor((startIndex + endIndex) / 2);
\r
3557 // Insert marker and check it's position relative to the selection
\r
3558 parent.insertBefore(marker, nodes[index]);
\r
3559 checkRng.moveToElementText(marker);
\r
3560 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
\r
3561 if (position > 0) {
\r
3562 // Marker is to the right
\r
3563 startIndex = index + 1;
\r
3564 } else if (position < 0) {
\r
3565 // Marker is to the left
\r
3566 endIndex = index - 1;
\r
3568 // Maker is where we are
\r
3574 // Setup container
\r
3575 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
\r
3577 // Handle element selection
\r
3578 if (container.nodeType == 1) {
\r
3579 dom.remove(marker);
\r
3581 // Find offset and container
\r
3582 offset = dom.nodeIndex(container);
\r
3583 container = container.parentNode;
\r
3585 // Move the offset if we are setting the end or the position is after an element
\r
3586 if (!start || index > 0)
\r
3589 // Calculate offset within text node
\r
3590 if (position > 0 || index == 0) {
\r
3591 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
\r
3592 offset = checkRng.text.length;
\r
3594 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
\r
3595 offset = container.nodeValue.length - checkRng.text.length;
\r
3598 dom.remove(marker);
\r
3601 domRange[start ? 'setStart' : 'setEnd'](container, offset);
\r
3604 // Find start point
\r
3605 findEndPoint(true);
\r
3607 // Find end point if needed
\r
3614 this.addRange = function(rng) {
\r
3615 var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
\r
3617 function setEndPoint(start) {
\r
3618 var container, offset, marker, tmpRng, nodes;
\r
3620 marker = dom.create('a');
\r
3621 container = start ? startContainer : endContainer;
\r
3622 offset = start ? startOffset : endOffset;
\r
3623 tmpRng = ieRng.duplicate();
\r
3625 if (container == doc) {
\r
3630 if (container.nodeType == 3) {
\r
3631 container.parentNode.insertBefore(marker, container);
\r
3632 tmpRng.moveToElementText(marker);
\r
3633 tmpRng.moveStart('character', offset);
\r
3634 dom.remove(marker);
\r
3635 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
\r
3637 nodes = container.childNodes;
\r
3639 if (nodes.length) {
\r
3640 if (offset >= nodes.length) {
\r
3641 dom.insertAfter(marker, nodes[nodes.length - 1]);
\r
3643 container.insertBefore(marker, nodes[offset]);
\r
3646 tmpRng.moveToElementText(marker);
\r
3648 // Empty node selection for example <div>|</div>
\r
3649 marker = doc.createTextNode(invisibleChar);
\r
3650 container.appendChild(marker);
\r
3651 tmpRng.moveToElementText(marker.parentNode);
\r
3652 tmpRng.collapse(TRUE);
\r
3655 ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
\r
3656 dom.remove(marker);
\r
3660 // Destroy cached range
\r
3663 // Setup some shorter versions
\r
3664 startContainer = rng.startContainer;
\r
3665 startOffset = rng.startOffset;
\r
3666 endContainer = rng.endContainer;
\r
3667 endOffset = rng.endOffset;
\r
3668 ieRng = body.createTextRange();
\r
3670 // If single element selection then try making a control selection out of it
\r
3671 if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
\r
3672 if (startOffset == endOffset - 1) {
\r
3674 ctrlRng = body.createControlRange();
\r
3675 ctrlRng.addElement(startContainer.childNodes[startOffset]);
\r
3677 ctrlRng.scrollIntoView();
\r
3685 // Set start/end point of selection
\r
3686 setEndPoint(true);
\r
3689 // Select the new range and scroll it into view
\r
3691 ieRng.scrollIntoView();
\r
3694 this.getRangeAt = function() {
\r
3695 // Setup new range if the cache is empty
\r
3696 if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
\r
3697 range = getRange();
\r
3699 // Store away text range for next call
\r
3700 lastIERng = selection.getRng();
\r
3703 // IE will say that the range is equal then produce an invalid argument exception
\r
3704 // if you perform specific operations in a keyup event. For example Ctrl+Del.
\r
3705 // This hack will invalidate the range cache if the exception occurs
\r
3707 range.startContainer.nextSibling;
\r
3709 range = getRange();
\r
3713 // Return cached range
\r
3717 this.destroy = function() {
\r
3718 // Destroy cached range and last IE range to avoid memory leaks
\r
3719 lastIERng = range = null;
\r
3722 // 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
3723 if (selection.dom.boxModel) {
\r
3725 var doc = dom.doc, body = doc.body, started, startRng;
\r
3727 // Make HTML element unselectable since we are going to handle selection by hand
\r
3728 doc.documentElement.unselectable = TRUE;
\r
3730 // Return range from point or null if it failed
\r
3731 function rngFromPoint(x, y) {
\r
3732 var rng = body.createTextRange();
\r
3735 rng.moveToPoint(x, y);
\r
3737 // IE sometimes throws and exception, so lets just ignore it
\r
3744 // Fires while the selection is changing
\r
3745 function selectionChange(e) {
\r
3748 // Check if the button is down or not
\r
3750 // Create range from mouse position
\r
3751 pointRng = rngFromPoint(e.x, e.y);
\r
3754 // Check if pointRange is before/after selection then change the endPoint
\r
3755 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
\r
3756 pointRng.setEndPoint('StartToStart', startRng);
\r
3758 pointRng.setEndPoint('EndToEnd', startRng);
\r
3760 pointRng.select();
\r
3766 // Removes listeners
\r
3767 function endSelection() {
\r
3768 dom.unbind(doc, 'mouseup', endSelection);
\r
3769 dom.unbind(doc, 'mousemove', selectionChange);
\r
3773 // Detect when user selects outside BODY
\r
3774 dom.bind(doc, 'mousedown', function(e) {
\r
3775 if (e.target.nodeName === 'HTML') {
\r
3781 // Setup start position
\r
3782 startRng = rngFromPoint(e.x, e.y);
\r
3784 // Listen for selection change events
\r
3785 dom.bind(doc, 'mouseup', endSelection);
\r
3786 dom.bind(doc, 'mousemove', selectionChange);
\r
3788 startRng.select();
\r
3796 // Expose the selection object
\r
3797 tinymce.dom.TridentSelection = Selection;
\r
3801 (function(tinymce) {
\r
3803 var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
\r
3805 tinymce.create('tinymce.dom.EventUtils', {
\r
3806 EventUtils : function() {
\r
3811 add : function(o, n, f, s) {
\r
3812 var cb, t = this, el = t.events, r;
\r
3814 if (n instanceof Array) {
\r
3817 each(n, function(n) {
\r
3818 r.push(t.add(o, n, f, s));
\r
3825 if (o && o.hasOwnProperty && o instanceof Array) {
\r
3828 each(o, function(o) {
\r
3830 r.push(t.add(o, n, f, s));
\r
3841 // Setup event callback
\r
3842 cb = function(e) {
\r
3843 // Is all events disabled
\r
3847 e = e || window.event;
\r
3849 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
\r
3852 e.target = e.srcElement;
\r
3854 // Patch in preventDefault, stopPropagation methods for W3C compatibility
\r
3855 tinymce.extend(e, t._stoppers);
\r
3861 return f.call(s, e);
\r
3864 if (n == 'unload') {
\r
3865 tinymce.unloads.unshift({func : cb});
\r
3869 if (n == 'init') {
\r
3878 // Store away listener reference
\r
3892 remove : function(o, n, f) {
\r
3893 var t = this, a = t.events, s = false, r;
\r
3896 if (o && o.hasOwnProperty && o instanceof Array) {
\r
3899 each(o, function(o) {
\r
3901 r.push(t.remove(o, n, f));
\r
3909 each(a, function(e, i) {
\r
3910 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
\r
3912 t._remove(o, n, e.cfunc);
\r
3921 clear : function(o) {
\r
3922 var t = this, a = t.events, i, e;
\r
3927 for (i = a.length - 1; i >= 0; i--) {
\r
3930 if (e.obj === o) {
\r
3931 t._remove(e.obj, e.name, e.cfunc);
\r
3932 e.obj = e.cfunc = null;
\r
3939 cancel : function(e) {
\r
3945 return this.prevent(e);
\r
3948 stop : function(e) {
\r
3949 if (e.stopPropagation)
\r
3950 e.stopPropagation();
\r
3952 e.cancelBubble = true;
\r
3957 prevent : function(e) {
\r
3958 if (e.preventDefault)
\r
3959 e.preventDefault();
\r
3961 e.returnValue = false;
\r
3966 destroy : function() {
\r
3969 each(t.events, function(e, i) {
\r
3970 t._remove(e.obj, e.name, e.cfunc);
\r
3971 e.obj = e.cfunc = null;
\r
3978 _add : function(o, n, f) {
\r
3979 if (o.attachEvent)
\r
3980 o.attachEvent('on' + n, f);
\r
3981 else if (o.addEventListener)
\r
3982 o.addEventListener(n, f, false);
\r
3987 _remove : function(o, n, f) {
\r
3990 if (o.detachEvent)
\r
3991 o.detachEvent('on' + n, f);
\r
3992 else if (o.removeEventListener)
\r
3993 o.removeEventListener(n, f, false);
\r
3995 o['on' + n] = null;
\r
3997 // Might fail with permission denined on IE so we just ignore that
\r
4002 _pageInit : function(win) {
\r
4005 // Keep it from running more than once
\r
4009 t.domLoaded = true;
\r
4011 each(t.inits, function(c) {
\r
4018 _wait : function(win) {
\r
4019 var t = this, doc = win.document;
\r
4021 // No need since the document is already loaded
\r
4022 if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
\r
4028 if (doc.attachEvent) {
\r
4029 doc.attachEvent("onreadystatechange", function() {
\r
4030 if (doc.readyState === "complete") {
\r
4031 doc.detachEvent("onreadystatechange", arguments.callee);
\r
4036 if (doc.documentElement.doScroll && win == win.top) {
\r
4042 // If IE is used, use the trick by Diego Perini
\r
4043 // http://javascript.nwbox.com/IEContentLoaded/
\r
4044 doc.documentElement.doScroll("left");
\r
4046 setTimeout(arguments.callee, 0);
\r
4053 } else if (doc.addEventListener) {
\r
4054 t._add(win, 'DOMContentLoaded', function() {
\r
4059 t._add(win, 'load', function() {
\r
4065 preventDefault : function() {
\r
4066 this.returnValue = false;
\r
4069 stopPropagation : function() {
\r
4070 this.cancelBubble = true;
\r
4075 Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
\r
4077 // Dispatch DOM content loaded event for IE and Safari
\r
4078 Event._wait(window);
\r
4080 tinymce.addUnload(function() {
\r
4085 (function(tinymce) {
\r
4086 tinymce.dom.Element = function(id, settings) {
\r
4087 var t = this, dom, el;
\r
4089 t.settings = settings = settings || {};
\r
4091 t.dom = dom = settings.dom || tinymce.DOM;
\r
4093 // Only IE leaks DOM references, this is a lot faster
\r
4094 if (!tinymce.isIE)
\r
4095 el = dom.get(t.id);
\r
4098 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
\r
4099 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
\r
4100 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
\r
4101 'isHidden,setHTML,get').split(/,/)
\r
4103 t[k] = function() {
\r
4106 for (i = 0; i < arguments.length; i++)
\r
4107 a.push(arguments[i]);
\r
4109 a = dom[k].apply(dom, a);
\r
4116 tinymce.extend(t, {
\r
4117 on : function(n, f, s) {
\r
4118 return tinymce.dom.Event.add(t.id, n, f, s);
\r
4121 getXY : function() {
\r
4123 x : parseInt(t.getStyle('left')),
\r
4124 y : parseInt(t.getStyle('top'))
\r
4128 getSize : function() {
\r
4129 var n = dom.get(t.id);
\r
4132 w : parseInt(t.getStyle('width') || n.clientWidth),
\r
4133 h : parseInt(t.getStyle('height') || n.clientHeight)
\r
4137 moveTo : function(x, y) {
\r
4138 t.setStyles({left : x, top : y});
\r
4141 moveBy : function(x, y) {
\r
4142 var p = t.getXY();
\r
4144 t.moveTo(p.x + x, p.y + y);
\r
4147 resizeTo : function(w, h) {
\r
4148 t.setStyles({width : w, height : h});
\r
4151 resizeBy : function(w, h) {
\r
4152 var s = t.getSize();
\r
4154 t.resizeTo(s.w + w, s.h + h);
\r
4157 update : function(k) {
\r
4160 if (tinymce.isIE6 && settings.blocker) {
\r
4164 if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
\r
4167 // Remove blocker on remove
\r
4168 if (k == 'remove') {
\r
4169 dom.remove(t.blocker);
\r
4174 t.blocker = dom.uniqueId();
\r
4175 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
\r
4176 dom.setStyle(b, 'opacity', 0);
\r
4178 b = dom.get(t.blocker);
\r
4180 dom.setStyles(b, {
\r
4181 left : t.getStyle('left', 1),
\r
4182 top : t.getStyle('top', 1),
\r
4183 width : t.getStyle('width', 1),
\r
4184 height : t.getStyle('height', 1),
\r
4185 display : t.getStyle('display', 1),
\r
4186 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
\r
4194 (function(tinymce) {
\r
4195 function trimNl(s) {
\r
4196 return s.replace(/[\n\r]+/g, '');
\r
4200 var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
\r
4202 tinymce.create('tinymce.dom.Selection', {
\r
4203 Selection : function(dom, win, serializer) {
\r
4208 t.serializer = serializer;
\r
4212 'onBeforeSetContent',
\r
4213 'onBeforeGetContent',
\r
4217 t[e] = new tinymce.util.Dispatcher(t);
\r
4220 // No W3C Range support
\r
4221 if (!t.win.getSelection)
\r
4222 t.tridentSel = new tinymce.dom.TridentSelection(t);
\r
4225 tinymce.addUnload(t.destroy, t);
\r
4228 getContent : function(s) {
\r
4229 var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
\r
4234 s.format = s.format || 'html';
\r
4235 t.onBeforeGetContent.dispatch(t, s);
\r
4237 if (s.format == 'text')
\r
4238 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
\r
4240 if (r.cloneContents) {
\r
4241 n = r.cloneContents();
\r
4245 } else if (is(r.item) || is(r.htmlText))
\r
4246 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
\r
4248 e.innerHTML = r.toString();
\r
4250 // Keep whitespace before and after
\r
4251 if (/^\s/.test(e.innerHTML))
\r
4254 if (/\s+$/.test(e.innerHTML))
\r
4257 s.getInner = true;
\r
4259 s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
\r
4260 t.onGetContent.dispatch(t, s);
\r
4265 setContent : function(h, s) {
\r
4266 var t = this, r = t.getRng(), c, d = t.win.document;
\r
4268 s = s || {format : 'html'};
\r
4270 h = s.content = t.dom.processHTML(h);
\r
4272 // Dispatch before set content event
\r
4273 t.onBeforeSetContent.dispatch(t, s);
\r
4276 if (r.insertNode) {
\r
4277 // Make caret marker since insertNode places the caret in the beginning of text after insert
\r
4278 h += '<span id="__caret">_</span>';
\r
4280 // Delete and insert new node
\r
4282 if (r.startContainer == d && r.endContainer == d) {
\r
4283 // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
\r
4284 d.body.innerHTML = h;
\r
4286 r.deleteContents();
\r
4287 if (d.body.childNodes.length == 0) {
\r
4288 d.body.innerHTML = h;
\r
4290 r.insertNode(r.createContextualFragment(h));
\r
4294 // Move to caret marker
\r
4295 c = t.dom.get('__caret');
\r
4296 // Make sure we wrap it compleatly, Opera fails with a simple select call
\r
4297 r = d.createRange();
\r
4298 r.setStartBefore(c);
\r
4299 r.setEndBefore(c);
\r
4302 // Remove the caret position
\r
4303 t.dom.remove('__caret');
\r
4306 // Delete content and get caret text selection
\r
4307 d.execCommand('Delete', false, null);
\r
4314 // Dispatch set content event
\r
4315 t.onSetContent.dispatch(t, s);
\r
4318 getStart : function() {
\r
4319 var rng = this.getRng(), startElement, parentElement, checkRng, node;
\r
4321 if (rng.duplicate || rng.item) {
\r
4322 // Control selection, return first item
\r
4324 return rng.item(0);
\r
4326 // Get start element
\r
4327 checkRng = rng.duplicate();
\r
4328 checkRng.collapse(1);
\r
4329 startElement = checkRng.parentElement();
\r
4331 // Check if range parent is inside the start element, then return the inner parent element
\r
4332 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
\r
4333 parentElement = node = rng.parentElement();
\r
4334 while (node = node.parentNode) {
\r
4335 if (node == startElement) {
\r
4336 startElement = parentElement;
\r
4341 // If start element is body element try to move to the first child if it exists
\r
4342 if (startElement && startElement.nodeName == 'BODY')
\r
4343 return startElement.firstChild || startElement;
\r
4345 return startElement;
\r
4347 startElement = rng.startContainer;
\r
4349 if (startElement.nodeType == 1 && startElement.hasChildNodes())
\r
4350 startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
\r
4352 if (startElement && startElement.nodeType == 3)
\r
4353 return startElement.parentNode;
\r
4355 return startElement;
\r
4359 getEnd : function() {
\r
4360 var t = this, r = t.getRng(), e, eo;
\r
4362 if (r.duplicate || r.item) {
\r
4366 r = r.duplicate();
\r
4368 e = r.parentElement();
\r
4370 if (e && e.nodeName == 'BODY')
\r
4371 return e.lastChild || e;
\r
4375 e = r.endContainer;
\r
4378 if (e.nodeType == 1 && e.hasChildNodes())
\r
4379 e = e.childNodes[eo > 0 ? eo - 1 : eo];
\r
4381 if (e && e.nodeType == 3)
\r
4382 return e.parentNode;
\r
4388 getBookmark : function(type, normalized) {
\r
4389 var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
\r
4391 function findIndex(name, element) {
\r
4394 each(dom.select(name), function(node, i) {
\r
4395 if (node == element)
\r
4403 function getLocation() {
\r
4404 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
\r
4406 function getPoint(rng, start) {
\r
4407 var container = rng[start ? 'startContainer' : 'endContainer'],
\r
4408 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
\r
4410 if (container.nodeType == 3) {
\r
4412 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
\r
4413 offset += node.nodeValue.length;
\r
4416 point.push(offset);
\r
4418 childNodes = container.childNodes;
\r
4420 if (offset >= childNodes.length && childNodes.length) {
\r
4422 offset = Math.max(0, childNodes.length - 1);
\r
4425 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
\r
4428 for (; container && container != root; container = container.parentNode)
\r
4429 point.push(t.dom.nodeIndex(container, normalized));
\r
4434 bookmark.start = getPoint(rng, true);
\r
4436 if (!t.isCollapsed())
\r
4437 bookmark.end = getPoint(rng);
\r
4442 return getLocation();
\r
4445 // Handle simple range
\r
4447 return {rng : t.getRng()};
\r
4450 id = dom.uniqueId();
\r
4451 collapsed = tinyMCE.activeEditor.selection.isCollapsed();
\r
4452 styles = 'overflow:hidden;line-height:0px';
\r
4454 // Explorer method
\r
4455 if (rng.duplicate || rng.item) {
\r
4458 rng2 = rng.duplicate();
\r
4460 // Insert start marker
\r
4462 rng.pasteHTML('<span _mce_type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
\r
4464 // Insert end marker
\r
4466 rng2.collapse(false);
\r
4467 rng2.pasteHTML('<span _mce_type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
\r
4470 // Control selection
\r
4471 element = rng.item(0);
\r
4472 name = element.nodeName;
\r
4474 return {name : name, index : findIndex(name, element)};
\r
4477 element = t.getNode();
\r
4478 name = element.nodeName;
\r
4479 if (name == 'IMG')
\r
4480 return {name : name, index : findIndex(name, element)};
\r
4483 rng2 = rng.cloneRange();
\r
4485 // Insert end marker
\r
4487 rng2.collapse(false);
\r
4488 rng2.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_end', style : styles}, chr));
\r
4491 rng.collapse(true);
\r
4492 rng.insertNode(dom.create('span', {_mce_type : "bookmark", id : id + '_start', style : styles}, chr));
\r
4495 t.moveToBookmark({id : id, keep : 1});
\r
4500 moveToBookmark : function(bookmark) {
\r
4501 var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
\r
4503 // Clear selection cache
\r
4505 t.tridentSel.destroy();
\r
4508 if (bookmark.start) {
\r
4509 rng = dom.createRng();
\r
4510 root = dom.getRoot();
\r
4512 function setEndPoint(start) {
\r
4513 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
\r
4516 // Find container node
\r
4517 for (node = root, i = point.length - 1; i >= 1; i--) {
\r
4518 children = node.childNodes;
\r
4520 if (children.length)
\r
4521 node = children[point[i]];
\r
4524 // Set offset within container node
\r
4526 rng.setStart(node, point[0]);
\r
4528 rng.setEnd(node, point[0]);
\r
4532 setEndPoint(true);
\r
4536 } else if (bookmark.id) {
\r
4537 function restoreEndPoint(suffix) {
\r
4538 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
\r
4541 node = marker.parentNode;
\r
4543 if (suffix == 'start') {
\r
4545 idx = dom.nodeIndex(marker);
\r
4547 node = marker.firstChild;
\r
4551 startContainer = endContainer = node;
\r
4552 startOffset = endOffset = idx;
\r
4555 idx = dom.nodeIndex(marker);
\r
4557 node = marker.firstChild;
\r
4561 endContainer = node;
\r
4566 prev = marker.previousSibling;
\r
4567 next = marker.nextSibling;
\r
4569 // Remove all marker text nodes
\r
4570 each(tinymce.grep(marker.childNodes), function(node) {
\r
4571 if (node.nodeType == 3)
\r
4572 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
\r
4575 // Remove marker but keep children if for example contents where inserted into the marker
\r
4576 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
\r
4577 while (marker = dom.get(bookmark.id + '_' + suffix))
\r
4578 dom.remove(marker, 1);
\r
4580 // If siblings are text nodes then merge them
\r
4581 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3) {
\r
4582 idx = prev.nodeValue.length;
\r
4583 prev.appendData(next.nodeValue);
\r
4586 if (suffix == 'start') {
\r
4587 startContainer = endContainer = prev;
\r
4588 startOffset = endOffset = idx;
\r
4590 endContainer = prev;
\r
4598 function addBogus(node) {
\r
4599 // Adds a bogus BR element for empty block elements
\r
4600 // on non IE browsers just to have a place to put the caret
\r
4601 if (!isIE && dom.isBlock(node) && !node.innerHTML)
\r
4602 node.innerHTML = '<br _mce_bogus="1" />';
\r
4607 // Restore start/end points
\r
4608 restoreEndPoint('start');
\r
4609 restoreEndPoint('end');
\r
4611 rng = dom.createRng();
\r
4612 rng.setStart(addBogus(startContainer), startOffset);
\r
4613 rng.setEnd(addBogus(endContainer), endOffset);
\r
4615 } else if (bookmark.name) {
\r
4616 t.select(dom.select(bookmark.name)[bookmark.index]);
\r
4617 } else if (bookmark.rng)
\r
4618 t.setRng(bookmark.rng);
\r
4622 select : function(node, content) {
\r
4623 var t = this, dom = t.dom, rng = dom.createRng(), idx;
\r
4625 idx = dom.nodeIndex(node);
\r
4626 rng.setStart(node.parentNode, idx);
\r
4627 rng.setEnd(node.parentNode, idx + 1);
\r
4629 // Find first/last text node or BR element
\r
4631 function setPoint(node, start) {
\r
4632 var walker = new tinymce.dom.TreeWalker(node, node);
\r
4636 if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
\r
4638 rng.setStart(node, 0);
\r
4640 rng.setEnd(node, node.nodeValue.length);
\r
4646 if (node.nodeName == 'BR') {
\r
4648 rng.setStartBefore(node);
\r
4650 rng.setEndBefore(node);
\r
4654 } while (node = (start ? walker.next() : walker.prev()));
\r
4657 setPoint(node, 1);
\r
4666 isCollapsed : function() {
\r
4667 var t = this, r = t.getRng(), s = t.getSel();
\r
4672 if (r.compareEndPoints)
\r
4673 return r.compareEndPoints('StartToEnd', r) === 0;
\r
4675 return !s || r.collapsed;
\r
4678 collapse : function(b) {
\r
4679 var t = this, r = t.getRng(), n;
\r
4681 // Control range on IE
\r
4684 r = this.win.document.body.createTextRange();
\r
4685 r.moveToElementText(n);
\r
4692 getSel : function() {
\r
4693 var t = this, w = this.win;
\r
4695 return w.getSelection ? w.getSelection() : w.document.selection;
\r
4698 getRng : function(w3c) {
\r
4699 var t = this, s, r;
\r
4701 // Found tridentSel object then we need to use that one
\r
4702 if (w3c && t.tridentSel)
\r
4703 return t.tridentSel.getRangeAt(0);
\r
4706 if (s = t.getSel())
\r
4707 r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
\r
4709 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
\r
4712 // No range found then create an empty one
\r
4713 // This can occur when the editor is placed in a hidden container element on Gecko
\r
4714 // Or on IE when there was an exception
\r
4716 r = t.win.document.createRange ? t.win.document.createRange() : t.win.document.body.createTextRange();
\r
4718 if (t.selectedRange && t.explicitRange) {
\r
4719 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
\r
4720 // Safari, Opera and Chrome only ever select text which causes the range to change.
\r
4721 // This lets us use the originally set range if the selection hasn't been changed by the user.
\r
4722 r = t.explicitRange;
\r
4724 t.selectedRange = null;
\r
4725 t.explicitRange = null;
\r
4731 setRng : function(r) {
\r
4734 if (!t.tridentSel) {
\r
4738 t.explicitRange = r;
\r
4739 s.removeAllRanges();
\r
4741 t.selectedRange = s.getRangeAt(0);
\r
4745 if (r.cloneRange) {
\r
4746 t.tridentSel.addRange(r);
\r
4750 // Is IE specific range
\r
4754 // Needed for some odd IE bug #1843306
\r
4759 setNode : function(n) {
\r
4762 t.setContent(t.dom.getOuterHTML(n));
\r
4767 getNode : function() {
\r
4768 var t = this, rng = t.getRng(), sel = t.getSel(), elm;
\r
4770 if (rng.setStart) {
\r
4771 // Range maybe lost after the editor is made visible again
\r
4773 return t.dom.getRoot();
\r
4775 elm = rng.commonAncestorContainer;
\r
4777 // Handle selection a image or other control like element such as anchors
\r
4778 if (!rng.collapsed) {
\r
4779 if (rng.startContainer == rng.endContainer) {
\r
4780 if (rng.startOffset - rng.endOffset < 2) {
\r
4781 if (rng.startContainer.hasChildNodes())
\r
4782 elm = rng.startContainer.childNodes[rng.startOffset];
\r
4786 // If the anchor node is a element instead of a text node then return this element
\r
4787 if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
\r
4788 return sel.anchorNode.childNodes[sel.anchorOffset];
\r
4791 if (elm && elm.nodeType == 3)
\r
4792 return elm.parentNode;
\r
4797 return rng.item ? rng.item(0) : rng.parentElement();
\r
4800 getSelectedBlocks : function(st, en) {
\r
4801 var t = this, dom = t.dom, sb, eb, n, bl = [];
\r
4803 sb = dom.getParent(st || t.getStart(), dom.isBlock);
\r
4804 eb = dom.getParent(en || t.getEnd(), dom.isBlock);
\r
4809 if (sb && eb && sb != eb) {
\r
4812 while ((n = n.nextSibling) && n != eb) {
\r
4813 if (dom.isBlock(n))
\r
4818 if (eb && sb != eb)
\r
4824 destroy : function(s) {
\r
4830 t.tridentSel.destroy();
\r
4832 // Manual destroy then remove unload handler
\r
4834 tinymce.removeUnload(t.destroy);
\r
4839 (function(tinymce) {
\r
4840 tinymce.create('tinymce.dom.XMLWriter', {
\r
4843 XMLWriter : function(s) {
\r
4844 // Get XML document
\r
4845 function getXML() {
\r
4846 var i = document.implementation;
\r
4848 if (!i || !i.createDocument) {
\r
4850 try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
\r
4851 try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
\r
4853 return i.createDocument('', '', null);
\r
4856 this.doc = getXML();
\r
4858 // Since Opera and WebKit doesn't escape > into > we need to do it our self to normalize the output for all browsers
\r
4859 this.valid = tinymce.isOpera || tinymce.isWebKit;
\r
4864 reset : function() {
\r
4865 var t = this, d = t.doc;
\r
4868 d.removeChild(d.firstChild);
\r
4870 t.node = d.appendChild(d.createElement("html"));
\r
4873 writeStartElement : function(n) {
\r
4876 t.node = t.node.appendChild(t.doc.createElement(n));
\r
4879 writeAttribute : function(n, v) {
\r
4881 v = v.replace(/>/g, '%MCGT%');
\r
4883 this.node.setAttribute(n, v);
\r
4886 writeEndElement : function() {
\r
4887 this.node = this.node.parentNode;
\r
4890 writeFullEndElement : function() {
\r
4891 var t = this, n = t.node;
\r
4893 n.appendChild(t.doc.createTextNode(""));
\r
4894 t.node = n.parentNode;
\r
4897 writeText : function(v) {
\r
4899 v = v.replace(/>/g, '%MCGT%');
\r
4901 this.node.appendChild(this.doc.createTextNode(v));
\r
4904 writeCDATA : function(v) {
\r
4905 this.node.appendChild(this.doc.createCDATASection(v));
\r
4908 writeComment : function(v) {
\r
4909 // Fix for bug #2035694
\r
4911 v = v.replace(/^\-|\-$/g, ' ');
\r
4913 this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
\r
4916 getContent : function() {
\r
4919 h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
\r
4920 h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
\r
4921 h = h.replace(/ ?\/>/g, ' />');
\r
4924 h = h.replace(/\%MCGT%/g, '>');
\r
4931 (function(tinymce) {
\r
4932 tinymce.create('tinymce.dom.StringWriter', {
\r
4939 StringWriter : function(s) {
\r
4940 this.settings = tinymce.extend({
\r
4941 indent_char : ' ',
\r
4948 reset : function() {
\r
4955 writeStartElement : function(n) {
\r
4956 this._writeAttributesEnd();
\r
4957 this.writeRaw('<' + n);
\r
4958 this.tags.push(n);
\r
4959 this.inAttr = true;
\r
4961 this.elementCount = this.count;
\r
4964 writeAttribute : function(n, v) {
\r
4967 t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
\r
4970 writeEndElement : function() {
\r
4973 if (this.tags.length > 0) {
\r
4974 n = this.tags.pop();
\r
4976 if (this._writeAttributesEnd(1))
\r
4977 this.writeRaw('</' + n + '>');
\r
4979 if (this.settings.indentation > 0)
\r
4980 this.writeRaw('\n');
\r
4984 writeFullEndElement : function() {
\r
4985 if (this.tags.length > 0) {
\r
4986 this._writeAttributesEnd();
\r
4987 this.writeRaw('</' + this.tags.pop() + '>');
\r
4989 if (this.settings.indentation > 0)
\r
4990 this.writeRaw('\n');
\r
4994 writeText : function(v) {
\r
4995 this._writeAttributesEnd();
\r
4996 this.writeRaw(this.encode(v));
\r
5000 writeCDATA : function(v) {
\r
5001 this._writeAttributesEnd();
\r
5002 this.writeRaw('<![CDATA[' + v + ']]>');
\r
5006 writeComment : function(v) {
\r
5007 this._writeAttributesEnd();
\r
5008 this.writeRaw('<!-- ' + v + '-->');
\r
5012 writeRaw : function(v) {
\r
5016 encode : function(s) {
\r
5017 return s.replace(/[<>&"]/g, function(v) {
\r
5036 getContent : function() {
\r
5040 _writeAttributesEnd : function(s) {
\r
5044 this.inAttr = false;
\r
5046 if (s && this.elementCount == this.count) {
\r
5047 this.writeRaw(' />');
\r
5051 this.writeRaw('>');
\r
5058 (function(tinymce) {
\r
5060 var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
\r
5062 function wildcardToRE(s) {
\r
5063 return s.replace(/([?+*])/g, '.$1');
\r
5066 tinymce.create('tinymce.dom.Serializer', {
\r
5067 Serializer : function(s) {
\r
5071 t.onPreProcess = new Dispatcher(t);
\r
5072 t.onPostProcess = new Dispatcher(t);
\r
5075 t.writer = new tinymce.dom.XMLWriter();
\r
5077 // IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
\r
5078 t.writer = new tinymce.dom.StringWriter();
\r
5081 // Default settings
\r
5082 t.settings = s = extend({
\r
5083 dom : tinymce.DOM,
\r
5087 invalid_attrs : /^(_mce_|_moz_|sizset|sizcache)/,
\r
5088 closed : /^(br|hr|input|meta|img|link|param|area)$/,
\r
5089 entity_encoding : 'named',
\r
5090 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
5091 valid_elements : '*[*]',
\r
5092 extended_valid_elements : 0,
\r
5093 invalid_elements : 0,
\r
5094 fix_table_elements : 1,
\r
5095 fix_list_elements : true,
\r
5096 fix_content_duplication : true,
\r
5097 convert_fonts_to_spans : false,
\r
5098 font_size_classes : 0,
\r
5099 apply_source_formatting : 0,
\r
5100 indent_mode : 'simple',
\r
5101 indent_char : '\t',
\r
5102 indent_levels : 1,
\r
5103 remove_linebreaks : 1,
\r
5104 remove_redundant_brs : 1,
\r
5105 element_format : 'xhtml'
\r
5109 t.schema = s.schema;
\r
5111 // Use raw entities if no entities are defined
\r
5112 if (s.entity_encoding == 'named' && !s.entities)
\r
5113 s.entity_encoding = 'raw';
\r
5115 if (s.remove_redundant_brs) {
\r
5116 t.onPostProcess.add(function(se, o) {
\r
5117 // Remove single BR at end of block elements since they get rendered
\r
5118 o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
\r
5119 // Check if it's a single element
\r
5120 if (/^<br \/>\s*<\//.test(a))
\r
5121 return '</' + c + '>';
\r
5128 // Remove XHTML element endings i.e. produce crap :) XHTML is better
\r
5129 if (s.element_format == 'html') {
\r
5130 t.onPostProcess.add(function(se, o) {
\r
5131 o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
\r
5135 if (s.fix_list_elements) {
\r
5136 t.onPreProcess.add(function(se, o) {
\r
5137 var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
\r
5139 function prevNode(e, n) {
\r
5140 var a = n.split(','), i;
\r
5142 while ((e = e.previousSibling) != null) {
\r
5143 for (i=0; i<a.length; i++) {
\r
5144 if (e.nodeName == a[i])
\r
5152 for (x=0; x<a.length; x++) {
\r
5153 nl = t.dom.select(a[x], o.node);
\r
5155 for (i=0; i<nl.length; i++) {
\r
5159 if (r.test(p.nodeName)) {
\r
5160 np = prevNode(n, 'LI');
\r
5163 np = t.dom.create('li');
\r
5164 np.innerHTML = ' ';
\r
5165 np.appendChild(n);
\r
5166 p.insertBefore(np, p.firstChild);
\r
5168 np.appendChild(n);
\r
5175 if (s.fix_table_elements) {
\r
5176 t.onPreProcess.add(function(se, o) {
\r
5177 // Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
\r
5178 // so Opera users with an older version will have to live with less compaible output not much we can do here
\r
5179 if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
\r
5180 each(t.dom.select('p table', o.node).reverse(), function(n) {
\r
5181 var parent = t.dom.getParent(n.parentNode, 'table,p');
\r
5183 if (parent.nodeName != 'TABLE') {
\r
5185 t.dom.split(parent, n);
\r
5187 // IE can sometimes fire an unknown runtime error so we just ignore it
\r
5196 setEntities : function(s) {
\r
5197 var t = this, a, i, l = {}, v;
\r
5199 // No need to setup more than once
\r
5200 if (t.entityLookup)
\r
5203 // Build regex and lookup array
\r
5205 for (i = 0; i < a.length; i += 2) {
\r
5208 // Don't add default & " etc.
\r
5209 if (v == 34 || v == 38 || v == 60 || v == 62)
\r
5212 l[String.fromCharCode(a[i])] = a[i + 1];
\r
5214 v = parseInt(a[i]).toString(16);
\r
5217 t.entityLookup = l;
\r
5220 setRules : function(s) {
\r
5226 t.validElements = {};
\r
5228 return t.addRules(s);
\r
5231 addRules : function(s) {
\r
5239 each(s.split(','), function(s) {
\r
5240 var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
\r
5242 // Extend with default rules
\r
5244 at = tinymce.extend([], dr.attribs);
\r
5246 // Parse attributes
\r
5247 if (p.length > 1) {
\r
5248 each(p[1].split('|'), function(s) {
\r
5253 // Parse attribute rule
\r
5254 s = s.replace(/::/g, '~');
\r
5255 s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
\r
5256 s[2] = s[2].replace(/~/g, ':');
\r
5258 // Add required attributes
\r
5259 if (s[1] == '!') {
\r
5264 // Remove inherited attributes
\r
5265 if (s[1] == '-') {
\r
5266 for (i = 0; i <at.length; i++) {
\r
5267 if (at[i].name == s[2]) {
\r
5275 // Add default attrib values
\r
5277 ar.defaultVal = s[4] || '';
\r
5280 // Add forced attrib values
\r
5282 ar.forcedVal = s[4];
\r
5285 // Add validation values
\r
5287 ar.validVals = s[4].split('?');
\r
5291 if (/[*.?]/.test(s[2])) {
\r
5293 ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
\r
5304 // Handle element names
\r
5305 each(tn, function(s, i) {
\r
5306 var pr = s.charAt(0), x = 1, ru = {};
\r
5308 // Extend with default rule data
\r
5311 ru.noEmpty = dr.noEmpty;
\r
5314 ru.fullEnd = dr.fullEnd;
\r
5317 ru.padd = dr.padd;
\r
5320 // Handle prefixes
\r
5323 ru.noEmpty = true;
\r
5327 ru.fullEnd = true;
\r
5338 tn[i] = s = s.substring(x);
\r
5339 t.validElements[s] = 1;
\r
5341 // Add element name or element regex
\r
5342 if (/[*.?]/.test(tn[0])) {
\r
5343 ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
\r
5344 t.wildRules = t.wildRules || {};
\r
5345 t.wildRules.push(ru);
\r
5349 // Store away default rule
\r
5359 ru.requiredAttribs = ra;
\r
5362 // Build valid attributes regexp
\r
5364 each(va, function(v) {
\r
5368 s += '(' + wildcardToRE(v) + ')';
\r
5370 ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
\r
5371 ru.wildAttribs = wat;
\r
5376 // Build valid elements regexp
\r
5378 each(t.validElements, function(v, k) {
\r
5385 t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
\r
5387 //console.debug(t.validElementsRE.toString());
\r
5388 //console.dir(t.rules);
\r
5389 //console.dir(t.wildRules);
\r
5392 findRule : function(n) {
\r
5393 var t = this, rl = t.rules, i, r;
\r
5404 for (i = 0; i < rl.length; i++) {
\r
5405 if (rl[i].nameRE.test(n))
\r
5412 findAttribRule : function(ru, n) {
\r
5413 var i, wa = ru.wildAttribs;
\r
5415 for (i = 0; i < wa.length; i++) {
\r
5416 if (wa[i].nameRE.test(n))
\r
5423 serialize : function(n, o) {
\r
5424 var h, t = this, doc, oldDoc, impl, selected;
\r
5428 o.format = o.format || 'html';
\r
5431 // IE looses the selected attribute on option elements so we need to store it
\r
5432 // See: http://support.microsoft.com/kb/829907
\r
5435 each(n.getElementsByTagName('option'), function(n) {
\r
5436 var v = t.dom.getAttrib(n, 'selected');
\r
5438 selected.push(v ? v : null);
\r
5442 n = n.cloneNode(true);
\r
5444 // IE looses the selected attribute on option elements so we need to restore it
\r
5446 each(n.getElementsByTagName('option'), function(n, i) {
\r
5447 t.dom.setAttrib(n, 'selected', selected[i]);
\r
5451 // Nodes needs to be attached to something in WebKit/Opera
\r
5452 // Older builds of Opera crashes if you attach the node to an document created dynamically
\r
5453 // and since we can't feature detect a crash we need to sniff the acutal build number
\r
5454 // This fix will make DOM ranges and make Sizzle happy!
\r
5455 impl = n.ownerDocument.implementation;
\r
5456 if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
\r
5457 // Create an empty HTML document
\r
5458 doc = impl.createHTMLDocument("");
\r
5460 // Add the element or it's children if it's a body element to the new document
\r
5461 each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
\r
5462 doc.body.appendChild(doc.importNode(node, true));
\r
5465 // Grab first child or body element for serialization
\r
5466 if (n.nodeName != 'BODY')
\r
5467 n = doc.body.firstChild;
\r
5471 // set the new document in DOMUtils so createElement etc works
\r
5472 oldDoc = t.dom.doc;
\r
5476 t.key = '' + (parseInt(t.key) + 1);
\r
5479 if (!o.no_events) {
\r
5481 t.onPreProcess.dispatch(t, o);
\r
5484 // Serialize HTML DOM into a string
\r
5487 t._serializeNode(n, o.getInner);
\r
5490 o.content = t.writer.getContent();
\r
5492 // Restore the old document if it was changed
\r
5494 t.dom.doc = oldDoc;
\r
5497 t.onPostProcess.dispatch(t, o);
\r
5499 t._postProcess(o);
\r
5502 return tinymce.trim(o.content);
\r
5505 // Internal functions
\r
5507 _postProcess : function(o) {
\r
5508 var t = this, s = t.settings, h = o.content, sc = [], p;
\r
5510 if (o.format == 'html') {
\r
5511 // Protect some elements
\r
5515 {pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
\r
5516 {pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
\r
5517 {pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
\r
5518 {pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
\r
5519 {pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
\r
5526 if (s.entity_encoding !== 'raw')
\r
5529 // Use BR instead of padded P elements inside editor and use <p> </p> outside editor
\r
5531 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
\r
5533 h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
\r
5535 // Since Gecko and Safari keeps whitespace in the DOM we need to
\r
5536 // remove it inorder to match other browsers. But I think Gecko and Safari is right.
\r
5537 // This process is only done when getting contents out from the editor.
\r
5539 // We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
\r
5540 h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1> </p>' : '<p$1> </p>');
\r
5542 if (s.remove_linebreaks) {
\r
5543 h = h.replace(/\r?\n|\r/g, ' ');
\r
5544 h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
\r
5545 h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
\r
5546 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
5547 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
5548 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
5551 // Simple indentation
\r
5552 if (s.apply_source_formatting && s.indent_mode == 'simple') {
\r
5553 // Add line breaks before and after block elements
\r
5554 h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
\r
5555 h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
\r
5556 h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
\r
5557 h = h.replace(/\n\n/g, '\n');
\r
5561 h = t._unprotect(h, p);
\r
5563 // Restore CDATA sections
\r
5564 h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
\r
5566 // Restore the \u00a0 character if raw mode is enabled
\r
5567 if (s.entity_encoding == 'raw')
\r
5568 h = h.replace(/<p> <\/p>|<p([^>]+)> <\/p>/g, '<p$1>\u00a0</p>');
\r
5570 // Restore noscript elements
\r
5571 h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
\r
5572 return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
\r
5579 _serializeNode : function(n, inner) {
\r
5580 var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed, keep, type, scopeName;
\r
5582 if (!s.node_filter || s.node_filter(n)) {
\r
5583 switch (n.nodeType) {
\r
5584 case 1: // Element
\r
5585 if (n.hasAttribute ? n.hasAttribute('_mce_bogus') : n.getAttribute('_mce_bogus'))
\r
5588 iv = keep = false;
\r
5589 hc = n.hasChildNodes();
\r
5590 nn = n.getAttribute('_mce_name') || n.nodeName.toLowerCase();
\r
5592 // Get internal type
\r
5593 type = n.getAttribute('_mce_type');
\r
5595 if (!t._info.cleanup) {
\r
5602 // Add correct prefix on IE
\r
5604 scopeName = n.scopeName;
\r
5605 if (scopeName && scopeName !== 'HTML' && scopeName !== 'html')
\r
5606 nn = scopeName + ':' + nn;
\r
5609 // Remove mce prefix on IE needed for the abbr element
\r
5610 if (nn.indexOf('mce:') === 0)
\r
5611 nn = nn.substring(4);
\r
5615 if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inner) {
\r
5622 // Fix IE content duplication (DOM can have multiple copies of the same node)
\r
5623 if (s.fix_content_duplication) {
\r
5624 if (n._mce_serialized == t.key)
\r
5627 n._mce_serialized = t.key;
\r
5630 // IE sometimes adds a / infront of the node name
\r
5631 if (nn.charAt(0) == '/')
\r
5632 nn = nn.substring(1);
\r
5633 } else if (isGecko) {
\r
5634 // Ignore br elements
\r
5635 if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
\r
5639 // Check if valid child
\r
5640 if (s.validate_children) {
\r
5641 if (t.elementName && !t.schema.isValid(t.elementName, nn)) {
\r
5646 t.elementName = nn;
\r
5649 ru = t.findRule(nn);
\r
5651 // No valid rule for this element could be found then skip it
\r
5657 nn = ru.name || nn;
\r
5658 closed = s.closed.test(nn);
\r
5660 // Skip empty nodes or empty node name in IE
\r
5661 if ((!hc && ru.noEmpty) || (isIE && !nn)) {
\r
5667 if (ru.requiredAttribs) {
\r
5668 a = ru.requiredAttribs;
\r
5670 for (i = a.length - 1; i >= 0; i--) {
\r
5671 if (this.dom.getAttrib(n, a[i]) !== '')
\r
5675 // None of the required was there
\r
5682 w.writeStartElement(nn);
\r
5684 // Add ordered attributes
\r
5686 for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
\r
5688 v = t._getAttrib(n, a);
\r
5691 w.writeAttribute(a.name, v);
\r
5695 // Add wild attributes
\r
5696 if (ru.validAttribsRE) {
\r
5697 at = t.dom.getAttribs(n);
\r
5698 for (i=at.length-1; i>-1; i--) {
\r
5701 if (no.specified) {
\r
5702 a = no.nodeName.toLowerCase();
\r
5704 if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
\r
5707 ar = t.findAttribRule(ru, a);
\r
5708 v = t._getAttrib(n, ar, a);
\r
5711 w.writeAttribute(a, v);
\r
5716 // Keep type attribute
\r
5718 w.writeAttribute('_mce_type', type);
\r
5720 // Write text from script
\r
5721 if (nn === 'script' && tinymce.trim(n.innerHTML)) {
\r
5722 w.writeText('// '); // Padd it with a comment so it will parse on older browsers
\r
5723 w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
\r
5728 // Padd empty nodes with a
\r
5730 // If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
\r
5731 if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
\r
5732 if (cn.hasAttribute ? cn.hasAttribute('_mce_bogus') : cn.getAttribute('_mce_bogus'))
\r
5733 w.writeText('\u00a0');
\r
5735 w.writeText('\u00a0'); // No children then padd it
\r
5741 // Check if valid child
\r
5742 if (s.validate_children && t.elementName && !t.schema.isValid(t.elementName, '#text'))
\r
5745 return w.writeText(n.nodeValue);
\r
5748 return w.writeCDATA(n.nodeValue);
\r
5750 case 8: // Comment
\r
5751 return w.writeComment(n.nodeValue);
\r
5753 } else if (n.nodeType == 1)
\r
5754 hc = n.hasChildNodes();
\r
5756 if (hc && !closed) {
\r
5757 cn = n.firstChild;
\r
5760 t._serializeNode(cn);
\r
5761 t.elementName = nn;
\r
5762 cn = cn.nextSibling;
\r
5766 // Write element end
\r
5769 w.writeFullEndElement();
\r
5771 w.writeEndElement();
\r
5775 _protect : function(o) {
\r
5778 o.items = o.items || [];
\r
5781 return s.replace(/[\r\n\\]/g, function(c) {
\r
5784 else if (c === '\\')
\r
5792 return s.replace(/\\[\\rn]/g, function(c) {
\r
5795 else if (c === '\\\\')
\r
5802 each(o.patterns, function(p) {
\r
5803 o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
\r
5810 return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
\r
5817 _unprotect : function(h, o) {
\r
5818 h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
\r
5819 return o.items[parseInt(b)];
\r
5827 _encode : function(h) {
\r
5828 var t = this, s = t.settings, l;
\r
5831 if (s.entity_encoding !== 'raw') {
\r
5832 if (s.entity_encoding.indexOf('named') != -1) {
\r
5833 t.setEntities(s.entities);
\r
5834 l = t.entityLookup;
\r
5836 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
5840 a = '&' + v + ';';
\r
5846 if (s.entity_encoding.indexOf('numeric') != -1) {
\r
5847 h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
\r
5848 return '&#' + a.charCodeAt(0) + ';';
\r
5856 _setup : function() {
\r
5857 var t = this, s = this.settings;
\r
5864 t.setRules(s.valid_elements);
\r
5865 t.addRules(s.extended_valid_elements);
\r
5867 if (s.invalid_elements)
\r
5868 t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
\r
5870 if (s.attrib_value_filter)
\r
5871 t.attribValueFilter = s.attribValueFilter;
\r
5874 _getAttrib : function(n, a, na) {
\r
5877 na = na || a.name;
\r
5879 if (a.forcedVal && (v = a.forcedVal)) {
\r
5880 if (v === '{$uid}')
\r
5881 return this.dom.uniqueId();
\r
5886 v = this.dom.getAttrib(n, na);
\r
5891 // Whats the point? Remove usless attribute value
\r
5898 if (this.attribValueFilter)
\r
5899 v = this.attribValueFilter(na, v, n);
\r
5901 if (a.validVals) {
\r
5902 for (i = a.validVals.length - 1; i >= 0; i--) {
\r
5903 if (v == a.validVals[i])
\r
5911 if (v === '' && typeof(a.defaultVal) != 'undefined') {
\r
5914 if (v === '{$uid}')
\r
5915 return this.dom.uniqueId();
\r
5919 // Remove internal mceItemXX classes when content is extracted from editor
\r
5920 if (na == 'class' && this.processObj.get)
\r
5921 v = v.replace(/\s?mceItem\w+\s?/g, '');
\r
5933 (function(tinymce) {
\r
5934 tinymce.dom.ScriptLoader = function(settings) {
\r
5940 scriptLoadedCallbacks = {},
\r
5941 queueLoadedCallbacks = [],
\r
5945 function loadScript(url, callback) {
\r
5946 var t = this, dom = tinymce.DOM, elm, uri, loc, id;
\r
5948 // Execute callback when script is loaded
\r
5953 elm.onreadystatechange = elm.onload = elm = null;
\r
5958 id = dom.uniqueId();
\r
5960 if (tinymce.isIE6) {
\r
5961 uri = new tinymce.util.URI(url);
\r
5964 // If script is from same domain and we
\r
5965 // use IE 6 then use XHR since it's more reliable
\r
5966 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol) {
\r
5967 tinymce.util.XHR.send({
\r
5968 url : tinymce._addVer(uri.getURI()),
\r
5969 success : function(content) {
\r
5970 // Create new temp script element
\r
5971 var script = dom.create('script', {
\r
5972 type : 'text/javascript'
\r
5975 // Evaluate script in global scope
\r
5976 script.text = content;
\r
5977 document.getElementsByTagName('head')[0].appendChild(script);
\r
5978 dom.remove(script);
\r
5988 // Create new script element
\r
5989 elm = dom.create('script', {
\r
5991 type : 'text/javascript',
\r
5992 src : tinymce._addVer(url)
\r
5995 // Add onload and readystate listeners
\r
5996 elm.onload = done;
\r
5997 elm.onreadystatechange = function() {
\r
5998 var state = elm.readyState;
\r
6000 // Loaded state is passed on IE 6 however there
\r
6001 // are known issues with this method but we can't use
\r
6002 // XHR in a cross domain loading
\r
6003 if (state == 'complete' || state == 'loaded')
\r
6007 // Most browsers support this feature so we report errors
\r
6008 // for those at least to help users track their missing plugins etc
\r
6009 // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
\r
6010 /*elm.onerror = function() {
\r
6011 alert('Failed to load: ' + url);
\r
6014 // Add script to document
\r
6015 (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
\r
6018 this.isDone = function(url) {
\r
6019 return states[url] == LOADED;
\r
6022 this.markDone = function(url) {
\r
6023 states[url] = LOADED;
\r
6026 this.add = this.load = function(url, callback, scope) {
\r
6027 var item, state = states[url];
\r
6029 // Add url to load queue
\r
6030 if (state == undefined) {
\r
6032 states[url] = QUEUED;
\r
6036 // Store away callback for later execution
\r
6037 if (!scriptLoadedCallbacks[url])
\r
6038 scriptLoadedCallbacks[url] = [];
\r
6040 scriptLoadedCallbacks[url].push({
\r
6042 scope : scope || this
\r
6047 this.loadQueue = function(callback, scope) {
\r
6048 this.loadScripts(queue, callback, scope);
\r
6051 this.loadScripts = function(scripts, callback, scope) {
\r
6054 function execScriptLoadedCallbacks(url) {
\r
6055 // Execute URL callback functions
\r
6056 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
\r
6057 callback.func.call(callback.scope);
\r
6060 scriptLoadedCallbacks[url] = undefined;
\r
6063 queueLoadedCallbacks.push({
\r
6065 scope : scope || this
\r
6068 loadScripts = function() {
\r
6069 var loadingScripts = tinymce.grep(scripts);
\r
6071 // Current scripts has been handled
\r
6072 scripts.length = 0;
\r
6074 // Load scripts that needs to be loaded
\r
6075 tinymce.each(loadingScripts, function(url) {
\r
6076 // Script is already loaded then execute script callbacks directly
\r
6077 if (states[url] == LOADED) {
\r
6078 execScriptLoadedCallbacks(url);
\r
6082 // Is script not loading then start loading it
\r
6083 if (states[url] != LOADING) {
\r
6084 states[url] = LOADING;
\r
6087 loadScript(url, function() {
\r
6088 states[url] = LOADED;
\r
6091 execScriptLoadedCallbacks(url);
\r
6093 // Load more scripts if they where added by the recently loaded script
\r
6099 // No scripts are currently loading then execute all pending queue loaded callbacks
\r
6101 tinymce.each(queueLoadedCallbacks, function(callback) {
\r
6102 callback.func.call(callback.scope);
\r
6105 queueLoadedCallbacks.length = 0;
\r
6113 // Global script loader
\r
6114 tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
\r
6117 tinymce.dom.TreeWalker = function(start_node, root_node) {
\r
6118 var node = start_node;
\r
6120 function findSibling(node, start_name, sibling_name, shallow) {
\r
6121 var sibling, parent;
\r
6124 // Walk into nodes if it has a start
\r
6125 if (!shallow && node[start_name])
\r
6126 return node[start_name];
\r
6128 // Return the sibling if it has one
\r
6129 if (node != root_node) {
\r
6130 sibling = node[sibling_name];
\r
6134 // Walk up the parents to look for siblings
\r
6135 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
\r
6136 sibling = parent[sibling_name];
\r
6144 this.current = function() {
\r
6148 this.next = function(shallow) {
\r
6149 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
\r
6152 this.prev = function(shallow) {
\r
6153 return (node = findSibling(node, 'lastChild', 'lastSibling', shallow));
\r
6158 var transitional = {};
\r
6160 function unpack(lookup, data) {
\r
6163 function replace(value) {
\r
6164 return value.replace(/[A-Z]+/g, function(key) {
\r
6165 return replace(lookup[key]);
\r
6170 for (key in lookup) {
\r
6171 if (lookup.hasOwnProperty(key))
\r
6172 lookup[key] = replace(lookup[key]);
\r
6175 // Unpack and parse data into object map
\r
6176 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]/g, function(str, name, children) {
\r
6179 children = children.split(/\|/);
\r
6181 for (i = children.length - 1; i >= 0; i--)
\r
6182 map[children[i]] = 1;
\r
6184 transitional[name] = map;
\r
6188 // This is the XHTML 1.0 transitional elements with it's children packed to reduce it's size
\r
6189 // we will later include the attributes here and use it as a default for valid elements but it
\r
6190 // requires us to rewrite the serializer engine
\r
6192 Z : '#|H|K|N|O|P',
\r
6193 Y : '#|X|form|R|Q',
\r
6194 X : 'p|T|div|U|W|isindex|fieldset|table',
\r
6195 W : 'pre|hr|blockquote|address|center|noframes',
\r
6196 U : 'ul|ol|dl|menu|dir',
\r
6197 ZC : '#|p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
\r
6198 T : 'h1|h2|h3|h4|h5|h6',
\r
6201 ZA : '#|a|G|J|M|O|P',
\r
6202 R : '#|a|H|K|N|O',
\r
6204 P : 'ins|del|script',
\r
6205 O : 'input|select|textarea|label|button',
\r
6207 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
\r
6210 J : 'tt|i|b|u|s|strike',
\r
6211 I : 'big|small|font|basefont',
\r
6213 G : 'br|span|bdo',
\r
6214 F : 'object|applet|img|map|iframe'
\r
6217 'object[#|param|X|form|a|H|K|N|O|Q]' +
\r
6224 'applet[#|param|X|form|a|H|K|N|O|Q]' +
\r
6227 'map[X|form|Q|area]' +
\r
6229 'iframe[#|X|form|a|H|K|N|O|Q]' +
\r
6255 'select[optgroup|option]' +
\r
6256 'optgroup[option]' +
\r
6260 'button[#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
\r
6262 'ins[#|X|form|a|H|K|N|O|Q]' +
\r
6264 'del[#|X|form|a|H|K|N|O|Q]' +
\r
6266 'div[#|X|form|a|H|K|N|O|Q]' +
\r
6268 'li[#|X|form|a|H|K|N|O|Q]' +
\r
6272 'dd[#|X|form|a|H|K|N|O|Q]' +
\r
6277 'blockquote[#|X|form|a|H|K|N|O|Q]' +
\r
6279 'center[#|X|form|a|H|K|N|O|Q]' +
\r
6280 'noframes[#|X|form|a|H|K|N|O|Q]' +
\r
6282 'fieldset[#|legend|X|form|a|H|K|N|O|Q]' +
\r
6284 'table[caption|col|colgroup|thead|tfoot|tbody|tr]' +
\r
6287 'colgroup[col]' +
\r
6290 'th[#|X|form|a|H|K|N|O|Q]' +
\r
6291 'form[#|X|a|H|K|N|O|Q]' +
\r
6292 'noscript[#|X|form|a|H|K|N|O|Q]' +
\r
6293 'td[#|X|form|a|H|K|N|O|Q]' +
\r
6298 'body[#|X|form|a|H|K|N|O|Q]'
\r
6301 tinymce.dom.Schema = function() {
\r
6302 var t = this, elements = transitional;
\r
6304 t.isValid = function(name, child_name) {
\r
6305 var element = elements[name];
\r
6307 return !!(element && (!child_name || element[child_name]));
\r
6311 (function(tinymce) {
\r
6312 tinymce.dom.RangeUtils = function(dom) {
\r
6313 var INVISIBLE_CHAR = '\uFEFF';
\r
6315 this.walk = function(rng, callback) {
\r
6316 var startContainer = rng.startContainer,
\r
6317 startOffset = rng.startOffset,
\r
6318 endContainer = rng.endContainer,
\r
6319 endOffset = rng.endOffset,
\r
6320 ancestor, startPoint,
\r
6321 endPoint, node, parent, siblings, nodes;
\r
6323 // Handle table cell selection the table plugin enables
\r
6324 // you to fake select table cells and perform formatting actions on them
\r
6325 nodes = dom.select('td.mceSelected,th.mceSelected');
\r
6326 if (nodes.length > 0) {
\r
6327 tinymce.each(nodes, function(node) {
\r
6334 function collectSiblings(node, name, end_node) {
\r
6335 var siblings = [];
\r
6337 for (; node && node != end_node; node = node[name])
\r
6338 siblings.push(node);
\r
6343 function findEndPoint(node, root) {
\r
6345 if (node.parentNode == root)
\r
6348 node = node.parentNode;
\r
6352 function walkBoundary(start_node, end_node, next) {
\r
6353 var siblingName = next ? 'nextSibling' : 'previousSibling';
\r
6355 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
\r
6356 parent = node.parentNode;
\r
6357 siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
\r
6359 if (siblings.length) {
\r
6361 siblings.reverse();
\r
6363 callback(siblings);
\r
6368 // If index based start position then resolve it
\r
6369 if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
\r
6370 startContainer = startContainer.childNodes[startOffset];
\r
6372 // If index based end position then resolve it
\r
6373 if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
\r
6374 endContainer = endContainer.childNodes[Math.min(startOffset == endOffset ? endOffset : endOffset - 1, endContainer.childNodes.length - 1)];
\r
6376 // Find common ancestor and end points
\r
6377 ancestor = dom.findCommonAncestor(startContainer, endContainer);
\r
6380 if (startContainer == endContainer)
\r
6381 return callback([startContainer]);
\r
6383 // Process left side
\r
6384 for (node = startContainer; node; node = node.parentNode) {
\r
6385 if (node == endContainer)
\r
6386 return walkBoundary(startContainer, ancestor, true);
\r
6388 if (node == ancestor)
\r
6392 // Process right side
\r
6393 for (node = endContainer; node; node = node.parentNode) {
\r
6394 if (node == startContainer)
\r
6395 return walkBoundary(endContainer, ancestor);
\r
6397 if (node == ancestor)
\r
6401 // Find start/end point
\r
6402 startPoint = findEndPoint(startContainer, ancestor) || startContainer;
\r
6403 endPoint = findEndPoint(endContainer, ancestor) || endContainer;
\r
6406 walkBoundary(startContainer, startPoint, true);
\r
6408 // Walk the middle from start to end point
\r
6409 siblings = collectSiblings(
\r
6410 startPoint == startContainer ? startPoint : startPoint.nextSibling,
\r
6412 endPoint == endContainer ? endPoint.nextSibling : endPoint
\r
6415 if (siblings.length)
\r
6416 callback(siblings);
\r
6418 // Walk right leaf
\r
6419 walkBoundary(endContainer, endPoint);
\r
6422 /* this.split = function(rng) {
\r
6423 var startContainer = rng.startContainer,
\r
6424 startOffset = rng.startOffset,
\r
6425 endContainer = rng.endContainer,
\r
6426 endOffset = rng.endOffset;
\r
6428 function splitText(node, offset) {
\r
6429 if (offset == node.nodeValue.length)
\r
6430 node.appendData(INVISIBLE_CHAR);
\r
6432 node = node.splitText(offset);
\r
6434 if (node.nodeValue === INVISIBLE_CHAR)
\r
6435 node.nodeValue = '';
\r
6440 // Handle single text node
\r
6441 if (startContainer == endContainer) {
\r
6442 if (startContainer.nodeType == 3) {
\r
6443 if (startOffset != 0)
\r
6444 startContainer = endContainer = splitText(startContainer, startOffset);
\r
6446 if (endOffset - startOffset != startContainer.nodeValue.length)
\r
6447 splitText(startContainer, endOffset - startOffset);
\r
6450 // Split startContainer text node if needed
\r
6451 if (startContainer.nodeType == 3 && startOffset != 0) {
\r
6452 startContainer = splitText(startContainer, startOffset);
\r
6456 // Split endContainer text node if needed
\r
6457 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
\r
6458 endContainer = splitText(endContainer, endOffset).previousSibling;
\r
6459 endOffset = endContainer.nodeValue.length;
\r
6464 startContainer : startContainer,
\r
6465 startOffset : startOffset,
\r
6466 endContainer : endContainer,
\r
6467 endOffset : endOffset
\r
6473 tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
\r
6474 if (rng1 && rng2) {
\r
6475 // Compare native IE ranges
\r
6476 if (rng1.item || rng1.duplicate) {
\r
6477 // Both are control ranges and the selected element matches
\r
6478 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
\r
6481 // Both are text ranges and the range matches
\r
6482 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
\r
6485 // Compare w3c ranges
\r
6486 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
\r
6494 (function(tinymce) {
\r
6495 // Shorten class names
\r
6496 var DOM = tinymce.DOM, is = tinymce.is;
\r
6498 tinymce.create('tinymce.ui.Control', {
\r
6499 Control : function(id, s) {
\r
6501 this.settings = s = s || {};
\r
6502 this.rendered = false;
\r
6503 this.onRender = new tinymce.util.Dispatcher(this);
\r
6504 this.classPrefix = '';
\r
6505 this.scope = s.scope || this;
\r
6506 this.disabled = 0;
\r
6510 setDisabled : function(s) {
\r
6513 if (s != this.disabled) {
\r
6514 e = DOM.get(this.id);
\r
6516 // Add accessibility title for unavailable actions
\r
6517 if (e && this.settings.unavailable_prefix) {
\r
6519 this.prevTitle = e.title;
\r
6520 e.title = this.settings.unavailable_prefix + ": " + e.title;
\r
6522 e.title = this.prevTitle;
\r
6525 this.setState('Disabled', s);
\r
6526 this.setState('Enabled', !s);
\r
6527 this.disabled = s;
\r
6531 isDisabled : function() {
\r
6532 return this.disabled;
\r
6535 setActive : function(s) {
\r
6536 if (s != this.active) {
\r
6537 this.setState('Active', s);
\r
6542 isActive : function() {
\r
6543 return this.active;
\r
6546 setState : function(c, s) {
\r
6547 var n = DOM.get(this.id);
\r
6549 c = this.classPrefix + c;
\r
6552 DOM.addClass(n, c);
\r
6554 DOM.removeClass(n, c);
\r
6557 isRendered : function() {
\r
6558 return this.rendered;
\r
6561 renderHTML : function() {
\r
6564 renderTo : function(n) {
\r
6565 DOM.setHTML(n, this.renderHTML());
\r
6568 postRender : function() {
\r
6571 // Set pending states
\r
6572 if (is(t.disabled)) {
\r
6578 if (is(t.active)) {
\r
6585 remove : function() {
\r
6586 DOM.remove(this.id);
\r
6590 destroy : function() {
\r
6591 tinymce.dom.Event.clear(this.id);
\r
6595 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
\r
6596 Container : function(id, s) {
\r
6597 this.parent(id, s);
\r
6599 this.controls = [];
\r
6604 add : function(c) {
\r
6605 this.lookup[c.id] = c;
\r
6606 this.controls.push(c);
\r
6611 get : function(n) {
\r
6612 return this.lookup[n];
\r
6617 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
\r
6618 Separator : function(id, s) {
\r
6619 this.parent(id, s);
\r
6620 this.classPrefix = 'mceSeparator';
\r
6623 renderHTML : function() {
\r
6624 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
\r
6628 (function(tinymce) {
\r
6629 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
6631 tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
\r
6632 MenuItem : function(id, s) {
\r
6633 this.parent(id, s);
\r
6634 this.classPrefix = 'mceMenuItem';
\r
6637 setSelected : function(s) {
\r
6638 this.setState('Selected', s);
\r
6639 this.selected = s;
\r
6642 isSelected : function() {
\r
6643 return this.selected;
\r
6646 postRender : function() {
\r
6651 // Set pending state
\r
6652 if (is(t.selected))
\r
6653 t.setSelected(t.selected);
\r
6658 (function(tinymce) {
\r
6659 var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
\r
6661 tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
\r
6662 Menu : function(id, s) {
\r
6667 t.collapsed = false;
\r
6669 t.onAddItem = new tinymce.util.Dispatcher(this);
\r
6672 expand : function(d) {
\r
6676 walk(t, function(o) {
\r
6682 t.collapsed = false;
\r
6685 collapse : function(d) {
\r
6689 walk(t, function(o) {
\r
6695 t.collapsed = true;
\r
6698 isCollapsed : function() {
\r
6699 return this.collapsed;
\r
6702 add : function(o) {
\r
6704 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
\r
6706 this.onAddItem.dispatch(this, o);
\r
6708 return this.items[o.id] = o;
\r
6711 addSeparator : function() {
\r
6712 return this.add({separator : true});
\r
6715 addMenu : function(o) {
\r
6717 o = this.createMenu(o);
\r
6721 return this.add(o);
\r
6724 hasMenus : function() {
\r
6725 return this.menuCount !== 0;
\r
6728 remove : function(o) {
\r
6729 delete this.items[o.id];
\r
6732 removeAll : function() {
\r
6735 walk(t, function(o) {
\r
6747 createMenu : function(o) {
\r
6748 var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
\r
6750 m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
\r
6756 (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
7084 (function(tinymce) {
\r
7085 var DOM = tinymce.DOM;
\r
7087 tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
\r
7088 Button : function(id, s) {
\r
7089 this.parent(id, s);
\r
7090 this.classPrefix = 'mceButton';
\r
7093 renderHTML : function() {
\r
7094 var cp = this.classPrefix, s = this.settings, h, l;
\r
7096 l = DOM.encode(s.label || '');
\r
7097 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
7100 h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
\r
7102 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
\r
7107 postRender : function() {
\r
7108 var t = this, s = t.settings;
\r
7110 tinymce.dom.Event.add(t.id, 'click', function(e) {
\r
7111 if (!t.isDisabled())
\r
7112 return s.onclick.call(s.scope, e);
\r
7118 (function(tinymce) {
\r
7119 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
7121 tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
\r
7122 ListBox : function(id, s) {
\r
7129 t.onChange = new Dispatcher(t);
\r
7131 t.onPostRender = new Dispatcher(t);
\r
7133 t.onAdd = new Dispatcher(t);
\r
7135 t.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
7137 t.classPrefix = 'mceListBox';
\r
7140 select : function(va) {
\r
7141 var t = this, fv, f;
\r
7143 if (va == undefined)
\r
7144 return t.selectByIndex(-1);
\r
7146 // Is string or number make function selector
\r
7147 if (va && va.call)
\r
7155 // Do we need to do something?
\r
7156 if (va != t.selectedValue) {
\r
7158 each(t.items, function(o, i) {
\r
7161 t.selectByIndex(i);
\r
7167 t.selectByIndex(-1);
\r
7171 selectByIndex : function(idx) {
\r
7172 var t = this, e, o;
\r
7174 if (idx != t.selectedIndex) {
\r
7175 e = DOM.get(t.id + '_text');
\r
7179 t.selectedValue = o.value;
\r
7180 t.selectedIndex = idx;
\r
7181 DOM.setHTML(e, DOM.encode(o.title));
\r
7182 DOM.removeClass(e, 'mceTitle');
\r
7184 DOM.setHTML(e, DOM.encode(t.settings.title));
\r
7185 DOM.addClass(e, 'mceTitle');
\r
7186 t.selectedValue = t.selectedIndex = null;
\r
7193 add : function(n, v, o) {
\r
7197 o = tinymce.extend(o, {
\r
7203 t.onAdd.dispatch(t, o);
\r
7206 getLength : function() {
\r
7207 return this.items.length;
\r
7210 renderHTML : function() {
\r
7211 var h = '', t = this, s = t.settings, cp = t.classPrefix;
\r
7213 h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
\r
7214 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
7215 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
7216 h += '</tr></tbody></table>';
\r
7221 showMenu : function() {
\r
7222 var t = this, p1, p2, e = DOM.get(this.id), m;
\r
7224 if (t.isDisabled() || t.items.length == 0)
\r
7227 if (t.menu && t.menu.isMenuVisible)
\r
7228 return t.hideMenu();
\r
7230 if (!t.isMenuRendered) {
\r
7232 t.isMenuRendered = true;
\r
7235 p1 = DOM.getPos(this.settings.menu_container);
\r
7236 p2 = DOM.getPos(e);
\r
7239 m.settings.offset_x = p2.x;
\r
7240 m.settings.offset_y = p2.y;
\r
7241 m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
\r
7245 m.items[t.oldID].setSelected(0);
\r
7247 each(t.items, function(o) {
\r
7248 if (o.value === t.selectedValue) {
\r
7249 m.items[o.id].setSelected(1);
\r
7254 m.showMenu(0, e.clientHeight);
\r
7256 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7257 DOM.addClass(t.id, t.classPrefix + 'Selected');
\r
7259 //DOM.get(t.id + '_text').focus();
\r
7262 hideMenu : function(e) {
\r
7265 if (t.menu && t.menu.isMenuVisible) {
\r
7266 // Prevent double toogles by canceling the mouse click event to the button
\r
7267 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
\r
7270 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
7271 DOM.removeClass(t.id, t.classPrefix + 'Selected');
\r
7272 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7273 t.menu.hideMenu();
\r
7278 renderMenu : function() {
\r
7281 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
7283 'class' : t.classPrefix + 'Menu mceNoIcons',
\r
7288 m.onHideMenu.add(t.hideMenu, t);
\r
7291 title : t.settings.title,
\r
7292 'class' : 'mceMenuItemTitle',
\r
7293 onclick : function() {
\r
7294 if (t.settings.onselect('') !== false)
\r
7295 t.select(''); // Must be runned after
\r
7299 each(t.items, function(o) {
\r
7300 // No value then treat it as a title
\r
7301 if (o.value === undefined) {
\r
7304 'class' : 'mceMenuItemTitle',
\r
7305 onclick : function() {
\r
7306 if (t.settings.onselect('') !== false)
\r
7307 t.select(''); // Must be runned after
\r
7311 o.id = DOM.uniqueId();
\r
7312 o.onclick = function() {
\r
7313 if (t.settings.onselect(o.value) !== false)
\r
7314 t.select(o.value); // Must be runned after
\r
7321 t.onRenderMenu.dispatch(t, m);
\r
7325 postRender : function() {
\r
7326 var t = this, cp = t.classPrefix;
\r
7328 Event.add(t.id, 'click', t.showMenu, t);
\r
7329 Event.add(t.id + '_text', 'focus', function() {
\r
7330 if (!t._focused) {
\r
7331 t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
\r
7332 var idx = -1, v, kc = e.keyCode;
\r
7334 // Find current index
\r
7335 each(t.items, function(v, i) {
\r
7336 if (t.selectedValue == v.value)
\r
7342 v = t.items[idx - 1];
\r
7343 else if (kc == 40)
\r
7344 v = t.items[idx + 1];
\r
7345 else if (kc == 13) {
\r
7346 // Fake select on enter
\r
7347 v = t.selectedValue;
\r
7348 t.selectedValue = null; // Needs to be null to fake change
\r
7349 t.settings.onselect(v);
\r
7350 return Event.cancel(e);
\r
7355 t.select(v.value);
\r
7362 Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
\r
7364 // Old IE doesn't have hover on all elements
\r
7365 if (tinymce.isIE6 || !DOM.boxModel) {
\r
7366 Event.add(t.id, 'mouseover', function() {
\r
7367 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
7368 DOM.addClass(t.id, cp + 'Hover');
\r
7371 Event.add(t.id, 'mouseout', function() {
\r
7372 if (!DOM.hasClass(t.id, cp + 'Disabled'))
\r
7373 DOM.removeClass(t.id, cp + 'Hover');
\r
7377 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
7380 destroy : function() {
\r
7383 Event.clear(this.id + '_text');
\r
7384 Event.clear(this.id + '_open');
\r
7388 (function(tinymce) {
\r
7389 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
\r
7391 tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
\r
7392 NativeListBox : function(id, s) {
\r
7393 this.parent(id, s);
\r
7394 this.classPrefix = 'mceNativeListBox';
\r
7397 setDisabled : function(s) {
\r
7398 DOM.get(this.id).disabled = s;
\r
7401 isDisabled : function() {
\r
7402 return DOM.get(this.id).disabled;
\r
7405 select : function(va) {
\r
7406 var t = this, fv, f;
\r
7408 if (va == undefined)
\r
7409 return t.selectByIndex(-1);
\r
7411 // Is string or number make function selector
\r
7412 if (va && va.call)
\r
7420 // Do we need to do something?
\r
7421 if (va != t.selectedValue) {
\r
7423 each(t.items, function(o, i) {
\r
7426 t.selectByIndex(i);
\r
7432 t.selectByIndex(-1);
\r
7436 selectByIndex : function(idx) {
\r
7437 DOM.get(this.id).selectedIndex = idx + 1;
\r
7438 this.selectedValue = this.items[idx] ? this.items[idx].value : null;
\r
7441 add : function(n, v, a) {
\r
7447 if (t.isRendered())
\r
7448 DOM.add(DOM.get(this.id), 'option', a, n);
\r
7457 t.onAdd.dispatch(t, o);
\r
7460 getLength : function() {
\r
7461 return this.items.length;
\r
7464 renderHTML : function() {
\r
7467 h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
\r
7469 each(t.items, function(it) {
\r
7470 h += DOM.createHTML('option', {value : it.value}, it.title);
\r
7473 h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
\r
7478 postRender : function() {
\r
7481 t.rendered = true;
\r
7483 function onChange(e) {
\r
7484 var v = t.items[e.target.selectedIndex - 1];
\r
7486 if (v && (v = v.value)) {
\r
7487 t.onChange.dispatch(t, v);
\r
7489 if (t.settings.onselect)
\r
7490 t.settings.onselect(v);
\r
7494 Event.add(t.id, 'change', onChange);
\r
7496 // Accessibility keyhandler
\r
7497 Event.add(t.id, 'keydown', function(e) {
\r
7500 Event.remove(t.id, 'change', ch);
\r
7502 bf = Event.add(t.id, 'blur', function() {
\r
7503 Event.add(t.id, 'change', onChange);
\r
7504 Event.remove(t.id, 'blur', bf);
\r
7507 if (e.keyCode == 13 || e.keyCode == 32) {
\r
7509 return Event.cancel(e);
\r
7513 t.onPostRender.dispatch(t, DOM.get(t.id));
\r
7517 (function(tinymce) {
\r
7518 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
7520 tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
\r
7521 MenuButton : function(id, s) {
\r
7522 this.parent(id, s);
\r
7524 this.onRenderMenu = new tinymce.util.Dispatcher(this);
\r
7526 s.menu_container = s.menu_container || DOM.doc.body;
\r
7529 showMenu : function() {
\r
7530 var t = this, p1, p2, e = DOM.get(t.id), m;
\r
7532 if (t.isDisabled())
\r
7535 if (!t.isMenuRendered) {
\r
7537 t.isMenuRendered = true;
\r
7540 if (t.isMenuVisible)
\r
7541 return t.hideMenu();
\r
7543 p1 = DOM.getPos(t.settings.menu_container);
\r
7544 p2 = DOM.getPos(e);
\r
7547 m.settings.offset_x = p2.x;
\r
7548 m.settings.offset_y = p2.y;
\r
7549 m.settings.vp_offset_x = p2.x;
\r
7550 m.settings.vp_offset_y = p2.y;
\r
7551 m.settings.keyboard_focus = t._focused;
\r
7552 m.showMenu(0, e.clientHeight);
\r
7554 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7555 t.setState('Selected', 1);
\r
7557 t.isMenuVisible = 1;
\r
7560 renderMenu : function() {
\r
7563 m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
\r
7565 'class' : this.classPrefix + 'Menu',
\r
7566 icons : t.settings.icons
\r
7569 m.onHideMenu.add(t.hideMenu, t);
\r
7571 t.onRenderMenu.dispatch(t, m);
\r
7575 hideMenu : function(e) {
\r
7578 // Prevent double toogles by canceling the mouse click event to the button
\r
7579 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
\r
7582 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
\r
7583 t.setState('Selected', 0);
\r
7584 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7586 t.menu.hideMenu();
\r
7589 t.isMenuVisible = 0;
\r
7592 postRender : function() {
\r
7593 var t = this, s = t.settings;
\r
7595 Event.add(t.id, 'click', function() {
\r
7596 if (!t.isDisabled()) {
\r
7598 s.onclick(t.value);
\r
7607 (function(tinymce) {
\r
7608 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
\r
7610 tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
\r
7611 SplitButton : function(id, s) {
\r
7612 this.parent(id, s);
\r
7613 this.classPrefix = 'mceSplitButton';
\r
7616 renderHTML : function() {
\r
7617 var h, t = this, s = t.settings, h1;
\r
7619 h = '<tbody><tr>';
\r
7622 h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
\r
7624 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
\r
7626 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
7628 h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
\r
7629 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
7631 h += '</tr></tbody>';
\r
7633 return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
\r
7636 postRender : function() {
\r
7637 var t = this, s = t.settings;
\r
7640 Event.add(t.id + '_action', 'click', function() {
\r
7641 if (!t.isDisabled())
\r
7642 s.onclick(t.value);
\r
7646 Event.add(t.id + '_open', 'click', t.showMenu, t);
\r
7647 Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
\r
7648 Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
\r
7650 // Old IE doesn't have hover on all elements
\r
7651 if (tinymce.isIE6 || !DOM.boxModel) {
\r
7652 Event.add(t.id, 'mouseover', function() {
\r
7653 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
7654 DOM.addClass(t.id, 'mceSplitButtonHover');
\r
7657 Event.add(t.id, 'mouseout', function() {
\r
7658 if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
\r
7659 DOM.removeClass(t.id, 'mceSplitButtonHover');
\r
7664 destroy : function() {
\r
7667 Event.clear(this.id + '_action');
\r
7668 Event.clear(this.id + '_open');
\r
7673 (function(tinymce) {
\r
7674 var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
\r
7676 tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
\r
7677 ColorSplitButton : function(id, s) {
\r
7682 t.settings = s = tinymce.extend({
\r
7683 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
7685 default_color : '#888888'
\r
7688 t.onShowMenu = new tinymce.util.Dispatcher(t);
\r
7690 t.onHideMenu = new tinymce.util.Dispatcher(t);
\r
7692 t.value = s.default_color;
\r
7695 showMenu : function() {
\r
7696 var t = this, r, p, e, p2;
\r
7698 if (t.isDisabled())
\r
7701 if (!t.isMenuRendered) {
\r
7703 t.isMenuRendered = true;
\r
7706 if (t.isMenuVisible)
\r
7707 return t.hideMenu();
\r
7709 e = DOM.get(t.id);
\r
7710 DOM.show(t.id + '_menu');
\r
7711 DOM.addClass(e, 'mceSplitButtonSelected');
\r
7712 p2 = DOM.getPos(e);
\r
7713 DOM.setStyles(t.id + '_menu', {
\r
7715 top : p2.y + e.clientHeight,
\r
7720 Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7721 t.onShowMenu.dispatch(t);
\r
7724 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
\r
7725 if (e.keyCode == 27)
\r
7729 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
\r
7732 t.isMenuVisible = 1;
\r
7735 hideMenu : function(e) {
\r
7738 // Prevent double toogles by canceling the mouse click event to the button
\r
7739 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
\r
7742 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
\r
7743 DOM.removeClass(t.id, 'mceSplitButtonSelected');
\r
7744 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
\r
7745 Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
\r
7746 DOM.hide(t.id + '_menu');
\r
7749 t.onHideMenu.dispatch(t);
\r
7751 t.isMenuVisible = 0;
\r
7754 renderMenu : function() {
\r
7755 var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
\r
7757 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
7758 m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
\r
7759 DOM.add(m, 'span', {'class' : 'mceMenuLine'});
\r
7761 n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
\r
7762 tb = DOM.add(n, 'tbody');
\r
7764 // Generate color grid
\r
7766 each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
\r
7767 c = c.replace(/^#/, '');
\r
7770 tr = DOM.add(tb, 'tr');
\r
7771 i = s.grid_width - 1;
\r
7774 n = DOM.add(tr, 'td');
\r
7776 n = DOM.add(n, 'a', {
\r
7777 href : 'javascript:;',
\r
7779 backgroundColor : '#' + c
\r
7781 _mce_color : '#' + c
\r
7785 if (s.more_colors_func) {
\r
7786 n = DOM.add(tb, 'tr');
\r
7787 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
\r
7788 n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
\r
7790 Event.add(n, 'click', function(e) {
\r
7791 s.more_colors_func.call(s.more_colors_scope || this);
\r
7792 return Event.cancel(e); // Cancel to fix onbeforeunload problem
\r
7796 DOM.addClass(m, 'mceColorSplitMenu');
\r
7798 Event.add(t.id + '_menu', 'click', function(e) {
\r
7803 if (e.nodeName == 'A' && (c = e.getAttribute('_mce_color')))
\r
7806 return Event.cancel(e); // Prevent IE auto save warning
\r
7812 setColor : function(c) {
\r
7815 DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
\r
7819 t.settings.onselect(c);
\r
7822 postRender : function() {
\r
7823 var t = this, id = t.id;
\r
7826 DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
\r
7827 DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
\r
7830 destroy : function() {
\r
7833 Event.clear(this.id + '_menu');
\r
7834 Event.clear(this.id + '_more');
\r
7835 DOM.remove(this.id + '_menu');
\r
7840 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
\r
7841 renderHTML : function() {
\r
7842 var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
\r
7845 for (i=0; i<cl.length; i++) {
\r
7846 // Get current control, prev control, next control and if the control is a list box or not
\r
7851 // Add toolbar start
\r
7853 c = 'mceToolbarStart';
\r
7856 c += ' mceToolbarStartButton';
\r
7857 else if (co.SplitButton)
\r
7858 c += ' mceToolbarStartSplitButton';
\r
7859 else if (co.ListBox)
\r
7860 c += ' mceToolbarStartListBox';
\r
7862 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
7865 // Add toolbar end before list box and after the previous button
\r
7866 // This is to fix the o2k7 editor skins
\r
7867 if (pr && co.ListBox) {
\r
7868 if (pr.Button || pr.SplitButton)
\r
7869 h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
7872 // Render control HTML
\r
7874 // IE 8 quick fix, needed to propertly generate a hit area for anchors
\r
7876 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
\r
7878 h += '<td>' + co.renderHTML() + '</td>';
\r
7880 // Add toolbar start after list box and before the next button
\r
7881 // This is to fix the o2k7 editor skins
\r
7882 if (nx && co.ListBox) {
\r
7883 if (nx.Button || nx.SplitButton)
\r
7884 h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
\r
7888 c = 'mceToolbarEnd';
\r
7891 c += ' mceToolbarEndButton';
\r
7892 else if (co.SplitButton)
\r
7893 c += ' mceToolbarEndSplitButton';
\r
7894 else if (co.ListBox)
\r
7895 c += ' mceToolbarEndListBox';
\r
7897 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
\r
7899 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
7903 (function(tinymce) {
\r
7904 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
\r
7906 tinymce.create('tinymce.AddOnManager', {
\r
7911 onAdd : new Dispatcher(this),
\r
7913 get : function(n) {
\r
7914 return this.lookup[n];
\r
7917 requireLangPack : function(n) {
\r
7918 var s = tinymce.settings;
\r
7920 if (s && s.language)
\r
7921 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
\r
7924 add : function(id, o) {
\r
7925 this.items.push(o);
\r
7926 this.lookup[id] = o;
\r
7927 this.onAdd.dispatch(this, id, o);
\r
7932 load : function(n, u, cb, s) {
\r
7938 if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
\r
7939 u = tinymce.baseURL + '/' + u;
\r
7941 t.urls[n] = u.substring(0, u.lastIndexOf('/'));
\r
7942 tinymce.ScriptLoader.add(u, cb, s);
\r
7946 // Create plugin and theme managers
\r
7947 tinymce.PluginManager = new tinymce.AddOnManager();
\r
7948 tinymce.ThemeManager = new tinymce.AddOnManager();
\r
7951 (function(tinymce) {
\r
7953 var each = tinymce.each, extend = tinymce.extend,
\r
7954 DOM = tinymce.DOM, Event = tinymce.dom.Event,
\r
7955 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
7956 explode = tinymce.explode,
\r
7957 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
\r
7959 // Setup some URLs where the editor API is located and where the document is
\r
7960 tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
\r
7961 if (!/[\/\\]$/.test(tinymce.documentBaseURL))
\r
7962 tinymce.documentBaseURL += '/';
\r
7964 tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
\r
7966 tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
\r
7968 // Add before unload listener
\r
7969 // This was required since IE was leaking memory if you added and removed beforeunload listeners
\r
7970 // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
\r
7971 tinymce.onBeforeUnload = new Dispatcher(tinymce);
\r
7973 // Must be on window or IE will leak if the editor is placed in frame or iframe
\r
7974 Event.add(window, 'beforeunload', function(e) {
\r
7975 tinymce.onBeforeUnload.dispatch(tinymce, e);
\r
7978 tinymce.onAddEditor = new Dispatcher(tinymce);
\r
7980 tinymce.onRemoveEditor = new Dispatcher(tinymce);
\r
7982 tinymce.EditorManager = extend(tinymce, {
\r
7987 activeEditor : null,
\r
7989 init : function(s) {
\r
7990 var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
\r
7992 function execCallback(se, n, s) {
\r
7998 if (tinymce.is(f, 'string')) {
\r
7999 s = f.replace(/\.\w+$/, '');
\r
8000 s = s ? tinymce.resolve(s) : 0;
\r
8001 f = tinymce.resolve(f);
\r
8004 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
\r
8015 Event.add(document, 'init', function() {
\r
8018 execCallback(s, 'onpageload');
\r
8022 l = s.elements || '';
\r
8024 if(l.length > 0) {
\r
8025 each(explode(l), function(v) {
\r
8027 ed = new tinymce.Editor(v, s);
\r
8031 each(document.forms, function(f) {
\r
8032 each(f.elements, function(e) {
\r
8033 if (e.name === v) {
\r
8034 v = 'mce_editor_' + instanceCounter++;
\r
8035 DOM.setAttrib(e, 'id', v);
\r
8037 ed = new tinymce.Editor(v, s);
\r
8049 case "specific_textareas":
\r
8050 function hasClass(n, c) {
\r
8051 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
\r
8054 each(DOM.select('textarea'), function(v) {
\r
8055 if (s.editor_deselector && hasClass(v, s.editor_deselector))
\r
8058 if (!s.editor_selector || hasClass(v, s.editor_selector)) {
\r
8059 // Can we use the name
\r
8060 e = DOM.get(v.name);
\r
8064 // Generate unique name if missing or already exists
\r
8065 if (!v.id || t.get(v.id))
\r
8066 v.id = DOM.uniqueId();
\r
8068 ed = new tinymce.Editor(v.id, s);
\r
8076 // Call onInit when all editors are initialized
\r
8080 each(el, function(ed) {
\r
8083 if (!ed.initialized) {
\r
8085 ed.onInit.add(function() {
\r
8090 execCallback(s, 'oninit');
\r
8097 execCallback(s, 'oninit');
\r
8103 get : function(id) {
\r
8104 if (id === undefined)
\r
8105 return this.editors;
\r
8107 return this.editors[id];
\r
8110 getInstanceById : function(id) {
\r
8111 return this.get(id);
\r
8114 add : function(editor) {
\r
8115 var self = this, editors = self.editors;
\r
8117 // Add named and index editor instance
\r
8118 editors[editor.id] = editor;
\r
8119 editors.push(editor);
\r
8121 self._setActive(editor);
\r
8122 self.onAddEditor.dispatch(self, editor);
\r
8125 // Patch the tinymce.Editor instance with jQuery adapter logic
\r
8126 if (tinymce.adapter)
\r
8127 tinymce.adapter.patchEditor(editor);
\r
8133 remove : function(editor) {
\r
8134 var t = this, i, editors = t.editors;
\r
8136 // Not in the collection
\r
8137 if (!editors[editor.id])
\r
8140 delete editors[editor.id];
\r
8142 for (i = 0; i < editors.length; i++) {
\r
8143 if (editors[i] == editor) {
\r
8144 editors.splice(i, 1);
\r
8149 // Select another editor since the active one was removed
\r
8150 if (t.activeEditor == editor)
\r
8151 t._setActive(editors[0]);
\r
8154 t.onRemoveEditor.dispatch(t, editor);
\r
8159 execCommand : function(c, u, v) {
\r
8160 var t = this, ed = t.get(v), w;
\r
8162 // Manager commands
\r
8168 case "mceAddEditor":
\r
8169 case "mceAddControl":
\r
8171 new tinymce.Editor(v, t.settings).render();
\r
8175 case "mceAddFrameControl":
\r
8178 // Add tinyMCE global instance and tinymce namespace to specified window
\r
8179 w.tinyMCE = tinyMCE;
\r
8180 w.tinymce = tinymce;
\r
8182 tinymce.DOM.doc = w.document;
\r
8183 tinymce.DOM.win = w;
\r
8185 ed = new tinymce.Editor(v.element_id, v);
\r
8188 // Fix IE memory leaks
\r
8189 if (tinymce.isIE) {
\r
8192 w.detachEvent('onunload', clr);
\r
8193 w = w.tinyMCE = w.tinymce = null; // IE leak
\r
8196 w.attachEvent('onunload', clr);
\r
8199 v.page_window = null;
\r
8203 case "mceRemoveEditor":
\r
8204 case "mceRemoveControl":
\r
8210 case 'mceToggleEditor':
\r
8212 t.execCommand('mceAddControl', 0, v);
\r
8216 if (ed.isHidden())
\r
8224 // Run command on active editor
\r
8225 if (t.activeEditor)
\r
8226 return t.activeEditor.execCommand(c, u, v);
\r
8231 execInstanceCommand : function(id, c, u, v) {
\r
8232 var ed = this.get(id);
\r
8235 return ed.execCommand(c, u, v);
\r
8240 triggerSave : function() {
\r
8241 each(this.editors, function(e) {
\r
8246 addI18n : function(p, o) {
\r
8247 var lo, i18n = this.i18n;
\r
8249 if (!tinymce.is(p, 'string')) {
\r
8250 each(p, function(o, lc) {
\r
8251 each(o, function(o, g) {
\r
8252 each(o, function(o, k) {
\r
8253 if (g === 'common')
\r
8254 i18n[lc + '.' + k] = o;
\r
8256 i18n[lc + '.' + g + '.' + k] = o;
\r
8261 each(o, function(o, k) {
\r
8262 i18n[p + '.' + k] = o;
\r
8267 // Private methods
\r
8269 _setActive : function(editor) {
\r
8270 this.selectedInstance = this.activeEditor = editor;
\r
8275 (function(tinymce) {
\r
8276 // Shorten these names
\r
8277 var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
\r
8278 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
\r
8279 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
\r
8280 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
\r
8281 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
\r
8283 tinymce.create('tinymce.Editor', {
\r
8284 Editor : function(id, s) {
\r
8287 t.id = t.editorId = id;
\r
8289 t.execCommands = {};
\r
8290 t.queryStateCommands = {};
\r
8291 t.queryValueCommands = {};
\r
8293 t.isNotDirty = false;
\r
8297 // Add events to the editor
\r
8301 'onBeforeRenderUI',
\r
8341 'onBeforeSetContent',
\r
8343 'onBeforeGetContent',
\r
8357 'onBeforeExecCommand',
\r
8367 'onSetProgressState'
\r
8369 t[e] = new Dispatcher(t);
\r
8372 t.settings = s = extend({
\r
8375 docs_language : 'en',
\r
8382 document_base_url : tinymce.documentBaseURL,
\r
8383 add_form_submit_trigger : 1,
\r
8385 add_unload_trigger : 1,
\r
8387 relative_urls : 1,
\r
8388 remove_script_host : 1,
\r
8389 table_inline_editing : 0,
\r
8390 object_resizing : 1,
\r
8392 accessibility_focus : 1,
\r
8393 custom_shortcuts : 1,
\r
8394 custom_undo_redo_keyboard_shortcuts : 1,
\r
8395 custom_undo_redo_restore_selection : 1,
\r
8396 custom_undo_redo : 1,
\r
8397 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
8398 visual_table_class : 'mceItemTable',
\r
8400 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
\r
8401 apply_source_formatting : 1,
\r
8402 directionality : 'ltr',
\r
8403 forced_root_block : 'p',
\r
8404 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
8406 padd_empty_editor : 1,
\r
8409 force_p_newlines : 1,
\r
8410 indentation : '30px',
\r
8412 fix_table_elements : 1,
\r
8413 inline_styles : 1,
\r
8414 convert_fonts_to_spans : true
\r
8417 t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
\r
8418 base_uri : tinyMCE.baseURI
\r
8421 t.baseURI = tinymce.baseURI;
\r
8424 t.execCallback('setup', t);
\r
8427 render : function(nst) {
\r
8428 var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
\r
8430 // Page is not loaded yet, wait for it
\r
8431 if (!Event.domLoaded) {
\r
8432 Event.add(document, 'init', function() {
\r
8438 tinyMCE.settings = s;
\r
8440 // Element not found, then skip initialization
\r
8441 if (!t.getElement())
\r
8444 // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
\r
8445 // browser says it has contentEditable support but there is no visible caret
\r
8446 // We will remove this check ones Apple implements full contentEditable support
\r
8447 if (tinymce.isIDevice)
\r
8450 // Add hidden input for non input elements inside form elements
\r
8451 if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
\r
8452 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
\r
8454 if (tinymce.WindowManager)
\r
8455 t.windowManager = new tinymce.WindowManager(t);
\r
8457 if (s.encoding == 'xml') {
\r
8458 t.onGetContent.add(function(ed, o) {
\r
8460 o.content = DOM.encode(o.content);
\r
8464 if (s.add_form_submit_trigger) {
\r
8465 t.onSubmit.addToTop(function() {
\r
8466 if (t.initialized) {
\r
8473 if (s.add_unload_trigger) {
\r
8474 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
\r
8475 if (t.initialized && !t.destroyed && !t.isHidden())
\r
8476 t.save({format : 'raw', no_events : true});
\r
8480 tinymce.addUnload(t.destroy, t);
\r
8482 if (s.submit_patch) {
\r
8483 t.onBeforeRenderUI.add(function() {
\r
8484 var n = t.getElement().form;
\r
8489 // Already patched
\r
8490 if (n._mceOldSubmit)
\r
8493 // Check page uses id="submit" or name="submit" for it's submit button
\r
8494 if (!n.submit.nodeType && !n.submit.length) {
\r
8495 t.formElement = n;
\r
8496 n._mceOldSubmit = n.submit;
\r
8497 n.submit = function() {
\r
8498 // Save all instances
\r
8499 tinymce.triggerSave();
\r
8502 return t.formElement._mceOldSubmit(t.formElement);
\r
8511 function loadScripts() {
\r
8513 sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
\r
8515 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
\r
8516 ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
\r
8518 each(explode(s.plugins), function(p) {
\r
8519 if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
\r
8520 // Skip safari plugin, since it is removed as of 3.3b1
\r
8521 if (p == 'safari')
\r
8524 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
\r
8528 // Init when que is loaded
\r
8529 sl.loadQueue(function() {
\r
8538 init : function() {
\r
8539 var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
\r
8544 s.theme = s.theme.replace(/-/, '');
\r
8545 o = ThemeManager.get(s.theme);
\r
8546 t.theme = new o();
\r
8548 if (t.theme.init && s.init_theme)
\r
8549 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
\r
8552 // Create all plugins
\r
8553 each(explode(s.plugins.replace(/\-/g, '')), function(p) {
\r
8554 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
\r
8559 t.plugins[p] = po;
\r
8566 // Setup popup CSS path(s)
\r
8567 if (s.popup_css !== false) {
\r
8569 s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
\r
8571 s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
\r
8574 if (s.popup_css_add)
\r
8575 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
\r
8577 t.controlManager = new tinymce.ControlManager(t);
\r
8579 if (s.custom_undo_redo) {
\r
8580 // Add initial undo level
\r
8581 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
\r
8582 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo)) {
\r
8583 if (!t.undoManager.hasUndo())
\r
8584 t.undoManager.add();
\r
8588 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
\r
8589 if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
\r
8590 t.undoManager.add();
\r
8594 t.onExecCommand.add(function(ed, c) {
\r
8595 // Don't refresh the select lists until caret move
\r
8596 if (!/^(FontName|FontSize)$/.test(c))
\r
8600 // Remove ghost selections on images and tables in Gecko
\r
8602 function repaint(a, o) {
\r
8603 if (!o || !o.initial)
\r
8604 t.execCommand('mceRepaint');
\r
8607 t.onUndo.add(repaint);
\r
8608 t.onRedo.add(repaint);
\r
8609 t.onSetContent.add(repaint);
\r
8612 // Enables users to override the control factory
\r
8613 t.onBeforeRenderUI.dispatch(t, t.controlManager);
\r
8616 if (s.render_ui) {
\r
8617 w = s.width || e.style.width || e.offsetWidth;
\r
8618 h = s.height || e.style.height || e.offsetHeight;
\r
8619 t.orgDisplay = e.style.display;
\r
8620 re = /^[0-9\.]+(|px)$/i;
\r
8622 if (re.test('' + w))
\r
8623 w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
\r
8625 if (re.test('' + h))
\r
8626 h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
\r
8629 o = t.theme.renderUI({
\r
8633 deltaWidth : s.delta_width,
\r
8634 deltaHeight : s.delta_height
\r
8637 t.editorContainer = o.editorContainer;
\r
8641 // User specified a document.domain value
\r
8642 if (document.domain && location.hostname != document.domain)
\r
8643 tinymce.relaxedDomain = document.domain;
\r
8646 DOM.setStyles(o.sizeContainer || o.editorContainer, {
\r
8651 h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
\r
8655 t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
\r
8657 // We only need to override paths if we have to
\r
8658 // IE has a bug where it remove site absolute urls to relative ones if this is specified
\r
8659 if (s.document_base_url != tinymce.documentBaseURL)
\r
8660 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
\r
8662 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" /><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
\r
8664 if (tinymce.relaxedDomain)
\r
8665 t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
\r
8667 bi = s.body_id || 'tinymce';
\r
8668 if (bi.indexOf('=') != -1) {
\r
8669 bi = t.getParam('body_id', '', 'hash');
\r
8670 bi = bi[t.id] || bi;
\r
8673 bc = s.body_class || '';
\r
8674 if (bc.indexOf('=') != -1) {
\r
8675 bc = t.getParam('body_class', '', 'hash');
\r
8676 bc = bc[t.id] || '';
\r
8679 t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
\r
8681 // Domain relaxing enabled, then set document domain
\r
8682 if (tinymce.relaxedDomain) {
\r
8683 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
\r
8684 if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
\r
8685 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
8686 else if (tinymce.isOpera)
\r
8687 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';
\r
8691 n = DOM.add(o.iframeContainer, 'iframe', {
\r
8692 id : t.id + "_ifr",
\r
8693 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
\r
8694 frameBorder : '0',
\r
8701 t.contentAreaContainer = o.iframeContainer;
\r
8702 DOM.get(o.editorContainer).style.display = t.orgDisplay;
\r
8703 DOM.get(t.id).style.display = 'none';
\r
8705 if (!isIE || !tinymce.relaxedDomain)
\r
8708 e = n = o = null; // Cleanup
\r
8711 setupIframe : function() {
\r
8712 var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
\r
8714 // Setup iframe body
\r
8715 if (!isIE || !tinymce.relaxedDomain) {
\r
8717 d.write(t.iframeHTML);
\r
8721 // Design mode needs to be added here Ctrl+A will fail otherwise
\r
8725 d.designMode = 'On';
\r
8727 // Will fail on Gecko if the editor is placed in an hidden container element
\r
8728 // The design mode will be set ones the editor is focused
\r
8732 // IE needs to use contentEditable or it will display non secure items for HTTPS
\r
8734 // It will not steal focus if we hide it while setting contentEditable
\r
8739 b.contentEditable = true;
\r
8744 t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
\r
8745 keep_values : true,
\r
8746 url_converter : t.convertURL,
\r
8747 url_converter_scope : t,
\r
8748 hex_colors : s.force_hex_style_colors,
\r
8749 class_filter : s.class_filter,
\r
8750 update_styles : 1,
\r
8751 fix_ie_paragraphs : 1,
\r
8752 valid_styles : s.valid_styles
\r
8755 t.schema = new tinymce.dom.Schema();
\r
8757 t.serializer = new tinymce.dom.Serializer(extend(s, {
\r
8758 valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
\r
8763 t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
\r
8765 t.formatter = new tinymce.Formatter(this);
\r
8767 // Register default formats
\r
8768 t.formatter.register({
\r
8770 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
\r
8771 {selector : 'img,table', styles : {'float' : 'left'}}
\r
8775 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
\r
8776 {selector : 'img', styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
\r
8777 {selector : 'table', styles : {marginLeft : 'auto', marginRight : 'auto'}}
\r
8781 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
\r
8782 {selector : 'img,table', styles : {'float' : 'right'}}
\r
8786 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
\r
8790 {inline : 'strong'},
\r
8791 {inline : 'span', styles : {fontWeight : 'bold'}},
\r
8797 {inline : 'span', styles : {fontStyle : 'italic'}},
\r
8802 {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
\r
8807 {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
\r
8811 forecolor : {inline : 'span', styles : {color : '%value'}},
\r
8812 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}},
\r
8813 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
\r
8814 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
\r
8815 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
\r
8816 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
\r
8819 {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
\r
8820 {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
\r
8821 {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
\r
8825 // Register default block formats
\r
8826 each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
\r
8827 t.formatter.register(name, {block : name, remove : 'all'});
\r
8830 // Register user defined formats
\r
8831 t.formatter.register(t.settings.formats);
\r
8833 t.undoManager = new tinymce.UndoManager(t);
\r
8836 t.undoManager.onAdd.add(function(um, l) {
\r
8838 return t.onChange.dispatch(t, l, um);
\r
8841 t.undoManager.onUndo.add(function(um, l) {
\r
8842 return t.onUndo.dispatch(t, l, um);
\r
8845 t.undoManager.onRedo.add(function(um, l) {
\r
8846 return t.onRedo.dispatch(t, l, um);
\r
8849 t.forceBlocks = new tinymce.ForceBlocks(t, {
\r
8850 forced_root_block : s.forced_root_block
\r
8853 t.editorCommands = new tinymce.EditorCommands(t);
\r
8856 t.serializer.onPreProcess.add(function(se, o) {
\r
8857 return t.onPreProcess.dispatch(t, o, se);
\r
8860 t.serializer.onPostProcess.add(function(se, o) {
\r
8861 return t.onPostProcess.dispatch(t, o, se);
\r
8864 t.onPreInit.dispatch(t);
\r
8866 if (!s.gecko_spellcheck)
\r
8867 t.getBody().spellcheck = 0;
\r
8872 t.controlManager.onPostRender.dispatch(t, t.controlManager);
\r
8873 t.onPostRender.dispatch(t);
\r
8875 if (s.directionality)
\r
8876 t.getBody().dir = s.directionality;
\r
8879 t.getBody().style.whiteSpace = "nowrap";
\r
8881 if (s.custom_elements) {
\r
8882 function handleCustom(ed, o) {
\r
8883 each(explode(s.custom_elements), function(v) {
\r
8886 if (v.indexOf('~') === 0) {
\r
8887 v = v.substring(1);
\r
8892 o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' _mce_name="$1"$2>');
\r
8893 o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
\r
8897 t.onBeforeSetContent.add(handleCustom);
\r
8898 t.onPostProcess.add(function(ed, o) {
\r
8900 handleCustom(ed, o);
\r
8904 if (s.handle_node_change_callback) {
\r
8905 t.onNodeChange.add(function(ed, cm, n) {
\r
8906 t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
\r
8910 if (s.save_callback) {
\r
8911 t.onSaveContent.add(function(ed, o) {
\r
8912 var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
8919 if (s.onchange_callback) {
\r
8920 t.onChange.add(function(ed, l) {
\r
8921 t.execCallback('onchange_callback', t, l);
\r
8925 if (s.convert_newlines_to_brs) {
\r
8926 t.onBeforeSetContent.add(function(ed, o) {
\r
8928 o.content = o.content.replace(/\r?\n/g, '<br />');
\r
8932 if (s.fix_nesting && isIE) {
\r
8933 t.onBeforeSetContent.add(function(ed, o) {
\r
8934 o.content = t._fixNesting(o.content);
\r
8938 if (s.preformatted) {
\r
8939 t.onPostProcess.add(function(ed, o) {
\r
8940 o.content = o.content.replace(/^\s*<pre.*?>/, '');
\r
8941 o.content = o.content.replace(/<\/pre>\s*$/, '');
\r
8944 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
\r
8948 if (s.verify_css_classes) {
\r
8949 t.serializer.attribValueFilter = function(n, v) {
\r
8952 if (n == 'class') {
\r
8953 // Build regexp for classes
\r
8954 if (!t.classesRE) {
\r
8955 cl = t.dom.getClasses();
\r
8957 if (cl.length > 0) {
\r
8960 each (cl, function(o) {
\r
8961 s += (s ? '|' : '') + o['class'];
\r
8964 t.classesRE = new RegExp('(' + s + ')', 'gi');
\r
8968 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
\r
8975 if (s.cleanup_callback) {
\r
8976 t.onBeforeSetContent.add(function(ed, o) {
\r
8977 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
8980 t.onPreProcess.add(function(ed, o) {
\r
8982 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
\r
8985 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
\r
8988 t.onPostProcess.add(function(ed, o) {
\r
8990 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
\r
8993 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
\r
8997 if (s.save_callback) {
\r
8998 t.onGetContent.add(function(ed, o) {
\r
9000 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
\r
9004 if (s.handle_event_callback) {
\r
9005 t.onEvent.add(function(ed, e, o) {
\r
9006 if (t.execCallback('handle_event_callback', e, ed, o) === false)
\r
9011 // Add visual aids when new contents is added
\r
9012 t.onSetContent.add(function() {
\r
9013 t.addVisual(t.getBody());
\r
9016 // Remove empty contents
\r
9017 if (s.padd_empty_editor) {
\r
9018 t.onPostProcess.add(function(ed, o) {
\r
9019 o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
\r
9024 // Fix gecko link bug, when a link is placed at the end of block elements there is
\r
9025 // no way to move the caret behind the link. This fix adds a bogus br element after the link
\r
9026 function fixLinks(ed, o) {
\r
9027 each(ed.dom.select('a'), function(n) {
\r
9028 var pn = n.parentNode;
\r
9030 if (ed.dom.isBlock(pn) && pn.lastChild === n)
\r
9031 ed.dom.add(pn, 'br', {'_mce_bogus' : 1});
\r
9035 t.onExecCommand.add(function(ed, cmd) {
\r
9036 if (cmd === 'CreateLink')
\r
9040 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
\r
9042 if (!s.readonly) {
\r
9044 // Design mode must be set here once again to fix a bug where
\r
9045 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
\r
9046 d.designMode = 'Off';
\r
9047 d.designMode = 'On';
\r
9049 // Will fail on Gecko if the editor is placed in an hidden container element
\r
9050 // The design mode will be set ones the editor is focused
\r
9055 // A small timeout was needed since firefox will remove. Bug: #1838304
\r
9056 setTimeout(function () {
\r
9060 t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
\r
9061 t.startContent = t.getContent({format : 'raw'});
\r
9062 t.initialized = true;
\r
9064 t.onInit.dispatch(t);
\r
9065 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
\r
9066 t.execCallback('init_instance_callback', t);
\r
9068 t.nodeChanged({initial : 1});
\r
9070 // Load specified content CSS last
\r
9071 if (s.content_css) {
\r
9072 tinymce.each(explode(s.content_css), function(u) {
\r
9073 t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
\r
9077 // Handle auto focus
\r
9078 if (s.auto_focus) {
\r
9079 setTimeout(function () {
\r
9080 var ed = tinymce.get(s.auto_focus);
\r
9082 ed.selection.select(ed.getBody(), 1);
\r
9083 ed.selection.collapse(1);
\r
9084 ed.getWin().focus();
\r
9093 focus : function(sf) {
\r
9094 var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
\r
9097 // Get selected control element
\r
9098 ieRng = t.selection.getRng();
\r
9100 controlElm = ieRng.item(0);
\r
9103 // Is not content editable
\r
9105 t.getWin().focus();
\r
9107 // Restore selected control element
\r
9108 // This is needed when for example an image is selected within a
\r
9109 // layer a call to focus will then remove the control selection
\r
9110 if (controlElm && controlElm.ownerDocument == doc) {
\r
9111 ieRng = doc.body.createControlRange();
\r
9112 ieRng.addElement(controlElm);
\r
9118 if (tinymce.activeEditor != t) {
\r
9119 if ((oed = tinymce.activeEditor) != null)
\r
9120 oed.onDeactivate.dispatch(oed, t);
\r
9122 t.onActivate.dispatch(t, oed);
\r
9125 tinymce._setActive(t);
\r
9128 execCallback : function(n) {
\r
9129 var t = this, f = t.settings[n], s;
\r
9134 // Look through lookup
\r
9135 if (t.callbackLookup && (s = t.callbackLookup[n])) {
\r
9140 if (is(f, 'string')) {
\r
9141 s = f.replace(/\.\w+$/, '');
\r
9142 s = s ? tinymce.resolve(s) : 0;
\r
9143 f = tinymce.resolve(f);
\r
9144 t.callbackLookup = t.callbackLookup || {};
\r
9145 t.callbackLookup[n] = {func : f, scope : s};
\r
9148 return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
\r
9151 translate : function(s) {
\r
9152 var c = this.settings.language || 'en', i18n = tinymce.i18n;
\r
9157 return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
\r
9158 return i18n[c + '.' + b] || '{#' + b + '}';
\r
9162 getLang : function(n, dv) {
\r
9163 return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
\r
9166 getParam : function(n, dv, ty) {
\r
9167 var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
\r
9169 if (ty === 'hash') {
\r
9172 if (is(v, 'string')) {
\r
9173 each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
\r
9177 o[tr(v[0])] = tr(v[1]);
\r
9179 o[tr(v[0])] = tr(v);
\r
9190 nodeChanged : function(o) {
\r
9191 var t = this, s = t.selection, n = (isIE ? s.getNode() : s.getStart()) || t.getBody();
\r
9193 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
\r
9194 if (t.initialized) {
\r
9196 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
\r
9198 // Get parents and add them to object
\r
9200 t.dom.getParent(n, function(node) {
\r
9201 if (node.nodeName == 'BODY')
\r
9204 o.parents.push(node);
\r
9207 t.onNodeChange.dispatch(
\r
9209 o ? o.controlManager || t.controlManager : t.controlManager,
\r
9217 addButton : function(n, s) {
\r
9220 t.buttons = t.buttons || {};
\r
9224 addCommand : function(n, f, s) {
\r
9225 this.execCommands[n] = {func : f, scope : s || this};
\r
9228 addQueryStateHandler : function(n, f, s) {
\r
9229 this.queryStateCommands[n] = {func : f, scope : s || this};
\r
9232 addQueryValueHandler : function(n, f, s) {
\r
9233 this.queryValueCommands[n] = {func : f, scope : s || this};
\r
9236 addShortcut : function(pa, desc, cmd_func, sc) {
\r
9239 if (!t.settings.custom_shortcuts)
\r
9242 t.shortcuts = t.shortcuts || {};
\r
9244 if (is(cmd_func, 'string')) {
\r
9247 cmd_func = function() {
\r
9248 t.execCommand(c, false, null);
\r
9252 if (is(cmd_func, 'object')) {
\r
9255 cmd_func = function() {
\r
9256 t.execCommand(c[0], c[1], c[2]);
\r
9260 each(explode(pa), function(pa) {
\r
9263 scope : sc || this,
\r
9270 each(explode(pa, '+'), function(v) {
\r
9279 o.charCode = v.charCodeAt(0);
\r
9280 o.keyCode = v.toUpperCase().charCodeAt(0);
\r
9284 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
\r
9290 execCommand : function(cmd, ui, val, a) {
\r
9291 var t = this, s = 0, o, st;
\r
9293 if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
\r
9297 t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
\r
9301 // Command callback
\r
9302 if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
\r
9303 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9307 // Registred commands
\r
9308 if (o = t.execCommands[cmd]) {
\r
9309 st = o.func.call(o.scope, ui, val);
\r
9311 // Fall through on true
\r
9312 if (st !== true) {
\r
9313 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9318 // Plugin commands
\r
9319 each(t.plugins, function(p) {
\r
9320 if (p.execCommand && p.execCommand(cmd, ui, val)) {
\r
9321 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9331 if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
\r
9332 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9336 // Execute global commands
\r
9337 if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
\r
9338 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9342 // Editor commands
\r
9343 if (t.editorCommands.execCommand(cmd, ui, val)) {
\r
9344 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9348 // Browser commands
\r
9349 t.getDoc().execCommand(cmd, ui, val);
\r
9350 t.onExecCommand.dispatch(t, cmd, ui, val, a);
\r
9353 queryCommandState : function(cmd) {
\r
9354 var t = this, o, s;
\r
9356 // Is hidden then return undefined
\r
9357 if (t._isHidden())
\r
9360 // Registred commands
\r
9361 if (o = t.queryStateCommands[cmd]) {
\r
9362 s = o.func.call(o.scope);
\r
9364 // Fall though on true
\r
9369 // Registred commands
\r
9370 o = t.editorCommands.queryCommandState(cmd);
\r
9374 // Browser commands
\r
9376 return this.getDoc().queryCommandState(cmd);
\r
9378 // Fails sometimes see bug: 1896577
\r
9382 queryCommandValue : function(c) {
\r
9383 var t = this, o, s;
\r
9385 // Is hidden then return undefined
\r
9386 if (t._isHidden())
\r
9389 // Registred commands
\r
9390 if (o = t.queryValueCommands[c]) {
\r
9391 s = o.func.call(o.scope);
\r
9393 // Fall though on true
\r
9398 // Registred commands
\r
9399 o = t.editorCommands.queryCommandValue(c);
\r
9403 // Browser commands
\r
9405 return this.getDoc().queryCommandValue(c);
\r
9407 // Fails sometimes see bug: 1896577
\r
9411 show : function() {
\r
9414 DOM.show(t.getContainer());
\r
9419 hide : function() {
\r
9420 var t = this, d = t.getDoc();
\r
9422 // Fixed bug where IE has a blinking cursor left from the editor
\r
9424 d.execCommand('SelectAll');
\r
9426 // We must save before we hide so Safari doesn't crash
\r
9428 DOM.hide(t.getContainer());
\r
9429 DOM.setStyle(t.id, 'display', t.orgDisplay);
\r
9432 isHidden : function() {
\r
9433 return !DOM.isHidden(this.id);
\r
9436 setProgressState : function(b, ti, o) {
\r
9437 this.onSetProgressState.dispatch(this, b, ti, o);
\r
9442 load : function(o) {
\r
9443 var t = this, e = t.getElement(), h;
\r
9449 // Double encode existing entities in the value
\r
9450 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
\r
9454 t.onLoadContent.dispatch(t, o);
\r
9456 o.element = e = null;
\r
9462 save : function(o) {
\r
9463 var t = this, e = t.getElement(), h, f;
\r
9465 if (!e || !t.initialized)
\r
9471 // Add undo level will trigger onchange event
\r
9472 if (!o.no_events) {
\r
9473 t.undoManager.typing = 0;
\r
9474 t.undoManager.add();
\r
9478 h = o.content = t.getContent(o);
\r
9481 t.onSaveContent.dispatch(t, o);
\r
9485 if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
\r
9488 // Update hidden form element
\r
9489 if (f = DOM.getParent(t.id, 'form')) {
\r
9490 each(f.elements, function(e) {
\r
9491 if (e.name == t.id) {
\r
9500 o.element = e = null;
\r
9505 setContent : function(h, o) {
\r
9509 o.format = o.format || 'html';
\r
9514 t.onBeforeSetContent.dispatch(t, o);
\r
9516 // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
\r
9517 // It will also be impossible to place the caret in the editor unless there is a BR element present
\r
9518 if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
\r
9519 o.content = t.dom.setHTML(t.getBody(), '<br _mce_bogus="1" />');
\r
9523 o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
\r
9525 if (o.format != 'raw' && t.settings.cleanup) {
\r
9526 o.getInner = true;
\r
9527 o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
\r
9531 t.onSetContent.dispatch(t, o);
\r
9536 getContent : function(o) {
\r
9540 o.format = o.format || 'html';
\r
9544 t.onBeforeGetContent.dispatch(t, o);
\r
9546 if (o.format != 'raw' && t.settings.cleanup) {
\r
9547 o.getInner = true;
\r
9548 h = t.serializer.serialize(t.getBody(), o);
\r
9550 h = t.getBody().innerHTML;
\r
9552 h = h.replace(/^\s*|\s*$/g, '');
\r
9556 t.onGetContent.dispatch(t, o);
\r
9561 isDirty : function() {
\r
9564 return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
\r
9567 getContainer : function() {
\r
9571 t.container = DOM.get(t.editorContainer || t.id + '_parent');
\r
9573 return t.container;
\r
9576 getContentAreaContainer : function() {
\r
9577 return this.contentAreaContainer;
\r
9580 getElement : function() {
\r
9581 return DOM.get(this.settings.content_element || this.id);
\r
9584 getWin : function() {
\r
9587 if (!t.contentWindow) {
\r
9588 e = DOM.get(t.id + "_ifr");
\r
9591 t.contentWindow = e.contentWindow;
\r
9594 return t.contentWindow;
\r
9597 getDoc : function() {
\r
9600 if (!t.contentDocument) {
\r
9604 t.contentDocument = w.document;
\r
9607 return t.contentDocument;
\r
9610 getBody : function() {
\r
9611 return this.bodyElement || this.getDoc().body;
\r
9614 convertURL : function(u, n, e) {
\r
9615 var t = this, s = t.settings;
\r
9617 // Use callback instead
\r
9618 if (s.urlconverter_callback)
\r
9619 return t.execCallback('urlconverter_callback', u, e, true, n);
\r
9621 // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
\r
9622 if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
\r
9625 // Convert to relative
\r
9626 if (s.relative_urls)
\r
9627 return t.documentBaseURI.toRelative(u);
\r
9629 // Convert to absolute
\r
9630 u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
\r
9635 addVisual : function(e) {
\r
9636 var t = this, s = t.settings;
\r
9638 e = e || t.getBody();
\r
9640 if (!is(t.hasVisual))
\r
9641 t.hasVisual = s.visual;
\r
9643 each(t.dom.select('table,a', e), function(e) {
\r
9646 switch (e.nodeName) {
\r
9648 v = t.dom.getAttrib(e, 'border');
\r
9650 if (!v || v == '0') {
\r
9652 t.dom.addClass(e, s.visual_table_class);
\r
9654 t.dom.removeClass(e, s.visual_table_class);
\r
9660 v = t.dom.getAttrib(e, 'name');
\r
9664 t.dom.addClass(e, 'mceItemAnchor');
\r
9666 t.dom.removeClass(e, 'mceItemAnchor');
\r
9673 t.onVisualAid.dispatch(t, e, t.hasVisual);
\r
9676 remove : function() {
\r
9677 var t = this, e = t.getContainer();
\r
9679 t.removed = 1; // Cancels post remove event execution
\r
9682 t.execCallback('remove_instance_callback', t);
\r
9683 t.onRemove.dispatch(t);
\r
9685 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
\r
9686 t.onExecCommand.listeners = [];
\r
9688 tinymce.remove(t);
\r
9692 destroy : function(s) {
\r
9695 // One time is enough
\r
9700 tinymce.removeUnload(t.destroy);
\r
9701 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
\r
9704 if (t.theme && t.theme.destroy)
\r
9705 t.theme.destroy();
\r
9707 // Destroy controls, selection and dom
\r
9708 t.controlManager.destroy();
\r
9709 t.selection.destroy();
\r
9712 // Remove all events
\r
9714 // Don't clear the window or document if content editable
\r
9715 // is enabled since other instances might still be present
\r
9716 if (!t.settings.content_editable) {
\r
9717 Event.clear(t.getWin());
\r
9718 Event.clear(t.getDoc());
\r
9721 Event.clear(t.getBody());
\r
9722 Event.clear(t.formElement);
\r
9725 if (t.formElement) {
\r
9726 t.formElement.submit = t.formElement._mceOldSubmit;
\r
9727 t.formElement._mceOldSubmit = null;
\r
9730 t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
\r
9733 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
\r
9738 // Internal functions
\r
9740 _addEvents : function() {
\r
9741 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
\r
9742 var t = this, i, s = t.settings, lo = {
\r
9743 mouseup : 'onMouseUp',
\r
9744 mousedown : 'onMouseDown',
\r
9745 click : 'onClick',
\r
9746 keyup : 'onKeyUp',
\r
9747 keydown : 'onKeyDown',
\r
9748 keypress : 'onKeyPress',
\r
9749 submit : 'onSubmit',
\r
9750 reset : 'onReset',
\r
9751 contextmenu : 'onContextMenu',
\r
9752 dblclick : 'onDblClick',
\r
9753 paste : 'onPaste' // Doesn't work in all browsers yet
\r
9756 function eventHandler(e, o) {
\r
9759 // Don't fire events when it's removed
\r
9763 // Generic event handler
\r
9764 if (t.onEvent.dispatch(t, e, o) !== false) {
\r
9765 // Specific event handler
\r
9766 t[lo[e.fakeType || e.type]].dispatch(t, e, o);
\r
9771 each(lo, function(v, k) {
\r
9773 case 'contextmenu':
\r
9774 if (tinymce.isOpera) {
\r
9775 // Fake contextmenu on Opera
\r
9776 t.dom.bind(t.getBody(), 'mousedown', function(e) {
\r
9778 e.fakeType = 'contextmenu';
\r
9783 t.dom.bind(t.getBody(), k, eventHandler);
\r
9787 t.dom.bind(t.getBody(), k, function(e) {
\r
9794 t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
\r
9798 t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
\r
9802 t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
\r
9807 // Fixes bug where a specified document_base_uri could result in broken images
\r
9808 // This will also fix drag drop of images in Gecko
\r
9809 if (tinymce.isGecko) {
\r
9810 // Convert all images to absolute URLs
\r
9811 /* t.onSetContent.add(function(ed, o) {
\r
9812 each(ed.dom.select('img'), function(e) {
\r
9815 if (v = e.getAttribute('_mce_src'))
\r
9816 e.src = t.documentBaseURI.toAbsolute(v);
\r
9820 t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
\r
9825 if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('_mce_src')))
\r
9826 e.src = t.documentBaseURI.toAbsolute(v);
\r
9830 // Set various midas options in Gecko
\r
9832 function setOpts() {
\r
9833 var t = this, d = t.getDoc(), s = t.settings;
\r
9835 if (isGecko && !s.readonly) {
\r
9836 if (t._isHidden()) {
\r
9838 if (!s.content_editable)
\r
9839 d.designMode = 'On';
\r
9841 // Fails if it's hidden
\r
9846 // Try new Gecko method
\r
9847 d.execCommand("styleWithCSS", 0, false);
\r
9850 if (!t._isHidden())
\r
9851 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
\r
9854 if (!s.table_inline_editing)
\r
9855 try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
\r
9857 if (!s.object_resizing)
\r
9858 try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
\r
9862 t.onBeforeExecCommand.add(setOpts);
\r
9863 t.onMouseDown.add(setOpts);
\r
9866 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
\r
9867 // WebKit can't even do simple things like selecting an image
\r
9868 // This also fixes so it's possible to select mceItemAnchors
\r
9869 if (tinymce.isWebKit) {
\r
9870 t.onClick.add(function(ed, e) {
\r
9873 // Needs tobe the setBaseAndExtend or it will fail to select floated images
\r
9874 if (e.nodeName == 'IMG' || (e.nodeName == 'A' && t.dom.hasClass(e, 'mceItemAnchor')))
\r
9875 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
\r
9879 // Add node change handlers
\r
9880 t.onMouseUp.add(t.nodeChanged);
\r
9881 //t.onClick.add(t.nodeChanged);
\r
9882 t.onKeyUp.add(function(ed, e) {
\r
9883 var c = e.keyCode;
\r
9885 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
9889 // Add reset handler
\r
9890 t.onReset.add(function() {
\r
9891 t.setContent(t.startContent, {format : 'raw'});
\r
9895 if (s.custom_shortcuts) {
\r
9896 if (s.custom_undo_redo_keyboard_shortcuts) {
\r
9897 t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
\r
9898 t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
\r
9901 // Add default shortcuts for gecko
\r
9902 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
\r
9903 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
\r
9904 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
\r
9906 // BlockFormat shortcuts keys
\r
9907 for (i=1; i<=6; i++)
\r
9908 t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
\r
9910 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
\r
9911 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
\r
9912 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
\r
9914 function find(e) {
\r
9917 if (!e.altKey && !e.ctrlKey && !e.metaKey)
\r
9920 each(t.shortcuts, function(o) {
\r
9921 if (tinymce.isMac && o.ctrl != e.metaKey)
\r
9923 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
\r
9926 if (o.alt != e.altKey)
\r
9929 if (o.shift != e.shiftKey)
\r
9932 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
\r
9941 t.onKeyUp.add(function(ed, e) {
\r
9945 return Event.cancel(e);
\r
9948 t.onKeyPress.add(function(ed, e) {
\r
9952 return Event.cancel(e);
\r
9955 t.onKeyDown.add(function(ed, e) {
\r
9959 o.func.call(o.scope);
\r
9960 return Event.cancel(e);
\r
9965 if (tinymce.isIE) {
\r
9966 // Fix so resize will only update the width and height attributes not the styles of an image
\r
9967 // It will also block mceItemNoResize items
\r
9968 t.dom.bind(t.getDoc(), 'controlselect', function(e) {
\r
9969 var re = t.resizeInfo, cb;
\r
9973 // Don't do this action for non image elements
\r
9974 if (e.nodeName !== 'IMG')
\r
9978 t.dom.unbind(re.node, re.ev, re.cb);
\r
9980 if (!t.dom.hasClass(e, 'mceItemNoResize')) {
\r
9982 cb = t.dom.bind(e, ev, function(e) {
\r
9987 if (v = t.dom.getStyle(e, 'width')) {
\r
9988 t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
\r
9989 t.dom.setStyle(e, 'width', '');
\r
9992 if (v = t.dom.getStyle(e, 'height')) {
\r
9993 t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
\r
9994 t.dom.setStyle(e, 'height', '');
\r
9998 ev = 'resizestart';
\r
9999 cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
\r
10002 re = t.resizeInfo = {
\r
10009 t.onKeyDown.add(function(ed, e) {
\r
10010 switch (e.keyCode) {
\r
10012 // Fix IE control + backspace browser bug
\r
10013 if (t.selection.getRng().item) {
\r
10014 ed.dom.remove(t.selection.getRng().item(0));
\r
10015 return Event.cancel(e);
\r
10020 /*if (t.dom.boxModel) {
\r
10021 t.getBody().style.height = '100%';
\r
10023 Event.add(t.getWin(), 'resize', function(e) {
\r
10024 var docElm = t.getDoc().documentElement;
\r
10026 docElm.style.height = (docElm.offsetHeight - 10) + 'px';
\r
10031 if (tinymce.isOpera) {
\r
10032 t.onClick.add(function(ed, e) {
\r
10033 Event.prevent(e);
\r
10037 // Add custom undo/redo handlers
\r
10038 if (s.custom_undo_redo) {
\r
10039 function addUndo() {
\r
10040 t.undoManager.typing = 0;
\r
10041 t.undoManager.add();
\r
10044 t.dom.bind(t.getDoc(), 'focusout', function(e) {
\r
10045 if (!t.removed && t.undoManager.typing)
\r
10049 t.onKeyUp.add(function(ed, e) {
\r
10050 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
\r
10054 t.onKeyDown.add(function(ed, e) {
\r
10055 var rng, parent, bookmark;
\r
10057 // IE has a really odd bug where the DOM might include an node that doesn't have
\r
10058 // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
\r
10059 // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
\r
10060 // after you delete contents from it. See: #3008923
\r
10061 if (isIE && e.keyCode == 46) {
\r
10062 rng = t.selection.getRng();
\r
10064 if (rng.parentElement) {
\r
10065 parent = rng.parentElement();
\r
10067 // Select next word when ctrl key is used in combo with delete
\r
10069 rng.moveEnd('word', 1);
\r
10073 // Delete contents
\r
10074 t.selection.getSel().clear();
\r
10076 // Check if we are within the same parent
\r
10077 if (rng.parentElement() == parent) {
\r
10078 bookmark = t.selection.getBookmark();
\r
10081 // Update the HTML and hopefully it will remove the artifacts
\r
10082 parent.innerHTML = parent.innerHTML;
\r
10084 // And since it's IE it can sometimes produce an unknown runtime error
\r
10087 // Restore the caret position
\r
10088 t.selection.moveToBookmark(bookmark);
\r
10091 // Block the default delete behavior since it might be broken
\r
10092 e.preventDefault();
\r
10097 // Is caracter positon keys
\r
10098 if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
\r
10099 if (t.undoManager.typing)
\r
10105 if (!t.undoManager.typing) {
\r
10106 t.undoManager.add();
\r
10107 t.undoManager.typing = 1;
\r
10111 t.onMouseDown.add(function() {
\r
10112 if (t.undoManager.typing)
\r
10118 _isHidden : function() {
\r
10124 // Weird, wheres that cursor selection?
\r
10125 s = this.selection.getSel();
\r
10126 return (!s || !s.rangeCount || s.rangeCount == 0);
\r
10129 // Fix for bug #1867292
\r
10130 _fixNesting : function(s) {
\r
10133 s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
\r
10136 // Handle end element
\r
10141 if (c !== d[d.length - 1].tag) {
\r
10142 for (i=d.length - 1; i>=0; i--) {
\r
10143 if (d[i].tag === c) {
\r
10153 if (d.length && d[d.length - 1].close) {
\r
10154 a = a + '</' + d[d.length - 1].tag + '>';
\r
10160 if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
\r
10163 // Ignore closed ones
\r
10164 if (/\/>$/.test(a))
\r
10167 d.push({tag : c}); // Push start element
\r
10173 // End all open tags
\r
10174 for (i=d.length - 1; i>=0; i--)
\r
10175 s += '</' + d[i].tag + '>';
\r
10182 (function(tinymce) {
\r
10183 // Added for compression purposes
\r
10184 var each = tinymce.each, undefined, TRUE = true, FALSE = false;
\r
10186 tinymce.EditorCommands = function(editor) {
\r
10187 var dom = editor.dom,
\r
10188 selection = editor.selection,
\r
10189 commands = {state: {}, exec : {}, value : {}},
\r
10190 settings = editor.settings,
\r
10193 function execCommand(command, ui, value) {
\r
10196 command = command.toLowerCase();
\r
10197 if (func = commands.exec[command]) {
\r
10198 func(command, ui, value);
\r
10205 function queryCommandState(command) {
\r
10208 command = command.toLowerCase();
\r
10209 if (func = commands.state[command])
\r
10210 return func(command);
\r
10215 function queryCommandValue(command) {
\r
10218 command = command.toLowerCase();
\r
10219 if (func = commands.value[command])
\r
10220 return func(command);
\r
10225 function addCommands(command_list, type) {
\r
10226 type = type || 'exec';
\r
10228 each(command_list, function(callback, command) {
\r
10229 each(command.toLowerCase().split(','), function(command) {
\r
10230 commands[type][command] = callback;
\r
10235 // Expose public methods
\r
10236 tinymce.extend(this, {
\r
10237 execCommand : execCommand,
\r
10238 queryCommandState : queryCommandState,
\r
10239 queryCommandValue : queryCommandValue,
\r
10240 addCommands : addCommands
\r
10243 // Private methods
\r
10245 function execNativeCommand(command, ui, value) {
\r
10246 if (ui === undefined)
\r
10249 if (value === undefined)
\r
10252 return editor.getDoc().execCommand(command, ui, value);
\r
10255 function isFormatMatch(name) {
\r
10256 return editor.formatter.match(name);
\r
10259 function toggleFormat(name, value) {
\r
10260 editor.formatter.toggle(name, value ? {value : value} : undefined);
\r
10263 function storeSelection(type) {
\r
10264 bookmark = selection.getBookmark(type);
\r
10267 function restoreSelection() {
\r
10268 selection.moveToBookmark(bookmark);
\r
10271 // Add execCommand overrides
\r
10273 // Ignore these, added for compatibility
\r
10274 'mceResetDesignMode,mceBeginUndoLevel' : function() {},
\r
10276 // Add undo manager logic
\r
10277 'mceEndUndoLevel,mceAddUndoLevel' : function() {
\r
10278 editor.undoManager.add();
\r
10281 'Cut,Copy,Paste' : function(command) {
\r
10282 var doc = editor.getDoc(), failed;
\r
10284 // Try executing the native command
\r
10286 execNativeCommand(command);
\r
10288 // Command failed
\r
10292 // Present alert message about clipboard access not being available
\r
10293 if (failed || !doc.queryCommandSupported(command)) {
\r
10294 if (tinymce.isGecko) {
\r
10295 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
\r
10297 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
\r
10300 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
\r
10304 // Override unlink command
\r
10305 unlink : function(command) {
\r
10306 if (selection.isCollapsed())
\r
10307 selection.select(selection.getNode());
\r
10309 execNativeCommand(command);
\r
10310 selection.collapse(FALSE);
\r
10313 // Override justify commands to use the text formatter engine
\r
10314 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
10315 var align = command.substring(7);
\r
10317 // Remove all other alignments first
\r
10318 each('left,center,right,full'.split(','), function(name) {
\r
10319 if (align != name)
\r
10320 editor.formatter.remove('align' + name);
\r
10323 toggleFormat('align' + align);
\r
10326 // Override list commands to fix WebKit bug
\r
10327 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
10328 var listElm, listParent;
\r
10330 execNativeCommand(command);
\r
10332 // WebKit produces lists within block elements so we need to split them
\r
10333 // we will replace the native list creation logic to custom logic later on
\r
10334 // TODO: Remove this when the list creation logic is removed
\r
10335 listElm = dom.getParent(selection.getNode(), 'ol,ul');
\r
10337 listParent = listElm.parentNode;
\r
10339 // If list is within a text block then split that block
\r
10340 if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
\r
10341 storeSelection();
\r
10342 dom.split(listParent, listElm);
\r
10343 restoreSelection();
\r
10348 // Override commands to use the text formatter engine
\r
10349 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
10350 toggleFormat(command);
\r
10353 // Override commands to use the text formatter engine
\r
10354 'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
\r
10355 toggleFormat(command, value);
\r
10358 FontSize : function(command, ui, value) {
\r
10359 var fontClasses, fontSizes;
\r
10361 // Convert font size 1-7 to styles
\r
10362 if (value >= 1 && value <= 7) {
\r
10363 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
10364 fontClasses = tinymce.explode(settings.font_size_classes);
\r
10367 value = fontClasses[value - 1] || value;
\r
10369 value = fontSizes[value - 1] || value;
\r
10372 toggleFormat(command, value);
\r
10375 RemoveFormat : function(command) {
\r
10376 editor.formatter.remove(command);
\r
10379 mceBlockQuote : function(command) {
\r
10380 toggleFormat('blockquote');
\r
10383 FormatBlock : function(command, ui, value) {
\r
10384 return toggleFormat(value || 'p');
\r
10387 mceCleanup : function() {
\r
10388 var bookmark = selection.getBookmark();
\r
10390 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
\r
10392 selection.moveToBookmark(bookmark);
\r
10395 mceRemoveNode : function(command, ui, value) {
\r
10396 var node = value || selection.getNode();
\r
10398 // Make sure that the body node isn't removed
\r
10399 if (node != editor.getBody()) {
\r
10400 storeSelection();
\r
10401 editor.dom.remove(node, TRUE);
\r
10402 restoreSelection();
\r
10406 mceSelectNodeDepth : function(command, ui, value) {
\r
10409 dom.getParent(selection.getNode(), function(node) {
\r
10410 if (node.nodeType == 1 && counter++ == value) {
\r
10411 selection.select(node);
\r
10414 }, editor.getBody());
\r
10417 mceSelectNode : function(command, ui, value) {
\r
10418 selection.select(value);
\r
10421 mceInsertContent : function(command, ui, value) {
\r
10422 selection.setContent(value);
\r
10425 mceInsertRawHTML : function(command, ui, value) {
\r
10426 selection.setContent('tiny_mce_marker');
\r
10427 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, value));
\r
10430 mceSetContent : function(command, ui, value) {
\r
10431 editor.setContent(value);
\r
10434 'Indent,Outdent' : function(command) {
\r
10435 var intentValue, indentUnit, value;
\r
10437 // Setup indent level
\r
10438 intentValue = settings.indentation;
\r
10439 indentUnit = /[a-z%]+$/i.exec(intentValue);
\r
10440 intentValue = parseInt(intentValue);
\r
10442 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
\r
10443 each(selection.getSelectedBlocks(), function(element) {
\r
10444 if (command == 'outdent') {
\r
10445 value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
\r
10446 dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
\r
10448 dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
\r
10451 execNativeCommand(command);
\r
10454 mceRepaint : function() {
\r
10457 if (tinymce.isGecko) {
\r
10459 storeSelection(TRUE);
\r
10461 if (selection.getSel())
\r
10462 selection.getSel().selectAllChildren(editor.getBody());
\r
10464 selection.collapse(TRUE);
\r
10465 restoreSelection();
\r
10472 mceToggleFormat : function(command, ui, value) {
\r
10473 editor.formatter.toggle(value);
\r
10476 InsertHorizontalRule : function() {
\r
10477 selection.setContent('<hr />');
\r
10480 mceToggleVisualAid : function() {
\r
10481 editor.hasVisual = !editor.hasVisual;
\r
10482 editor.addVisual();
\r
10485 mceReplaceContent : function(command, ui, value) {
\r
10486 selection.setContent(value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
\r
10489 mceInsertLink : function(command, ui, value) {
\r
10490 var link = dom.getParent(selection.getNode(), 'a');
\r
10492 if (tinymce.is(value, 'string'))
\r
10493 value = {href : value};
\r
10496 execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
\r
10497 each(dom.select('a[href=javascript:mctmp(0);]'), function(link) {
\r
10498 dom.setAttribs(link, value);
\r
10502 dom.setAttribs(link, value);
\r
10504 editor.dom.remove(link, TRUE);
\r
10508 selectAll : function() {
\r
10509 var root = dom.getRoot(), rng = dom.createRng();
\r
10511 rng.setStart(root, 0);
\r
10512 rng.setEnd(root, root.childNodes.length);
\r
10514 editor.selection.setRng(rng);
\r
10518 // Add queryCommandState overrides
\r
10520 // Override justify commands
\r
10521 'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
\r
10522 return isFormatMatch('align' + command.substring(7));
\r
10525 'Bold,Italic,Underline,Strikethrough' : function(command) {
\r
10526 return isFormatMatch(command);
\r
10529 mceBlockQuote : function() {
\r
10530 return isFormatMatch('blockquote');
\r
10533 Outdent : function() {
\r
10536 if (settings.inline_styles) {
\r
10537 if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
10540 if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
\r
10544 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
\r
10547 'InsertUnorderedList,InsertOrderedList' : function(command) {
\r
10548 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
\r
10552 // Add queryCommandValue overrides
\r
10554 'FontSize,FontName' : function(command) {
\r
10555 var value = 0, parent;
\r
10557 if (parent = dom.getParent(selection.getNode(), 'span')) {
\r
10558 if (command == 'fontsize')
\r
10559 value = parent.style.fontSize;
\r
10561 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
\r
10568 // Add undo manager logic
\r
10569 if (settings.custom_undo_redo) {
\r
10571 Undo : function() {
\r
10572 editor.undoManager.undo();
\r
10575 Redo : function() {
\r
10576 editor.undoManager.redo();
\r
10582 (function(tinymce) {
\r
10583 var Dispatcher = tinymce.util.Dispatcher;
\r
10585 tinymce.UndoManager = function(editor) {
\r
10586 var self, index = 0, data = [];
\r
10588 function getContent() {
\r
10589 return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
\r
10595 onAdd : new Dispatcher(self),
\r
10596 onUndo : new Dispatcher(self),
\r
10597 onRedo : new Dispatcher(self),
\r
10599 add : function(level) {
\r
10600 var i, settings = editor.settings, lastLevel;
\r
10602 level = level || {};
\r
10603 level.content = getContent();
\r
10605 // Add undo level if needed
\r
10606 lastLevel = data[index];
\r
10607 if (lastLevel && lastLevel.content == level.content) {
\r
10608 if (index > 0 || data.length == 1)
\r
10612 // Time to compress
\r
10613 if (settings.custom_undo_redo_levels) {
\r
10614 if (data.length > settings.custom_undo_redo_levels) {
\r
10615 for (i = 0; i < data.length - 1; i++)
\r
10616 data[i] = data[i + 1];
\r
10619 index = data.length;
\r
10623 // Get a non intrusive normalized bookmark
\r
10624 level.bookmark = editor.selection.getBookmark(2, true);
\r
10626 // Crop array if needed
\r
10627 if (index < data.length - 1) {
\r
10628 // Treat first level as initial
\r
10632 data.length = index + 1;
\r
10635 data.push(level);
\r
10636 index = data.length - 1;
\r
10638 self.onAdd.dispatch(self, level);
\r
10639 editor.isNotDirty = 0;
\r
10644 undo : function() {
\r
10647 if (self.typing) {
\r
10653 level = data[--index];
\r
10655 editor.setContent(level.content, {format : 'raw'});
\r
10656 editor.selection.moveToBookmark(level.bookmark);
\r
10658 self.onUndo.dispatch(self, level);
\r
10664 redo : function() {
\r
10667 if (index < data.length - 1) {
\r
10668 level = data[++index];
\r
10670 editor.setContent(level.content, {format : 'raw'});
\r
10671 editor.selection.moveToBookmark(level.bookmark);
\r
10673 self.onRedo.dispatch(self, level);
\r
10679 clear : function() {
\r
10681 index = self.typing = 0;
\r
10684 hasUndo : function() {
\r
10685 return index > 0 || self.typing;
\r
10688 hasRedo : function() {
\r
10689 return index < data.length - 1;
\r
10695 (function(tinymce) {
\r
10697 var Event = tinymce.dom.Event,
\r
10698 isIE = tinymce.isIE,
\r
10699 isGecko = tinymce.isGecko,
\r
10700 isOpera = tinymce.isOpera,
\r
10701 each = tinymce.each,
\r
10702 extend = tinymce.extend,
\r
10706 function cloneFormats(node) {
\r
10707 var clone, temp, inner;
\r
10710 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
\r
10712 temp = node.cloneNode(false);
\r
10713 temp.appendChild(clone);
\r
10716 clone = inner = node.cloneNode(false);
\r
10719 clone.removeAttribute('id');
\r
10721 } while (node = node.parentNode);
\r
10724 return {wrapper : clone, inner : inner};
\r
10727 // Checks if the selection/caret is at the end of the specified block element
\r
10728 function isAtEnd(rng, par) {
\r
10729 var rng2 = par.ownerDocument.createRange();
\r
10731 rng2.setStart(rng.endContainer, rng.endOffset);
\r
10732 rng2.setEndAfter(par);
\r
10734 // 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
10735 return rng2.cloneContents().textContent.length == 0;
\r
10738 function isEmpty(n) {
\r
10741 n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
\r
10742 n = n.replace(/<[^>]+>/g, ''); // Remove all tags
\r
10744 return n.replace(/[ \u00a0\t\r\n]+/g, '') == '';
\r
10747 function splitList(selection, dom, li) {
\r
10748 var listBlock, block;
\r
10750 if (isEmpty(li)) {
\r
10751 listBlock = dom.getParent(li, 'ul,ol');
\r
10753 if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
\r
10754 dom.split(listBlock, li);
\r
10755 block = dom.create('p', 0, '<br _mce_bogus="1" />');
\r
10756 dom.replace(block, li);
\r
10757 selection.select(block, 1);
\r
10766 tinymce.create('tinymce.ForceBlocks', {
\r
10767 ForceBlocks : function(ed) {
\r
10768 var t = this, s = ed.settings, elm;
\r
10772 elm = (s.forced_root_block || 'p').toLowerCase();
\r
10773 s.element = elm.toUpperCase();
\r
10775 ed.onPreInit.add(t.setup, t);
\r
10777 t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi');
\r
10778 t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
10779 t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
\r
10780 t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi');
\r
10781 t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
\r
10783 function padd(ed, o) {
\r
10785 o.content = o.content.replace(t.reOpera, '</' + elm + '>');
\r
10787 o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
\r
10789 if (!isIE && !isOpera && o.set) {
\r
10790 // Use instead of BR in padded paragraphs
\r
10791 o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
\r
10792 o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
\r
10794 o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
\r
10797 ed.onBeforeSetContent.add(padd);
\r
10798 ed.onPostProcess.add(padd);
\r
10800 if (s.forced_root_block) {
\r
10801 ed.onInit.add(t.forceRoots, t);
\r
10802 ed.onSetContent.add(t.forceRoots, t);
\r
10803 ed.onBeforeGetContent.add(t.forceRoots, t);
\r
10807 setup : function() {
\r
10808 var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
\r
10810 // Force root blocks when typing and when getting output
\r
10811 if (s.forced_root_block) {
\r
10812 ed.onBeforeExecCommand.add(t.forceRoots, t);
\r
10813 ed.onKeyUp.add(t.forceRoots, t);
\r
10814 ed.onPreProcess.add(t.forceRoots, t);
\r
10817 if (s.force_br_newlines) {
\r
10818 // Force IE to produce BRs on enter
\r
10820 ed.onKeyPress.add(function(ed, e) {
\r
10823 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
\r
10824 selection.setContent('<br id="__" /> ', {format : 'raw'});
\r
10825 n = dom.get('__');
\r
10826 n.removeAttribute('id');
\r
10827 selection.select(n);
\r
10828 selection.collapse();
\r
10829 return Event.cancel(e);
\r
10835 if (s.force_p_newlines) {
\r
10837 ed.onKeyPress.add(function(ed, e) {
\r
10838 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
\r
10842 // Ungly hack to for IE to preserve the formatting when you press
\r
10843 // enter at the end of a block element with formatted contents
\r
10844 // This logic overrides the browsers default logic with
\r
10845 // custom logic that enables us to control the output
\r
10846 tinymce.addUnload(function() {
\r
10847 t._previousFormats = 0; // Fix IE leak
\r
10850 ed.onKeyPress.add(function(ed, e) {
\r
10851 t._previousFormats = 0;
\r
10853 // Clone the current formats, this will later be applied to the new block contents
\r
10854 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
\r
10855 t._previousFormats = cloneFormats(ed.selection.getStart());
\r
10858 ed.onKeyUp.add(function(ed, e) {
\r
10859 // Let IE break the element and the wrap the new caret location in the previous formats
\r
10860 if (e.keyCode == 13 && !e.shiftKey) {
\r
10861 var parent = ed.selection.getStart(), fmt = t._previousFormats;
\r
10863 // Parent is an empty block
\r
10864 if (!parent.hasChildNodes()) {
\r
10865 parent = dom.getParent(parent, dom.isBlock);
\r
10868 parent.innerHTML = '';
\r
10870 if (t._previousFormats) {
\r
10871 parent.appendChild(fmt.wrapper);
\r
10872 fmt.inner.innerHTML = '\uFEFF';
\r
10874 parent.innerHTML = '\uFEFF';
\r
10876 selection.select(parent, 1);
\r
10877 ed.getDoc().execCommand('Delete', false, null);
\r
10885 ed.onKeyDown.add(function(ed, e) {
\r
10886 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
\r
10887 t.backspaceDelete(e, e.keyCode == 8);
\r
10892 // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
\r
10893 if (tinymce.isWebKit) {
\r
10894 function insertBr(ed) {
\r
10895 var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
\r
10897 // Insert BR element
\r
10898 rng.insertNode(br = dom.create('br'));
\r
10900 // Place caret after BR
\r
10901 rng.setStartAfter(br);
\r
10902 rng.setEndAfter(br);
\r
10903 selection.setRng(rng);
\r
10905 // Could not place caret after BR then insert an nbsp entity and move the caret
\r
10906 if (selection.getSel().focusNode == br.previousSibling) {
\r
10907 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
\r
10908 selection.collapse(TRUE);
\r
10911 // Create a temporary DIV after the BR and get the position as it
\r
10912 // seems like getPos() returns 0 for text nodes and BR elements.
\r
10913 dom.insertAfter(div, br);
\r
10914 divYPos = dom.getPos(div).y;
\r
10917 // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
\r
10918 if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
\r
10919 ed.getWin().scrollTo(0, divYPos);
\r
10922 ed.onKeyPress.add(function(ed, e) {
\r
10923 if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
\r
10930 // Padd empty inline elements within block elements
\r
10931 // For example: <p><strong><em></em></strong></p> becomes <p><strong><em> </em></strong></p>
\r
10932 ed.onPreProcess.add(function(ed, o) {
\r
10933 each(dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
\r
10934 if (isEmpty(p)) {
\r
10935 each(dom.select('span,em,strong,b,i', o.node), function(n) {
\r
10936 if (!n.hasChildNodes()) {
\r
10937 n.appendChild(ed.getDoc().createTextNode('\u00a0'));
\r
10938 return FALSE; // Break the loop one padding is enough
\r
10945 // IE specific fixes
\r
10947 // Replaces IE:s auto generated paragraphs with the specified element name
\r
10948 if (s.element != 'P') {
\r
10949 ed.onKeyPress.add(function(ed, e) {
\r
10950 t.lastElm = selection.getNode().nodeName;
\r
10953 ed.onKeyUp.add(function(ed, e) {
\r
10954 var bl, n = selection.getNode(), b = ed.getBody();
\r
10956 if (b.childNodes.length === 1 && n.nodeName == 'P') {
\r
10957 n = dom.rename(n, s.element);
\r
10958 selection.select(n);
\r
10959 selection.collapse();
\r
10960 ed.nodeChanged();
\r
10961 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
\r
10962 bl = dom.getParent(n, 'p');
\r
10965 dom.rename(bl, s.element);
\r
10966 ed.nodeChanged();
\r
10974 find : function(n, t, s) {
\r
10975 var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
\r
10977 while (n = w.nextNode()) {
\r
10981 if (t == 0 && n == s)
\r
10985 if (t == 1 && c == s)
\r
10992 forceRoots : function(ed, e) {
\r
10993 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
10994 var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
\r
10996 // Fix for bug #1863847
\r
10997 //if (e && e.keyCode == 13)
\r
11000 // Wrap non blocks into blocks
\r
11001 for (i = nl.length - 1; i >= 0; i--) {
\r
11004 // Ignore internal elements
\r
11005 if (nx.nodeType === 1 && nx.getAttribute('_mce_type')) {
\r
11010 // Is text or non block element
\r
11011 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
\r
11013 // Create new block but ignore whitespace
\r
11014 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
\r
11015 // Store selection
\r
11016 if (si == -2 && r) {
\r
11018 // If selection is element then mark it
\r
11019 if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
\r
11020 // Save the id of the selected element
\r
11021 eid = n.getAttribute("id");
\r
11022 n.setAttribute("id", "__mce");
\r
11024 // If element is inside body, might not be the case in contentEdiable mode
\r
11025 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
\r
11026 so = r.startOffset;
\r
11027 eo = r.endOffset;
\r
11028 si = t.find(b, 0, r.startContainer);
\r
11029 ei = t.find(b, 0, r.endContainer);
\r
11033 // Force control range into text range
\r
11035 tr = d.body.createTextRange();
\r
11036 tr.moveToElementText(r.item(0));
\r
11040 tr = d.body.createTextRange();
\r
11041 tr.moveToElementText(b);
\r
11043 bp = tr.move('character', c) * -1;
\r
11045 tr = r.duplicate();
\r
11047 sp = tr.move('character', c) * -1;
\r
11049 tr = r.duplicate();
\r
11051 le = (tr.move('character', c) * -1) - sp;
\r
11058 // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
\r
11059 // See: http://support.microsoft.com/kb/829907
\r
11060 bl = ed.dom.create(ed.settings.forced_root_block);
\r
11061 nx.parentNode.replaceChild(bl, nx);
\r
11062 bl.appendChild(nx);
\r
11065 if (bl.hasChildNodes())
\r
11066 bl.insertBefore(nx, bl.firstChild);
\r
11068 bl.appendChild(nx);
\r
11071 bl = null; // Time to create new block
\r
11074 // Restore selection
\r
11077 bl = b.getElementsByTagName(ed.settings.element)[0];
\r
11078 r = d.createRange();
\r
11080 // Select last location or generated block
\r
11082 r.setStart(t.find(b, 1, si), so);
\r
11084 r.setStart(bl, 0);
\r
11086 // Select last location or generated block
\r
11088 r.setEnd(t.find(b, 1, ei), eo);
\r
11093 s.removeAllRanges();
\r
11098 r = s.createRange();
\r
11099 r.moveToElementText(b);
\r
11101 r.moveStart('character', si);
\r
11102 r.moveEnd('character', ei);
\r
11108 } else if (!isIE && (n = ed.dom.get('__mce'))) {
\r
11109 // Restore the id of the selected element
\r
11111 n.setAttribute('id', eid);
\r
11113 n.removeAttribute('id');
\r
11115 // Move caret before selected element
\r
11116 r = d.createRange();
\r
11117 r.setStartBefore(n);
\r
11118 r.setEndBefore(n);
\r
11123 getParentBlock : function(n) {
\r
11124 var d = this.dom;
\r
11126 return d.getParent(n, d.isBlock);
\r
11129 insertPara : function(e) {
\r
11130 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
11131 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
11133 // If root blocks are forced then use Operas default behavior since it's really good
\r
11134 // Removed due to bug: #1853816
\r
11135 // if (se.forced_root_block && isOpera)
\r
11138 // Setup before range
\r
11139 rb = d.createRange();
\r
11141 // If is before the first block element and in body, then move it into first block element
\r
11142 rb.setStart(s.anchorNode, s.anchorOffset);
\r
11143 rb.collapse(TRUE);
\r
11145 // Setup after range
\r
11146 ra = d.createRange();
\r
11148 // If is before the first block element and in body, then move it into first block element
\r
11149 ra.setStart(s.focusNode, s.focusOffset);
\r
11150 ra.collapse(TRUE);
\r
11152 // Setup start/end points
\r
11153 dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
\r
11154 sn = dir ? s.anchorNode : s.focusNode;
\r
11155 so = dir ? s.anchorOffset : s.focusOffset;
\r
11156 en = dir ? s.focusNode : s.anchorNode;
\r
11157 eo = dir ? s.focusOffset : s.anchorOffset;
\r
11159 // If selection is in empty table cell
\r
11160 if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
\r
11161 if (sn.firstChild.nodeName == 'BR')
\r
11162 dom.remove(sn.firstChild); // Remove BR
\r
11164 // Create two new block elements
\r
11165 if (sn.childNodes.length == 0) {
\r
11166 ed.dom.add(sn, se.element, null, '<br />');
\r
11167 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
11169 n = sn.innerHTML;
\r
11170 sn.innerHTML = '';
\r
11171 ed.dom.add(sn, se.element, null, n);
\r
11172 aft = ed.dom.add(sn, se.element, null, '<br />');
\r
11175 // Move caret into the last one
\r
11176 r = d.createRange();
\r
11177 r.selectNodeContents(aft);
\r
11179 ed.selection.setRng(r);
\r
11184 // If the caret is in an invalid location in FF we need to move it into the first block
\r
11185 if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
\r
11186 sn = en = sn.firstChild;
\r
11188 rb = d.createRange();
\r
11189 rb.setStart(sn, 0);
\r
11190 ra = d.createRange();
\r
11191 ra.setStart(en, 0);
\r
11194 // Never use body as start or end node
\r
11195 sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
11196 sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
\r
11197 en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
\r
11198 en = en.nodeName == "BODY" ? en.firstChild : en;
\r
11200 // Get start and end blocks
\r
11201 sb = t.getParentBlock(sn);
\r
11202 eb = t.getParentBlock(en);
\r
11203 bn = sb ? sb.nodeName : se.element; // Get block name to create
\r
11205 // Return inside list use default browser behavior
\r
11206 if (n = t.dom.getParent(sb, 'li,pre')) {
\r
11207 if (n.nodeName == 'LI')
\r
11208 return splitList(ed.selection, t.dom, n);
\r
11213 // If caption or absolute layers then always generate new blocks within
\r
11214 if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
11219 // If caption or absolute layers then always generate new blocks within
\r
11220 if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
\r
11226 if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
\r
11231 // Setup new before and after blocks
\r
11232 bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
\r
11233 aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
\r
11235 // Remove id from after clone
\r
11236 aft.removeAttribute('id');
\r
11238 // Is header and cursor is at the end, then force paragraph under
\r
11239 if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
\r
11240 aft = ed.dom.create(se.element);
\r
11242 // Find start chop node
\r
11245 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
11249 } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
\r
11251 // Find end chop node
\r
11254 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
\r
11258 } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
\r
11260 // Place first chop part into before block element
\r
11261 if (sc.nodeName == bn)
\r
11262 rb.setStart(sc, 0);
\r
11264 rb.setStartBefore(sc);
\r
11266 rb.setEnd(sn, so);
\r
11267 bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
11269 // Place secnd chop part within new block element
\r
11271 ra.setEndAfter(ec);
\r
11273 //console.debug(s.focusNode, s.focusOffset);
\r
11276 ra.setStart(en, eo);
\r
11277 aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
\r
11279 // Create range around everything
\r
11280 r = d.createRange();
\r
11281 if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
\r
11282 r.setStartBefore(sc.parentNode);
\r
11284 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
\r
11285 r.setStartBefore(rb.startContainer);
\r
11287 r.setStart(rb.startContainer, rb.startOffset);
\r
11290 if (!ec.nextSibling && ec.parentNode.nodeName == bn)
\r
11291 r.setEndAfter(ec.parentNode);
\r
11293 r.setEnd(ra.endContainer, ra.endOffset);
\r
11295 // Delete and replace it with new block elements
\r
11296 r.deleteContents();
\r
11299 ed.getWin().scrollTo(0, vp.y);
\r
11301 // Never wrap blocks in blocks
\r
11302 if (bef.firstChild && bef.firstChild.nodeName == bn)
\r
11303 bef.innerHTML = bef.firstChild.innerHTML;
\r
11305 if (aft.firstChild && aft.firstChild.nodeName == bn)
\r
11306 aft.innerHTML = aft.firstChild.innerHTML;
\r
11308 // Padd empty blocks
\r
11309 if (isEmpty(bef))
\r
11310 bef.innerHTML = '<br />';
\r
11312 function appendStyles(e, en) {
\r
11313 var nl = [], nn, n, i;
\r
11315 e.innerHTML = '';
\r
11317 // Make clones of style elements
\r
11318 if (se.keep_styles) {
\r
11321 // We only want style specific elements
\r
11322 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
\r
11323 nn = n.cloneNode(FALSE);
\r
11324 dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
\r
11327 } while (n = n.parentNode);
\r
11330 // Append style elements to aft
\r
11331 if (nl.length > 0) {
\r
11332 for (i = nl.length - 1, nn = e; i >= 0; i--)
\r
11333 nn = nn.appendChild(nl[i]);
\r
11335 // Padd most inner style element
\r
11336 nl[0].innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
11337 return nl[0]; // Move caret to most inner element
\r
11339 e.innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
\r
11342 // Fill empty afterblook with current style
\r
11343 if (isEmpty(aft))
\r
11344 car = appendStyles(aft, en);
\r
11346 // Opera needs this one backwards for older versions
\r
11347 if (isOpera && parseFloat(opera.version()) < 9.5) {
\r
11348 r.insertNode(bef);
\r
11349 r.insertNode(aft);
\r
11351 r.insertNode(aft);
\r
11352 r.insertNode(bef);
\r
11359 function first(n) {
\r
11360 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
\r
11363 // Move cursor and scroll into view
\r
11364 r = d.createRange();
\r
11365 r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
\r
11367 s.removeAllRanges();
\r
11370 // 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
11371 y = ed.dom.getPos(aft).y;
\r
11372 ch = aft.clientHeight;
\r
11374 // Is element within viewport
\r
11375 if (y < vp.y || y + ch > vp.y + vp.h) {
\r
11376 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
11377 //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
11383 backspaceDelete : function(e, bs) {
\r
11384 var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;
\r
11386 // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
\r
11387 if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
\r
11388 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
\r
11390 // Walk the dom backwards until we find a text node
\r
11391 for (n = sc.lastChild; n; n = walker.prev()) {
\r
11392 if (n.nodeType == 3) {
\r
11393 r.setStart(n, n.nodeValue.length);
\r
11394 r.collapse(true);
\r
11401 // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
\r
11402 // This workaround removes the element by hand and moves the caret to the previous element
\r
11403 if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
\r
11404 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
\r
11405 // Find previous block element
\r
11407 while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
\r
11410 if (sc != b.firstChild) {
\r
11411 // Find last text node
\r
11412 w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
\r
11413 while (tn = w.nextNode())
\r
11416 // Place caret at the end of last text node
\r
11417 r = ed.getDoc().createRange();
\r
11418 r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
\r
11419 r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
\r
11422 // Remove the target container
\r
11423 ed.dom.remove(sc);
\r
11426 return Event.cancel(e);
\r
11434 (function(tinymce) {
\r
11436 var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
\r
11438 tinymce.create('tinymce.ControlManager', {
\r
11439 ControlManager : function(ed, s) {
\r
11445 t.onAdd = new tinymce.util.Dispatcher(t);
\r
11446 t.onPostRender = new tinymce.util.Dispatcher(t);
\r
11447 t.prefix = s.prefix || ed.id + '_';
\r
11450 t.onPostRender.add(function() {
\r
11451 each(t.controls, function(c) {
\r
11457 get : function(id) {
\r
11458 return this.controls[this.prefix + id] || this.controls[id];
\r
11461 setActive : function(id, s) {
\r
11464 if (c = this.get(id))
\r
11470 setDisabled : function(id, s) {
\r
11473 if (c = this.get(id))
\r
11474 c.setDisabled(s);
\r
11479 add : function(c) {
\r
11483 t.controls[c.id] = c;
\r
11484 t.onAdd.dispatch(c, t);
\r
11490 createControl : function(n) {
\r
11491 var c, t = this, ed = t.editor;
\r
11493 each(ed.plugins, function(p) {
\r
11494 if (p.createControl) {
\r
11495 c = p.createControl(n, t);
\r
11504 case "separator":
\r
11505 return t.createSeparator();
\r
11508 if (!c && ed.buttons && (c = ed.buttons[n]))
\r
11509 return t.createButton(n, c);
\r
11514 createDropMenu : function(id, s, cc) {
\r
11515 var t = this, ed = t.editor, c, bm, v, cls;
\r
11518 'class' : 'mceDropDown',
\r
11519 constrain : ed.settings.constrain_menus
\r
11522 s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
\r
11523 if (v = ed.getParam('skin_variant'))
\r
11524 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
\r
11526 id = t.prefix + id;
\r
11527 cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
\r
11528 c = t.controls[id] = new cls(id, s);
\r
11529 c.onAddItem.add(function(c, o) {
\r
11530 var s = o.settings;
\r
11532 s.title = ed.getLang(s.title, s.title);
\r
11534 if (!s.onclick) {
\r
11535 s.onclick = function(v) {
\r
11537 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
11542 ed.onRemove.add(function() {
\r
11546 // Fix for bug #1897785, #1898007
\r
11547 if (tinymce.isIE) {
\r
11548 c.onShowMenu.add(function() {
\r
11549 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
11552 bm = ed.selection.getBookmark(1);
\r
11555 c.onHideMenu.add(function() {
\r
11557 ed.selection.moveToBookmark(bm);
\r
11566 createListBox : function(id, s, cc) {
\r
11567 var t = this, ed = t.editor, cmd, c, cls;
\r
11572 s.title = ed.translate(s.title);
\r
11573 s.scope = s.scope || ed;
\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
11585 control_manager : t
\r
11588 id = t.prefix + id;
\r
11590 if (ed.settings.use_native_selects)
\r
11591 c = new tinymce.ui.NativeListBox(id, s);
\r
11593 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
\r
11594 c = new cls(id, s);
\r
11597 t.controls[id] = c;
\r
11599 // Fix focus problem in Safari
\r
11600 if (tinymce.isWebKit) {
\r
11601 c.onPostRender.add(function(c, n) {
\r
11602 // Store bookmark on mousedown
\r
11603 Event.add(n, 'mousedown', function() {
\r
11604 ed.bookmark = ed.selection.getBookmark(1);
\r
11607 // Restore on focus, since it might be lost
\r
11608 Event.add(n, 'focus', function() {
\r
11609 ed.selection.moveToBookmark(ed.bookmark);
\r
11610 ed.bookmark = null;
\r
11616 ed.onMouseDown.add(c.hideMenu, c);
\r
11621 createButton : function(id, s, cc) {
\r
11622 var t = this, ed = t.editor, o, c, cls;
\r
11627 s.title = ed.translate(s.title);
\r
11628 s.label = ed.translate(s.label);
\r
11629 s.scope = s.scope || ed;
\r
11631 if (!s.onclick && !s.menu_button) {
\r
11632 s.onclick = function() {
\r
11633 ed.execCommand(s.cmd, s.ui || false, s.value);
\r
11639 'class' : 'mce_' + id,
\r
11640 unavailable_prefix : ed.getLang('unavailable', ''),
\r
11642 control_manager : t
\r
11645 id = t.prefix + id;
\r
11647 if (s.menu_button) {
\r
11648 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
\r
11649 c = new cls(id, s);
\r
11650 ed.onMouseDown.add(c.hideMenu, c);
\r
11652 cls = t._cls.button || tinymce.ui.Button;
\r
11653 c = new cls(id, s);
\r
11659 createMenuButton : function(id, s, cc) {
\r
11661 s.menu_button = 1;
\r
11663 return this.createButton(id, s, cc);
\r
11666 createSplitButton : function(id, s, cc) {
\r
11667 var t = this, ed = t.editor, cmd, c, cls;
\r
11672 s.title = ed.translate(s.title);
\r
11673 s.scope = s.scope || ed;
\r
11675 if (!s.onclick) {
\r
11676 s.onclick = function(v) {
\r
11677 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
11681 if (!s.onselect) {
\r
11682 s.onselect = function(v) {
\r
11683 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
11689 'class' : 'mce_' + id,
\r
11691 control_manager : t
\r
11694 id = t.prefix + id;
\r
11695 cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
\r
11696 c = t.add(new cls(id, s));
\r
11697 ed.onMouseDown.add(c.hideMenu, c);
\r
11702 createColorSplitButton : function(id, s, cc) {
\r
11703 var t = this, ed = t.editor, cmd, c, cls, bm;
\r
11708 s.title = ed.translate(s.title);
\r
11709 s.scope = s.scope || ed;
\r
11711 if (!s.onclick) {
\r
11712 s.onclick = function(v) {
\r
11713 if (tinymce.isIE)
\r
11714 bm = ed.selection.getBookmark(1);
\r
11716 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
11720 if (!s.onselect) {
\r
11721 s.onselect = function(v) {
\r
11722 ed.execCommand(s.cmd, s.ui || false, v || s.value);
\r
11728 'class' : 'mce_' + id,
\r
11729 'menu_class' : ed.getParam('skin') + 'Skin',
\r
11731 more_colors_title : ed.getLang('more_colors')
\r
11734 id = t.prefix + id;
\r
11735 cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
\r
11736 c = new cls(id, s);
\r
11737 ed.onMouseDown.add(c.hideMenu, c);
\r
11739 // Remove the menu element when the editor is removed
\r
11740 ed.onRemove.add(function() {
\r
11744 // Fix for bug #1897785, #1898007
\r
11745 if (tinymce.isIE) {
\r
11746 c.onShowMenu.add(function() {
\r
11747 // IE 8 needs focus in order to store away a range with the current collapsed caret location
\r
11749 bm = ed.selection.getBookmark(1);
\r
11752 c.onHideMenu.add(function() {
\r
11754 ed.selection.moveToBookmark(bm);
\r
11763 createToolbar : function(id, s, cc) {
\r
11764 var c, t = this, cls;
\r
11766 id = t.prefix + id;
\r
11767 cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
\r
11768 c = new cls(id, s);
\r
11776 createSeparator : function(cc) {
\r
11777 var cls = cc || this._cls.separator || tinymce.ui.Separator;
\r
11779 return new cls();
\r
11782 setControlType : function(n, c) {
\r
11783 return this._cls[n.toLowerCase()] = c;
\r
11786 destroy : function() {
\r
11787 each(this.controls, function(c) {
\r
11791 this.controls = null;
\r
11796 (function(tinymce) {
\r
11797 var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
\r
11799 tinymce.create('tinymce.WindowManager', {
\r
11800 WindowManager : function(ed) {
\r
11804 t.onOpen = new Dispatcher(t);
\r
11805 t.onClose = new Dispatcher(t);
\r
11810 open : function(s, p) {
\r
11811 var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
\r
11813 // Default some options
\r
11816 sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
\r
11817 sh = isOpera ? vp.h : screen.height;
\r
11818 s.name = s.name || 'mc_' + new Date().getTime();
\r
11819 s.width = parseInt(s.width || 320);
\r
11820 s.height = parseInt(s.height || 240);
\r
11821 s.resizable = true;
\r
11822 s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
\r
11823 s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
\r
11824 p.inline = false;
\r
11825 p.mce_width = s.width;
\r
11826 p.mce_height = s.height;
\r
11827 p.mce_auto_focus = s.auto_focus;
\r
11833 s.dialogWidth = s.width + 'px';
\r
11834 s.dialogHeight = s.height + 'px';
\r
11835 s.scroll = s.scrollbars || false;
\r
11839 // Build features string
\r
11840 each(s, function(v, k) {
\r
11841 if (tinymce.is(v, 'boolean'))
\r
11842 v = v ? 'yes' : 'no';
\r
11844 if (!/^(name|url)$/.test(k)) {
\r
11846 f += (f ? ';' : '') + k + ':' + v;
\r
11848 f += (f ? ',' : '') + k + '=' + v;
\r
11854 t.onOpen.dispatch(t, s, p);
\r
11856 u = s.url || s.file;
\r
11857 u = tinymce._addVer(u);
\r
11860 if (isIE && mo) {
\r
11862 window.showModalDialog(u, window, f);
\r
11864 w = window.open(u, s.name, f);
\r
11870 alert(t.editor.getLang('popup_blocked'));
\r
11873 close : function(w) {
\r
11875 this.onClose.dispatch(this);
\r
11878 createInstance : function(cl, a, b, c, d, e) {
\r
11879 var f = tinymce.resolve(cl);
\r
11881 return new f(a, b, c, d, e);
\r
11884 confirm : function(t, cb, s, w) {
\r
11887 cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
\r
11890 alert : function(tx, cb, s, w) {
\r
11894 w.alert(t._decode(t.editor.getLang(tx, tx)));
\r
11900 resizeBy : function(dw, dh, win) {
\r
11901 win.resizeBy(dw, dh);
\r
11904 // Internal functions
\r
11906 _decode : function(s) {
\r
11907 return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
\r
11911 (function(tinymce) {
\r
11912 function CommandManager() {
\r
11913 var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
\r
11915 function add(collection, cmd, func, scope) {
\r
11916 if (typeof(cmd) == 'string')
\r
11919 tinymce.each(cmd, function(cmd) {
\r
11920 collection[cmd.toLowerCase()] = {func : func, scope : scope};
\r
11924 tinymce.extend(this, {
\r
11925 add : function(cmd, func, scope) {
\r
11926 add(execCommands, cmd, func, scope);
\r
11929 addQueryStateHandler : function(cmd, func, scope) {
\r
11930 add(queryStateCommands, cmd, func, scope);
\r
11933 addQueryValueHandler : function(cmd, func, scope) {
\r
11934 add(queryValueCommands, cmd, func, scope);
\r
11937 execCommand : function(scope, cmd, ui, value, args) {
\r
11938 if (cmd = execCommands[cmd.toLowerCase()]) {
\r
11939 if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
\r
11944 queryCommandValue : function() {
\r
11945 if (cmd = queryValueCommands[cmd.toLowerCase()])
\r
11946 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
11949 queryCommandState : function() {
\r
11950 if (cmd = queryStateCommands[cmd.toLowerCase()])
\r
11951 return cmd.func.call(scope || cmd.scope, ui, value, args);
\r
11956 tinymce.GlobalCommands = new CommandManager();
\r
11958 (function(tinymce) {
\r
11959 tinymce.Formatter = function(ed) {
\r
11960 var formats = {},
\r
11961 each = tinymce.each,
\r
11963 selection = ed.selection,
\r
11964 TreeWalker = tinymce.dom.TreeWalker,
\r
11965 rangeUtils = new tinymce.dom.RangeUtils(dom),
\r
11966 isValid = ed.schema.isValid,
\r
11967 isBlock = dom.isBlock,
\r
11968 forcedRootBlock = ed.settings.forced_root_block,
\r
11969 nodeIndex = dom.nodeIndex,
\r
11970 INVISIBLE_CHAR = '\uFEFF',
\r
11971 MCE_ATTR_RE = /^(src|href|style)$/,
\r
11975 pendingFormats = {apply : [], remove : []};
\r
11977 function isArray(obj) {
\r
11978 return obj instanceof Array;
\r
11981 function getParents(node, selector) {
\r
11982 return dom.getParents(node, selector, dom.getRoot());
\r
11985 function isCaretNode(node) {
\r
11986 return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
\r
11989 // Public functions
\r
11991 function get(name) {
\r
11992 return name ? formats[name] : formats;
\r
11995 function register(name, format) {
\r
11997 if (typeof(name) !== 'string') {
\r
11998 each(name, function(format, name) {
\r
11999 register(name, format);
\r
12002 // Force format into array and add it to internal collection
\r
12003 format = format.length ? format : [format];
\r
12005 each(format, function(format) {
\r
12006 // Set deep to false by default on selector formats this to avoid removing
\r
12007 // alignment on images inside paragraphs when alignment is changed on paragraphs
\r
12008 if (format.deep === undefined)
\r
12009 format.deep = !format.selector;
\r
12011 // Default to true
\r
12012 if (format.split === undefined)
\r
12013 format.split = !format.selector || format.inline;
\r
12015 // Default to true
\r
12016 if (format.remove === undefined && format.selector && !format.inline)
\r
12017 format.remove = 'none';
\r
12019 // Mark format as a mixed format inline + block level
\r
12020 if (format.selector && format.inline) {
\r
12021 format.mixed = true;
\r
12022 format.block_expand = true;
\r
12025 // Split classes if needed
\r
12026 if (typeof(format.classes) === 'string')
\r
12027 format.classes = format.classes.split(/\s+/);
\r
12030 formats[name] = format;
\r
12035 function apply(name, vars, node) {
\r
12036 var formatList = get(name), format = formatList[0], bookmark, rng, i;
\r
12038 function moveStart(rng) {
\r
12039 var container = rng.startContainer,
\r
12040 offset = rng.startOffset,
\r
12043 // Move startContainer/startOffset in to a suitable node
\r
12044 if (container.nodeType == 1 || container.nodeValue === "") {
\r
12045 container = container.nodeType == 1 ? container.childNodes[offset] : container;
\r
12047 // Might fail if the offset is behind the last element in it's container
\r
12049 walker = new TreeWalker(container, container.parentNode);
\r
12050 for (node = walker.current(); node; node = walker.next()) {
\r
12051 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
\r
12052 rng.setStart(node, 0);
\r
12062 function setElementFormat(elm, fmt) {
\r
12063 fmt = fmt || format;
\r
12066 each(fmt.styles, function(value, name) {
\r
12067 dom.setStyle(elm, name, replaceVars(value, vars));
\r
12070 each(fmt.attributes, function(value, name) {
\r
12071 dom.setAttrib(elm, name, replaceVars(value, vars));
\r
12074 each(fmt.classes, function(value) {
\r
12075 value = replaceVars(value, vars);
\r
12077 if (!dom.hasClass(elm, value))
\r
12078 dom.addClass(elm, value);
\r
12083 function applyRngStyle(rng) {
\r
12084 var newWrappers = [], wrapName, wrapElm;
\r
12086 // Setup wrapper element
\r
12087 wrapName = format.inline || format.block;
\r
12088 wrapElm = dom.create(wrapName);
\r
12089 setElementFormat(wrapElm);
\r
12091 rangeUtils.walk(rng, function(nodes) {
\r
12092 var currentWrapElm;
\r
12094 function process(node) {
\r
12095 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
\r
12097 // Stop wrapping on br elements
\r
12098 if (isEq(nodeName, 'br')) {
\r
12099 currentWrapElm = 0;
\r
12101 // Remove any br elements when we wrap things
\r
12102 if (format.block)
\r
12103 dom.remove(node);
\r
12108 // If node is wrapper type
\r
12109 if (format.wrapper && matchNode(node, name, vars)) {
\r
12110 currentWrapElm = 0;
\r
12114 // Can we rename the block
\r
12115 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
\r
12116 node = dom.rename(node, wrapName);
\r
12117 setElementFormat(node);
\r
12118 newWrappers.push(node);
\r
12119 currentWrapElm = 0;
\r
12123 // Handle selector patterns
\r
12124 if (format.selector) {
\r
12125 // Look for matching formats
\r
12126 each(formatList, function(format) {
\r
12127 if (dom.is(node, format.selector) && !isCaretNode(node)) {
\r
12128 setElementFormat(node, format);
\r
12133 // Continue processing if a selector match wasn't found and a inline element is defined
\r
12134 if (!format.inline || found) {
\r
12135 currentWrapElm = 0;
\r
12140 // Is it valid to wrap this item
\r
12141 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName)) {
\r
12142 // Start wrapping
\r
12143 if (!currentWrapElm) {
\r
12145 currentWrapElm = wrapElm.cloneNode(FALSE);
\r
12146 node.parentNode.insertBefore(currentWrapElm, node);
\r
12147 newWrappers.push(currentWrapElm);
\r
12150 currentWrapElm.appendChild(node);
\r
12152 // Start a new wrapper for possible children
\r
12153 currentWrapElm = 0;
\r
12155 each(tinymce.grep(node.childNodes), process);
\r
12157 // End the last wrapper
\r
12158 currentWrapElm = 0;
\r
12162 // Process siblings from range
\r
12163 each(nodes, process);
\r
12167 each(newWrappers, function(node) {
\r
12170 function getChildCount(node) {
\r
12173 each(node.childNodes, function(node) {
\r
12174 if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
\r
12181 function mergeStyles(node) {
\r
12182 var child, clone;
\r
12184 each(node.childNodes, function(node) {
\r
12185 if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
\r
12187 return FALSE; // break loop
\r
12191 // If child was found and of the same type as the current node
\r
12192 if (child && matchName(child, format)) {
\r
12193 clone = child.cloneNode(FALSE);
\r
12194 setElementFormat(clone);
\r
12196 dom.replace(clone, node, TRUE);
\r
12197 dom.remove(child, 1);
\r
12200 return clone || node;
\r
12203 childCount = getChildCount(node);
\r
12205 // Remove empty nodes
\r
12206 if (childCount === 0) {
\r
12207 dom.remove(node, 1);
\r
12211 if (format.inline || format.wrapper) {
\r
12212 // Merges the current node with it's children of similar type to reduce the number of elements
\r
12213 if (!format.exact && childCount === 1)
\r
12214 node = mergeStyles(node);
\r
12216 // Remove/merge children
\r
12217 each(formatList, function(format) {
\r
12218 // Merge all children of similar type will move styles from child to parent
\r
12219 // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
\r
12220 // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
\r
12221 each(dom.select(format.inline, node), function(child) {
\r
12222 removeFormat(format, vars, child, format.exact ? child : null);
\r
12226 // Remove child if direct parent is of same type
\r
12227 if (matchNode(node.parentNode, name, vars)) {
\r
12228 dom.remove(node, 1);
\r
12233 // Look for parent with similar style format
\r
12234 if (format.merge_with_parents) {
\r
12235 dom.getParent(node.parentNode, function(parent) {
\r
12236 if (matchNode(parent, name, vars)) {
\r
12237 dom.remove(node, 1);
\r
12244 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
\r
12246 node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
\r
12247 node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
\r
12255 rng = dom.createRng();
\r
12257 rng.setStartBefore(node);
\r
12258 rng.setEndAfter(node);
\r
12260 applyRngStyle(expandRng(rng, formatList));
\r
12262 if (!selection.isCollapsed() || !format.inline) {
\r
12263 // Apply formatting to selection
\r
12264 bookmark = selection.getBookmark();
\r
12265 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
\r
12267 selection.moveToBookmark(bookmark);
\r
12268 selection.setRng(moveStart(selection.getRng(TRUE)));
\r
12269 ed.nodeChanged();
\r
12271 performCaretAction('apply', name, vars);
\r
12276 function remove(name, vars, node) {
\r
12277 var formatList = get(name), format = formatList[0], bookmark, i, rng;
\r
12279 function moveStart(rng) {
\r
12280 var container = rng.startContainer,
\r
12281 offset = rng.startOffset,
\r
12282 walker, node, nodes, tmpNode;
\r
12284 // Convert text node into index if possible
\r
12285 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
\r
12286 container = container.parentNode;
\r
12287 offset = nodeIndex(container) + 1;
\r
12290 // Move startContainer/startOffset in to a suitable node
\r
12291 if (container.nodeType == 1) {
\r
12292 nodes = container.childNodes;
\r
12293 container = nodes[Math.min(offset, nodes.length - 1)];
\r
12294 walker = new TreeWalker(container);
\r
12296 // If offset is at end of the parent node walk to the next one
\r
12297 if (offset > nodes.length - 1)
\r
12300 for (node = walker.current(); node; node = walker.next()) {
\r
12301 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
\r
12302 // IE has a "neat" feature where it moves the start node into the closest element
\r
12303 // we can avoid this by inserting an element before it and then remove it after we set the selection
\r
12304 tmpNode = dom.create('a', null, INVISIBLE_CHAR);
\r
12305 node.parentNode.insertBefore(tmpNode, node);
\r
12307 // Set selection and remove tmpNode
\r
12308 rng.setStart(node, 0);
\r
12309 selection.setRng(rng);
\r
12310 dom.remove(tmpNode);
\r
12318 // Merges the styles for each node
\r
12319 function process(node) {
\r
12320 var children, i, l;
\r
12322 // Grab the children first since the nodelist might be changed
\r
12323 children = tinymce.grep(node.childNodes);
\r
12325 // Process current node
\r
12326 for (i = 0, l = formatList.length; i < l; i++) {
\r
12327 if (removeFormat(formatList[i], vars, node, node))
\r
12331 // Process the children
\r
12332 if (format.deep) {
\r
12333 for (i = 0, l = children.length; i < l; i++)
\r
12334 process(children[i]);
\r
12338 function findFormatRoot(container) {
\r
12341 // Find format root
\r
12342 each(getParents(container.parentNode).reverse(), function(parent) {
\r
12345 // Find format root element
\r
12346 if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
\r
12347 // Is the node matching the format we are looking for
\r
12348 format = matchNode(parent, name, vars);
\r
12349 if (format && format.split !== false)
\r
12350 formatRoot = parent;
\r
12354 return formatRoot;
\r
12357 function wrapAndSplit(format_root, container, target, split) {
\r
12358 var parent, clone, lastClone, firstClone, i, formatRootParent;
\r
12360 // Format root found then clone formats and split it
\r
12361 if (format_root) {
\r
12362 formatRootParent = format_root.parentNode;
\r
12364 for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
\r
12365 clone = parent.cloneNode(FALSE);
\r
12367 for (i = 0; i < formatList.length; i++) {
\r
12368 if (removeFormat(formatList[i], vars, clone, clone)) {
\r
12374 // Build wrapper node
\r
12377 clone.appendChild(lastClone);
\r
12380 firstClone = clone;
\r
12382 lastClone = clone;
\r
12386 // Never split block elements if the format is mixed
\r
12387 if (split && (!format.mixed || !isBlock(format_root)))
\r
12388 container = dom.split(format_root, container);
\r
12390 // Wrap container in cloned formats
\r
12392 target.parentNode.insertBefore(lastClone, target);
\r
12393 firstClone.appendChild(target);
\r
12397 return container;
\r
12400 function splitToFormatRoot(container) {
\r
12401 return wrapAndSplit(findFormatRoot(container), container, container, true);
\r
12404 function unwrap(start) {
\r
12405 var node = dom.get(start ? '_start' : '_end'),
\r
12406 out = node[start ? 'firstChild' : 'lastChild'];
\r
12408 // If the end is placed within the start the result will be removed
\r
12409 // So this checks if the out node is a bookmark node if it is it
\r
12410 // checks for another more suitable node
\r
12411 if (isBookmarkNode(out))
\r
12412 out = out[start ? 'firstChild' : 'lastChild'];
\r
12414 dom.remove(node, true);
\r
12419 function removeRngStyle(rng) {
\r
12420 var startContainer, endContainer;
\r
12422 rng = expandRng(rng, formatList, TRUE);
\r
12424 if (format.split) {
\r
12425 startContainer = getContainer(rng, TRUE);
\r
12426 endContainer = getContainer(rng);
\r
12428 if (startContainer != endContainer) {
\r
12429 // Wrap start/end nodes in span element since these might be cloned/moved
\r
12430 startContainer = wrap(startContainer, 'span', {id : '_start', _mce_type : 'bookmark'});
\r
12431 endContainer = wrap(endContainer, 'span', {id : '_end', _mce_type : 'bookmark'});
\r
12433 // Split start/end
\r
12434 splitToFormatRoot(startContainer);
\r
12435 splitToFormatRoot(endContainer);
\r
12437 // Unwrap start/end to get real elements again
\r
12438 startContainer = unwrap(TRUE);
\r
12439 endContainer = unwrap();
\r
12441 startContainer = endContainer = splitToFormatRoot(startContainer);
\r
12443 // Update range positions since they might have changed after the split operations
\r
12444 rng.startContainer = startContainer.parentNode;
\r
12445 rng.startOffset = nodeIndex(startContainer);
\r
12446 rng.endContainer = endContainer.parentNode;
\r
12447 rng.endOffset = nodeIndex(endContainer) + 1;
\r
12450 // Remove items between start/end
\r
12451 rangeUtils.walk(rng, function(nodes) {
\r
12452 each(nodes, function(node) {
\r
12460 rng = dom.createRng();
\r
12461 rng.setStartBefore(node);
\r
12462 rng.setEndAfter(node);
\r
12463 removeRngStyle(rng);
\r
12467 if (!selection.isCollapsed() || !format.inline) {
\r
12468 bookmark = selection.getBookmark();
\r
12469 removeRngStyle(selection.getRng(TRUE));
\r
12470 selection.moveToBookmark(bookmark);
\r
12472 // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
\r
12473 if (match(name, vars, selection.getStart())) {
\r
12474 moveStart(selection.getRng(true));
\r
12477 ed.nodeChanged();
\r
12479 performCaretAction('remove', name, vars);
\r
12482 function toggle(name, vars, node) {
\r
12483 if (match(name, vars, node))
\r
12484 remove(name, vars, node);
\r
12486 apply(name, vars, node);
\r
12489 function matchNode(node, name, vars, similar) {
\r
12490 var formatList = get(name), format, i, classes;
\r
12492 function matchItems(node, format, item_name) {
\r
12493 var key, value, items = format[item_name], i;
\r
12495 // Check all items
\r
12497 // Non indexed object
\r
12498 if (items.length === undefined) {
\r
12499 for (key in items) {
\r
12500 if (items.hasOwnProperty(key)) {
\r
12501 if (item_name === 'attributes')
\r
12502 value = dom.getAttrib(node, key);
\r
12504 value = getStyle(node, key);
\r
12506 if (similar && !value && !format.exact)
\r
12509 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
\r
12514 // Only one match needed for indexed arrays
\r
12515 for (i = 0; i < items.length; i++) {
\r
12516 if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
\r
12525 if (formatList && node) {
\r
12526 // Check each format in list
\r
12527 for (i = 0; i < formatList.length; i++) {
\r
12528 format = formatList[i];
\r
12530 // Name name, attributes, styles and classes
\r
12531 if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
\r
12533 if (classes = format.classes) {
\r
12534 for (i = 0; i < classes.length; i++) {
\r
12535 if (!dom.hasClass(node, classes[i]))
\r
12546 function match(name, vars, node) {
\r
12547 var startNode, i;
\r
12549 function matchParents(node) {
\r
12550 // Find first node with similar format settings
\r
12551 node = dom.getParent(node, function(node) {
\r
12552 return !!matchNode(node, name, vars, true);
\r
12555 // Do an exact check on the similar format element
\r
12556 return matchNode(node, name, vars);
\r
12559 // Check specified node
\r
12561 return matchParents(node);
\r
12563 // Check pending formats
\r
12564 if (selection.isCollapsed()) {
\r
12565 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
12566 if (pendingFormats.apply[i].name == name)
\r
12570 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
12571 if (pendingFormats.remove[i].name == name)
\r
12575 return matchParents(selection.getNode());
\r
12578 // Check selected node
\r
12579 node = selection.getNode();
\r
12580 if (matchParents(node))
\r
12583 // Check start node if it's different
\r
12584 startNode = selection.getStart();
\r
12585 if (startNode != node) {
\r
12586 if (matchParents(startNode))
\r
12593 function matchAll(names, vars) {
\r
12594 var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
\r
12596 // If the selection is collapsed then check pending formats
\r
12597 if (selection.isCollapsed()) {
\r
12598 for (ni = 0; ni < names.length; ni++) {
\r
12599 // If the name is to be removed, then stop it from being added
\r
12600 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
\r
12601 name = names[ni];
\r
12603 if (pendingFormats.remove[i].name == name) {
\r
12604 checkedMap[name] = true;
\r
12610 // If the format is to be applied
\r
12611 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
\r
12612 for (ni = 0; ni < names.length; ni++) {
\r
12613 name = names[ni];
\r
12615 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
\r
12616 checkedMap[name] = true;
\r
12617 matchedFormatNames.push(name);
\r
12623 // Check start of selection for formats
\r
12624 startElement = selection.getStart();
\r
12625 dom.getParent(startElement, function(node) {
\r
12628 for (i = 0; i < names.length; i++) {
\r
12631 if (!checkedMap[name] && matchNode(node, name, vars)) {
\r
12632 checkedMap[name] = true;
\r
12633 matchedFormatNames.push(name);
\r
12638 return matchedFormatNames;
\r
12641 function canApply(name) {
\r
12642 var formatList = get(name), startNode, parents, i, x, selector;
\r
12644 if (formatList) {
\r
12645 startNode = selection.getStart();
\r
12646 parents = getParents(startNode);
\r
12648 for (x = formatList.length - 1; x >= 0; x--) {
\r
12649 selector = formatList[x].selector;
\r
12651 // Format is not selector based, then always return TRUE
\r
12655 for (i = parents.length - 1; i >= 0; i--) {
\r
12656 if (dom.is(parents[i], selector))
\r
12665 // Expose to public
\r
12666 tinymce.extend(this, {
\r
12668 register : register,
\r
12673 matchAll : matchAll,
\r
12674 matchNode : matchNode,
\r
12675 canApply : canApply
\r
12678 // Private functions
\r
12680 function matchName(node, format) {
\r
12681 // Check for inline match
\r
12682 if (isEq(node, format.inline))
\r
12685 // Check for block match
\r
12686 if (isEq(node, format.block))
\r
12689 // Check for selector match
\r
12690 if (format.selector)
\r
12691 return dom.is(node, format.selector);
\r
12694 function isEq(str1, str2) {
\r
12695 str1 = str1 || '';
\r
12696 str2 = str2 || '';
\r
12698 str1 = '' + (str1.nodeName || str1);
\r
12699 str2 = '' + (str2.nodeName || str2);
\r
12701 return str1.toLowerCase() == str2.toLowerCase();
\r
12704 function getStyle(node, name) {
\r
12705 var styleVal = dom.getStyle(node, name);
\r
12707 // Force the format to hex
\r
12708 if (name == 'color' || name == 'backgroundColor')
\r
12709 styleVal = dom.toHex(styleVal);
\r
12711 // Opera will return bold as 700
\r
12712 if (name == 'fontWeight' && styleVal == 700)
\r
12713 styleVal = 'bold';
\r
12715 return '' + styleVal;
\r
12718 function replaceVars(value, vars) {
\r
12719 if (typeof(value) != "string")
\r
12720 value = value(vars);
\r
12722 value = value.replace(/%(\w+)/g, function(str, name) {
\r
12723 return vars[name] || str;
\r
12730 function isWhiteSpaceNode(node) {
\r
12731 return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
\r
12734 function wrap(node, name, attrs) {
\r
12735 var wrapper = dom.create(name, attrs);
\r
12737 node.parentNode.insertBefore(wrapper, node);
\r
12738 wrapper.appendChild(node);
\r
12743 function expandRng(rng, format, remove) {
\r
12744 var startContainer = rng.startContainer,
\r
12745 startOffset = rng.startOffset,
\r
12746 endContainer = rng.endContainer,
\r
12747 endOffset = rng.endOffset, sibling, lastIdx;
\r
12749 // This function walks up the tree if there is no siblings before/after the node
\r
12750 function findParentContainer(container, child_name, sibling_name, root) {
\r
12751 var parent, child;
\r
12753 root = root || dom.getRoot();
\r
12756 // Check if we can move up are we at root level or body level
\r
12757 parent = container.parentNode;
\r
12759 // Stop expanding on block elements or root depending on format
\r
12760 if (parent == root || (!format[0].block_expand && isBlock(parent)))
\r
12761 return container;
\r
12763 for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
\r
12764 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
12765 return container;
\r
12767 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
12768 return container;
\r
12771 container = container.parentNode;
\r
12774 return container;
\r
12777 // If index based start position then resolve it
\r
12778 if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
\r
12779 lastIdx = startContainer.childNodes.length - 1;
\r
12780 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
\r
12782 if (startContainer.nodeType == 3)
\r
12786 // If index based end position then resolve it
\r
12787 if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
\r
12788 lastIdx = endContainer.childNodes.length - 1;
\r
12789 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
\r
12791 if (endContainer.nodeType == 3)
\r
12792 endOffset = endContainer.nodeValue.length;
\r
12795 // Exclude bookmark nodes if possible
\r
12796 if (isBookmarkNode(startContainer.parentNode))
\r
12797 startContainer = startContainer.parentNode;
\r
12799 if (isBookmarkNode(startContainer))
\r
12800 startContainer = startContainer.nextSibling || startContainer;
\r
12802 if (isBookmarkNode(endContainer.parentNode))
\r
12803 endContainer = endContainer.parentNode;
\r
12805 if (isBookmarkNode(endContainer))
\r
12806 endContainer = endContainer.previousSibling || endContainer;
\r
12808 // Move start/end point up the tree if the leaves are sharp and if we are in different containers
\r
12809 // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
\r
12810 // This will reduce the number of wrapper elements that needs to be created
\r
12811 // Move start point up the tree
\r
12812 if (format[0].inline || format[0].block_expand) {
\r
12813 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
12814 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
12817 // Expand start/end container to matching selector
\r
12818 if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
\r
12819 function findSelectorEndPoint(container, sibling_name) {
\r
12820 var parents, i, y;
\r
12822 if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
\r
12823 container = container[sibling_name];
\r
12825 parents = getParents(container);
\r
12826 for (i = 0; i < parents.length; i++) {
\r
12827 for (y = 0; y < format.length; y++) {
\r
12828 if (dom.is(parents[i], format[y].selector))
\r
12829 return parents[i];
\r
12833 return container;
\r
12836 // Find new startContainer/endContainer if there is better one
\r
12837 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
\r
12838 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
\r
12841 // Expand start/end container to matching block element or text node
\r
12842 if (format[0].block || format[0].selector) {
\r
12843 function findBlockEndPoint(container, sibling_name, sibling_name2) {
\r
12846 // Expand to block of similar type
\r
12847 if (!format[0].wrapper)
\r
12848 node = dom.getParent(container, format[0].block);
\r
12850 // Expand to first wrappable block element or any block element
\r
12852 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
\r
12854 // Exclude inner lists from wrapping
\r
12855 if (node && format[0].wrapper)
\r
12856 node = getParents(node, 'ul,ol').reverse()[0] || node;
\r
12858 // Didn't find a block element look for first/last wrappable element
\r
12860 node = container;
\r
12862 while (node[sibling_name] && !isBlock(node[sibling_name])) {
\r
12863 node = node[sibling_name];
\r
12865 // Break on BR but include it will be removed later on
\r
12866 // we can't remove it now since we need to check if it can be wrapped
\r
12867 if (isEq(node, 'br'))
\r
12872 return node || container;
\r
12875 // Find new startContainer/endContainer if there is better one
\r
12876 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
\r
12877 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
\r
12879 // Non block element then try to expand up the leaf
\r
12880 if (format[0].block) {
\r
12881 if (!isBlock(startContainer))
\r
12882 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
\r
12884 if (!isBlock(endContainer))
\r
12885 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
\r
12889 // Setup index for startContainer
\r
12890 if (startContainer.nodeType == 1) {
\r
12891 startOffset = nodeIndex(startContainer);
\r
12892 startContainer = startContainer.parentNode;
\r
12895 // Setup index for endContainer
\r
12896 if (endContainer.nodeType == 1) {
\r
12897 endOffset = nodeIndex(endContainer) + 1;
\r
12898 endContainer = endContainer.parentNode;
\r
12901 // Return new range like object
\r
12903 startContainer : startContainer,
\r
12904 startOffset : startOffset,
\r
12905 endContainer : endContainer,
\r
12906 endOffset : endOffset
\r
12910 function removeFormat(format, vars, node, compare_node) {
\r
12911 var i, attrs, stylesModified;
\r
12913 // Check if node matches format
\r
12914 if (!matchName(node, format))
\r
12917 // Should we compare with format attribs and styles
\r
12918 if (format.remove != 'all') {
\r
12920 each(format.styles, function(value, name) {
\r
12921 value = replaceVars(value, vars);
\r
12924 if (typeof(name) === 'number') {
\r
12926 compare_node = 0;
\r
12929 if (!compare_node || isEq(getStyle(compare_node, name), value))
\r
12930 dom.setStyle(node, name, '');
\r
12932 stylesModified = 1;
\r
12935 // Remove style attribute if it's empty
\r
12936 if (stylesModified && dom.getAttrib(node, 'style') == '') {
\r
12937 node.removeAttribute('style');
\r
12938 node.removeAttribute('_mce_style');
\r
12941 // Remove attributes
\r
12942 each(format.attributes, function(value, name) {
\r
12945 value = replaceVars(value, vars);
\r
12948 if (typeof(name) === 'number') {
\r
12950 compare_node = 0;
\r
12953 if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
\r
12954 // Keep internal classes
\r
12955 if (name == 'class') {
\r
12956 value = dom.getAttrib(node, name);
\r
12958 // Build new class value where everything is removed except the internal prefixed classes
\r
12960 each(value.split(/\s+/), function(cls) {
\r
12961 if (/mce\w+/.test(cls))
\r
12962 valueOut += (valueOut ? ' ' : '') + cls;
\r
12965 // We got some internal classes left
\r
12967 dom.setAttrib(node, name, valueOut);
\r
12973 // IE6 has a bug where the attribute doesn't get removed correctly
\r
12974 if (name == "class")
\r
12975 node.removeAttribute('className');
\r
12977 // Remove mce prefixed attributes
\r
12978 if (MCE_ATTR_RE.test(name))
\r
12979 node.removeAttribute('_mce_' + name);
\r
12981 node.removeAttribute(name);
\r
12985 // Remove classes
\r
12986 each(format.classes, function(value) {
\r
12987 value = replaceVars(value, vars);
\r
12989 if (!compare_node || dom.hasClass(compare_node, value))
\r
12990 dom.removeClass(node, value);
\r
12993 // Check for non internal attributes
\r
12994 attrs = dom.getAttribs(node);
\r
12995 for (i = 0; i < attrs.length; i++) {
\r
12996 if (attrs[i].nodeName.indexOf('_') !== 0)
\r
13001 // Remove the inline child if it's empty for example <b> or <span>
\r
13002 if (format.remove != 'none') {
\r
13003 removeNode(node, format);
\r
13008 function removeNode(node, format) {
\r
13009 var parentNode = node.parentNode, rootBlockElm;
\r
13011 if (format.block) {
\r
13012 if (!forcedRootBlock) {
\r
13013 function find(node, next, inc) {
\r
13014 node = getNonWhiteSpaceSibling(node, next, inc);
\r
13016 return !node || (node.nodeName == 'BR' || isBlock(node));
\r
13019 // Append BR elements if needed before we remove the block
\r
13020 if (isBlock(node) && !isBlock(parentNode)) {
\r
13021 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
\r
13022 node.insertBefore(dom.create('br'), node.firstChild);
\r
13024 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
\r
13025 node.appendChild(dom.create('br'));
\r
13028 // Wrap the block in a forcedRootBlock if we are at the root of document
\r
13029 if (parentNode == dom.getRoot()) {
\r
13030 if (!format.list_block || !isEq(node, format.list_block)) {
\r
13031 each(tinymce.grep(node.childNodes), function(node) {
\r
13032 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
\r
13033 if (!rootBlockElm)
\r
13034 rootBlockElm = wrap(node, forcedRootBlock);
\r
13036 rootBlockElm.appendChild(node);
\r
13038 rootBlockElm = 0;
\r
13045 // Never remove nodes that isn't the specified inline element if a selector is specified too
\r
13046 if (format.selector && format.inline && !isEq(format.inline, node))
\r
13049 dom.remove(node, 1);
\r
13052 function getNonWhiteSpaceSibling(node, next, inc) {
\r
13054 next = next ? 'nextSibling' : 'previousSibling';
\r
13056 for (node = inc ? node : node[next]; node; node = node[next]) {
\r
13057 if (node.nodeType == 1 || !isWhiteSpaceNode(node))
\r
13063 function isBookmarkNode(node) {
\r
13064 return node && node.nodeType == 1 && node.getAttribute('_mce_type') == 'bookmark';
\r
13067 function mergeSiblings(prev, next) {
\r
13068 var marker, sibling, tmpSibling;
\r
13070 function compareElements(node1, node2) {
\r
13071 // Not the same name
\r
13072 if (node1.nodeName != node2.nodeName)
\r
13075 function getAttribs(node) {
\r
13076 var attribs = {};
\r
13078 each(dom.getAttribs(node), function(attr) {
\r
13079 var name = attr.nodeName.toLowerCase();
\r
13081 // Don't compare internal attributes or style
\r
13082 if (name.indexOf('_') !== 0 && name !== 'style')
\r
13083 attribs[name] = dom.getAttrib(node, name);
\r
13089 function compareObjects(obj1, obj2) {
\r
13092 for (name in obj1) {
\r
13093 // Obj1 has item obj2 doesn't have
\r
13094 if (obj1.hasOwnProperty(name)) {
\r
13095 value = obj2[name];
\r
13097 // Obj2 doesn't have obj1 item
\r
13098 if (value === undefined)
\r
13101 // Obj2 item has a different value
\r
13102 if (obj1[name] != value)
\r
13105 // Delete similar value
\r
13106 delete obj2[name];
\r
13110 // Check if obj 2 has something obj 1 doesn't have
\r
13111 for (name in obj2) {
\r
13112 // Obj2 has item obj1 doesn't have
\r
13113 if (obj2.hasOwnProperty(name))
\r
13120 // Attribs are not the same
\r
13121 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
\r
13124 // Styles are not the same
\r
13125 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
\r
13131 // Check if next/prev exists and that they are elements
\r
13132 if (prev && next) {
\r
13133 function findElementSibling(node, sibling_name) {
\r
13134 for (sibling = node; sibling; sibling = sibling[sibling_name]) {
\r
13135 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
\r
13138 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
\r
13145 // If previous sibling is empty then jump over it
\r
13146 prev = findElementSibling(prev, 'previousSibling');
\r
13147 next = findElementSibling(next, 'nextSibling');
\r
13149 // Compare next and previous nodes
\r
13150 if (compareElements(prev, next)) {
\r
13151 // Append nodes between
\r
13152 for (sibling = prev.nextSibling; sibling && sibling != next;) {
\r
13153 tmpSibling = sibling;
\r
13154 sibling = sibling.nextSibling;
\r
13155 prev.appendChild(tmpSibling);
\r
13158 // Remove next node
\r
13159 dom.remove(next);
\r
13161 // Move children into prev node
\r
13162 each(tinymce.grep(next.childNodes), function(node) {
\r
13163 prev.appendChild(node);
\r
13173 function isTextBlock(name) {
\r
13174 return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
\r
13177 function getContainer(rng, start) {
\r
13178 var container, offset, lastIdx;
\r
13180 container = rng[start ? 'startContainer' : 'endContainer'];
\r
13181 offset = rng[start ? 'startOffset' : 'endOffset'];
\r
13183 if (container.nodeType == 1) {
\r
13184 lastIdx = container.childNodes.length - 1;
\r
13186 if (!start && offset)
\r
13189 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
\r
13192 return container;
\r
13195 function performCaretAction(type, name, vars) {
\r
13196 var i, currentPendingFormats = pendingFormats[type],
\r
13197 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
\r
13199 function hasPending() {
\r
13200 return pendingFormats.apply.length || pendingFormats.remove.length;
\r
13203 function resetPending() {
\r
13204 pendingFormats.apply = [];
\r
13205 pendingFormats.remove = [];
\r
13208 function perform(caret_node) {
\r
13209 // Apply pending formats
\r
13210 each(pendingFormats.apply.reverse(), function(item) {
\r
13211 apply(item.name, item.vars, caret_node);
\r
13214 // Remove pending formats
\r
13215 each(pendingFormats.remove.reverse(), function(item) {
\r
13216 remove(item.name, item.vars, caret_node);
\r
13219 dom.remove(caret_node, 1);
\r
13223 // Check if it already exists then ignore it
\r
13224 for (i = currentPendingFormats.length - 1; i >= 0; i--) {
\r
13225 if (currentPendingFormats[i].name == name)
\r
13229 currentPendingFormats.push({name : name, vars : vars});
\r
13231 // Check if it's in the other type, then remove it
\r
13232 for (i = otherPendingFormats.length - 1; i >= 0; i--) {
\r
13233 if (otherPendingFormats[i].name == name)
\r
13234 otherPendingFormats.splice(i, 1);
\r
13237 // Pending apply or remove formats
\r
13238 if (hasPending()) {
\r
13239 ed.getDoc().execCommand('FontName', false, 'mceinline');
\r
13240 pendingFormats.lastRng = selection.getRng();
\r
13242 // IE will convert the current word
\r
13243 each(dom.select('font,span'), function(node) {
\r
13246 if (isCaretNode(node)) {
\r
13247 bookmark = selection.getBookmark();
\r
13249 selection.moveToBookmark(bookmark);
\r
13250 ed.nodeChanged();
\r
13254 // Only register listeners once if we need to
\r
13255 if (!pendingFormats.isListening && hasPending()) {
\r
13256 pendingFormats.isListening = true;
\r
13258 each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
\r
13259 ed[event].addToTop(function(ed, e) {
\r
13260 // Do we have pending formats and is the selection moved has moved
\r
13261 if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
\r
13262 each(dom.select('font,span'), function(node) {
\r
13263 var textNode, rng;
\r
13265 // Look for marker
\r
13266 if (isCaretNode(node)) {
\r
13267 textNode = node.firstChild;
\r
13272 rng = dom.createRng();
\r
13273 rng.setStart(textNode, textNode.nodeValue.length);
\r
13274 rng.setEnd(textNode, textNode.nodeValue.length);
\r
13275 selection.setRng(rng);
\r
13276 ed.nodeChanged();
\r
13278 dom.remove(node);
\r
13282 // Always unbind and clear pending styles on keyup
\r
13283 if (e.type == 'keyup' || e.type == 'mouseup')
\r
13294 tinymce.onAddEditor.add(function(tinymce, ed) {
\r
13295 var filters, fontSizes, dom, settings = ed.settings;
\r
13297 if (settings.inline_styles) {
\r
13298 fontSizes = tinymce.explode(settings.font_size_style_values);
\r
13300 function replaceWithSpan(node, styles) {
\r
13301 dom.replace(dom.create('span', {
\r
13307 font : function(dom, node) {
\r
13308 replaceWithSpan(node, {
\r
13309 backgroundColor : node.style.backgroundColor,
\r
13310 color : node.color,
\r
13311 fontFamily : node.face,
\r
13312 fontSize : fontSizes[parseInt(node.size) - 1]
\r
13316 u : function(dom, node) {
\r
13317 replaceWithSpan(node, {
\r
13318 textDecoration : 'underline'
\r
13322 strike : function(dom, node) {
\r
13323 replaceWithSpan(node, {
\r
13324 textDecoration : 'line-through'
\r
13329 function convert(editor, params) {
\r
13330 dom = editor.dom;
\r
13332 if (settings.convert_fonts_to_spans) {
\r
13333 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
\r
13334 filters[node.nodeName.toLowerCase()](ed.dom, node);
\r
13339 ed.onPreProcess.add(convert);
\r
13341 ed.onInit.add(function() {
\r
13342 ed.selection.onSetContent.add(convert);
\r