]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/tiny_mce_jquery_src.js
Release 6.2.2
[Github/sugarcrm.git] / include / javascript / tiny_mce / tiny_mce_jquery_src.js
1 (function(win) {
2         var whiteSpaceRe = /^\s*|\s*$/g,
3                 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
4
5         var tinymce = {
6                 majorVersion : '3',
7
8                 minorVersion : '4.2',
9
10                 releaseDate : '2011-04-07',
11
12                 _init : function() {
13                         var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
14
15                         t.isOpera = win.opera && opera.buildNumber;
16
17                         t.isWebKit = /WebKit/.test(ua);
18
19                         t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
20
21                         t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
22
23                         t.isGecko = !t.isWebKit && /Gecko/.test(ua);
24
25                         t.isMac = ua.indexOf('Mac') != -1;
26
27                         t.isAir = /adobeair/i.test(ua);
28
29                         t.isIDevice = /(iPad|iPhone)/.test(ua);
30
31                         // TinyMCE .NET webcontrol might be setting the values for TinyMCE
32                         if (win.tinyMCEPreInit) {
33                                 t.suffix = tinyMCEPreInit.suffix;
34                                 t.baseURL = tinyMCEPreInit.base;
35                                 t.query = tinyMCEPreInit.query;
36                                 return;
37                         }
38
39                         // Get suffix and base
40                         t.suffix = '';
41
42                         // If base element found, add that infront of baseURL
43                         nl = d.getElementsByTagName('base');
44                         for (i=0; i<nl.length; i++) {
45                                 if (v = nl[i].href) {
46                                         // Host only value like http://site.com or http://site.com:8008
47                                         if (/^https?:\/\/[^\/]+$/.test(v))
48                                                 v += '/';
49
50                                         base = v ? v.match(/.*\//)[0] : ''; // Get only directory
51                                 }
52                         }
53
54                         function getBase(n) {
55                                 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
56                                         if (/_(src|dev)\.js/g.test(n.src))
57                                                 t.suffix = '_src';
58
59                                         if ((p = n.src.indexOf('?')) != -1)
60                                                 t.query = n.src.substring(p + 1);
61
62                                         t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
63
64                                         // If path to script is relative and a base href was found add that one infront
65                                         // the src property will always be an absolute one on non IE browsers and IE 8
66                                         // so this logic will basically only be executed on older IE versions
67                                         if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
68                                                 t.baseURL = base + t.baseURL;
69
70                                         return t.baseURL;
71                                 }
72
73                                 return null;
74                         };
75
76                         // Check document
77                         nl = d.getElementsByTagName('script');
78                         for (i=0; i<nl.length; i++) {
79                                 if (getBase(nl[i]))
80                                         return;
81                         }
82
83                         // Check head
84                         n = d.getElementsByTagName('head')[0];
85                         if (n) {
86                                 nl = n.getElementsByTagName('script');
87                                 for (i=0; i<nl.length; i++) {
88                                         if (getBase(nl[i]))
89                                                 return;
90                                 }
91                         }
92
93                         return;
94                 },
95
96                 is : function(o, t) {
97                         if (!t)
98                                 return o !== undefined;
99
100                         if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
101                                 return true;
102
103                         return typeof(o) == t;
104                 },
105
106                 makeMap : function(items, delim, map) {
107                         var i;
108
109                         items = items || [];
110                         delim = delim || ',';
111
112                         if (typeof(items) == "string")
113                                 items = items.split(delim);
114
115                         map = map || {};
116
117                         i = items.length;
118                         while (i--)
119                                 map[items[i]] = {};
120
121                         return map;
122                 },
123
124                 each : function(o, cb, s) {
125                         var n, l;
126
127                         if (!o)
128                                 return 0;
129
130                         s = s || o;
131
132                         if (o.length !== undefined) {
133                                 // Indexed arrays, needed for Safari
134                                 for (n=0, l = o.length; n < l; n++) {
135                                         if (cb.call(s, o[n], n, o) === false)
136                                                 return 0;
137                                 }
138                         } else {
139                                 // Hashtables
140                                 for (n in o) {
141                                         if (o.hasOwnProperty(n)) {
142                                                 if (cb.call(s, o[n], n, o) === false)
143                                                         return 0;
144                                         }
145                                 }
146                         }
147
148                         return 1;
149                 },
150
151
152                 trim : function(s) {
153                         return (s ? '' + s : '').replace(whiteSpaceRe, '');
154                 },
155
156                 create : function(s, p, root) {
157                         var t = this, sp, ns, cn, scn, c, de = 0;
158
159                         // Parse : <prefix> <class>:<super class>
160                         s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
161                         cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
162
163                         // Create namespace for new class
164                         ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
165
166                         // Class already exists
167                         if (ns[cn])
168                                 return;
169
170                         // Make pure static class
171                         if (s[2] == 'static') {
172                                 ns[cn] = p;
173
174                                 if (this.onCreate)
175                                         this.onCreate(s[2], s[3], ns[cn]);
176
177                                 return;
178                         }
179
180                         // Create default constructor
181                         if (!p[cn]) {
182                                 p[cn] = function() {};
183                                 de = 1;
184                         }
185
186                         // Add constructor and methods
187                         ns[cn] = p[cn];
188                         t.extend(ns[cn].prototype, p);
189
190                         // Extend
191                         if (s[5]) {
192                                 sp = t.resolve(s[5]).prototype;
193                                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
194
195                                 // Extend constructor
196                                 c = ns[cn];
197                                 if (de) {
198                                         // Add passthrough constructor
199                                         ns[cn] = function() {
200                                                 return sp[scn].apply(this, arguments);
201                                         };
202                                 } else {
203                                         // Add inherit constructor
204                                         ns[cn] = function() {
205                                                 this.parent = sp[scn];
206                                                 return c.apply(this, arguments);
207                                         };
208                                 }
209                                 ns[cn].prototype[cn] = ns[cn];
210
211                                 // Add super methods
212                                 t.each(sp, function(f, n) {
213                                         ns[cn].prototype[n] = sp[n];
214                                 });
215
216                                 // Add overridden methods
217                                 t.each(p, function(f, n) {
218                                         // Extend methods if needed
219                                         if (sp[n]) {
220                                                 ns[cn].prototype[n] = function() {
221                                                         this.parent = sp[n];
222                                                         return f.apply(this, arguments);
223                                                 };
224                                         } else {
225                                                 if (n != cn)
226                                                         ns[cn].prototype[n] = f;
227                                         }
228                                 });
229                         }
230
231                         // Add static methods
232                         t.each(p['static'], function(f, n) {
233                                 ns[cn][n] = f;
234                         });
235
236                         if (this.onCreate)
237                                 this.onCreate(s[2], s[3], ns[cn].prototype);
238                 },
239
240                 walk : function(o, f, n, s) {
241                         s = s || this;
242
243                         if (o) {
244                                 if (n)
245                                         o = o[n];
246
247                                 tinymce.each(o, function(o, i) {
248                                         if (f.call(s, o, i, n) === false)
249                                                 return false;
250
251                                         tinymce.walk(o, f, n, s);
252                                 });
253                         }
254                 },
255
256                 createNS : function(n, o) {
257                         var i, v;
258
259                         o = o || win;
260
261                         n = n.split('.');
262                         for (i=0; i<n.length; i++) {
263                                 v = n[i];
264
265                                 if (!o[v])
266                                         o[v] = {};
267
268                                 o = o[v];
269                         }
270
271                         return o;
272                 },
273
274                 resolve : function(n, o) {
275                         var i, l;
276
277                         o = o || win;
278
279                         n = n.split('.');
280                         for (i = 0, l = n.length; i < l; i++) {
281                                 o = o[n[i]];
282
283                                 if (!o)
284                                         break;
285                         }
286
287                         return o;
288                 },
289
290                 addUnload : function(f, s) {
291                         var t = this;
292
293                         f = {func : f, scope : s || this};
294
295                         if (!t.unloads) {
296                                 function unload() {
297                                         var li = t.unloads, o, n;
298
299                                         if (li) {
300                                                 // Call unload handlers
301                                                 for (n in li) {
302                                                         o = li[n];
303
304                                                         if (o && o.func)
305                                                                 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
306                                                 }
307
308                                                 // Detach unload function
309                                                 if (win.detachEvent) {
310                                                         win.detachEvent('onbeforeunload', fakeUnload);
311                                                         win.detachEvent('onunload', unload);
312                                                 } else if (win.removeEventListener)
313                                                         win.removeEventListener('unload', unload, false);
314
315                                                 // Destroy references
316                                                 t.unloads = o = li = w = unload = 0;
317
318                                                 // Run garbarge collector on IE
319                                                 if (win.CollectGarbage)
320                                                         CollectGarbage();
321                                         }
322                                 };
323
324                                 function fakeUnload() {
325                                         var d = document;
326
327                                         // Is there things still loading, then do some magic
328                                         if (d.readyState == 'interactive') {
329                                                 function stop() {
330                                                         // Prevent memory leak
331                                                         d.detachEvent('onstop', stop);
332
333                                                         // Call unload handler
334                                                         if (unload)
335                                                                 unload();
336
337                                                         d = 0;
338                                                 };
339
340                                                 // Fire unload when the currently loading page is stopped
341                                                 if (d)
342                                                         d.attachEvent('onstop', stop);
343
344                                                 // Remove onstop listener after a while to prevent the unload function
345                                                 // to execute if the user presses cancel in an onbeforeunload
346                                                 // confirm dialog and then presses the browser stop button
347                                                 win.setTimeout(function() {
348                                                         if (d)
349                                                                 d.detachEvent('onstop', stop);
350                                                 }, 0);
351                                         }
352                                 };
353
354                                 // Attach unload handler
355                                 if (win.attachEvent) {
356                                         win.attachEvent('onunload', unload);
357                                         win.attachEvent('onbeforeunload', fakeUnload);
358                                 } else if (win.addEventListener)
359                                         win.addEventListener('unload', unload, false);
360
361                                 // Setup initial unload handler array
362                                 t.unloads = [f];
363                         } else
364                                 t.unloads.push(f);
365
366                         return f;
367                 },
368
369                 removeUnload : function(f) {
370                         var u = this.unloads, r = null;
371
372                         tinymce.each(u, function(o, i) {
373                                 if (o && o.func == f) {
374                                         u.splice(i, 1);
375                                         r = f;
376                                         return false;
377                                 }
378                         });
379
380                         return r;
381                 },
382
383                 explode : function(s, d) {
384                         return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
385                 },
386
387                 _addVer : function(u) {
388                         var v;
389
390                         if (!this.query)
391                                 return u;
392
393                         v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
394
395                         if (u.indexOf('#') == -1)
396                                 return u + v;
397
398                         return u.replace('#', v + '#');
399                 },
400
401                 // Fix function for IE 9 where regexps isn't working correctly
402                 // Todo: remove me once MS fixes the bug
403                 _replace : function(find, replace, str) {
404                         // On IE9 we have to fake $x replacement
405                         if (isRegExpBroken) {
406                                 return str.replace(find, function() {
407                                         var val = replace, args = arguments, i;
408
409                                         for (i = 0; i < args.length - 2; i++) {
410                                                 if (args[i] === undefined) {
411                                                         val = val.replace(new RegExp('\\$' + i, 'g'), '');
412                                                 } else {
413                                                         val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
414                                                 }
415                                         }
416
417                                         return val;
418                                 });
419                         }
420
421                         return str.replace(find, replace);
422                 }
423
424                 };
425
426         // Initialize the API
427         tinymce._init();
428
429         // Expose tinymce namespace to the global namespace (window)
430         win.tinymce = win.tinyMCE = tinymce;
431
432         // Describe the different namespaces
433
434         })(window);
435
436 (function($, tinymce) {
437         var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undefined;
438
439         // jQuery is undefined
440         if (!$)
441                 return alert("Load jQuery first!");
442
443         // Stick jQuery into the tinymce namespace
444         tinymce.$ = $;
445
446         // Setup adapter
447         tinymce.adapter = {
448                 patchEditor : function(editor) {
449                         var fn = $.fn;
450
451                         // Adapt the css function to make sure that the data-mce-style
452                         // attribute gets updated with the new style information
453                         function css(name, value) {
454                                 var self = this;
455
456                                 // Remove data-mce-style when set operation occurs
457                                 if (value)
458                                         self.removeAttr('data-mce-style');
459
460                                 return fn.css.apply(self, arguments);
461                         };
462
463                         // Apapt the attr function to make sure that it uses the data-mce- prefixed variants
464                         function attr(name, value) {
465                                 var self = this;
466
467                                 // Update/retrive data-mce- attribute variants
468                                 if (attrRegExp.test(name)) {
469                                         if (value !== undefined) {
470                                                 // Use TinyMCE behavior when setting the specifc attributes
471                                                 self.each(function(i, node) {
472                                                         editor.dom.setAttrib(node, name, value);
473                                                 });
474
475                                                 return self;
476                                         } else
477                                                 return self.attr('data-mce-' + name);
478                                 }
479
480                                 // Default behavior
481                                 return fn.attr.apply(self, arguments);
482                         };
483
484                         function htmlPatchFunc(func) {
485                                 // Returns a modified function that processes
486                                 // the HTML before executing the action this makes sure
487                                 // that href/src etc gets moved into the data-mce- variants
488                                 return function(content) {
489                                         if (content)
490                                                 content = editor.dom.processHTML(content);
491
492                                         return func.call(this, content);
493                                 };
494                         };
495
496                         // Patch various jQuery functions to handle tinymce specific attribute and content behavior
497                         // we don't patch the jQuery.fn directly since it will most likely break compatibility
498                         // with other jQuery logic on the page. Only instances created by TinyMCE should be patched.
499                         function patch(jq) {
500                                 // Patch some functions, only patch the object once
501                                 if (jq.css !== css) {
502                                         // Patch css/attr to use the data-mce- prefixed attribute variants
503                                         jq.css = css;
504                                         jq.attr = attr;
505
506                                         // Patch HTML functions to use the DOMUtils.processHTML filter logic
507                                         jq.html = htmlPatchFunc(fn.html);
508                                         jq.append = htmlPatchFunc(fn.append);
509                                         jq.prepend = htmlPatchFunc(fn.prepend);
510                                         jq.after = htmlPatchFunc(fn.after);
511                                         jq.before = htmlPatchFunc(fn.before);
512                                         jq.replaceWith = htmlPatchFunc(fn.replaceWith);
513                                         jq.tinymce = editor;
514
515                                         // Each pushed jQuery instance needs to be patched
516                                         // as well for example when traversing the DOM
517                                         jq.pushStack = function() {
518                                                 return patch(fn.pushStack.apply(this, arguments));
519                                         };
520                                 }
521
522                                 return jq;
523                         };
524
525                         // Add a $ function on each editor instance this one is scoped for the editor document object
526                         // this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red');
527                         editor.$ = function(selector, scope) {
528                                 var doc = editor.getDoc();
529
530                                 return patch($(selector || doc, doc || scope));
531                         };
532                 }
533         };
534
535         // Patch in core NS functions
536         tinymce.extend = $.extend;
537         tinymce.extend(tinymce, {
538                 map : $.map,
539                 grep : function(a, f) {return $.grep(a, f || function(){return 1;});},
540                 inArray : function(a, v) {return $.inArray(v, a || []);}
541
542                 /* Didn't iterate stylesheets
543                 each : function(o, cb, s) {
544                         if (!o)
545                                 return 0;
546
547                         var r = 1;
548
549                         $.each(o, function(nr, el){
550                                 if (cb.call(s, el, nr, o) === false) {
551                                         r = 0;
552                                         return false;
553                                 }
554                         });
555
556                         return r;
557                 }*/
558         });
559
560         // Patch in functions in various clases
561         // Add a "#ifndefjquery" statement around each core API function you add below
562         var patches = {
563                 'tinymce.dom.DOMUtils' : {
564                         /*
565                         addClass : function(e, c) {
566                                 if (is(e, 'array') && is(e[0], 'string'))
567                                         e = e.join(',#');
568                                 return (e && $(is(e, 'string') ? '#' + e : e)
569                                         .addClass(c)
570                                         .attr('class')) || false;
571                         },
572
573                         hasClass : function(n, c) {
574                                 return $(is(n, 'string') ? '#' + n : n).hasClass(c);
575                         },
576
577                         removeClass : function(e, c) {
578                                 if (!e)
579                                         return false;
580
581                                 var r = [];
582
583                                 $(is(e, 'string') ? '#' + e : e)
584                                         .removeClass(c)
585                                         .each(function(){
586                                                 r.push(this.className);
587                                         });
588
589                                 return r.length == 1 ? r[0] : r;
590                         },
591                         */
592
593                         select : function(pattern, scope) {
594                                 var t = this;
595
596                                 return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []);
597                         },
598
599                         is : function(n, patt) {
600                                 return $(this.get(n)).is(patt);
601                         }
602
603                         /*
604                         show : function(e) {
605                                 if (is(e, 'array') && is(e[0], 'string'))
606                                         e = e.join(',#');
607
608                                 $(is(e, 'string') ? '#' + e : e).css('display', 'block');
609                         },
610
611                         hide : function(e) {
612                                 if (is(e, 'array') && is(e[0], 'string'))
613                                         e = e.join(',#');
614
615                                 $(is(e, 'string') ? '#' + e : e).css('display', 'none');
616                         },
617
618                         isHidden : function(e) {
619                                 return $(is(e, 'string') ? '#' + e : e).is(':hidden');
620                         },
621
622                         insertAfter : function(n, e) {
623                                 return $(is(e, 'string') ? '#' + e : e).after(n);
624                         },
625
626                         replace : function(o, n, k) {
627                                 n = $(is(n, 'string') ? '#' + n : n);
628
629                                 if (k)
630                                         n.children().appendTo(o);
631
632                                 n.replaceWith(o);
633                         },
634
635                         setStyle : function(n, na, v) {
636                                 if (is(n, 'array') && is(n[0], 'string'))
637                                         n = n.join(',#');
638
639                                 $(is(n, 'string') ? '#' + n : n).css(na, v);
640                         },
641
642                         getStyle : function(n, na, c) {
643                                 return $(is(n, 'string') ? '#' + n : n).css(na);
644                         },
645
646                         setStyles : function(e, o) {
647                                 if (is(e, 'array') && is(e[0], 'string'))
648                                         e = e.join(',#');
649                                 $(is(e, 'string') ? '#' + e : e).css(o);
650                         },
651
652                         setAttrib : function(e, n, v) {
653                                 var t = this, s = t.settings;
654
655                                 if (is(e, 'array') && is(e[0], 'string'))
656                                         e = e.join(',#');
657
658                                 e = $(is(e, 'string') ? '#' + e : e);
659
660                                 switch (n) {
661                                         case "style":
662                                                 e.each(function(i, v){
663                                                         if (s.keep_values)
664                                                                 $(v).attr('data-mce-style', v);
665
666                                                         v.style.cssText = v;
667                                                 });
668                                                 break;
669
670                                         case "class":
671                                                 e.each(function(){
672                                                         this.className = v;
673                                                 });
674                                                 break;
675
676                                         case "src":
677                                         case "href":
678                                                 e.each(function(i, v){
679                                                         if (s.keep_values) {
680                                                                 if (s.url_converter)
681                                                                         v = s.url_converter.call(s.url_converter_scope || t, v, n, v);
682
683                                                                 t.setAttrib(v, 'data-mce-' + n, v);
684                                                         }
685                                                 });
686
687                                                 break;
688                                 }
689
690                                 if (v !== null && v.length !== 0)
691                                         e.attr(n, '' + v);
692                                 else
693                                         e.removeAttr(n);
694                         },
695
696                         setAttribs : function(e, o) {
697                                 var t = this;
698
699                                 $.each(o, function(n, v){
700                                         t.setAttrib(e,n,v);
701                                 });
702                         }
703                         */
704                 }
705
706 /*
707                 'tinymce.dom.Event' : {
708                         add : function (o, n, f, s) {
709                                 var lo, cb;
710
711                                 cb = function(e) {
712                                         e.target = e.target || this;
713                                         f.call(s || this, e);
714                                 };
715
716                                 if (is(o, 'array') && is(o[0], 'string'))
717                                         o = o.join(',#');
718                                 o = $(is(o, 'string') ? '#' + o : o);
719                                 if (n == 'init') {
720                                         o.ready(cb, s);
721                                 } else {
722                                         if (s) {
723                                                 o.bind(n, s, cb);
724                                         } else {
725                                                 o.bind(n, cb);
726                                         }
727                                 }
728
729                                 lo = this._jqLookup || (this._jqLookup = []);
730                                 lo.push({func : f, cfunc : cb});
731
732                                 return cb;
733                         },
734
735                         remove : function(o, n, f) {
736                                 // Find cfunc
737                                 $(this._jqLookup).each(function() {
738                                         if (this.func === f)
739                                                 f = this.cfunc;
740                                 });
741
742                                 if (is(o, 'array') && is(o[0], 'string'))
743                                         o = o.join(',#');
744
745                                 $(is(o, 'string') ? '#' + o : o).unbind(n,f);
746
747                                 return true;
748                         }
749                 }
750 */
751         };
752
753         // Patch functions after a class is created
754         tinymce.onCreate = function(ty, c, p) {
755                 tinymce.extend(p, patches[c]);
756         };
757 })(window.jQuery, tinymce);
758
759
760
761 tinymce.create('tinymce.util.Dispatcher', {
762         scope : null,
763         listeners : null,
764
765         Dispatcher : function(s) {
766                 this.scope = s || this;
767                 this.listeners = [];
768         },
769
770         add : function(cb, s) {
771                 this.listeners.push({cb : cb, scope : s || this.scope});
772
773                 return cb;
774         },
775
776         addToTop : function(cb, s) {
777                 this.listeners.unshift({cb : cb, scope : s || this.scope});
778
779                 return cb;
780         },
781
782         remove : function(cb) {
783                 var l = this.listeners, o = null;
784
785                 tinymce.each(l, function(c, i) {
786                         if (cb == c.cb) {
787                                 o = cb;
788                                 l.splice(i, 1);
789                                 return false;
790                         }
791                 });
792
793                 return o;
794         },
795
796         dispatch : function() {
797                 var s, a = arguments, i, li = this.listeners, c;
798
799                 // Needs to be a real loop since the listener count might change while looping
800                 // And this is also more efficient
801                 for (i = 0; i<li.length; i++) {
802                         c = li[i];
803                         s = c.cb.apply(c.scope, a);
804
805                         if (s === false)
806                                 break;
807                 }
808
809                 return s;
810         }
811
812         });
813
814 (function() {
815         var each = tinymce.each;
816
817         tinymce.create('tinymce.util.URI', {
818                 URI : function(u, s) {
819                         var t = this, o, a, b;
820
821                         // Trim whitespace
822                         u = tinymce.trim(u);
823
824                         // Default settings
825                         s = t.settings = s || {};
826
827                         // Strange app protocol or local anchor
828                         if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
829                                 t.source = u;
830                                 return;
831                         }
832
833                         // Absolute path with no host, fake host and protocol
834                         if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
835                                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
836
837                         // Relative path http:// or protocol relative //path
838                         if (!/^\w*:?\/\//.test(u))
839                                 u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
840
841                         // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
842                         u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
843                         u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
844                         each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
845                                 var s = u[i];
846
847                                 // Zope 3 workaround, they use @@something
848                                 if (s)
849                                         s = s.replace(/\(mce_at\)/g, '@@');
850
851                                 t[v] = s;
852                         });
853
854                         if (b = s.base_uri) {
855                                 if (!t.protocol)
856                                         t.protocol = b.protocol;
857
858                                 if (!t.userInfo)
859                                         t.userInfo = b.userInfo;
860
861                                 if (!t.port && t.host == 'mce_host')
862                                         t.port = b.port;
863
864                                 if (!t.host || t.host == 'mce_host')
865                                         t.host = b.host;
866
867                                 t.source = '';
868                         }
869
870                         //t.path = t.path || '/';
871                 },
872
873                 setPath : function(p) {
874                         var t = this;
875
876                         p = /^(.*?)\/?(\w+)?$/.exec(p);
877
878                         // Update path parts
879                         t.path = p[0];
880                         t.directory = p[1];
881                         t.file = p[2];
882
883                         // Rebuild source
884                         t.source = '';
885                         t.getURI();
886                 },
887
888                 toRelative : function(u) {
889                         var t = this, o;
890
891                         if (u === "./")
892                                 return u;
893
894                         u = new tinymce.util.URI(u, {base_uri : t});
895
896                         // Not on same domain/port or protocol
897                         if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
898                                 return u.getURI();
899
900                         o = t.toRelPath(t.path, u.path);
901
902                         // Add query
903                         if (u.query)
904                                 o += '?' + u.query;
905
906                         // Add anchor
907                         if (u.anchor)
908                                 o += '#' + u.anchor;
909
910                         return o;
911                 },
912         
913                 toAbsolute : function(u, nh) {
914                         var u = new tinymce.util.URI(u, {base_uri : this});
915
916                         return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
917                 },
918
919                 toRelPath : function(base, path) {
920                         var items, bp = 0, out = '', i, l;
921
922                         // Split the paths
923                         base = base.substring(0, base.lastIndexOf('/'));
924                         base = base.split('/');
925                         items = path.split('/');
926
927                         if (base.length >= items.length) {
928                                 for (i = 0, l = base.length; i < l; i++) {
929                                         if (i >= items.length || base[i] != items[i]) {
930                                                 bp = i + 1;
931                                                 break;
932                                         }
933                                 }
934                         }
935
936                         if (base.length < items.length) {
937                                 for (i = 0, l = items.length; i < l; i++) {
938                                         if (i >= base.length || base[i] != items[i]) {
939                                                 bp = i + 1;
940                                                 break;
941                                         }
942                                 }
943                         }
944
945                         if (bp == 1)
946                                 return path;
947
948                         for (i = 0, l = base.length - (bp - 1); i < l; i++)
949                                 out += "../";
950
951                         for (i = bp - 1, l = items.length; i < l; i++) {
952                                 if (i != bp - 1)
953                                         out += "/" + items[i];
954                                 else
955                                         out += items[i];
956                         }
957
958                         return out;
959                 },
960
961                 toAbsPath : function(base, path) {
962                         var i, nb = 0, o = [], tr, outPath;
963
964                         // Split paths
965                         tr = /\/$/.test(path) ? '/' : '';
966                         base = base.split('/');
967                         path = path.split('/');
968
969                         // Remove empty chunks
970                         each(base, function(k) {
971                                 if (k)
972                                         o.push(k);
973                         });
974
975                         base = o;
976
977                         // Merge relURLParts chunks
978                         for (i = path.length - 1, o = []; i >= 0; i--) {
979                                 // Ignore empty or .
980                                 if (path[i].length == 0 || path[i] == ".")
981                                         continue;
982
983                                 // Is parent
984                                 if (path[i] == '..') {
985                                         nb++;
986                                         continue;
987                                 }
988
989                                 // Move up
990                                 if (nb > 0) {
991                                         nb--;
992                                         continue;
993                                 }
994
995                                 o.push(path[i]);
996                         }
997
998                         i = base.length - nb;
999
1000                         // If /a/b/c or /
1001                         if (i <= 0)
1002                                 outPath = o.reverse().join('/');
1003                         else
1004                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
1005
1006                         // Add front / if it's needed
1007                         if (outPath.indexOf('/') !== 0)
1008                                 outPath = '/' + outPath;
1009
1010                         // Add traling / if it's needed
1011                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
1012                                 outPath += tr;
1013
1014                         return outPath;
1015                 },
1016
1017                 getURI : function(nh) {
1018                         var s, t = this;
1019
1020                         // Rebuild source
1021                         if (!t.source || nh) {
1022                                 s = '';
1023
1024                                 if (!nh) {
1025                                         if (t.protocol)
1026                                                 s += t.protocol + '://';
1027
1028                                         if (t.userInfo)
1029                                                 s += t.userInfo + '@';
1030
1031                                         if (t.host)
1032                                                 s += t.host;
1033
1034                                         if (t.port)
1035                                                 s += ':' + t.port;
1036                                 }
1037
1038                                 if (t.path)
1039                                         s += t.path;
1040
1041                                 if (t.query)
1042                                         s += '?' + t.query;
1043
1044                                 if (t.anchor)
1045                                         s += '#' + t.anchor;
1046
1047                                 t.source = s;
1048                         }
1049
1050                         return t.source;
1051                 }
1052         });
1053 })();
1054
1055 (function() {
1056         var each = tinymce.each;
1057
1058         tinymce.create('static tinymce.util.Cookie', {
1059                 getHash : function(n) {
1060                         var v = this.get(n), h;
1061
1062                         if (v) {
1063                                 each(v.split('&'), function(v) {
1064                                         v = v.split('=');
1065                                         h = h || {};
1066                                         h[unescape(v[0])] = unescape(v[1]);
1067                                 });
1068                         }
1069
1070                         return h;
1071                 },
1072
1073                 setHash : function(n, v, e, p, d, s) {
1074                         var o = '';
1075
1076                         each(v, function(v, k) {
1077                                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
1078                         });
1079
1080                         this.set(n, o, e, p, d, s);
1081                 },
1082
1083                 get : function(n) {
1084                         var c = document.cookie, e, p = n + "=", b;
1085
1086                         // Strict mode
1087                         if (!c)
1088                                 return;
1089
1090                         b = c.indexOf("; " + p);
1091
1092                         if (b == -1) {
1093                                 b = c.indexOf(p);
1094
1095                                 if (b != 0)
1096                                         return null;
1097                         } else
1098                                 b += 2;
1099
1100                         e = c.indexOf(";", b);
1101
1102                         if (e == -1)
1103                                 e = c.length;
1104
1105                         return unescape(c.substring(b + p.length, e));
1106                 },
1107
1108                 set : function(n, v, e, p, d, s) {
1109                         document.cookie = n + "=" + escape(v) +
1110                                 ((e) ? "; expires=" + e.toGMTString() : "") +
1111                                 ((p) ? "; path=" + escape(p) : "") +
1112                                 ((d) ? "; domain=" + d : "") +
1113                                 ((s) ? "; secure" : "");
1114                 },
1115
1116                 remove : function(n, p) {
1117                         var d = new Date();
1118
1119                         d.setTime(d.getTime() - 1000);
1120
1121                         this.set(n, '', d, p, d);
1122                 }
1123         });
1124 })();
1125
1126 (function() {
1127         function serialize(o, quote) {
1128                 var i, v, t;
1129
1130                 quote = quote || '"';
1131
1132                 if (o == null)
1133                         return 'null';
1134
1135                 t = typeof o;
1136
1137                 if (t == 'string') {
1138                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
1139
1140                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
1141                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
1142                                 if (quote === '"' && a === "'")
1143                                         return a;
1144
1145                                 i = v.indexOf(b);
1146
1147                                 if (i + 1)
1148                                         return '\\' + v.charAt(i + 1);
1149
1150                                 a = b.charCodeAt().toString(16);
1151
1152                                 return '\\u' + '0000'.substring(a.length) + a;
1153                         }) + quote;
1154                 }
1155
1156                 if (t == 'object') {
1157                         if (o.hasOwnProperty && o instanceof Array) {
1158                                         for (i=0, v = '['; i<o.length; i++)
1159                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
1160
1161                                         return v + ']';
1162                                 }
1163
1164                                 v = '{';
1165
1166                                 for (i in o)
1167                                         v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
1168
1169                                 return v + '}';
1170                 }
1171
1172                 return '' + o;
1173         };
1174
1175         tinymce.util.JSON = {
1176                 serialize: serialize,
1177
1178                 parse: function(s) {
1179                         try {
1180                                 return eval('(' + s + ')');
1181                         } catch (ex) {
1182                                 // Ignore
1183                         }
1184                 }
1185
1186                 };
1187 })();
1188 tinymce.create('static tinymce.util.XHR', {
1189         send : function(o) {
1190                 var x, t, w = window, c = 0;
1191
1192                 // Default settings
1193                 o.scope = o.scope || this;
1194                 o.success_scope = o.success_scope || o.scope;
1195                 o.error_scope = o.error_scope || o.scope;
1196                 o.async = o.async === false ? false : true;
1197                 o.data = o.data || '';
1198
1199                 function get(s) {
1200                         x = 0;
1201
1202                         try {
1203                                 x = new ActiveXObject(s);
1204                         } catch (ex) {
1205                         }
1206
1207                         return x;
1208                 };
1209
1210                 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1211
1212                 if (x) {
1213                         if (x.overrideMimeType)
1214                                 x.overrideMimeType(o.content_type);
1215
1216                         x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1217
1218                         if (o.content_type)
1219                                 x.setRequestHeader('Content-Type', o.content_type);
1220
1221                         x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1222
1223                         x.send(o.data);
1224
1225                         function ready() {
1226                                 if (!o.async || x.readyState == 4 || c++ > 10000) {
1227                                         if (o.success && c < 10000 && x.status == 200)
1228                                                 o.success.call(o.success_scope, '' + x.responseText, x, o);
1229                                         else if (o.error)
1230                                                 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
1231
1232                                         x = null;
1233                                 } else
1234                                         w.setTimeout(ready, 10);
1235                         };
1236
1237                         // Syncronous request
1238                         if (!o.async)
1239                                 return ready();
1240
1241                         // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1242                         t = w.setTimeout(ready, 10);
1243                 }
1244         }
1245 });
1246
1247 (function() {
1248         var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1249
1250         tinymce.create('tinymce.util.JSONRequest', {
1251                 JSONRequest : function(s) {
1252                         this.settings = extend({
1253                         }, s);
1254                         this.count = 0;
1255                 },
1256
1257                 send : function(o) {
1258                         var ecb = o.error, scb = o.success;
1259
1260                         o = extend(this.settings, o);
1261
1262                         o.success = function(c, x) {
1263                                 c = JSON.parse(c);
1264
1265                                 if (typeof(c) == 'undefined') {
1266                                         c = {
1267                                                 error : 'JSON Parse error.'
1268                                         };
1269                                 }
1270
1271                                 if (c.error)
1272                                         ecb.call(o.error_scope || o.scope, c.error, x);
1273                                 else
1274                                         scb.call(o.success_scope || o.scope, c.result);
1275                         };
1276
1277                         o.error = function(ty, x) {
1278                                 if (ecb)
1279                                         ecb.call(o.error_scope || o.scope, ty, x);
1280                         };
1281
1282                         o.data = JSON.serialize({
1283                                 id : o.id || 'c' + (this.count++),
1284                                 method : o.method,
1285                                 params : o.params
1286                         });
1287
1288                         // JSON content type for Ruby on rails. Bug: #1883287
1289                         o.content_type = 'application/json';
1290
1291                         XHR.send(o);
1292                 },
1293
1294                 'static' : {
1295                         sendRPC : function(o) {
1296                                 return new tinymce.util.JSONRequest().send(o);
1297                         }
1298                 }
1299         });
1300 }());
1301 (function(tinymce) {
1302         var namedEntities, baseEntities, reverseEntities,
1303                 attrsCharsRegExp = /[&\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1304                 textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1305                 rawCharsRegExp = /[<>&\"\']/g,
1306                 entityRegExp = /&(#)?([\w]+);/g,
1307                 asciiMap = {
1308                                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1309                                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1310                                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1311                                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1312                                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1313                 };
1314
1315         // Raw entities
1316         baseEntities = {
1317                 '"' : '&quot;',
1318                 "'" : '&#39;',
1319                 '<' : '&lt;',
1320                 '>' : '&gt;',
1321                 '&' : '&amp;'
1322         };
1323
1324         // Reverse lookup table for raw entities
1325         reverseEntities = {
1326                 '&lt;' : '<',
1327                 '&gt;' : '>',
1328                 '&amp;' : '&',
1329                 '&quot;' : '"',
1330                 '&apos;' : "'"
1331         };
1332
1333         // Decodes text by using the browser
1334         function nativeDecode(text) {
1335                 var elm;
1336
1337                 elm = document.createElement("div");
1338                 elm.innerHTML = text;
1339
1340                 return elm.textContent || elm.innerText || text;
1341         };
1342
1343         // Build a two way lookup table for the entities
1344         function buildEntitiesLookup(items, radix) {
1345                 var i, chr, entity, lookup = {};
1346
1347                 if (items) {
1348                         items = items.split(',');
1349                         radix = radix || 10;
1350
1351                         // Build entities lookup table
1352                         for (i = 0; i < items.length; i += 2) {
1353                                 chr = String.fromCharCode(parseInt(items[i], radix));
1354
1355                                 // Only add non base entities
1356                                 if (!baseEntities[chr]) {
1357                                         entity = '&' + items[i + 1] + ';';
1358                                         lookup[chr] = entity;
1359                                         lookup[entity] = chr;
1360                                 }
1361                         }
1362
1363                         return lookup;
1364                 }
1365         };
1366
1367         // Unpack entities lookup where the numbers are in radix 32 to reduce the size
1368         namedEntities = buildEntitiesLookup(
1369                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1370                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1371                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1372                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1373                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1374                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1375                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1376                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1377                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1378                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1379                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1380                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1381                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1382                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1383                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1384                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1385                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1386                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1387                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1388                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1389                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1390                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1391                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1392                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1393                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
1394         , 32);
1395
1396         tinymce.html = tinymce.html || {};
1397
1398         tinymce.html.Entities = {
1399                 encodeRaw : function(text, attr) {
1400                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1401                                 return baseEntities[chr] || chr;
1402                         });
1403                 },
1404
1405                 encodeAllRaw : function(text) {
1406                         return ('' + text).replace(rawCharsRegExp, function(chr) {
1407                                 return baseEntities[chr] || chr;
1408                         });
1409                 },
1410
1411                 encodeNumeric : function(text, attr) {
1412                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1413                                 // Multi byte sequence convert it to a single entity
1414                                 if (chr.length > 1)
1415                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1416
1417                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1418                         });
1419                 },
1420
1421                 encodeNamed : function(text, attr, entities) {
1422                         entities = entities || namedEntities;
1423
1424                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1425                                 return baseEntities[chr] || entities[chr] || chr;
1426                         });
1427                 },
1428
1429                 getEncodeFunc : function(name, entities) {
1430                         var Entities = tinymce.html.Entities;
1431
1432                         entities = buildEntitiesLookup(entities) || namedEntities;
1433
1434                         function encodeNamedAndNumeric(text, attr) {
1435                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1436                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1437                                 });
1438                         };
1439
1440                         function encodeCustomNamed(text, attr) {
1441                                 return Entities.encodeNamed(text, attr, entities);
1442                         };
1443
1444                         // Replace + with , to be compatible with previous TinyMCE versions
1445                         name = tinymce.makeMap(name.replace(/\+/g, ','));
1446
1447                         // Named and numeric encoder
1448                         if (name.named && name.numeric)
1449                                 return encodeNamedAndNumeric;
1450
1451                         // Named encoder
1452                         if (name.named) {
1453                                 // Custom names
1454                                 if (entities)
1455                                         return encodeCustomNamed;
1456
1457                                 return Entities.encodeNamed;
1458                         }
1459
1460                         // Numeric
1461                         if (name.numeric)
1462                                 return Entities.encodeNumeric;
1463
1464                         // Raw encoder
1465                         return Entities.encodeRaw;
1466                 },
1467
1468                 decode : function(text) {
1469                         return text.replace(entityRegExp, function(all, numeric, value) {
1470                                 if (numeric) {
1471                                         value = parseInt(value);
1472
1473                                         // Support upper UTF
1474                                         if (value > 0xFFFF) {
1475                                                 value -= 0x10000;
1476
1477                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1478                                         } else
1479                                                 return asciiMap[value] || String.fromCharCode(value);
1480                                 }
1481
1482                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1483                         });
1484                 }
1485         };
1486 })(tinymce);
1487
1488 tinymce.html.Styles = function(settings, schema) {
1489         var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1490                 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1491                 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1492                 trimRightRegExp = /\s+$/,
1493                 urlColorRegExp = /rgb/,
1494                 undef, i, encodingLookup = {}, encodingItems;
1495
1496         settings = settings || {};
1497
1498         encodingItems = '\\" \\\' \\; \\: ; : _'.split(' ');
1499         for (i = 0; i < encodingItems.length; i++) {
1500                 encodingLookup[encodingItems[i]] = '_' + i;
1501                 encodingLookup['_' + i] = encodingItems[i];
1502         }
1503
1504         function toHex(match, r, g, b) {
1505                 function hex(val) {
1506                         val = parseInt(val).toString(16);
1507
1508                         return val.length > 1 ? val : '0' + val; // 0 -> 00
1509                 };
1510
1511                 return '#' + hex(r) + hex(g) + hex(b);
1512         };
1513
1514         return {
1515                 toHex : function(color) {
1516                         return color.replace(rgbRegExp, toHex);
1517                 },
1518
1519                 parse : function(css) {
1520                         var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1521
1522                         function compress(prefix, suffix) {
1523                                 var top, right, bottom, left;
1524
1525                                 // Get values and check it it needs compressing
1526                                 top = styles[prefix + '-top' + suffix];
1527                                 if (!top)
1528                                         return;
1529
1530                                 right = styles[prefix + '-right' + suffix];
1531                                 if (top != right)
1532                                         return;
1533
1534                                 bottom = styles[prefix + '-bottom' + suffix];
1535                                 if (right != bottom)
1536                                         return;
1537
1538                                 left = styles[prefix + '-left' + suffix];
1539                                 if (bottom != left)
1540                                         return;
1541
1542                                 // Compress
1543                                 styles[prefix + suffix] = left;
1544                                 delete styles[prefix + '-top' + suffix];
1545                                 delete styles[prefix + '-right' + suffix];
1546                                 delete styles[prefix + '-bottom' + suffix];
1547                                 delete styles[prefix + '-left' + suffix];
1548                         };
1549
1550                         function canCompress(key) {
1551                                 var value = styles[key], i;
1552
1553                                 if (!value || value.indexOf(' ') < 0)
1554                                         return;
1555
1556                                 value = value.split(' ');
1557                                 i = value.length;
1558                                 while (i--) {
1559                                         if (value[i] !== value[0])
1560                                                 return false;
1561                                 }
1562
1563                                 styles[key] = value[0];
1564
1565                                 return true;
1566                         };
1567
1568                         function compress2(target, a, b, c) {
1569                                 if (!canCompress(a))
1570                                         return;
1571
1572                                 if (!canCompress(b))
1573                                         return;
1574
1575                                 if (!canCompress(c))
1576                                         return;
1577
1578                                 // Compress
1579                                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
1580                                 delete styles[a];
1581                                 delete styles[b];
1582                                 delete styles[c];
1583                         };
1584
1585                         // Encodes the specified string by replacing all \" \' ; : with _<num>
1586                         function encode(str) {
1587                                 isEncoded = true;
1588
1589                                 return encodingLookup[str];
1590                         };
1591
1592                         // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
1593                         // It will also decode the \" \' if keep_slashes is set to fale or omitted
1594                         function decode(str, keep_slashes) {
1595                                 if (isEncoded) {
1596                                         str = str.replace(/_[0-9]/g, function(str) {
1597                                                 return encodingLookup[str];
1598                                         });
1599                                 }
1600
1601                                 if (!keep_slashes)
1602                                         str = str.replace(/\\([\'\";:])/g, "$1");
1603
1604                                 return str;
1605                         }
1606
1607                         if (css) {
1608                                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
1609                                 css = css.replace(/\\[\"\';:_]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
1610                                         return str.replace(/[;:]/g, encode);
1611                                 });
1612
1613                                 // Parse styles
1614                                 while (matches = styleRegExp.exec(css)) {
1615                                         name = matches[1].replace(trimRightRegExp, '').toLowerCase();
1616                                         value = matches[2].replace(trimRightRegExp, '');
1617
1618                                         if (name && value.length > 0) {
1619                                                 // Opera will produce 700 instead of bold in their style values
1620                                                 if (name === 'font-weight' && value === '700')
1621                                                         value = 'bold';
1622                                                 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
1623                                                         value = value.toLowerCase();            
1624
1625                                                 // Convert RGB colors to HEX
1626                                                 value = value.replace(rgbRegExp, toHex);
1627
1628                                                 // Convert URLs and force them into url('value') format
1629                                                 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
1630                                                         str = str || str2;
1631
1632                                                         if (str) {
1633                                                                 str = decode(str);
1634
1635                                                                 // Force strings into single quote format
1636                                                                 return "'" + str.replace(/\'/g, "\\'") + "'";
1637                                                         }
1638
1639                                                         url = decode(url || url2 || url3);
1640
1641                                                         // Convert the URL to relative/absolute depending on config
1642                                                         if (urlConverter)
1643                                                                 url = urlConverter.call(urlConverterScope, url, 'style');
1644
1645                                                         // Output new URL format
1646                                                         return "url('" + url.replace(/\'/g, "\\'") + "')";
1647                                                 });
1648
1649                                                 styles[name] = isEncoded ? decode(value, true) : value;
1650                                         }
1651
1652                                         styleRegExp.lastIndex = matches.index + matches[0].length;
1653                                 }
1654
1655                                 // Compress the styles to reduce it's size for example IE will expand styles
1656                                 compress("border", "");
1657                                 compress("border", "-width");
1658                                 compress("border", "-color");
1659                                 compress("border", "-style");
1660                                 compress("padding", "");
1661                                 compress("margin", "");
1662                                 compress2('border', 'border-width', 'border-style', 'border-color');
1663
1664                                 // Remove pointless border, IE produces these
1665                                 if (styles.border === 'medium none')
1666                                         delete styles.border;
1667                         }
1668
1669                         return styles;
1670                 },
1671
1672                 serialize : function(styles, element_name) {
1673                         var css = '', name, value;
1674
1675                         function serializeStyles(name) {
1676                                 var styleList, i, l, name, value;
1677
1678                                 styleList = schema.styles[name];
1679                                 if (styleList) {
1680                                         for (i = 0, l = styleList.length; i < l; i++) {
1681                                                 name = styleList[i];
1682                                                 value = styles[name];
1683
1684                                                 if (value !== undef && value.length > 0)
1685                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1686                                         }
1687                                 }
1688                         };
1689
1690                         // Serialize styles according to schema
1691                         if (element_name && schema && schema.styles) {
1692                                 // Serialize global styles and element specific styles
1693                                 serializeStyles('*');
1694                                 serializeStyles(name);
1695                         } else {
1696                                 // Output the styles in the order they are inside the object
1697                                 for (name in styles) {
1698                                         value = styles[name];
1699
1700                                         if (value !== undef && value.length > 0)
1701                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1702                                 }
1703                         }
1704
1705                         return css;
1706                 }
1707         };
1708 };
1709
1710 (function(tinymce) {
1711         var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap,
1712                 whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1713
1714         function split(str, delim) {
1715                 return str.split(delim || ',');
1716         };
1717
1718         function unpack(lookup, data) {
1719                 var key, elements = {};
1720
1721                 function replace(value) {
1722                         return value.replace(/[A-Z]+/g, function(key) {
1723                                 return replace(lookup[key]);
1724                         });
1725                 };
1726
1727                 // Unpack lookup
1728                 for (key in lookup) {
1729                         if (lookup.hasOwnProperty(key))
1730                                 lookup[key] = replace(lookup[key]);
1731                 }
1732
1733                 // Unpack and parse data into object map
1734                 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
1735                         attributes = split(attributes, '|');
1736
1737                         elements[name] = {
1738                                 attributes : makeMap(attributes),
1739                                 attributesOrder : attributes,
1740                                 children : makeMap(children, '|', {'#comment' : {}})
1741                         }
1742                 });
1743
1744                 return elements;
1745         };
1746
1747         // Build a lookup table for block elements both lowercase and uppercase
1748         blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + 
1749                                                 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + 
1750                                                 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
1751         blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
1752
1753         // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
1754         transitional = unpack({
1755                 Z : 'H|K|N|O|P',
1756                 Y : 'X|form|R|Q',
1757                 ZG : 'E|span|width|align|char|charoff|valign',
1758                 X : 'p|T|div|U|W|isindex|fieldset|table',
1759                 ZF : 'E|align|char|charoff|valign',
1760                 W : 'pre|hr|blockquote|address|center|noframes',
1761                 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
1762                 ZD : '[E][S]',
1763                 U : 'ul|ol|dl|menu|dir',
1764                 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
1765                 T : 'h1|h2|h3|h4|h5|h6',
1766                 ZB : 'X|S|Q',
1767                 S : 'R|P',
1768                 ZA : 'a|G|J|M|O|P',
1769                 R : 'a|H|K|N|O',
1770                 Q : 'noscript|P',
1771                 P : 'ins|del|script',
1772                 O : 'input|select|textarea|label|button',
1773                 N : 'M|L',
1774                 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
1775                 L : 'sub|sup',
1776                 K : 'J|I',
1777                 J : 'tt|i|b|u|s|strike',
1778                 I : 'big|small|font|basefont',
1779                 H : 'G|F',
1780                 G : 'br|span|bdo',
1781                 F : 'object|applet|img|map|iframe',
1782                 E : 'A|B|C',
1783                 D : 'accesskey|tabindex|onfocus|onblur',
1784                 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
1785                 B : 'lang|xml:lang|dir',
1786                 A : 'id|class|style|title'
1787         }, 'script[id|charset|type|language|src|defer|xml:space][]' + 
1788                 'style[B|id|type|media|title|xml:space][]' + 
1789                 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
1790                 'param[id|name|value|valuetype|type][]' + 
1791                 'p[E|align][#|S]' + 
1792                 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
1793                 'br[A|clear][]' + 
1794                 'span[E][#|S]' + 
1795                 'bdo[A|C|B][#|S]' + 
1796                 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
1797                 'h1[E|align][#|S]' + 
1798                 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
1799                 'map[B|C|A|name][X|form|Q|area]' + 
1800                 'h2[E|align][#|S]' + 
1801                 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
1802                 'h3[E|align][#|S]' + 
1803                 'tt[E][#|S]' + 
1804                 'i[E][#|S]' + 
1805                 'b[E][#|S]' + 
1806                 'u[E][#|S]' + 
1807                 's[E][#|S]' + 
1808                 'strike[E][#|S]' + 
1809                 'big[E][#|S]' + 
1810                 'small[E][#|S]' + 
1811                 'font[A|B|size|color|face][#|S]' + 
1812                 'basefont[id|size|color|face][]' + 
1813                 'em[E][#|S]' + 
1814                 'strong[E][#|S]' + 
1815                 'dfn[E][#|S]' + 
1816                 'code[E][#|S]' + 
1817                 'q[E|cite][#|S]' + 
1818                 'samp[E][#|S]' + 
1819                 'kbd[E][#|S]' + 
1820                 'var[E][#|S]' + 
1821                 'cite[E][#|S]' + 
1822                 'abbr[E][#|S]' + 
1823                 'acronym[E][#|S]' + 
1824                 'sub[E][#|S]' + 
1825                 'sup[E][#|S]' + 
1826                 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
1827                 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
1828                 'optgroup[E|disabled|label][option]' + 
1829                 'option[E|selected|disabled|label|value][]' + 
1830                 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
1831                 'label[E|for|accesskey|onfocus|onblur][#|S]' + 
1832                 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
1833                 'h4[E|align][#|S]' + 
1834                 'ins[E|cite|datetime][#|Y]' + 
1835                 'h5[E|align][#|S]' + 
1836                 'del[E|cite|datetime][#|Y]' + 
1837                 'h6[E|align][#|S]' + 
1838                 'div[E|align][#|Y]' + 
1839                 'ul[E|type|compact][li]' + 
1840                 'li[E|type|value][#|Y]' + 
1841                 'ol[E|type|compact|start][li]' + 
1842                 'dl[E|compact][dt|dd]' + 
1843                 'dt[E][#|S]' + 
1844                 'dd[E][#|Y]' + 
1845                 'menu[E|compact][li]' + 
1846                 'dir[E|compact][li]' + 
1847                 'pre[E|width|xml:space][#|ZA]' + 
1848                 'hr[E|align|noshade|size|width][]' + 
1849                 'blockquote[E|cite][#|Y]' + 
1850                 'address[E][#|S|p]' + 
1851                 'center[E][#|Y]' + 
1852                 'noframes[E][#|Y]' + 
1853                 'isindex[A|B|prompt][]' + 
1854                 'fieldset[E][#|legend|Y]' + 
1855                 'legend[E|accesskey|align][#|S]' + 
1856                 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
1857                 'caption[E|align][#|S]' + 
1858                 'col[ZG][]' + 
1859                 'colgroup[ZG][col]' + 
1860                 'thead[ZF][tr]' + 
1861                 'tr[ZF|bgcolor][th|td]' + 
1862                 'th[E|ZE][#|Y]' + 
1863                 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
1864                 'noscript[E][#|Y]' + 
1865                 'td[E|ZE][#|Y]' + 
1866                 'tfoot[ZF][tr]' + 
1867                 'tbody[ZF][tr]' + 
1868                 'area[E|D|shape|coords|href|nohref|alt|target][]' + 
1869                 'base[id|href|target][]' + 
1870                 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
1871         );
1872
1873         boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,autoplay,loop,controls');
1874         shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
1875         nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,object'), shortEndedElementsMap);
1876         whiteSpaceElementsMap = makeMap('pre,script,style');
1877         selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
1878
1879         tinymce.html.Schema = function(settings) {
1880                 var self = this, elements = {}, children = {}, patternElements = [], validStyles;
1881
1882                 settings = settings || {};
1883
1884                 // Allow all elements and attributes if verify_html is set to false
1885                 if (settings.verify_html === false)
1886                         settings.valid_elements = '*[*]';
1887
1888                 // Build styles list
1889                 if (settings.valid_styles) {
1890                         validStyles = {};
1891
1892                         // Convert styles into a rule list
1893                         each(settings.valid_styles, function(value, key) {
1894                                 validStyles[key] = tinymce.explode(value);
1895                         });
1896                 }
1897
1898                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
1899                 function patternToRegExp(str) {
1900                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
1901                 };
1902
1903                 // Parses the specified valid_elements string and adds to the current rules
1904                 // This function is a bit hard to read since it's heavily optimized for speed
1905                 function addValidElements(valid_elements) {
1906                         var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
1907                                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
1908                                 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
1909                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
1910                                 hasPatternsRegExp = /[*?+]/;
1911
1912                         if (valid_elements) {
1913                                 // Split valid elements into an array with rules
1914                                 valid_elements = split(valid_elements);
1915
1916                                 if (elements['@']) {
1917                                         globalAttributes = elements['@'].attributes;
1918                                         globalAttributesOrder = elements['@'].attributesOrder;
1919                                 }
1920
1921                                 // Loop all rules
1922                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
1923                                         // Parse element rule
1924                                         matches = elementRuleRegExp.exec(valid_elements[ei]);
1925                                         if (matches) {
1926                                                 // Setup local names for matches
1927                                                 prefix = matches[1];
1928                                                 elementName = matches[2];
1929                                                 outputName = matches[3];
1930                                                 attrData = matches[4];
1931
1932                                                 // Create new attributes and attributesOrder
1933                                                 attributes = {};
1934                                                 attributesOrder = [];
1935
1936                                                 // Create the new element
1937                                                 element = {
1938                                                         attributes : attributes,
1939                                                         attributesOrder : attributesOrder
1940                                                 };
1941
1942                                                 // Padd empty elements prefix
1943                                                 if (prefix === '#')
1944                                                         element.paddEmpty = true;
1945
1946                                                 // Remove empty elements prefix
1947                                                 if (prefix === '-')
1948                                                         element.removeEmpty = true;
1949
1950                                                 // Copy attributes from global rule into current rule
1951                                                 if (globalAttributes) {
1952                                                         for (key in globalAttributes)
1953                                                                 attributes[key] = globalAttributes[key];
1954
1955                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
1956                                                 }
1957
1958                                                 // Attributes defined
1959                                                 if (attrData) {
1960                                                         attrData = split(attrData, '|');
1961                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {
1962                                                                 matches = attrRuleRegExp.exec(attrData[ai]);
1963                                                                 if (matches) {
1964                                                                         attr = {};
1965                                                                         attrType = matches[1];
1966                                                                         attrName = matches[2].replace(/::/g, ':');
1967                                                                         prefix = matches[3];
1968                                                                         value = matches[4];
1969
1970                                                                         // Required
1971                                                                         if (attrType === '!') {
1972                                                                                 element.attributesRequired = element.attributesRequired || [];
1973                                                                                 element.attributesRequired.push(attrName);
1974                                                                                 attr.required = true;
1975                                                                         }
1976
1977                                                                         // Denied from global
1978                                                                         if (attrType === '-') {
1979                                                                                 delete attributes[attrName];
1980                                                                                 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
1981                                                                                 continue;
1982                                                                         }
1983
1984                                                                         // Default value
1985                                                                         if (prefix) {
1986                                                                                 // Default value
1987                                                                                 if (prefix === '=') {
1988                                                                                         element.attributesDefault = element.attributesDefault || [];
1989                                                                                         element.attributesDefault.push({name: attrName, value: value});
1990                                                                                         attr.defaultValue = value;
1991                                                                                 }
1992
1993                                                                                 // Forced value
1994                                                                                 if (prefix === ':') {
1995                                                                                         element.attributesForced = element.attributesForced || [];
1996                                                                                         element.attributesForced.push({name: attrName, value: value});
1997                                                                                         attr.forcedValue = value;
1998                                                                                 }
1999
2000                                                                                 // Required values
2001                                                                                 if (prefix === '<')
2002                                                                                         attr.validValues = makeMap(value, '?');
2003                                                                         }
2004
2005                                                                         // Check for attribute patterns
2006                                                                         if (hasPatternsRegExp.test(attrName)) {
2007                                                                                 element.attributePatterns = element.attributePatterns || [];
2008                                                                                 attr.pattern = patternToRegExp(attrName);
2009                                                                                 element.attributePatterns.push(attr);
2010                                                                         } else {
2011                                                                                 // Add attribute to order list if it doesn't already exist
2012                                                                                 if (!attributes[attrName])
2013                                                                                         attributesOrder.push(attrName);
2014
2015                                                                                 attributes[attrName] = attr;
2016                                                                         }
2017                                                                 }
2018                                                         }
2019                                                 }
2020
2021                                                 // Global rule, store away these for later usage
2022                                                 if (!globalAttributes && elementName == '@') {
2023                                                         globalAttributes = attributes;
2024                                                         globalAttributesOrder = attributesOrder;
2025                                                 }
2026
2027                                                 // Handle substitute elements such as b/strong
2028                                                 if (outputName) {
2029                                                         element.outputName = elementName;
2030                                                         elements[outputName] = element;
2031                                                 }
2032
2033                                                 // Add pattern or exact element
2034                                                 if (hasPatternsRegExp.test(elementName)) {
2035                                                         element.pattern = patternToRegExp(elementName);
2036                                                         patternElements.push(element);
2037                                                 } else
2038                                                         elements[elementName] = element;
2039                                         }
2040                                 }
2041                         }
2042                 };
2043
2044                 function setValidElements(valid_elements) {
2045                         elements = {};
2046                         patternElements = [];
2047
2048                         addValidElements(valid_elements);
2049
2050                         each(transitional, function(element, name) {
2051                                 children[name] = element.children;
2052                         });
2053                 };
2054
2055                 // Adds custom non HTML elements to the schema
2056                 function addCustomElements(custom_elements) {
2057                         var customElementRegExp = /^(~)?(.+)$/;
2058
2059                         if (custom_elements) {
2060                                 each(split(custom_elements), function(rule) {
2061                                         var matches = customElementRegExp.exec(rule),
2062                                                 cloneName = matches[1] === '~' ? 'span' : 'div',
2063                                                 name = matches[2];
2064
2065                                         children[name] = children[cloneName];
2066
2067                                         // Add custom elements at span/div positions
2068                                         each(children, function(element, child) {
2069                                                 if (element[cloneName])
2070                                                         element[name] = element[cloneName];
2071                                         });
2072                                 });
2073                         }
2074                 };
2075
2076                 // Adds valid children to the schema object
2077                 function addValidChildren(valid_children) {
2078                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2079
2080                         if (valid_children) {
2081                                 each(split(valid_children), function(rule) {
2082                                         var matches = childRuleRegExp.exec(rule), parent, prefix;
2083
2084                                         if (matches) {
2085                                                 prefix = matches[1];
2086
2087                                                 // Add/remove items from default
2088                                                 if (prefix)
2089                                                         parent = children[matches[2]];
2090                                                 else
2091                                                         parent = children[matches[2]] = {'#comment' : {}};
2092
2093                                                 parent = children[matches[2]];
2094
2095                                                 each(split(matches[3], '|'), function(child) {
2096                                                         if (prefix === '-')
2097                                                                 delete parent[child];
2098                                                         else
2099                                                                 parent[child] = {};
2100                                                 });
2101                                         }
2102                                 });
2103                         }
2104                 }
2105
2106                 if (!settings.valid_elements) {
2107                         // No valid elements defined then clone the elements from the transitional spec
2108                         each(transitional, function(element, name) {
2109                                 elements[name] = {
2110                                         attributes : element.attributes,
2111                                         attributesOrder : element.attributesOrder
2112                                 };
2113
2114                                 children[name] = element.children;
2115                         });
2116
2117                         // Switch these
2118                         each(split('strong/b,em/i'), function(item) {
2119                                 item = split(item, '/');
2120                                 elements[item[1]].outputName = item[0];
2121                         });
2122
2123                         // Add default alt attribute for images
2124                         elements.img.attributesDefault = [{name: 'alt', value: ''}];
2125
2126                         // Remove these if they are empty by default
2127                         each(split('ol,ul,li,sub,sup,blockquote,tr,div,span,font,a,table,tbody'), function(name) {
2128                                 elements[name].removeEmpty = true;
2129                         });
2130
2131                         // Padd these by default
2132                         each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
2133                                 elements[name].paddEmpty = true;
2134                         });
2135                 } else
2136                         setValidElements(settings.valid_elements);
2137
2138                 addCustomElements(settings.custom_elements);
2139                 addValidChildren(settings.valid_children);
2140                 addValidElements(settings.extended_valid_elements);
2141
2142                 // Todo: Remove this when we fix list handling to be valid
2143                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
2144
2145                 // Delete invalid elements
2146                 if (settings.invalid_elements) {
2147                         tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
2148                                 if (elements[item])
2149                                         delete elements[item];
2150                         });
2151                 }
2152
2153                 self.children = children;
2154
2155                 self.styles = validStyles;
2156
2157                 self.getBoolAttrs = function() {
2158                         return boolAttrMap;
2159                 };
2160
2161                 self.getBlockElements = function() {
2162                         return blockElementsMap;
2163                 };
2164
2165                 self.getShortEndedElements = function() {
2166                         return shortEndedElementsMap;
2167                 };
2168
2169                 self.getSelfClosingElements = function() {
2170                         return selfClosingElementsMap;
2171                 };
2172
2173                 self.getNonEmptyElements = function() {
2174                         return nonEmptyElementsMap;
2175                 };
2176
2177                 self.getWhiteSpaceElements = function() {
2178                         return whiteSpaceElementsMap;
2179                 };
2180
2181                 self.isValidChild = function(name, child) {
2182                         var parent = children[name];
2183
2184                         return !!(parent && parent[child]);
2185                 };
2186
2187                 self.getElementRule = function(name) {
2188                         var element = elements[name], i;
2189
2190                         // Exact match found
2191                         if (element)
2192                                 return element;
2193
2194                         // No exact match then try the patterns
2195                         i = patternElements.length;
2196                         while (i--) {
2197                                 element = patternElements[i];
2198
2199                                 if (element.pattern.test(name))
2200                                         return element;
2201                         }
2202                 };
2203
2204                 self.addValidElements = addValidElements;
2205
2206                 self.setValidElements = setValidElements;
2207
2208                 self.addCustomElements = addCustomElements;
2209
2210                 self.addValidChildren = addValidChildren;
2211         };
2212
2213         // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
2214         tinymce.html.Schema.boolAttrMap = boolAttrMap;
2215         tinymce.html.Schema.blockElementsMap = blockElementsMap;
2216 })(tinymce);
2217
2218 (function(tinymce) {
2219         tinymce.html.SaxParser = function(settings, schema) {
2220                 var self = this, noop = function() {};
2221
2222                 settings = settings || {};
2223                 self.schema = schema = schema || new tinymce.html.Schema();
2224
2225                 if (settings.fix_self_closing !== false)
2226                         settings.fix_self_closing = true;
2227
2228                 // Add handler functions from settings and setup default handlers
2229                 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2230                         if (name)
2231                                 self[name] = settings[name] || noop;
2232                 });
2233
2234                 self.parse = function(html) {
2235                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name,
2236                                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
2237                                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2238                                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
2239
2240                         function processEndTag(name) {
2241                                 var pos, i;
2242
2243                                 // Find position of parent of the same type
2244                                 pos = stack.length;
2245                                 while (pos--) {
2246                                         if (stack[pos].name === name)
2247                                                 break;                                          
2248                                 }
2249
2250                                 // Found parent
2251                                 if (pos >= 0) {
2252                                         // Close all the open elements
2253                                         for (i = stack.length - 1; i >= pos; i--) {
2254                                                 name = stack[i];
2255
2256                                                 if (name.valid)
2257                                                         self.end(name.name);
2258                                         }
2259
2260                                         // Remove the open elements from the stack
2261                                         stack.length = pos;
2262                                 }
2263                         };
2264
2265                         // Precompile RegExps and map objects
2266                         tokenRegExp = new RegExp('<(?:' +
2267                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment
2268                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2269                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2270                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2271                                 '(?:\\/([^>]+)>)|' + // End element
2272                                 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
2273                         ')', 'g');
2274
2275                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2276                         specialElements = {
2277                                 'script' : /<\/script[^>]*>/gi,
2278                                 'style' : /<\/style[^>]*>/gi,
2279                                 'noscript' : /<\/noscript[^>]*>/gi
2280                         };
2281
2282                         // Setup lookup tables for empty elements and boolean attributes
2283                         shortEndedElements = schema.getShortEndedElements();
2284                         selfClosing = schema.getSelfClosingElements();
2285                         fillAttrsMap = schema.getBoolAttrs();
2286                         validate = settings.validate;
2287                         fixSelfClosing = settings.fix_self_closing;
2288
2289                         while (matches = tokenRegExp.exec(html)) {
2290                                 // Text
2291                                 if (index < matches.index)
2292                                         self.text(decode(html.substr(index, matches.index - index)));
2293
2294                                 if (value = matches[6]) { // End element
2295                                         processEndTag(value.toLowerCase());
2296                                 } else if (value = matches[7]) { // Start element
2297                                         value = value.toLowerCase();
2298                                         isShortEnded = value in shortEndedElements;
2299
2300                                         // Is self closing tag for example an <li> after an open <li>
2301                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
2302                                                 processEndTag(value);
2303
2304                                         // Validate element
2305                                         if (!validate || (elementRule = schema.getElementRule(value))) {
2306                                                 isValidElement = true;
2307
2308                                                 // Grab attributes map and patters when validation is enabled
2309                                                 if (validate) {
2310                                                         validAttributesMap = elementRule.attributes;
2311                                                         validAttributePatterns = elementRule.attributePatterns;
2312                                                 }
2313
2314                                                 // Parse attributes
2315                                                 if (attribsValue = matches[8]) {
2316                                                         attrList = [];
2317                                                         attrList.map = {};
2318
2319                                                         attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
2320                                                                 var attrRule, i;
2321
2322                                                                 name = name.toLowerCase();
2323                                                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2324
2325                                                                 // Validate name and value
2326                                                                 if (validate && name.indexOf('data-') !== 0) {
2327                                                                         attrRule = validAttributesMap[name];
2328
2329                                                                         // Find rule by pattern matching
2330                                                                         if (!attrRule && validAttributePatterns) {
2331                                                                                 i = validAttributePatterns.length;
2332                                                                                 while (i--) {
2333                                                                                         attrRule = validAttributePatterns[i];
2334                                                                                         if (attrRule.pattern.test(name))
2335                                                                                                 break;
2336                                                                                 }
2337
2338                                                                                 // No rule matched
2339                                                                                 if (i === -1)
2340                                                                                         attrRule = null;
2341                                                                         }
2342
2343                                                                         // No attribute rule found
2344                                                                         if (!attrRule)
2345                                                                                 return;
2346
2347                                                                         // Validate value
2348                                                                         if (attrRule.validValues && !(value in attrRule.validValues))
2349                                                                                 return;
2350                                                                 }
2351
2352                                                                 // Add attribute to list and map
2353                                                                 attrList.map[name] = value;
2354                                                                 attrList.push({
2355                                                                         name: name,
2356                                                                         value: value
2357                                                                 });
2358                                                         });
2359                                                 } else {
2360                                                         attrList = [];
2361                                                         attrList.map = {};
2362                                                 }
2363
2364                                                 // Process attributes if validation is enabled
2365                                                 if (validate) {
2366                                                         attributesRequired = elementRule.attributesRequired;
2367                                                         attributesDefault = elementRule.attributesDefault;
2368                                                         attributesForced = elementRule.attributesForced;
2369
2370                                                         // Handle forced attributes
2371                                                         if (attributesForced) {
2372                                                                 i = attributesForced.length;
2373                                                                 while (i--) {
2374                                                                         attr = attributesForced[i];
2375                                                                         name = attr.name;
2376                                                                         attrValue = attr.value;
2377
2378                                                                         if (attrValue === '{$uid}')
2379                                                                                 attrValue = 'mce_' + idCount++;
2380
2381                                                                         attrList.map[name] = attrValue;
2382                                                                         attrList.push({name: name, value: attrValue});
2383                                                                 }
2384                                                         }
2385
2386                                                         // Handle default attributes
2387                                                         if (attributesDefault) {
2388                                                                 i = attributesDefault.length;
2389                                                                 while (i--) {
2390                                                                         attr = attributesDefault[i];
2391                                                                         name = attr.name;
2392
2393                                                                         if (!(name in attrList.map)) {
2394                                                                                 attrValue = attr.value;
2395
2396                                                                                 if (attrValue === '{$uid}')
2397                                                                                         attrValue = 'mce_' + idCount++;
2398
2399                                                                                 attrList.map[name] = attrValue;
2400                                                                                 attrList.push({name: name, value: attrValue});
2401                                                                         }
2402                                                                 }
2403                                                         }
2404
2405                                                         // Handle required attributes
2406                                                         if (attributesRequired) {
2407                                                                 i = attributesRequired.length;
2408                                                                 while (i--) {
2409                                                                         if (attributesRequired[i] in attrList.map)
2410                                                                                 break;
2411                                                                 }
2412
2413                                                                 // None of the required attributes where found
2414                                                                 if (i === -1)
2415                                                                         isValidElement = false;
2416                                                         }
2417
2418                                                         // Invalidate element if it's marked as bogus
2419                                                         if (attrList.map['data-mce-bogus'])
2420                                                                 isValidElement = false;
2421                                                 }
2422
2423                                                 if (isValidElement)
2424                                                         self.start(value, attrList, isShortEnded);
2425                                         } else
2426                                                 isValidElement = false;
2427
2428                                         // Treat script, noscript and style a bit different since they may include code that looks like elements
2429                                         if (endRegExp = specialElements[value]) {
2430                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;
2431
2432                                                 if (matches = endRegExp.exec(html)) {
2433                                                         if (isValidElement)
2434                                                                 text = html.substr(index, matches.index - index);
2435
2436                                                         index = matches.index + matches[0].length;
2437                                                 } else {
2438                                                         text = html.substr(index);
2439                                                         index = html.length;
2440                                                 }
2441
2442                                                 if (isValidElement && text.length > 0)
2443                                                         self.text(text, true);
2444
2445                                                 if (isValidElement)
2446                                                         self.end(value);
2447
2448                                                 tokenRegExp.lastIndex = index;
2449                                                 continue;
2450                                         }
2451
2452                                         // Push value on to stack
2453                                         if (!isShortEnded) {
2454                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
2455                                                         stack.push({name: value, valid: isValidElement});
2456                                                 else if (isValidElement)
2457                                                         self.end(value);
2458                                         }
2459                                 } else if (value = matches[1]) { // Comment
2460                                         self.comment(value);
2461                                 } else if (value = matches[2]) { // CDATA
2462                                         self.cdata(value);
2463                                 } else if (value = matches[3]) { // DOCTYPE
2464                                         self.doctype(value);
2465                                 } else if (value = matches[4]) { // PI
2466                                         self.pi(value, matches[5]);
2467                                 }
2468
2469                                 index = matches.index + matches[0].length;
2470                         }
2471
2472                         // Text
2473                         if (index < html.length)
2474                                 self.text(decode(html.substr(index)));
2475
2476                         // Close any open elements
2477                         for (i = stack.length - 1; i >= 0; i--) {
2478                                 value = stack[i];
2479
2480                                 if (value.valid)
2481                                         self.end(value.name);
2482                         }
2483                 };
2484         }
2485 })(tinymce);
2486
2487 (function(tinymce) {
2488         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
2489                 '#text' : 3,
2490                 '#comment' : 8,
2491                 '#cdata' : 4,
2492                 '#pi' : 7,
2493                 '#doctype' : 10,
2494                 '#document-fragment' : 11
2495         };
2496
2497         // Walks the tree left/right
2498         function walk(node, root_node, prev) {
2499                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
2500
2501                 // Walk into nodes if it has a start
2502                 if (node[startName])
2503                         return node[startName];
2504
2505                 // Return the sibling if it has one
2506                 if (node !== root_node) {
2507                         sibling = node[siblingName];
2508
2509                         if (sibling)
2510                                 return sibling;
2511
2512                         // Walk up the parents to look for siblings
2513                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
2514                                 sibling = parent[siblingName];
2515
2516                                 if (sibling)
2517                                         return sibling;
2518                         }
2519                 }
2520         };
2521
2522         function Node(name, type) {
2523                 this.name = name;
2524                 this.type = type;
2525
2526                 if (type === 1) {
2527                         this.attributes = [];
2528                         this.attributes.map = {};
2529                 }
2530         }
2531
2532         tinymce.extend(Node.prototype, {
2533                 replace : function(node) {
2534                         var self = this;
2535
2536                         if (node.parent)
2537                                 node.remove();
2538
2539                         self.insert(node, self);
2540                         self.remove();
2541
2542                         return self;
2543                 },
2544
2545                 attr : function(name, value) {
2546                         var self = this, attrs, i, undef;
2547
2548                         if (typeof name !== "string") {
2549                                 for (i in name)
2550                                         self.attr(i, name[i]);
2551
2552                                 return self;
2553                         }
2554
2555                         if (attrs = self.attributes) {
2556                                 if (value !== undef) {
2557                                         // Remove attribute
2558                                         if (value === null) {
2559                                                 if (name in attrs.map) {
2560                                                         delete attrs.map[name];
2561
2562                                                         i = attrs.length;
2563                                                         while (i--) {
2564                                                                 if (attrs[i].name === name) {
2565                                                                         attrs = attrs.splice(i, 1);
2566                                                                         return self;
2567                                                                 }
2568                                                         }
2569                                                 }
2570
2571                                                 return self;
2572                                         }
2573
2574                                         // Set attribute
2575                                         if (name in attrs.map) {
2576                                                 // Set attribute
2577                                                 i = attrs.length;
2578                                                 while (i--) {
2579                                                         if (attrs[i].name === name) {
2580                                                                 attrs[i].value = value;
2581                                                                 break;
2582                                                         }
2583                                                 }
2584                                         } else
2585                                                 attrs.push({name: name, value: value});
2586
2587                                         attrs.map[name] = value;
2588
2589                                         return self;
2590                                 } else {
2591                                         return attrs.map[name];
2592                                 }
2593                         }
2594                 },
2595
2596                 clone : function() {
2597                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
2598
2599                         // Clone element attributes
2600                         if (selfAttrs = self.attributes) {
2601                                 cloneAttrs = [];
2602                                 cloneAttrs.map = {};
2603
2604                                 for (i = 0, l = selfAttrs.length; i < l; i++) {
2605                                         selfAttr = selfAttrs[i];
2606
2607                                         // Clone everything except id
2608                                         if (selfAttr.name !== 'id') {
2609                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
2610                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;
2611                                         }
2612                                 }
2613
2614                                 clone.attributes = cloneAttrs;
2615                         }
2616
2617                         clone.value = self.value;
2618                         clone.shortEnded = self.shortEnded;
2619
2620                         return clone;
2621                 },
2622
2623                 wrap : function(wrapper) {
2624                         var self = this;
2625
2626                         self.parent.insert(wrapper, self);
2627                         wrapper.append(self);
2628
2629                         return self;
2630                 },
2631
2632                 unwrap : function() {
2633                         var self = this, node, next;
2634
2635                         for (node = self.firstChild; node; ) {
2636                                 next = node.next;
2637                                 self.insert(node, self, true);
2638                                 node = next;
2639                         }
2640
2641                         self.remove();
2642                 },
2643
2644                 remove : function() {
2645                         var self = this, parent = self.parent, next = self.next, prev = self.prev;
2646
2647                         if (parent) {
2648                                 if (parent.firstChild === self) {
2649                                         parent.firstChild = next;
2650
2651                                         if (next)
2652                                                 next.prev = null;
2653                                 } else {
2654                                         prev.next = next;
2655                                 }
2656
2657                                 if (parent.lastChild === self) {
2658                                         parent.lastChild = prev;
2659
2660                                         if (prev)
2661                                                 prev.next = null;
2662                                 } else {
2663                                         next.prev = prev;
2664                                 }
2665
2666                                 self.parent = self.next = self.prev = null;
2667                         }
2668
2669                         return self;
2670                 },
2671
2672                 append : function(node) {
2673                         var self = this, last;
2674
2675                         if (node.parent)
2676                                 node.remove();
2677
2678                         last = self.lastChild;
2679                         if (last) {
2680                                 last.next = node;
2681                                 node.prev = last;
2682                                 self.lastChild = node;
2683                         } else
2684                                 self.lastChild = self.firstChild = node;
2685
2686                         node.parent = self;
2687
2688                         return node;
2689                 },
2690
2691                 insert : function(node, ref_node, before) {
2692                         var parent;
2693
2694                         if (node.parent)
2695                                 node.remove();
2696
2697                         parent = ref_node.parent || this;
2698
2699                         if (before) {
2700                                 if (ref_node === parent.firstChild)
2701                                         parent.firstChild = node;
2702                                 else
2703                                         ref_node.prev.next = node;
2704
2705                                 node.prev = ref_node.prev;
2706                                 node.next = ref_node;
2707                                 ref_node.prev = node;
2708                         } else {
2709                                 if (ref_node === parent.lastChild)
2710                                         parent.lastChild = node;
2711                                 else
2712                                         ref_node.next.prev = node;
2713
2714                                 node.next = ref_node.next;
2715                                 node.prev = ref_node;
2716                                 ref_node.next = node;
2717                         }
2718
2719                         node.parent = parent;
2720
2721                         return node;
2722                 },
2723
2724                 getAll : function(name) {
2725                         var self = this, node, collection = [];
2726
2727                         for (node = self.firstChild; node; node = walk(node, self)) {
2728                                 if (node.name === name)
2729                                         collection.push(node);
2730                         }
2731
2732                         return collection;
2733                 },
2734
2735                 empty : function() {
2736                         var self = this, nodes, i, node;
2737
2738                         // Remove all children
2739                         if (self.firstChild) {
2740                                 nodes = [];
2741
2742                                 // Collect the children
2743                                 for (node = self.firstChild; node; node = walk(node, self))
2744                                         nodes.push(node);
2745
2746                                 // Remove the children
2747                                 i = nodes.length;
2748                                 while (i--) {
2749                                         node = nodes[i];
2750                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
2751                                 }
2752                         }
2753
2754                         self.firstChild = self.lastChild = null;
2755
2756                         return self;
2757                 },
2758
2759                 isEmpty : function(elements) {
2760                         var self = this, node = self.firstChild, i, name;
2761
2762                         if (node) {
2763                                 do {
2764                                         if (node.type === 1) {
2765                                                 // Ignore bogus elements
2766                                                 if (node.attributes.map['data-mce-bogus'])
2767                                                         continue;
2768
2769                                                 // Keep empty elements like <img />
2770                                                 if (elements[node.name])
2771                                                         return false;
2772
2773                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
2774                                                 i = node.attributes.length;
2775                                                 while (i--) {
2776                                                         name = node.attributes[i].name;
2777                                                         if (name === "name" || name.indexOf('data-') === 0)
2778                                                                 return false;
2779                                                 }
2780                                         }
2781
2782                                         // Keep non whitespace text nodes
2783                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
2784                                                 return false;
2785                                 } while (node = walk(node, self));
2786                         }
2787
2788                         return true;
2789                 }
2790         });
2791
2792         tinymce.extend(Node, {
2793                 create : function(name, attrs) {
2794                         var node, attrName;
2795
2796                         // Create node
2797                         node = new Node(name, typeLookup[name] || 1);
2798
2799                         // Add attributes if needed
2800                         if (attrs) {
2801                                 for (attrName in attrs)
2802                                         node.attr(attrName, attrs[attrName]);
2803                         }
2804
2805                         return node;
2806                 }
2807         });
2808
2809         tinymce.html.Node = Node;
2810 })(tinymce);
2811
2812 (function(tinymce) {
2813         var Node = tinymce.html.Node;
2814
2815         tinymce.html.DomParser = function(settings, schema) {
2816                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
2817
2818                 settings = settings || {};
2819                 settings.validate = "validate" in settings ? settings.validate : true;
2820                 settings.root_name = settings.root_name || 'body';
2821                 self.schema = schema = schema || new tinymce.html.Schema();
2822
2823                 function fixInvalidChildren(nodes) {
2824                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
2825                                 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
2826
2827                         nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
2828                         nonEmptyElements = schema.getNonEmptyElements();
2829
2830                         for (ni = 0; ni < nodes.length; ni++) {
2831                                 node = nodes[ni];
2832
2833                                 // Already removed
2834                                 if (!node.parent)
2835                                         continue;
2836
2837                                 // Get list of all parent nodes until we find a valid parent to stick the child into
2838                                 parents = [node];
2839                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
2840                                         parents.push(parent);
2841
2842                                 // Found a suitable parent
2843                                 if (parent && parents.length > 1) {
2844                                         // Reverse the array since it makes looping easier
2845                                         parents.reverse();
2846
2847                                         // Clone the related parent and insert that after the moved node
2848                                         newParent = currentNode = self.filterNode(parents[0].clone());
2849
2850                                         // Start cloning and moving children on the left side of the target node
2851                                         for (i = 0; i < parents.length - 1; i++) {
2852                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {
2853                                                         tempNode = self.filterNode(parents[i].clone());
2854                                                         currentNode.append(tempNode);
2855                                                 } else
2856                                                         tempNode = currentNode;
2857
2858                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
2859                                                         nextNode = childNode.next;
2860                                                         tempNode.append(childNode);
2861                                                         childNode = nextNode;
2862                                                 }
2863
2864                                                 currentNode = tempNode;
2865                                         }
2866
2867                                         if (!newParent.isEmpty(nonEmptyElements)) {
2868                                                 parent.insert(newParent, parents[0], true);
2869                                                 parent.insert(node, newParent);
2870                                         } else {
2871                                                 parent.insert(node, parents[0], true);
2872                                         }
2873
2874                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
2875                                         parent = parents[0];
2876                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
2877                                                 parent.empty().remove();
2878                                         }
2879                                 } else if (node.parent) {
2880                                         // If it's an LI try to find a UL/OL for it or wrap it
2881                                         if (node.name === 'li') {
2882                                                 sibling = node.prev;
2883                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2884                                                         sibling.append(node);
2885                                                         continue;
2886                                                 }
2887
2888                                                 sibling = node.next;
2889                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2890                                                         sibling.insert(node, sibling.firstChild, true);
2891                                                         continue;
2892                                                 }
2893
2894                                                 node.wrap(self.filterNode(new Node('ul', 1)));
2895                                                 continue;
2896                                         }
2897
2898                                         // Try wrapping the element in a DIV
2899                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
2900                                                 node.wrap(self.filterNode(new Node('div', 1)));
2901                                         } else {
2902                                                 // We failed wrapping it, then remove or unwrap it
2903                                                 if (node.name === 'style' || node.name === 'script')
2904                                                         node.empty().remove();
2905                                                 else
2906                                                         node.unwrap();
2907                                         }
2908                                 }
2909                         }
2910                 };
2911
2912                 self.filterNode = function(node) {
2913                         var i, name, list;
2914
2915                         // Run element filters
2916                         if (name in nodeFilters) {
2917                                 list = matchedNodes[name];
2918
2919                                 if (list)
2920                                         list.push(node);
2921                                 else
2922                                         matchedNodes[name] = [node];
2923                         }
2924
2925                         // Run attribute filters
2926                         i = attributeFilters.length;
2927                         while (i--) {
2928                                 name = attributeFilters[i].name;
2929
2930                                 if (name in node.attributes.map) {
2931                                         list = matchedAttributes[name];
2932
2933                                         if (list)
2934                                                 list.push(node);
2935                                         else
2936                                                 matchedAttributes[name] = [node];
2937                                 }
2938                         }
2939
2940                         return node;
2941                 };
2942
2943                 self.addNodeFilter = function(name, callback) {
2944                         tinymce.each(tinymce.explode(name), function(name) {
2945                                 var list = nodeFilters[name];
2946
2947                                 if (!list)
2948                                         nodeFilters[name] = list = [];
2949
2950                                 list.push(callback);
2951                         });
2952                 };
2953
2954                 self.addAttributeFilter = function(name, callback) {
2955                         tinymce.each(tinymce.explode(name), function(name) {
2956                                 var i;
2957
2958                                 for (i = 0; i < attributeFilters.length; i++) {
2959                                         if (attributeFilters[i].name === name) {
2960                                                 attributeFilters[i].callbacks.push(callback);
2961                                                 return;
2962                                         }
2963                                 }
2964
2965                                 attributeFilters.push({name: name, callbacks: [callback]});
2966                         });
2967                 };
2968
2969                 self.parse = function(html, args) {
2970                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
2971                                 blockElements, startWhiteSpaceRegExp, invalidChildren = [],
2972                                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements;
2973
2974                         args = args || {};
2975                         matchedNodes = {};
2976                         matchedAttributes = {};
2977                         blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
2978                         nonEmptyElements = schema.getNonEmptyElements();
2979                         children = schema.children;
2980                         validate = settings.validate;
2981
2982                         whiteSpaceElements = schema.getWhiteSpaceElements();
2983                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;
2984                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;
2985                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;
2986
2987                         function createNode(name, type) {
2988                                 var node = new Node(name, type), list;
2989
2990                                 if (name in nodeFilters) {
2991                                         list = matchedNodes[name];
2992
2993                                         if (list)
2994                                                 list.push(node);
2995                                         else
2996                                                 matchedNodes[name] = [node];
2997                                 }
2998
2999                                 return node;
3000                         };
3001
3002                         function removeWhitespaceBefore(node) {
3003                                 var textNode, textVal, sibling;
3004
3005                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {
3006                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3007
3008                                         if (textVal.length > 0) {
3009                                                 textNode.value = textVal;
3010                                                 textNode = textNode.prev;
3011                                         } else {
3012                                                 sibling = textNode.prev;
3013                                                 textNode.remove();
3014                                                 textNode = sibling;
3015                                         }
3016                                 }
3017                         };
3018
3019                         parser = new tinymce.html.SaxParser({
3020                                 validate : validate,
3021                                 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
3022
3023                                 cdata: function(text) {
3024                                         node.append(createNode('#cdata', 4)).value = text;
3025                                 },
3026
3027                                 text: function(text, raw) {
3028                                         var textNode;
3029
3030                                         // Trim all redundant whitespace on non white space elements
3031                                         if (!whiteSpaceElements[node.name]) {
3032                                                 text = text.replace(allWhiteSpaceRegExp, ' ');
3033
3034                                                 if (node.lastChild && blockElements[node.lastChild.name])
3035                                                         text = text.replace(startWhiteSpaceRegExp, '');
3036                                         }
3037
3038                                         // Do we need to create the node
3039                                         if (text.length !== 0) {
3040                                                 textNode = createNode('#text', 3);
3041                                                 textNode.raw = !!raw;
3042                                                 node.append(textNode).value = text;
3043                                         }
3044                                 },
3045
3046                                 comment: function(text) {
3047                                         node.append(createNode('#comment', 8)).value = text;
3048                                 },
3049
3050                                 pi: function(name, text) {
3051                                         node.append(createNode(name, 7)).value = text;
3052                                         removeWhitespaceBefore(node);
3053                                 },
3054
3055                                 doctype: function(text) {
3056                                         var newNode;
3057                 
3058                                         newNode = node.append(createNode('#doctype', 10));
3059                                         newNode.value = text;
3060                                         removeWhitespaceBefore(node);
3061                                 },
3062
3063                                 start: function(name, attrs, empty) {
3064                                         var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
3065
3066                                         elementRule = validate ? schema.getElementRule(name) : {};
3067                                         if (elementRule) {
3068                                                 newNode = createNode(elementRule.outputName || name, 1);
3069                                                 newNode.attributes = attrs;
3070                                                 newNode.shortEnded = empty;
3071
3072                                                 node.append(newNode);
3073
3074                                                 // Check if node is valid child of the parent node is the child is
3075                                                 // unknown we don't collect it since it's probably a custom element
3076                                                 parent = children[node.name];
3077                                                 if (parent && children[newNode.name] && !parent[newNode.name])
3078                                                         invalidChildren.push(newNode);
3079
3080                                                 attrFiltersLen = attributeFilters.length;
3081                                                 while (attrFiltersLen--) {
3082                                                         attrName = attributeFilters[attrFiltersLen].name;
3083
3084                                                         if (attrName in attrs.map) {
3085                                                                 list = matchedAttributes[attrName];
3086
3087                                                                 if (list)
3088                                                                         list.push(newNode);
3089                                                                 else
3090                                                                         matchedAttributes[attrName] = [newNode];
3091                                                         }
3092                                                 }
3093
3094                                                 // Trim whitespace before block
3095                                                 if (blockElements[name])
3096                                                         removeWhitespaceBefore(newNode);
3097
3098                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />
3099                                                 if (!empty)
3100                                                         node = newNode;
3101                                         }
3102                                 },
3103
3104                                 end: function(name) {
3105                                         var textNode, elementRule, text, sibling, tempNode;
3106
3107                                         elementRule = validate ? schema.getElementRule(name) : {};
3108                                         if (elementRule) {
3109                                                 if (blockElements[name]) {
3110                                                         if (!whiteSpaceElements[node.name]) {
3111                                                                 // Trim whitespace at beginning of block
3112                                                                 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
3113                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');
3114
3115                                                                         if (text.length > 0) {
3116                                                                                 textNode.value = text;
3117                                                                                 textNode = textNode.next;
3118                                                                         } else {
3119                                                                                 sibling = textNode.next;
3120                                                                                 textNode.remove();
3121                                                                                 textNode = sibling;
3122                                                                         }
3123                                                                 }
3124
3125                                                                 // Trim whitespace at end of block
3126                                                                 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
3127                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');
3128
3129                                                                         if (text.length > 0) {
3130                                                                                 textNode.value = text;
3131                                                                                 textNode = textNode.prev;
3132                                                                         } else {
3133                                                                                 sibling = textNode.prev;
3134                                                                                 textNode.remove();
3135                                                                                 textNode = sibling;
3136                                                                         }
3137                                                                 }
3138                                                         }
3139
3140                                                         // Trim start white space
3141                                                         textNode = node.prev;
3142                                                         if (textNode && textNode.type === 3) {
3143                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');
3144
3145                                                                 if (text.length > 0)
3146                                                                         textNode.value = text;
3147                                                                 else
3148                                                                         textNode.remove();
3149                                                         }
3150                                                 }
3151
3152                                                 // Handle empty nodes
3153                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {
3154                                                         if (node.isEmpty(nonEmptyElements)) {
3155                                                                 if (elementRule.paddEmpty)
3156                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';
3157                                                                 else {
3158                                                                         // Leave nodes that have a name like <a name="name">
3159                                                                         if (!node.attributes.map.name) {
3160                                                                                 tempNode = node.parent;
3161                                                                                 node.empty().remove();
3162                                                                                 node = tempNode;
3163                                                                                 return;
3164                                                                         }
3165                                                                 }
3166                                                         }
3167                                                 }
3168
3169                                                 node = node.parent;
3170                                         }
3171                                 }
3172                         }, schema);
3173
3174                         rootNode = node = new Node(settings.root_name, 11);
3175
3176                         parser.parse(html);
3177
3178                         if (validate)
3179                                 fixInvalidChildren(invalidChildren);
3180
3181                         // Run node filters
3182                         for (name in matchedNodes) {
3183                                 list = nodeFilters[name];
3184                                 nodes = matchedNodes[name];
3185
3186                                 // Remove already removed children
3187                                 fi = nodes.length;
3188                                 while (fi--) {
3189                                         if (!nodes[fi].parent)
3190                                                 nodes.splice(fi, 1);
3191                                 }
3192
3193                                 for (i = 0, l = list.length; i < l; i++)
3194                                         list[i](nodes, name, args);
3195                         }
3196
3197                         // Run attribute filters
3198                         for (i = 0, l = attributeFilters.length; i < l; i++) {
3199                                 list = attributeFilters[i];
3200
3201                                 if (list.name in matchedAttributes) {
3202                                         nodes = matchedAttributes[list.name];
3203
3204                                         // Remove already removed children
3205                                         fi = nodes.length;
3206                                         while (fi--) {
3207                                                 if (!nodes[fi].parent)
3208                                                         nodes.splice(fi, 1);
3209                                         }
3210
3211                                         for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
3212                                                 list.callbacks[fi](nodes, list.name, args);
3213                                 }
3214                         }
3215
3216                         return rootNode;
3217                 };
3218
3219                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
3220                 // make it possible to place the caret inside empty blocks. This logic tries to remove
3221                 // these elements and keep br elements that where intended to be there intact
3222                 if (settings.remove_trailing_brs) {
3223                         self.addNodeFilter('br', function(nodes, name) {
3224                                 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
3225                                         nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
3226
3227                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
3228                                 for (i = 0; i < l; i++) {
3229                                         node = nodes[i];
3230                                         parent = node.parent;
3231
3232                                         if (blockElements[node.parent.name] && node === parent.lastChild) {
3233                                                 // Loop all nodes to the right of the current node and check for other BR elements
3234                                                 // excluding bookmarks since they are invisible
3235                                                 prev = node.prev;
3236                                                 while (prev) {
3237                                                         prevName = prev.name;
3238
3239                                                         // Ignore bookmarks
3240                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
3241                                                                 // Found a non BR element
3242                                                                 if (prevName !== "br")
3243                                                                         break;
3244         
3245                                                                 // Found another br it's a <br><br> structure then don't remove anything
3246                                                                 if (prevName === 'br') {
3247                                                                         node = null;
3248                                                                         break;
3249                                                                 }
3250                                                         }
3251
3252                                                         prev = prev.prev;
3253                                                 }
3254
3255                                                 if (node) {
3256                                                         node.remove();
3257
3258                                                         // Is the parent to be considered empty after we removed the BR
3259                                                         if (parent.isEmpty(nonEmptyElements)) {
3260                                                                 elementRule = schema.getElementRule(parent.name);
3261
3262                                                                 // Remove or padd the element depending on schema rule
3263                                                                 if (elementRule.removeEmpty)
3264                                                                         parent.remove();
3265                                                                 else if (elementRule.paddEmpty) 
3266                                                                         parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
3267                                                         }
3268                                                 }
3269                                         }
3270                                 }
3271                         });
3272                 }
3273         }
3274 })(tinymce);
3275
3276 tinymce.html.Writer = function(settings) {
3277         var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
3278
3279         settings = settings || {};
3280         indent = settings.indent;
3281         indentBefore = tinymce.makeMap(settings.indent_before || '');
3282         indentAfter = tinymce.makeMap(settings.indent_after || '');
3283         encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
3284         htmlOutput = settings.element_format == "html";
3285
3286         return {
3287                 start: function(name, attrs, empty) {
3288                         var i, l, attr, value;
3289
3290                         if (indent && indentBefore[name] && html.length > 0) {
3291                                 value = html[html.length - 1];
3292
3293                                 if (value.length > 0 && value !== '\n')
3294                                         html.push('\n');
3295                         }
3296
3297                         html.push('<', name);
3298
3299                         if (attrs) {
3300                                 for (i = 0, l = attrs.length; i < l; i++) {
3301                                         attr = attrs[i];
3302                                         html.push(' ', attr.name, '="', encode(attr.value, true), '"');
3303                                 }
3304                         }
3305
3306                         if (!empty || htmlOutput)
3307                                 html[html.length] = '>';
3308                         else
3309                                 html[html.length] = ' />';
3310
3311                         if (empty && indent && indentAfter[name] && html.length > 0) {
3312                                 value = html[html.length - 1];
3313
3314                                 if (value.length > 0 && value !== '\n')
3315                                         html.push('\n');
3316                         }
3317                 },
3318
3319                 end: function(name) {
3320                         var value;
3321
3322                         /*if (indent && indentBefore[name] && html.length > 0) {
3323                                 value = html[html.length - 1];
3324
3325                                 if (value.length > 0 && value !== '\n')
3326                                         html.push('\n');
3327                         }*/
3328
3329                         html.push('</', name, '>');
3330
3331                         if (indent && indentAfter[name] && html.length > 0) {
3332                                 value = html[html.length - 1];
3333
3334                                 if (value.length > 0 && value !== '\n')
3335                                         html.push('\n');
3336                         }
3337                 },
3338
3339                 text: function(text, raw) {
3340                         if (text.length > 0)
3341                                 html[html.length] = raw ? text : encode(text);
3342                 },
3343
3344                 cdata: function(text) {
3345                         html.push('<![CDATA[', text, ']]>');
3346                 },
3347
3348                 comment: function(text) {
3349                         html.push('<!--', text, '-->');
3350                 },
3351
3352                 pi: function(name, text) {
3353                         if (text)
3354                                 html.push('<?', name, ' ', text, '?>');
3355                         else
3356                                 html.push('<?', name, '?>');
3357
3358                         if (indent)
3359                                 html.push('\n');
3360                 },
3361
3362                 doctype: function(text) {
3363                         html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
3364                 },
3365
3366                 reset: function() {
3367                         html.length = 0;
3368                 },
3369
3370                 getContent: function() {
3371                         return html.join('').replace(/\n$/, '');
3372                 }
3373         };
3374 };
3375
3376 (function(tinymce) {
3377         tinymce.html.Serializer = function(settings, schema) {
3378                 var self = this, writer = new tinymce.html.Writer(settings);
3379
3380                 settings = settings || {};
3381                 settings.validate = "validate" in settings ? settings.validate : true;
3382
3383                 self.schema = schema = schema || new tinymce.html.Schema();
3384                 self.writer = writer;
3385
3386                 self.serialize = function(node) {
3387                         var handlers, validate;
3388
3389                         validate = settings.validate;
3390
3391                         handlers = {
3392                                 // #text
3393                                 3: function(node, raw) {
3394                                         writer.text(node.value, node.raw);
3395                                 },
3396
3397                                 // #comment
3398                                 8: function(node) {
3399                                         writer.comment(node.value);
3400                                 },
3401
3402                                 // Processing instruction
3403                                 7: function(node) {
3404                                         writer.pi(node.name, node.value);
3405                                 },
3406
3407                                 // Doctype
3408                                 10: function(node) {
3409                                         writer.doctype(node.value);
3410                                 },
3411
3412                                 // CDATA
3413                                 4: function(node) {
3414                                         writer.cdata(node.value);
3415                                 },
3416
3417                                 // Document fragment
3418                                 11: function(node) {
3419                                         if ((node = node.firstChild)) {
3420                                                 do {
3421                                                         walk(node);
3422                                                 } while (node = node.next);
3423                                         }
3424                                 }
3425                         };
3426
3427                         writer.reset();
3428
3429                         function walk(node) {
3430                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
3431
3432                                 if (!handler) {
3433                                         name = node.name;
3434                                         isEmpty = node.shortEnded;
3435                                         attrs = node.attributes;
3436
3437                                         // Sort attributes
3438                                         if (validate && attrs && attrs.length > 1) {
3439                                                 sortedAttrs = [];
3440                                                 sortedAttrs.map = {};
3441
3442                                                 elementRule = schema.getElementRule(node.name);
3443                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
3444                                                         attrName = elementRule.attributesOrder[i];
3445
3446                                                         if (attrName in attrs.map) {
3447                                                                 attrValue = attrs.map[attrName];
3448                                                                 sortedAttrs.map[attrName] = attrValue;
3449                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3450                                                         }
3451                                                 }
3452
3453                                                 for (i = 0, l = attrs.length; i < l; i++) {
3454                                                         attrName = attrs[i].name;
3455
3456                                                         if (!(attrName in sortedAttrs.map)) {
3457                                                                 attrValue = attrs.map[attrName];
3458                                                                 sortedAttrs.map[attrName] = attrValue;
3459                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3460                                                         }
3461                                                 }
3462
3463                                                 attrs = sortedAttrs;
3464                                         }
3465
3466                                         writer.start(node.name, attrs, isEmpty);
3467
3468                                         if (!isEmpty) {
3469                                                 if ((node = node.firstChild)) {
3470                                                         do {
3471                                                                 walk(node);
3472                                                         } while (node = node.next);
3473                                                 }
3474
3475                                                 writer.end(name);
3476                                         }
3477                                 } else
3478                                         handler(node);
3479                         }
3480
3481                         // Serialize element and treat all non elements as fragments
3482                         if (node.type == 1 && !settings.inner)
3483                                 walk(node);
3484                         else
3485                                 handlers[11](node);
3486
3487                         return writer.getContent();
3488                 };
3489         }
3490 })(tinymce);
3491
3492 (function(tinymce) {
3493         // Shorten names
3494         var each = tinymce.each,
3495                 is = tinymce.is,
3496                 isWebKit = tinymce.isWebKit,
3497                 isIE = tinymce.isIE,
3498                 Entities = tinymce.html.Entities,
3499                 simpleSelectorRe = /^([a-z0-9],?)+$/i,
3500                 blockElementsMap = tinymce.html.Schema.blockElementsMap,
3501                 whiteSpaceRegExp = /^[ \t\r\n]*$/;
3502
3503         tinymce.create('tinymce.dom.DOMUtils', {
3504                 doc : null,
3505                 root : null,
3506                 files : null,
3507                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
3508                 props : {
3509                         "for" : "htmlFor",
3510                         "class" : "className",
3511                         className : "className",
3512                         checked : "checked",
3513                         disabled : "disabled",
3514                         maxlength : "maxLength",
3515                         readonly : "readOnly",
3516                         selected : "selected",
3517                         value : "value",
3518                         id : "id",
3519                         name : "name",
3520                         type : "type"
3521                 },
3522
3523                 DOMUtils : function(d, s) {
3524                         var t = this, globalStyle;
3525
3526                         t.doc = d;
3527                         t.win = window;
3528                         t.files = {};
3529                         t.cssFlicker = false;
3530                         t.counter = 0;
3531                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;
3532                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
3533                         t.hasOuterHTML = "outerHTML" in d.createElement("a");
3534
3535                         t.settings = s = tinymce.extend({
3536                                 keep_values : false,
3537                                 hex_colors : 1
3538                         }, s);
3539                         
3540                         t.schema = s.schema;
3541                         t.styles = new tinymce.html.Styles({
3542                                 url_converter : s.url_converter,
3543                                 url_converter_scope : s.url_converter_scope
3544                         }, s.schema);
3545
3546                         // Fix IE6SP2 flicker and check it failed for pre SP2
3547                         if (tinymce.isIE6) {
3548                                 try {
3549                                         d.execCommand('BackgroundImageCache', false, true);
3550                                 } catch (e) {
3551                                         t.cssFlicker = true;
3552                                 }
3553                         }
3554
3555                         if (isIE) {
3556                                 // Add missing HTML 4/5 elements to IE
3557                                 ('abbr article aside audio canvas ' +
3558                                 'details figcaption figure footer ' +
3559                                 'header hgroup mark menu meter nav ' +
3560                                 'output progress section summary ' +
3561                                 'time video').replace(/\w+/g, function(name) {
3562                                         d.createElement(name);
3563                                 });
3564                         }
3565
3566                         tinymce.addUnload(t.destroy, t);
3567                 },
3568
3569                 getRoot : function() {
3570                         var t = this, s = t.settings;
3571
3572                         return (s && t.get(s.root_element)) || t.doc.body;
3573                 },
3574
3575                 getViewPort : function(w) {
3576                         var d, b;
3577
3578                         w = !w ? this.win : w;
3579                         d = w.document;
3580                         b = this.boxModel ? d.documentElement : d.body;
3581
3582                         // Returns viewport size excluding scrollbars
3583                         return {
3584                                 x : w.pageXOffset || b.scrollLeft,
3585                                 y : w.pageYOffset || b.scrollTop,
3586                                 w : w.innerWidth || b.clientWidth,
3587                                 h : w.innerHeight || b.clientHeight
3588                         };
3589                 },
3590
3591                 getRect : function(e) {
3592                         var p, t = this, sr;
3593
3594                         e = t.get(e);
3595                         p = t.getPos(e);
3596                         sr = t.getSize(e);
3597
3598                         return {
3599                                 x : p.x,
3600                                 y : p.y,
3601                                 w : sr.w,
3602                                 h : sr.h
3603                         };
3604                 },
3605
3606                 getSize : function(e) {
3607                         var t = this, w, h;
3608
3609                         e = t.get(e);
3610                         w = t.getStyle(e, 'width');
3611                         h = t.getStyle(e, 'height');
3612
3613                         // Non pixel value, then force offset/clientWidth
3614                         if (w.indexOf('px') === -1)
3615                                 w = 0;
3616
3617                         // Non pixel value, then force offset/clientWidth
3618                         if (h.indexOf('px') === -1)
3619                                 h = 0;
3620
3621                         return {
3622                                 w : parseInt(w) || e.offsetWidth || e.clientWidth,
3623                                 h : parseInt(h) || e.offsetHeight || e.clientHeight
3624                         };
3625                 },
3626
3627                 getParent : function(n, f, r) {
3628                         return this.getParents(n, f, r, false);
3629                 },
3630
3631                 getParents : function(n, f, r, c) {
3632                         var t = this, na, se = t.settings, o = [];
3633
3634                         n = t.get(n);
3635                         c = c === undefined;
3636
3637                         if (se.strict_root)
3638                                 r = r || t.getRoot();
3639
3640                         // Wrap node name as func
3641                         if (is(f, 'string')) {
3642                                 na = f;
3643
3644                                 if (f === '*') {
3645                                         f = function(n) {return n.nodeType == 1;};
3646                                 } else {
3647                                         f = function(n) {
3648                                                 return t.is(n, na);
3649                                         };
3650                                 }
3651                         }
3652
3653                         while (n) {
3654                                 if (n == r || !n.nodeType || n.nodeType === 9)
3655                                         break;
3656
3657                                 if (!f || f(n)) {
3658                                         if (c)
3659                                                 o.push(n);
3660                                         else
3661                                                 return n;
3662                                 }
3663
3664                                 n = n.parentNode;
3665                         }
3666
3667                         return c ? o : null;
3668                 },
3669
3670                 get : function(e) {
3671                         var n;
3672
3673                         if (e && this.doc && typeof(e) == 'string') {
3674                                 n = e;
3675                                 e = this.doc.getElementById(e);
3676
3677                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
3678                                 if (e && e.id !== n)
3679                                         return this.doc.getElementsByName(n)[1];
3680                         }
3681
3682                         return e;
3683                 },
3684
3685                 getNext : function(node, selector) {
3686                         return this._findSib(node, selector, 'nextSibling');
3687                 },
3688
3689                 getPrev : function(node, selector) {
3690                         return this._findSib(node, selector, 'previousSibling');
3691                 },
3692
3693
3694                 add : function(p, n, a, h, c) {
3695                         var t = this;
3696
3697                         return this.run(p, function(p) {
3698                                 var e, k;
3699
3700                                 e = is(n, 'string') ? t.doc.createElement(n) : n;
3701                                 t.setAttribs(e, a);
3702
3703                                 if (h) {
3704                                         if (h.nodeType)
3705                                                 e.appendChild(h);
3706                                         else
3707                                                 t.setHTML(e, h);
3708                                 }
3709
3710                                 return !c ? p.appendChild(e) : e;
3711                         });
3712                 },
3713
3714                 create : function(n, a, h) {
3715                         return this.add(this.doc.createElement(n), n, a, h, 1);
3716                 },
3717
3718                 createHTML : function(n, a, h) {
3719                         var o = '', t = this, k;
3720
3721                         o += '<' + n;
3722
3723                         for (k in a) {
3724                                 if (a.hasOwnProperty(k))
3725                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';
3726                         }
3727
3728                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
3729                         if (typeof(h) != "undefined")
3730                                 return o + '>' + h + '</' + n + '>';
3731
3732                         return o + ' />';
3733                 },
3734
3735                 remove : function(node, keep_children) {
3736                         return this.run(node, function(node) {
3737                                 var child, parent = node.parentNode;
3738
3739                                 if (!parent)
3740                                         return null;
3741
3742                                 if (keep_children) {
3743                                         while (child = node.firstChild) {
3744                                                 // IE 8 will crash if you don't remove completely empty text nodes
3745                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
3746                                                         parent.insertBefore(child, node);
3747                                                 else
3748                                                         node.removeChild(child);
3749                                         }
3750                                 }
3751
3752                                 return parent.removeChild(node);
3753                         });
3754                 },
3755
3756                 setStyle : function(n, na, v) {
3757                         var t = this;
3758
3759                         return t.run(n, function(e) {
3760                                 var s, i;
3761
3762                                 s = e.style;
3763
3764                                 // Camelcase it, if needed
3765                                 na = na.replace(/-(\D)/g, function(a, b){
3766                                         return b.toUpperCase();
3767                                 });
3768
3769                                 // Default px suffix on these
3770                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
3771                                         v += 'px';
3772
3773                                 switch (na) {
3774                                         case 'opacity':
3775                                                 // IE specific opacity
3776                                                 if (isIE) {
3777                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
3778
3779                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)
3780                                                                 s.display = 'inline-block';
3781                                                 }
3782
3783                                                 // Fix for older browsers
3784                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
3785                                                 break;
3786
3787                                         case 'float':
3788                                                 isIE ? s.styleFloat = v : s.cssFloat = v;
3789                                                 break;
3790                                         
3791                                         default:
3792                                                 s[na] = v || '';
3793                                 }
3794
3795                                 // Force update of the style data
3796                                 if (t.settings.update_styles)
3797                                         t.setAttrib(e, 'data-mce-style');
3798                         });
3799                 },
3800
3801                 getStyle : function(n, na, c) {
3802                         n = this.get(n);
3803
3804                         if (!n)
3805                                 return;
3806
3807                         // Gecko
3808                         if (this.doc.defaultView && c) {
3809                                 // Remove camelcase
3810                                 na = na.replace(/[A-Z]/g, function(a){
3811                                         return '-' + a;
3812                                 });
3813
3814                                 try {
3815                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
3816                                 } catch (ex) {
3817                                         // Old safari might fail
3818                                         return null;
3819                                 }
3820                         }
3821
3822                         // Camelcase it, if needed
3823                         na = na.replace(/-(\D)/g, function(a, b){
3824                                 return b.toUpperCase();
3825                         });
3826
3827                         if (na == 'float')
3828                                 na = isIE ? 'styleFloat' : 'cssFloat';
3829
3830                         // IE & Opera
3831                         if (n.currentStyle && c)
3832                                 return n.currentStyle[na];
3833
3834                         return n.style ? n.style[na] : undefined;
3835                 },
3836
3837                 setStyles : function(e, o) {
3838                         var t = this, s = t.settings, ol;
3839
3840                         ol = s.update_styles;
3841                         s.update_styles = 0;
3842
3843                         each(o, function(v, n) {
3844                                 t.setStyle(e, n, v);
3845                         });
3846
3847                         // Update style info
3848                         s.update_styles = ol;
3849                         if (s.update_styles)
3850                                 t.setAttrib(e, s.cssText);
3851                 },
3852
3853                 removeAllAttribs: function(e) {
3854                         return this.run(e, function(e) {
3855                                 var i, attrs = e.attributes;
3856                                 for (i = attrs.length - 1; i >= 0; i--) {
3857                                         e.removeAttributeNode(attrs.item(i));
3858                                 }
3859                         });
3860                 },
3861
3862                 setAttrib : function(e, n, v) {
3863                         var t = this;
3864
3865                         // Whats the point
3866                         if (!e || !n)
3867                                 return;
3868
3869                         // Strict XML mode
3870                         if (t.settings.strict)
3871                                 n = n.toLowerCase();
3872
3873                         return this.run(e, function(e) {
3874                                 var s = t.settings;
3875
3876                                 switch (n) {
3877                                         case "style":
3878                                                 if (!is(v, 'string')) {
3879                                                         each(v, function(v, n) {
3880                                                                 t.setStyle(e, n, v);
3881                                                         });
3882
3883                                                         return;
3884                                                 }
3885
3886                                                 // No mce_style for elements with these since they might get resized by the user
3887                                                 if (s.keep_values) {
3888                                                         if (v && !t._isRes(v))
3889                                                                 e.setAttribute('data-mce-style', v, 2);
3890                                                         else
3891                                                                 e.removeAttribute('data-mce-style', 2);
3892                                                 }
3893
3894                                                 e.style.cssText = v;
3895                                                 break;
3896
3897                                         case "class":
3898                                                 e.className = v || ''; // Fix IE null bug
3899                                                 break;
3900
3901                                         case "src":
3902                                         case "href":
3903                                                 if (s.keep_values) {
3904                                                         if (s.url_converter)
3905                                                                 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
3906
3907                                                         t.setAttrib(e, 'data-mce-' + n, v, 2);
3908                                                 }
3909
3910                                                 break;
3911
3912                                         case "shape":
3913                                                 e.setAttribute('data-mce-style', v);
3914                                                 break;
3915                                 }
3916
3917                                 if (is(v) && v !== null && v.length !== 0)
3918                                         e.setAttribute(n, '' + v, 2);
3919                                 else
3920                                         e.removeAttribute(n, 2);
3921                         });
3922                 },
3923
3924                 setAttribs : function(e, o) {
3925                         var t = this;
3926
3927                         return this.run(e, function(e) {
3928                                 each(o, function(v, n) {
3929                                         t.setAttrib(e, n, v);
3930                                 });
3931                         });
3932                 },
3933
3934                 getAttrib : function(e, n, dv) {
3935                         var v, t = this;
3936
3937                         e = t.get(e);
3938
3939                         if (!e || e.nodeType !== 1)
3940                                 return false;
3941
3942                         if (!is(dv))
3943                                 dv = '';
3944
3945                         // Try the mce variant for these
3946                         if (/^(src|href|style|coords|shape)$/.test(n)) {
3947                                 v = e.getAttribute("data-mce-" + n);
3948
3949                                 if (v)
3950                                         return v;
3951                         }
3952
3953                         if (isIE && t.props[n]) {
3954                                 v = e[t.props[n]];
3955                                 v = v && v.nodeValue ? v.nodeValue : v;
3956                         }
3957
3958                         if (!v)
3959                                 v = e.getAttribute(n, 2);
3960
3961                         // Check boolean attribs
3962                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
3963                                 if (e[t.props[n]] === true && v === '')
3964                                         return n;
3965
3966                                 return v ? n : '';
3967                         }
3968
3969                         // Inner input elements will override attributes on form elements
3970                         if (e.nodeName === "FORM" && e.getAttributeNode(n))
3971                                 return e.getAttributeNode(n).nodeValue;
3972
3973                         if (n === 'style') {
3974                                 v = v || e.style.cssText;
3975
3976                                 if (v) {
3977                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);
3978
3979                                         if (t.settings.keep_values && !t._isRes(v))
3980                                                 e.setAttribute('data-mce-style', v);
3981                                 }
3982                         }
3983
3984                         // Remove Apple and WebKit stuff
3985                         if (isWebKit && n === "class" && v)
3986                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
3987
3988                         // Handle IE issues
3989                         if (isIE) {
3990                                 switch (n) {
3991                                         case 'rowspan':
3992                                         case 'colspan':
3993                                                 // IE returns 1 as default value
3994                                                 if (v === 1)
3995                                                         v = '';
3996
3997                                                 break;
3998
3999                                         case 'size':
4000                                                 // IE returns +0 as default value for size
4001                                                 if (v === '+0' || v === 20 || v === 0)
4002                                                         v = '';
4003
4004                                                 break;
4005
4006                                         case 'width':
4007                                         case 'height':
4008                                         case 'vspace':
4009                                         case 'checked':
4010                                         case 'disabled':
4011                                         case 'readonly':
4012                                                 if (v === 0)
4013                                                         v = '';
4014
4015                                                 break;
4016
4017                                         case 'hspace':
4018                                                 // IE returns -1 as default value
4019                                                 if (v === -1)
4020                                                         v = '';
4021
4022                                                 break;
4023
4024                                         case 'maxlength':
4025                                         case 'tabindex':
4026                                                 // IE returns default value
4027                                                 if (v === 32768 || v === 2147483647 || v === '32768')
4028                                                         v = '';
4029
4030                                                 break;
4031
4032                                         case 'multiple':
4033                                         case 'compact':
4034                                         case 'noshade':
4035                                         case 'nowrap':
4036                                                 if (v === 65535)
4037                                                         return n;
4038
4039                                                 return dv;
4040
4041                                         case 'shape':
4042                                                 v = v.toLowerCase();
4043                                                 break;
4044
4045                                         default:
4046                                                 // IE has odd anonymous function for event attributes
4047                                                 if (n.indexOf('on') === 0 && v)
4048                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
4049                                 }
4050                         }
4051
4052                         return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
4053                 },
4054
4055                 getPos : function(n, ro) {
4056                         var t = this, x = 0, y = 0, e, d = t.doc, r;
4057
4058                         n = t.get(n);
4059                         ro = ro || d.body;
4060
4061                         if (n) {
4062                                 // Use getBoundingClientRect on IE, Opera has it but it's not perfect
4063                                 if (isIE && !t.stdMode) {
4064                                         n = n.getBoundingClientRect();
4065                                         e = t.boxModel ? d.documentElement : d.body;
4066                                         x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
4067                                         x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
4068
4069                                         return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
4070                                 }
4071
4072                                 r = n;
4073                                 while (r && r != ro && r.nodeType) {
4074                                         x += r.offsetLeft || 0;
4075                                         y += r.offsetTop || 0;
4076                                         r = r.offsetParent;
4077                                 }
4078
4079                                 r = n.parentNode;
4080                                 while (r && r != ro && r.nodeType) {
4081                                         x -= r.scrollLeft || 0;
4082                                         y -= r.scrollTop || 0;
4083                                         r = r.parentNode;
4084                                 }
4085                         }
4086
4087                         return {x : x, y : y};
4088                 },
4089
4090                 parseStyle : function(st) {
4091                         return this.styles.parse(st);
4092                 },
4093
4094                 serializeStyle : function(o, name) {
4095                         return this.styles.serialize(o, name);
4096                 },
4097
4098                 loadCSS : function(u) {
4099                         var t = this, d = t.doc, head;
4100
4101                         if (!u)
4102                                 u = '';
4103
4104                         head = t.select('head')[0];
4105
4106                         each(u.split(','), function(u) {
4107                                 var link;
4108
4109                                 if (t.files[u])
4110                                         return;
4111
4112                                 t.files[u] = true;
4113                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
4114
4115                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
4116                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
4117                                 // It's ugly but it seems to work fine.
4118                                 if (isIE && d.documentMode && d.recalc) {
4119                                         link.onload = function() {
4120                                                 if (d.recalc)
4121                                                         d.recalc();
4122
4123                                                 link.onload = null;
4124                                         };
4125                                 }
4126
4127                                 head.appendChild(link);
4128                         });
4129                 },
4130
4131                 addClass : function(e, c) {
4132                         return this.run(e, function(e) {
4133                                 var o;
4134
4135                                 if (!c)
4136                                         return 0;
4137
4138                                 if (this.hasClass(e, c))
4139                                         return e.className;
4140
4141                                 o = this.removeClass(e, c);
4142
4143                                 return e.className = (o != '' ? (o + ' ') : '') + c;
4144                         });
4145                 },
4146
4147                 removeClass : function(e, c) {
4148                         var t = this, re;
4149
4150                         return t.run(e, function(e) {
4151                                 var v;
4152
4153                                 if (t.hasClass(e, c)) {
4154                                         if (!re)
4155                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
4156
4157                                         v = e.className.replace(re, ' ');
4158                                         v = tinymce.trim(v != ' ' ? v : '');
4159
4160                                         e.className = v;
4161
4162                                         // Empty class attr
4163                                         if (!v) {
4164                                                 e.removeAttribute('class');
4165                                                 e.removeAttribute('className');
4166                                         }
4167
4168                                         return v;
4169                                 }
4170
4171                                 return e.className;
4172                         });
4173                 },
4174
4175                 hasClass : function(n, c) {
4176                         n = this.get(n);
4177
4178                         if (!n || !c)
4179                                 return false;
4180
4181                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
4182                 },
4183
4184                 show : function(e) {
4185                         return this.setStyle(e, 'display', 'block');
4186                 },
4187
4188                 hide : function(e) {
4189                         return this.setStyle(e, 'display', 'none');
4190                 },
4191
4192                 isHidden : function(e) {
4193                         e = this.get(e);
4194
4195                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
4196                 },
4197
4198                 uniqueId : function(p) {
4199                         return (!p ? 'mce_' : p) + (this.counter++);
4200                 },
4201
4202                 setHTML : function(element, html) {
4203                         var self = this;
4204
4205                         return self.run(element, function(element) {
4206                                 if (isIE) {
4207                                         // Remove all child nodes, IE keeps empty text nodes in DOM
4208                                         while (element.firstChild)
4209                                                 element.removeChild(element.firstChild);
4210
4211                                         try {
4212                                                 // IE will remove comments from the beginning
4213                                                 // unless you padd the contents with something
4214                                                 element.innerHTML = '<br />' + html;
4215                                                 element.removeChild(element.firstChild);
4216                                         } catch (ex) {
4217                                                 // 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
4218                                                 // This seems to fix this problem
4219
4220                                                 // Create new div with HTML contents and a BR infront to keep comments
4221                                                 element = self.create('div');
4222                                                 element.innerHTML = '<br />' + html;
4223
4224                                                 // Add all children from div to target
4225                                                 each (element.childNodes, function(node, i) {
4226                                                         // Skip br element
4227                                                         if (i)
4228                                                                 element.appendChild(node);
4229                                                 });
4230                                         }
4231                                 } else
4232                                         element.innerHTML = html;
4233
4234                                 return html;
4235                         });
4236                 },
4237
4238                 getOuterHTML : function(elm) {
4239                         var doc, self = this;
4240
4241                         elm = self.get(elm);
4242
4243                         if (!elm)
4244                                 return null;
4245
4246                         if (elm.nodeType === 1 && self.hasOuterHTML)
4247                                 return elm.outerHTML;
4248
4249                         doc = (elm.ownerDocument || self.doc).createElement("body");
4250                         doc.appendChild(elm.cloneNode(true));
4251
4252                         return doc.innerHTML;
4253                 },
4254
4255                 setOuterHTML : function(e, h, d) {
4256                         var t = this;
4257
4258                         function setHTML(e, h, d) {
4259                                 var n, tp;
4260
4261                                 tp = d.createElement("body");
4262                                 tp.innerHTML = h;
4263
4264                                 n = tp.lastChild;
4265                                 while (n) {
4266                                         t.insertAfter(n.cloneNode(true), e);
4267                                         n = n.previousSibling;
4268                                 }
4269
4270                                 t.remove(e);
4271                         };
4272
4273                         return this.run(e, function(e) {
4274                                 e = t.get(e);
4275
4276                                 // Only set HTML on elements
4277                                 if (e.nodeType == 1) {
4278                                         d = d || e.ownerDocument || t.doc;
4279
4280                                         if (isIE) {
4281                                                 try {
4282                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error
4283                                                         if (isIE && e.nodeType == 1)
4284                                                                 e.outerHTML = h;
4285                                                         else
4286                                                                 setHTML(e, h, d);
4287                                                 } catch (ex) {
4288                                                         // Fix for unknown runtime error
4289                                                         setHTML(e, h, d);
4290                                                 }
4291                                         } else
4292                                                 setHTML(e, h, d);
4293                                 }
4294                         });
4295                 },
4296
4297                 decode : Entities.decode,
4298
4299                 encode : Entities.encodeAllRaw,
4300
4301                 insertAfter : function(node, reference_node) {
4302                         reference_node = this.get(reference_node);
4303
4304                         return this.run(node, function(node) {
4305                                 var parent, nextSibling;
4306
4307                                 parent = reference_node.parentNode;
4308                                 nextSibling = reference_node.nextSibling;
4309
4310                                 if (nextSibling)
4311                                         parent.insertBefore(node, nextSibling);
4312                                 else
4313                                         parent.appendChild(node);
4314
4315                                 return node;
4316                         });
4317                 },
4318
4319                 isBlock : function(node) {
4320                         var type = node.nodeType;
4321
4322                         // If it's a node then check the type and use the nodeName
4323                         if (type)
4324                                 return !!(type === 1 && blockElementsMap[node.nodeName]);
4325
4326                         return !!blockElementsMap[node];
4327                 },
4328
4329                 replace : function(n, o, k) {
4330                         var t = this;
4331
4332                         if (is(o, 'array'))
4333                                 n = n.cloneNode(true);
4334
4335                         return t.run(o, function(o) {
4336                                 if (k) {
4337                                         each(tinymce.grep(o.childNodes), function(c) {
4338                                                 n.appendChild(c);
4339                                         });
4340                                 }
4341
4342                                 return o.parentNode.replaceChild(n, o);
4343                         });
4344                 },
4345
4346                 rename : function(elm, name) {
4347                         var t = this, newElm;
4348
4349                         if (elm.nodeName != name.toUpperCase()) {
4350                                 // Rename block element
4351                                 newElm = t.create(name);
4352
4353                                 // Copy attribs to new block
4354                                 each(t.getAttribs(elm), function(attr_node) {
4355                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
4356                                 });
4357
4358                                 // Replace block
4359                                 t.replace(newElm, elm, 1);
4360                         }
4361
4362                         return newElm || elm;
4363                 },
4364
4365                 findCommonAncestor : function(a, b) {
4366                         var ps = a, pe;
4367
4368                         while (ps) {
4369                                 pe = b;
4370
4371                                 while (pe && ps != pe)
4372                                         pe = pe.parentNode;
4373
4374                                 if (ps == pe)
4375                                         break;
4376
4377                                 ps = ps.parentNode;
4378                         }
4379
4380                         if (!ps && a.ownerDocument)
4381                                 return a.ownerDocument.documentElement;
4382
4383                         return ps;
4384                 },
4385
4386                 toHex : function(s) {
4387                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
4388
4389                         function hex(s) {
4390                                 s = parseInt(s).toString(16);
4391
4392                                 return s.length > 1 ? s : '0' + s; // 0 -> 00
4393                         };
4394
4395                         if (c) {
4396                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
4397
4398                                 return s;
4399                         }
4400
4401                         return s;
4402                 },
4403
4404                 getClasses : function() {
4405                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
4406
4407                         if (t.classes)
4408                                 return t.classes;
4409
4410                         function addClasses(s) {
4411                                 // IE style imports
4412                                 each(s.imports, function(r) {
4413                                         addClasses(r);
4414                                 });
4415
4416                                 each(s.cssRules || s.rules, function(r) {
4417                                         // Real type or fake it on IE
4418                                         switch (r.type || 1) {
4419                                                 // Rule
4420                                                 case 1:
4421                                                         if (r.selectorText) {
4422                                                                 each(r.selectorText.split(','), function(v) {
4423                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");
4424
4425                                                                         // Is internal or it doesn't contain a class
4426                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
4427                                                                                 return;
4428
4429                                                                         // Remove everything but class name
4430                                                                         ov = v;
4431                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
4432
4433                                                                         // Filter classes
4434                                                                         if (f && !(v = f(v, ov)))
4435                                                                                 return;
4436
4437                                                                         if (!lo[v]) {
4438                                                                                 cl.push({'class' : v});
4439                                                                                 lo[v] = 1;
4440                                                                         }
4441                                                                 });
4442                                                         }
4443                                                         break;
4444
4445                                                 // Import
4446                                                 case 3:
4447                                                         addClasses(r.styleSheet);
4448                                                         break;
4449                                         }
4450                                 });
4451                         };
4452
4453                         try {
4454                                 each(t.doc.styleSheets, addClasses);
4455                         } catch (ex) {
4456                                 // Ignore
4457                         }
4458
4459                         if (cl.length > 0)
4460                                 t.classes = cl;
4461
4462                         return cl;
4463                 },
4464
4465                 run : function(e, f, s) {
4466                         var t = this, o;
4467
4468                         if (t.doc && typeof(e) === 'string')
4469                                 e = t.get(e);
4470
4471                         if (!e)
4472                                 return false;
4473
4474                         s = s || this;
4475                         if (!e.nodeType && (e.length || e.length === 0)) {
4476                                 o = [];
4477
4478                                 each(e, function(e, i) {
4479                                         if (e) {
4480                                                 if (typeof(e) == 'string')
4481                                                         e = t.doc.getElementById(e);
4482
4483                                                 o.push(f.call(s, e, i));
4484                                         }
4485                                 });
4486
4487                                 return o;
4488                         }
4489
4490                         return f.call(s, e);
4491                 },
4492
4493                 getAttribs : function(n) {
4494                         var o;
4495
4496                         n = this.get(n);
4497
4498                         if (!n)
4499                                 return [];
4500
4501                         if (isIE) {
4502                                 o = [];
4503
4504                                 // Object will throw exception in IE
4505                                 if (n.nodeName == 'OBJECT')
4506                                         return n.attributes;
4507
4508                                 // IE doesn't keep the selected attribute if you clone option elements
4509                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
4510                                         o.push({specified : 1, nodeName : 'selected'});
4511
4512                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
4513                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
4514                                         o.push({specified : 1, nodeName : a});
4515                                 });
4516
4517                                 return o;
4518                         }
4519
4520                         return n.attributes;
4521                 },
4522
4523                 isEmpty : function(node, elements) {
4524                         var self = this, i, attributes, type, walker, name;
4525
4526                         node = node.firstChild;
4527                         if (node) {
4528                                 walker = new tinymce.dom.TreeWalker(node);
4529                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
4530
4531                                 do {
4532                                         type = node.nodeType;
4533
4534                                         if (type === 1) {
4535                                                 // Ignore bogus elements
4536                                                 if (node.getAttribute('data-mce-bogus'))
4537                                                         continue;
4538
4539                                                 // Keep empty elements like <img />
4540                                                 if (elements && elements[node.nodeName.toLowerCase()])
4541                                                         return false;
4542
4543                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
4544                                                 attributes = self.getAttribs(node);
4545                                                 i = node.attributes.length;
4546                                                 while (i--) {
4547                                                         name = node.attributes[i].nodeName;
4548                                                         if (name === "name" || name.indexOf('data-') === 0)
4549                                                                 return false;
4550                                                 }
4551                                         }
4552
4553                                         // Keep non whitespace text nodes
4554                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
4555                                                 return false;
4556                                 } while (node = walker.next());
4557                         }
4558
4559                         return true;
4560                 },
4561
4562                 destroy : function(s) {
4563                         var t = this;
4564
4565                         if (t.events)
4566                                 t.events.destroy();
4567
4568                         t.win = t.doc = t.root = t.events = null;
4569
4570                         // Manual destroy then remove unload handler
4571                         if (!s)
4572                                 tinymce.removeUnload(t.destroy);
4573                 },
4574
4575                 createRng : function() {
4576                         var d = this.doc;
4577
4578                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
4579                 },
4580
4581                 nodeIndex : function(node, normalized) {
4582                         var idx = 0, lastNodeType, lastNode, nodeType, nodeValueExists;
4583
4584                         if (node) {
4585                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
4586                                         nodeType = node.nodeType;
4587
4588                                         // Normalize text nodes
4589                                         if (normalized && nodeType == 3) {
4590                                                 // ensure that text nodes that have been removed are handled correctly in Internet Explorer.
4591                                                 // (the nodeValue attribute will not exist, and will error here).
4592                                                 nodeValueExists = false;
4593                                                 try {nodeValueExists = node.nodeValue.length} catch (c) {}
4594                                                 if (nodeType == lastNodeType || !nodeValueExists)
4595                                                         continue;
4596                                         }
4597                                         idx++;
4598                                         lastNodeType = nodeType;
4599                                 }
4600                         }
4601
4602                         return idx;
4603                 },
4604
4605                 split : function(pe, e, re) {
4606                         var t = this, r = t.createRng(), bef, aft, pa;
4607
4608                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
4609                         // but we don't want that in our code since it serves no purpose for the end user
4610                         // For example if this is chopped:
4611                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>
4612                         // would produce:
4613                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
4614                         // this function will then trim of empty edges and produce:
4615                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>
4616                         function trim(node) {
4617                                 var i, children = node.childNodes, type = node.nodeType;
4618
4619                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
4620                                         return;
4621
4622                                 for (i = children.length - 1; i >= 0; i--)
4623                                         trim(children[i]);
4624
4625                                 if (type != 9) {
4626                                         // Keep non whitespace text nodes
4627                                         if (type == 3 && node.nodeValue.length > 0) {
4628                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
4629                                                 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
4630                                                         return;
4631                                         } else if (type == 1) {
4632                                                 // If the only child is a bookmark then move it up
4633                                                 children = node.childNodes;
4634                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
4635                                                         node.parentNode.insertBefore(children[0], node);
4636
4637                                                 // Keep non empty elements or img, hr etc
4638                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
4639                                                         return;
4640                                         }
4641
4642                                         t.remove(node);
4643                                 }
4644
4645                                 return node;
4646                         };
4647
4648                         if (pe && e) {
4649                                 // Get before chunk
4650                                 r.setStart(pe.parentNode, t.nodeIndex(pe));
4651                                 r.setEnd(e.parentNode, t.nodeIndex(e));
4652                                 bef = r.extractContents();
4653
4654                                 // Get after chunk
4655                                 r = t.createRng();
4656                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
4657                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
4658                                 aft = r.extractContents();
4659
4660                                 // Insert before chunk
4661                                 pa = pe.parentNode;
4662                                 pa.insertBefore(trim(bef), pe);
4663
4664                                 // Insert middle chunk
4665                                 if (re)
4666                                         pa.replaceChild(re, e);
4667                                 else
4668                                         pa.insertBefore(e, pe);
4669
4670                                 // Insert after chunk
4671                                 pa.insertBefore(trim(aft), pe);
4672                                 t.remove(pe);
4673
4674                                 return re || e;
4675                         }
4676                 },
4677
4678                 bind : function(target, name, func, scope) {
4679                         var t = this;
4680
4681                         if (!t.events)
4682                                 t.events = new tinymce.dom.EventUtils();
4683
4684                         return t.events.add(target, name, func, scope || this);
4685                 },
4686
4687                 unbind : function(target, name, func) {
4688                         var t = this;
4689
4690                         if (!t.events)
4691                                 t.events = new tinymce.dom.EventUtils();
4692
4693                         return t.events.remove(target, name, func);
4694                 },
4695
4696
4697                 _findSib : function(node, selector, name) {
4698                         var t = this, f = selector;
4699
4700                         if (node) {
4701                                 // If expression make a function of it using is
4702                                 if (is(f, 'string')) {
4703                                         f = function(node) {
4704                                                 return t.is(node, selector);
4705                                         };
4706                                 }
4707
4708                                 // Loop all siblings
4709                                 for (node = node[name]; node; node = node[name]) {
4710                                         if (f(node))
4711                                                 return node;
4712                                 }
4713                         }
4714
4715                         return null;
4716                 },
4717
4718                 _isRes : function(c) {
4719                         // Is live resizble element
4720                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
4721                 }
4722
4723                 /*
4724                 walk : function(n, f, s) {
4725                         var d = this.doc, w;
4726
4727                         if (d.createTreeWalker) {
4728                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
4729
4730                                 while ((n = w.nextNode()) != null)
4731                                         f.call(s || this, n);
4732                         } else
4733                                 tinymce.walk(n, f, 'childNodes', s);
4734                 }
4735                 */
4736
4737                 /*
4738                 toRGB : function(s) {
4739                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
4740
4741                         if (c) {
4742                                 // #FFF -> #FFFFFF
4743                                 if (!is(c[3]))
4744                                         c[3] = c[2] = c[1];
4745
4746                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
4747                         }
4748
4749                         return s;
4750                 }
4751                 */
4752         });
4753
4754         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
4755 })(tinymce);
4756
4757 (function(ns) {
4758         // Range constructor
4759         function Range(dom) {
4760                 var t = this,
4761                         doc = dom.doc,
4762                         EXTRACT = 0,
4763                         CLONE = 1,
4764                         DELETE = 2,
4765                         TRUE = true,
4766                         FALSE = false,
4767                         START_OFFSET = 'startOffset',
4768                         START_CONTAINER = 'startContainer',
4769                         END_CONTAINER = 'endContainer',
4770                         END_OFFSET = 'endOffset',
4771                         extend = tinymce.extend,
4772                         nodeIndex = dom.nodeIndex;
4773
4774                 extend(t, {
4775                         // Inital states
4776                         startContainer : doc,
4777                         startOffset : 0,
4778                         endContainer : doc,
4779                         endOffset : 0,
4780                         collapsed : TRUE,
4781                         commonAncestorContainer : doc,
4782
4783                         // Range constants
4784                         START_TO_START : 0,
4785                         START_TO_END : 1,
4786                         END_TO_END : 2,
4787                         END_TO_START : 3,
4788
4789                         // Public methods
4790                         setStart : setStart,
4791                         setEnd : setEnd,
4792                         setStartBefore : setStartBefore,
4793                         setStartAfter : setStartAfter,
4794                         setEndBefore : setEndBefore,
4795                         setEndAfter : setEndAfter,
4796                         collapse : collapse,
4797                         selectNode : selectNode,
4798                         selectNodeContents : selectNodeContents,
4799                         compareBoundaryPoints : compareBoundaryPoints,
4800                         deleteContents : deleteContents,
4801                         extractContents : extractContents,
4802                         cloneContents : cloneContents,
4803                         insertNode : insertNode,
4804                         surroundContents : surroundContents,
4805                         cloneRange : cloneRange
4806                 });
4807
4808                 function setStart(n, o) {
4809                         _setEndPoint(TRUE, n, o);
4810                 };
4811
4812                 function setEnd(n, o) {
4813                         _setEndPoint(FALSE, n, o);
4814                 };
4815
4816                 function setStartBefore(n) {
4817                         setStart(n.parentNode, nodeIndex(n));
4818                 };
4819
4820                 function setStartAfter(n) {
4821                         setStart(n.parentNode, nodeIndex(n) + 1);
4822                 };
4823
4824                 function setEndBefore(n) {
4825                         setEnd(n.parentNode, nodeIndex(n));
4826                 };
4827
4828                 function setEndAfter(n) {
4829                         setEnd(n.parentNode, nodeIndex(n) + 1);
4830                 };
4831
4832                 function collapse(ts) {
4833                         if (ts) {
4834                                 t[END_CONTAINER] = t[START_CONTAINER];
4835                                 t[END_OFFSET] = t[START_OFFSET];
4836                         } else {
4837                                 t[START_CONTAINER] = t[END_CONTAINER];
4838                                 t[START_OFFSET] = t[END_OFFSET];
4839                         }
4840
4841                         t.collapsed = TRUE;
4842                 };
4843
4844                 function selectNode(n) {
4845                         setStartBefore(n);
4846                         setEndAfter(n);
4847                 };
4848
4849                 function selectNodeContents(n) {
4850                         setStart(n, 0);
4851                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
4852                 };
4853
4854                 function compareBoundaryPoints(h, r) {
4855                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
4856                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
4857
4858                         // Check START_TO_START
4859                         if (h === 0)
4860                                 return _compareBoundaryPoints(sc, so, rsc, rso);
4861         
4862                         // Check START_TO_END
4863                         if (h === 1)
4864                                 return _compareBoundaryPoints(ec, eo, rsc, rso);
4865         
4866                         // Check END_TO_END
4867                         if (h === 2)
4868                                 return _compareBoundaryPoints(ec, eo, rec, reo);
4869         
4870                         // Check END_TO_START
4871                         if (h === 3) 
4872                                 return _compareBoundaryPoints(sc, so, rec, reo);
4873                 };
4874
4875                 function deleteContents() {
4876                         _traverse(DELETE);
4877                 };
4878
4879                 function extractContents() {
4880                         return _traverse(EXTRACT);
4881                 };
4882
4883                 function cloneContents() {
4884                         return _traverse(CLONE);
4885                 };
4886
4887                 function insertNode(n) {
4888                         var startContainer = this[START_CONTAINER],
4889                                 startOffset = this[START_OFFSET], nn, o;
4890
4891                         // Node is TEXT_NODE or CDATA
4892                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
4893                                 if (!startOffset) {
4894                                         // At the start of text
4895                                         startContainer.parentNode.insertBefore(n, startContainer);
4896                                 } else if (startOffset >= startContainer.nodeValue.length) {
4897                                         // At the end of text
4898                                         dom.insertAfter(n, startContainer);
4899                                 } else {
4900                                         // Middle, need to split
4901                                         nn = startContainer.splitText(startOffset);
4902                                         startContainer.parentNode.insertBefore(n, nn);
4903                                 }
4904                         } else {
4905                                 // Insert element node
4906                                 if (startContainer.childNodes.length > 0)
4907                                         o = startContainer.childNodes[startOffset];
4908
4909                                 if (o)
4910                                         startContainer.insertBefore(n, o);
4911                                 else
4912                                         startContainer.appendChild(n);
4913                         }
4914                 };
4915
4916                 function surroundContents(n) {
4917                         var f = t.extractContents();
4918
4919                         t.insertNode(n);
4920                         n.appendChild(f);
4921                         t.selectNode(n);
4922                 };
4923
4924                 function cloneRange() {
4925                         return extend(new Range(dom), {
4926                                 startContainer : t[START_CONTAINER],
4927                                 startOffset : t[START_OFFSET],
4928                                 endContainer : t[END_CONTAINER],
4929                                 endOffset : t[END_OFFSET],
4930                                 collapsed : t.collapsed,
4931                                 commonAncestorContainer : t.commonAncestorContainer
4932                         });
4933                 };
4934
4935                 // Private methods
4936
4937                 function _getSelectedNode(container, offset) {
4938                         var child;
4939
4940                         if (container.nodeType == 3 /* TEXT_NODE */)
4941                                 return container;
4942
4943                         if (offset < 0)
4944                                 return container;
4945
4946                         child = container.firstChild;
4947                         while (child && offset > 0) {
4948                                 --offset;
4949                                 child = child.nextSibling;
4950                         }
4951
4952                         if (child)
4953                                 return child;
4954
4955                         return container;
4956                 };
4957
4958                 function _isCollapsed() {
4959                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
4960                 };
4961
4962                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
4963                         var c, offsetC, n, cmnRoot, childA, childB;
4964                         
4965                         // In the first case the boundary-points have the same container. A is before B
4966                         // if its offset is less than the offset of B, A is equal to B if its offset is
4967                         // equal to the offset of B, and A is after B if its offset is greater than the
4968                         // offset of B.
4969                         if (containerA == containerB) {
4970                                 if (offsetA == offsetB)
4971                                         return 0; // equal
4972
4973                                 if (offsetA < offsetB)
4974                                         return -1; // before
4975
4976                                 return 1; // after
4977                         }
4978
4979                         // In the second case a child node C of the container of A is an ancestor
4980                         // container of B. In this case, A is before B if the offset of A is less than or
4981                         // equal to the index of the child node C and A is after B otherwise.
4982                         c = containerB;
4983                         while (c && c.parentNode != containerA)
4984                                 c = c.parentNode;
4985
4986                         if (c) {
4987                                 offsetC = 0;
4988                                 n = containerA.firstChild;
4989
4990                                 while (n != c && offsetC < offsetA) {
4991                                         offsetC++;
4992                                         n = n.nextSibling;
4993                                 }
4994
4995                                 if (offsetA <= offsetC)
4996                                         return -1; // before
4997
4998                                 return 1; // after
4999                         }
5000
5001                         // In the third case a child node C of the container of B is an ancestor container
5002                         // of A. In this case, A is before B if the index of the child node C is less than
5003                         // the offset of B and A is after B otherwise.
5004                         c = containerA;
5005                         while (c && c.parentNode != containerB) {
5006                                 c = c.parentNode;
5007                         }
5008
5009                         if (c) {
5010                                 offsetC = 0;
5011                                 n = containerB.firstChild;
5012
5013                                 while (n != c && offsetC < offsetB) {
5014                                         offsetC++;
5015                                         n = n.nextSibling;
5016                                 }
5017
5018                                 if (offsetC < offsetB)
5019                                         return -1; // before
5020
5021                                 return 1; // after
5022                         }
5023
5024                         // In the fourth case, none of three other cases hold: the containers of A and B
5025                         // are siblings or descendants of sibling nodes. In this case, A is before B if
5026                         // the container of A is before the container of B in a pre-order traversal of the
5027                         // Ranges' context tree and A is after B otherwise.
5028                         cmnRoot = dom.findCommonAncestor(containerA, containerB);
5029                         childA = containerA;
5030
5031                         while (childA && childA.parentNode != cmnRoot)
5032                                 childA = childA.parentNode;
5033
5034                         if (!childA)
5035                                 childA = cmnRoot;
5036
5037                         childB = containerB;
5038                         while (childB && childB.parentNode != cmnRoot)
5039                                 childB = childB.parentNode;
5040
5041                         if (!childB)
5042                                 childB = cmnRoot;
5043
5044                         if (childA == childB)
5045                                 return 0; // equal
5046
5047                         n = cmnRoot.firstChild;
5048                         while (n) {
5049                                 if (n == childA)
5050                                         return -1; // before
5051
5052                                 if (n == childB)
5053                                         return 1; // after
5054
5055                                 n = n.nextSibling;
5056                         }
5057                 };
5058
5059                 function _setEndPoint(st, n, o) {
5060                         var ec, sc;
5061
5062                         if (st) {
5063                                 t[START_CONTAINER] = n;
5064                                 t[START_OFFSET] = o;
5065                         } else {
5066                                 t[END_CONTAINER] = n;
5067                                 t[END_OFFSET] = o;
5068                         }
5069
5070                         // If one boundary-point of a Range is set to have a root container
5071                         // other than the current one for the Range, the Range is collapsed to
5072                         // the new position. This enforces the restriction that both boundary-
5073                         // points of a Range must have the same root container.
5074                         ec = t[END_CONTAINER];
5075                         while (ec.parentNode)
5076                                 ec = ec.parentNode;
5077
5078                         sc = t[START_CONTAINER];
5079                         while (sc.parentNode)
5080                                 sc = sc.parentNode;
5081
5082                         if (sc == ec) {
5083                                 // The start position of a Range is guaranteed to never be after the
5084                                 // end position. To enforce this restriction, if the start is set to
5085                                 // be at a position after the end, the Range is collapsed to that
5086                                 // position.
5087                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
5088                                         t.collapse(st);
5089                         } else
5090                                 t.collapse(st);
5091
5092                         t.collapsed = _isCollapsed();
5093                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
5094                 };
5095
5096                 function _traverse(how) {
5097                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
5098
5099                         if (t[START_CONTAINER] == t[END_CONTAINER])
5100                                 return _traverseSameContainer(how);
5101
5102                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
5103                                 if (p == t[START_CONTAINER])
5104                                         return _traverseCommonStartContainer(c, how);
5105
5106                                 ++endContainerDepth;
5107                         }
5108
5109                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
5110                                 if (p == t[END_CONTAINER])
5111                                         return _traverseCommonEndContainer(c, how);
5112
5113                                 ++startContainerDepth;
5114                         }
5115
5116                         depthDiff = startContainerDepth - endContainerDepth;
5117
5118                         startNode = t[START_CONTAINER];
5119                         while (depthDiff > 0) {
5120                                 startNode = startNode.parentNode;
5121                                 depthDiff--;
5122                         }
5123
5124                         endNode = t[END_CONTAINER];
5125                         while (depthDiff < 0) {
5126                                 endNode = endNode.parentNode;
5127                                 depthDiff++;
5128                         }
5129
5130                         // ascend the ancestor hierarchy until we have a common parent.
5131                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
5132                                 startNode = sp;
5133                                 endNode = ep;
5134                         }
5135
5136                         return _traverseCommonAncestors(startNode, endNode, how);
5137                 };
5138
5139                  function _traverseSameContainer(how) {
5140                         var frag, s, sub, n, cnt, sibling, xferNode;
5141
5142                         if (how != DELETE)
5143                                 frag = doc.createDocumentFragment();
5144
5145                         // If selection is empty, just return the fragment
5146                         if (t[START_OFFSET] == t[END_OFFSET])
5147                                 return frag;
5148
5149                         // Text node needs special case handling
5150                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
5151                                 // get the substring
5152                                 s = t[START_CONTAINER].nodeValue;
5153                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
5154
5155                                 // set the original text node to its new value
5156                                 if (how != CLONE) {
5157                                         t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
5158
5159                                         // Nothing is partially selected, so collapse to start point
5160                                         t.collapse(TRUE);
5161                                 }
5162
5163                                 if (how == DELETE)
5164                                         return;
5165
5166                                 frag.appendChild(doc.createTextNode(sub));
5167                                 return frag;
5168                         }
5169
5170                         // Copy nodes between the start/end offsets.
5171                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
5172                         cnt = t[END_OFFSET] - t[START_OFFSET];
5173
5174                         while (cnt > 0) {
5175                                 sibling = n.nextSibling;
5176                                 xferNode = _traverseFullySelected(n, how);
5177
5178                                 if (frag)
5179                                         frag.appendChild( xferNode );
5180
5181                                 --cnt;
5182                                 n = sibling;
5183                         }
5184
5185                         // Nothing is partially selected, so collapse to start point
5186                         if (how != CLONE)
5187                                 t.collapse(TRUE);
5188
5189                         return frag;
5190                 };
5191
5192                 function _traverseCommonStartContainer(endAncestor, how) {
5193                         var frag, n, endIdx, cnt, sibling, xferNode;
5194
5195                         if (how != DELETE)
5196                                 frag = doc.createDocumentFragment();
5197
5198                         n = _traverseRightBoundary(endAncestor, how);
5199
5200                         if (frag)
5201                                 frag.appendChild(n);
5202
5203                         endIdx = nodeIndex(endAncestor);
5204                         cnt = endIdx - t[START_OFFSET];
5205
5206                         if (cnt <= 0) {
5207                                 // Collapse to just before the endAncestor, which
5208                                 // is partially selected.
5209                                 if (how != CLONE) {
5210                                         t.setEndBefore(endAncestor);
5211                                         t.collapse(FALSE);
5212                                 }
5213
5214                                 return frag;
5215                         }
5216
5217                         n = endAncestor.previousSibling;
5218                         while (cnt > 0) {
5219                                 sibling = n.previousSibling;
5220                                 xferNode = _traverseFullySelected(n, how);
5221
5222                                 if (frag)
5223                                         frag.insertBefore(xferNode, frag.firstChild);
5224
5225                                 --cnt;
5226                                 n = sibling;
5227                         }
5228
5229                         // Collapse to just before the endAncestor, which
5230                         // is partially selected.
5231                         if (how != CLONE) {
5232                                 t.setEndBefore(endAncestor);
5233                                 t.collapse(FALSE);
5234                         }
5235
5236                         return frag;
5237                 };
5238
5239                 function _traverseCommonEndContainer(startAncestor, how) {
5240                         var frag, startIdx, n, cnt, sibling, xferNode;
5241
5242                         if (how != DELETE)
5243                                 frag = doc.createDocumentFragment();
5244
5245                         n = _traverseLeftBoundary(startAncestor, how);
5246                         if (frag)
5247                                 frag.appendChild(n);
5248
5249                         startIdx = nodeIndex(startAncestor);
5250                         ++startIdx; // Because we already traversed it
5251
5252                         cnt = t[END_OFFSET] - startIdx;
5253                         n = startAncestor.nextSibling;
5254                         while (cnt > 0) {
5255                                 sibling = n.nextSibling;
5256                                 xferNode = _traverseFullySelected(n, how);
5257
5258                                 if (frag)
5259                                         frag.appendChild(xferNode);
5260
5261                                 --cnt;
5262                                 n = sibling;
5263                         }
5264
5265                         if (how != CLONE) {
5266                                 t.setStartAfter(startAncestor);
5267                                 t.collapse(TRUE);
5268                         }
5269
5270                         return frag;
5271                 };
5272
5273                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
5274                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
5275
5276                         if (how != DELETE)
5277                                 frag = doc.createDocumentFragment();
5278
5279                         n = _traverseLeftBoundary(startAncestor, how);
5280                         if (frag)
5281                                 frag.appendChild(n);
5282
5283                         commonParent = startAncestor.parentNode;
5284                         startOffset = nodeIndex(startAncestor);
5285                         endOffset = nodeIndex(endAncestor);
5286                         ++startOffset;
5287
5288                         cnt = endOffset - startOffset;
5289                         sibling = startAncestor.nextSibling;
5290
5291                         while (cnt > 0) {
5292                                 nextSibling = sibling.nextSibling;
5293                                 n = _traverseFullySelected(sibling, how);
5294
5295                                 if (frag)
5296                                         frag.appendChild(n);
5297
5298                                 sibling = nextSibling;
5299                                 --cnt;
5300                         }
5301
5302                         n = _traverseRightBoundary(endAncestor, how);
5303
5304                         if (frag)
5305                                 frag.appendChild(n);
5306
5307                         if (how != CLONE) {
5308                                 t.setStartAfter(startAncestor);
5309                                 t.collapse(TRUE);
5310                         }
5311
5312                         return frag;
5313                 };
5314
5315                 function _traverseRightBoundary(root, how) {
5316                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
5317
5318                         if (next == root)
5319                                 return _traverseNode(next, isFullySelected, FALSE, how);
5320
5321                         parent = next.parentNode;
5322                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);
5323
5324                         while (parent) {
5325                                 while (next) {
5326                                         prevSibling = next.previousSibling;
5327                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
5328
5329                                         if (how != DELETE)
5330                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
5331
5332                                         isFullySelected = TRUE;
5333                                         next = prevSibling;
5334                                 }
5335
5336                                 if (parent == root)
5337                                         return clonedParent;
5338
5339                                 next = parent.previousSibling;
5340                                 parent = parent.parentNode;
5341
5342                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
5343
5344                                 if (how != DELETE)
5345                                         clonedGrandParent.appendChild(clonedParent);
5346
5347                                 clonedParent = clonedGrandParent;
5348                         }
5349                 };
5350
5351                 function _traverseLeftBoundary(root, how) {
5352                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
5353
5354                         if (next == root)
5355                                 return _traverseNode(next, isFullySelected, TRUE, how);
5356
5357                         parent = next.parentNode;
5358                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);
5359
5360                         while (parent) {
5361                                 while (next) {
5362                                         nextSibling = next.nextSibling;
5363                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
5364
5365                                         if (how != DELETE)
5366                                                 clonedParent.appendChild(clonedChild);
5367
5368                                         isFullySelected = TRUE;
5369                                         next = nextSibling;
5370                                 }
5371
5372                                 if (parent == root)
5373                                         return clonedParent;
5374
5375                                 next = parent.nextSibling;
5376                                 parent = parent.parentNode;
5377
5378                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
5379
5380                                 if (how != DELETE)
5381                                         clonedGrandParent.appendChild(clonedParent);
5382
5383                                 clonedParent = clonedGrandParent;
5384                         }
5385                 };
5386
5387                 function _traverseNode(n, isFullySelected, isLeft, how) {
5388                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;
5389
5390                         if (isFullySelected)
5391                                 return _traverseFullySelected(n, how);
5392
5393                         if (n.nodeType == 3 /* TEXT_NODE */) {
5394                                 txtValue = n.nodeValue;
5395
5396                                 if (isLeft) {
5397                                         offset = t[START_OFFSET];
5398                                         newNodeValue = txtValue.substring(offset);
5399                                         oldNodeValue = txtValue.substring(0, offset);
5400                                 } else {
5401                                         offset = t[END_OFFSET];
5402                                         newNodeValue = txtValue.substring(0, offset);
5403                                         oldNodeValue = txtValue.substring(offset);
5404                                 }
5405
5406                                 if (how != CLONE)
5407                                         n.nodeValue = oldNodeValue;
5408
5409                                 if (how == DELETE)
5410                                         return;
5411
5412                                 newNode = n.cloneNode(FALSE);
5413                                 newNode.nodeValue = newNodeValue;
5414
5415                                 return newNode;
5416                         }
5417
5418                         if (how == DELETE)
5419                                 return;
5420
5421                         return n.cloneNode(FALSE);
5422                 };
5423
5424                 function _traverseFullySelected(n, how) {
5425                         if (how != DELETE)
5426                                 return how == CLONE ? n.cloneNode(TRUE) : n;
5427
5428                         n.parentNode.removeChild(n);
5429                 };
5430         };
5431
5432         ns.Range = Range;
5433 })(tinymce.dom);
5434
5435 (function() {
5436         function Selection(selection) {
5437                 var t = this, invisibleChar = '\uFEFF', range, lastIERng, dom = selection.dom, TRUE = true, FALSE = false;
5438
5439                 // Returns a W3C DOM compatible range object by using the IE Range API
5440                 function getRange() {
5441                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed;
5442
5443                         // If selection is outside the current document just return an empty range
5444                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
5445                         if (element.ownerDocument != dom.doc)
5446                                 return domRange;
5447
5448                         collapsed = selection.isCollapsed();
5449
5450                         // Handle control selection or text selection of a image
5451                         if (ieRange.item || !element.hasChildNodes()) {
5452                                 if (collapsed) {
5453                                         domRange.setStart(element, 0);
5454                                         domRange.setEnd(element, 0);
5455                                 } else {
5456                                         domRange.setStart(element.parentNode, dom.nodeIndex(element));
5457                                         domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
5458                                 }
5459
5460                                 return domRange;
5461                         }
5462
5463                         function findEndPoint(start) {
5464                                 var marker, container, offset, nodes, startIndex = 0, endIndex, index, parent, checkRng, position;
5465
5466                                 // Setup temp range and collapse it
5467                                 checkRng = ieRange.duplicate();
5468                                 checkRng.collapse(start);
5469
5470                                 // Create marker and insert it at the end of the endpoints parent
5471                                 marker = dom.create('a');
5472                                 parent = checkRng.parentElement();
5473
5474                                 // If parent doesn't have any children then set the container to that parent and the index to 0
5475                                 if (!parent.hasChildNodes()) {
5476                                         domRange[start ? 'setStart' : 'setEnd'](parent, 0);
5477                                         return;
5478                                 }
5479
5480                                 parent.appendChild(marker);
5481                                 checkRng.moveToElementText(marker);
5482                                 position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
5483                                 if (position > 0) {
5484                                         // The position is after the end of the parent element.
5485                                         // This is the case where IE puts the caret to the left edge of a table.
5486                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](parent);
5487                                         dom.remove(marker);
5488                                         return;
5489                                 }
5490
5491                                 // Setup node list and endIndex
5492                                 nodes = tinymce.grep(parent.childNodes);
5493                                 endIndex = nodes.length - 1;
5494                                 // Perform a binary search for the position
5495                                 while (startIndex <= endIndex) {
5496                                         index = Math.floor((startIndex + endIndex) / 2);
5497
5498                                         // Insert marker and check it's position relative to the selection
5499                                         parent.insertBefore(marker, nodes[index]);
5500                                         checkRng.moveToElementText(marker);
5501                                         position = ieRange.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', checkRng);
5502                                         if (position > 0) {
5503                                                 // Marker is to the right
5504                                                 startIndex = index + 1;
5505                                         } else if (position < 0) {
5506                                                 // Marker is to the left
5507                                                 endIndex = index - 1;
5508                                         } else {
5509                                                 // Maker is where we are
5510                                                 found = true;
5511                                                 break;
5512                                         }
5513                                 }
5514
5515                                 // Setup container
5516                                 container = position > 0 || index == 0 ? marker.nextSibling : marker.previousSibling;
5517
5518                                 // Handle element selection
5519                                 if (container.nodeType == 1) {
5520                                         dom.remove(marker);
5521
5522                                         // Find offset and container
5523                                         offset = dom.nodeIndex(container);
5524                                         container = container.parentNode;
5525
5526                                         // Move the offset if we are setting the end or the position is after an element
5527                                         if (!start || index > 0)
5528                                                 offset++;
5529                                 } else {
5530                                         // Calculate offset within text node
5531                                         if (position > 0 || index == 0) {
5532                                                 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
5533                                                 offset = checkRng.text.length;
5534                                         } else {
5535                                                 checkRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', ieRange);
5536                                                 offset = container.nodeValue.length - checkRng.text.length;
5537                                         }
5538
5539                                         dom.remove(marker);
5540                                 }
5541
5542                                 domRange[start ? 'setStart' : 'setEnd'](container, offset);
5543                         };
5544
5545                         // Find start point
5546                         findEndPoint(true);
5547
5548                         // Find end point if needed
5549                         if (!collapsed)
5550                                 findEndPoint();
5551
5552                         return domRange;
5553                 };
5554
5555                 this.addRange = function(rng) {
5556                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
5557
5558                         function setEndPoint(start) {
5559                                 var container, offset, marker, tmpRng, nodes;
5560
5561                                 marker = dom.create('a');
5562                                 container = start ? startContainer : endContainer;
5563                                 offset = start ? startOffset : endOffset;
5564                                 tmpRng = ieRng.duplicate();
5565
5566                                 if (container == doc || container == doc.documentElement) {
5567                                         container = body;
5568                                         offset = 0;
5569                                 }
5570
5571                                 if (container.nodeType == 3) {
5572                                         container.parentNode.insertBefore(marker, container);
5573                                         tmpRng.moveToElementText(marker);
5574                                         tmpRng.moveStart('character', offset);
5575                                         dom.remove(marker);
5576                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5577                                 } else {
5578                                         nodes = container.childNodes;
5579
5580                                         if (nodes.length) {
5581                                                 if (offset >= nodes.length) {
5582                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);
5583                                                 } else {
5584                                                         container.insertBefore(marker, nodes[offset]);
5585                                                 }
5586
5587                                                 tmpRng.moveToElementText(marker);
5588                                         } else {
5589                                                 // Empty node selection for example <div>|</div>
5590                                                 marker = doc.createTextNode(invisibleChar);
5591                                                 container.appendChild(marker);
5592                                                 tmpRng.moveToElementText(marker.parentNode);
5593                                                 tmpRng.collapse(TRUE);
5594                                         }
5595
5596                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5597                                         dom.remove(marker);
5598                                 }
5599                         }
5600
5601                         // Destroy cached range
5602                         this.destroy();
5603
5604                         // Setup some shorter versions
5605                         startContainer = rng.startContainer;
5606                         startOffset = rng.startOffset;
5607                         endContainer = rng.endContainer;
5608                         endOffset = rng.endOffset;
5609                         ieRng = body.createTextRange();
5610
5611                         // If single element selection then try making a control selection out of it
5612                         if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
5613                                 if (startOffset == endOffset - 1) {
5614                                         try {
5615                                                 ctrlRng = body.createControlRange();
5616                                                 ctrlRng.addElement(startContainer.childNodes[startOffset]);
5617                                                 ctrlRng.select();
5618                                                 return;
5619                                         } catch (ex) {
5620                                                 // Ignore
5621                                         }
5622                                 }
5623                         }
5624
5625                         // Set start/end point of selection
5626                         setEndPoint(true);
5627                         setEndPoint();
5628
5629                         // Select the new range and scroll it into view
5630                         ieRng.select();
5631                 };
5632
5633                 this.getRangeAt = function() {
5634                         // Setup new range if the cache is empty
5635                         if (!range || !tinymce.dom.RangeUtils.compareRanges(lastIERng, selection.getRng())) {
5636                                 range = getRange();
5637
5638                                 // Store away text range for next call
5639                                 lastIERng = selection.getRng();
5640                         }
5641
5642                         // IE will say that the range is equal then produce an invalid argument exception
5643                         // if you perform specific operations in a keyup event. For example Ctrl+Del.
5644                         // This hack will invalidate the range cache if the exception occurs
5645                         try {
5646                                 range.startContainer.nextSibling;
5647                         } catch (ex) {
5648                                 range = getRange();
5649                                 lastIERng = null;
5650                         }
5651
5652                         // Return cached range
5653                         return range;
5654                 };
5655
5656                 this.destroy = function() {
5657                         // Destroy cached range and last IE range to avoid memory leaks
5658                         lastIERng = range = null;
5659                 };
5660         };
5661
5662         // Expose the selection object
5663         tinymce.dom.TridentSelection = Selection;
5664 })();
5665
5666
5667 (function(tinymce) {
5668         // Shorten names
5669         var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
5670
5671         tinymce.create('tinymce.dom.EventUtils', {
5672                 EventUtils : function() {
5673                         this.inits = [];
5674                         this.events = [];
5675                 },
5676
5677                 add : function(o, n, f, s) {
5678                         var cb, t = this, el = t.events, r;
5679
5680                         if (n instanceof Array) {
5681                                 r = [];
5682
5683                                 each(n, function(n) {
5684                                         r.push(t.add(o, n, f, s));
5685                                 });
5686
5687                                 return r;
5688                         }
5689
5690                         // Handle array
5691                         if (o && o.hasOwnProperty && o instanceof Array) {
5692                                 r = [];
5693
5694                                 each(o, function(o) {
5695                                         o = DOM.get(o);
5696                                         r.push(t.add(o, n, f, s));
5697                                 });
5698
5699                                 return r;
5700                         }
5701
5702                         o = DOM.get(o);
5703
5704                         if (!o)
5705                                 return;
5706
5707                         // Setup event callback
5708                         cb = function(e) {
5709                                 // Is all events disabled
5710                                 if (t.disabled)
5711                                         return;
5712
5713                                 e = e || window.event;
5714
5715                                 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
5716                                 if (e && isIE) {
5717                                         if (!e.target)
5718                                                 e.target = e.srcElement;
5719
5720                                         // Patch in preventDefault, stopPropagation methods for W3C compatibility
5721                                         tinymce.extend(e, t._stoppers);
5722                                 }
5723
5724                                 if (!s)
5725                                         return f(e);
5726
5727                                 return f.call(s, e);
5728                         };
5729
5730                         if (n == 'unload') {
5731                                 tinymce.unloads.unshift({func : cb});
5732                                 return cb;
5733                         }
5734
5735                         if (n == 'init') {
5736                                 if (t.domLoaded)
5737                                         cb();
5738                                 else
5739                                         t.inits.push(cb);
5740
5741                                 return cb;
5742                         }
5743
5744                         // Store away listener reference
5745                         el.push({
5746                                 obj : o,
5747                                 name : n,
5748                                 func : f,
5749                                 cfunc : cb,
5750                                 scope : s
5751                         });
5752
5753                         t._add(o, n, cb);
5754
5755                         return f;
5756                 },
5757
5758                 remove : function(o, n, f) {
5759                         var t = this, a = t.events, s = false, r;
5760
5761                         // Handle array
5762                         if (o && o.hasOwnProperty && o instanceof Array) {
5763                                 r = [];
5764
5765                                 each(o, function(o) {
5766                                         o = DOM.get(o);
5767                                         r.push(t.remove(o, n, f));
5768                                 });
5769
5770                                 return r;
5771                         }
5772
5773                         o = DOM.get(o);
5774
5775                         each(a, function(e, i) {
5776                                 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
5777                                         a.splice(i, 1);
5778                                         t._remove(o, n, e.cfunc);
5779                                         s = true;
5780                                         return false;
5781                                 }
5782                         });
5783
5784                         return s;
5785                 },
5786
5787                 clear : function(o) {
5788                         var t = this, a = t.events, i, e;
5789
5790                         if (o) {
5791                                 o = DOM.get(o);
5792
5793                                 for (i = a.length - 1; i >= 0; i--) {
5794                                         e = a[i];
5795
5796                                         if (e.obj === o) {
5797                                                 t._remove(e.obj, e.name, e.cfunc);
5798                                                 e.obj = e.cfunc = null;
5799                                                 a.splice(i, 1);
5800                                         }
5801                                 }
5802                         }
5803                 },
5804
5805                 cancel : function(e) {
5806                         if (!e)
5807                                 return false;
5808
5809                         this.stop(e);
5810
5811                         return this.prevent(e);
5812                 },
5813
5814                 stop : function(e) {
5815                         if (e.stopPropagation)
5816                                 e.stopPropagation();
5817                         else
5818                                 e.cancelBubble = true;
5819
5820                         return false;
5821                 },
5822
5823                 prevent : function(e) {
5824                         if (e.preventDefault)
5825                                 e.preventDefault();
5826                         else
5827                                 e.returnValue = false;
5828
5829                         return false;
5830                 },
5831
5832                 destroy : function() {
5833                         var t = this;
5834
5835                         each(t.events, function(e, i) {
5836                                 t._remove(e.obj, e.name, e.cfunc);
5837                                 e.obj = e.cfunc = null;
5838                         });
5839
5840                         t.events = [];
5841                         t = null;
5842                 },
5843
5844                 _add : function(o, n, f) {
5845                         if (o.attachEvent)
5846                                 o.attachEvent('on' + n, f);
5847                         else if (o.addEventListener)
5848                                 o.addEventListener(n, f, false);
5849                         else
5850                                 o['on' + n] = f;
5851                 },
5852
5853                 _remove : function(o, n, f) {
5854                         if (o) {
5855                                 try {
5856                                         if (o.detachEvent)
5857                                                 o.detachEvent('on' + n, f);
5858                                         else if (o.removeEventListener)
5859                                                 o.removeEventListener(n, f, false);
5860                                         else
5861                                                 o['on' + n] = null;
5862                                 } catch (ex) {
5863                                         // Might fail with permission denined on IE so we just ignore that
5864                                 }
5865                         }
5866                 },
5867
5868                 _pageInit : function(win) {
5869                         var t = this;
5870
5871                         // Keep it from running more than once
5872                         if (t.domLoaded)
5873                                 return;
5874
5875                         t.domLoaded = true;
5876
5877                         each(t.inits, function(c) {
5878                                 c();
5879                         });
5880
5881                         t.inits = [];
5882                 },
5883
5884                 _wait : function(win) {
5885                         var t = this, doc = win.document;
5886
5887                         // No need since the document is already loaded
5888                         if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
5889                                 t.domLoaded = 1;
5890                                 return;
5891                         }
5892
5893                         // Use IE method
5894                         if (doc.attachEvent) {
5895                                 doc.attachEvent("onreadystatechange", function() {
5896                                         if (doc.readyState === "complete") {
5897                                                 doc.detachEvent("onreadystatechange", arguments.callee);
5898                                                 t._pageInit(win);
5899                                         }
5900                                 });
5901
5902                                 if (doc.documentElement.doScroll && win == win.top) {
5903                                         (function() {
5904                                                 if (t.domLoaded)
5905                                                         return;
5906
5907                                                 try {
5908                                                         // If IE is used, use the trick by Diego Perini
5909                                                         // http://javascript.nwbox.com/IEContentLoaded/
5910                                                         doc.documentElement.doScroll("left");
5911                                                 } catch (ex) {
5912                                                         setTimeout(arguments.callee, 0);
5913                                                         return;
5914                                                 }
5915
5916                                                 t._pageInit(win);
5917                                         })();
5918                                 }
5919                         } else if (doc.addEventListener) {
5920                                 t._add(win, 'DOMContentLoaded', function() {
5921                                         t._pageInit(win);
5922                                 });
5923                         }
5924
5925                         t._add(win, 'load', function() {
5926                                 t._pageInit(win);
5927                         });
5928                 },
5929
5930                 _stoppers : {
5931                         preventDefault : function() {
5932                                 this.returnValue = false;
5933                         },
5934
5935                         stopPropagation : function() {
5936                                 this.cancelBubble = true;
5937                         }
5938                 }
5939         });
5940
5941         Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
5942
5943         // Dispatch DOM content loaded event for IE and Safari
5944         Event._wait(window);
5945
5946         tinymce.addUnload(function() {
5947                 Event.destroy();
5948         });
5949 })(tinymce);
5950
5951 (function(tinymce) {
5952         tinymce.dom.Element = function(id, settings) {
5953                 var t = this, dom, el;
5954
5955                 t.settings = settings = settings || {};
5956                 t.id = id;
5957                 t.dom = dom = settings.dom || tinymce.DOM;
5958
5959                 // Only IE leaks DOM references, this is a lot faster
5960                 if (!tinymce.isIE)
5961                         el = dom.get(t.id);
5962
5963                 tinymce.each(
5964                                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
5965                                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
5966                                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
5967                                 'isHidden,setHTML,get').split(/,/)
5968                         , function(k) {
5969                                 t[k] = function() {
5970                                         var a = [id], i;
5971
5972                                         for (i = 0; i < arguments.length; i++)
5973                                                 a.push(arguments[i]);
5974
5975                                         a = dom[k].apply(dom, a);
5976                                         t.update(k);
5977
5978                                         return a;
5979                                 };
5980                 });
5981
5982                 tinymce.extend(t, {
5983                         on : function(n, f, s) {
5984                                 return tinymce.dom.Event.add(t.id, n, f, s);
5985                         },
5986
5987                         getXY : function() {
5988                                 return {
5989                                         x : parseInt(t.getStyle('left')),
5990                                         y : parseInt(t.getStyle('top'))
5991                                 };
5992                         },
5993
5994                         getSize : function() {
5995                                 var n = dom.get(t.id);
5996
5997                                 return {
5998                                         w : parseInt(t.getStyle('width') || n.clientWidth),
5999                                         h : parseInt(t.getStyle('height') || n.clientHeight)
6000                                 };
6001                         },
6002
6003                         moveTo : function(x, y) {
6004                                 t.setStyles({left : x, top : y});
6005                         },
6006
6007                         moveBy : function(x, y) {
6008                                 var p = t.getXY();
6009
6010                                 t.moveTo(p.x + x, p.y + y);
6011                         },
6012
6013                         resizeTo : function(w, h) {
6014                                 t.setStyles({width : w, height : h});
6015                         },
6016
6017                         resizeBy : function(w, h) {
6018                                 var s = t.getSize();
6019
6020                                 t.resizeTo(s.w + w, s.h + h);
6021                         },
6022
6023                         update : function(k) {
6024                                 var b;
6025
6026                                 if (tinymce.isIE6 && settings.blocker) {
6027                                         k = k || '';
6028
6029                                         // Ignore getters
6030                                         if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
6031                                                 return;
6032
6033                                         // Remove blocker on remove
6034                                         if (k == 'remove') {
6035                                                 dom.remove(t.blocker);
6036                                                 return;
6037                                         }
6038
6039                                         if (!t.blocker) {
6040                                                 t.blocker = dom.uniqueId();
6041                                                 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
6042                                                 dom.setStyle(b, 'opacity', 0);
6043                                         } else
6044                                                 b = dom.get(t.blocker);
6045
6046                                         dom.setStyles(b, {
6047                                                 left : t.getStyle('left', 1),
6048                                                 top : t.getStyle('top', 1),
6049                                                 width : t.getStyle('width', 1),
6050                                                 height : t.getStyle('height', 1),
6051                                                 display : t.getStyle('display', 1),
6052                                                 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
6053                                         });
6054                                 }
6055                         }
6056                 });
6057         };
6058 })(tinymce);
6059
6060 (function(tinymce) {
6061         function trimNl(s) {
6062                 return s.replace(/[\n\r]+/g, '');
6063         };
6064
6065         // Shorten names
6066         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
6067
6068         tinymce.create('tinymce.dom.Selection', {
6069                 Selection : function(dom, win, serializer) {
6070                         var t = this;
6071
6072                         t.dom = dom;
6073                         t.win = win;
6074                         t.serializer = serializer;
6075
6076                         // Add events
6077                         each([
6078                                 'onBeforeSetContent',
6079
6080                                 'onBeforeGetContent',
6081
6082                                 'onSetContent',
6083
6084                                 'onGetContent'
6085                         ], function(e) {
6086                                 t[e] = new tinymce.util.Dispatcher(t);
6087                         });
6088
6089                         // No W3C Range support
6090                         if (!t.win.getSelection)
6091                                 t.tridentSel = new tinymce.dom.TridentSelection(t);
6092
6093                         if (tinymce.isIE && dom.boxModel)
6094                                 this._fixIESelection();
6095
6096                         // Prevent leaks
6097                         tinymce.addUnload(t.destroy, t);
6098                 },
6099
6100                 getContent : function(s) {
6101                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
6102
6103                         s = s || {};
6104                         wb = wa = '';
6105                         s.get = true;
6106                         s.format = s.format || 'html';
6107                         t.onBeforeGetContent.dispatch(t, s);
6108
6109                         if (s.format == 'text')
6110                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
6111
6112                         if (r.cloneContents) {
6113                                 n = r.cloneContents();
6114
6115                                 if (n)
6116                                         e.appendChild(n);
6117                         } else if (is(r.item) || is(r.htmlText))
6118                                 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
6119                         else
6120                                 e.innerHTML = r.toString();
6121
6122                         // Keep whitespace before and after
6123                         if (/^\s/.test(e.innerHTML))
6124                                 wb = ' ';
6125
6126                         if (/\s+$/.test(e.innerHTML))
6127                                 wa = ' ';
6128
6129                         s.getInner = true;
6130
6131                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
6132                         t.onGetContent.dispatch(t, s);
6133
6134                         return s.content;
6135                 },
6136
6137                 setContent : function(content, args) {
6138                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
6139
6140                         args = args || {format : 'html'};
6141                         args.set = true;
6142                         content = args.content = content;
6143
6144                         // Dispatch before set content event
6145                         if (!args.no_events)
6146                                 self.onBeforeSetContent.dispatch(self, args);
6147
6148                         content = args.content;
6149
6150                         if (rng.insertNode) {
6151                                 // Make caret marker since insertNode places the caret in the beginning of text after insert
6152                                 content += '<span id="__caret">_</span>';
6153
6154                                 // Delete and insert new node
6155                                 if (rng.startContainer == doc && rng.endContainer == doc) {
6156                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
6157                                         doc.body.innerHTML = content;
6158                                 } else {
6159                                         rng.deleteContents();
6160
6161                                         if (doc.body.childNodes.length == 0) {
6162                                                 doc.body.innerHTML = content;
6163                                         } else {
6164                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges
6165                                                 if (rng.createContextualFragment) {
6166                                                         rng.insertNode(rng.createContextualFragment(content));
6167                                                 } else {
6168                                                         // Fake createContextualFragment call in IE 9
6169                                                         frag = doc.createDocumentFragment();
6170                                                         temp = doc.createElement('div');
6171
6172                                                         frag.appendChild(temp);
6173                                                         temp.outerHTML = content;
6174
6175                                                         rng.insertNode(frag);
6176                                                 }
6177                                         }
6178                                 }
6179
6180                                 // Move to caret marker
6181                                 caretNode = self.dom.get('__caret');
6182
6183                                 // Make sure we wrap it compleatly, Opera fails with a simple select call
6184                                 rng = doc.createRange();
6185                                 rng.setStartBefore(caretNode);
6186                                 rng.setEndBefore(caretNode);
6187                                 self.setRng(rng);
6188
6189                                 // Remove the caret position
6190                                 self.dom.remove('__caret');
6191                                 self.setRng(rng);
6192                         } else {
6193                                 if (rng.item) {
6194                                         // Delete content and get caret text selection
6195                                         doc.execCommand('Delete', false, null);
6196                                         rng = self.getRng();
6197                                 }
6198
6199                                 rng.pasteHTML(content);
6200                         }
6201
6202                         // Dispatch set content event
6203                         if (!args.no_events)
6204                                 self.onSetContent.dispatch(self, args);
6205                 },
6206
6207                 getStart : function() {
6208                         var rng = this.getRng(), startElement, parentElement, checkRng, node;
6209
6210                         if (rng.duplicate || rng.item) {
6211                                 // Control selection, return first item
6212                                 if (rng.item)
6213                                         return rng.item(0);
6214
6215                                 // Get start element
6216                                 checkRng = rng.duplicate();
6217                                 checkRng.collapse(1);
6218                                 startElement = checkRng.parentElement();
6219
6220                                 // Check if range parent is inside the start element, then return the inner parent element
6221                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
6222                                 parentElement = node = rng.parentElement();
6223                                 while (node = node.parentNode) {
6224                                         if (node == startElement) {
6225                                                 startElement = parentElement;
6226                                                 break;
6227                                         }
6228                                 }
6229
6230                                 return startElement;
6231                         } else {
6232                                 startElement = rng.startContainer;
6233
6234                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())
6235                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
6236
6237                                 if (startElement && startElement.nodeType == 3)
6238                                         return startElement.parentNode;
6239
6240                                 return startElement;
6241                         }
6242                 },
6243
6244                 getEnd : function() {
6245                         var t = this, r = t.getRng(), e, eo;
6246
6247                         if (r.duplicate || r.item) {
6248                                 if (r.item)
6249                                         return r.item(0);
6250
6251                                 r = r.duplicate();
6252                                 r.collapse(0);
6253                                 e = r.parentElement();
6254
6255                                 if (e && e.nodeName == 'BODY')
6256                                         return e.lastChild || e;
6257
6258                                 return e;
6259                         } else {
6260                                 e = r.endContainer;
6261                                 eo = r.endOffset;
6262
6263                                 if (e.nodeType == 1 && e.hasChildNodes())
6264                                         e = e.childNodes[eo > 0 ? eo - 1 : eo];
6265
6266                                 if (e && e.nodeType == 3)
6267                                         return e.parentNode;
6268
6269                                 return e;
6270                         }
6271                 },
6272
6273                 getBookmark : function(type, normalized) {
6274                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
6275
6276                         function findIndex(name, element) {
6277                                 var index = 0;
6278
6279                                 each(dom.select(name), function(node, i) {
6280                                         if (node == element)
6281                                                 index = i;
6282                                 });
6283
6284                                 return index;
6285                         };
6286
6287                         if (type == 2) {
6288                                 function getLocation() {
6289                                         var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
6290
6291                                         function getPoint(rng, start) {
6292                                                 var container = rng[start ? 'startContainer' : 'endContainer'],
6293                                                         offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
6294
6295                                                 if (container.nodeType == 3) {
6296                                                         if (normalized) {
6297                                                                 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
6298                                                                         offset += node.nodeValue.length;
6299                                                         }
6300
6301                                                         point.push(offset);
6302                                                 } else {
6303                                                         childNodes = container.childNodes;
6304
6305                                                         if (offset >= childNodes.length && childNodes.length) {
6306                                                                 after = 1;
6307                                                                 offset = Math.max(0, childNodes.length - 1);
6308                                                         }
6309
6310                                                         point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
6311                                                 }
6312
6313                                                 for (; container && container != root; container = container.parentNode)
6314                                                         point.push(t.dom.nodeIndex(container, normalized));
6315
6316                                                 return point;
6317                                         };
6318
6319                                         bookmark.start = getPoint(rng, true);
6320
6321                                         if (!t.isCollapsed())
6322                                                 bookmark.end = getPoint(rng);
6323
6324                                         return bookmark;
6325                                 };
6326
6327                                 return getLocation();
6328                         }
6329
6330                         // Handle simple range
6331                         if (type)
6332                                 return {rng : t.getRng()};
6333
6334                         rng = t.getRng();
6335                         id = dom.uniqueId();
6336                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();
6337                         styles = 'overflow:hidden;line-height:0px';
6338
6339                         // Explorer method
6340                         if (rng.duplicate || rng.item) {
6341                                 // Text selection
6342                                 if (!rng.item) {
6343                                         rng2 = rng.duplicate();
6344
6345                                         try {
6346                                                 // Insert start marker
6347                                                 rng.collapse();
6348                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
6349
6350                                                 // Insert end marker
6351                                                 if (!collapsed) {
6352                                                         rng2.collapse(false);
6353
6354                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
6355                                                         rng.moveToElementText(rng2.parentElement());
6356                                                         if (rng.compareEndPoints('StartToEnd', rng2) == 0)
6357                                                                 rng2.move('character', -1);
6358
6359                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
6360                                                 }
6361                                         } catch (ex) {
6362                                                 // IE might throw unspecified error so lets ignore it
6363                                                 return null;
6364                                         }
6365                                 } else {
6366                                         // Control selection
6367                                         element = rng.item(0);
6368                                         name = element.nodeName;
6369
6370                                         return {name : name, index : findIndex(name, element)};
6371                                 }
6372                         } else {
6373                                 element = t.getNode();
6374                                 name = element.nodeName;
6375                                 if (name == 'IMG')
6376                                         return {name : name, index : findIndex(name, element)};
6377
6378                                 // W3C method
6379                                 rng2 = rng.cloneRange();
6380
6381                                 // Insert end marker
6382                                 if (!collapsed) {
6383                                         rng2.collapse(false);
6384                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
6385                                 }
6386
6387                                 rng.collapse(true);
6388                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
6389                         }
6390
6391                         t.moveToBookmark({id : id, keep : 1});
6392
6393                         return {id : id};
6394                 },
6395
6396                 moveToBookmark : function(bookmark) {
6397                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
6398
6399                         // Clear selection cache
6400                         if (t.tridentSel)
6401                                 t.tridentSel.destroy();
6402
6403                         if (bookmark) {
6404                                 if (bookmark.start) {
6405                                         rng = dom.createRng();
6406                                         root = dom.getRoot();
6407
6408                                         function setEndPoint(start) {
6409                                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
6410
6411                                                 if (point) {
6412                                                         offset = point[0];
6413
6414                                                         // Find container node
6415                                                         for (node = root, i = point.length - 1; i >= 1; i--) {
6416                                                                 children = node.childNodes;
6417
6418                                                                 if (point[i] > children.length - 1)
6419                                                                         return;
6420
6421                                                                 node = children[point[i]];
6422                                                         }
6423
6424                                                         // Move text offset to best suitable location
6425                                                         if (node.nodeType === 3)
6426                                                                 offset = Math.min(point[0], node.nodeValue.length);
6427
6428                                                         // Move element offset to best suitable location
6429                                                         if (node.nodeType === 1)
6430                                                                 offset = Math.min(point[0], node.childNodes.length);
6431
6432                                                         // Set offset within container node
6433                                                         if (start)
6434                                                                 rng.setStart(node, offset);
6435                                                         else
6436                                                                 rng.setEnd(node, offset);
6437                                                 }
6438
6439                                                 return true;
6440                                         };
6441
6442                                         if (setEndPoint(true) && setEndPoint()) {
6443                                                 t.setRng(rng);
6444                                         }
6445                                 } else if (bookmark.id) {
6446                                         function restoreEndPoint(suffix) {
6447                                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
6448
6449                                                 if (marker) {
6450                                                         node = marker.parentNode;
6451
6452                                                         if (suffix == 'start') {
6453                                                                 if (!keep) {
6454                                                                         idx = dom.nodeIndex(marker);
6455                                                                 } else {
6456                                                                         node = marker.firstChild;
6457                                                                         idx = 1;
6458                                                                 }
6459
6460                                                                 startContainer = endContainer = node;
6461                                                                 startOffset = endOffset = idx;
6462                                                         } else {
6463                                                                 if (!keep) {
6464                                                                         idx = dom.nodeIndex(marker);
6465                                                                 } else {
6466                                                                         node = marker.firstChild;
6467                                                                         idx = 1;
6468                                                                 }
6469
6470                                                                 endContainer = node;
6471                                                                 endOffset = idx;
6472                                                         }
6473
6474                                                         if (!keep) {
6475                                                                 prev = marker.previousSibling;
6476                                                                 next = marker.nextSibling;
6477
6478                                                                 // Remove all marker text nodes
6479                                                                 each(tinymce.grep(marker.childNodes), function(node) {
6480                                                                         if (node.nodeType == 3)
6481                                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
6482                                                                 });
6483
6484                                                                 // Remove marker but keep children if for example contents where inserted into the marker
6485                                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
6486                                                                 while (marker = dom.get(bookmark.id + '_' + suffix))
6487                                                                         dom.remove(marker, 1);
6488
6489                                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
6490                                                                 // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
6491                                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
6492                                                                         idx = prev.nodeValue.length;
6493                                                                         prev.appendData(next.nodeValue);
6494                                                                         dom.remove(next);
6495
6496                                                                         if (suffix == 'start') {
6497                                                                                 startContainer = endContainer = prev;
6498                                                                                 startOffset = endOffset = idx;
6499                                                                         } else {
6500                                                                                 endContainer = prev;
6501                                                                                 endOffset = idx;
6502                                                                         }
6503                                                                 }
6504                                                         }
6505                                                 }
6506                                         };
6507
6508                                         function addBogus(node) {
6509                                                 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
6510                                                 if (dom.isBlock(node) && !node.innerHTML)
6511                                                         node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
6512
6513                                                 return node;
6514                                         };
6515
6516                                         // Restore start/end points
6517                                         restoreEndPoint('start');
6518                                         restoreEndPoint('end');
6519
6520                                         if (startContainer) {
6521                                                 rng = dom.createRng();
6522                                                 rng.setStart(addBogus(startContainer), startOffset);
6523                                                 rng.setEnd(addBogus(endContainer), endOffset);
6524                                                 t.setRng(rng);
6525                                         }
6526                                 } else if (bookmark.name) {
6527                                         t.select(dom.select(bookmark.name)[bookmark.index]);
6528                                 } else if (bookmark.rng)
6529                                         t.setRng(bookmark.rng);
6530                         }
6531                 },
6532
6533                 select : function(node, content) {
6534                         var t = this, dom = t.dom, rng = dom.createRng(), idx;
6535
6536                         if (node) {
6537                                 idx = dom.nodeIndex(node);
6538                                 rng.setStart(node.parentNode, idx);
6539                                 rng.setEnd(node.parentNode, idx + 1);
6540
6541                                 // Find first/last text node or BR element
6542                                 if (content) {
6543                                         function setPoint(node, start) {
6544                                                 var walker = new tinymce.dom.TreeWalker(node, node);
6545
6546                                                 do {
6547                                                         // Text node
6548                                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
6549                                                                 if (start)
6550                                                                         rng.setStart(node, 0);
6551                                                                 else
6552                                                                         rng.setEnd(node, node.nodeValue.length);
6553
6554                                                                 return;
6555                                                         }
6556
6557                                                         // BR element
6558                                                         if (node.nodeName == 'BR') {
6559                                                                 if (start)
6560                                                                         rng.setStartBefore(node);
6561                                                                 else
6562                                                                         rng.setEndBefore(node);
6563
6564                                                                 return;
6565                                                         }
6566                                                 } while (node = (start ? walker.next() : walker.prev()));
6567                                         };
6568
6569                                         setPoint(node, 1);
6570                                         setPoint(node);
6571                                 }
6572
6573                                 t.setRng(rng);
6574                         }
6575
6576                         return node;
6577                 },
6578
6579                 isCollapsed : function() {
6580                         var t = this, r = t.getRng(), s = t.getSel();
6581
6582                         if (!r || r.item)
6583                                 return false;
6584
6585                         if (r.compareEndPoints)
6586                                 return r.compareEndPoints('StartToEnd', r) === 0;
6587
6588                         return !s || r.collapsed;
6589                 },
6590
6591                 collapse : function(to_start) {
6592                         var self = this, rng = self.getRng(), node;
6593
6594                         // Control range on IE
6595                         if (rng.item) {
6596                                 node = rng.item(0);
6597                                 rng = self.win.document.body.createTextRange();
6598                                 rng.moveToElementText(node);
6599                         }
6600
6601                         rng.collapse(!!to_start);
6602                         self.setRng(rng);
6603                 },
6604
6605                 getSel : function() {
6606                         var t = this, w = this.win;
6607
6608                         return w.getSelection ? w.getSelection() : w.document.selection;
6609                 },
6610
6611                 getRng : function(w3c) {
6612                         var t = this, s, r, elm, doc = t.win.document;
6613
6614                         // Found tridentSel object then we need to use that one
6615                         if (w3c && t.tridentSel)
6616                                 return t.tridentSel.getRangeAt(0);
6617
6618                         try {
6619                                 if (s = t.getSel())
6620                                         r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
6621                         } catch (ex) {
6622                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
6623                         }
6624
6625                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
6626                         if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
6627                                 elm = doc.selection.createRange().item(0);
6628                                 r = doc.createRange();
6629                                 r.setStartBefore(elm);
6630                                 r.setEndAfter(elm);
6631                         }
6632
6633                         // No range found then create an empty one
6634                         // This can occur when the editor is placed in a hidden container element on Gecko
6635                         // Or on IE when there was an exception
6636                         if (!r)
6637                                 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
6638
6639                         if (t.selectedRange && t.explicitRange) {
6640                                 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
6641                                         // Safari, Opera and Chrome only ever select text which causes the range to change.
6642                                         // This lets us use the originally set range if the selection hasn't been changed by the user.
6643                                         r = t.explicitRange;
6644                                 } else {
6645                                         t.selectedRange = null;
6646                                         t.explicitRange = null;
6647                                 }
6648                         }
6649
6650                         return r;
6651                 },
6652
6653                 setRng : function(r) {
6654                         var s, t = this;
6655                         
6656                         if (!t.tridentSel) {
6657                                 s = t.getSel();
6658
6659                                 if (s) {
6660                                         t.explicitRange = r;
6661
6662                                         try {
6663                                                 s.removeAllRanges();
6664                                         } catch (ex) {
6665                                                 // IE9 might throw errors here don't know why
6666                                         }
6667
6668                                         s.addRange(r);
6669                                         t.selectedRange = s.getRangeAt(0);
6670                                 }
6671                         } else {
6672                                 // Is W3C Range
6673                                 if (r.cloneRange) {
6674                                         t.tridentSel.addRange(r);
6675                                         return;
6676                                 }
6677
6678                                 // Is IE specific range
6679                                 try {
6680                                         r.select();
6681                                 } catch (ex) {
6682                                         // Needed for some odd IE bug #1843306
6683                                 }
6684                         }
6685                 },
6686
6687                 setNode : function(n) {
6688                         var t = this;
6689
6690                         t.setContent(t.dom.getOuterHTML(n));
6691
6692                         return n;
6693                 },
6694
6695                 getNode : function() {
6696                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
6697
6698                         // Range maybe lost after the editor is made visible again
6699                         if (!rng)
6700                                 return t.dom.getRoot();
6701
6702                         if (rng.setStart) {
6703                                 elm = rng.commonAncestorContainer;
6704
6705                                 // Handle selection a image or other control like element such as anchors
6706                                 if (!rng.collapsed) {
6707                                         if (rng.startContainer == rng.endContainer) {
6708                                                 if (rng.endOffset - rng.startOffset < 2) {
6709                                                         if (rng.startContainer.hasChildNodes())
6710                                                                 elm = rng.startContainer.childNodes[rng.startOffset];
6711                                                 }
6712                                         }
6713
6714                                         // If the anchor node is a element instead of a text node then return this element
6715                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 
6716                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];
6717
6718                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
6719                                         // This happens when you double click an underlined word in FireFox.
6720                                         if (start.nodeType === 3 && end.nodeType === 3) {
6721                                                 function skipEmptyTextNodes(n, forwards) {
6722                                                         var orig = n;
6723                                                         while (n && n.nodeType === 3 && n.length === 0) {
6724                                                                 n = forwards ? n.nextSibling : n.previousSibling;
6725                                                         }
6726                                                         return n || orig;
6727                                                 }
6728                                                 if (start.length === rng.startOffset) {
6729                                                         start = skipEmptyTextNodes(start.nextSibling, true);
6730                                                 } else {
6731                                                         start = start.parentNode;
6732                                                 }
6733                                                 if (rng.endOffset === 0) {
6734                                                         end = skipEmptyTextNodes(end.previousSibling, false);
6735                                                 } else {
6736                                                         end = end.parentNode;
6737                                                 }
6738
6739                                                 if (start && start === end)
6740                                                         return start;
6741                                         }
6742                                 }
6743
6744                                 if (elm && elm.nodeType == 3)
6745                                         return elm.parentNode;
6746
6747                                 return elm;
6748                         }
6749
6750                         return rng.item ? rng.item(0) : rng.parentElement();
6751                 },
6752
6753                 getSelectedBlocks : function(st, en) {
6754                         var t = this, dom = t.dom, sb, eb, n, bl = [];
6755
6756                         sb = dom.getParent(st || t.getStart(), dom.isBlock);
6757                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);
6758
6759                         if (sb)
6760                                 bl.push(sb);
6761
6762                         if (sb && eb && sb != eb) {
6763                                 n = sb;
6764
6765                                 while ((n = n.nextSibling) && n != eb) {
6766                                         if (dom.isBlock(n))
6767                                                 bl.push(n);
6768                                 }
6769                         }
6770
6771                         if (eb && sb != eb)
6772                                 bl.push(eb);
6773
6774                         return bl;
6775                 },
6776
6777                 destroy : function(s) {
6778                         var t = this;
6779
6780                         t.win = null;
6781
6782                         if (t.tridentSel)
6783                                 t.tridentSel.destroy();
6784
6785                         // Manual destroy then remove unload handler
6786                         if (!s)
6787                                 tinymce.removeUnload(t.destroy);
6788                 },
6789
6790                 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
6791                 _fixIESelection : function() {
6792                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
6793
6794                         // Make HTML element unselectable since we are going to handle selection by hand
6795                         doc.documentElement.unselectable = true;
6796
6797                         // Return range from point or null if it failed
6798                         function rngFromPoint(x, y) {
6799                                 var rng = body.createTextRange();
6800
6801                                 try {
6802                                         rng.moveToPoint(x, y);
6803                                 } catch (ex) {
6804                                         // IE sometimes throws and exception, so lets just ignore it
6805                                         rng = null;
6806                                 }
6807
6808                                 return rng;
6809                         };
6810
6811                         // Fires while the selection is changing
6812                         function selectionChange(e) {
6813                                 var pointRng;
6814
6815                                 // Check if the button is down or not
6816                                 if (e.button) {
6817                                         // Create range from mouse position
6818                                         pointRng = rngFromPoint(e.x, e.y);
6819
6820                                         if (pointRng) {
6821                                                 // Check if pointRange is before/after selection then change the endPoint
6822                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
6823                                                         pointRng.setEndPoint('StartToStart', startRng);
6824                                                 else
6825                                                         pointRng.setEndPoint('EndToEnd', startRng);
6826
6827                                                 pointRng.select();
6828                                         }
6829                                 } else
6830                                         endSelection();
6831                         }
6832
6833                         // Removes listeners
6834                         function endSelection() {
6835                                 var rng = doc.selection.createRange();
6836
6837                                 // If the range is collapsed then use the last start range
6838                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
6839                                         startRng.select();
6840
6841                                 dom.unbind(doc, 'mouseup', endSelection);
6842                                 dom.unbind(doc, 'mousemove', selectionChange);
6843                                 startRng = started = 0;
6844                         };
6845
6846                         // Detect when user selects outside BODY
6847                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
6848                                 if (e.target.nodeName === 'HTML') {
6849                                         if (started)
6850                                                 endSelection();
6851
6852                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
6853                                         htmlElm = doc.documentElement;
6854                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)
6855                                                 return;
6856
6857                                         started = 1;
6858                                         // Setup start position
6859                                         startRng = rngFromPoint(e.x, e.y);
6860                                         if (startRng) {
6861                                                 // Listen for selection change events
6862                                                 dom.bind(doc, 'mouseup', endSelection);
6863                                                 dom.bind(doc, 'mousemove', selectionChange);
6864
6865                                                 dom.win.focus();
6866                                                 startRng.select();
6867                                         }
6868                                 }
6869                         });
6870                 }
6871         });
6872 })(tinymce);
6873
6874 (function(tinymce) {
6875         tinymce.dom.Serializer = function(settings, dom, schema) {
6876                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
6877
6878                 // Support the old apply_source_formatting option
6879                 if (!settings.apply_source_formatting)
6880                         settings.indent = false;
6881
6882                 settings.remove_trailing_brs = true;
6883
6884                 // Default DOM and Schema if they are undefined
6885                 dom = dom || tinymce.DOM;
6886                 schema = schema || new tinymce.html.Schema(settings);
6887                 settings.entity_encoding = settings.entity_encoding || 'named';
6888
6889                 onPreProcess = new tinymce.util.Dispatcher(self);
6890
6891                 onPostProcess = new tinymce.util.Dispatcher(self);
6892
6893                 htmlParser = new tinymce.html.DomParser(settings, schema);
6894
6895                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
6896                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
6897                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
6898
6899                         while (i--) {
6900                                 node = nodes[i];
6901
6902                                 value = node.attributes.map[internalName];
6903                                 if (value !== undef) {
6904                                         // Set external name to internal value and remove internal
6905                                         node.attr(name, value.length > 0 ? value : null);
6906                                         node.attr(internalName, null);
6907                                 } else {
6908                                         // No internal attribute found then convert the value we have in the DOM
6909                                         value = node.attributes.map[name];
6910
6911                                         if (name === "style")
6912                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);
6913                                         else if (urlConverter)
6914                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);
6915
6916                                         node.attr(name, value.length > 0 ? value : null);
6917                                 }
6918                         }
6919                 });
6920
6921                 // Remove internal classes mceItem<..>
6922                 htmlParser.addAttributeFilter('class', function(nodes, name) {
6923                         var i = nodes.length, node, value;
6924
6925                         while (i--) {
6926                                 node = nodes[i];
6927                                 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
6928                                 node.attr('class', value.length > 0 ? value : null);
6929                         }
6930                 });
6931
6932                 // Remove bookmark elements
6933                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
6934                         var i = nodes.length, node;
6935
6936                         while (i--) {
6937                                 node = nodes[i];
6938
6939                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
6940                                         node.remove();
6941                         }
6942                 });
6943
6944                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
6945                 htmlParser.addNodeFilter('script,style', function(nodes, name) {
6946                         var i = nodes.length, node, value;
6947
6948                         function trim(value) {
6949                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
6950                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')
6951                                                 .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
6952                                                 .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
6953                         };
6954
6955                         while (i--) {
6956                                 node = nodes[i];
6957                                 value = node.firstChild ? node.firstChild.value : '';
6958
6959                                 if (name === "script") {
6960                                         // Remove mce- prefix from script elements
6961                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
6962
6963                                         if (value.length > 0)
6964                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
6965                                 } else {
6966                                         if (value.length > 0)
6967                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
6968                                 }
6969                         }
6970                 });
6971
6972                 // Convert comments to cdata and handle protected comments
6973                 htmlParser.addNodeFilter('#comment', function(nodes, name) {
6974                         var i = nodes.length, node;
6975
6976                         while (i--) {
6977                                 node = nodes[i];
6978
6979                                 if (node.value.indexOf('[CDATA[') === 0) {
6980                                         node.name = '#cdata';
6981                                         node.type = 4;
6982                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
6983                                 } else if (node.value.indexOf('mce:protected ') === 0) {
6984                                         node.name = "#text";
6985                                         node.type = 3;
6986                                         node.raw = true;
6987                                         node.value = unescape(node.value).substr(14);
6988                                 }
6989                         }
6990                 });
6991
6992                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
6993                         var i = nodes.length, node;
6994
6995                         while (i--) {
6996                                 node = nodes[i];
6997                                 if (node.type === 7)
6998                                         node.remove();
6999                                 else if (node.type === 1) {
7000                                         if (name === "input" && !("type" in node.attributes.map))
7001                                                 node.attr('type', 'text');
7002                                 }
7003                         }
7004                 });
7005
7006                 // Fix list elements, TODO: Replace this later
7007                 if (settings.fix_list_elements) {
7008                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
7009                                 var i = nodes.length, node, parentNode;
7010
7011                                 while (i--) {
7012                                         node = nodes[i];
7013                                         parentNode = node.parent;
7014
7015                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {
7016                                                 if (node.prev && node.prev.name === 'li') {
7017                                                         node.prev.append(node);
7018                                                 }
7019                                         }
7020                                 }
7021                         });
7022                 }
7023
7024                 // Remove internal data attributes
7025                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
7026                         var i = nodes.length;
7027
7028                         while (i--) {
7029                                 nodes[i].attr(name, null);
7030                         }
7031                 });
7032
7033                 // Return public methods
7034                 return {
7035                         schema : schema,
7036
7037                         addNodeFilter : htmlParser.addNodeFilter,
7038
7039                         addAttributeFilter : htmlParser.addAttributeFilter,
7040
7041                         onPreProcess : onPreProcess,
7042
7043                         onPostProcess : onPostProcess,
7044
7045                         serialize : function(node, args) {
7046                                 var impl, doc, oldDoc, htmlSerializer, content;
7047
7048                                 // Explorer won't clone contents of script and style and the
7049                                 // selected index of select elements are cleared on a clone operation.
7050                                 if (isIE && dom.select('script,style,select').length > 0) {
7051                                         content = node.innerHTML;
7052                                         node = node.cloneNode(false);
7053                                         dom.setHTML(node, content);
7054                                 } else
7055                                         node = node.cloneNode(true);
7056
7057                                 // Nodes needs to be attached to something in WebKit/Opera
7058                                 // Older builds of Opera crashes if you attach the node to an document created dynamically
7059                                 // and since we can't feature detect a crash we need to sniff the acutal build number
7060                                 // This fix will make DOM ranges and make Sizzle happy!
7061                                 impl = node.ownerDocument.implementation;
7062                                 if (impl.createHTMLDocument) {
7063                                         // Create an empty HTML document
7064                                         doc = impl.createHTMLDocument("");
7065
7066                                         // Add the element or it's children if it's a body element to the new document
7067                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
7068                                                 doc.body.appendChild(doc.importNode(node, true));
7069                                         });
7070
7071                                         // Grab first child or body element for serialization
7072                                         if (node.nodeName != 'BODY')
7073                                                 node = doc.body.firstChild;
7074                                         else
7075                                                 node = doc.body;
7076
7077                                         // set the new document in DOMUtils so createElement etc works
7078                                         oldDoc = dom.doc;
7079                                         dom.doc = doc;
7080                                 }
7081
7082                                 args = args || {};
7083                                 args.format = args.format || 'html';
7084
7085                                 // Pre process
7086                                 if (!args.no_events) {
7087                                         args.node = node;
7088                                         onPreProcess.dispatch(self, args);
7089                                 }
7090
7091                                 // Setup serializer
7092                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);
7093
7094                                 // Parse and serialize HTML
7095                                 args.content = htmlSerializer.serialize(
7096                                         htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
7097                                 );
7098
7099                                 // Replace all BOM characters for now until we can find a better solution
7100                                 if (!args.cleanup)
7101                                         args.content = args.content.replace(/\uFEFF/g, '');
7102
7103                                 // Post process
7104                                 if (!args.no_events)
7105                                         onPostProcess.dispatch(self, args);
7106
7107                                 // Restore the old document if it was changed
7108                                 if (oldDoc)
7109                                         dom.doc = oldDoc;
7110
7111                                 args.node = null;
7112
7113                                 return args.content;
7114                         },
7115
7116                         addRules : function(rules) {
7117                                 schema.addValidElements(rules);
7118                         },
7119
7120                         setRules : function(rules) {
7121                                 schema.setValidElements(rules);
7122                         }
7123                 };
7124         };
7125 })(tinymce);
7126 (function(tinymce) {
7127         tinymce.dom.ScriptLoader = function(settings) {
7128                 var QUEUED = 0,
7129                         LOADING = 1,
7130                         LOADED = 2,
7131                         states = {},
7132                         queue = [],
7133                         scriptLoadedCallbacks = {},
7134                         queueLoadedCallbacks = [],
7135                         loading = 0,
7136                         undefined;
7137
7138                 function loadScript(url, callback) {
7139                         var t = this, dom = tinymce.DOM, elm, uri, loc, id;
7140
7141                         // Execute callback when script is loaded
7142                         function done() {
7143                                 dom.remove(id);
7144
7145                                 if (elm)
7146                                         elm.onreadystatechange = elm.onload = elm = null;
7147
7148                                 callback();
7149                         };
7150                         
7151                         function error() {
7152                                 // Report the error so it's easier for people to spot loading errors
7153                                 if (typeof(console) !== "undefined" && console.log)
7154                                         console.log("Failed to load: " + url);
7155
7156                                 // We can't mark it as done if there is a load error since
7157                                 // A) We don't want to produce 404 errors on the server and
7158                                 // B) the onerror event won't fire on all browsers.
7159                                 // done();
7160                         };
7161
7162                         id = dom.uniqueId();
7163
7164                         if (tinymce.isIE6) {
7165                                 uri = new tinymce.util.URI(url);
7166                                 loc = location;
7167
7168                                 // If script is from same domain and we
7169                                 // use IE 6 then use XHR since it's more reliable
7170                                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
7171                                         tinymce.util.XHR.send({
7172                                                 url : tinymce._addVer(uri.getURI()),
7173                                                 success : function(content) {
7174                                                         // Create new temp script element
7175                                                         var script = dom.create('script', {
7176                                                                 type : 'text/javascript'
7177                                                         });
7178
7179                                                         // Evaluate script in global scope
7180                                                         script.text = content;
7181                                                         document.getElementsByTagName('head')[0].appendChild(script);
7182                                                         dom.remove(script);
7183
7184                                                         done();
7185                                                 },
7186                                                 
7187                                                 error : error
7188                                         });
7189
7190                                         return;
7191                                 }
7192                         }
7193
7194                         // Create new script element
7195                         elm = dom.create('script', {
7196                                 id : id,
7197                                 type : 'text/javascript',
7198                                 src : tinymce._addVer(url)
7199                         });
7200
7201                         // Add onload listener for non IE browsers since IE9
7202                         // fires onload event before the script is parsed and executed
7203                         if (!tinymce.isIE)
7204                                 elm.onload = done;
7205
7206                         // Add onerror event will get fired on some browsers but not all of them
7207                         elm.onerror = error;
7208
7209                         // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
7210                         if (!tinymce.isOpera) {
7211                                 elm.onreadystatechange = function() {
7212                                         var state = elm.readyState;
7213
7214                                         // Loaded state is passed on IE 6 however there
7215                                         // are known issues with this method but we can't use
7216                                         // XHR in a cross domain loading
7217                                         if (state == 'complete' || state == 'loaded')
7218                                                 done();
7219                                 };
7220                         }
7221
7222                         // Most browsers support this feature so we report errors
7223                         // for those at least to help users track their missing plugins etc
7224                         // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
7225                         /*elm.onerror = function() {
7226                                 alert('Failed to load: ' + url);
7227                         };*/
7228
7229                         // Add script to document
7230                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
7231                 };
7232
7233                 this.isDone = function(url) {
7234                         return states[url] == LOADED;
7235                 };
7236
7237                 this.markDone = function(url) {
7238                         states[url] = LOADED;
7239                 };
7240
7241                 this.add = this.load = function(url, callback, scope) {
7242                         var item, state = states[url];
7243
7244                         // Add url to load queue
7245                         if (state == undefined) {
7246                                 queue.push(url);
7247                                 states[url] = QUEUED;
7248                         }
7249
7250                         if (callback) {
7251                                 // Store away callback for later execution
7252                                 if (!scriptLoadedCallbacks[url])
7253                                         scriptLoadedCallbacks[url] = [];
7254
7255                                 scriptLoadedCallbacks[url].push({
7256                                         func : callback,
7257                                         scope : scope || this
7258                                 });
7259                         }
7260                 };
7261
7262                 this.loadQueue = function(callback, scope) {
7263                         this.loadScripts(queue, callback, scope);
7264                 };
7265
7266                 this.loadScripts = function(scripts, callback, scope) {
7267                         var loadScripts;
7268
7269                         function execScriptLoadedCallbacks(url) {
7270                                 // Execute URL callback functions
7271                                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
7272                                         callback.func.call(callback.scope);
7273                                 });
7274
7275                                 scriptLoadedCallbacks[url] = undefined;
7276                         };
7277
7278                         queueLoadedCallbacks.push({
7279                                 func : callback,
7280                                 scope : scope || this
7281                         });
7282
7283                         loadScripts = function() {
7284                                 var loadingScripts = tinymce.grep(scripts);
7285
7286                                 // Current scripts has been handled
7287                                 scripts.length = 0;
7288
7289                                 // Load scripts that needs to be loaded
7290                                 tinymce.each(loadingScripts, function(url) {
7291                                         // Script is already loaded then execute script callbacks directly
7292                                         if (states[url] == LOADED) {
7293                                                 execScriptLoadedCallbacks(url);
7294                                                 return;
7295                                         }
7296
7297                                         // Is script not loading then start loading it
7298                                         if (states[url] != LOADING) {
7299                                                 states[url] = LOADING;
7300                                                 loading++;
7301
7302                                                 loadScript(url, function() {
7303                                                         states[url] = LOADED;
7304                                                         loading--;
7305
7306                                                         execScriptLoadedCallbacks(url);
7307
7308                                                         // Load more scripts if they where added by the recently loaded script
7309                                                         loadScripts();
7310                                                 });
7311                                         }
7312                                 });
7313
7314                                 // No scripts are currently loading then execute all pending queue loaded callbacks
7315                                 if (!loading) {
7316                                         tinymce.each(queueLoadedCallbacks, function(callback) {
7317                                                 callback.func.call(callback.scope);
7318                                         });
7319
7320                                         queueLoadedCallbacks.length = 0;
7321                                 }
7322                         };
7323
7324                         loadScripts();
7325                 };
7326         };
7327
7328         // Global script loader
7329         tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
7330 })(tinymce);
7331
7332 tinymce.dom.TreeWalker = function(start_node, root_node) {
7333         var node = start_node;
7334
7335         function findSibling(node, start_name, sibling_name, shallow) {
7336                 var sibling, parent;
7337
7338                 if (node) {
7339                         // Walk into nodes if it has a start
7340                         if (!shallow && node[start_name])
7341                                 return node[start_name];
7342
7343                         // Return the sibling if it has one
7344                         if (node != root_node) {
7345                                 sibling = node[sibling_name];
7346                                 if (sibling)
7347                                         return sibling;
7348
7349                                 // Walk up the parents to look for siblings
7350                                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
7351                                         sibling = parent[sibling_name];
7352                                         if (sibling)
7353                                                 return sibling;
7354                                 }
7355                         }
7356                 }
7357         };
7358
7359         this.current = function() {
7360                 return node;
7361         };
7362
7363         this.next = function(shallow) {
7364                 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
7365         };
7366
7367         this.prev = function(shallow) {
7368                 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
7369         };
7370 };
7371
7372 (function(tinymce) {
7373         tinymce.dom.RangeUtils = function(dom) {
7374                 var INVISIBLE_CHAR = '\uFEFF';
7375
7376                 this.walk = function(rng, callback) {
7377                         var startContainer = rng.startContainer,
7378                                 startOffset = rng.startOffset,
7379                                 endContainer = rng.endContainer,
7380                                 endOffset = rng.endOffset,
7381                                 ancestor, startPoint,
7382                                 endPoint, node, parent, siblings, nodes;
7383
7384                         // Handle table cell selection the table plugin enables
7385                         // you to fake select table cells and perform formatting actions on them
7386                         nodes = dom.select('td.mceSelected,th.mceSelected');
7387                         if (nodes.length > 0) {
7388                                 tinymce.each(nodes, function(node) {
7389                                         callback([node]);
7390                                 });
7391
7392                                 return;
7393                         }
7394
7395                         function collectSiblings(node, name, end_node) {
7396                                 var siblings = [];
7397
7398                                 for (; node && node != end_node; node = node[name])
7399                                         siblings.push(node);
7400
7401                                 return siblings;
7402                         };
7403
7404                         function findEndPoint(node, root) {
7405                                 do {
7406                                         if (node.parentNode == root)
7407                                                 return node;
7408
7409                                         node = node.parentNode;
7410                                 } while(node);
7411                         };
7412
7413                         function walkBoundary(start_node, end_node, next) {
7414                                 var siblingName = next ? 'nextSibling' : 'previousSibling';
7415
7416                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
7417                                         parent = node.parentNode;
7418                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
7419
7420                                         if (siblings.length) {
7421                                                 if (!next)
7422                                                         siblings.reverse();
7423
7424                                                 callback(siblings);
7425                                         }
7426                                 }
7427                         };
7428
7429                         // If index based start position then resolve it
7430                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
7431                                 startContainer = startContainer.childNodes[startOffset];
7432
7433                         // If index based end position then resolve it
7434                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
7435                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
7436
7437                         // Find common ancestor and end points
7438                         ancestor = dom.findCommonAncestor(startContainer, endContainer);
7439
7440                         // Same container
7441                         if (startContainer == endContainer)
7442                                 return callback([startContainer]);
7443
7444                         // Process left side
7445                         for (node = startContainer; node; node = node.parentNode) {
7446                                 if (node == endContainer)
7447                                         return walkBoundary(startContainer, ancestor, true);
7448
7449                                 if (node == ancestor)
7450                                         break;
7451                         }
7452
7453                         // Process right side
7454                         for (node = endContainer; node; node = node.parentNode) {
7455                                 if (node == startContainer)
7456                                         return walkBoundary(endContainer, ancestor);
7457
7458                                 if (node == ancestor)
7459                                         break;
7460                         }
7461
7462                         // Find start/end point
7463                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;
7464                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;
7465
7466                         // Walk left leaf
7467                         walkBoundary(startContainer, startPoint, true);
7468
7469                         // Walk the middle from start to end point
7470                         siblings = collectSiblings(
7471                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,
7472                                 'nextSibling',
7473                                 endPoint == endContainer ? endPoint.nextSibling : endPoint
7474                         );
7475
7476                         if (siblings.length)
7477                                 callback(siblings);
7478
7479                         // Walk right leaf
7480                         walkBoundary(endContainer, endPoint);
7481                 };
7482
7483                 /*              this.split = function(rng) {
7484                         var startContainer = rng.startContainer,
7485                                 startOffset = rng.startOffset,
7486                                 endContainer = rng.endContainer,
7487                                 endOffset = rng.endOffset;
7488
7489                         function splitText(node, offset) {
7490                                 if (offset == node.nodeValue.length)
7491                                         node.appendData(INVISIBLE_CHAR);
7492
7493                                 node = node.splitText(offset);
7494
7495                                 if (node.nodeValue === INVISIBLE_CHAR)
7496                                         node.nodeValue = '';
7497
7498                                 return node;
7499                         };
7500
7501                         // Handle single text node
7502                         if (startContainer == endContainer) {
7503                                 if (startContainer.nodeType == 3) {
7504                                         if (startOffset != 0)
7505                                                 startContainer = endContainer = splitText(startContainer, startOffset);
7506
7507                                         if (endOffset - startOffset != startContainer.nodeValue.length)
7508                                                 splitText(startContainer, endOffset - startOffset);
7509                                 }
7510                         } else {
7511                                 // Split startContainer text node if needed
7512                                 if (startContainer.nodeType == 3 && startOffset != 0) {
7513                                         startContainer = splitText(startContainer, startOffset);
7514                                         startOffset = 0;
7515                                 }
7516
7517                                 // Split endContainer text node if needed
7518                                 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
7519                                         endContainer = splitText(endContainer, endOffset).previousSibling;
7520                                         endOffset = endContainer.nodeValue.length;
7521                                 }
7522                         }
7523
7524                         return {
7525                                 startContainer : startContainer,
7526                                 startOffset : startOffset,
7527                                 endContainer : endContainer,
7528                                 endOffset : endOffset
7529                         };
7530                 };
7531 */
7532         };
7533
7534         tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
7535                 if (rng1 && rng2) {
7536                         // Compare native IE ranges
7537                         if (rng1.item || rng1.duplicate) {
7538                                 // Both are control ranges and the selected element matches
7539                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
7540                                         return true;
7541
7542                                 // Both are text ranges and the range matches
7543                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
7544                                         return true;
7545                         } else {
7546                                 // Compare w3c ranges
7547                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
7548                         }
7549                 }
7550
7551                 return false;
7552         };
7553 })(tinymce);
7554
7555 (function(tinymce) {
7556         var Event = tinymce.dom.Event, each = tinymce.each;
7557
7558         tinymce.create('tinymce.ui.KeyboardNavigation', {
7559                 KeyboardNavigation: function(settings, dom) {
7560                         var t = this, root = settings.root, items = settings.items,
7561                                         enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
7562                                         excludeFromTabOrder = settings.excludeFromTabOrder,
7563                                         itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
7564
7565                         dom = dom || tinymce.DOM;
7566
7567                         itemFocussed = function(evt) {
7568                                 focussedId = evt.target.id;
7569                         };
7570                         
7571                         itemBlurred = function(evt) {
7572                                 dom.setAttrib(evt.target.id, 'tabindex', '-1');
7573                         };
7574                         
7575                         rootFocussed = function(evt) {
7576                                 var item = dom.get(focussedId);
7577                                 dom.setAttrib(item, 'tabindex', '0');
7578                                 item.focus();
7579                         };
7580                         
7581                         t.focus = function() {
7582                                 dom.get(focussedId).focus();
7583                         };
7584
7585                         t.destroy = function() {
7586                                 each(items, function(item) {
7587                                         dom.unbind(dom.get(item.id), 'focus', itemFocussed);
7588                                         dom.unbind(dom.get(item.id), 'blur', itemBlurred);
7589                                 });
7590
7591                                 dom.unbind(dom.get(root), 'focus', rootFocussed);
7592                                 dom.unbind(dom.get(root), 'keydown', rootKeydown);
7593
7594                                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
7595                                 t.destroy = function() {};
7596                         };
7597                         
7598                         t.moveFocus = function(dir, evt) {
7599                                 var idx = -1, controls = t.controls, newFocus;
7600
7601                                 if (!focussedId)
7602                                         return;
7603
7604                                 each(items, function(item, index) {
7605                                         if (item.id === focussedId) {
7606                                                 idx = index;
7607                                                 return false;
7608                                         }
7609                                 });
7610
7611                                 idx += dir;
7612                                 if (idx < 0) {
7613                                         idx = items.length - 1;
7614                                 } else if (idx >= items.length) {
7615                                         idx = 0;
7616                                 }
7617                                 
7618                                 newFocus = items[idx];
7619                                 dom.setAttrib(focussedId, 'tabindex', '-1');
7620                                 dom.setAttrib(newFocus.id, 'tabindex', '0');
7621                                 dom.get(newFocus.id).focus();
7622
7623                                 if (settings.actOnFocus) {
7624                                         settings.onAction(newFocus.id);
7625                                 }
7626
7627                                 if (evt)
7628                                         Event.cancel(evt);
7629                         };
7630                         
7631                         rootKeydown = function(evt) {
7632                                 var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
7633                                 
7634                                 switch (evt.keyCode) {
7635                                         case DOM_VK_LEFT:
7636                                                 if (enableLeftRight) t.moveFocus(-1);
7637                                                 break;
7638         
7639                                         case DOM_VK_RIGHT:
7640                                                 if (enableLeftRight) t.moveFocus(1);
7641                                                 break;
7642         
7643                                         case DOM_VK_UP:
7644                                                 if (enableUpDown) t.moveFocus(-1);
7645                                                 break;
7646
7647                                         case DOM_VK_DOWN:
7648                                                 if (enableUpDown) t.moveFocus(1);
7649                                                 break;
7650
7651                                         case DOM_VK_ESCAPE:
7652                                                 if (settings.onCancel) {
7653                                                         settings.onCancel();
7654                                                         Event.cancel(evt);
7655                                                 }
7656                                                 break;
7657
7658                                         case DOM_VK_ENTER:
7659                                         case DOM_VK_RETURN:
7660                                         case DOM_VK_SPACE:
7661                                                 if (settings.onAction) {
7662                                                         settings.onAction(focussedId);
7663                                                         Event.cancel(evt);
7664                                                 }
7665                                                 break;
7666                                 }
7667                         };
7668
7669                         // Set up state and listeners for each item.
7670                         each(items, function(item, idx) {
7671                                 var tabindex;
7672
7673                                 if (!item.id) {
7674                                         item.id = dom.uniqueId('_mce_item_');
7675                                 }
7676
7677                                 if (excludeFromTabOrder) {
7678                                         dom.bind(item.id, 'blur', itemBlurred);
7679                                         tabindex = '-1';
7680                                 } else {
7681                                         tabindex = (idx === 0 ? '0' : '-1');
7682                                 }
7683
7684                                 dom.setAttrib(item.id, 'tabindex', tabindex);
7685                                 dom.bind(dom.get(item.id), 'focus', itemFocussed);
7686                         });
7687                         
7688                         // Setup initial state for root element.
7689                         if (items[0]){
7690                                 focussedId = items[0].id;
7691                         }
7692
7693                         dom.setAttrib(root, 'tabindex', '-1');
7694                         
7695                         // Setup listeners for root element.
7696                         dom.bind(dom.get(root), 'focus', rootFocussed);
7697                         dom.bind(dom.get(root), 'keydown', rootKeydown);
7698                 }
7699         });
7700 })(tinymce);
7701 (function(tinymce) {
7702         // Shorten class names
7703         var DOM = tinymce.DOM, is = tinymce.is;
7704
7705         tinymce.create('tinymce.ui.Control', {
7706                 Control : function(id, s, editor) {
7707                         this.id = id;
7708                         this.settings = s = s || {};
7709                         this.rendered = false;
7710                         this.onRender = new tinymce.util.Dispatcher(this);
7711                         this.classPrefix = '';
7712                         this.scope = s.scope || this;
7713                         this.disabled = 0;
7714                         this.active = 0;
7715                         this.editor = editor;
7716                 },
7717                 
7718                 setAriaProperty : function(property, value) {
7719                         var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
7720                         if (element) {
7721                                 DOM.setAttrib(element, 'aria-' + property, !!value);
7722                         }
7723                 },
7724                 
7725                 focus : function() {
7726                         DOM.get(this.id).focus();
7727                 },
7728
7729                 setDisabled : function(s) {
7730                         if (s != this.disabled) {
7731                                 this.setAriaProperty('disabled', s);
7732
7733                                 this.setState('Disabled', s);
7734                                 this.setState('Enabled', !s);
7735                                 this.disabled = s;
7736                         }
7737                 },
7738
7739                 isDisabled : function() {
7740                         return this.disabled;
7741                 },
7742
7743                 setActive : function(s) {
7744                         if (s != this.active) {
7745                                 this.setState('Active', s);
7746                                 this.active = s;
7747                                 this.setAriaProperty('pressed', s);
7748                         }
7749                 },
7750
7751                 isActive : function() {
7752                         return this.active;
7753                 },
7754
7755                 setState : function(c, s) {
7756                         var n = DOM.get(this.id);
7757
7758                         c = this.classPrefix + c;
7759
7760                         if (s)
7761                                 DOM.addClass(n, c);
7762                         else
7763                                 DOM.removeClass(n, c);
7764                 },
7765
7766                 isRendered : function() {
7767                         return this.rendered;
7768                 },
7769
7770                 renderHTML : function() {
7771                 },
7772
7773                 renderTo : function(n) {
7774                         DOM.setHTML(n, this.renderHTML());
7775                 },
7776
7777                 postRender : function() {
7778                         var t = this, b;
7779
7780                         // Set pending states
7781                         if (is(t.disabled)) {
7782                                 b = t.disabled;
7783                                 t.disabled = -1;
7784                                 t.setDisabled(b);
7785                         }
7786
7787                         if (is(t.active)) {
7788                                 b = t.active;
7789                                 t.active = -1;
7790                                 t.setActive(b);
7791                         }
7792                 },
7793
7794                 remove : function() {
7795                         DOM.remove(this.id);
7796                         this.destroy();
7797                 },
7798
7799                 destroy : function() {
7800                         tinymce.dom.Event.clear(this.id);
7801                 }
7802         });
7803 })(tinymce);
7804 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
7805         Container : function(id, s, editor) {
7806                 this.parent(id, s, editor);
7807
7808                 this.controls = [];
7809
7810                 this.lookup = {};
7811         },
7812
7813         add : function(c) {
7814                 this.lookup[c.id] = c;
7815                 this.controls.push(c);
7816
7817                 return c;
7818         },
7819
7820         get : function(n) {
7821                 return this.lookup[n];
7822         }
7823 });
7824
7825
7826 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
7827         Separator : function(id, s) {
7828                 this.parent(id, s);
7829                 this.classPrefix = 'mceSeparator';
7830                 this.setDisabled(true);
7831         },
7832
7833         renderHTML : function() {
7834                 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
7835         }
7836 });
7837
7838 (function(tinymce) {
7839         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
7840
7841         tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
7842                 MenuItem : function(id, s) {
7843                         this.parent(id, s);
7844                         this.classPrefix = 'mceMenuItem';
7845                 },
7846
7847                 setSelected : function(s) {
7848                         this.setState('Selected', s);
7849                         this.setAriaProperty('checked', !!s);
7850                         this.selected = s;
7851                 },
7852
7853                 isSelected : function() {
7854                         return this.selected;
7855                 },
7856
7857                 postRender : function() {
7858                         var t = this;
7859                         
7860                         t.parent();
7861
7862                         // Set pending state
7863                         if (is(t.selected))
7864                                 t.setSelected(t.selected);
7865                 }
7866         });
7867 })(tinymce);
7868
7869 (function(tinymce) {
7870         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
7871
7872         tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
7873                 Menu : function(id, s) {
7874                         var t = this;
7875
7876                         t.parent(id, s);
7877                         t.items = {};
7878                         t.collapsed = false;
7879                         t.menuCount = 0;
7880                         t.onAddItem = new tinymce.util.Dispatcher(this);
7881                 },
7882
7883                 expand : function(d) {
7884                         var t = this;
7885
7886                         if (d) {
7887                                 walk(t, function(o) {
7888                                         if (o.expand)
7889                                                 o.expand();
7890                                 }, 'items', t);
7891                         }
7892
7893                         t.collapsed = false;
7894                 },
7895
7896                 collapse : function(d) {
7897                         var t = this;
7898
7899                         if (d) {
7900                                 walk(t, function(o) {
7901                                         if (o.collapse)
7902                                                 o.collapse();
7903                                 }, 'items', t);
7904                         }
7905
7906                         t.collapsed = true;
7907                 },
7908
7909                 isCollapsed : function() {
7910                         return this.collapsed;
7911                 },
7912
7913                 add : function(o) {
7914                         if (!o.settings)
7915                                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
7916
7917                         this.onAddItem.dispatch(this, o);
7918
7919                         return this.items[o.id] = o;
7920                 },
7921
7922                 addSeparator : function() {
7923                         return this.add({separator : true});
7924                 },
7925
7926                 addMenu : function(o) {
7927                         if (!o.collapse)
7928                                 o = this.createMenu(o);
7929
7930                         this.menuCount++;
7931
7932                         return this.add(o);
7933                 },
7934
7935                 hasMenus : function() {
7936                         return this.menuCount !== 0;
7937                 },
7938
7939                 remove : function(o) {
7940                         delete this.items[o.id];
7941                 },
7942
7943                 removeAll : function() {
7944                         var t = this;
7945
7946                         walk(t, function(o) {
7947                                 if (o.removeAll)
7948                                         o.removeAll();
7949                                 else
7950                                         o.remove();
7951
7952                                 o.destroy();
7953                         }, 'items', t);
7954
7955                         t.items = {};
7956                 },
7957
7958                 createMenu : function(o) {
7959                         var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
7960
7961                         m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
7962
7963                         return m;
7964                 }
7965         });
7966 })(tinymce);
7967 (function(tinymce) {
7968         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
7969
7970         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
7971                 DropMenu : function(id, s) {
7972                         s = s || {};
7973                         s.container = s.container || DOM.doc.body;
7974                         s.offset_x = s.offset_x || 0;
7975                         s.offset_y = s.offset_y || 0;
7976                         s.vp_offset_x = s.vp_offset_x || 0;
7977                         s.vp_offset_y = s.vp_offset_y || 0;
7978
7979                         if (is(s.icons) && !s.icons)
7980                                 s['class'] += ' mceNoIcons';
7981
7982                         this.parent(id, s);
7983                         this.onShowMenu = new tinymce.util.Dispatcher(this);
7984                         this.onHideMenu = new tinymce.util.Dispatcher(this);
7985                         this.classPrefix = 'mceMenu';
7986                 },
7987
7988                 createMenu : function(s) {
7989                         var t = this, cs = t.settings, m;
7990
7991                         s.container = s.container || cs.container;
7992                         s.parent = t;
7993                         s.constrain = s.constrain || cs.constrain;
7994                         s['class'] = s['class'] || cs['class'];
7995                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
7996                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
7997                         s.keyboard_focus = cs.keyboard_focus;
7998                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
7999
8000                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
8001
8002                         return m;
8003                 },
8004                 
8005                 focus : function() {
8006                         var t = this;
8007                         if (t.keyboardNav) {
8008                                 t.keyboardNav.focus();
8009                         }
8010                 },
8011
8012                 update : function() {
8013                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
8014
8015                         tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
8016                         th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
8017
8018                         if (!DOM.boxModel)
8019                                 t.element.setStyles({width : tw + 2, height : th + 2});
8020                         else
8021                                 t.element.setStyles({width : tw, height : th});
8022
8023                         if (s.max_width)
8024                                 DOM.setStyle(co, 'width', tw);
8025
8026                         if (s.max_height) {
8027                                 DOM.setStyle(co, 'height', th);
8028
8029                                 if (tb.clientHeight < s.max_height)
8030                                         DOM.setStyle(co, 'overflow', 'hidden');
8031                         }
8032                 },
8033
8034                 showMenu : function(x, y, px) {
8035                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
8036
8037                         t.collapse(1);
8038
8039                         if (t.isMenuVisible)
8040                                 return;
8041
8042                         if (!t.rendered) {
8043                                 co = DOM.add(t.settings.container, t.renderNode());
8044
8045                                 each(t.items, function(o) {
8046                                         o.postRender();
8047                                 });
8048
8049                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
8050                         } else
8051                                 co = DOM.get('menu_' + t.id);
8052
8053                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
8054                         if (!tinymce.isOpera)
8055                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
8056
8057                         DOM.show(co);
8058                         t.update();
8059
8060                         x += s.offset_x || 0;
8061                         y += s.offset_y || 0;
8062                         vp.w -= 4;
8063                         vp.h -= 4;
8064
8065                         // Move inside viewport if not submenu
8066                         if (s.constrain) {
8067                                 w = co.clientWidth - ot;
8068                                 h = co.clientHeight - ot;
8069                                 mx = vp.x + vp.w;
8070                                 my = vp.y + vp.h;
8071
8072                                 if ((x + s.vp_offset_x + w) > mx)
8073                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
8074
8075                                 if ((y + s.vp_offset_y + h) > my)
8076                                         y = Math.max(0, (my - s.vp_offset_y) - h);
8077                         }
8078
8079                         DOM.setStyles(co, {left : x , top : y});
8080                         t.element.update();
8081
8082                         t.isMenuVisible = 1;
8083                         t.mouseClickFunc = Event.add(co, 'click', function(e) {
8084                                 var m;
8085
8086                                 e = e.target;
8087
8088                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
8089                                         m = t.items[e.id];
8090
8091                                         if (m.isDisabled())
8092                                                 return;
8093
8094                                         dm = t;
8095
8096                                         while (dm) {
8097                                                 if (dm.hideMenu)
8098                                                         dm.hideMenu();
8099
8100                                                 dm = dm.settings.parent;
8101                                         }
8102
8103                                         if (m.settings.onclick)
8104                                                 m.settings.onclick(e);
8105
8106                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
8107                                 }
8108                         });
8109
8110                         if (t.hasMenus()) {
8111                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
8112                                         var m, r, mi;
8113
8114                                         e = e.target;
8115                                         if (e && (e = DOM.getParent(e, 'tr'))) {
8116                                                 m = t.items[e.id];
8117
8118                                                 if (t.lastMenu)
8119                                                         t.lastMenu.collapse(1);
8120
8121                                                 if (m.isDisabled())
8122                                                         return;
8123
8124                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
8125                                                         //p = DOM.getPos(s.container);
8126                                                         r = DOM.getRect(e);
8127                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
8128                                                         t.lastMenu = m;
8129                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
8130                                                 }
8131                                         }
8132                                 });
8133                         }
8134                         
8135                         Event.add(co, 'keydown', t._keyHandler, t);
8136
8137                         t.onShowMenu.dispatch(t);
8138
8139                         if (s.keyboard_focus) { 
8140                                 t._setupKeyboardNav(); 
8141                         }
8142                 },
8143
8144                 hideMenu : function(c) {
8145                         var t = this, co = DOM.get('menu_' + t.id), e;
8146
8147                         if (!t.isMenuVisible)
8148                                 return;
8149
8150                         if (t.keyboardNav) t.keyboardNav.destroy();
8151                         Event.remove(co, 'mouseover', t.mouseOverFunc);
8152                         Event.remove(co, 'click', t.mouseClickFunc);
8153                         Event.remove(co, 'keydown', t._keyHandler);
8154                         DOM.hide(co);
8155                         t.isMenuVisible = 0;
8156
8157                         if (!c)
8158                                 t.collapse(1);
8159
8160                         if (t.element)
8161                                 t.element.hide();
8162
8163                         if (e = DOM.get(t.id))
8164                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
8165
8166                         t.onHideMenu.dispatch(t);
8167                 },
8168
8169                 add : function(o) {
8170                         var t = this, co;
8171
8172                         o = t.parent(o);
8173
8174                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))
8175                                 t._add(DOM.select('tbody', co)[0], o);
8176
8177                         return o;
8178                 },
8179
8180                 collapse : function(d) {
8181                         this.parent(d);
8182                         this.hideMenu(1);
8183                 },
8184
8185                 remove : function(o) {
8186                         DOM.remove(o.id);
8187                         this.destroy();
8188
8189                         return this.parent(o);
8190                 },
8191
8192                 destroy : function() {
8193                         var t = this, co = DOM.get('menu_' + t.id);
8194
8195                         if (t.keyboardNav) t.keyboardNav.destroy();
8196                         Event.remove(co, 'mouseover', t.mouseOverFunc);
8197                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
8198                         Event.remove(co, 'click', t.mouseClickFunc);
8199                         Event.remove(co, 'keydown', t._keyHandler);
8200
8201                         if (t.element)
8202                                 t.element.remove();
8203
8204                         DOM.remove(co);
8205                 },
8206
8207                 renderNode : function() {
8208                         var t = this, s = t.settings, n, tb, co, w;
8209
8210                         w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
8211                         if (t.settings.parent) {
8212                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
8213                         }
8214                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
8215                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
8216
8217                         if (s.menu_line)
8218                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
8219
8220 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
8221                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
8222                         tb = DOM.add(n, 'tbody');
8223
8224                         each(t.items, function(o) {
8225                                 t._add(tb, o);
8226                         });
8227
8228                         t.rendered = true;
8229
8230                         return w;
8231                 },
8232
8233                 // Internal functions
8234                 _setupKeyboardNav : function(){
8235                         var contextMenu, menuItems, t=this; 
8236                         contextMenu = DOM.select('#menu_' + t.id)[0];
8237                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
8238                         menuItems.splice(0,0,contextMenu);
8239                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({
8240                                 root: 'menu_' + t.id,
8241                                 items: menuItems,
8242                                 onCancel: function() {
8243                                         t.hideMenu();
8244                                 },
8245                                 enableUpDown: true
8246                         });
8247                         contextMenu.focus();
8248                 },
8249
8250                 _keyHandler : function(evt) {
8251                         var t = this, e;
8252                         switch (evt.keyCode) {
8253                                 case 37: // Left
8254                                         if (t.settings.parent) {
8255                                                 t.hideMenu();
8256                                                 t.settings.parent.focus();
8257                                                 Event.cancel(evt);
8258                                         }
8259                                         break;
8260                                 case 39: // Right
8261                                         if (t.mouseOverFunc)
8262                                                 t.mouseOverFunc(evt);
8263                                         break;
8264                         }
8265                 },
8266
8267                 _add : function(tb, o) {
8268                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
8269
8270                         if (s.separator) {
8271                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
8272                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
8273
8274                                 if (n = ro.previousSibling)
8275                                         DOM.addClass(n, 'mceLast');
8276
8277                                 return;
8278                         }
8279
8280                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
8281                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
8282                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
8283
8284                         if (s.parent) {
8285                                 DOM.setAttrib(a, 'aria-haspopup', 'true');
8286                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
8287                         }
8288
8289                         DOM.addClass(it, s['class']);
8290 //                      n = DOM.add(n, 'span', {'class' : 'item'});
8291
8292                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
8293
8294                         if (s.icon_src)
8295                                 DOM.add(ic, 'img', {src : s.icon_src});
8296
8297                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
8298
8299                         if (o.settings.style)
8300                                 DOM.setAttrib(n, 'style', o.settings.style);
8301
8302                         if (tb.childNodes.length == 1)
8303                                 DOM.addClass(ro, 'mceFirst');
8304
8305                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
8306                                 DOM.addClass(ro, 'mceFirst');
8307
8308                         if (o.collapse)
8309                                 DOM.addClass(ro, cp + 'ItemSub');
8310
8311                         if (n = ro.previousSibling)
8312                                 DOM.removeClass(n, 'mceLast');
8313
8314                         DOM.addClass(ro, 'mceLast');
8315                 }
8316         });
8317 })(tinymce);
8318 (function(tinymce) {
8319         var DOM = tinymce.DOM;
8320
8321         tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
8322                 Button : function(id, s, ed) {
8323                         this.parent(id, s, ed);
8324                         this.classPrefix = 'mceButton';
8325                 },
8326
8327                 renderHTML : function() {
8328                         var cp = this.classPrefix, s = this.settings, h, l;
8329
8330                         l = DOM.encode(s.label || '');
8331                         h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
8332
8333                         if (s.image)
8334                                 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
8335                         else
8336                                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
8337
8338                         h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
8339                         h += '</a>';
8340                         return h;
8341                 },
8342
8343                 postRender : function() {
8344                         var t = this, s = t.settings;
8345
8346                         tinymce.dom.Event.add(t.id, 'click', function(e) {
8347                                 if (!t.isDisabled())
8348                                         return s.onclick.call(s.scope, e);
8349                         });
8350                 }
8351         });
8352 })(tinymce);
8353
8354 (function(tinymce) {
8355         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
8356
8357         tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
8358                 ListBox : function(id, s, ed) {
8359                         var t = this;
8360
8361                         t.parent(id, s, ed);
8362
8363                         t.items = [];
8364
8365                         t.onChange = new Dispatcher(t);
8366
8367                         t.onPostRender = new Dispatcher(t);
8368
8369                         t.onAdd = new Dispatcher(t);
8370
8371                         t.onRenderMenu = new tinymce.util.Dispatcher(this);
8372
8373                         t.classPrefix = 'mceListBox';
8374                 },
8375
8376                 select : function(va) {
8377                         var t = this, fv, f;
8378
8379                         if (va == undefined)
8380                                 return t.selectByIndex(-1);
8381
8382                         // Is string or number make function selector
8383                         if (va && va.call)
8384                                 f = va;
8385                         else {
8386                                 f = function(v) {
8387                                         return v == va;
8388                                 };
8389                         }
8390
8391                         // Do we need to do something?
8392                         if (va != t.selectedValue) {
8393                                 // Find item
8394                                 each(t.items, function(o, i) {
8395                                         if (f(o.value)) {
8396                                                 fv = 1;
8397                                                 t.selectByIndex(i);
8398                                                 return false;
8399                                         }
8400                                 });
8401
8402                                 if (!fv)
8403                                         t.selectByIndex(-1);
8404                         }
8405                 },
8406
8407                 selectByIndex : function(idx) {
8408                         var t = this, e, o;
8409
8410                         if (idx != t.selectedIndex) {
8411                                 e = DOM.get(t.id + '_text');
8412                                 o = t.items[idx];
8413
8414                                 if (o) {
8415                                         t.selectedValue = o.value;
8416                                         t.selectedIndex = idx;
8417                                         DOM.setHTML(e, DOM.encode(o.title));
8418                                         DOM.removeClass(e, 'mceTitle');
8419                                         DOM.setAttrib(t.id, 'aria-valuenow', o.title);
8420                                 } else {
8421                                         DOM.setHTML(e, DOM.encode(t.settings.title));
8422                                         DOM.addClass(e, 'mceTitle');
8423                                         t.selectedValue = t.selectedIndex = null;
8424                                         DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
8425                                 }
8426                                 e = 0;
8427                         }
8428                 },
8429
8430                 add : function(n, v, o) {
8431                         var t = this;
8432
8433                         o = o || {};
8434                         o = tinymce.extend(o, {
8435                                 title : n,
8436                                 value : v
8437                         });
8438
8439                         t.items.push(o);
8440                         t.onAdd.dispatch(t, o);
8441                 },
8442
8443                 getLength : function() {
8444                         return this.items.length;
8445                 },
8446
8447                 renderHTML : function() {
8448                         var h = '', t = this, s = t.settings, cp = t.classPrefix;
8449
8450                         h = '<span role="button" aria-haspopup="true" aria-labelledby="' + t.id +'_text" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
8451                         h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
8452                         h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
8453                         h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
8454                         h += '</tr></tbody></table></span>';
8455
8456                         return h;
8457                 },
8458
8459                 showMenu : function() {
8460                         var t = this, p1, p2, e = DOM.get(this.id), m;
8461
8462                         if (t.isDisabled() || t.items.length == 0)
8463                                 return;
8464
8465                         if (t.menu && t.menu.isMenuVisible)
8466                                 return t.hideMenu();
8467
8468                         if (!t.isMenuRendered) {
8469                                 t.renderMenu();
8470                                 t.isMenuRendered = true;
8471                         }
8472
8473                         p1 = DOM.getPos(this.settings.menu_container);
8474                         p2 = DOM.getPos(e);
8475
8476                         m = t.menu;
8477                         m.settings.offset_x = p2.x;
8478                         m.settings.offset_y = p2.y;
8479                         m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
8480
8481                         // Select in menu
8482                         if (t.oldID)
8483                                 m.items[t.oldID].setSelected(0);
8484
8485                         each(t.items, function(o) {
8486                                 if (o.value === t.selectedValue) {
8487                                         m.items[o.id].setSelected(1);
8488                                         t.oldID = o.id;
8489                                 }
8490                         });
8491
8492                         m.showMenu(0, e.clientHeight);
8493
8494                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
8495                         DOM.addClass(t.id, t.classPrefix + 'Selected');
8496
8497                         //DOM.get(t.id + '_text').focus();
8498                 },
8499
8500                 hideMenu : function(e) {
8501                         var t = this;
8502
8503                         if (t.menu && t.menu.isMenuVisible) {
8504                                 DOM.removeClass(t.id, t.classPrefix + 'Selected');
8505
8506                                 // Prevent double toogles by canceling the mouse click event to the button
8507                                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
8508                                         return;
8509
8510                                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
8511                                         DOM.removeClass(t.id, t.classPrefix + 'Selected');
8512                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
8513                                         t.menu.hideMenu();
8514                                 }
8515                         }
8516                 },
8517
8518                 renderMenu : function() {
8519                         var t = this, m;
8520
8521                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
8522                                 menu_line : 1,
8523                                 'class' : t.classPrefix + 'Menu mceNoIcons',
8524                                 max_width : 150,
8525                                 max_height : 150
8526                         });
8527
8528                         m.onHideMenu.add(function() {
8529                                 t.hideMenu();
8530                                 t.focus();
8531                         });
8532
8533                         m.add({
8534                                 title : t.settings.title,
8535                                 'class' : 'mceMenuItemTitle',
8536                                 onclick : function() {
8537                                         if (t.settings.onselect('') !== false)
8538                                                 t.select(''); // Must be runned after
8539                                 }
8540                         });
8541
8542                         each(t.items, function(o) {
8543                                 // No value then treat it as a title
8544                                 if (o.value === undefined) {
8545                                         m.add({
8546                                                 title : o.title,
8547                                                 'class' : 'mceMenuItemTitle',
8548                                                 onclick : function() {
8549                                                         if (t.settings.onselect('') !== false)
8550                                                                 t.select(''); // Must be runned after
8551                                                 }
8552                                         });
8553                                 } else {
8554                                         o.id = DOM.uniqueId();
8555                                         o.onclick = function() {
8556                                                 if (t.settings.onselect(o.value) !== false)
8557                                                         t.select(o.value); // Must be runned after
8558                                         };
8559
8560                                         m.add(o);
8561                                 }
8562                         });
8563
8564                         t.onRenderMenu.dispatch(t, m);
8565                         t.menu = m;
8566                 },
8567
8568                 postRender : function() {
8569                         var t = this, cp = t.classPrefix;
8570
8571                         Event.add(t.id, 'click', t.showMenu, t);
8572                         Event.add(t.id, 'keydown', function(evt) {
8573                                 if (evt.keyCode == 32) { // Space
8574                                         t.showMenu(evt);
8575                                         Event.cancel(evt);
8576                                 }
8577                         });
8578                         Event.add(t.id, 'focus', function() {
8579                                 if (!t._focused) {
8580                                         t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
8581                                                 if (e.keyCode == 40) {
8582                                                         t.showMenu();
8583                                                         Event.cancel(e);
8584                                                 }
8585                                         });
8586                                         t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
8587                                                 var v;
8588                                                 if (e.keyCode == 13) {
8589                                                         // Fake select on enter
8590                                                         v = t.selectedValue;
8591                                                         t.selectedValue = null; // Needs to be null to fake change
8592                                                         Event.cancel(e);
8593                                                         t.settings.onselect(v);
8594                                                 }
8595                                         });
8596                                 }
8597
8598                                 t._focused = 1;
8599                         });
8600                         Event.add(t.id, 'blur', function() {
8601                                 Event.remove(t.id, 'keydown', t.keyDownHandler);
8602                                 Event.remove(t.id, 'keypress', t.keyPressHandler);
8603                                 t._focused = 0;
8604                         });
8605
8606                         // Old IE doesn't have hover on all elements
8607                         if (tinymce.isIE6 || !DOM.boxModel) {
8608                                 Event.add(t.id, 'mouseover', function() {
8609                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
8610                                                 DOM.addClass(t.id, cp + 'Hover');
8611                                 });
8612
8613                                 Event.add(t.id, 'mouseout', function() {
8614                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
8615                                                 DOM.removeClass(t.id, cp + 'Hover');
8616                                 });
8617                         }
8618
8619                         t.onPostRender.dispatch(t, DOM.get(t.id));
8620                 },
8621
8622                 destroy : function() {
8623                         this.parent();
8624
8625                         Event.clear(this.id + '_text');
8626                         Event.clear(this.id + '_open');
8627                 }
8628         });
8629 })(tinymce);
8630 (function(tinymce) {
8631         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
8632
8633         tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
8634                 NativeListBox : function(id, s) {
8635                         this.parent(id, s);
8636                         this.classPrefix = 'mceNativeListBox';
8637                 },
8638
8639                 setDisabled : function(s) {
8640                         DOM.get(this.id).disabled = s;
8641                         this.setAriaProperty('disabled', s);
8642                 },
8643
8644                 isDisabled : function() {
8645                         return DOM.get(this.id).disabled;
8646                 },
8647
8648                 select : function(va) {
8649                         var t = this, fv, f;
8650
8651                         if (va == undefined)
8652                                 return t.selectByIndex(-1);
8653
8654                         // Is string or number make function selector
8655                         if (va && va.call)
8656                                 f = va;
8657                         else {
8658                                 f = function(v) {
8659                                         return v == va;
8660                                 };
8661                         }
8662
8663                         // Do we need to do something?
8664                         if (va != t.selectedValue) {
8665                                 // Find item
8666                                 each(t.items, function(o, i) {
8667                                         if (f(o.value)) {
8668                                                 fv = 1;
8669                                                 t.selectByIndex(i);
8670                                                 return false;
8671                                         }
8672                                 });
8673
8674                                 if (!fv)
8675                                         t.selectByIndex(-1);
8676                         }
8677                 },
8678
8679                 selectByIndex : function(idx) {
8680                         DOM.get(this.id).selectedIndex = idx + 1;
8681                         this.selectedValue = this.items[idx] ? this.items[idx].value : null;
8682                 },
8683
8684                 add : function(n, v, a) {
8685                         var o, t = this;
8686
8687                         a = a || {};
8688                         a.value = v;
8689
8690                         if (t.isRendered())
8691                                 DOM.add(DOM.get(this.id), 'option', a, n);
8692
8693                         o = {
8694                                 title : n,
8695                                 value : v,
8696                                 attribs : a
8697                         };
8698
8699                         t.items.push(o);
8700                         t.onAdd.dispatch(t, o);
8701                 },
8702
8703                 getLength : function() {
8704                         return this.items.length;
8705                 },
8706
8707                 renderHTML : function() {
8708                         var h, t = this;
8709
8710                         h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
8711
8712                         each(t.items, function(it) {
8713                                 h += DOM.createHTML('option', {value : it.value}, it.title);
8714                         });
8715
8716                         h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
8717                         h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
8718                         return h;
8719                 },
8720
8721                 postRender : function() {
8722                         var t = this, ch, changeListenerAdded = true;
8723
8724                         t.rendered = true;
8725
8726                         function onChange(e) {
8727                                 var v = t.items[e.target.selectedIndex - 1];
8728
8729                                 if (v && (v = v.value)) {
8730                                         t.onChange.dispatch(t, v);
8731
8732                                         if (t.settings.onselect)
8733                                                 t.settings.onselect(v);
8734                                 }
8735                         };
8736
8737                         Event.add(t.id, 'change', onChange);
8738
8739                         // Accessibility keyhandler
8740                         Event.add(t.id, 'keydown', function(e) {
8741                                 var bf;
8742
8743                                 Event.remove(t.id, 'change', ch);
8744                                 changeListenerAdded = false;
8745
8746                                 bf = Event.add(t.id, 'blur', function() {
8747                                         if (changeListenerAdded) return;
8748                                         changeListenerAdded = true;
8749                                         Event.add(t.id, 'change', onChange);
8750                                         Event.remove(t.id, 'blur', bf);
8751                                 });
8752
8753                                 if (e.keyCode == 13 || e.keyCode == 32) {
8754                                         onChange(e);
8755                                         return Event.cancel(e);
8756                                 }
8757                         });
8758
8759                         t.onPostRender.dispatch(t, DOM.get(t.id));
8760                 }
8761         });
8762 })(tinymce);
8763 (function(tinymce) {
8764         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
8765
8766         tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
8767                 MenuButton : function(id, s, ed) {
8768                         this.parent(id, s, ed);
8769
8770                         this.onRenderMenu = new tinymce.util.Dispatcher(this);
8771
8772                         s.menu_container = s.menu_container || DOM.doc.body;
8773                 },
8774
8775                 showMenu : function() {
8776                         var t = this, p1, p2, e = DOM.get(t.id), m;
8777
8778                         if (t.isDisabled())
8779                                 return;
8780
8781                         if (!t.isMenuRendered) {
8782                                 t.renderMenu();
8783                                 t.isMenuRendered = true;
8784                         }
8785
8786                         if (t.isMenuVisible)
8787                                 return t.hideMenu();
8788
8789                         p1 = DOM.getPos(t.settings.menu_container);
8790                         p2 = DOM.getPos(e);
8791
8792                         m = t.menu;
8793                         m.settings.offset_x = p2.x;
8794                         m.settings.offset_y = p2.y;
8795                         m.settings.vp_offset_x = p2.x;
8796                         m.settings.vp_offset_y = p2.y;
8797                         m.settings.keyboard_focus = t._focused;
8798                         m.showMenu(0, e.clientHeight);
8799
8800                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
8801                         t.setState('Selected', 1);
8802
8803                         t.isMenuVisible = 1;
8804                 },
8805
8806                 renderMenu : function() {
8807                         var t = this, m;
8808
8809                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
8810                                 menu_line : 1,
8811                                 'class' : this.classPrefix + 'Menu',
8812                                 icons : t.settings.icons
8813                         });
8814
8815                         m.onHideMenu.add(function() {
8816                                 t.hideMenu();
8817                                 t.focus();
8818                         });
8819
8820                         t.onRenderMenu.dispatch(t, m);
8821                         t.menu = m;
8822                 },
8823
8824                 hideMenu : function(e) {
8825                         var t = this;
8826
8827                         // Prevent double toogles by canceling the mouse click event to the button
8828                         if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
8829                                 return;
8830
8831                         if (!e || !DOM.getParent(e.target, '.mceMenu')) {
8832                                 t.setState('Selected', 0);
8833                                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
8834                                 if (t.menu)
8835                                         t.menu.hideMenu();
8836                         }
8837
8838                         t.isMenuVisible = 0;
8839                 },
8840
8841                 postRender : function() {
8842                         var t = this, s = t.settings;
8843
8844                         Event.add(t.id, 'click', function() {
8845                                 if (!t.isDisabled()) {
8846                                         if (s.onclick)
8847                                                 s.onclick(t.value);
8848
8849                                         t.showMenu();
8850                                 }
8851                         });
8852                 }
8853         });
8854 })(tinymce);
8855
8856 (function(tinymce) {
8857         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
8858
8859         tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
8860                 SplitButton : function(id, s, ed) {
8861                         this.parent(id, s, ed);
8862                         this.classPrefix = 'mceSplitButton';
8863                 },
8864
8865                 renderHTML : function() {
8866                         var h, t = this, s = t.settings, h1;
8867
8868                         h = '<tbody><tr>';
8869
8870                         if (s.image)
8871                                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
8872                         else
8873                                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
8874
8875                         h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
8876                         h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
8877         
8878                         h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
8879                         h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
8880
8881                         h += '</tr></tbody>';
8882                         h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0',  'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
8883                         return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
8884                 },
8885
8886                 postRender : function() {
8887                         var t = this, s = t.settings, activate;
8888
8889                         if (s.onclick) {
8890                                 activate = function(evt) {
8891                                         if (!t.isDisabled()) {
8892                                                 s.onclick(t.value);
8893                                                 Event.cancel(evt);
8894                                         }
8895                                 };
8896                                 Event.add(t.id + '_action', 'click', activate);
8897                                 Event.add(t.id, ['click', 'keydown'], function(evt) {
8898                                         var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
8899                                         if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
8900                                                 activate();
8901                                                 Event.cancel(evt);
8902                                         } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
8903                                                 t.showMenu();
8904                                                 Event.cancel(evt);
8905                                         }
8906                                 });
8907                         }
8908
8909                         Event.add(t.id + '_open', 'click', function (evt) {
8910                                 t.showMenu();
8911                                 Event.cancel(evt);
8912                         });
8913                         Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
8914                         Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
8915
8916                         // Old IE doesn't have hover on all elements
8917                         if (tinymce.isIE6 || !DOM.boxModel) {
8918                                 Event.add(t.id, 'mouseover', function() {
8919                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
8920                                                 DOM.addClass(t.id, 'mceSplitButtonHover');
8921                                 });
8922
8923                                 Event.add(t.id, 'mouseout', function() {
8924                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
8925                                                 DOM.removeClass(t.id, 'mceSplitButtonHover');
8926                                 });
8927                         }
8928                 },
8929
8930                 destroy : function() {
8931                         this.parent();
8932
8933                         Event.clear(this.id + '_action');
8934                         Event.clear(this.id + '_open');
8935                         Event.clear(this.id);
8936                 }
8937         });
8938 })(tinymce);
8939
8940 (function(tinymce) {
8941         var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
8942
8943         tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
8944                 ColorSplitButton : function(id, s, ed) {
8945                         var t = this;
8946
8947                         t.parent(id, s, ed);
8948
8949                         t.settings = s = tinymce.extend({
8950                                 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',
8951                                 grid_width : 8,
8952                                 default_color : '#888888'
8953                         }, t.settings);
8954
8955                         t.onShowMenu = new tinymce.util.Dispatcher(t);
8956
8957                         t.onHideMenu = new tinymce.util.Dispatcher(t);
8958
8959                         t.value = s.default_color;
8960                 },
8961
8962                 showMenu : function() {
8963                         var t = this, r, p, e, p2;
8964
8965                         if (t.isDisabled())
8966                                 return;
8967
8968                         if (!t.isMenuRendered) {
8969                                 t.renderMenu();
8970                                 t.isMenuRendered = true;
8971                         }
8972
8973                         if (t.isMenuVisible)
8974                                 return t.hideMenu();
8975
8976                         e = DOM.get(t.id);
8977                         DOM.show(t.id + '_menu');
8978                         DOM.addClass(e, 'mceSplitButtonSelected');
8979                         p2 = DOM.getPos(e);
8980                         DOM.setStyles(t.id + '_menu', {
8981                                 left : p2.x,
8982                                 top : p2.y + e.clientHeight,
8983                                 zIndex : 200000
8984                         });
8985                         e = 0;
8986
8987                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
8988                         t.onShowMenu.dispatch(t);
8989
8990                         if (t._focused) {
8991                                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
8992                                         if (e.keyCode == 27)
8993                                                 t.hideMenu();
8994                                 });
8995
8996                                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
8997                         }
8998
8999                         t.isMenuVisible = 1;
9000                 },
9001
9002                 hideMenu : function(e) {
9003                         var t = this;
9004
9005                         if (t.isMenuVisible) {
9006                                 // Prevent double toogles by canceling the mouse click event to the button
9007                                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
9008                                         return;
9009
9010                                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
9011                                         DOM.removeClass(t.id, 'mceSplitButtonSelected');
9012                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9013                                         Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
9014                                         DOM.hide(t.id + '_menu');
9015                                 }
9016
9017                                 t.isMenuVisible = 0;
9018                         }
9019                 },
9020
9021                 renderMenu : function() {
9022                         var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
9023
9024                         w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
9025                         m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
9026                         DOM.add(m, 'span', {'class' : 'mceMenuLine'});
9027
9028                         n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
9029                         tb = DOM.add(n, 'tbody');
9030
9031                         // Generate color grid
9032                         i = 0;
9033                         each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
9034                                 c = c.replace(/^#/, '');
9035
9036                                 if (!i--) {
9037                                         tr = DOM.add(tb, 'tr');
9038                                         i = s.grid_width - 1;
9039                                 }
9040
9041                                 n = DOM.add(tr, 'td');
9042                                 n = DOM.add(n, 'a', {
9043                                         role : 'option',
9044                                         href : 'javascript:;',
9045                                         style : {
9046                                                 backgroundColor : '#' + c
9047                                         },
9048                                         'title': t.editor.getLang('colors.' + c, c),
9049                                         'data-mce-color' : '#' + c
9050                                 });
9051
9052                                 if (t.editor.forcedHighContrastMode) {
9053                                         n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
9054                                         if (n.getContext && (context = n.getContext("2d"))) {
9055                                                 context.fillStyle = '#' + c;
9056                                                 context.fillRect(0, 0, 16, 16);
9057                                         } else {
9058                                                 // No point leaving a canvas element around if it's not supported for drawing on anyway.
9059                                                 DOM.remove(n);
9060                                         }
9061                                 }
9062                         });
9063
9064                         if (s.more_colors_func) {
9065                                 n = DOM.add(tb, 'tr');
9066                                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
9067                                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
9068
9069                                 Event.add(n, 'click', function(e) {
9070                                         s.more_colors_func.call(s.more_colors_scope || this);
9071                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
9072                                 });
9073                         }
9074
9075                         DOM.addClass(m, 'mceColorSplitMenu');
9076                         
9077                         new tinymce.ui.KeyboardNavigation({
9078                                 root: t.id + '_menu',
9079                                 items: DOM.select('a', t.id + '_menu'),
9080                                 onCancel: function() {
9081                                         t.hideMenu();
9082                                         t.focus();
9083                                 }
9084                         });
9085
9086                         // Prevent IE from scrolling and hindering click to occur #4019
9087                         Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
9088
9089                         Event.add(t.id + '_menu', 'click', function(e) {
9090                                 var c;
9091
9092                                 e = DOM.getParent(e.target, 'a', tb);
9093
9094                                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
9095                                         t.setColor(c);
9096
9097                                 return Event.cancel(e); // Prevent IE auto save warning
9098                         });
9099
9100                         return w;
9101                 },
9102
9103                 setColor : function(c) {
9104                         this.displayColor(c);
9105                         this.hideMenu();
9106                         this.settings.onselect(c);
9107                 },
9108                 
9109                 displayColor : function(c) {
9110                         var t = this;
9111
9112                         DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
9113
9114                         t.value = c;
9115                 },
9116
9117                 postRender : function() {
9118                         var t = this, id = t.id;
9119
9120                         t.parent();
9121                         DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
9122                         DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
9123                 },
9124
9125                 destroy : function() {
9126                         this.parent();
9127
9128                         Event.clear(this.id + '_menu');
9129                         Event.clear(this.id + '_more');
9130                         DOM.remove(this.id + '_menu');
9131                 }
9132         });
9133 })(tinymce);
9134
9135 (function(tinymce) {
9136 // Shorten class names
9137 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
9138 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
9139         renderHTML : function() {
9140                 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
9141
9142                 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
9143                 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
9144                 h.push("<span role='application'>");
9145                 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
9146                 each(controls, function(toolbar) {
9147                         h.push(toolbar.renderHTML());
9148                 });
9149                 h.push("</span>");
9150                 h.push('</div>');
9151
9152                 return h.join('');
9153         },
9154         
9155         focus : function() {
9156                 this.keyNav.focus();
9157         },
9158         
9159         postRender : function() {
9160                 var t = this, items = [];
9161
9162                 each(t.controls, function(toolbar) {
9163                         each (toolbar.controls, function(control) {
9164                                 if (control.id) {
9165                                         items.push(control);
9166                                 }
9167                         });
9168                 });
9169
9170                 t.keyNav = new tinymce.ui.KeyboardNavigation({
9171                         root: t.id,
9172                         items: items,
9173                         onCancel: function() {
9174                                 t.editor.focus();
9175                         },
9176                         excludeFromTabOrder: !t.settings.tab_focus_toolbar
9177                 });
9178         },
9179         
9180         destroy : function() {
9181                 var self = this;
9182
9183                 self.parent();
9184                 self.keyNav.destroy();
9185                 Event.clear(self.id);
9186         }
9187 });
9188 })(tinymce);
9189
9190 (function(tinymce) {
9191 // Shorten class names
9192 var dom = tinymce.DOM, each = tinymce.each;
9193 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
9194         renderHTML : function() {
9195                 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
9196
9197                 cl = t.controls;
9198                 for (i=0; i<cl.length; i++) {
9199                         // Get current control, prev control, next control and if the control is a list box or not
9200                         co = cl[i];
9201                         pr = cl[i - 1];
9202                         nx = cl[i + 1];
9203
9204                         // Add toolbar start
9205                         if (i === 0) {
9206                                 c = 'mceToolbarStart';
9207
9208                                 if (co.Button)
9209                                         c += ' mceToolbarStartButton';
9210                                 else if (co.SplitButton)
9211                                         c += ' mceToolbarStartSplitButton';
9212                                 else if (co.ListBox)
9213                                         c += ' mceToolbarStartListBox';
9214
9215                                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
9216                         }
9217
9218                         // Add toolbar end before list box and after the previous button
9219                         // This is to fix the o2k7 editor skins
9220                         if (pr && co.ListBox) {
9221                                 if (pr.Button || pr.SplitButton)
9222                                         h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
9223                         }
9224
9225                         // Render control HTML
9226
9227                         // IE 8 quick fix, needed to propertly generate a hit area for anchors
9228                         if (dom.stdMode)
9229                                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
9230                         else
9231                                 h += '<td>' + co.renderHTML() + '</td>';
9232
9233                         // Add toolbar start after list box and before the next button
9234                         // This is to fix the o2k7 editor skins
9235                         if (nx && co.ListBox) {
9236                                 if (nx.Button || nx.SplitButton)
9237                                         h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
9238                         }
9239                 }
9240
9241                 c = 'mceToolbarEnd';
9242
9243                 if (co.Button)
9244                         c += ' mceToolbarEndButton';
9245                 else if (co.SplitButton)
9246                         c += ' mceToolbarEndSplitButton';
9247                 else if (co.ListBox)
9248                         c += ' mceToolbarEndListBox';
9249
9250                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
9251
9252                 return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
9253         }
9254 });
9255 })(tinymce);
9256
9257 (function(tinymce) {
9258         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
9259
9260         tinymce.create('tinymce.AddOnManager', {
9261                 AddOnManager : function() {
9262                         var self = this;
9263
9264                         self.items = [];
9265                         self.urls = {};
9266                         self.lookup = {};
9267                         self.onAdd = new Dispatcher(self);
9268                 },
9269
9270                 get : function(n) {
9271                         return this.lookup[n];
9272                 },
9273
9274                 requireLangPack : function(n) {
9275                         var s = tinymce.settings;
9276
9277                         if (s && s.language && s.language_load !== false)
9278                                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
9279                 },
9280
9281                 add : function(id, o) {
9282                         this.items.push(o);
9283                         this.lookup[id] = o;
9284                         this.onAdd.dispatch(this, id, o);
9285
9286                         return o;
9287                 },
9288
9289                 load : function(n, u, cb, s) {
9290                         var t = this;
9291
9292                         if (t.urls[n])
9293                                 return;
9294
9295                         if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
9296                                 u = tinymce.baseURL + '/' + u;
9297
9298                         t.urls[n] = u.substring(0, u.lastIndexOf('/'));
9299
9300                         if (!t.lookup[n])
9301                                 tinymce.ScriptLoader.add(u, cb, s);
9302                 }
9303         });
9304
9305         // Create plugin and theme managers
9306         tinymce.PluginManager = new tinymce.AddOnManager();
9307         tinymce.ThemeManager = new tinymce.AddOnManager();
9308 }(tinymce));
9309
9310 (function(tinymce) {
9311         // Shorten names
9312         var each = tinymce.each, extend = tinymce.extend,
9313                 DOM = tinymce.DOM, Event = tinymce.dom.Event,
9314                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
9315                 explode = tinymce.explode,
9316                 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
9317
9318         // Setup some URLs where the editor API is located and where the document is
9319         tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
9320         if (!/[\/\\]$/.test(tinymce.documentBaseURL))
9321                 tinymce.documentBaseURL += '/';
9322
9323         tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
9324
9325         tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
9326
9327         // Add before unload listener
9328         // This was required since IE was leaking memory if you added and removed beforeunload listeners
9329         // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
9330         tinymce.onBeforeUnload = new Dispatcher(tinymce);
9331
9332         // Must be on window or IE will leak if the editor is placed in frame or iframe
9333         Event.add(window, 'beforeunload', function(e) {
9334                 tinymce.onBeforeUnload.dispatch(tinymce, e);
9335         });
9336
9337         tinymce.onAddEditor = new Dispatcher(tinymce);
9338
9339         tinymce.onRemoveEditor = new Dispatcher(tinymce);
9340
9341         tinymce.EditorManager = extend(tinymce, {
9342                 editors : [],
9343
9344                 i18n : {},
9345
9346                 activeEditor : null,
9347
9348                 init : function(s) {
9349                         var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
9350
9351                         function execCallback(se, n, s) {
9352                                 var f = se[n];
9353
9354                                 if (!f)
9355                                         return;
9356
9357                                 if (tinymce.is(f, 'string')) {
9358                                         s = f.replace(/\.\w+$/, '');
9359                                         s = s ? tinymce.resolve(s) : 0;
9360                                         f = tinymce.resolve(f);
9361                                 }
9362
9363                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
9364                         };
9365
9366                         s = extend({
9367                                 theme : "simple",
9368                                 language : "en"
9369                         }, s);
9370
9371                         t.settings = s;
9372
9373                         // Legacy call
9374                         Event.add(document, 'init', function() {
9375                                 var l, co;
9376
9377                                 execCallback(s, 'onpageload');
9378
9379                                 switch (s.mode) {
9380                                         case "exact":
9381                                                 l = s.elements || '';
9382
9383                                                 if(l.length > 0) {
9384                                                         each(explode(l), function(v) {
9385                                                                 if (DOM.get(v)) {
9386                                                                         ed = new tinymce.Editor(v, s);
9387                                                                         el.push(ed);
9388                                                                         ed.render(1);
9389                                                                 } else {
9390                                                                         each(document.forms, function(f) {
9391                                                                                 each(f.elements, function(e) {
9392                                                                                         if (e.name === v) {
9393                                                                                                 v = 'mce_editor_' + instanceCounter++;
9394                                                                                                 DOM.setAttrib(e, 'id', v);
9395
9396                                                                                                 ed = new tinymce.Editor(v, s);
9397                                                                                                 el.push(ed);
9398                                                                                                 ed.render(1);
9399                                                                                         }
9400                                                                                 });
9401                                                                         });
9402                                                                 }
9403                                                         });
9404                                                 }
9405                                                 break;
9406
9407                                         case "textareas":
9408                                         case "specific_textareas":
9409                                                 function hasClass(n, c) {
9410                                                         return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
9411                                                 };
9412
9413                                                 each(DOM.select('textarea'), function(v) {
9414                                                         if (s.editor_deselector && hasClass(v, s.editor_deselector))
9415                                                                 return;
9416
9417                                                         if (!s.editor_selector || hasClass(v, s.editor_selector)) {
9418                                                                 // Can we use the name
9419                                                                 e = DOM.get(v.name);
9420                                                                 if (!v.id && !e)
9421                                                                         v.id = v.name;
9422
9423                                                                 // Generate unique name if missing or already exists
9424                                                                 if (!v.id || t.get(v.id))
9425                                                                         v.id = DOM.uniqueId();
9426
9427                                                                 ed = new tinymce.Editor(v.id, s);
9428                                                                 el.push(ed);
9429                                                                 ed.render(1);
9430                                                         }
9431                                                 });
9432                                                 break;
9433                                 }
9434
9435                                 // Call onInit when all editors are initialized
9436                                 if (s.oninit) {
9437                                         l = co = 0;
9438
9439                                         each(el, function(ed) {
9440                                                 co++;
9441
9442                                                 if (!ed.initialized) {
9443                                                         // Wait for it
9444                                                         ed.onInit.add(function() {
9445                                                                 l++;
9446
9447                                                                 // All done
9448                                                                 if (l == co)
9449                                                                         execCallback(s, 'oninit');
9450                                                         });
9451                                                 } else
9452                                                         l++;
9453
9454                                                 // All done
9455                                                 if (l == co)
9456                                                         execCallback(s, 'oninit');                                      
9457                                         });
9458                                 }
9459                         });
9460                 },
9461
9462                 get : function(id) {
9463                         if (id === undefined)
9464                                 return this.editors;
9465
9466                         return this.editors[id];
9467                 },
9468
9469                 getInstanceById : function(id) {
9470                         return this.get(id);
9471                 },
9472
9473                 add : function(editor) {
9474                         var self = this, editors = self.editors;
9475
9476                         // Add named and index editor instance
9477                         editors[editor.id] = editor;
9478                         editors.push(editor);
9479
9480                         self._setActive(editor);
9481                         self.onAddEditor.dispatch(self, editor);
9482
9483
9484                         // Patch the tinymce.Editor instance with jQuery adapter logic
9485                         if (tinymce.adapter)
9486                                 tinymce.adapter.patchEditor(editor);
9487
9488
9489                         return editor;
9490                 },
9491
9492                 remove : function(editor) {
9493                         var t = this, i, editors = t.editors;
9494
9495                         // Not in the collection
9496                         if (!editors[editor.id])
9497                                 return null;
9498
9499                         delete editors[editor.id];
9500
9501                         for (i = 0; i < editors.length; i++) {
9502                                 if (editors[i] == editor) {
9503                                         editors.splice(i, 1);
9504                                         break;
9505                                 }
9506                         }
9507
9508                         // Select another editor since the active one was removed
9509                         if (t.activeEditor == editor)
9510                                 t._setActive(editors[0]);
9511
9512                         editor.destroy();
9513                         t.onRemoveEditor.dispatch(t, editor);
9514
9515                         return editor;
9516                 },
9517
9518                 execCommand : function(c, u, v) {
9519                         var t = this, ed = t.get(v), w;
9520
9521                         // Manager commands
9522                         switch (c) {
9523                                 case "mceFocus":
9524                                         ed.focus();
9525                                         return true;
9526
9527                                 case "mceAddEditor":
9528                                 case "mceAddControl":
9529                                         if (!t.get(v))
9530                                                 new tinymce.Editor(v, t.settings).render();
9531
9532                                         return true;
9533
9534                                 case "mceAddFrameControl":
9535                                         w = v.window;
9536
9537                                         // Add tinyMCE global instance and tinymce namespace to specified window
9538                                         w.tinyMCE = tinyMCE;
9539                                         w.tinymce = tinymce;
9540
9541                                         tinymce.DOM.doc = w.document;
9542                                         tinymce.DOM.win = w;
9543
9544                                         ed = new tinymce.Editor(v.element_id, v);
9545                                         ed.render();
9546
9547                                         // Fix IE memory leaks
9548                                         if (tinymce.isIE) {
9549                                                 function clr() {
9550                                                         ed.destroy();
9551                                                         w.detachEvent('onunload', clr);
9552                                                         w = w.tinyMCE = w.tinymce = null; // IE leak
9553                                                 };
9554
9555                                                 w.attachEvent('onunload', clr);
9556                                         }
9557
9558                                         v.page_window = null;
9559
9560                                         return true;
9561
9562                                 case "mceRemoveEditor":
9563                                 case "mceRemoveControl":
9564                                         if (ed)
9565                                                 ed.remove();
9566
9567                                         return true;
9568
9569                                 case 'mceToggleEditor':
9570                                         if (!ed) {
9571                                                 t.execCommand('mceAddControl', 0, v);
9572                                                 return true;
9573                                         }
9574
9575                                         if (ed.isHidden())
9576                                                 ed.show();
9577                                         else
9578                                                 ed.hide();
9579
9580                                         return true;
9581                         }
9582
9583                         // Run command on active editor
9584                         if (t.activeEditor)
9585                                 return t.activeEditor.execCommand(c, u, v);
9586
9587                         return false;
9588                 },
9589
9590                 execInstanceCommand : function(id, c, u, v) {
9591                         var ed = this.get(id);
9592
9593                         if (ed)
9594                                 return ed.execCommand(c, u, v);
9595
9596                         return false;
9597                 },
9598
9599                 triggerSave : function() {
9600                         each(this.editors, function(e) {
9601                                 e.save();
9602                         });
9603                 },
9604
9605                 addI18n : function(p, o) {
9606                         var lo, i18n = this.i18n;
9607
9608                         if (!tinymce.is(p, 'string')) {
9609                                 each(p, function(o, lc) {
9610                                         each(o, function(o, g) {
9611                                                 each(o, function(o, k) {
9612                                                         if (g === 'common')
9613                                                                 i18n[lc + '.' + k] = o;
9614                                                         else
9615                                                                 i18n[lc + '.' + g + '.' + k] = o;
9616                                                 });
9617                                         });
9618                                 });
9619                         } else {
9620                                 each(o, function(o, k) {
9621                                         i18n[p + '.' + k] = o;
9622                                 });
9623                         }
9624                 },
9625
9626                 // Private methods
9627
9628                 _setActive : function(editor) {
9629                         this.selectedInstance = this.activeEditor = editor;
9630                 }
9631         });
9632 })(tinymce);
9633
9634 (function(tinymce) {
9635         // Shorten these names
9636         var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
9637                 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
9638                 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
9639                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
9640                 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
9641
9642         tinymce.create('tinymce.Editor', {
9643                 Editor : function(id, s) {
9644                         var t = this;
9645
9646                         t.id = t.editorId = id;
9647
9648                         t.execCommands = {};
9649                         t.queryStateCommands = {};
9650                         t.queryValueCommands = {};
9651
9652                         t.isNotDirty = false;
9653
9654                         t.plugins = {};
9655
9656                         // Add events to the editor
9657                         each([
9658                                 'onPreInit',
9659
9660                                 'onBeforeRenderUI',
9661
9662                                 'onPostRender',
9663
9664                                 'onInit',
9665
9666                                 'onRemove',
9667
9668                                 'onActivate',
9669
9670                                 'onDeactivate',
9671
9672                                 'onClick',
9673
9674                                 'onEvent',
9675
9676                                 'onMouseUp',
9677
9678                                 'onMouseDown',
9679
9680                                 'onDblClick',
9681
9682                                 'onKeyDown',
9683
9684                                 'onKeyUp',
9685
9686                                 'onKeyPress',
9687
9688                                 'onContextMenu',
9689
9690                                 'onSubmit',
9691
9692                                 'onReset',
9693
9694                                 'onPaste',
9695
9696                                 'onPreProcess',
9697
9698                                 'onPostProcess',
9699
9700                                 'onBeforeSetContent',
9701
9702                                 'onBeforeGetContent',
9703
9704                                 'onSetContent',
9705
9706                                 'onGetContent',
9707
9708                                 'onLoadContent',
9709
9710                                 'onSaveContent',
9711
9712                                 'onNodeChange',
9713
9714                                 'onChange',
9715
9716                                 'onBeforeExecCommand',
9717
9718                                 'onExecCommand',
9719
9720                                 'onUndo',
9721
9722                                 'onRedo',
9723
9724                                 'onVisualAid',
9725
9726                                 'onSetProgressState'
9727                         ], function(e) {
9728                                 t[e] = new Dispatcher(t);
9729                         });
9730
9731                         t.settings = s = extend({
9732                                 id : id,
9733                                 language : 'en',
9734                                 docs_language : 'en',
9735                                 theme : 'simple',
9736                                 skin : 'default',
9737                                 delta_width : 0,
9738                                 delta_height : 0,
9739                                 popup_css : '',
9740                                 plugins : '',
9741                                 document_base_url : tinymce.documentBaseURL,
9742                                 add_form_submit_trigger : 1,
9743                                 submit_patch : 1,
9744                                 add_unload_trigger : 1,
9745                                 convert_urls : 1,
9746                                 relative_urls : 1,
9747                                 remove_script_host : 1,
9748                                 table_inline_editing : 0,
9749                                 object_resizing : 1,
9750                                 cleanup : 1,
9751                                 accessibility_focus : 1,
9752                                 custom_shortcuts : 1,
9753                                 custom_undo_redo_keyboard_shortcuts : 1,
9754                                 custom_undo_redo_restore_selection : 1,
9755                                 custom_undo_redo : 1,
9756                                 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
9757                                 visual_table_class : 'mceItemTable',
9758                                 visual : 1,
9759                                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
9760                                 apply_source_formatting : 1,
9761                                 directionality : 'ltr',
9762                                 forced_root_block : 'p',
9763                                 hidden_input : 1,
9764                                 padd_empty_editor : 1,
9765                                 render_ui : 1,
9766                                 init_theme : 1,
9767                                 force_p_newlines : 1,
9768                                 indentation : '30px',
9769                                 keep_styles : 1,
9770                                 fix_table_elements : 1,
9771                                 inline_styles : 1,
9772                                 convert_fonts_to_spans : true,
9773                                 indent : 'simple',
9774                                 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
9775                                 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
9776                                 validate : true,
9777                                 entity_encoding : 'named',
9778                                 url_converter : t.convertURL,
9779                                 url_converter_scope : t,
9780                                 ie7_compat : true
9781                         }, s);
9782
9783                         t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
9784                                 base_uri : tinyMCE.baseURI
9785                         });
9786
9787                         t.baseURI = tinymce.baseURI;
9788
9789                         t.contentCSS = [];
9790
9791                         // Call setup
9792                         t.execCallback('setup', t);
9793                 },
9794
9795                 render : function(nst) {
9796                         var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
9797
9798                         // Page is not loaded yet, wait for it
9799                         if (!Event.domLoaded) {
9800                                 Event.add(document, 'init', function() {
9801                                         t.render();
9802                                 });
9803                                 return;
9804                         }
9805
9806                         tinyMCE.settings = s;
9807
9808                         // Element not found, then skip initialization
9809                         if (!t.getElement())
9810                                 return;
9811
9812                         // Is a iPad/iPhone, then skip initialization. We need to sniff here since the
9813                         // browser says it has contentEditable support but there is no visible caret
9814                         // We will remove this check ones Apple implements full contentEditable support
9815                         if (tinymce.isIDevice)
9816                                 return;
9817
9818                         // Add hidden input for non input elements inside form elements
9819                         if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
9820                                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
9821
9822                         if (tinymce.WindowManager)
9823                                 t.windowManager = new tinymce.WindowManager(t);
9824
9825                         if (s.encoding == 'xml') {
9826                                 t.onGetContent.add(function(ed, o) {
9827                                         if (o.save)
9828                                                 o.content = DOM.encode(o.content);
9829                                 });
9830                         }
9831
9832                         if (s.add_form_submit_trigger) {
9833                                 t.onSubmit.addToTop(function() {
9834                                         if (t.initialized) {
9835                                                 t.save();
9836                                                 t.isNotDirty = 1;
9837                                         }
9838                                 });
9839                         }
9840
9841                         if (s.add_unload_trigger) {
9842                                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
9843                                         if (t.initialized && !t.destroyed && !t.isHidden())
9844                                                 t.save({format : 'raw', no_events : true});
9845                                 });
9846                         }
9847
9848                         tinymce.addUnload(t.destroy, t);
9849
9850                         if (s.submit_patch) {
9851                                 t.onBeforeRenderUI.add(function() {
9852                                         var n = t.getElement().form;
9853
9854                                         if (!n)
9855                                                 return;
9856
9857                                         // Already patched
9858                                         if (n._mceOldSubmit)
9859                                                 return;
9860
9861                                         // Check page uses id="submit" or name="submit" for it's submit button
9862                                         if (!n.submit.nodeType && !n.submit.length) {
9863                                                 t.formElement = n;
9864                                                 n._mceOldSubmit = n.submit;
9865                                                 n.submit = function() {
9866                                                         // Save all instances
9867                                                         tinymce.triggerSave();
9868                                                         t.isNotDirty = 1;
9869
9870                                                         return t.formElement._mceOldSubmit(t.formElement);
9871                                                 };
9872                                         }
9873
9874                                         n = null;
9875                                 });
9876                         }
9877
9878                         // Load scripts
9879                         function loadScripts() {
9880                                 if (s.language && s.language_load !== false)
9881                                         sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
9882
9883                                 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
9884                                         ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
9885
9886                                 each(explode(s.plugins), function(p) {
9887                                         if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
9888                                                 // Skip safari plugin, since it is removed as of 3.3b1
9889                                                 if (p == 'safari')
9890                                                         return;
9891
9892                                                 PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
9893                                         }
9894                                 });
9895
9896                                 // Init when que is loaded
9897                                 sl.loadQueue(function() {
9898                                         if (!t.removed)
9899                                                 t.init();
9900                                 });
9901                         };
9902
9903                         loadScripts();
9904                 },
9905
9906                 init : function() {
9907                         var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i;
9908
9909                         tinymce.add(t);
9910
9911                         s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
9912
9913                         if (s.theme) {
9914                                 s.theme = s.theme.replace(/-/, '');
9915                                 o = ThemeManager.get(s.theme);
9916                                 t.theme = new o();
9917
9918                                 if (t.theme.init && s.init_theme)
9919                                         t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
9920                         }
9921
9922                         // Create all plugins
9923                         each(explode(s.plugins.replace(/\-/g, '')), function(p) {
9924                                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
9925
9926                                 if (c) {
9927                                         po = new c(t, u);
9928
9929                                         t.plugins[p] = po;
9930
9931                                         if (po.init)
9932                                                 po.init(t, u);
9933                                 }
9934                         });
9935
9936                         // Setup popup CSS path(s)
9937                         if (s.popup_css !== false) {
9938                                 if (s.popup_css)
9939                                         s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
9940                                 else
9941                                         s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
9942                         }
9943
9944                         if (s.popup_css_add)
9945                                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
9946
9947                         t.controlManager = new tinymce.ControlManager(t);
9948
9949                         if (s.custom_undo_redo) {
9950                                 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
9951                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
9952                                                 t.undoManager.beforeChange();
9953                                 });
9954
9955                                 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
9956                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
9957                                                 t.undoManager.add();
9958                                 });
9959                         }
9960
9961                         t.onExecCommand.add(function(ed, c) {
9962                                 // Don't refresh the select lists until caret move
9963                                 if (!/^(FontName|FontSize)$/.test(c))
9964                                         t.nodeChanged();
9965                         });
9966
9967                         // Remove ghost selections on images and tables in Gecko
9968                         if (isGecko) {
9969                                 function repaint(a, o) {
9970                                         if (!o || !o.initial)
9971                                                 t.execCommand('mceRepaint');
9972                                 };
9973
9974                                 t.onUndo.add(repaint);
9975                                 t.onRedo.add(repaint);
9976                                 t.onSetContent.add(repaint);
9977                         }
9978
9979                         // Enables users to override the control factory
9980                         t.onBeforeRenderUI.dispatch(t, t.controlManager);
9981
9982                         // Measure box
9983                         if (s.render_ui) {
9984                                 w = s.width || e.style.width || e.offsetWidth;
9985                                 h = s.height || e.style.height || e.offsetHeight;
9986                                 t.orgDisplay = e.style.display;
9987                                 re = /^[0-9\.]+(|px)$/i;
9988
9989                                 if (re.test('' + w))
9990                                         w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
9991
9992                                 if (re.test('' + h))
9993                                         h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
9994
9995                                 // Render UI
9996                                 o = t.theme.renderUI({
9997                                         targetNode : e,
9998                                         width : w,
9999                                         height : h,
10000                                         deltaWidth : s.delta_width,
10001                                         deltaHeight : s.delta_height
10002                                 });
10003
10004                                 t.editorContainer = o.editorContainer;
10005                         }
10006
10007
10008                         // User specified a document.domain value
10009                         if (document.domain && location.hostname != document.domain)
10010                                 tinymce.relaxedDomain = document.domain;
10011
10012                         // Resize editor
10013                         DOM.setStyles(o.sizeContainer || o.editorContainer, {
10014                                 width : w,
10015                                 height : h
10016                         });
10017
10018                         // Load specified content CSS last
10019                         if (s.content_css) {
10020                                 tinymce.each(explode(s.content_css), function(u) {
10021                                         t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
10022                                 });
10023                         }
10024
10025                         h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
10026                         if (h < 100)
10027                                 h = 100;
10028
10029                         t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
10030
10031                         // We only need to override paths if we have to
10032                         // IE has a bug where it remove site absolute urls to relative ones if this is specified
10033                         if (s.document_base_url != tinymce.documentBaseURL)
10034                                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
10035
10036                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
10037                         if (s.ie7_compat)
10038                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
10039                         else
10040                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
10041
10042                         t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
10043
10044                         // Firefox 2 doesn't load stylesheets correctly this way
10045                         if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
10046                                 for (i = 0; i < t.contentCSS.length; i++)
10047                                         t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
10048
10049                                 t.contentCSS = [];
10050                         }
10051
10052                         bi = s.body_id || 'tinymce';
10053                         if (bi.indexOf('=') != -1) {
10054                                 bi = t.getParam('body_id', '', 'hash');
10055                                 bi = bi[t.id] || bi;
10056                         }
10057
10058                         bc = s.body_class || '';
10059                         if (bc.indexOf('=') != -1) {
10060                                 bc = t.getParam('body_class', '', 'hash');
10061                                 bc = bc[t.id] || '';
10062                         }
10063
10064                         t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
10065
10066                         // Domain relaxing enabled, then set document domain
10067                         if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
10068                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
10069                                 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();})()';                         
10070                         }
10071
10072                         // Create iframe
10073                         // TODO: ACC add the appropriate description on this.
10074                         n = DOM.add(o.iframeContainer, 'iframe', { 
10075                                 id : t.id + "_ifr",
10076                                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
10077                                 frameBorder : '0', 
10078                                 title : s.aria_label,
10079                                 style : {
10080                                         width : '100%',
10081                                         height : h
10082                                 }
10083                         });
10084
10085                         t.contentAreaContainer = o.iframeContainer;
10086                         DOM.get(o.editorContainer).style.display = t.orgDisplay;
10087                         DOM.get(t.id).style.display = 'none';
10088                         DOM.setAttrib(t.id, 'aria-hidden', true);
10089
10090                         if (!tinymce.relaxedDomain || !u)
10091                                 t.setupIframe();
10092
10093                         e = n = o = null; // Cleanup
10094                 },
10095
10096                 setupIframe : function() {
10097                         var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
10098
10099                         // Setup iframe body
10100                         if (!isIE || !tinymce.relaxedDomain) {
10101                                 d.open();
10102                                 d.write(t.iframeHTML);
10103                                 d.close();
10104
10105                                 if (tinymce.relaxedDomain)
10106                                         d.domain = tinymce.relaxedDomain;
10107                         }
10108
10109                         // Design mode needs to be added here Ctrl+A will fail otherwise
10110                         if (!isIE) {
10111                                 try {
10112                                         if (!s.readonly)
10113                                                 d.designMode = 'On';
10114                                 } catch (ex) {
10115                                         // Will fail on Gecko if the editor is placed in an hidden container element
10116                                         // The design mode will be set ones the editor is focused
10117                                 }
10118                         }
10119
10120                         // IE needs to use contentEditable or it will display non secure items for HTTPS
10121                         if (isIE) {
10122                                 // It will not steal focus if we hide it while setting contentEditable
10123                                 b = t.getBody();
10124                                 DOM.hide(b);
10125
10126                                 if (!s.readonly)
10127                                         b.contentEditable = true;
10128
10129                                 DOM.show(b);
10130                         }
10131
10132                         t.schema = new tinymce.html.Schema(s);
10133
10134                         t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
10135                                 keep_values : true,
10136                                 url_converter : t.convertURL,
10137                                 url_converter_scope : t,
10138                                 hex_colors : s.force_hex_style_colors,
10139                                 class_filter : s.class_filter,
10140                                 update_styles : 1,
10141                                 fix_ie_paragraphs : 1,
10142                                 schema : t.schema
10143                         });
10144
10145                         t.parser = new tinymce.html.DomParser(s, t.schema);
10146
10147                         // Force anchor names closed
10148                         t.parser.addAttributeFilter('name', function(nodes, name) {
10149                                 var i = nodes.length, sibling, prevSibling, parent, node;
10150
10151                                 while (i--) {
10152                                         node = nodes[i];
10153                                         if (node.name === 'a' && node.firstChild) {
10154                                                 parent = node.parent;
10155
10156                                                 // Move children after current node
10157                                                 sibling = node.lastChild;
10158                                                 do {
10159                                                         prevSibling = sibling.prev;
10160                                                         parent.insert(sibling, node);
10161                                                         sibling = prevSibling;
10162                                                 } while (sibling);
10163                                         }
10164                                 }
10165                         });
10166
10167                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style
10168                         t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
10169                                 var i = nodes.length, node, dom = t.dom, value;
10170
10171                                 while (i--) {
10172                                         node = nodes[i];
10173                                         value = node.attr(name);
10174
10175                                         if (name === "style")
10176                                                 node.attr('data-mce-style', dom.serializeStyle(dom.parseStyle(value), node.name));
10177                                         else
10178                                                 node.attr('data-mce-' + name, t.convertURL(value, name, node.name));
10179                                 }
10180                         });
10181
10182                         // Keep scripts from executing
10183                         t.parser.addNodeFilter('script', function(nodes, name) {
10184                                 var i = nodes.length;
10185
10186                                 while (i--)
10187                                         nodes[i].attr('type', 'mce-text/javascript');
10188                         });
10189
10190                         t.parser.addNodeFilter('#cdata', function(nodes, name) {
10191                                 var i = nodes.length, node;
10192
10193                                 while (i--) {
10194                                         node = nodes[i];
10195                                         node.type = 8;
10196                                         node.name = '#comment';
10197                                         node.value = '[CDATA[' + node.value + ']]';
10198                                 }
10199                         });
10200
10201                         t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
10202                                 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
10203
10204                                 while (i--) {
10205                                         node = nodes[i];
10206
10207                                         if (node.isEmpty(nonEmptyElements))
10208                                                 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
10209                                 }
10210                         });
10211
10212                         t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
10213
10214                         t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
10215
10216                         t.formatter = new tinymce.Formatter(this);
10217
10218                         // Register default formats
10219                         t.formatter.register({
10220                                 alignleft : [
10221                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
10222                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
10223                                 ],
10224
10225                                 aligncenter : [
10226                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
10227                                         {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
10228                                         {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
10229                                 ],
10230
10231                                 alignright : [
10232                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
10233                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
10234                                 ],
10235
10236                                 alignfull : [
10237                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
10238                                 ],
10239
10240                                 bold : [
10241                                         {inline : 'strong', remove : 'all'},
10242                                         {inline : 'span', styles : {fontWeight : 'bold'}},
10243                                         {inline : 'b', remove : 'all'}
10244                                 ],
10245
10246                                 italic : [
10247                                         {inline : 'em', remove : 'all'},
10248                                         {inline : 'span', styles : {fontStyle : 'italic'}},
10249                                         {inline : 'i', remove : 'all'}
10250                                 ],
10251
10252                                 underline : [
10253                                         {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
10254                                         {inline : 'u', remove : 'all'}
10255                                 ],
10256
10257                                 strikethrough : [
10258                                         {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
10259                                         {inline : 'strike', remove : 'all'}
10260                                 ],
10261
10262                                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
10263                                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
10264                                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
10265                                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
10266                                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
10267                                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
10268                                 subscript : {inline : 'sub'},
10269                                 superscript : {inline : 'sup'},
10270
10271                                 removeformat : [
10272                                         {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
10273                                         {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
10274                                         {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
10275                                 ]
10276                         });
10277
10278                         // Register default block formats
10279                         each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
10280                                 t.formatter.register(name, {block : name, remove : 'all'});
10281                         });
10282
10283                         // Register user defined formats
10284                         t.formatter.register(t.settings.formats);
10285
10286                         t.undoManager = new tinymce.UndoManager(t);
10287
10288                         // Pass through
10289                         t.undoManager.onAdd.add(function(um, l) {
10290                                 if (um.hasUndo())
10291                                         return t.onChange.dispatch(t, l, um);
10292                         });
10293
10294                         t.undoManager.onUndo.add(function(um, l) {
10295                                 return t.onUndo.dispatch(t, l, um);
10296                         });
10297
10298                         t.undoManager.onRedo.add(function(um, l) {
10299                                 return t.onRedo.dispatch(t, l, um);
10300                         });
10301
10302                         t.forceBlocks = new tinymce.ForceBlocks(t, {
10303                                 forced_root_block : s.forced_root_block
10304                         });
10305
10306                         t.editorCommands = new tinymce.EditorCommands(t);
10307
10308                         // Pass through
10309                         t.serializer.onPreProcess.add(function(se, o) {
10310                                 return t.onPreProcess.dispatch(t, o, se);
10311                         });
10312
10313                         t.serializer.onPostProcess.add(function(se, o) {
10314                                 return t.onPostProcess.dispatch(t, o, se);
10315                         });
10316
10317                         t.onPreInit.dispatch(t);
10318
10319                         if (!s.gecko_spellcheck)
10320                                 t.getBody().spellcheck = 0;
10321
10322                         if (!s.readonly)
10323                                 t._addEvents();
10324
10325                         t.controlManager.onPostRender.dispatch(t, t.controlManager);
10326                         t.onPostRender.dispatch(t);
10327
10328                         if (s.directionality)
10329                                 t.getBody().dir = s.directionality;
10330
10331                         if (s.nowrap)
10332                                 t.getBody().style.whiteSpace = "nowrap";
10333
10334                         if (s.handle_node_change_callback) {
10335                                 t.onNodeChange.add(function(ed, cm, n) {
10336                                         t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
10337                                 });
10338                         }
10339
10340                         if (s.save_callback) {
10341                                 t.onSaveContent.add(function(ed, o) {
10342                                         var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
10343
10344                                         if (h)
10345                                                 o.content = h;
10346                                 });
10347                         }
10348
10349                         if (s.onchange_callback) {
10350                                 t.onChange.add(function(ed, l) {
10351                                         t.execCallback('onchange_callback', t, l);
10352                                 });
10353                         }
10354
10355                         if (s.protect) {
10356                                 t.onBeforeSetContent.add(function(ed, o) {
10357                                         if (s.protect) {
10358                                                 each(s.protect, function(pattern) {
10359                                                         o.content = o.content.replace(pattern, function(str) {
10360                                                                 return '<!--mce:protected ' + escape(str) + '-->';
10361                                                         });
10362                                                 });
10363                                         }
10364                                 });
10365                         }
10366
10367                         if (s.convert_newlines_to_brs) {
10368                                 t.onBeforeSetContent.add(function(ed, o) {
10369                                         if (o.initial)
10370                                                 o.content = o.content.replace(/\r?\n/g, '<br />');
10371                                 });
10372                         }
10373
10374                         if (s.preformatted) {
10375                                 t.onPostProcess.add(function(ed, o) {
10376                                         o.content = o.content.replace(/^\s*<pre.*?>/, '');
10377                                         o.content = o.content.replace(/<\/pre>\s*$/, '');
10378
10379                                         if (o.set)
10380                                                 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
10381                                 });
10382                         }
10383
10384                         if (s.verify_css_classes) {
10385                                 t.serializer.attribValueFilter = function(n, v) {
10386                                         var s, cl;
10387
10388                                         if (n == 'class') {
10389                                                 // Build regexp for classes
10390                                                 if (!t.classesRE) {
10391                                                         cl = t.dom.getClasses();
10392
10393                                                         if (cl.length > 0) {
10394                                                                 s = '';
10395
10396                                                                 each (cl, function(o) {
10397                                                                         s += (s ? '|' : '') + o['class'];
10398                                                                 });
10399
10400                                                                 t.classesRE = new RegExp('(' + s + ')', 'gi');
10401                                                         }
10402                                                 }
10403
10404                                                 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
10405                                         }
10406
10407                                         return v;
10408                                 };
10409                         }
10410
10411                         if (s.cleanup_callback) {
10412                                 t.onBeforeSetContent.add(function(ed, o) {
10413                                         o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
10414                                 });
10415
10416                                 t.onPreProcess.add(function(ed, o) {
10417                                         if (o.set)
10418                                                 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
10419
10420                                         if (o.get)
10421                                                 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
10422                                 });
10423
10424                                 t.onPostProcess.add(function(ed, o) {
10425                                         if (o.set)
10426                                                 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
10427
10428                                         if (o.get)                                              
10429                                                 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
10430                                 });
10431                         }
10432
10433                         if (s.save_callback) {
10434                                 t.onGetContent.add(function(ed, o) {
10435                                         if (o.save)
10436                                                 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
10437                                 });
10438                         }
10439
10440                         if (s.handle_event_callback) {
10441                                 t.onEvent.add(function(ed, e, o) {
10442                                         if (t.execCallback('handle_event_callback', e, ed, o) === false)
10443                                                 Event.cancel(e);
10444                                 });
10445                         }
10446
10447                         // Add visual aids when new contents is added
10448                         t.onSetContent.add(function() {
10449                                 t.addVisual(t.getBody());
10450                         });
10451
10452                         // Remove empty contents
10453                         if (s.padd_empty_editor) {
10454                                 t.onPostProcess.add(function(ed, o) {
10455                                         o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
10456                                 });
10457                         }
10458
10459                         if (isGecko) {
10460                                 // Fix gecko link bug, when a link is placed at the end of block elements there is
10461                                 // no way to move the caret behind the link. This fix adds a bogus br element after the link
10462                                 function fixLinks(ed, o) {
10463                                         each(ed.dom.select('a'), function(n) {
10464                                                 var pn = n.parentNode;
10465
10466                                                 if (ed.dom.isBlock(pn) && pn.lastChild === n)
10467                                                         ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
10468                                         });
10469                                 };
10470
10471                                 t.onExecCommand.add(function(ed, cmd) {
10472                                         if (cmd === 'CreateLink')
10473                                                 fixLinks(ed);
10474                                 });
10475
10476                                 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
10477
10478                                 if (!s.readonly) {
10479                                         try {
10480                                                 // Design mode must be set here once again to fix a bug where
10481                                                 // Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
10482                                                 d.designMode = 'Off';
10483                                                 d.designMode = 'On';
10484                                         } catch (ex) {
10485                                                 // Will fail on Gecko if the editor is placed in an hidden container element
10486                                                 // The design mode will be set ones the editor is focused
10487                                         }
10488                                 }
10489                         }
10490
10491                         // A small timeout was needed since firefox will remove. Bug: #1838304
10492                         setTimeout(function () {
10493                                 if (t.removed)
10494                                         return;
10495
10496                                 t.load({initial : true, format : 'html'});
10497                                 t.startContent = t.getContent({format : 'raw'});
10498                                 t.undoManager.add();
10499                                 t.initialized = true;
10500
10501                                 t.onInit.dispatch(t);
10502                                 t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
10503                                 t.execCallback('init_instance_callback', t);
10504                                 t.focus(true);
10505                                 t.nodeChanged({initial : 1});
10506
10507                                 // Load specified content CSS last
10508                                 each(t.contentCSS, function(u) {
10509                                         t.dom.loadCSS(u);
10510                                 });
10511
10512                                 // Handle auto focus
10513                                 if (s.auto_focus) {
10514                                         setTimeout(function () {
10515                                                 var ed = tinymce.get(s.auto_focus);
10516
10517                                                 ed.selection.select(ed.getBody(), 1);
10518                                                 ed.selection.collapse(1);
10519                                                 ed.getWin().focus();
10520                                         }, 100);
10521                                 }
10522                         }, 1);
10523         
10524                         e = null;
10525                 },
10526
10527
10528                 focus : function(sf) {
10529                         var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
10530
10531                         if (!sf) {
10532                                 // Get selected control element
10533                                 ieRng = t.selection.getRng();
10534                                 if (ieRng.item) {
10535                                         controlElm = ieRng.item(0);
10536                                 }
10537
10538                                 // Is not content editable
10539                                 if (!ce)
10540                                         t.getWin().focus();
10541
10542                                 // Restore selected control element
10543                                 // This is needed when for example an image is selected within a
10544                                 // layer a call to focus will then remove the control selection
10545                                 if (controlElm && controlElm.ownerDocument == doc) {
10546                                         ieRng = doc.body.createControlRange();
10547                                         ieRng.addElement(controlElm);
10548                                         ieRng.select();
10549                                 }
10550
10551                         }
10552
10553                         if (tinymce.activeEditor != t) {
10554                                 if ((oed = tinymce.activeEditor) != null)
10555                                         oed.onDeactivate.dispatch(oed, t);
10556
10557                                 t.onActivate.dispatch(t, oed);
10558                         }
10559
10560                         tinymce._setActive(t);
10561                 },
10562
10563                 execCallback : function(n) {
10564                         var t = this, f = t.settings[n], s;
10565
10566                         if (!f)
10567                                 return;
10568
10569                         // Look through lookup
10570                         if (t.callbackLookup && (s = t.callbackLookup[n])) {
10571                                 f = s.func;
10572                                 s = s.scope;
10573                         }
10574
10575                         if (is(f, 'string')) {
10576                                 s = f.replace(/\.\w+$/, '');
10577                                 s = s ? tinymce.resolve(s) : 0;
10578                                 f = tinymce.resolve(f);
10579                                 t.callbackLookup = t.callbackLookup || {};
10580                                 t.callbackLookup[n] = {func : f, scope : s};
10581                         }
10582
10583                         return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
10584                 },
10585
10586                 translate : function(s) {
10587                         var c = this.settings.language || 'en', i18n = tinymce.i18n;
10588
10589                         if (!s)
10590                                 return '';
10591
10592                         return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
10593                                 return i18n[c + '.' + b] || '{#' + b + '}';
10594                         });
10595                 },
10596
10597                 getLang : function(n, dv) {
10598                         return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
10599                 },
10600
10601                 getParam : function(n, dv, ty) {
10602                         var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
10603
10604                         if (ty === 'hash') {
10605                                 o = {};
10606
10607                                 if (is(v, 'string')) {
10608                                         each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
10609                                                 v = v.split('=');
10610
10611                                                 if (v.length > 1)
10612                                                         o[tr(v[0])] = tr(v[1]);
10613                                                 else
10614                                                         o[tr(v[0])] = tr(v);
10615                                         });
10616                                 } else
10617                                         o = v;
10618
10619                                 return o;
10620                         }
10621
10622                         return v;
10623                 },
10624
10625                 nodeChanged : function(o) {
10626                         var t = this, s = t.selection, n = s.getStart() || t.getBody();
10627
10628                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
10629                         if (t.initialized) {
10630                                 o = o || {};
10631                                 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
10632
10633                                 // Get parents and add them to object
10634                                 o.parents = [];
10635                                 t.dom.getParent(n, function(node) {
10636                                         if (node.nodeName == 'BODY')
10637                                                 return true;
10638
10639                                         o.parents.push(node);
10640                                 });
10641
10642                                 t.onNodeChange.dispatch(
10643                                         t,
10644                                         o ? o.controlManager || t.controlManager : t.controlManager,
10645                                         n,
10646                                         s.isCollapsed(),
10647                                         o
10648                                 );
10649                         }
10650                 },
10651
10652                 addButton : function(n, s) {
10653                         var t = this;
10654
10655                         t.buttons = t.buttons || {};
10656                         t.buttons[n] = s;
10657                 },
10658
10659                 addCommand : function(name, callback, scope) {
10660                         this.execCommands[name] = {func : callback, scope : scope || this};
10661                 },
10662
10663                 addQueryStateHandler : function(name, callback, scope) {
10664                         this.queryStateCommands[name] = {func : callback, scope : scope || this};
10665                 },
10666
10667                 addQueryValueHandler : function(name, callback, scope) {
10668                         this.queryValueCommands[name] = {func : callback, scope : scope || this};
10669                 },
10670
10671                 addShortcut : function(pa, desc, cmd_func, sc) {
10672                         var t = this, c;
10673
10674                         if (!t.settings.custom_shortcuts)
10675                                 return false;
10676
10677                         t.shortcuts = t.shortcuts || {};
10678
10679                         if (is(cmd_func, 'string')) {
10680                                 c = cmd_func;
10681
10682                                 cmd_func = function() {
10683                                         t.execCommand(c, false, null);
10684                                 };
10685                         }
10686
10687                         if (is(cmd_func, 'object')) {
10688                                 c = cmd_func;
10689
10690                                 cmd_func = function() {
10691                                         t.execCommand(c[0], c[1], c[2]);
10692                                 };
10693                         }
10694
10695                         each(explode(pa), function(pa) {
10696                                 var o = {
10697                                         func : cmd_func,
10698                                         scope : sc || this,
10699                                         desc : desc,
10700                                         alt : false,
10701                                         ctrl : false,
10702                                         shift : false
10703                                 };
10704
10705                                 each(explode(pa, '+'), function(v) {
10706                                         switch (v) {
10707                                                 case 'alt':
10708                                                 case 'ctrl':
10709                                                 case 'shift':
10710                                                         o[v] = true;
10711                                                         break;
10712
10713                                                 default:
10714                                                         o.charCode = v.charCodeAt(0);
10715                                                         o.keyCode = v.toUpperCase().charCodeAt(0);
10716                                         }
10717                                 });
10718
10719                                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
10720                         });
10721
10722                         return true;
10723                 },
10724
10725                 execCommand : function(cmd, ui, val, a) {
10726                         var t = this, s = 0, o, st;
10727
10728                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
10729                                 t.focus();
10730
10731                         o = {};
10732                         t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
10733                         if (o.terminate)
10734                                 return false;
10735
10736                         // Command callback
10737                         if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
10738                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
10739                                 return true;
10740                         }
10741
10742                         // Registred commands
10743                         if (o = t.execCommands[cmd]) {
10744                                 st = o.func.call(o.scope, ui, val);
10745
10746                                 // Fall through on true
10747                                 if (st !== true) {
10748                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
10749                                         return st;
10750                                 }
10751                         }
10752
10753                         // Plugin commands
10754                         each(t.plugins, function(p) {
10755                                 if (p.execCommand && p.execCommand(cmd, ui, val)) {
10756                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
10757                                         s = 1;
10758                                         return false;
10759                                 }
10760                         });
10761
10762                         if (s)
10763                                 return true;
10764
10765                         // Theme commands
10766                         if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
10767                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
10768                                 return true;
10769                         }
10770
10771                         // Editor commands
10772                         if (t.editorCommands.execCommand(cmd, ui, val)) {
10773                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
10774                                 return true;
10775                         }
10776
10777                         // Browser commands
10778                         t.getDoc().execCommand(cmd, ui, val);
10779                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
10780                 },
10781
10782                 queryCommandState : function(cmd) {
10783                         var t = this, o, s;
10784
10785                         // Is hidden then return undefined
10786                         if (t._isHidden())
10787                                 return;
10788
10789                         // Registred commands
10790                         if (o = t.queryStateCommands[cmd]) {
10791                                 s = o.func.call(o.scope);
10792
10793                                 // Fall though on true
10794                                 if (s !== true)
10795                                         return s;
10796                         }
10797
10798                         // Registred commands
10799                         o = t.editorCommands.queryCommandState(cmd);
10800                         if (o !== -1)
10801                                 return o;
10802
10803                         // Browser commands
10804                         try {
10805                                 return this.getDoc().queryCommandState(cmd);
10806                         } catch (ex) {
10807                                 // Fails sometimes see bug: 1896577
10808                         }
10809                 },
10810
10811                 queryCommandValue : function(c) {
10812                         var t = this, o, s;
10813
10814                         // Is hidden then return undefined
10815                         if (t._isHidden())
10816                                 return;
10817
10818                         // Registred commands
10819                         if (o = t.queryValueCommands[c]) {
10820                                 s = o.func.call(o.scope);
10821
10822                                 // Fall though on true
10823                                 if (s !== true)
10824                                         return s;
10825                         }
10826
10827                         // Registred commands
10828                         o = t.editorCommands.queryCommandValue(c);
10829                         if (is(o))
10830                                 return o;
10831
10832                         // Browser commands
10833                         try {
10834                                 return this.getDoc().queryCommandValue(c);
10835                         } catch (ex) {
10836                                 // Fails sometimes see bug: 1896577
10837                         }
10838                 },
10839
10840                 show : function() {
10841                         var t = this;
10842
10843                         DOM.show(t.getContainer());
10844                         DOM.hide(t.id);
10845                         t.load();
10846                 },
10847
10848                 hide : function() {
10849                         var t = this, d = t.getDoc();
10850
10851                         // Fixed bug where IE has a blinking cursor left from the editor
10852                         if (isIE && d)
10853                                 d.execCommand('SelectAll');
10854
10855                         // We must save before we hide so Safari doesn't crash
10856                         t.save();
10857                         DOM.hide(t.getContainer());
10858                         DOM.setStyle(t.id, 'display', t.orgDisplay);
10859                 },
10860
10861                 isHidden : function() {
10862                         return !DOM.isHidden(this.id);
10863                 },
10864
10865                 setProgressState : function(b, ti, o) {
10866                         this.onSetProgressState.dispatch(this, b, ti, o);
10867
10868                         return b;
10869                 },
10870
10871                 load : function(o) {
10872                         var t = this, e = t.getElement(), h;
10873
10874                         if (e) {
10875                                 o = o || {};
10876                                 o.load = true;
10877
10878                                 // Double encode existing entities in the value
10879                                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
10880                                 o.element = e;
10881
10882                                 if (!o.no_events)
10883                                         t.onLoadContent.dispatch(t, o);
10884
10885                                 o.element = e = null;
10886
10887                                 return h;
10888                         }
10889                 },
10890
10891                 save : function(o) {
10892                         var t = this, e = t.getElement(), h, f;
10893
10894                         if (!e || !t.initialized)
10895                                 return;
10896
10897                         o = o || {};
10898                         o.save = true;
10899
10900                         // Add undo level will trigger onchange event
10901                         if (!o.no_events) {
10902                                 t.undoManager.typing = false;
10903                                 t.undoManager.add();
10904                         }
10905
10906                         o.element = e;
10907                         h = o.content = t.getContent(o);
10908
10909                         if (!o.no_events)
10910                                 t.onSaveContent.dispatch(t, o);
10911
10912                         h = o.content;
10913
10914                         if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
10915                                 e.innerHTML = h;
10916
10917                                 // Update hidden form element
10918                                 if (f = DOM.getParent(t.id, 'form')) {
10919                                         each(f.elements, function(e) {
10920                                                 if (e.name == t.id) {
10921                                                         e.value = h;
10922                                                         return false;
10923                                                 }
10924                                         });
10925                                 }
10926                         } else
10927                                 e.value = h;
10928
10929                         o.element = e = null;
10930
10931                         return h;
10932                 },
10933
10934                 setContent : function(content, args) {
10935                         var self = this, rootNode, body = self.getBody();
10936
10937                         // Setup args object
10938                         args = args || {};
10939                         args.format = args.format || 'html';
10940                         args.set = true;
10941                         args.content = content;
10942
10943                         // Do preprocessing
10944                         if (!args.no_events)
10945                                 self.onBeforeSetContent.dispatch(self, args);
10946
10947                         content = args.content;
10948
10949                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
10950                         // It will also be impossible to place the caret in the editor unless there is a BR element present
10951                         if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
10952                                 body.innerHTML = '<br data-mce-bogus="1" />';
10953                                 return;
10954                         }
10955
10956                         // Parse and serialize the html
10957                         if (args.format !== 'raw') {
10958                                 content = new tinymce.html.Serializer({}, self.schema).serialize(
10959                                         self.parser.parse(content)
10960                                 );
10961                         }
10962
10963                         // Set the new cleaned contents to the editor
10964                         args.content = tinymce.trim(content);
10965                         self.dom.setHTML(body, args.content);
10966
10967                         // Do post processing
10968                         if (!args.no_events)
10969                                 self.onSetContent.dispatch(self, args);
10970
10971                         return args.content;
10972                 },
10973
10974                 getContent : function(args) {
10975                         var self = this, content;
10976
10977                         // Setup args object
10978                         args = args || {};
10979                         args.format = args.format || 'html';
10980                         args.get = true;
10981
10982                         // Do preprocessing
10983                         if (!args.no_events)
10984                                 self.onBeforeGetContent.dispatch(self, args);
10985
10986                         // Get raw contents or by default the cleaned contents
10987                         if (args.format == 'raw')
10988                                 content = self.getBody().innerHTML;
10989                         else
10990                                 content = self.serializer.serialize(self.getBody(), args);
10991
10992                         args.content = tinymce.trim(content);
10993
10994                         // Do post processing
10995                         if (!args.no_events)
10996                                 self.onGetContent.dispatch(self, args);
10997
10998                         return args.content;
10999                 },
11000
11001                 isDirty : function() {
11002                         var self = this;
11003
11004                         return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
11005                 },
11006
11007                 getContainer : function() {
11008                         var t = this;
11009
11010                         if (!t.container)
11011                                 t.container = DOM.get(t.editorContainer || t.id + '_parent');
11012
11013                         return t.container;
11014                 },
11015
11016                 getContentAreaContainer : function() {
11017                         return this.contentAreaContainer;
11018                 },
11019
11020                 getElement : function() {
11021                         return DOM.get(this.settings.content_element || this.id);
11022                 },
11023
11024                 getWin : function() {
11025                         var t = this, e;
11026
11027                         if (!t.contentWindow) {
11028                                 e = DOM.get(t.id + "_ifr");
11029
11030                                 if (e)
11031                                         t.contentWindow = e.contentWindow;
11032                         }
11033
11034                         return t.contentWindow;
11035                 },
11036
11037                 getDoc : function() {
11038                         var t = this, w;
11039
11040                         if (!t.contentDocument) {
11041                                 w = t.getWin();
11042
11043                                 if (w)
11044                                         t.contentDocument = w.document;
11045                         }
11046
11047                         return t.contentDocument;
11048                 },
11049
11050                 getBody : function() {
11051                         return this.bodyElement || this.getDoc().body;
11052                 },
11053
11054                 convertURL : function(u, n, e) {
11055                         var t = this, s = t.settings;
11056
11057                         // Use callback instead
11058                         if (s.urlconverter_callback)
11059                                 return t.execCallback('urlconverter_callback', u, e, true, n);
11060
11061                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
11062                         if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
11063                                 return u;
11064
11065                         // Convert to relative
11066                         if (s.relative_urls)
11067                                 return t.documentBaseURI.toRelative(u);
11068
11069                         // Convert to absolute
11070                         u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
11071
11072                         return u;
11073                 },
11074
11075                 addVisual : function(e) {
11076                         var t = this, s = t.settings;
11077
11078                         e = e || t.getBody();
11079
11080                         if (!is(t.hasVisual))
11081                                 t.hasVisual = s.visual;
11082
11083                         each(t.dom.select('table,a', e), function(e) {
11084                                 var v;
11085
11086                                 switch (e.nodeName) {
11087                                         case 'TABLE':
11088                                                 v = t.dom.getAttrib(e, 'border');
11089
11090                                                 if (!v || v == '0') {
11091                                                         if (t.hasVisual)
11092                                                                 t.dom.addClass(e, s.visual_table_class);
11093                                                         else
11094                                                                 t.dom.removeClass(e, s.visual_table_class);
11095                                                 }
11096
11097                                                 return;
11098
11099                                         case 'A':
11100                                                 v = t.dom.getAttrib(e, 'name');
11101
11102                                                 if (v) {
11103                                                         if (t.hasVisual)
11104                                                                 t.dom.addClass(e, 'mceItemAnchor');
11105                                                         else
11106                                                                 t.dom.removeClass(e, 'mceItemAnchor');
11107                                                 }
11108
11109                                                 return;
11110                                 }
11111                         });
11112
11113                         t.onVisualAid.dispatch(t, e, t.hasVisual);
11114                 },
11115
11116                 remove : function() {
11117                         var t = this, e = t.getContainer();
11118
11119                         t.removed = 1; // Cancels post remove event execution
11120                         t.hide();
11121
11122                         t.execCallback('remove_instance_callback', t);
11123                         t.onRemove.dispatch(t);
11124
11125                         // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
11126                         t.onExecCommand.listeners = [];
11127
11128                         tinymce.remove(t);
11129                         DOM.remove(e);
11130                 },
11131
11132                 destroy : function(s) {
11133                         var t = this;
11134
11135                         // One time is enough
11136                         if (t.destroyed)
11137                                 return;
11138
11139                         if (!s) {
11140                                 tinymce.removeUnload(t.destroy);
11141                                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
11142
11143                                 // Manual destroy
11144                                 if (t.theme && t.theme.destroy)
11145                                         t.theme.destroy();
11146
11147                                 // Destroy controls, selection and dom
11148                                 t.controlManager.destroy();
11149                                 t.selection.destroy();
11150                                 t.dom.destroy();
11151
11152                                 // Remove all events
11153
11154                                 // Don't clear the window or document if content editable
11155                                 // is enabled since other instances might still be present
11156                                 if (!t.settings.content_editable) {
11157                                         Event.clear(t.getWin());
11158                                         Event.clear(t.getDoc());
11159                                 }
11160
11161                                 Event.clear(t.getBody());
11162                                 Event.clear(t.formElement);
11163                         }
11164
11165                         if (t.formElement) {
11166                                 t.formElement.submit = t.formElement._mceOldSubmit;
11167                                 t.formElement._mceOldSubmit = null;
11168                         }
11169
11170                         t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
11171
11172                         if (t.selection)
11173                                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
11174
11175                         t.destroyed = 1;
11176                 },
11177
11178                 // Internal functions
11179
11180                 _addEvents : function() {
11181                         // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
11182                         var t = this, i, s = t.settings, dom = t.dom, lo = {
11183                                 mouseup : 'onMouseUp',
11184                                 mousedown : 'onMouseDown',
11185                                 click : 'onClick',
11186                                 keyup : 'onKeyUp',
11187                                 keydown : 'onKeyDown',
11188                                 keypress : 'onKeyPress',
11189                                 submit : 'onSubmit',
11190                                 reset : 'onReset',
11191                                 contextmenu : 'onContextMenu',
11192                                 dblclick : 'onDblClick',
11193                                 paste : 'onPaste' // Doesn't work in all browsers yet
11194                         };
11195
11196                         function eventHandler(e, o) {
11197                                 var ty = e.type;
11198
11199                                 // Don't fire events when it's removed
11200                                 if (t.removed)
11201                                         return;
11202
11203                                 // Generic event handler
11204                                 if (t.onEvent.dispatch(t, e, o) !== false) {
11205                                         // Specific event handler
11206                                         t[lo[e.fakeType || e.type]].dispatch(t, e, o);
11207                                 }
11208                         };
11209
11210                         // Add DOM events
11211                         each(lo, function(v, k) {
11212                                 switch (k) {
11213                                         case 'contextmenu':
11214                                                 dom.bind(t.getDoc(), k, eventHandler);
11215                                                 break;
11216
11217                                         case 'paste':
11218                                                 dom.bind(t.getBody(), k, function(e) {
11219                                                         eventHandler(e);
11220                                                 });
11221                                                 break;
11222
11223                                         case 'submit':
11224                                         case 'reset':
11225                                                 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
11226                                                 break;
11227
11228                                         default:
11229                                                 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
11230                                 }
11231                         });
11232
11233                         dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
11234                                 t.focus(true);
11235                         });
11236
11237
11238                         // Fixes bug where a specified document_base_uri could result in broken images
11239                         // This will also fix drag drop of images in Gecko
11240                         if (tinymce.isGecko) {
11241                                 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
11242                                         var v;
11243
11244                                         e = e.target;
11245
11246                                         if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
11247                                                 e.src = t.documentBaseURI.toAbsolute(v);
11248                                 });
11249                         }
11250
11251                         // Set various midas options in Gecko
11252                         if (isGecko) {
11253                                 function setOpts() {
11254                                         var t = this, d = t.getDoc(), s = t.settings;
11255
11256                                         if (isGecko && !s.readonly) {
11257                                                 if (t._isHidden()) {
11258                                                         try {
11259                                                                 if (!s.content_editable)
11260                                                                         d.designMode = 'On';
11261                                                         } catch (ex) {
11262                                                                 // Fails if it's hidden
11263                                                         }
11264                                                 }
11265
11266                                                 try {
11267                                                         // Try new Gecko method
11268                                                         d.execCommand("styleWithCSS", 0, false);
11269                                                 } catch (ex) {
11270                                                         // Use old method
11271                                                         if (!t._isHidden())
11272                                                                 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
11273                                                 }
11274
11275                                                 if (!s.table_inline_editing)
11276                                                         try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
11277
11278                                                 if (!s.object_resizing)
11279                                                         try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
11280                                         }
11281                                 };
11282
11283                                 t.onBeforeExecCommand.add(setOpts);
11284                                 t.onMouseDown.add(setOpts);
11285                         }
11286
11287                         // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
11288                         // WebKit can't even do simple things like selecting an image
11289                         // This also fixes so it's possible to select mceItemAnchors
11290                         if (tinymce.isWebKit) {
11291                                 t.onClick.add(function(ed, e) {
11292                                         e = e.target;
11293
11294                                         // Needs tobe the setBaseAndExtend or it will fail to select floated images
11295                                         if (e.nodeName == 'IMG' || (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))) {
11296                                                 t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
11297                                                 t.nodeChanged();
11298                                         }
11299                                 });
11300                         }
11301
11302                         // Add node change handlers
11303                         t.onMouseUp.add(t.nodeChanged);
11304                         //t.onClick.add(t.nodeChanged);
11305                         t.onKeyUp.add(function(ed, e) {
11306                                 var c = e.keyCode;
11307
11308                                 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)
11309                                         t.nodeChanged();
11310                         });
11311
11312                         // Add reset handler
11313                         t.onReset.add(function() {
11314                                 t.setContent(t.startContent, {format : 'raw'});
11315                         });
11316
11317                         // Add shortcuts
11318                         if (s.custom_shortcuts) {
11319                                 if (s.custom_undo_redo_keyboard_shortcuts) {
11320                                         t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
11321                                         t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
11322                                 }
11323
11324                                 // Add default shortcuts for gecko
11325                                 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
11326                                 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
11327                                 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
11328
11329                                 // BlockFormat shortcuts keys
11330                                 for (i=1; i<=6; i++)
11331                                         t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
11332
11333                                 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
11334                                 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
11335                                 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
11336
11337                                 function find(e) {
11338                                         var v = null;
11339
11340                                         if (!e.altKey && !e.ctrlKey && !e.metaKey)
11341                                                 return v;
11342
11343                                         each(t.shortcuts, function(o) {
11344                                                 if (tinymce.isMac && o.ctrl != e.metaKey)
11345                                                         return;
11346                                                 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
11347                                                         return;
11348
11349                                                 if (o.alt != e.altKey)
11350                                                         return;
11351
11352                                                 if (o.shift != e.shiftKey)
11353                                                         return;
11354
11355                                                 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
11356                                                         v = o;
11357                                                         return false;
11358                                                 }
11359                                         });
11360
11361                                         return v;
11362                                 };
11363
11364                                 t.onKeyUp.add(function(ed, e) {
11365                                         var o = find(e);
11366
11367                                         if (o)
11368                                                 return Event.cancel(e);
11369                                 });
11370
11371                                 t.onKeyPress.add(function(ed, e) {
11372                                         var o = find(e);
11373
11374                                         if (o)
11375                                                 return Event.cancel(e);
11376                                 });
11377
11378                                 t.onKeyDown.add(function(ed, e) {
11379                                         var o = find(e);
11380
11381                                         if (o) {
11382                                                 o.func.call(o.scope);
11383                                                 return Event.cancel(e);
11384                                         }
11385                                 });
11386                         }
11387
11388                         if (tinymce.isIE) {
11389                                 // Fix so resize will only update the width and height attributes not the styles of an image
11390                                 // It will also block mceItemNoResize items
11391                                 dom.bind(t.getDoc(), 'controlselect', function(e) {
11392                                         var re = t.resizeInfo, cb;
11393
11394                                         e = e.target;
11395
11396                                         // Don't do this action for non image elements
11397                                         if (e.nodeName !== 'IMG')
11398                                                 return;
11399
11400                                         if (re)
11401                                                 dom.unbind(re.node, re.ev, re.cb);
11402
11403                                         if (!dom.hasClass(e, 'mceItemNoResize')) {
11404                                                 ev = 'resizeend';
11405                                                 cb = dom.bind(e, ev, function(e) {
11406                                                         var v;
11407
11408                                                         e = e.target;
11409
11410                                                         if (v = dom.getStyle(e, 'width')) {
11411                                                                 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
11412                                                                 dom.setStyle(e, 'width', '');
11413                                                         }
11414
11415                                                         if (v = dom.getStyle(e, 'height')) {
11416                                                                 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
11417                                                                 dom.setStyle(e, 'height', '');
11418                                                         }
11419                                                 });
11420                                         } else {
11421                                                 ev = 'resizestart';
11422                                                 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
11423                                         }
11424
11425                                         re = t.resizeInfo = {
11426                                                 node : e,
11427                                                 ev : ev,
11428                                                 cb : cb
11429                                         };
11430                                 });
11431
11432                                 t.onKeyDown.add(function(ed, e) {
11433                                         var sel;
11434
11435                                         switch (e.keyCode) {
11436                                                 case 8:
11437                                                         sel = t.getDoc().selection;
11438
11439                                                         // Fix IE control + backspace browser bug
11440                                                         if (sel.createRange && sel.createRange().item) {
11441                                                                 ed.dom.remove(sel.createRange().item(0));
11442                                                                 return Event.cancel(e);
11443                                                         }
11444                                         }
11445                                 });
11446                         }
11447
11448                         if (tinymce.isOpera) {
11449                                 t.onClick.add(function(ed, e) {
11450                                         Event.prevent(e);
11451                                 });
11452                         }
11453
11454                         // Add custom undo/redo handlers
11455                         if (s.custom_undo_redo) {
11456                                 function addUndo() {
11457                                         t.undoManager.typing = false;
11458                                         t.undoManager.add();
11459                                 };
11460
11461                                 dom.bind(t.getDoc(), 'focusout', function(e) {
11462                                         if (!t.removed && t.undoManager.typing)
11463                                                 addUndo();
11464                                 });
11465
11466                                 // Add undo level when contents is drag/dropped within the editor
11467                                 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
11468                                         addUndo();
11469                                 });
11470
11471                                 t.onKeyUp.add(function(ed, e) {
11472                                         var rng, parent, bookmark;
11473
11474                                         // Fix for bug #3168, to remove odd ".." nodes from the DOM we need to get/set the HTML of the parent node.
11475                                         if (isIE && e.keyCode == 8) {
11476                                                 rng = t.selection.getRng();
11477                                                 if (rng.parentElement) {
11478                                                         parent = rng.parentElement();
11479                                                         bookmark = t.selection.getBookmark();
11480                                                         parent.innerHTML = parent.innerHTML;
11481                                                         t.selection.moveToBookmark(bookmark);
11482                                                 }
11483                                         }
11484
11485                                         if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey)
11486                                                 addUndo();
11487                                 });
11488
11489                                 t.onKeyDown.add(function(ed, e) {
11490                                         var rng, parent, bookmark, keyCode = e.keyCode;
11491
11492                                         // IE has a really odd bug where the DOM might include an node that doesn't have
11493                                         // a proper structure. If you try to access nodeValue it would throw an illegal value exception.
11494                                         // This seems to only happen when you delete contents and it seems to be avoidable if you refresh the element
11495                                         // after you delete contents from it. See: #3008923
11496                                         if (isIE && keyCode == 46) {
11497                                                 rng = t.selection.getRng();
11498
11499                                                 if (rng.parentElement) {
11500                                                         parent = rng.parentElement();
11501
11502                                                         if (!t.undoManager.typing) {
11503                                                                 t.undoManager.beforeChange();
11504                                                                 t.undoManager.typing = true;
11505                                                                 t.undoManager.add();
11506                                                         }
11507
11508                                                         // Select next word when ctrl key is used in combo with delete
11509                                                         if (e.ctrlKey) {
11510                                                                 rng.moveEnd('word', 1);
11511                                                                 rng.select();
11512                                                         }
11513
11514                                                         // Delete contents
11515                                                         t.selection.getSel().clear();
11516
11517                                                         // Check if we are within the same parent
11518                                                         if (rng.parentElement() == parent) {
11519                                                                 bookmark = t.selection.getBookmark();
11520
11521                                                                 try {
11522                                                                         // Update the HTML and hopefully it will remove the artifacts
11523                                                                         parent.innerHTML = parent.innerHTML;
11524                                                                 } catch (ex) {
11525                                                                         // And since it's IE it can sometimes produce an unknown runtime error
11526                                                                 }
11527
11528                                                                 // Restore the caret position
11529                                                                 t.selection.moveToBookmark(bookmark);
11530                                                         }
11531
11532                                                         // Block the default delete behavior since it might be broken
11533                                                         e.preventDefault();
11534                                                         return;
11535                                                 }
11536                                         }
11537
11538                                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
11539                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
11540                                                 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
11541                                                 // Todo: Remove this once we normalize enter behavior on IE
11542                                                 if (tinymce.isIE && keyCode == 13)
11543                                                         t.undoManager.beforeChange();
11544
11545                                                 if (t.undoManager.typing)
11546                                                         addUndo();
11547
11548                                                 return;
11549                                         }
11550
11551                                         // If key isn't shift,ctrl,alt,capslock,metakey
11552                                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
11553                                                 t.undoManager.beforeChange();
11554                                                 t.undoManager.add();
11555                                                 t.undoManager.typing = true;
11556                                         }
11557                                 });
11558
11559                                 t.onMouseDown.add(function() {
11560                                         if (t.undoManager.typing)
11561                                                 addUndo();
11562                                 });
11563                         }
11564                         
11565                         // Bug fix for FireFox keeping styles from end of selection instead of start.
11566                         if (tinymce.isGecko) {
11567                                 function getAttributeApplyFunction() {
11568                                         var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
11569
11570                                         return function() {
11571                                                 var target = t.selection.getStart();
11572                                                 t.dom.removeAllAttribs(target);
11573                                                 each(template, function(attr) {
11574                                                         target.setAttributeNode(attr.cloneNode(true));
11575                                                 });
11576                                         };
11577                                 }
11578
11579                                 function isSelectionAcrossElements() {
11580                                         var s = t.selection;
11581
11582                                         return !s.isCollapsed() && s.getStart() != s.getEnd();
11583                                 }
11584
11585                                 t.onKeyPress.add(function(ed, e) {
11586                                         var applyAttributes;
11587
11588                                         if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
11589                                                 applyAttributes = getAttributeApplyFunction();
11590                                                 t.getDoc().execCommand('delete', false, null);
11591                                                 applyAttributes();
11592
11593                                                 return Event.cancel(e);
11594                                         }
11595                                 });
11596
11597                                 t.dom.bind(t.getDoc(), 'cut', function(e) {
11598                                         var applyAttributes;
11599
11600                                         if (isSelectionAcrossElements()) {
11601                                                 applyAttributes = getAttributeApplyFunction();
11602                                                 t.onKeyUp.addToTop(Event.cancel, Event);
11603
11604                                                 setTimeout(function() {
11605                                                         applyAttributes();
11606                                                         t.onKeyUp.remove(Event.cancel, Event);
11607                                                 }, 0);
11608                                         }
11609                                 });
11610                         }
11611                 },
11612
11613                 _isHidden : function() {
11614                         var s;
11615
11616                         if (!isGecko)
11617                                 return 0;
11618
11619                         // Weird, wheres that cursor selection?
11620                         s = this.selection.getSel();
11621                         return (!s || !s.rangeCount || s.rangeCount == 0);
11622                 }
11623         });
11624 })(tinymce);
11625
11626 (function(tinymce) {
11627         // Added for compression purposes
11628         var each = tinymce.each, undefined, TRUE = true, FALSE = false;
11629
11630         tinymce.EditorCommands = function(editor) {
11631                 var dom = editor.dom,
11632                         selection = editor.selection,
11633                         commands = {state: {}, exec : {}, value : {}},
11634                         settings = editor.settings,
11635                         bookmark;
11636
11637                 function execCommand(command, ui, value) {
11638                         var func;
11639
11640                         command = command.toLowerCase();
11641                         if (func = commands.exec[command]) {
11642                                 func(command, ui, value);
11643                                 return TRUE;
11644                         }
11645
11646                         return FALSE;
11647                 };
11648
11649                 function queryCommandState(command) {
11650                         var func;
11651
11652                         command = command.toLowerCase();
11653                         if (func = commands.state[command])
11654                                 return func(command);
11655
11656                         return -1;
11657                 };
11658
11659                 function queryCommandValue(command) {
11660                         var func;
11661
11662                         command = command.toLowerCase();
11663                         if (func = commands.value[command])
11664                                 return func(command);
11665
11666                         return FALSE;
11667                 };
11668
11669                 function addCommands(command_list, type) {
11670                         type = type || 'exec';
11671
11672                         each(command_list, function(callback, command) {
11673                                 each(command.toLowerCase().split(','), function(command) {
11674                                         commands[type][command] = callback;
11675                                 });
11676                         });
11677                 };
11678
11679                 // Expose public methods
11680                 tinymce.extend(this, {
11681                         execCommand : execCommand,
11682                         queryCommandState : queryCommandState,
11683                         queryCommandValue : queryCommandValue,
11684                         addCommands : addCommands
11685                 });
11686
11687                 // Private methods
11688
11689                 function execNativeCommand(command, ui, value) {
11690                         if (ui === undefined)
11691                                 ui = FALSE;
11692
11693                         if (value === undefined)
11694                                 value = null;
11695
11696                         return editor.getDoc().execCommand(command, ui, value);
11697                 };
11698
11699                 function isFormatMatch(name) {
11700                         return editor.formatter.match(name);
11701                 };
11702
11703                 function toggleFormat(name, value) {
11704                         editor.formatter.toggle(name, value ? {value : value} : undefined);
11705                 };
11706
11707                 function storeSelection(type) {
11708                         bookmark = selection.getBookmark(type);
11709                 };
11710
11711                 function restoreSelection() {
11712                         selection.moveToBookmark(bookmark);
11713                 };
11714
11715                 // Add execCommand overrides
11716                 addCommands({
11717                         // Ignore these, added for compatibility
11718                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},
11719
11720                         // Add undo manager logic
11721                         'mceEndUndoLevel,mceAddUndoLevel' : function() {
11722                                 editor.undoManager.add();
11723                         },
11724
11725                         'Cut,Copy,Paste' : function(command) {
11726                                 var doc = editor.getDoc(), failed;
11727
11728                                 // Try executing the native command
11729                                 try {
11730                                         execNativeCommand(command);
11731                                 } catch (ex) {
11732                                         // Command failed
11733                                         failed = TRUE;
11734                                 }
11735
11736                                 // Present alert message about clipboard access not being available
11737                                 if (failed || !doc.queryCommandSupported(command)) {
11738                                         if (tinymce.isGecko) {
11739                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
11740                                                         if (state)
11741                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
11742                                                 });
11743                                         } else
11744                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
11745                                 }
11746                         },
11747
11748                         // Override unlink command
11749                         unlink : function(command) {
11750                                 if (selection.isCollapsed())
11751                                         selection.select(selection.getNode());
11752
11753                                 execNativeCommand(command);
11754                                 selection.collapse(FALSE);
11755                         },
11756
11757                         // Override justify commands to use the text formatter engine
11758                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
11759                                 var align = command.substring(7);
11760
11761                                 // Remove all other alignments first
11762                                 each('left,center,right,full'.split(','), function(name) {
11763                                         if (align != name)
11764                                                 editor.formatter.remove('align' + name);
11765                                 });
11766
11767                                 toggleFormat('align' + align);
11768                                 execCommand('mceRepaint');
11769                         },
11770
11771                         // Override list commands to fix WebKit bug
11772                         'InsertUnorderedList,InsertOrderedList' : function(command) {
11773                                 var listElm, listParent;
11774
11775                                 execNativeCommand(command);
11776
11777                                 // WebKit produces lists within block elements so we need to split them
11778                                 // we will replace the native list creation logic to custom logic later on
11779                                 // TODO: Remove this when the list creation logic is removed
11780                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');
11781                                 if (listElm) {
11782                                         listParent = listElm.parentNode;
11783
11784                                         // If list is within a text block then split that block
11785                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
11786                                                 storeSelection();
11787                                                 dom.split(listParent, listElm);
11788                                                 restoreSelection();
11789                                         }
11790                                 }
11791                         },
11792
11793                         // Override commands to use the text formatter engine
11794                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
11795                                 toggleFormat(command);
11796                         },
11797
11798                         // Override commands to use the text formatter engine
11799                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
11800                                 toggleFormat(command, value);
11801                         },
11802
11803                         FontSize : function(command, ui, value) {
11804                                 var fontClasses, fontSizes;
11805
11806                                 // Convert font size 1-7 to styles
11807                                 if (value >= 1 && value <= 7) {
11808                                         fontSizes = tinymce.explode(settings.font_size_style_values);
11809                                         fontClasses = tinymce.explode(settings.font_size_classes);
11810
11811                                         if (fontClasses)
11812                                                 value = fontClasses[value - 1] || value;
11813                                         else
11814                                                 value = fontSizes[value - 1] || value;
11815                                 }
11816
11817                                 toggleFormat(command, value);
11818                         },
11819
11820                         RemoveFormat : function(command) {
11821                                 editor.formatter.remove(command);
11822                         },
11823
11824                         mceBlockQuote : function(command) {
11825                                 toggleFormat('blockquote');
11826                         },
11827
11828                         FormatBlock : function(command, ui, value) {
11829                                 return toggleFormat(value || 'p');
11830                         },
11831
11832                         mceCleanup : function() {
11833                                 var bookmark = selection.getBookmark();
11834
11835                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
11836
11837                                 selection.moveToBookmark(bookmark);
11838                         },
11839
11840                         mceRemoveNode : function(command, ui, value) {
11841                                 var node = value || selection.getNode();
11842
11843                                 // Make sure that the body node isn't removed
11844                                 if (node != editor.getBody()) {
11845                                         storeSelection();
11846                                         editor.dom.remove(node, TRUE);
11847                                         restoreSelection();
11848                                 }
11849                         },
11850
11851                         mceSelectNodeDepth : function(command, ui, value) {
11852                                 var counter = 0;
11853
11854                                 dom.getParent(selection.getNode(), function(node) {
11855                                         if (node.nodeType == 1 && counter++ == value) {
11856                                                 selection.select(node);
11857                                                 return FALSE;
11858                                         }
11859                                 }, editor.getBody());
11860                         },
11861
11862                         mceSelectNode : function(command, ui, value) {
11863                                 selection.select(value);
11864                         },
11865
11866                         mceInsertContent : function(command, ui, value) {
11867                                 var caretNode, rng, rootNode, parent, node, rng, nodeRect, viewPortRect, args;
11868
11869                                 function findSuitableCaretNode(node, root_node, next) {
11870                                         var walker = new tinymce.dom.TreeWalker(next ? node.nextSibling : node.previousSibling, root_node);
11871
11872                                         while ((node = walker.current())) {
11873                                                 if ((node.nodeType == 3 && tinymce.trim(node.nodeValue).length) || node.nodeName == 'BR' || node.nodeName == 'IMG')
11874                                                         return node;
11875
11876                                                 if (next)
11877                                                         walker.next();
11878                                                 else
11879                                                         walker.prev();
11880                                         }
11881                                 };
11882
11883                                 args = {content: value, format: 'html'};
11884                                 selection.onBeforeSetContent.dispatch(selection, args);
11885                                 value = args.content;
11886
11887                                 // Add caret at end of contents if it's missing
11888                                 if (value.indexOf('{$caret}') == -1)
11889                                         value += '{$caret}';
11890
11891                                 // Set the content at selection to a span and replace it's contents with the value
11892                                 selection.setContent('<span id="__mce">\uFEFF</span>', {no_events : false});
11893                                 dom.setOuterHTML('__mce', value.replace(/\{\$caret\}/, '<span data-mce-type="bookmark" id="__mce">\uFEFF</span>'));
11894
11895                                 caretNode = dom.select('#__mce')[0];
11896                                 rootNode = dom.getRoot();
11897
11898                                 // Move the caret into the last suitable location within the previous sibling if it's a block since the block might be split
11899                                 if (caretNode.previousSibling && dom.isBlock(caretNode.previousSibling) || caretNode.parentNode == rootNode) {
11900                                         node = findSuitableCaretNode(caretNode, rootNode);
11901                                         if (node) {
11902                                                 if (node.nodeName == 'BR')
11903                                                         node.parentNode.insertBefore(caretNode, node);
11904                                                 else
11905                                                         dom.insertAfter(caretNode, node);
11906                                         }
11907                                 }
11908
11909                                 // Find caret root parent and clean it up using the serializer to avoid nesting
11910                                 while (caretNode) {
11911                                         if (caretNode === rootNode) {
11912                                                 // Clean up the parent element by parsing and serializing it
11913                                                 // This will remove invalid elements/attributes and fix nesting issues
11914                                                 dom.setOuterHTML(parent, 
11915                                                         new tinymce.html.Serializer({}, editor.schema).serialize(
11916                                                                 editor.parser.parse(dom.getOuterHTML(parent))
11917                                                         )
11918                                                 );
11919
11920                                                 break;
11921                                         }
11922
11923                                         parent = caretNode;
11924                                         caretNode = caretNode.parentNode;
11925                                 }
11926
11927                                 // Find caret after cleanup and move selection to that location
11928                                 caretNode = dom.select('#__mce')[0];
11929                                 if (caretNode) {
11930                                         node = findSuitableCaretNode(caretNode, rootNode) || findSuitableCaretNode(caretNode, rootNode, true);
11931                                         dom.remove(caretNode);
11932
11933                                         if (node) {
11934                                                 rng = dom.createRng();
11935
11936                                                 if (node.nodeType == 3) {
11937                                                         rng.setStart(node, node.length);
11938                                                         rng.setEnd(node, node.length);
11939                                                 } else {
11940                                                         if (node.nodeName == 'BR') {
11941                                                                 rng.setStartBefore(node);
11942                                                                 rng.setEndBefore(node);
11943                                                         } else {
11944                                                                 rng.setStartAfter(node);
11945                                                                 rng.setEndAfter(node);
11946                                                         }
11947                                                 }
11948
11949                                                 selection.setRng(rng);
11950
11951                                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
11952                                                 if (!tinymce.isIE) {
11953                                                         node = dom.create('span', null, '\u00a0');
11954                                                         rng.insertNode(node);
11955                                                         nodeRect = dom.getRect(node);
11956                                                         viewPortRect = dom.getViewPort(editor.getWin());
11957
11958                                                         // Check if node is out side the viewport if it is then scroll to it
11959                                                         if ((nodeRect.y > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
11960                                                                 (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
11961                                                                 editor.getBody().scrollLeft = nodeRect.x;
11962                                                                 editor.getBody().scrollTop = nodeRect.y;
11963                                                         }
11964
11965                                                         dom.remove(node);
11966                                                 }
11967
11968                                                 // Make sure that the selection is collapsed after we removed the node fixes a WebKit bug
11969                                                 // where WebKit would place the endContainer/endOffset at a different location than the startContainer/startOffset
11970                                                 selection.collapse(true);
11971                                         }
11972                                 }
11973
11974                                 selection.onSetContent.dispatch(selection, args);
11975                                 editor.addVisual();
11976                         },
11977
11978                         mceInsertRawHTML : function(command, ui, value) {
11979                                 selection.setContent('tiny_mce_marker');
11980                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
11981                         },
11982
11983                         mceSetContent : function(command, ui, value) {
11984                                 editor.setContent(value);
11985                         },
11986
11987                         'Indent,Outdent' : function(command) {
11988                                 var intentValue, indentUnit, value;
11989
11990                                 // Setup indent level
11991                                 intentValue = settings.indentation;
11992                                 indentUnit = /[a-z%]+$/i.exec(intentValue);
11993                                 intentValue = parseInt(intentValue);
11994
11995                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
11996                                         each(selection.getSelectedBlocks(), function(element) {
11997                                                 if (command == 'outdent') {
11998                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
11999                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
12000                                                 } else
12001                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
12002                                         });
12003                                 } else
12004                                         execNativeCommand(command);
12005                         },
12006
12007                         mceRepaint : function() {
12008                                 var bookmark;
12009
12010                                 if (tinymce.isGecko) {
12011                                         try {
12012                                                 storeSelection(TRUE);
12013
12014                                                 if (selection.getSel())
12015                                                         selection.getSel().selectAllChildren(editor.getBody());
12016
12017                                                 selection.collapse(TRUE);
12018                                                 restoreSelection();
12019                                         } catch (ex) {
12020                                                 // Ignore
12021                                         }
12022                                 }
12023                         },
12024
12025                         mceToggleFormat : function(command, ui, value) {
12026                                 editor.formatter.toggle(value);
12027                         },
12028
12029                         InsertHorizontalRule : function() {
12030                                 editor.execCommand('mceInsertContent', false, '<hr />');
12031                         },
12032
12033                         mceToggleVisualAid : function() {
12034                                 editor.hasVisual = !editor.hasVisual;
12035                                 editor.addVisual();
12036                         },
12037
12038                         mceReplaceContent : function(command, ui, value) {
12039                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
12040                         },
12041
12042                         mceInsertLink : function(command, ui, value) {
12043                                 var link = dom.getParent(selection.getNode(), 'a'), img, floatVal;
12044
12045                                 if (tinymce.is(value, 'string'))
12046                                         value = {href : value};
12047
12048                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
12049                                 value.href = value.href.replace(' ', '%20');
12050
12051                                 if (!link) {
12052                                         // WebKit can't create links on float images for some odd reason so just remove it and restore it later
12053                                         if (tinymce.isWebKit) {
12054                                                 img = dom.getParent(selection.getNode(), 'img');
12055
12056                                                 if (img) {
12057                                                         floatVal = img.style.cssFloat;
12058                                                         img.style.cssFloat = null;
12059                                                 }
12060                                         }
12061
12062                                         execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
12063
12064                                         // Restore float value
12065                                         if (floatVal)
12066                                                 img.style.cssFloat = floatVal;
12067
12068                                         each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
12069                                                 dom.setAttribs(link, value);
12070                                         });
12071                                 } else {
12072                                         if (value.href)
12073                                                 dom.setAttribs(link, value);
12074                                         else
12075                                                 editor.dom.remove(link, TRUE);
12076                                 }
12077                         },
12078                         
12079                         selectAll : function() {
12080                                 var root = dom.getRoot(), rng = dom.createRng();
12081
12082                                 rng.setStart(root, 0);
12083                                 rng.setEnd(root, root.childNodes.length);
12084
12085                                 editor.selection.setRng(rng);
12086                         }
12087                 });
12088
12089                 // Add queryCommandState overrides
12090                 addCommands({
12091                         // Override justify commands
12092                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12093                                 return isFormatMatch('align' + command.substring(7));
12094                         },
12095
12096                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
12097                                 return isFormatMatch(command);
12098                         },
12099
12100                         mceBlockQuote : function() {
12101                                 return isFormatMatch('blockquote');
12102                         },
12103
12104                         Outdent : function() {
12105                                 var node;
12106
12107                                 if (settings.inline_styles) {
12108                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12109                                                 return TRUE;
12110
12111                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12112                                                 return TRUE;
12113                                 }
12114
12115                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
12116                         },
12117
12118                         'InsertUnorderedList,InsertOrderedList' : function(command) {
12119                                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
12120                         }
12121                 }, 'state');
12122
12123                 // Add queryCommandValue overrides
12124                 addCommands({
12125                         'FontSize,FontName' : function(command) {
12126                                 var value = 0, parent;
12127
12128                                 if (parent = dom.getParent(selection.getNode(), 'span')) {
12129                                         if (command == 'fontsize')
12130                                                 value = parent.style.fontSize;
12131                                         else
12132                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
12133                                 }
12134
12135                                 return value;
12136                         }
12137                 }, 'value');
12138
12139                 // Add undo manager logic
12140                 if (settings.custom_undo_redo) {
12141                         addCommands({
12142                                 Undo : function() {
12143                                         editor.undoManager.undo();
12144                                 },
12145
12146                                 Redo : function() {
12147                                         editor.undoManager.redo();
12148                                 }
12149                         });
12150                 }
12151         };
12152 })(tinymce);
12153
12154 (function(tinymce) {
12155         var Dispatcher = tinymce.util.Dispatcher;
12156
12157         tinymce.UndoManager = function(editor) {
12158                 var self, index = 0, data = [];
12159
12160                 function getContent() {
12161                         return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
12162                 };
12163
12164                 return self = {
12165                         typing : false,
12166
12167                         onAdd : new Dispatcher(self),
12168
12169                         onUndo : new Dispatcher(self),
12170
12171                         onRedo : new Dispatcher(self),
12172
12173                         beforeChange : function() {
12174                                 // Set before bookmark on previous level
12175                                 if (data[index])
12176                                         data[index].beforeBookmark = editor.selection.getBookmark(2, true);
12177                         },
12178
12179                         add : function(level) {
12180                                 var i, settings = editor.settings, lastLevel;
12181
12182                                 level = level || {};
12183                                 level.content = getContent();
12184
12185                                 // Add undo level if needed
12186                                 lastLevel = data[index];
12187                                 if (lastLevel && lastLevel.content == level.content)
12188                                         return null;
12189
12190                                 // Time to compress
12191                                 if (settings.custom_undo_redo_levels) {
12192                                         if (data.length > settings.custom_undo_redo_levels) {
12193                                                 for (i = 0; i < data.length - 1; i++)
12194                                                         data[i] = data[i + 1];
12195
12196                                                 data.length--;
12197                                                 index = data.length;
12198                                         }
12199                                 }
12200
12201                                 // Get a non intrusive normalized bookmark
12202                                 level.bookmark = editor.selection.getBookmark(2, true);
12203
12204                                 // Crop array if needed
12205                                 if (index < data.length - 1)
12206                                         data.length = index + 1;
12207
12208                                 data.push(level);
12209                                 index = data.length - 1;
12210
12211                                 self.onAdd.dispatch(self, level);
12212                                 editor.isNotDirty = 0;
12213
12214                                 return level;
12215                         },
12216
12217                         undo : function() {
12218                                 var level, i;
12219
12220                                 if (self.typing) {
12221                                         self.add();
12222                                         self.typing = false;
12223                                 }
12224
12225                                 if (index > 0) {
12226                                         level = data[--index];
12227
12228                                         editor.setContent(level.content, {format : 'raw'});
12229                                         editor.selection.moveToBookmark(level.beforeBookmark);
12230
12231                                         self.onUndo.dispatch(self, level);
12232                                 }
12233
12234                                 return level;
12235                         },
12236
12237                         redo : function() {
12238                                 var level;
12239
12240                                 if (index < data.length - 1) {
12241                                         level = data[++index];
12242
12243                                         editor.setContent(level.content, {format : 'raw'});
12244                                         editor.selection.moveToBookmark(level.bookmark);
12245
12246                                         self.onRedo.dispatch(self, level);
12247                                 }
12248
12249                                 return level;
12250                         },
12251
12252                         clear : function() {
12253                                 data = [];
12254                                 index = 0;
12255                                 self.typing = false;
12256                         },
12257
12258                         hasUndo : function() {
12259                                 return index > 0 || this.typing;
12260                         },
12261
12262                         hasRedo : function() {
12263                                 return index < data.length - 1 && !this.typing;
12264                         }
12265                 };
12266         };
12267 })(tinymce);
12268
12269 (function(tinymce) {
12270         // Shorten names
12271         var Event = tinymce.dom.Event,
12272                 isIE = tinymce.isIE,
12273                 isGecko = tinymce.isGecko,
12274                 isOpera = tinymce.isOpera,
12275                 each = tinymce.each,
12276                 extend = tinymce.extend,
12277                 TRUE = true,
12278                 FALSE = false;
12279
12280         function cloneFormats(node) {
12281                 var clone, temp, inner;
12282
12283                 do {
12284                         if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
12285                                 if (clone) {
12286                                         temp = node.cloneNode(false);
12287                                         temp.appendChild(clone);
12288                                         clone = temp;
12289                                 } else {
12290                                         clone = inner = node.cloneNode(false);
12291                                 }
12292
12293                                 clone.removeAttribute('id');
12294                         }
12295                 } while (node = node.parentNode);
12296
12297                 if (clone)
12298                         return {wrapper : clone, inner : inner};
12299         };
12300
12301         // Checks if the selection/caret is at the end of the specified block element
12302         function isAtEnd(rng, par) {
12303                 var rng2 = par.ownerDocument.createRange();
12304
12305                 rng2.setStart(rng.endContainer, rng.endOffset);
12306                 rng2.setEndAfter(par);
12307
12308                 // 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
12309                 return rng2.cloneContents().textContent.length == 0;
12310         };
12311
12312         function splitList(selection, dom, li) {
12313                 var listBlock, block;
12314
12315                 if (dom.isEmpty(li)) {
12316                         listBlock = dom.getParent(li, 'ul,ol');
12317
12318                         if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
12319                                 dom.split(listBlock, li);
12320                                 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
12321                                 dom.replace(block, li);
12322                                 selection.select(block, 1);
12323                         }
12324
12325                         return FALSE;
12326                 }
12327
12328                 return TRUE;
12329         };
12330
12331         tinymce.create('tinymce.ForceBlocks', {
12332                 ForceBlocks : function(ed) {
12333                         var t = this, s = ed.settings, elm;
12334
12335                         t.editor = ed;
12336                         t.dom = ed.dom;
12337                         elm = (s.forced_root_block || 'p').toLowerCase();
12338                         s.element = elm.toUpperCase();
12339
12340                         ed.onPreInit.add(t.setup, t);
12341
12342                         if (s.forced_root_block) {
12343                                 ed.onInit.add(t.forceRoots, t);
12344                                 ed.onSetContent.add(t.forceRoots, t);
12345                                 ed.onBeforeGetContent.add(t.forceRoots, t);
12346                                 ed.onExecCommand.add(function(ed, cmd) {
12347                                         if (cmd == 'mceInsertContent') {
12348                                                 t.forceRoots();
12349                                                 ed.nodeChanged();
12350                                         }
12351                                 });
12352                         }
12353                 },
12354
12355                 setup : function() {
12356                         var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection;
12357
12358                         // Force root blocks when typing and when getting output
12359                         if (s.forced_root_block) {
12360                                 ed.onBeforeExecCommand.add(t.forceRoots, t);
12361                                 ed.onKeyUp.add(t.forceRoots, t);
12362                                 ed.onPreProcess.add(t.forceRoots, t);
12363                         }
12364
12365                         if (s.force_br_newlines) {
12366                                 // Force IE to produce BRs on enter
12367                                 if (isIE) {
12368                                         ed.onKeyPress.add(function(ed, e) {
12369                                                 var n;
12370
12371                                                 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
12372                                                         selection.setContent('<br id="__" /> ', {format : 'raw'});
12373                                                         n = dom.get('__');
12374                                                         n.removeAttribute('id');
12375                                                         selection.select(n);
12376                                                         selection.collapse();
12377                                                         return Event.cancel(e);
12378                                                 }
12379                                         });
12380                                 }
12381                         }
12382
12383                         if (s.force_p_newlines) {
12384                                 if (!isIE) {
12385                                         ed.onKeyPress.add(function(ed, e) {
12386                                                 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
12387                                                         Event.cancel(e);
12388                                         });
12389                                 } else {
12390                                         // Ungly hack to for IE to preserve the formatting when you press
12391                                         // enter at the end of a block element with formatted contents
12392                                         // This logic overrides the browsers default logic with
12393                                         // custom logic that enables us to control the output
12394                                         tinymce.addUnload(function() {
12395                                                 t._previousFormats = 0; // Fix IE leak
12396                                         });
12397
12398                                         ed.onKeyPress.add(function(ed, e) {
12399                                                 t._previousFormats = 0;
12400
12401                                                 // Clone the current formats, this will later be applied to the new block contents
12402                                                 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
12403                                                         t._previousFormats = cloneFormats(ed.selection.getStart());
12404                                         });
12405
12406                                         ed.onKeyUp.add(function(ed, e) {
12407                                                 // Let IE break the element and the wrap the new caret location in the previous formats
12408                                                 if (e.keyCode == 13 && !e.shiftKey) {
12409                                                         var parent = ed.selection.getStart(), fmt = t._previousFormats;
12410
12411                                                         // Parent is an empty block
12412                                                         if (!parent.hasChildNodes() && fmt) {
12413                                                                 parent = dom.getParent(parent, dom.isBlock);
12414
12415                                                                 if (parent && parent.nodeName != 'LI') {
12416                                                                         parent.innerHTML = '';
12417
12418                                                                         if (t._previousFormats) {
12419                                                                                 parent.appendChild(fmt.wrapper);
12420                                                                                 fmt.inner.innerHTML = '\uFEFF';
12421                                                                         } else
12422                                                                                 parent.innerHTML = '\uFEFF';
12423
12424                                                                         selection.select(parent, 1);
12425                                                                         selection.collapse(true);
12426                                                                         ed.getDoc().execCommand('Delete', false, null);
12427                                                                         t._previousFormats = 0;
12428                                                                 }
12429                                                         }
12430                                                 }
12431                                         });
12432                                 }
12433
12434                                 if (isGecko) {
12435                                         ed.onKeyDown.add(function(ed, e) {
12436                                                 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
12437                                                         t.backspaceDelete(e, e.keyCode == 8);
12438                                         });
12439                                 }
12440                         }
12441
12442                         // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
12443                         if (tinymce.isWebKit) {
12444                                 function insertBr(ed) {
12445                                         var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
12446
12447                                         // Insert BR element
12448                                         rng.insertNode(br = dom.create('br'));
12449
12450                                         // Place caret after BR
12451                                         rng.setStartAfter(br);
12452                                         rng.setEndAfter(br);
12453                                         selection.setRng(rng);
12454
12455                                         // Could not place caret after BR then insert an nbsp entity and move the caret
12456                                         if (selection.getSel().focusNode == br.previousSibling) {
12457                                                 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
12458                                                 selection.collapse(TRUE);
12459                                         }
12460
12461                                         // Create a temporary DIV after the BR and get the position as it
12462                                         // seems like getPos() returns 0 for text nodes and BR elements.
12463                                         dom.insertAfter(div, br);
12464                                         divYPos = dom.getPos(div).y;
12465                                         dom.remove(div);
12466
12467                                         // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
12468                                         if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
12469                                                 ed.getWin().scrollTo(0, divYPos);
12470                                 };
12471
12472                                 ed.onKeyPress.add(function(ed, e) {
12473                                         if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
12474                                                 insertBr(ed);
12475                                                 Event.cancel(e);
12476                                         }
12477                                 });
12478                         }
12479
12480                         // IE specific fixes
12481                         if (isIE) {
12482                                 // Replaces IE:s auto generated paragraphs with the specified element name
12483                                 if (s.element != 'P') {
12484                                         ed.onKeyPress.add(function(ed, e) {
12485                                                 t.lastElm = selection.getNode().nodeName;
12486                                         });
12487
12488                                         ed.onKeyUp.add(function(ed, e) {
12489                                                 var bl, n = selection.getNode(), b = ed.getBody();
12490
12491                                                 if (b.childNodes.length === 1 && n.nodeName == 'P') {
12492                                                         n = dom.rename(n, s.element);
12493                                                         selection.select(n);
12494                                                         selection.collapse();
12495                                                         ed.nodeChanged();
12496                                                 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
12497                                                         bl = dom.getParent(n, 'p');
12498
12499                                                         if (bl) {
12500                                                                 dom.rename(bl, s.element);
12501                                                                 ed.nodeChanged();
12502                                                         }
12503                                                 }
12504                                         });
12505                                 }
12506                         }
12507                 },
12508
12509                 find : function(n, t, s) {
12510                         var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, FALSE), c = -1;
12511
12512                         while (n = w.nextNode()) {
12513                                 c++;
12514
12515                                 // Index by node
12516                                 if (t == 0 && n == s)
12517                                         return c;
12518
12519                                 // Node by index
12520                                 if (t == 1 && c == s)
12521                                         return n;
12522                         }
12523
12524                         return -1;
12525                 },
12526
12527                 forceRoots : function(ed, e) {
12528                         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;
12529                         var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
12530
12531                         // Fix for bug #1863847
12532                         //if (e && e.keyCode == 13)
12533                         //      return TRUE;
12534
12535                         // Wrap non blocks into blocks
12536                         for (i = nl.length - 1; i >= 0; i--) {
12537                                 nx = nl[i];
12538
12539                                 // Ignore internal elements
12540                                 if (nx.nodeType === 1 && nx.getAttribute('data-mce-type')) {
12541                                         bl = null;
12542                                         continue;
12543                                 }
12544
12545                                 // Is text or non block element
12546                                 if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
12547                                         if (!bl) {
12548                                                 // Create new block but ignore whitespace
12549                                                 if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
12550                                                         // Store selection
12551                                                         if (si == -2 && r) {
12552                                                                 if (!isIE || r.setStart) {
12553                                                                         // If selection is element then mark it
12554                                                                         if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
12555                                                                                 // Save the id of the selected element
12556                                                                                 eid = n.getAttribute("id");
12557                                                                                 n.setAttribute("id", "__mce");
12558                                                                         } else {
12559                                                                                 // If element is inside body, might not be the case in contentEdiable mode
12560                                                                                 if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
12561                                                                                         so = r.startOffset;
12562                                                                                         eo = r.endOffset;
12563                                                                                         si = t.find(b, 0, r.startContainer);
12564                                                                                         ei = t.find(b, 0, r.endContainer);
12565                                                                                 }
12566                                                                         }
12567                                                                 } else {
12568                                                                         // Force control range into text range
12569                                                                         if (r.item) {
12570                                                                                 tr = d.body.createTextRange();
12571                                                                                 tr.moveToElementText(r.item(0));
12572                                                                                 r = tr;
12573                                                                         }
12574
12575                                                                         tr = d.body.createTextRange();
12576                                                                         tr.moveToElementText(b);
12577                                                                         tr.collapse(1);
12578                                                                         bp = tr.move('character', c) * -1;
12579
12580                                                                         tr = r.duplicate();
12581                                                                         tr.collapse(1);
12582                                                                         sp = tr.move('character', c) * -1;
12583
12584                                                                         tr = r.duplicate();
12585                                                                         tr.collapse(0);
12586                                                                         le = (tr.move('character', c) * -1) - sp;
12587
12588                                                                         si = sp - bp;
12589                                                                         ei = le;
12590                                                                 }
12591                                                         }
12592
12593                                                         // Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
12594                                                         // See: http://support.microsoft.com/kb/829907
12595                                                         bl = ed.dom.create(ed.settings.forced_root_block);
12596                                                         nx.parentNode.replaceChild(bl, nx);
12597                                                         bl.appendChild(nx);
12598                                                 }
12599                                         } else {
12600                                                 if (bl.hasChildNodes())
12601                                                         bl.insertBefore(nx, bl.firstChild);
12602                                                 else
12603                                                         bl.appendChild(nx);
12604                                         }
12605                                 } else
12606                                         bl = null; // Time to create new block
12607                         }
12608
12609                         // Restore selection
12610                         if (si != -2) {
12611                                 if (!isIE || r.setStart) {
12612                                         bl = b.getElementsByTagName(ed.settings.element)[0];
12613                                         r = d.createRange();
12614
12615                                         // Select last location or generated block
12616                                         if (si != -1)
12617                                                 r.setStart(t.find(b, 1, si), so);
12618                                         else
12619                                                 r.setStart(bl, 0);
12620
12621                                         // Select last location or generated block
12622                                         if (ei != -1)
12623                                                 r.setEnd(t.find(b, 1, ei), eo);
12624                                         else
12625                                                 r.setEnd(bl, 0);
12626
12627                                         if (s) {
12628                                                 s.removeAllRanges();
12629                                                 s.addRange(r);
12630                                         }
12631                                 } else {
12632                                         try {
12633                                                 r = s.createRange();
12634                                                 r.moveToElementText(b);
12635                                                 r.collapse(1);
12636                                                 r.moveStart('character', si);
12637                                                 r.moveEnd('character', ei);
12638                                                 r.select();
12639                                         } catch (ex) {
12640                                                 // Ignore
12641                                         }
12642                                 }
12643                         } else if ((!isIE || r.setStart) && (n = ed.dom.get('__mce'))) {
12644                                 // Restore the id of the selected element
12645                                 if (eid)
12646                                         n.setAttribute('id', eid);
12647                                 else
12648                                         n.removeAttribute('id');
12649
12650                                 // Move caret before selected element
12651                                 r = d.createRange();
12652                                 r.setStartBefore(n);
12653                                 r.setEndBefore(n);
12654                                 se.setRng(r);
12655                         }
12656                 },
12657
12658                 getParentBlock : function(n) {
12659                         var d = this.dom;
12660
12661                         return d.getParent(n, d.isBlock);
12662                 },
12663
12664                 insertPara : function(e) {
12665                         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;
12666                         var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
12667
12668                         ed.undoManager.beforeChange();
12669
12670                         // If root blocks are forced then use Operas default behavior since it's really good
12671 // Removed due to bug: #1853816
12672 //                      if (se.forced_root_block && isOpera)
12673 //                              return TRUE;
12674
12675                         // Setup before range
12676                         rb = d.createRange();
12677
12678                         // If is before the first block element and in body, then move it into first block element
12679                         rb.setStart(s.anchorNode, s.anchorOffset);
12680                         rb.collapse(TRUE);
12681
12682                         // Setup after range
12683                         ra = d.createRange();
12684
12685                         // If is before the first block element and in body, then move it into first block element
12686                         ra.setStart(s.focusNode, s.focusOffset);
12687                         ra.collapse(TRUE);
12688
12689                         // Setup start/end points
12690                         dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
12691                         sn = dir ? s.anchorNode : s.focusNode;
12692                         so = dir ? s.anchorOffset : s.focusOffset;
12693                         en = dir ? s.focusNode : s.anchorNode;
12694                         eo = dir ? s.focusOffset : s.anchorOffset;
12695
12696                         // If selection is in empty table cell
12697                         if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
12698                                 if (sn.firstChild.nodeName == 'BR')
12699                                         dom.remove(sn.firstChild); // Remove BR
12700
12701                                 // Create two new block elements
12702                                 if (sn.childNodes.length == 0) {
12703                                         ed.dom.add(sn, se.element, null, '<br />');
12704                                         aft = ed.dom.add(sn, se.element, null, '<br />');
12705                                 } else {
12706                                         n = sn.innerHTML;
12707                                         sn.innerHTML = '';
12708                                         ed.dom.add(sn, se.element, null, n);
12709                                         aft = ed.dom.add(sn, se.element, null, '<br />');
12710                                 }
12711
12712                                 // Move caret into the last one
12713                                 r = d.createRange();
12714                                 r.selectNodeContents(aft);
12715                                 r.collapse(1);
12716                                 ed.selection.setRng(r);
12717
12718                                 return FALSE;
12719                         }
12720
12721                         // If the caret is in an invalid location in FF we need to move it into the first block
12722                         if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
12723                                 sn = en = sn.firstChild;
12724                                 so = eo = 0;
12725                                 rb = d.createRange();
12726                                 rb.setStart(sn, 0);
12727                                 ra = d.createRange();
12728                                 ra.setStart(en, 0);
12729                         }
12730
12731                         // Never use body as start or end node
12732                         sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
12733                         sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
12734                         en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
12735                         en = en.nodeName == "BODY" ? en.firstChild : en;
12736
12737                         // Get start and end blocks
12738                         sb = t.getParentBlock(sn);
12739                         eb = t.getParentBlock(en);
12740                         bn = sb ? sb.nodeName : se.element; // Get block name to create
12741
12742                         // Return inside list use default browser behavior
12743                         if (n = t.dom.getParent(sb, 'li,pre')) {
12744                                 if (n.nodeName == 'LI')
12745                                         return splitList(ed.selection, t.dom, n);
12746
12747                                 return TRUE;
12748                         }
12749
12750                         // If caption or absolute layers then always generate new blocks within
12751                         if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
12752                                 bn = se.element;
12753                                 sb = null;
12754                         }
12755
12756                         // If caption or absolute layers then always generate new blocks within
12757                         if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
12758                                 bn = se.element;
12759                                 eb = null;
12760                         }
12761
12762                         // Use P instead
12763                         if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
12764                                 bn = se.element;
12765                                 sb = eb = null;
12766                         }
12767
12768                         // Setup new before and after blocks
12769                         bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
12770                         aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
12771
12772                         // Remove id from after clone
12773                         aft.removeAttribute('id');
12774
12775                         // Is header and cursor is at the end, then force paragraph under
12776                         if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
12777                                 aft = ed.dom.create(se.element);
12778
12779                         // Find start chop node
12780                         n = sc = sn;
12781                         do {
12782                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
12783                                         break;
12784
12785                                 sc = n;
12786                         } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
12787
12788                         // Find end chop node
12789                         n = ec = en;
12790                         do {
12791                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
12792                                         break;
12793
12794                                 ec = n;
12795                         } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
12796
12797                         // Place first chop part into before block element
12798                         if (sc.nodeName == bn)
12799                                 rb.setStart(sc, 0);
12800                         else
12801                                 rb.setStartBefore(sc);
12802
12803                         rb.setEnd(sn, so);
12804                         bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
12805
12806                         // Place secnd chop part within new block element
12807                         try {
12808                                 ra.setEndAfter(ec);
12809                         } catch(ex) {
12810                                 //console.debug(s.focusNode, s.focusOffset);
12811                         }
12812
12813                         ra.setStart(en, eo);
12814                         aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
12815
12816                         // Create range around everything
12817                         r = d.createRange();
12818                         if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
12819                                 r.setStartBefore(sc.parentNode);
12820                         } else {
12821                                 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
12822                                         r.setStartBefore(rb.startContainer);
12823                                 else
12824                                         r.setStart(rb.startContainer, rb.startOffset);
12825                         }
12826
12827                         if (!ec.nextSibling && ec.parentNode.nodeName == bn)
12828                                 r.setEndAfter(ec.parentNode);
12829                         else
12830                                 r.setEnd(ra.endContainer, ra.endOffset);
12831
12832                         // Delete and replace it with new block elements
12833                         r.deleteContents();
12834
12835                         if (isOpera)
12836                                 ed.getWin().scrollTo(0, vp.y);
12837
12838                         // Never wrap blocks in blocks
12839                         if (bef.firstChild && bef.firstChild.nodeName == bn)
12840                                 bef.innerHTML = bef.firstChild.innerHTML;
12841
12842                         if (aft.firstChild && aft.firstChild.nodeName == bn)
12843                                 aft.innerHTML = aft.firstChild.innerHTML;
12844
12845                         // Padd empty blocks
12846                         if (dom.isEmpty(bef))
12847                                 bef.innerHTML = '<br />';
12848
12849                         function appendStyles(e, en) {
12850                                 var nl = [], nn, n, i;
12851
12852                                 e.innerHTML = '';
12853
12854                                 // Make clones of style elements
12855                                 if (se.keep_styles) {
12856                                         n = en;
12857                                         do {
12858                                                 // We only want style specific elements
12859                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
12860                                                         nn = n.cloneNode(FALSE);
12861                                                         dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
12862                                                         nl.push(nn);
12863                                                 }
12864                                         } while (n = n.parentNode);
12865                                 }
12866
12867                                 // Append style elements to aft
12868                                 if (nl.length > 0) {
12869                                         for (i = nl.length - 1, nn = e; i >= 0; i--)
12870                                                 nn = nn.appendChild(nl[i]);
12871
12872                                         // Padd most inner style element
12873                                         nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
12874                                         return nl[0]; // Move caret to most inner element
12875                                 } else
12876                                         e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
12877                         };
12878
12879                         // Fill empty afterblook with current style
12880                         if (dom.isEmpty(aft))
12881                                 car = appendStyles(aft, en);
12882
12883                         // Opera needs this one backwards for older versions
12884                         if (isOpera && parseFloat(opera.version()) < 9.5) {
12885                                 r.insertNode(bef);
12886                                 r.insertNode(aft);
12887                         } else {
12888                                 r.insertNode(aft);
12889                                 r.insertNode(bef);
12890                         }
12891
12892                         // Normalize
12893                         aft.normalize();
12894                         bef.normalize();
12895
12896                         function first(n) {
12897                                 return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE).nextNode() || n;
12898                         };
12899
12900                         // Move cursor and scroll into view
12901                         r = d.createRange();
12902                         r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
12903                         r.collapse(1);
12904                         s.removeAllRanges();
12905                         s.addRange(r);
12906
12907                         // 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
12908                         y = ed.dom.getPos(aft).y;
12909                         //ch = aft.clientHeight;
12910
12911                         // Is element within viewport
12912                         if (y < vp.y || y + 25 > vp.y + vp.h) {
12913                                 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
12914
12915                                 /*console.debug(
12916                                         'Element: y=' + y + ', h=' + ch + ', ' +
12917                                         'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
12918                                 );*/
12919                         }
12920
12921                         ed.undoManager.add();
12922
12923                         return FALSE;
12924                 },
12925
12926                 backspaceDelete : function(e, bs) {
12927                         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;
12928
12929                         // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
12930                         if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
12931                                 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
12932
12933                                 // Walk the dom backwards until we find a text node
12934                                 for (n = sc.lastChild; n; n = walker.prev()) {
12935                                         if (n.nodeType == 3) {
12936                                                 r.setStart(n, n.nodeValue.length);
12937                                                 r.collapse(true);
12938                                                 se.setRng(r);
12939                                                 return;
12940                                         }
12941                                 }
12942                         }
12943
12944                         // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
12945                         // This workaround removes the element by hand and moves the caret to the previous element
12946                         if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
12947                                 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
12948                                         // Find previous block element
12949                                         n = sc;
12950                                         while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
12951
12952                                         if (n) {
12953                                                 if (sc != b.firstChild) {
12954                                                         // Find last text node
12955                                                         w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
12956                                                         while (tn = w.nextNode())
12957                                                                 n = tn;
12958
12959                                                         // Place caret at the end of last text node
12960                                                         r = ed.getDoc().createRange();
12961                                                         r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
12962                                                         r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
12963                                                         se.setRng(r);
12964
12965                                                         // Remove the target container
12966                                                         ed.dom.remove(sc);
12967                                                 }
12968
12969                                                 return Event.cancel(e);
12970                                         }
12971                                 }
12972                         }
12973                 }
12974         });
12975 })(tinymce);
12976
12977 (function(tinymce) {
12978         // Shorten names
12979         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
12980
12981         tinymce.create('tinymce.ControlManager', {
12982                 ControlManager : function(ed, s) {
12983                         var t = this, i;
12984
12985                         s = s || {};
12986                         t.editor = ed;
12987                         t.controls = {};
12988                         t.onAdd = new tinymce.util.Dispatcher(t);
12989                         t.onPostRender = new tinymce.util.Dispatcher(t);
12990                         t.prefix = s.prefix || ed.id + '_';
12991                         t._cls = {};
12992
12993                         t.onPostRender.add(function() {
12994                                 each(t.controls, function(c) {
12995                                         c.postRender();
12996                                 });
12997                         });
12998                 },
12999
13000                 get : function(id) {
13001                         return this.controls[this.prefix + id] || this.controls[id];
13002                 },
13003
13004                 setActive : function(id, s) {
13005                         var c = null;
13006
13007                         if (c = this.get(id))
13008                                 c.setActive(s);
13009
13010                         return c;
13011                 },
13012
13013                 setDisabled : function(id, s) {
13014                         var c = null;
13015
13016                         if (c = this.get(id))
13017                                 c.setDisabled(s);
13018
13019                         return c;
13020                 },
13021
13022                 add : function(c) {
13023                         var t = this;
13024
13025                         if (c) {
13026                                 t.controls[c.id] = c;
13027                                 t.onAdd.dispatch(c, t);
13028                         }
13029
13030                         return c;
13031                 },
13032
13033                 createControl : function(n) {
13034                         var c, t = this, ed = t.editor;
13035
13036                         each(ed.plugins, function(p) {
13037                                 if (p.createControl) {
13038                                         c = p.createControl(n, t);
13039
13040                                         if (c)
13041                                                 return false;
13042                                 }
13043                         });
13044
13045                         switch (n) {
13046                                 case "|":
13047                                 case "separator":
13048                                         return t.createSeparator();
13049                         }
13050
13051                         if (!c && ed.buttons && (c = ed.buttons[n]))
13052                                 return t.createButton(n, c);
13053
13054                         return t.add(c);
13055                 },
13056
13057                 createDropMenu : function(id, s, cc) {
13058                         var t = this, ed = t.editor, c, bm, v, cls;
13059
13060                         s = extend({
13061                                 'class' : 'mceDropDown',
13062                                 constrain : ed.settings.constrain_menus
13063                         }, s);
13064
13065                         s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
13066                         if (v = ed.getParam('skin_variant'))
13067                                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
13068
13069                         id = t.prefix + id;
13070                         cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
13071                         c = t.controls[id] = new cls(id, s);
13072                         c.onAddItem.add(function(c, o) {
13073                                 var s = o.settings;
13074
13075                                 s.title = ed.getLang(s.title, s.title);
13076
13077                                 if (!s.onclick) {
13078                                         s.onclick = function(v) {
13079                                                 if (s.cmd)
13080                                                         ed.execCommand(s.cmd, s.ui || false, s.value);
13081                                         };
13082                                 }
13083                         });
13084
13085                         ed.onRemove.add(function() {
13086                                 c.destroy();
13087                         });
13088
13089                         // Fix for bug #1897785, #1898007
13090                         if (tinymce.isIE) {
13091                                 c.onShowMenu.add(function() {
13092                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
13093                                         ed.focus();
13094
13095                                         bm = ed.selection.getBookmark(1);
13096                                 });
13097
13098                                 c.onHideMenu.add(function() {
13099                                         if (bm) {
13100                                                 ed.selection.moveToBookmark(bm);
13101                                                 bm = 0;
13102                                         }
13103                                 });
13104                         }
13105
13106                         return t.add(c);
13107                 },
13108
13109                 createListBox : function(id, s, cc) {
13110                         var t = this, ed = t.editor, cmd, c, cls;
13111
13112                         if (t.get(id))
13113                                 return null;
13114
13115                         s.title = ed.translate(s.title);
13116                         s.scope = s.scope || ed;
13117
13118                         if (!s.onselect) {
13119                                 s.onselect = function(v) {
13120                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13121                                 };
13122                         }
13123
13124                         s = extend({
13125                                 title : s.title,
13126                                 'class' : 'mce_' + id,
13127                                 scope : s.scope,
13128                                 control_manager : t
13129                         }, s);
13130
13131                         id = t.prefix + id;
13132
13133                         if (ed.settings.use_native_selects)
13134                                 c = new tinymce.ui.NativeListBox(id, s);
13135                         else {
13136                                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
13137                                 c = new cls(id, s, ed);
13138                         }
13139
13140                         t.controls[id] = c;
13141
13142                         // Fix focus problem in Safari
13143                         if (tinymce.isWebKit) {
13144                                 c.onPostRender.add(function(c, n) {
13145                                         // Store bookmark on mousedown
13146                                         Event.add(n, 'mousedown', function() {
13147                                                 ed.bookmark = ed.selection.getBookmark(1);
13148                                         });
13149
13150                                         // Restore on focus, since it might be lost
13151                                         Event.add(n, 'focus', function() {
13152                                                 ed.selection.moveToBookmark(ed.bookmark);
13153                                                 ed.bookmark = null;
13154                                         });
13155                                 });
13156                         }
13157
13158                         if (c.hideMenu)
13159                                 ed.onMouseDown.add(c.hideMenu, c);
13160
13161                         return t.add(c);
13162                 },
13163
13164                 createButton : function(id, s, cc) {
13165                         var t = this, ed = t.editor, o, c, cls;
13166
13167                         if (t.get(id))
13168                                 return null;
13169
13170                         s.title = ed.translate(s.title);
13171                         s.label = ed.translate(s.label);
13172                         s.scope = s.scope || ed;
13173
13174                         if (!s.onclick && !s.menu_button) {
13175                                 s.onclick = function() {
13176                                         ed.execCommand(s.cmd, s.ui || false, s.value);
13177                                 };
13178                         }
13179
13180                         s = extend({
13181                                 title : s.title,
13182                                 'class' : 'mce_' + id,
13183                                 unavailable_prefix : ed.getLang('unavailable', ''),
13184                                 scope : s.scope,
13185                                 control_manager : t
13186                         }, s);
13187
13188                         id = t.prefix + id;
13189
13190                         if (s.menu_button) {
13191                                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
13192                                 c = new cls(id, s, ed);
13193                                 ed.onMouseDown.add(c.hideMenu, c);
13194                         } else {
13195                                 cls = t._cls.button || tinymce.ui.Button;
13196                                 c = new cls(id, s);
13197                         }
13198
13199                         return t.add(c);
13200                 },
13201
13202                 createMenuButton : function(id, s, cc) {
13203                         s = s || {};
13204                         s.menu_button = 1;
13205
13206                         return this.createButton(id, s, cc);
13207                 },
13208
13209                 createSplitButton : function(id, s, cc) {
13210                         var t = this, ed = t.editor, cmd, c, cls;
13211
13212                         if (t.get(id))
13213                                 return null;
13214
13215                         s.title = ed.translate(s.title);
13216                         s.scope = s.scope || ed;
13217
13218                         if (!s.onclick) {
13219                                 s.onclick = function(v) {
13220                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13221                                 };
13222                         }
13223
13224                         if (!s.onselect) {
13225                                 s.onselect = function(v) {
13226                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13227                                 };
13228                         }
13229
13230                         s = extend({
13231                                 title : s.title,
13232                                 'class' : 'mce_' + id,
13233                                 scope : s.scope,
13234                                 control_manager : t
13235                         }, s);
13236
13237                         id = t.prefix + id;
13238                         cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
13239                         c = t.add(new cls(id, s, ed));
13240                         ed.onMouseDown.add(c.hideMenu, c);
13241
13242                         return c;
13243                 },
13244
13245                 createColorSplitButton : function(id, s, cc) {
13246                         var t = this, ed = t.editor, cmd, c, cls, bm;
13247
13248                         if (t.get(id))
13249                                 return null;
13250
13251                         s.title = ed.translate(s.title);
13252                         s.scope = s.scope || ed;
13253
13254                         if (!s.onclick) {
13255                                 s.onclick = function(v) {
13256                                         if (tinymce.isIE)
13257                                                 bm = ed.selection.getBookmark(1);
13258
13259                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13260                                 };
13261                         }
13262
13263                         if (!s.onselect) {
13264                                 s.onselect = function(v) {
13265                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
13266                                 };
13267                         }
13268
13269                         s = extend({
13270                                 title : s.title,
13271                                 'class' : 'mce_' + id,
13272                                 'menu_class' : ed.getParam('skin') + 'Skin',
13273                                 scope : s.scope,
13274                                 more_colors_title : ed.getLang('more_colors')
13275                         }, s);
13276
13277                         id = t.prefix + id;
13278                         cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
13279                         c = new cls(id, s, ed);
13280                         ed.onMouseDown.add(c.hideMenu, c);
13281
13282                         // Remove the menu element when the editor is removed
13283                         ed.onRemove.add(function() {
13284                                 c.destroy();
13285                         });
13286
13287                         // Fix for bug #1897785, #1898007
13288                         if (tinymce.isIE) {
13289                                 c.onShowMenu.add(function() {
13290                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
13291                                         ed.focus();
13292                                         bm = ed.selection.getBookmark(1);
13293                                 });
13294
13295                                 c.onHideMenu.add(function() {
13296                                         if (bm) {
13297                                                 ed.selection.moveToBookmark(bm);
13298                                                 bm = 0;
13299                                         }
13300                                 });
13301                         }
13302
13303                         return t.add(c);
13304                 },
13305
13306                 createToolbar : function(id, s, cc) {
13307                         var c, t = this, cls;
13308
13309                         id = t.prefix + id;
13310                         cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
13311                         c = new cls(id, s, t.editor);
13312
13313                         if (t.get(id))
13314                                 return null;
13315
13316                         return t.add(c);
13317                 },
13318                 
13319                 createToolbarGroup : function(id, s, cc) {
13320                         var c, t = this, cls;
13321                         id = t.prefix + id;
13322                         cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
13323                         c = new cls(id, s, t.editor);
13324                         
13325                         if (t.get(id))
13326                                 return null;
13327                         
13328                         return t.add(c);
13329                 },
13330
13331                 createSeparator : function(cc) {
13332                         var cls = cc || this._cls.separator || tinymce.ui.Separator;
13333
13334                         return new cls();
13335                 },
13336
13337                 setControlType : function(n, c) {
13338                         return this._cls[n.toLowerCase()] = c;
13339                 },
13340         
13341                 destroy : function() {
13342                         each(this.controls, function(c) {
13343                                 c.destroy();
13344                         });
13345
13346                         this.controls = null;
13347                 }
13348         });
13349 })(tinymce);
13350
13351 (function(tinymce) {
13352         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
13353
13354         tinymce.create('tinymce.WindowManager', {
13355                 WindowManager : function(ed) {
13356                         var t = this;
13357
13358                         t.editor = ed;
13359                         t.onOpen = new Dispatcher(t);
13360                         t.onClose = new Dispatcher(t);
13361                         t.params = {};
13362                         t.features = {};
13363                 },
13364
13365                 open : function(s, p) {
13366                         var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
13367
13368                         // Default some options
13369                         s = s || {};
13370                         p = p || {};
13371                         sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
13372                         sh = isOpera ? vp.h : screen.height;
13373                         s.name = s.name || 'mc_' + new Date().getTime();
13374                         s.width = parseInt(s.width || 320);
13375                         s.height = parseInt(s.height || 240);
13376                         s.resizable = true;
13377                         s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
13378                         s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
13379                         p.inline = false;
13380                         p.mce_width = s.width;
13381                         p.mce_height = s.height;
13382                         p.mce_auto_focus = s.auto_focus;
13383
13384                         if (mo) {
13385                                 if (isIE) {
13386                                         s.center = true;
13387                                         s.help = false;
13388                                         s.dialogWidth = s.width + 'px';
13389                                         s.dialogHeight = s.height + 'px';
13390                                         s.scroll = s.scrollbars || false;
13391                                 }
13392                         }
13393
13394                         // Build features string
13395                         each(s, function(v, k) {
13396                                 if (tinymce.is(v, 'boolean'))
13397                                         v = v ? 'yes' : 'no';
13398
13399                                 if (!/^(name|url)$/.test(k)) {
13400                                         if (isIE && mo)
13401                                                 f += (f ? ';' : '') + k + ':' + v;
13402                                         else
13403                                                 f += (f ? ',' : '') + k + '=' + v;
13404                                 }
13405                         });
13406
13407                         t.features = s;
13408                         t.params = p;
13409                         t.onOpen.dispatch(t, s, p);
13410
13411                         u = s.url || s.file;
13412                         u = tinymce._addVer(u);
13413
13414                         try {
13415                                 if (isIE && mo) {
13416                                         w = 1;
13417                                         window.showModalDialog(u, window, f);
13418                                 } else
13419                                         w = window.open(u, s.name, f);
13420                         } catch (ex) {
13421                                 // Ignore
13422                         }
13423
13424                         if (!w)
13425                                 alert(t.editor.getLang('popup_blocked'));
13426                 },
13427
13428                 close : function(w) {
13429                         w.close();
13430                         this.onClose.dispatch(this);
13431                 },
13432
13433                 createInstance : function(cl, a, b, c, d, e) {
13434                         var f = tinymce.resolve(cl);
13435
13436                         return new f(a, b, c, d, e);
13437                 },
13438
13439                 confirm : function(t, cb, s, w) {
13440                         w = w || window;
13441
13442                         cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
13443                 },
13444
13445                 alert : function(tx, cb, s, w) {
13446                         var t = this;
13447
13448                         w = w || window;
13449                         w.alert(t._decode(t.editor.getLang(tx, tx)));
13450
13451                         if (cb)
13452                                 cb.call(s || t);
13453                 },
13454
13455                 resizeBy : function(dw, dh, win) {
13456                         win.resizeBy(dw, dh);
13457                 },
13458
13459                 // Internal functions
13460
13461                 _decode : function(s) {
13462                         return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
13463                 }
13464         });
13465 }(tinymce));
13466 (function(tinymce) {
13467         tinymce.Formatter = function(ed) {
13468                 var formats = {},
13469                         each = tinymce.each,
13470                         dom = ed.dom,
13471                         selection = ed.selection,
13472                         TreeWalker = tinymce.dom.TreeWalker,
13473                         rangeUtils = new tinymce.dom.RangeUtils(dom),
13474                         isValid = ed.schema.isValidChild,
13475                         isBlock = dom.isBlock,
13476                         forcedRootBlock = ed.settings.forced_root_block,
13477                         nodeIndex = dom.nodeIndex,
13478                         INVISIBLE_CHAR = '\uFEFF',
13479                         MCE_ATTR_RE = /^(src|href|style)$/,
13480                         FALSE = false,
13481                         TRUE = true,
13482                         undefined,
13483                         pendingFormats = {apply : [], remove : []};
13484
13485                 function isArray(obj) {
13486                         return obj instanceof Array;
13487                 };
13488
13489                 function getParents(node, selector) {
13490                         return dom.getParents(node, selector, dom.getRoot());
13491                 };
13492
13493                 function isCaretNode(node) {
13494                         return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
13495                 };
13496
13497                 // Public functions
13498
13499                 function get(name) {
13500                         return name ? formats[name] : formats;
13501                 };
13502
13503                 function register(name, format) {
13504                         if (name) {
13505                                 if (typeof(name) !== 'string') {
13506                                         each(name, function(format, name) {
13507                                                 register(name, format);
13508                                         });
13509                                 } else {
13510                                         // Force format into array and add it to internal collection
13511                                         format = format.length ? format : [format];
13512
13513                                         each(format, function(format) {
13514                                                 // Set deep to false by default on selector formats this to avoid removing
13515                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs
13516                                                 if (format.deep === undefined)
13517                                                         format.deep = !format.selector;
13518
13519                                                 // Default to true
13520                                                 if (format.split === undefined)
13521                                                         format.split = !format.selector || format.inline;
13522
13523                                                 // Default to true
13524                                                 if (format.remove === undefined && format.selector && !format.inline)
13525                                                         format.remove = 'none';
13526
13527                                                 // Mark format as a mixed format inline + block level
13528                                                 if (format.selector && format.inline) {
13529                                                         format.mixed = true;
13530                                                         format.block_expand = true;
13531                                                 }
13532
13533                                                 // Split classes if needed
13534                                                 if (typeof(format.classes) === 'string')
13535                                                         format.classes = format.classes.split(/\s+/);
13536                                         });
13537
13538                                         formats[name] = format;
13539                                 }
13540                         }
13541                 };
13542
13543                 var getTextDecoration = function(node) {
13544                         var decoration;
13545
13546                         ed.dom.getParent(node, function(n) {
13547                                 decoration = ed.dom.getStyle(n, 'text-decoration');
13548                                 return decoration && decoration !== 'none';
13549                         });
13550
13551                         return decoration;
13552                 };
13553
13554                 var processUnderlineAndColor = function(node) {
13555                         var textDecoration;
13556                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
13557                                 textDecoration = getTextDecoration(node.parentNode);
13558                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {
13559                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);
13560                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
13561                                         ed.dom.setStyle(node, 'text-decoration', null);
13562                                 }
13563                         }
13564                 };
13565
13566                 function apply(name, vars, node) {
13567                         var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
13568
13569                         function moveStart(rng) {
13570                                 var container = rng.startContainer,
13571                                         offset = rng.startOffset,
13572                                         walker, node;
13573
13574                                 // Move startContainer/startOffset in to a suitable node
13575                                 if (container.nodeType == 1 || container.nodeValue === "") {
13576                                         container = container.nodeType == 1 ? container.childNodes[offset] : container;
13577
13578                                         // Might fail if the offset is behind the last element in it's container
13579                                         if (container) {
13580                                                 walker = new TreeWalker(container, container.parentNode);
13581                                                 for (node = walker.current(); node; node = walker.next()) {
13582                                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
13583                                                                 rng.setStart(node, 0);
13584                                                                 break;
13585                                                         }
13586                                                 }
13587                                         }
13588                                 }
13589
13590                                 return rng;
13591                         };
13592
13593                         function setElementFormat(elm, fmt) {
13594                                 fmt = fmt || format;
13595
13596                                 if (elm) {
13597                                         each(fmt.styles, function(value, name) {
13598                                                 dom.setStyle(elm, name, replaceVars(value, vars));
13599                                         });
13600
13601                                         each(fmt.attributes, function(value, name) {
13602                                                 dom.setAttrib(elm, name, replaceVars(value, vars));
13603                                         });
13604
13605                                         each(fmt.classes, function(value) {
13606                                                 value = replaceVars(value, vars);
13607
13608                                                 if (!dom.hasClass(elm, value))
13609                                                         dom.addClass(elm, value);
13610                                         });
13611                                 }
13612                         };
13613
13614                         function applyRngStyle(rng) {
13615                                 var newWrappers = [], wrapName, wrapElm;
13616
13617                                 // Setup wrapper element
13618                                 wrapName = format.inline || format.block;
13619                                 wrapElm = dom.create(wrapName);
13620                                 setElementFormat(wrapElm);
13621
13622                                 rangeUtils.walk(rng, function(nodes) {
13623                                         var currentWrapElm;
13624
13625                                         function process(node) {
13626                                                 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
13627
13628                                                 // Stop wrapping on br elements
13629                                                 if (isEq(nodeName, 'br')) {
13630                                                         currentWrapElm = 0;
13631
13632                                                         // Remove any br elements when we wrap things
13633                                                         if (format.block)
13634                                                                 dom.remove(node);
13635
13636                                                         return;
13637                                                 }
13638
13639                                                 // If node is wrapper type
13640                                                 if (format.wrapper && matchNode(node, name, vars)) {
13641                                                         currentWrapElm = 0;
13642                                                         return;
13643                                                 }
13644
13645                                                 // Can we rename the block
13646                                                 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
13647                                                         node = dom.rename(node, wrapName);
13648                                                         setElementFormat(node);
13649                                                         newWrappers.push(node);
13650                                                         currentWrapElm = 0;
13651                                                         return;
13652                                                 }
13653
13654                                                 // Handle selector patterns
13655                                                 if (format.selector) {
13656                                                         // Look for matching formats
13657                                                         each(formatList, function(format) {
13658                                                                 // Check collapsed state if it exists
13659                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {
13660                                                                         return;
13661                                                                 }
13662
13663                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {
13664                                                                         setElementFormat(node, format);
13665                                                                         found = true;
13666                                                                 }
13667                                                         });
13668
13669                                                         // Continue processing if a selector match wasn't found and a inline element is defined
13670                                                         if (!format.inline || found) {
13671                                                                 currentWrapElm = 0;
13672                                                                 return;
13673                                                         }
13674                                                 }
13675
13676                                                 // Is it valid to wrap this item
13677                                                 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
13678                                                                 !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
13679                                                         // Start wrapping
13680                                                         if (!currentWrapElm) {
13681                                                                 // Wrap the node
13682                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
13683                                                                 node.parentNode.insertBefore(currentWrapElm, node);
13684                                                                 newWrappers.push(currentWrapElm);
13685                                                         }
13686
13687                                                         currentWrapElm.appendChild(node);
13688                                                 } else {
13689                                                         // Start a new wrapper for possible children
13690                                                         currentWrapElm = 0;
13691
13692                                                         each(tinymce.grep(node.childNodes), process);
13693
13694                                                         // End the last wrapper
13695                                                         currentWrapElm = 0;
13696                                                 }
13697                                         };
13698
13699                                         // Process siblings from range
13700                                         each(nodes, process);
13701                                 });
13702
13703                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
13704                                 if (format.wrap_links === false) {
13705                                         each(newWrappers, function(node) {
13706                                                 function process(node) {
13707                                                         var i, currentWrapElm, children;
13708
13709                                                         if (node.nodeName === 'A') {
13710                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
13711                                                                 newWrappers.push(currentWrapElm);
13712
13713                                                                 children = tinymce.grep(node.childNodes);
13714                                                                 for (i = 0; i < children.length; i++)
13715                                                                         currentWrapElm.appendChild(children[i]);
13716
13717                                                                 node.appendChild(currentWrapElm);
13718                                                         }
13719
13720                                                         each(tinymce.grep(node.childNodes), process);
13721                                                 };
13722
13723                                                 process(node);
13724                                         });
13725                                 }
13726
13727                                 // Cleanup
13728                                 each(newWrappers, function(node) {
13729                                         var childCount;
13730
13731                                         function getChildCount(node) {
13732                                                 var count = 0;
13733
13734                                                 each(node.childNodes, function(node) {
13735                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
13736                                                                 count++;
13737                                                 });
13738
13739                                                 return count;
13740                                         };
13741
13742                                         function mergeStyles(node) {
13743                                                 var child, clone;
13744
13745                                                 each(node.childNodes, function(node) {
13746                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
13747                                                                 child = node;
13748                                                                 return FALSE; // break loop
13749                                                         }
13750                                                 });
13751
13752                                                 // If child was found and of the same type as the current node
13753                                                 if (child && matchName(child, format)) {
13754                                                         clone = child.cloneNode(FALSE);
13755                                                         setElementFormat(clone);
13756
13757                                                         dom.replace(clone, node, TRUE);
13758                                                         dom.remove(child, 1);
13759                                                 }
13760
13761                                                 return clone || node;
13762                                         };
13763
13764                                         childCount = getChildCount(node);
13765
13766                                         // Remove empty nodes but only if there is multiple wrappers and they are not block
13767                                         // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
13768                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
13769                                                 dom.remove(node, 1);
13770                                                 return;
13771                                         }
13772
13773                                         if (format.inline || format.wrapper) {
13774                                                 // Merges the current node with it's children of similar type to reduce the number of elements
13775                                                 if (!format.exact && childCount === 1)
13776                                                         node = mergeStyles(node);
13777
13778                                                 // Remove/merge children
13779                                                 each(formatList, function(format) {
13780                                                         // Merge all children of similar type will move styles from child to parent
13781                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
13782                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
13783                                                         each(dom.select(format.inline, node), function(child) {
13784                                                                 var parent;
13785
13786                                                                 // When wrap_links is set to false we don't want
13787                                                                 // to remove the format on children within links
13788                                                                 if (format.wrap_links === false) {
13789                                                                         parent = child.parentNode;
13790
13791                                                                         do {
13792                                                                                 if (parent.nodeName === 'A')
13793                                                                                         return;
13794                                                                         } while (parent = parent.parentNode);
13795                                                                 }
13796
13797                                                                 removeFormat(format, vars, child, format.exact ? child : null);
13798                                                         });
13799                                                 });
13800
13801                                                 // Remove child if direct parent is of same type
13802                                                 if (matchNode(node.parentNode, name, vars)) {
13803                                                         dom.remove(node, 1);
13804                                                         node = 0;
13805                                                         return TRUE;
13806                                                 }
13807
13808                                                 // Look for parent with similar style format
13809                                                 if (format.merge_with_parents) {
13810                                                         dom.getParent(node.parentNode, function(parent) {
13811                                                                 if (matchNode(parent, name, vars)) {
13812                                                                         dom.remove(node, 1);
13813                                                                         node = 0;
13814                                                                         return TRUE;
13815                                                                 }
13816                                                         });
13817                                                 }
13818
13819                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
13820                                                 if (node) {
13821                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
13822                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
13823                                                 }
13824                                         }
13825                                 });
13826                         };
13827
13828                         if (format) {
13829                                 if (node) {
13830                                         rng = dom.createRng();
13831
13832                                         rng.setStartBefore(node);
13833                                         rng.setEndAfter(node);
13834
13835                                         applyRngStyle(expandRng(rng, formatList));
13836                                 } else {
13837                                         if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
13838                                                 // Obtain selection node before selection is unselected by applyRngStyle()
13839                                                 var curSelNode = ed.selection.getNode();
13840
13841                                                 // Apply formatting to selection
13842                                                 bookmark = selection.getBookmark();
13843                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));
13844
13845                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.
13846                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
13847                                                         tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
13848                                                         processUnderlineAndColor(curSelNode);
13849                                                 }
13850
13851                                                 selection.moveToBookmark(bookmark);
13852                                                 selection.setRng(moveStart(selection.getRng(TRUE)));
13853                                                 ed.nodeChanged();
13854                                         } else
13855                                                 performCaretAction('apply', name, vars);
13856                                 }
13857                         }
13858                 };
13859
13860                 function remove(name, vars, node) {
13861                         var formatList = get(name), format = formatList[0], bookmark, i, rng;
13862
13863                         function moveStart(rng) {
13864                                 var container = rng.startContainer,
13865                                         offset = rng.startOffset,
13866                                         walker, node, nodes, tmpNode;
13867
13868                                 // Convert text node into index if possible
13869                                 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
13870                                         container = container.parentNode;
13871                                         offset = nodeIndex(container) + 1;
13872                                 }
13873
13874                                 // Move startContainer/startOffset in to a suitable node
13875                                 if (container.nodeType == 1) {
13876                                         nodes = container.childNodes;
13877                                         container = nodes[Math.min(offset, nodes.length - 1)];
13878                                         walker = new TreeWalker(container);
13879
13880                                         // If offset is at end of the parent node walk to the next one
13881                                         if (offset > nodes.length - 1)
13882                                                 walker.next();
13883
13884                                         for (node = walker.current(); node; node = walker.next()) {
13885                                                 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
13886                                                         // IE has a "neat" feature where it moves the start node into the closest element
13887                                                         // we can avoid this by inserting an element before it and then remove it after we set the selection
13888                                                         tmpNode = dom.create('a', null, INVISIBLE_CHAR);
13889                                                         node.parentNode.insertBefore(tmpNode, node);
13890
13891                                                         // Set selection and remove tmpNode
13892                                                         rng.setStart(node, 0);
13893                                                         selection.setRng(rng);
13894                                                         dom.remove(tmpNode);
13895
13896                                                         return;
13897                                                 }
13898                                         }
13899                                 }
13900                         };
13901
13902                         // Merges the styles for each node
13903                         function process(node) {
13904                                 var children, i, l;
13905
13906                                 // Grab the children first since the nodelist might be changed
13907                                 children = tinymce.grep(node.childNodes);
13908
13909                                 // Process current node
13910                                 for (i = 0, l = formatList.length; i < l; i++) {
13911                                         if (removeFormat(formatList[i], vars, node, node))
13912                                                 break;
13913                                 }
13914
13915                                 // Process the children
13916                                 if (format.deep) {
13917                                         for (i = 0, l = children.length; i < l; i++)
13918                                                 process(children[i]);
13919                                 }
13920                         };
13921
13922                         function findFormatRoot(container) {
13923                                 var formatRoot;
13924
13925                                 // Find format root
13926                                 each(getParents(container.parentNode).reverse(), function(parent) {
13927                                         var format;
13928
13929                                         // Find format root element
13930                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
13931                                                 // Is the node matching the format we are looking for
13932                                                 format = matchNode(parent, name, vars);
13933                                                 if (format && format.split !== false)
13934                                                         formatRoot = parent;
13935                                         }
13936                                 });
13937
13938                                 return formatRoot;
13939                         };
13940
13941                         function wrapAndSplit(format_root, container, target, split) {
13942                                 var parent, clone, lastClone, firstClone, i, formatRootParent;
13943
13944                                 // Format root found then clone formats and split it
13945                                 if (format_root) {
13946                                         formatRootParent = format_root.parentNode;
13947
13948                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
13949                                                 clone = parent.cloneNode(FALSE);
13950
13951                                                 for (i = 0; i < formatList.length; i++) {
13952                                                         if (removeFormat(formatList[i], vars, clone, clone)) {
13953                                                                 clone = 0;
13954                                                                 break;
13955                                                         }
13956                                                 }
13957
13958                                                 // Build wrapper node
13959                                                 if (clone) {
13960                                                         if (lastClone)
13961                                                                 clone.appendChild(lastClone);
13962
13963                                                         if (!firstClone)
13964                                                                 firstClone = clone;
13965
13966                                                         lastClone = clone;
13967                                                 }
13968                                         }
13969
13970                                         // Never split block elements if the format is mixed
13971                                         if (split && (!format.mixed || !isBlock(format_root)))
13972                                                 container = dom.split(format_root, container);
13973
13974                                         // Wrap container in cloned formats
13975                                         if (lastClone) {
13976                                                 target.parentNode.insertBefore(lastClone, target);
13977                                                 firstClone.appendChild(target);
13978                                         }
13979                                 }
13980
13981                                 return container;
13982                         };
13983
13984                         function splitToFormatRoot(container) {
13985                                 return wrapAndSplit(findFormatRoot(container), container, container, true);
13986                         };
13987
13988                         function unwrap(start) {
13989                                 var node = dom.get(start ? '_start' : '_end'),
13990                                         out = node[start ? 'firstChild' : 'lastChild'];
13991
13992                                 // If the end is placed within the start the result will be removed
13993                                 // So this checks if the out node is a bookmark node if it is it
13994                                 // checks for another more suitable node
13995                                 if (isBookmarkNode(out))
13996                                         out = out[start ? 'firstChild' : 'lastChild'];
13997
13998                                 dom.remove(node, true);
13999
14000                                 return out;
14001                         };
14002
14003                         function removeRngStyle(rng) {
14004                                 var startContainer, endContainer;
14005
14006                                 rng = expandRng(rng, formatList, TRUE);
14007
14008                                 if (format.split) {
14009                                         startContainer = getContainer(rng, TRUE);
14010                                         endContainer = getContainer(rng);
14011
14012                                         if (startContainer != endContainer) {
14013                                                 // Wrap start/end nodes in span element since these might be cloned/moved
14014                                                 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
14015                                                 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
14016
14017                                                 // Split start/end
14018                                                 splitToFormatRoot(startContainer);
14019                                                 splitToFormatRoot(endContainer);
14020
14021                                                 // Unwrap start/end to get real elements again
14022                                                 startContainer = unwrap(TRUE);
14023                                                 endContainer = unwrap();
14024                                         } else
14025                                                 startContainer = endContainer = splitToFormatRoot(startContainer);
14026
14027                                         // Update range positions since they might have changed after the split operations
14028                                         rng.startContainer = startContainer.parentNode;
14029                                         rng.startOffset = nodeIndex(startContainer);
14030                                         rng.endContainer = endContainer.parentNode;
14031                                         rng.endOffset = nodeIndex(endContainer) + 1;
14032                                 }
14033
14034                                 // Remove items between start/end
14035                                 rangeUtils.walk(rng, function(nodes) {
14036                                         each(nodes, function(node) {
14037                                                 process(node);
14038
14039                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
14040                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
14041                                                         removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
14042                                                 }
14043                                         });
14044                                 });
14045                         };
14046
14047                         // Handle node
14048                         if (node) {
14049                                 rng = dom.createRng();
14050                                 rng.setStartBefore(node);
14051                                 rng.setEndAfter(node);
14052                                 removeRngStyle(rng);
14053                                 return;
14054                         }
14055
14056                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
14057                                 bookmark = selection.getBookmark();
14058                                 removeRngStyle(selection.getRng(TRUE));
14059                                 selection.moveToBookmark(bookmark);
14060
14061                                 // 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
14062                                 if (match(name, vars, selection.getStart())) {
14063                                         moveStart(selection.getRng(true));
14064                                 }
14065
14066                                 ed.nodeChanged();
14067                         } else
14068                                 performCaretAction('remove', name, vars);
14069                 };
14070
14071                 function toggle(name, vars, node) {
14072                         var fmt = get(name);
14073
14074                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
14075                                 remove(name, vars, node);
14076                         else
14077                                 apply(name, vars, node);
14078                 };
14079
14080                 function matchNode(node, name, vars, similar) {
14081                         var formatList = get(name), format, i, classes;
14082
14083                         function matchItems(node, format, item_name) {
14084                                 var key, value, items = format[item_name], i;
14085
14086                                 // Check all items
14087                                 if (items) {
14088                                         // Non indexed object
14089                                         if (items.length === undefined) {
14090                                                 for (key in items) {
14091                                                         if (items.hasOwnProperty(key)) {
14092                                                                 if (item_name === 'attributes')
14093                                                                         value = dom.getAttrib(node, key);
14094                                                                 else
14095                                                                         value = getStyle(node, key);
14096
14097                                                                 if (similar && !value && !format.exact)
14098                                                                         return;
14099
14100                                                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
14101                                                                         return;
14102                                                         }
14103                                                 }
14104                                         } else {
14105                                                 // Only one match needed for indexed arrays
14106                                                 for (i = 0; i < items.length; i++) {
14107                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
14108                                                                 return format;
14109                                                 }
14110                                         }
14111                                 }
14112
14113                                 return format;
14114                         };
14115
14116                         if (formatList && node) {
14117                                 // Check each format in list
14118                                 for (i = 0; i < formatList.length; i++) {
14119                                         format = formatList[i];
14120
14121                                         // Name name, attributes, styles and classes
14122                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
14123                                                 // Match classes
14124                                                 if (classes = format.classes) {
14125                                                         for (i = 0; i < classes.length; i++) {
14126                                                                 if (!dom.hasClass(node, classes[i]))
14127                                                                         return;
14128                                                         }
14129                                                 }
14130
14131                                                 return format;
14132                                         }
14133                                 }
14134                         }
14135                 };
14136
14137                 function match(name, vars, node) {
14138                         var startNode, i;
14139
14140                         function matchParents(node) {
14141                                 // Find first node with similar format settings
14142                                 node = dom.getParent(node, function(node) {
14143                                         return !!matchNode(node, name, vars, true);
14144                                 });
14145
14146                                 // Do an exact check on the similar format element
14147                                 return matchNode(node, name, vars);
14148                         };
14149
14150                         // Check specified node
14151                         if (node)
14152                                 return matchParents(node);
14153
14154                         // Check pending formats
14155                         if (selection.isCollapsed()) {
14156                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
14157                                         if (pendingFormats.apply[i].name == name)
14158                                                 return true;
14159                                 }
14160
14161                                 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
14162                                         if (pendingFormats.remove[i].name == name)
14163                                                 return false;
14164                                 }
14165
14166                                 return matchParents(selection.getNode());
14167                         }
14168
14169                         // Check selected node
14170                         node = selection.getNode();
14171                         if (matchParents(node))
14172                                 return TRUE;
14173
14174                         // Check start node if it's different
14175                         startNode = selection.getStart();
14176                         if (startNode != node) {
14177                                 if (matchParents(startNode))
14178                                         return TRUE;
14179                         }
14180
14181                         return FALSE;
14182                 };
14183
14184                 function matchAll(names, vars) {
14185                         var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
14186
14187                         // If the selection is collapsed then check pending formats
14188                         if (selection.isCollapsed()) {
14189                                 for (ni = 0; ni < names.length; ni++) {
14190                                         // If the name is to be removed, then stop it from being added
14191                                         for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
14192                                                 name = names[ni];
14193
14194                                                 if (pendingFormats.remove[i].name == name) {
14195                                                         checkedMap[name] = true;
14196                                                         break;
14197                                                 }
14198                                         }
14199                                 }
14200
14201                                 // If the format is to be applied
14202                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
14203                                         for (ni = 0; ni < names.length; ni++) {
14204                                                 name = names[ni];
14205
14206                                                 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
14207                                                         checkedMap[name] = true;
14208                                                         matchedFormatNames.push(name);
14209                                                 }
14210                                         }
14211                                 }
14212                         }
14213
14214                         // Check start of selection for formats
14215                         startElement = selection.getStart();
14216                         dom.getParent(startElement, function(node) {
14217                                 var i, name;
14218
14219                                 for (i = 0; i < names.length; i++) {
14220                                         name = names[i];
14221
14222                                         if (!checkedMap[name] && matchNode(node, name, vars)) {
14223                                                 checkedMap[name] = true;
14224                                                 matchedFormatNames.push(name);
14225                                         }
14226                                 }
14227                         });
14228
14229                         return matchedFormatNames;
14230                 };
14231
14232                 function canApply(name) {
14233                         var formatList = get(name), startNode, parents, i, x, selector;
14234
14235                         if (formatList) {
14236                                 startNode = selection.getStart();
14237                                 parents = getParents(startNode);
14238
14239                                 for (x = formatList.length - 1; x >= 0; x--) {
14240                                         selector = formatList[x].selector;
14241
14242                                         // Format is not selector based, then always return TRUE
14243                                         if (!selector)
14244                                                 return TRUE;
14245
14246                                         for (i = parents.length - 1; i >= 0; i--) {
14247                                                 if (dom.is(parents[i], selector))
14248                                                         return TRUE;
14249                                         }
14250                                 }
14251                         }
14252
14253                         return FALSE;
14254                 };
14255
14256                 // Expose to public
14257                 tinymce.extend(this, {
14258                         get : get,
14259                         register : register,
14260                         apply : apply,
14261                         remove : remove,
14262                         toggle : toggle,
14263                         match : match,
14264                         matchAll : matchAll,
14265                         matchNode : matchNode,
14266                         canApply : canApply
14267                 });
14268
14269                 // Private functions
14270
14271                 function matchName(node, format) {
14272                         // Check for inline match
14273                         if (isEq(node, format.inline))
14274                                 return TRUE;
14275
14276                         // Check for block match
14277                         if (isEq(node, format.block))
14278                                 return TRUE;
14279
14280                         // Check for selector match
14281                         if (format.selector)
14282                                 return dom.is(node, format.selector);
14283                 };
14284
14285                 function isEq(str1, str2) {
14286                         str1 = str1 || '';
14287                         str2 = str2 || '';
14288
14289                         str1 = '' + (str1.nodeName || str1);
14290                         str2 = '' + (str2.nodeName || str2);
14291
14292                         return str1.toLowerCase() == str2.toLowerCase();
14293                 };
14294
14295                 function getStyle(node, name) {
14296                         var styleVal = dom.getStyle(node, name);
14297
14298                         // Force the format to hex
14299                         if (name == 'color' || name == 'backgroundColor')
14300                                 styleVal = dom.toHex(styleVal);
14301
14302                         // Opera will return bold as 700
14303                         if (name == 'fontWeight' && styleVal == 700)
14304                                 styleVal = 'bold';
14305
14306                         return '' + styleVal;
14307                 };
14308
14309                 function replaceVars(value, vars) {
14310                         if (typeof(value) != "string")
14311                                 value = value(vars);
14312                         else if (vars) {
14313                                 value = value.replace(/%(\w+)/g, function(str, name) {
14314                                         return vars[name] || str;
14315                                 });
14316                         }
14317
14318                         return value;
14319                 };
14320
14321                 function isWhiteSpaceNode(node) {
14322                         return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
14323                 };
14324
14325                 function wrap(node, name, attrs) {
14326                         var wrapper = dom.create(name, attrs);
14327
14328                         node.parentNode.insertBefore(wrapper, node);
14329                         wrapper.appendChild(node);
14330
14331                         return wrapper;
14332                 };
14333
14334                 function expandRng(rng, format, remove) {
14335                         var startContainer = rng.startContainer,
14336                                 startOffset = rng.startOffset,
14337                                 endContainer = rng.endContainer,
14338                                 endOffset = rng.endOffset, sibling, lastIdx, leaf;
14339
14340                         // This function walks up the tree if there is no siblings before/after the node
14341                         function findParentContainer(container, child_name, sibling_name, root) {
14342                                 var parent, child;
14343
14344                                 root = root || dom.getRoot();
14345
14346                                 for (;;) {
14347                                         // Check if we can move up are we at root level or body level
14348                                         parent = container.parentNode;
14349
14350                                         // Stop expanding on block elements or root depending on format
14351                                         if (parent == root || (!format[0].block_expand && isBlock(parent)))
14352                                                 return container;
14353
14354                                         for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
14355                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
14356                                                         return container;
14357
14358                                                 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
14359                                                         return container;
14360                                         }
14361
14362                                         container = container.parentNode;
14363                                 }
14364
14365                                 return container;
14366                         };
14367
14368                         // This function walks down the tree to find the leaf at the selection.
14369                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
14370                         function findLeaf(node, offset) {
14371                                 if (offset === undefined)
14372                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;
14373                                 while (node && node.hasChildNodes()) {
14374                                         node = node.childNodes[offset];
14375                                         if (node)
14376                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
14377                                 }
14378                                 return { node: node, offset: offset };
14379                         }
14380
14381                         // If index based start position then resolve it
14382                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
14383                                 lastIdx = startContainer.childNodes.length - 1;
14384                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
14385
14386                                 if (startContainer.nodeType == 3)
14387                                         startOffset = 0;
14388                         }
14389
14390                         // If index based end position then resolve it
14391                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
14392                                 lastIdx = endContainer.childNodes.length - 1;
14393                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
14394
14395                                 if (endContainer.nodeType == 3)
14396                                         endOffset = endContainer.nodeValue.length;
14397                         }
14398
14399                         // Exclude bookmark nodes if possible
14400                         if (isBookmarkNode(startContainer.parentNode))
14401                                 startContainer = startContainer.parentNode;
14402
14403                         if (isBookmarkNode(startContainer))
14404                                 startContainer = startContainer.nextSibling || startContainer;
14405
14406                         if (isBookmarkNode(endContainer.parentNode)) {
14407                                 endOffset = dom.nodeIndex(endContainer);
14408                                 endContainer = endContainer.parentNode;
14409                         }
14410
14411                         if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
14412                                 endContainer = endContainer.previousSibling;
14413                                 endOffset = endContainer.length;
14414                         }
14415
14416                         if (format[0].inline) {
14417                                 // Avoid applying formatting to a trailing space.
14418                                 leaf = findLeaf(endContainer, endOffset);
14419                                 if (leaf.node) {
14420                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
14421                                                 leaf = findLeaf(leaf.node.previousSibling);
14422
14423                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
14424                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
14425
14426                                                 if (leaf.offset > 1) {
14427                                                         endContainer = leaf.node;
14428                                                         endContainer.splitText(leaf.offset - 1);
14429                                                 } else if (leaf.node.previousSibling) {
14430                                                         endContainer = leaf.node.previousSibling;
14431                                                 }
14432                                         }
14433                                 }
14434                         }
14435                         
14436                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers
14437                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
14438                         // This will reduce the number of wrapper elements that needs to be created
14439                         // Move start point up the tree
14440                         if (format[0].inline || format[0].block_expand) {
14441                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
14442                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
14443                         }
14444
14445                         // Expand start/end container to matching selector
14446                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
14447                                 function findSelectorEndPoint(container, sibling_name) {
14448                                         var parents, i, y, curFormat;
14449
14450                                         if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
14451                                                 container = container[sibling_name];
14452
14453                                         parents = getParents(container);
14454                                         for (i = 0; i < parents.length; i++) {
14455                                                 for (y = 0; y < format.length; y++) {
14456                                                         curFormat = format[y];
14457
14458                                                         // If collapsed state is set then skip formats that doesn't match that
14459                                                         if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
14460                                                                 continue;
14461
14462                                                         if (dom.is(parents[i], curFormat.selector))
14463                                                                 return parents[i];
14464                                                 }
14465                                         }
14466
14467                                         return container;
14468                                 };
14469
14470                                 // Find new startContainer/endContainer if there is better one
14471                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
14472                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
14473                         }
14474
14475                         // Expand start/end container to matching block element or text node
14476                         if (format[0].block || format[0].selector) {
14477                                 function findBlockEndPoint(container, sibling_name, sibling_name2) {
14478                                         var node;
14479
14480                                         // Expand to block of similar type
14481                                         if (!format[0].wrapper)
14482                                                 node = dom.getParent(container, format[0].block);
14483
14484                                         // Expand to first wrappable block element or any block element
14485                                         if (!node)
14486                                                 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
14487
14488                                         // Exclude inner lists from wrapping
14489                                         if (node && format[0].wrapper)
14490                                                 node = getParents(node, 'ul,ol').reverse()[0] || node;
14491
14492                                         // Didn't find a block element look for first/last wrappable element
14493                                         if (!node) {
14494                                                 node = container;
14495
14496                                                 while (node[sibling_name] && !isBlock(node[sibling_name])) {
14497                                                         node = node[sibling_name];
14498
14499                                                         // Break on BR but include it will be removed later on
14500                                                         // we can't remove it now since we need to check if it can be wrapped
14501                                                         if (isEq(node, 'br'))
14502                                                                 break;
14503                                                 }
14504                                         }
14505
14506                                         return node || container;
14507                                 };
14508
14509                                 // Find new startContainer/endContainer if there is better one
14510                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
14511                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
14512
14513                                 // Non block element then try to expand up the leaf
14514                                 if (format[0].block) {
14515                                         if (!isBlock(startContainer))
14516                                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
14517
14518                                         if (!isBlock(endContainer))
14519                                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
14520                                 }
14521                         }
14522
14523                         // Setup index for startContainer
14524                         if (startContainer.nodeType == 1) {
14525                                 startOffset = nodeIndex(startContainer);
14526                                 startContainer = startContainer.parentNode;
14527                         }
14528
14529                         // Setup index for endContainer
14530                         if (endContainer.nodeType == 1) {
14531                                 endOffset = nodeIndex(endContainer) + 1;
14532                                 endContainer = endContainer.parentNode;
14533                         }
14534
14535                         // Return new range like object
14536                         return {
14537                                 startContainer : startContainer,
14538                                 startOffset : startOffset,
14539                                 endContainer : endContainer,
14540                                 endOffset : endOffset
14541                         };
14542                 }
14543
14544                 function removeFormat(format, vars, node, compare_node) {
14545                         var i, attrs, stylesModified;
14546
14547                         // Check if node matches format
14548                         if (!matchName(node, format))
14549                                 return FALSE;
14550
14551                         // Should we compare with format attribs and styles
14552                         if (format.remove != 'all') {
14553                                 // Remove styles
14554                                 each(format.styles, function(value, name) {
14555                                         value = replaceVars(value, vars);
14556
14557                                         // Indexed array
14558                                         if (typeof(name) === 'number') {
14559                                                 name = value;
14560                                                 compare_node = 0;
14561                                         }
14562
14563                                         if (!compare_node || isEq(getStyle(compare_node, name), value))
14564                                                 dom.setStyle(node, name, '');
14565
14566                                         stylesModified = 1;
14567                                 });
14568
14569                                 // Remove style attribute if it's empty
14570                                 if (stylesModified && dom.getAttrib(node, 'style') == '') {
14571                                         node.removeAttribute('style');
14572                                         node.removeAttribute('data-mce-style');
14573                                 }
14574
14575                                 // Remove attributes
14576                                 each(format.attributes, function(value, name) {
14577                                         var valueOut;
14578
14579                                         value = replaceVars(value, vars);
14580
14581                                         // Indexed array
14582                                         if (typeof(name) === 'number') {
14583                                                 name = value;
14584                                                 compare_node = 0;
14585                                         }
14586
14587                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
14588                                                 // Keep internal classes
14589                                                 if (name == 'class') {
14590                                                         value = dom.getAttrib(node, name);
14591                                                         if (value) {
14592                                                                 // Build new class value where everything is removed except the internal prefixed classes
14593                                                                 valueOut = '';
14594                                                                 each(value.split(/\s+/), function(cls) {
14595                                                                         if (/mce\w+/.test(cls))
14596                                                                                 valueOut += (valueOut ? ' ' : '') + cls;
14597                                                                 });
14598
14599                                                                 // We got some internal classes left
14600                                                                 if (valueOut) {
14601                                                                         dom.setAttrib(node, name, valueOut);
14602                                                                         return;
14603                                                                 }
14604                                                         }
14605                                                 }
14606
14607                                                 // IE6 has a bug where the attribute doesn't get removed correctly
14608                                                 if (name == "class")
14609                                                         node.removeAttribute('className');
14610
14611                                                 // Remove mce prefixed attributes
14612                                                 if (MCE_ATTR_RE.test(name))
14613                                                         node.removeAttribute('data-mce-' + name);
14614
14615                                                 node.removeAttribute(name);
14616                                         }
14617                                 });
14618
14619                                 // Remove classes
14620                                 each(format.classes, function(value) {
14621                                         value = replaceVars(value, vars);
14622
14623                                         if (!compare_node || dom.hasClass(compare_node, value))
14624                                                 dom.removeClass(node, value);
14625                                 });
14626
14627                                 // Check for non internal attributes
14628                                 attrs = dom.getAttribs(node);
14629                                 for (i = 0; i < attrs.length; i++) {
14630                                         if (attrs[i].nodeName.indexOf('_') !== 0)
14631                                                 return FALSE;
14632                                 }
14633                         }
14634
14635                         // Remove the inline child if it's empty for example <b> or <span>
14636                         if (format.remove != 'none') {
14637                                 removeNode(node, format);
14638                                 return TRUE;
14639                         }
14640                 };
14641
14642                 function removeNode(node, format) {
14643                         var parentNode = node.parentNode, rootBlockElm;
14644
14645                         if (format.block) {
14646                                 if (!forcedRootBlock) {
14647                                         function find(node, next, inc) {
14648                                                 node = getNonWhiteSpaceSibling(node, next, inc);
14649
14650                                                 return !node || (node.nodeName == 'BR' || isBlock(node));
14651                                         };
14652
14653                                         // Append BR elements if needed before we remove the block
14654                                         if (isBlock(node) && !isBlock(parentNode)) {
14655                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
14656                                                         node.insertBefore(dom.create('br'), node.firstChild);
14657
14658                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
14659                                                         node.appendChild(dom.create('br'));
14660                                         }
14661                                 } else {
14662                                         // Wrap the block in a forcedRootBlock if we are at the root of document
14663                                         if (parentNode == dom.getRoot()) {
14664                                                 if (!format.list_block || !isEq(node, format.list_block)) {
14665                                                         each(tinymce.grep(node.childNodes), function(node) {
14666                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
14667                                                                         if (!rootBlockElm)
14668                                                                                 rootBlockElm = wrap(node, forcedRootBlock);
14669                                                                         else
14670                                                                                 rootBlockElm.appendChild(node);
14671                                                                 } else
14672                                                                         rootBlockElm = 0;
14673                                                         });
14674                                                 }
14675                                         }
14676                                 }
14677                         }
14678
14679                         // Never remove nodes that isn't the specified inline element if a selector is specified too
14680                         if (format.selector && format.inline && !isEq(format.inline, node))
14681                                 return;
14682
14683                         dom.remove(node, 1);
14684                 };
14685
14686                 function getNonWhiteSpaceSibling(node, next, inc) {
14687                         if (node) {
14688                                 next = next ? 'nextSibling' : 'previousSibling';
14689
14690                                 for (node = inc ? node : node[next]; node; node = node[next]) {
14691                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node))
14692                                                 return node;
14693                                 }
14694                         }
14695                 };
14696
14697                 function isBookmarkNode(node) {
14698                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
14699                 };
14700
14701                 function mergeSiblings(prev, next) {
14702                         var marker, sibling, tmpSibling;
14703
14704                         function compareElements(node1, node2) {
14705                                 // Not the same name
14706                                 if (node1.nodeName != node2.nodeName)
14707                                         return FALSE;
14708
14709                                 function getAttribs(node) {
14710                                         var attribs = {};
14711
14712                                         each(dom.getAttribs(node), function(attr) {
14713                                                 var name = attr.nodeName.toLowerCase();
14714
14715                                                 // Don't compare internal attributes or style
14716                                                 if (name.indexOf('_') !== 0 && name !== 'style')
14717                                                         attribs[name] = dom.getAttrib(node, name);
14718                                         });
14719
14720                                         return attribs;
14721                                 };
14722
14723                                 function compareObjects(obj1, obj2) {
14724                                         var value, name;
14725
14726                                         for (name in obj1) {
14727                                                 // Obj1 has item obj2 doesn't have
14728                                                 if (obj1.hasOwnProperty(name)) {
14729                                                         value = obj2[name];
14730
14731                                                         // Obj2 doesn't have obj1 item
14732                                                         if (value === undefined)
14733                                                                 return FALSE;
14734
14735                                                         // Obj2 item has a different value
14736                                                         if (obj1[name] != value)
14737                                                                 return FALSE;
14738
14739                                                         // Delete similar value
14740                                                         delete obj2[name];
14741                                                 }
14742                                         }
14743
14744                                         // Check if obj 2 has something obj 1 doesn't have
14745                                         for (name in obj2) {
14746                                                 // Obj2 has item obj1 doesn't have
14747                                                 if (obj2.hasOwnProperty(name))
14748                                                         return FALSE;
14749                                         }
14750
14751                                         return TRUE;
14752                                 };
14753
14754                                 // Attribs are not the same
14755                                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
14756                                         return FALSE;
14757
14758                                 // Styles are not the same
14759                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
14760                                         return FALSE;
14761
14762                                 return TRUE;
14763                         };
14764
14765                         // Check if next/prev exists and that they are elements
14766                         if (prev && next) {
14767                                 function findElementSibling(node, sibling_name) {
14768                                         for (sibling = node; sibling; sibling = sibling[sibling_name]) {
14769                                                 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
14770                                                         return node;
14771
14772                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
14773                                                         return sibling;
14774                                         }
14775
14776                                         return node;
14777                                 };
14778
14779                                 // If previous sibling is empty then jump over it
14780                                 prev = findElementSibling(prev, 'previousSibling');
14781                                 next = findElementSibling(next, 'nextSibling');
14782
14783                                 // Compare next and previous nodes
14784                                 if (compareElements(prev, next)) {
14785                                         // Append nodes between
14786                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {
14787                                                 tmpSibling = sibling;
14788                                                 sibling = sibling.nextSibling;
14789                                                 prev.appendChild(tmpSibling);
14790                                         }
14791
14792                                         // Remove next node
14793                                         dom.remove(next);
14794
14795                                         // Move children into prev node
14796                                         each(tinymce.grep(next.childNodes), function(node) {
14797                                                 prev.appendChild(node);
14798                                         });
14799
14800                                         return prev;
14801                                 }
14802                         }
14803
14804                         return next;
14805                 };
14806
14807                 function isTextBlock(name) {
14808                         return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
14809                 };
14810
14811                 function getContainer(rng, start) {
14812                         var container, offset, lastIdx;
14813
14814                         container = rng[start ? 'startContainer' : 'endContainer'];
14815                         offset = rng[start ? 'startOffset' : 'endOffset'];
14816
14817                         if (container.nodeType == 1) {
14818                                 lastIdx = container.childNodes.length - 1;
14819
14820                                 if (!start && offset)
14821                                         offset--;
14822
14823                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
14824                         }
14825
14826                         return container;
14827                 };
14828
14829                 function performCaretAction(type, name, vars) {
14830                         var i, currentPendingFormats = pendingFormats[type],
14831                                 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
14832
14833                         function hasPending() {
14834                                 return pendingFormats.apply.length || pendingFormats.remove.length;
14835                         };
14836
14837                         function resetPending() {
14838                                 pendingFormats.apply = [];
14839                                 pendingFormats.remove = [];
14840                         };
14841
14842                         function perform(caret_node) {
14843                                 // Apply pending formats
14844                                 each(pendingFormats.apply.reverse(), function(item) {
14845                                         apply(item.name, item.vars, caret_node);
14846
14847                                         // Colored nodes should be underlined so that the color of the underline matches the text color.
14848                                         if (item.name === 'forecolor' && item.vars.value)
14849                                                 processUnderlineAndColor(caret_node.parentNode);
14850                                 });
14851
14852                                 // Remove pending formats
14853                                 each(pendingFormats.remove.reverse(), function(item) {
14854                                         remove(item.name, item.vars, caret_node);
14855                                 });
14856
14857                                 dom.remove(caret_node, 1);
14858                                 resetPending();
14859                         };
14860
14861                         // Check if it already exists then ignore it
14862                         for (i = currentPendingFormats.length - 1; i >= 0; i--) {
14863                                 if (currentPendingFormats[i].name == name)
14864                                         return;
14865                         }
14866
14867                         currentPendingFormats.push({name : name, vars : vars});
14868
14869                         // Check if it's in the other type, then remove it
14870                         for (i = otherPendingFormats.length - 1; i >= 0; i--) {
14871                                 if (otherPendingFormats[i].name == name)
14872                                         otherPendingFormats.splice(i, 1);
14873                         }
14874
14875                         // Pending apply or remove formats
14876                         if (hasPending()) {
14877                                 ed.getDoc().execCommand('FontName', false, 'mceinline');
14878                                 pendingFormats.lastRng = selection.getRng();
14879
14880                                 // IE will convert the current word
14881                                 each(dom.select('font,span'), function(node) {
14882                                         var bookmark;
14883
14884                                         if (isCaretNode(node)) {
14885                                                 bookmark = selection.getBookmark();
14886                                                 perform(node);
14887                                                 selection.moveToBookmark(bookmark);
14888                                                 ed.nodeChanged();
14889                                         }
14890                                 });
14891
14892                                 // Only register listeners once if we need to
14893                                 if (!pendingFormats.isListening && hasPending()) {
14894                                         pendingFormats.isListening = true;
14895
14896                                         each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
14897                                                 ed[event].addToTop(function(ed, e) {
14898                                                         // Do we have pending formats and is the selection moved has moved
14899                                                         if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
14900                                                                 each(dom.select('font,span'), function(node) {
14901                                                                         var textNode, rng;
14902
14903                                                                         // Look for marker
14904                                                                         if (isCaretNode(node)) {
14905                                                                                 textNode = node.firstChild;
14906
14907                                                                                 if (textNode) {
14908                                                                                         perform(node);
14909
14910                                                                                         rng = dom.createRng();
14911                                                                                         rng.setStart(textNode, textNode.nodeValue.length);
14912                                                                                         rng.setEnd(textNode, textNode.nodeValue.length);
14913                                                                                         selection.setRng(rng);
14914                                                                                         ed.nodeChanged();
14915                                                                                 } else
14916                                                                                         dom.remove(node);
14917                                                                         }
14918                                                                 });
14919
14920                                                                 // Always unbind and clear pending styles on keyup
14921                                                                 if (e.type == 'keyup' || e.type == 'mouseup')
14922                                                                         resetPending();
14923                                                         }
14924                                                 });
14925                                         });
14926                                 }
14927                         }
14928                 };
14929         };
14930 })(tinymce);
14931
14932 tinymce.onAddEditor.add(function(tinymce, ed) {
14933         var filters, fontSizes, dom, settings = ed.settings;
14934
14935         if (settings.inline_styles) {
14936                 fontSizes = tinymce.explode(settings.font_size_style_values);
14937
14938                 function replaceWithSpan(node, styles) {
14939                         tinymce.each(styles, function(value, name) {
14940                                 if (value)
14941                                         dom.setStyle(node, name, value);
14942                         });
14943
14944                         dom.rename(node, 'span');
14945                 };
14946
14947                 filters = {
14948                         font : function(dom, node) {
14949                                 replaceWithSpan(node, {
14950                                         backgroundColor : node.style.backgroundColor,
14951                                         color : node.color,
14952                                         fontFamily : node.face,
14953                                         fontSize : fontSizes[parseInt(node.size) - 1]
14954                                 });
14955                         },
14956
14957                         u : function(dom, node) {
14958                                 replaceWithSpan(node, {
14959                                         textDecoration : 'underline'
14960                                 });
14961                         },
14962
14963                         strike : function(dom, node) {
14964                                 replaceWithSpan(node, {
14965                                         textDecoration : 'line-through'
14966                                 });
14967                         }
14968                 };
14969
14970                 function convert(editor, params) {
14971                         dom = editor.dom;
14972
14973                         if (settings.convert_fonts_to_spans) {
14974                                 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
14975                                         filters[node.nodeName.toLowerCase()](ed.dom, node);
14976                                 });
14977                         }
14978                 };
14979
14980                 ed.onPreProcess.add(convert);
14981                 ed.onSetContent.add(convert);
14982
14983                 ed.onInit.add(function() {
14984                         ed.selection.onSetContent.add(convert);
14985                 });
14986         }
14987 });
14988