]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/tiny_mce_prototype_src.js
Release 6.2.3
[Github/sugarcrm.git] / include / javascript / tiny_mce / tiny_mce_prototype_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.4',
9
10                 releaseDate : '2011-08-04',
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.isIE7 = t.isIE && /MSIE [7]/.test(ua);
24
25                         t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
26
27                         t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
28
29                         t.isGecko = !t.isWebKit && /Gecko/.test(ua);
30
31                         t.isMac = ua.indexOf('Mac') != -1;
32
33                         t.isAir = /adobeair/i.test(ua);
34
35                         t.isIDevice = /(iPad|iPhone)/.test(ua);
36                         
37                         t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
38
39                         // TinyMCE .NET webcontrol might be setting the values for TinyMCE
40                         if (win.tinyMCEPreInit) {
41                                 t.suffix = tinyMCEPreInit.suffix;
42                                 t.baseURL = tinyMCEPreInit.base;
43                                 t.query = tinyMCEPreInit.query;
44                                 return;
45                         }
46
47                         // Get suffix and base
48                         t.suffix = '';
49
50                         // If base element found, add that infront of baseURL
51                         nl = d.getElementsByTagName('base');
52                         for (i=0; i<nl.length; i++) {
53                                 if (v = nl[i].href) {
54                                         // Host only value like http://site.com or http://site.com:8008
55                                         if (/^https?:\/\/[^\/]+$/.test(v))
56                                                 v += '/';
57
58                                         base = v ? v.match(/.*\//)[0] : ''; // Get only directory
59                                 }
60                         }
61
62                         function getBase(n) {
63                                 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
64                                         if (/_(src|dev)\.js/g.test(n.src))
65                                                 t.suffix = '_src';
66
67                                         if ((p = n.src.indexOf('?')) != -1)
68                                                 t.query = n.src.substring(p + 1);
69
70                                         t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
71
72                                         // If path to script is relative and a base href was found add that one infront
73                                         // the src property will always be an absolute one on non IE browsers and IE 8
74                                         // so this logic will basically only be executed on older IE versions
75                                         if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
76                                                 t.baseURL = base + t.baseURL;
77
78                                         return t.baseURL;
79                                 }
80
81                                 return null;
82                         };
83
84                         // Check document
85                         nl = d.getElementsByTagName('script');
86                         for (i=0; i<nl.length; i++) {
87                                 if (getBase(nl[i]))
88                                         return;
89                         }
90
91                         // Check head
92                         n = d.getElementsByTagName('head')[0];
93                         if (n) {
94                                 nl = n.getElementsByTagName('script');
95                                 for (i=0; i<nl.length; i++) {
96                                         if (getBase(nl[i]))
97                                                 return;
98                                 }
99                         }
100
101                         return;
102                 },
103
104                 is : function(o, t) {
105                         if (!t)
106                                 return o !== undefined;
107
108                         if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
109                                 return true;
110
111                         return typeof(o) == t;
112                 },
113
114                 makeMap : function(items, delim, map) {
115                         var i;
116
117                         items = items || [];
118                         delim = delim || ',';
119
120                         if (typeof(items) == "string")
121                                 items = items.split(delim);
122
123                         map = map || {};
124
125                         i = items.length;
126                         while (i--)
127                                 map[items[i]] = {};
128
129                         return map;
130                 },
131
132                 each : function(o, cb, s) {
133                         var n, l;
134
135                         if (!o)
136                                 return 0;
137
138                         s = s || o;
139
140                         if (o.length !== undefined) {
141                                 // Indexed arrays, needed for Safari
142                                 for (n=0, l = o.length; n < l; n++) {
143                                         if (cb.call(s, o[n], n, o) === false)
144                                                 return 0;
145                                 }
146                         } else {
147                                 // Hashtables
148                                 for (n in o) {
149                                         if (o.hasOwnProperty(n)) {
150                                                 if (cb.call(s, o[n], n, o) === false)
151                                                         return 0;
152                                         }
153                                 }
154                         }
155
156                         return 1;
157                 },
158
159
160                 map : function(a, f) {
161                         var o = [];
162
163                         tinymce.each(a, function(v) {
164                                 o.push(f(v));
165                         });
166
167                         return o;
168                 },
169
170                 grep : function(a, f) {
171                         var o = [];
172
173                         tinymce.each(a, function(v) {
174                                 if (!f || f(v))
175                                         o.push(v);
176                         });
177
178                         return o;
179                 },
180
181                 inArray : function(a, v) {
182                         var i, l;
183
184                         if (a) {
185                                 for (i = 0, l = a.length; i < l; i++) {
186                                         if (a[i] === v)
187                                                 return i;
188                                 }
189                         }
190
191                         return -1;
192                 },
193
194                 extend : function(o, e) {
195                         var i, l, a = arguments;
196
197                         for (i = 1, l = a.length; i < l; i++) {
198                                 e = a[i];
199
200                                 tinymce.each(e, function(v, n) {
201                                         if (v !== undefined)
202                                                 o[n] = v;
203                                 });
204                         }
205
206                         return o;
207                 },
208
209
210                 trim : function(s) {
211                         return (s ? '' + s : '').replace(whiteSpaceRe, '');
212                 },
213
214                 create : function(s, p, root) {
215                         var t = this, sp, ns, cn, scn, c, de = 0;
216
217                         // Parse : <prefix> <class>:<super class>
218                         s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
219                         cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
220
221                         // Create namespace for new class
222                         ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
223
224                         // Class already exists
225                         if (ns[cn])
226                                 return;
227
228                         // Make pure static class
229                         if (s[2] == 'static') {
230                                 ns[cn] = p;
231
232                                 if (this.onCreate)
233                                         this.onCreate(s[2], s[3], ns[cn]);
234
235                                 return;
236                         }
237
238                         // Create default constructor
239                         if (!p[cn]) {
240                                 p[cn] = function() {};
241                                 de = 1;
242                         }
243
244                         // Add constructor and methods
245                         ns[cn] = p[cn];
246                         t.extend(ns[cn].prototype, p);
247
248                         // Extend
249                         if (s[5]) {
250                                 sp = t.resolve(s[5]).prototype;
251                                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
252
253                                 // Extend constructor
254                                 c = ns[cn];
255                                 if (de) {
256                                         // Add passthrough constructor
257                                         ns[cn] = function() {
258                                                 return sp[scn].apply(this, arguments);
259                                         };
260                                 } else {
261                                         // Add inherit constructor
262                                         ns[cn] = function() {
263                                                 this.parent = sp[scn];
264                                                 return c.apply(this, arguments);
265                                         };
266                                 }
267                                 ns[cn].prototype[cn] = ns[cn];
268
269                                 // Add super methods
270                                 t.each(sp, function(f, n) {
271                                         ns[cn].prototype[n] = sp[n];
272                                 });
273
274                                 // Add overridden methods
275                                 t.each(p, function(f, n) {
276                                         // Extend methods if needed
277                                         if (sp[n]) {
278                                                 ns[cn].prototype[n] = function() {
279                                                         this.parent = sp[n];
280                                                         return f.apply(this, arguments);
281                                                 };
282                                         } else {
283                                                 if (n != cn)
284                                                         ns[cn].prototype[n] = f;
285                                         }
286                                 });
287                         }
288
289                         // Add static methods
290                         t.each(p['static'], function(f, n) {
291                                 ns[cn][n] = f;
292                         });
293
294                         if (this.onCreate)
295                                 this.onCreate(s[2], s[3], ns[cn].prototype);
296                 },
297
298                 walk : function(o, f, n, s) {
299                         s = s || this;
300
301                         if (o) {
302                                 if (n)
303                                         o = o[n];
304
305                                 tinymce.each(o, function(o, i) {
306                                         if (f.call(s, o, i, n) === false)
307                                                 return false;
308
309                                         tinymce.walk(o, f, n, s);
310                                 });
311                         }
312                 },
313
314                 createNS : function(n, o) {
315                         var i, v;
316
317                         o = o || win;
318
319                         n = n.split('.');
320                         for (i=0; i<n.length; i++) {
321                                 v = n[i];
322
323                                 if (!o[v])
324                                         o[v] = {};
325
326                                 o = o[v];
327                         }
328
329                         return o;
330                 },
331
332                 resolve : function(n, o) {
333                         var i, l;
334
335                         o = o || win;
336
337                         n = n.split('.');
338                         for (i = 0, l = n.length; i < l; i++) {
339                                 o = o[n[i]];
340
341                                 if (!o)
342                                         break;
343                         }
344
345                         return o;
346                 },
347
348                 addUnload : function(f, s) {
349                         var t = this;
350
351                         f = {func : f, scope : s || this};
352
353                         if (!t.unloads) {
354                                 function unload() {
355                                         var li = t.unloads, o, n;
356
357                                         if (li) {
358                                                 // Call unload handlers
359                                                 for (n in li) {
360                                                         o = li[n];
361
362                                                         if (o && o.func)
363                                                                 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
364                                                 }
365
366                                                 // Detach unload function
367                                                 if (win.detachEvent) {
368                                                         win.detachEvent('onbeforeunload', fakeUnload);
369                                                         win.detachEvent('onunload', unload);
370                                                 } else if (win.removeEventListener)
371                                                         win.removeEventListener('unload', unload, false);
372
373                                                 // Destroy references
374                                                 t.unloads = o = li = w = unload = 0;
375
376                                                 // Run garbarge collector on IE
377                                                 if (win.CollectGarbage)
378                                                         CollectGarbage();
379                                         }
380                                 };
381
382                                 function fakeUnload() {
383                                         var d = document;
384
385                                         // Is there things still loading, then do some magic
386                                         if (d.readyState == 'interactive') {
387                                                 function stop() {
388                                                         // Prevent memory leak
389                                                         d.detachEvent('onstop', stop);
390
391                                                         // Call unload handler
392                                                         if (unload)
393                                                                 unload();
394
395                                                         d = 0;
396                                                 };
397
398                                                 // Fire unload when the currently loading page is stopped
399                                                 if (d)
400                                                         d.attachEvent('onstop', stop);
401
402                                                 // Remove onstop listener after a while to prevent the unload function
403                                                 // to execute if the user presses cancel in an onbeforeunload
404                                                 // confirm dialog and then presses the browser stop button
405                                                 win.setTimeout(function() {
406                                                         if (d)
407                                                                 d.detachEvent('onstop', stop);
408                                                 }, 0);
409                                         }
410                                 };
411
412                                 // Attach unload handler
413                                 if (win.attachEvent) {
414                                         win.attachEvent('onunload', unload);
415                                         win.attachEvent('onbeforeunload', fakeUnload);
416                                 } else if (win.addEventListener)
417                                         win.addEventListener('unload', unload, false);
418
419                                 // Setup initial unload handler array
420                                 t.unloads = [f];
421                         } else
422                                 t.unloads.push(f);
423
424                         return f;
425                 },
426
427                 removeUnload : function(f) {
428                         var u = this.unloads, r = null;
429
430                         tinymce.each(u, function(o, i) {
431                                 if (o && o.func == f) {
432                                         u.splice(i, 1);
433                                         r = f;
434                                         return false;
435                                 }
436                         });
437
438                         return r;
439                 },
440
441                 explode : function(s, d) {
442                         return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
443                 },
444
445                 _addVer : function(u) {
446                         var v;
447
448                         if (!this.query)
449                                 return u;
450
451                         v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
452
453                         if (u.indexOf('#') == -1)
454                                 return u + v;
455
456                         return u.replace('#', v + '#');
457                 },
458
459                 // Fix function for IE 9 where regexps isn't working correctly
460                 // Todo: remove me once MS fixes the bug
461                 _replace : function(find, replace, str) {
462                         // On IE9 we have to fake $x replacement
463                         if (isRegExpBroken) {
464                                 return str.replace(find, function() {
465                                         var val = replace, args = arguments, i;
466
467                                         for (i = 0; i < args.length - 2; i++) {
468                                                 if (args[i] === undefined) {
469                                                         val = val.replace(new RegExp('\\$' + i, 'g'), '');
470                                                 } else {
471                                                         val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
472                                                 }
473                                         }
474
475                                         return val;
476                                 });
477                         }
478
479                         return str.replace(find, replace);
480                 }
481
482                 };
483
484         // Initialize the API
485         tinymce._init();
486
487         // Expose tinymce namespace to the global namespace (window)
488         win.tinymce = win.tinyMCE = tinymce;
489
490         // Describe the different namespaces
491
492         })(window);
493
494
495
496 (function() {
497         if (!window.Prototype)
498                 return alert("Load prototype first!");
499
500         // Patch in core NS functions
501         tinymce.extend(tinymce, {
502                 trim : function(s) {return s ? s.strip() : '';},
503                 inArray : function(a, v) {return a && a.indexOf ? a.indexOf(v) : -1;}
504         });
505
506         // Patch in functions in various clases
507         // Add a "#ifndefjquery" statement around each core API function you add below
508         var patches = {
509                 'tinymce.util.JSON' : {
510                         /*serialize : function(o) {
511                                 return o.toJSON();
512                         }*/
513                 },
514         };
515
516         // Patch functions after a class is created
517         tinymce.onCreate = function(ty, c, p) {
518                 tinymce.extend(p, patches[c]);
519         };
520 })();
521
522
523 tinymce.create('tinymce.util.Dispatcher', {
524         scope : null,
525         listeners : null,
526
527         Dispatcher : function(s) {
528                 this.scope = s || this;
529                 this.listeners = [];
530         },
531
532         add : function(cb, s) {
533                 this.listeners.push({cb : cb, scope : s || this.scope});
534
535                 return cb;
536         },
537
538         addToTop : function(cb, s) {
539                 this.listeners.unshift({cb : cb, scope : s || this.scope});
540
541                 return cb;
542         },
543
544         remove : function(cb) {
545                 var l = this.listeners, o = null;
546
547                 tinymce.each(l, function(c, i) {
548                         if (cb == c.cb) {
549                                 o = cb;
550                                 l.splice(i, 1);
551                                 return false;
552                         }
553                 });
554
555                 return o;
556         },
557
558         dispatch : function() {
559                 var s, a = arguments, i, li = this.listeners, c;
560
561                 // Needs to be a real loop since the listener count might change while looping
562                 // And this is also more efficient
563                 for (i = 0; i<li.length; i++) {
564                         c = li[i];
565                         s = c.cb.apply(c.scope, a);
566
567                         if (s === false)
568                                 break;
569                 }
570
571                 return s;
572         }
573
574         });
575
576 (function() {
577         var each = tinymce.each;
578
579         tinymce.create('tinymce.util.URI', {
580                 URI : function(u, s) {
581                         var t = this, o, a, b, base_url;
582
583                         // Trim whitespace
584                         u = tinymce.trim(u);
585
586                         // Default settings
587                         s = t.settings = s || {};
588
589                         // Strange app protocol or local anchor
590                         if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
591                                 t.source = u;
592                                 return;
593                         }
594
595                         // Absolute path with no host, fake host and protocol
596                         if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
597                                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
598
599                         // Relative path http:// or protocol relative //path
600                         if (!/^[\w-]*:?\/\//.test(u)) {
601                                 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
602                                 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
603                         }
604
605                         // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
606                         u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
607                         u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
608                         each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
609                                 var s = u[i];
610
611                                 // Zope 3 workaround, they use @@something
612                                 if (s)
613                                         s = s.replace(/\(mce_at\)/g, '@@');
614
615                                 t[v] = s;
616                         });
617
618                         if (b = s.base_uri) {
619                                 if (!t.protocol)
620                                         t.protocol = b.protocol;
621
622                                 if (!t.userInfo)
623                                         t.userInfo = b.userInfo;
624
625                                 if (!t.port && t.host == 'mce_host')
626                                         t.port = b.port;
627
628                                 if (!t.host || t.host == 'mce_host')
629                                         t.host = b.host;
630
631                                 t.source = '';
632                         }
633
634                         //t.path = t.path || '/';
635                 },
636
637                 setPath : function(p) {
638                         var t = this;
639
640                         p = /^(.*?)\/?(\w+)?$/.exec(p);
641
642                         // Update path parts
643                         t.path = p[0];
644                         t.directory = p[1];
645                         t.file = p[2];
646
647                         // Rebuild source
648                         t.source = '';
649                         t.getURI();
650                 },
651
652                 toRelative : function(u) {
653                         var t = this, o;
654
655                         if (u === "./")
656                                 return u;
657
658                         u = new tinymce.util.URI(u, {base_uri : t});
659
660                         // Not on same domain/port or protocol
661                         if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
662                                 return u.getURI();
663
664                         o = t.toRelPath(t.path, u.path);
665
666                         // Add query
667                         if (u.query)
668                                 o += '?' + u.query;
669
670                         // Add anchor
671                         if (u.anchor)
672                                 o += '#' + u.anchor;
673
674                         return o;
675                 },
676         
677                 toAbsolute : function(u, nh) {
678                         var u = new tinymce.util.URI(u, {base_uri : this});
679
680                         return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
681                 },
682
683                 toRelPath : function(base, path) {
684                         var items, bp = 0, out = '', i, l;
685
686                         // Split the paths
687                         base = base.substring(0, base.lastIndexOf('/'));
688                         base = base.split('/');
689                         items = path.split('/');
690
691                         if (base.length >= items.length) {
692                                 for (i = 0, l = base.length; i < l; i++) {
693                                         if (i >= items.length || base[i] != items[i]) {
694                                                 bp = i + 1;
695                                                 break;
696                                         }
697                                 }
698                         }
699
700                         if (base.length < items.length) {
701                                 for (i = 0, l = items.length; i < l; i++) {
702                                         if (i >= base.length || base[i] != items[i]) {
703                                                 bp = i + 1;
704                                                 break;
705                                         }
706                                 }
707                         }
708
709                         if (bp == 1)
710                                 return path;
711
712                         for (i = 0, l = base.length - (bp - 1); i < l; i++)
713                                 out += "../";
714
715                         for (i = bp - 1, l = items.length; i < l; i++) {
716                                 if (i != bp - 1)
717                                         out += "/" + items[i];
718                                 else
719                                         out += items[i];
720                         }
721
722                         return out;
723                 },
724
725                 toAbsPath : function(base, path) {
726                         var i, nb = 0, o = [], tr, outPath;
727
728                         // Split paths
729                         tr = /\/$/.test(path) ? '/' : '';
730                         base = base.split('/');
731                         path = path.split('/');
732
733                         // Remove empty chunks
734                         each(base, function(k) {
735                                 if (k)
736                                         o.push(k);
737                         });
738
739                         base = o;
740
741                         // Merge relURLParts chunks
742                         for (i = path.length - 1, o = []; i >= 0; i--) {
743                                 // Ignore empty or .
744                                 if (path[i].length == 0 || path[i] == ".")
745                                         continue;
746
747                                 // Is parent
748                                 if (path[i] == '..') {
749                                         nb++;
750                                         continue;
751                                 }
752
753                                 // Move up
754                                 if (nb > 0) {
755                                         nb--;
756                                         continue;
757                                 }
758
759                                 o.push(path[i]);
760                         }
761
762                         i = base.length - nb;
763
764                         // If /a/b/c or /
765                         if (i <= 0)
766                                 outPath = o.reverse().join('/');
767                         else
768                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
769
770                         // Add front / if it's needed
771                         if (outPath.indexOf('/') !== 0)
772                                 outPath = '/' + outPath;
773
774                         // Add traling / if it's needed
775                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
776                                 outPath += tr;
777
778                         return outPath;
779                 },
780
781                 getURI : function(nh) {
782                         var s, t = this;
783
784                         // Rebuild source
785                         if (!t.source || nh) {
786                                 s = '';
787
788                                 if (!nh) {
789                                         if (t.protocol)
790                                                 s += t.protocol + '://';
791
792                                         if (t.userInfo)
793                                                 s += t.userInfo + '@';
794
795                                         if (t.host)
796                                                 s += t.host;
797
798                                         if (t.port)
799                                                 s += ':' + t.port;
800                                 }
801
802                                 if (t.path)
803                                         s += t.path;
804
805                                 if (t.query)
806                                         s += '?' + t.query;
807
808                                 if (t.anchor)
809                                         s += '#' + t.anchor;
810
811                                 t.source = s;
812                         }
813
814                         return t.source;
815                 }
816         });
817 })();
818
819 (function() {
820         var each = tinymce.each;
821
822         tinymce.create('static tinymce.util.Cookie', {
823                 getHash : function(n) {
824                         var v = this.get(n), h;
825
826                         if (v) {
827                                 each(v.split('&'), function(v) {
828                                         v = v.split('=');
829                                         h = h || {};
830                                         h[unescape(v[0])] = unescape(v[1]);
831                                 });
832                         }
833
834                         return h;
835                 },
836
837                 setHash : function(n, v, e, p, d, s) {
838                         var o = '';
839
840                         each(v, function(v, k) {
841                                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
842                         });
843
844                         this.set(n, o, e, p, d, s);
845                 },
846
847                 get : function(n) {
848                         var c = document.cookie, e, p = n + "=", b;
849
850                         // Strict mode
851                         if (!c)
852                                 return;
853
854                         b = c.indexOf("; " + p);
855
856                         if (b == -1) {
857                                 b = c.indexOf(p);
858
859                                 if (b != 0)
860                                         return null;
861                         } else
862                                 b += 2;
863
864                         e = c.indexOf(";", b);
865
866                         if (e == -1)
867                                 e = c.length;
868
869                         return unescape(c.substring(b + p.length, e));
870                 },
871
872                 set : function(n, v, e, p, d, s) {
873                         document.cookie = n + "=" + escape(v) +
874                                 ((e) ? "; expires=" + e.toGMTString() : "") +
875                                 ((p) ? "; path=" + escape(p) : "") +
876                                 ((d) ? "; domain=" + d : "") +
877                                 ((s) ? "; secure" : "");
878                 },
879
880                 remove : function(n, p) {
881                         var d = new Date();
882
883                         d.setTime(d.getTime() - 1000);
884
885                         this.set(n, '', d, p, d);
886                 }
887         });
888 })();
889
890 (function() {
891         function serialize(o, quote) {
892                 var i, v, t;
893
894                 quote = quote || '"';
895
896                 if (o == null)
897                         return 'null';
898
899                 t = typeof o;
900
901                 if (t == 'string') {
902                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
903
904                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
905                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility
906                                 if (quote === '"' && a === "'")
907                                         return a;
908
909                                 i = v.indexOf(b);
910
911                                 if (i + 1)
912                                         return '\\' + v.charAt(i + 1);
913
914                                 a = b.charCodeAt().toString(16);
915
916                                 return '\\u' + '0000'.substring(a.length) + a;
917                         }) + quote;
918                 }
919
920                 if (t == 'object') {
921                         if (o.hasOwnProperty && o instanceof Array) {
922                                         for (i=0, v = '['; i<o.length; i++)
923                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);
924
925                                         return v + ']';
926                                 }
927
928                                 v = '{';
929
930                                 for (i in o)
931                                         v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
932
933                                 return v + '}';
934                 }
935
936                 return '' + o;
937         };
938
939         tinymce.util.JSON = {
940                 serialize: serialize,
941
942                 parse: function(s) {
943                         try {
944                                 return eval('(' + s + ')');
945                         } catch (ex) {
946                                 // Ignore
947                         }
948                 }
949
950                 };
951 })();
952 tinymce.create('static tinymce.util.XHR', {
953         send : function(o) {
954                 var x, t, w = window, c = 0;
955
956                 // Default settings
957                 o.scope = o.scope || this;
958                 o.success_scope = o.success_scope || o.scope;
959                 o.error_scope = o.error_scope || o.scope;
960                 o.async = o.async === false ? false : true;
961                 o.data = o.data || '';
962
963                 function get(s) {
964                         x = 0;
965
966                         try {
967                                 x = new ActiveXObject(s);
968                         } catch (ex) {
969                         }
970
971                         return x;
972                 };
973
974                 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
975
976                 if (x) {
977                         if (x.overrideMimeType)
978                                 x.overrideMimeType(o.content_type);
979
980                         x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
981
982                         if (o.content_type)
983                                 x.setRequestHeader('Content-Type', o.content_type);
984
985                         x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
986
987                         x.send(o.data);
988
989                         function ready() {
990                                 if (!o.async || x.readyState == 4 || c++ > 10000) {
991                                         if (o.success && c < 10000 && x.status == 200)
992                                                 o.success.call(o.success_scope, '' + x.responseText, x, o);
993                                         else if (o.error)
994                                                 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
995
996                                         x = null;
997                                 } else
998                                         w.setTimeout(ready, 10);
999                         };
1000
1001                         // Syncronous request
1002                         if (!o.async)
1003                                 return ready();
1004
1005                         // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1006                         t = w.setTimeout(ready, 10);
1007                 }
1008         }
1009 });
1010
1011 (function() {
1012         var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1013
1014         tinymce.create('tinymce.util.JSONRequest', {
1015                 JSONRequest : function(s) {
1016                         this.settings = extend({
1017                         }, s);
1018                         this.count = 0;
1019                 },
1020
1021                 send : function(o) {
1022                         var ecb = o.error, scb = o.success;
1023
1024                         o = extend(this.settings, o);
1025
1026                         o.success = function(c, x) {
1027                                 c = JSON.parse(c);
1028
1029                                 if (typeof(c) == 'undefined') {
1030                                         c = {
1031                                                 error : 'JSON Parse error.'
1032                                         };
1033                                 }
1034
1035                                 if (c.error)
1036                                         ecb.call(o.error_scope || o.scope, c.error, x);
1037                                 else
1038                                         scb.call(o.success_scope || o.scope, c.result);
1039                         };
1040
1041                         o.error = function(ty, x) {
1042                                 if (ecb)
1043                                         ecb.call(o.error_scope || o.scope, ty, x);
1044                         };
1045
1046                         o.data = JSON.serialize({
1047                                 id : o.id || 'c' + (this.count++),
1048                                 method : o.method,
1049                                 params : o.params
1050                         });
1051
1052                         // JSON content type for Ruby on rails. Bug: #1883287
1053                         o.content_type = 'application/json';
1054
1055                         XHR.send(o);
1056                 },
1057
1058                 'static' : {
1059                         sendRPC : function(o) {
1060                                 return new tinymce.util.JSONRequest().send(o);
1061                         }
1062                 }
1063         });
1064 }());
1065 (function(tinymce){
1066         tinymce.VK = {
1067                 DELETE:46,
1068                 BACKSPACE:8
1069                 
1070         }
1071
1072 })(tinymce);
1073
1074 (function(tinymce) {
1075         function cleanupStylesWhenDeleting(ed) {
1076                 var dom = ed.dom, selection = ed.selection, VK= tinymce.VK;
1077                         ed.onKeyUp.add(function(ed, e) {
1078                                 if (e.keyCode == VK.DELETE ||e.keyCode == VK.BACKSPACE) {
1079                                         var startContainer = selection.getRng().startContainer;
1080                                         var blockElement = startContainer;
1081                                         while (!dom.isBlock(blockElement)) {
1082                                                 blockElement = blockElement.parentNode;
1083                                         }
1084                                         var spans = dom.select("span.Apple-style-span", blockElement);
1085                                         dom.remove(spans, true);
1086                                 }
1087                         });
1088         }
1089
1090         tinymce.create('tinymce.util.Quirks', {
1091                 Quirks: function(ed) {
1092                         if (tinymce.isWebKit) {
1093                                 cleanupStylesWhenDeleting(ed);
1094                         }
1095                                                 
1096                 }
1097         });
1098 })(tinymce);    
1099
1100 (function(tinymce) {
1101         var namedEntities, baseEntities, reverseEntities,
1102                 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1103                 textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1104                 rawCharsRegExp = /[<>&\"\']/g,
1105                 entityRegExp = /&(#x|#)?([\w]+);/g,
1106                 asciiMap = {
1107                                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1108                                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1109                                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1110                                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1111                                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1112                 };
1113
1114         // Raw entities
1115         baseEntities = {
1116                 '\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
1117                 "'" : '&#39;',
1118                 '<' : '&lt;',
1119                 '>' : '&gt;',
1120                 '&' : '&amp;'
1121         };
1122
1123         // Reverse lookup table for raw entities
1124         reverseEntities = {
1125                 '&lt;' : '<',
1126                 '&gt;' : '>',
1127                 '&amp;' : '&',
1128                 '&quot;' : '"',
1129                 '&apos;' : "'"
1130         };
1131
1132         // Decodes text by using the browser
1133         function nativeDecode(text) {
1134                 var elm;
1135
1136                 elm = document.createElement("div");
1137                 elm.innerHTML = text;
1138
1139                 return elm.textContent || elm.innerText || text;
1140         };
1141
1142         // Build a two way lookup table for the entities
1143         function buildEntitiesLookup(items, radix) {
1144                 var i, chr, entity, lookup = {};
1145
1146                 if (items) {
1147                         items = items.split(',');
1148                         radix = radix || 10;
1149
1150                         // Build entities lookup table
1151                         for (i = 0; i < items.length; i += 2) {
1152                                 chr = String.fromCharCode(parseInt(items[i], radix));
1153
1154                                 // Only add non base entities
1155                                 if (!baseEntities[chr]) {
1156                                         entity = '&' + items[i + 1] + ';';
1157                                         lookup[chr] = entity;
1158                                         lookup[entity] = chr;
1159                                 }
1160                         }
1161
1162                         return lookup;
1163                 }
1164         };
1165
1166         // Unpack entities lookup where the numbers are in radix 32 to reduce the size
1167         namedEntities = buildEntitiesLookup(
1168                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1169                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1170                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1171                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1172                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1173                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1174                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1175                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1176                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1177                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1178                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1179                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1180                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1181                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1182                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1183                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1184                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1185                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1186                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1187                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1188                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1189                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1190                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1191                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1192                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
1193         , 32);
1194
1195         tinymce.html = tinymce.html || {};
1196
1197         tinymce.html.Entities = {
1198                 encodeRaw : function(text, attr) {
1199                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1200                                 return baseEntities[chr] || chr;
1201                         });
1202                 },
1203
1204                 encodeAllRaw : function(text) {
1205                         return ('' + text).replace(rawCharsRegExp, function(chr) {
1206                                 return baseEntities[chr] || chr;
1207                         });
1208                 },
1209
1210                 encodeNumeric : function(text, attr) {
1211                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1212                                 // Multi byte sequence convert it to a single entity
1213                                 if (chr.length > 1)
1214                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1215
1216                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1217                         });
1218                 },
1219
1220                 encodeNamed : function(text, attr, entities) {
1221                         entities = entities || namedEntities;
1222
1223                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1224                                 return baseEntities[chr] || entities[chr] || chr;
1225                         });
1226                 },
1227
1228                 getEncodeFunc : function(name, entities) {
1229                         var Entities = tinymce.html.Entities;
1230
1231                         entities = buildEntitiesLookup(entities) || namedEntities;
1232
1233                         function encodeNamedAndNumeric(text, attr) {
1234                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1235                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1236                                 });
1237                         };
1238
1239                         function encodeCustomNamed(text, attr) {
1240                                 return Entities.encodeNamed(text, attr, entities);
1241                         };
1242
1243                         // Replace + with , to be compatible with previous TinyMCE versions
1244                         name = tinymce.makeMap(name.replace(/\+/g, ','));
1245
1246                         // Named and numeric encoder
1247                         if (name.named && name.numeric)
1248                                 return encodeNamedAndNumeric;
1249
1250                         // Named encoder
1251                         if (name.named) {
1252                                 // Custom names
1253                                 if (entities)
1254                                         return encodeCustomNamed;
1255
1256                                 return Entities.encodeNamed;
1257                         }
1258
1259                         // Numeric
1260                         if (name.numeric)
1261                                 return Entities.encodeNumeric;
1262
1263                         // Raw encoder
1264                         return Entities.encodeRaw;
1265                 },
1266
1267                 decode : function(text) {
1268                         return text.replace(entityRegExp, function(all, numeric, value) {
1269                                 if (numeric) {
1270                                         value = parseInt(value, numeric.length === 2 ? 16 : 10);
1271
1272                                         // Support upper UTF
1273                                         if (value > 0xFFFF) {
1274                                                 value -= 0x10000;
1275
1276                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1277                                         } else
1278                                                 return asciiMap[value] || String.fromCharCode(value);
1279                                 }
1280
1281                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1282                         });
1283                 }
1284         };
1285 })(tinymce);
1286
1287 tinymce.html.Styles = function(settings, schema) {
1288         var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1289                 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1290                 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1291                 trimRightRegExp = /\s+$/,
1292                 urlColorRegExp = /rgb/,
1293                 undef, i, encodingLookup = {}, encodingItems;
1294
1295         settings = settings || {};
1296
1297         encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1298         for (i = 0; i < encodingItems.length; i++) {
1299                 encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1300                 encodingLookup['\uFEFF' + i] = encodingItems[i];
1301         }
1302
1303         function toHex(match, r, g, b) {
1304                 function hex(val) {
1305                         val = parseInt(val).toString(16);
1306
1307                         return val.length > 1 ? val : '0' + val; // 0 -> 00
1308                 };
1309
1310                 return '#' + hex(r) + hex(g) + hex(b);
1311         };
1312
1313         return {
1314                 toHex : function(color) {
1315                         return color.replace(rgbRegExp, toHex);
1316                 },
1317
1318                 parse : function(css) {
1319                         var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1320
1321                         function compress(prefix, suffix) {
1322                                 var top, right, bottom, left;
1323
1324                                 // Get values and check it it needs compressing
1325                                 top = styles[prefix + '-top' + suffix];
1326                                 if (!top)
1327                                         return;
1328
1329                                 right = styles[prefix + '-right' + suffix];
1330                                 if (top != right)
1331                                         return;
1332
1333                                 bottom = styles[prefix + '-bottom' + suffix];
1334                                 if (right != bottom)
1335                                         return;
1336
1337                                 left = styles[prefix + '-left' + suffix];
1338                                 if (bottom != left)
1339                                         return;
1340
1341                                 // Compress
1342                                 styles[prefix + suffix] = left;
1343                                 delete styles[prefix + '-top' + suffix];
1344                                 delete styles[prefix + '-right' + suffix];
1345                                 delete styles[prefix + '-bottom' + suffix];
1346                                 delete styles[prefix + '-left' + suffix];
1347                         };
1348
1349                         function canCompress(key) {
1350                                 var value = styles[key], i;
1351
1352                                 if (!value || value.indexOf(' ') < 0)
1353                                         return;
1354
1355                                 value = value.split(' ');
1356                                 i = value.length;
1357                                 while (i--) {
1358                                         if (value[i] !== value[0])
1359                                                 return false;
1360                                 }
1361
1362                                 styles[key] = value[0];
1363
1364                                 return true;
1365                         };
1366
1367                         function compress2(target, a, b, c) {
1368                                 if (!canCompress(a))
1369                                         return;
1370
1371                                 if (!canCompress(b))
1372                                         return;
1373
1374                                 if (!canCompress(c))
1375                                         return;
1376
1377                                 // Compress
1378                                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
1379                                 delete styles[a];
1380                                 delete styles[b];
1381                                 delete styles[c];
1382                         };
1383
1384                         // Encodes the specified string by replacing all \" \' ; : with _<num>
1385                         function encode(str) {
1386                                 isEncoded = true;
1387
1388                                 return encodingLookup[str];
1389                         };
1390
1391                         // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
1392                         // It will also decode the \" \' if keep_slashes is set to fale or omitted
1393                         function decode(str, keep_slashes) {
1394                                 if (isEncoded) {
1395                                         str = str.replace(/\uFEFF[0-9]/g, function(str) {
1396                                                 return encodingLookup[str];
1397                                         });
1398                                 }
1399
1400                                 if (!keep_slashes)
1401                                         str = str.replace(/\\([\'\";:])/g, "$1");
1402
1403                                 return str;
1404                         }
1405
1406                         if (css) {
1407                                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
1408                                 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
1409                                         return str.replace(/[;:]/g, encode);
1410                                 });
1411
1412                                 // Parse styles
1413                                 while (matches = styleRegExp.exec(css)) {
1414                                         name = matches[1].replace(trimRightRegExp, '').toLowerCase();
1415                                         value = matches[2].replace(trimRightRegExp, '');
1416
1417                                         if (name && value.length > 0) {
1418                                                 // Opera will produce 700 instead of bold in their style values
1419                                                 if (name === 'font-weight' && value === '700')
1420                                                         value = 'bold';
1421                                                 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
1422                                                         value = value.toLowerCase();            
1423
1424                                                 // Convert RGB colors to HEX
1425                                                 value = value.replace(rgbRegExp, toHex);
1426
1427                                                 // Convert URLs and force them into url('value') format
1428                                                 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
1429                                                         str = str || str2;
1430
1431                                                         if (str) {
1432                                                                 str = decode(str);
1433
1434                                                                 // Force strings into single quote format
1435                                                                 return "'" + str.replace(/\'/g, "\\'") + "'";
1436                                                         }
1437
1438                                                         url = decode(url || url2 || url3);
1439
1440                                                         // Convert the URL to relative/absolute depending on config
1441                                                         if (urlConverter)
1442                                                                 url = urlConverter.call(urlConverterScope, url, 'style');
1443
1444                                                         // Output new URL format
1445                                                         return "url('" + url.replace(/\'/g, "\\'") + "')";
1446                                                 });
1447
1448                                                 styles[name] = isEncoded ? decode(value, true) : value;
1449                                         }
1450
1451                                         styleRegExp.lastIndex = matches.index + matches[0].length;
1452                                 }
1453
1454                                 // Compress the styles to reduce it's size for example IE will expand styles
1455                                 compress("border", "");
1456                                 compress("border", "-width");
1457                                 compress("border", "-color");
1458                                 compress("border", "-style");
1459                                 compress("padding", "");
1460                                 compress("margin", "");
1461                                 compress2('border', 'border-width', 'border-style', 'border-color');
1462
1463                                 // Remove pointless border, IE produces these
1464                                 if (styles.border === 'medium none')
1465                                         delete styles.border;
1466                         }
1467
1468                         return styles;
1469                 },
1470
1471                 serialize : function(styles, element_name) {
1472                         var css = '', name, value;
1473
1474                         function serializeStyles(name) {
1475                                 var styleList, i, l, value;
1476
1477                                 styleList = schema.styles[name];
1478                                 if (styleList) {
1479                                         for (i = 0, l = styleList.length; i < l; i++) {
1480                                                 name = styleList[i];
1481                                                 value = styles[name];
1482
1483                                                 if (value !== undef && value.length > 0)
1484                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1485                                         }
1486                                 }
1487                         };
1488
1489                         // Serialize styles according to schema
1490                         if (element_name && schema && schema.styles) {
1491                                 // Serialize global styles and element specific styles
1492                                 serializeStyles('*');
1493                                 serializeStyles(element_name);
1494                         } else {
1495                                 // Output the styles in the order they are inside the object
1496                                 for (name in styles) {
1497                                         value = styles[name];
1498
1499                                         if (value !== undef && value.length > 0)
1500                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1501                                 }
1502                         }
1503
1504                         return css;
1505                 }
1506         };
1507 };
1508
1509 (function(tinymce) {
1510         var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {},
1511                 whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1512
1513         function split(str, delim) {
1514                 return str.split(delim || ',');
1515         };
1516
1517         function unpack(lookup, data) {
1518                 var key, elements = {};
1519
1520                 function replace(value) {
1521                         return value.replace(/[A-Z]+/g, function(key) {
1522                                 return replace(lookup[key]);
1523                         });
1524                 };
1525
1526                 // Unpack lookup
1527                 for (key in lookup) {
1528                         if (lookup.hasOwnProperty(key))
1529                                 lookup[key] = replace(lookup[key]);
1530                 }
1531
1532                 // Unpack and parse data into object map
1533                 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
1534                         attributes = split(attributes, '|');
1535
1536                         elements[name] = {
1537                                 attributes : makeMap(attributes),
1538                                 attributesOrder : attributes,
1539                                 children : makeMap(children, '|', {'#comment' : {}})
1540                         }
1541                 });
1542
1543                 return elements;
1544         };
1545
1546         // Build a lookup table for block elements both lowercase and uppercase
1547         blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + 
1548                                                 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + 
1549                                                 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
1550         blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
1551
1552         // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
1553         transitional = unpack({
1554                 Z : 'H|K|N|O|P',
1555                 Y : 'X|form|R|Q',
1556                 ZG : 'E|span|width|align|char|charoff|valign',
1557                 X : 'p|T|div|U|W|isindex|fieldset|table',
1558                 ZF : 'E|align|char|charoff|valign',
1559                 W : 'pre|hr|blockquote|address|center|noframes',
1560                 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
1561                 ZD : '[E][S]',
1562                 U : 'ul|ol|dl|menu|dir',
1563                 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
1564                 T : 'h1|h2|h3|h4|h5|h6',
1565                 ZB : 'X|S|Q',
1566                 S : 'R|P',
1567                 ZA : 'a|G|J|M|O|P',
1568                 R : 'a|H|K|N|O',
1569                 Q : 'noscript|P',
1570                 P : 'ins|del|script',
1571                 O : 'input|select|textarea|label|button',
1572                 N : 'M|L',
1573                 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
1574                 L : 'sub|sup',
1575                 K : 'J|I',
1576                 J : 'tt|i|b|u|s|strike',
1577                 I : 'big|small|font|basefont',
1578                 H : 'G|F',
1579                 G : 'br|span|bdo',
1580                 F : 'object|applet|img|map|iframe',
1581                 E : 'A|B|C',
1582                 D : 'accesskey|tabindex|onfocus|onblur',
1583                 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
1584                 B : 'lang|xml:lang|dir',
1585                 A : 'id|class|style|title'
1586         }, 'script[id|charset|type|language|src|defer|xml:space][]' + 
1587                 'style[B|id|type|media|title|xml:space][]' + 
1588                 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
1589                 'param[id|name|value|valuetype|type][]' + 
1590                 'p[E|align][#|S]' + 
1591                 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
1592                 'br[A|clear][]' + 
1593                 'span[E][#|S]' + 
1594                 'bdo[A|C|B][#|S]' + 
1595                 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
1596                 'h1[E|align][#|S]' + 
1597                 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
1598                 'map[B|C|A|name][X|form|Q|area]' + 
1599                 'h2[E|align][#|S]' + 
1600                 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
1601                 'h3[E|align][#|S]' + 
1602                 'tt[E][#|S]' + 
1603                 'i[E][#|S]' + 
1604                 'b[E][#|S]' + 
1605                 'u[E][#|S]' + 
1606                 's[E][#|S]' + 
1607                 'strike[E][#|S]' + 
1608                 'big[E][#|S]' + 
1609                 'small[E][#|S]' + 
1610                 'font[A|B|size|color|face][#|S]' + 
1611                 'basefont[id|size|color|face][]' + 
1612                 'em[E][#|S]' + 
1613                 'strong[E][#|S]' + 
1614                 'dfn[E][#|S]' + 
1615                 'code[E][#|S]' + 
1616                 'q[E|cite][#|S]' + 
1617                 'samp[E][#|S]' + 
1618                 'kbd[E][#|S]' + 
1619                 'var[E][#|S]' + 
1620                 'cite[E][#|S]' + 
1621                 'abbr[E][#|S]' + 
1622                 'acronym[E][#|S]' + 
1623                 'sub[E][#|S]' + 
1624                 'sup[E][#|S]' + 
1625                 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
1626                 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
1627                 'optgroup[E|disabled|label][option]' + 
1628                 'option[E|selected|disabled|label|value][]' + 
1629                 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
1630                 'label[E|for|accesskey|onfocus|onblur][#|S]' + 
1631                 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
1632                 'h4[E|align][#|S]' + 
1633                 'ins[E|cite|datetime][#|Y]' + 
1634                 'h5[E|align][#|S]' + 
1635                 'del[E|cite|datetime][#|Y]' + 
1636                 'h6[E|align][#|S]' + 
1637                 'div[E|align][#|Y]' + 
1638                 'ul[E|type|compact][li]' + 
1639                 'li[E|type|value][#|Y]' + 
1640                 'ol[E|type|compact|start][li]' + 
1641                 'dl[E|compact][dt|dd]' + 
1642                 'dt[E][#|S]' + 
1643                 'dd[E][#|Y]' + 
1644                 'menu[E|compact][li]' + 
1645                 'dir[E|compact][li]' + 
1646                 'pre[E|width|xml:space][#|ZA]' + 
1647                 'hr[E|align|noshade|size|width][]' + 
1648                 'blockquote[E|cite][#|Y]' + 
1649                 'address[E][#|S|p]' + 
1650                 'center[E][#|Y]' + 
1651                 'noframes[E][#|Y]' + 
1652                 'isindex[A|B|prompt][]' + 
1653                 'fieldset[E][#|legend|Y]' + 
1654                 'legend[E|accesskey|align][#|S]' + 
1655                 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
1656                 'caption[E|align][#|S]' + 
1657                 'col[ZG][]' + 
1658                 'colgroup[ZG][col]' + 
1659                 'thead[ZF][tr]' + 
1660                 'tr[ZF|bgcolor][th|td]' + 
1661                 'th[E|ZE][#|Y]' + 
1662                 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
1663                 'noscript[E][#|Y]' + 
1664                 'td[E|ZE][#|Y]' + 
1665                 'tfoot[ZF][tr]' + 
1666                 'tbody[ZF][tr]' + 
1667                 'area[E|D|shape|coords|href|nohref|alt|target][]' + 
1668                 'base[id|href|target][]' + 
1669                 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
1670         );
1671
1672         boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls');
1673         shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
1674         nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap);
1675         whiteSpaceElementsMap = makeMap('pre,script,style,textarea');
1676         selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
1677
1678         tinymce.html.Schema = function(settings) {
1679                 var self = this, elements = {}, children = {}, patternElements = [], validStyles;
1680
1681                 settings = settings || {};
1682
1683                 // Allow all elements and attributes if verify_html is set to false
1684                 if (settings.verify_html === false)
1685                         settings.valid_elements = '*[*]';
1686
1687                 // Build styles list
1688                 if (settings.valid_styles) {
1689                         validStyles = {};
1690
1691                         // Convert styles into a rule list
1692                         each(settings.valid_styles, function(value, key) {
1693                                 validStyles[key] = tinymce.explode(value);
1694                         });
1695                 }
1696
1697                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
1698                 function patternToRegExp(str) {
1699                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
1700                 };
1701
1702                 // Parses the specified valid_elements string and adds to the current rules
1703                 // This function is a bit hard to read since it's heavily optimized for speed
1704                 function addValidElements(valid_elements) {
1705                         var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
1706                                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
1707                                 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
1708                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
1709                                 hasPatternsRegExp = /[*?+]/;
1710
1711                         if (valid_elements) {
1712                                 // Split valid elements into an array with rules
1713                                 valid_elements = split(valid_elements);
1714
1715                                 if (elements['@']) {
1716                                         globalAttributes = elements['@'].attributes;
1717                                         globalAttributesOrder = elements['@'].attributesOrder;
1718                                 }
1719
1720                                 // Loop all rules
1721                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {
1722                                         // Parse element rule
1723                                         matches = elementRuleRegExp.exec(valid_elements[ei]);
1724                                         if (matches) {
1725                                                 // Setup local names for matches
1726                                                 prefix = matches[1];
1727                                                 elementName = matches[2];
1728                                                 outputName = matches[3];
1729                                                 attrData = matches[4];
1730
1731                                                 // Create new attributes and attributesOrder
1732                                                 attributes = {};
1733                                                 attributesOrder = [];
1734
1735                                                 // Create the new element
1736                                                 element = {
1737                                                         attributes : attributes,
1738                                                         attributesOrder : attributesOrder
1739                                                 };
1740
1741                                                 // Padd empty elements prefix
1742                                                 if (prefix === '#')
1743                                                         element.paddEmpty = true;
1744
1745                                                 // Remove empty elements prefix
1746                                                 if (prefix === '-')
1747                                                         element.removeEmpty = true;
1748
1749                                                 // Copy attributes from global rule into current rule
1750                                                 if (globalAttributes) {
1751                                                         for (key in globalAttributes)
1752                                                                 attributes[key] = globalAttributes[key];
1753
1754                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
1755                                                 }
1756
1757                                                 // Attributes defined
1758                                                 if (attrData) {
1759                                                         attrData = split(attrData, '|');
1760                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {
1761                                                                 matches = attrRuleRegExp.exec(attrData[ai]);
1762                                                                 if (matches) {
1763                                                                         attr = {};
1764                                                                         attrType = matches[1];
1765                                                                         attrName = matches[2].replace(/::/g, ':');
1766                                                                         prefix = matches[3];
1767                                                                         value = matches[4];
1768
1769                                                                         // Required
1770                                                                         if (attrType === '!') {
1771                                                                                 element.attributesRequired = element.attributesRequired || [];
1772                                                                                 element.attributesRequired.push(attrName);
1773                                                                                 attr.required = true;
1774                                                                         }
1775
1776                                                                         // Denied from global
1777                                                                         if (attrType === '-') {
1778                                                                                 delete attributes[attrName];
1779                                                                                 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
1780                                                                                 continue;
1781                                                                         }
1782
1783                                                                         // Default value
1784                                                                         if (prefix) {
1785                                                                                 // Default value
1786                                                                                 if (prefix === '=') {
1787                                                                                         element.attributesDefault = element.attributesDefault || [];
1788                                                                                         element.attributesDefault.push({name: attrName, value: value});
1789                                                                                         attr.defaultValue = value;
1790                                                                                 }
1791
1792                                                                                 // Forced value
1793                                                                                 if (prefix === ':') {
1794                                                                                         element.attributesForced = element.attributesForced || [];
1795                                                                                         element.attributesForced.push({name: attrName, value: value});
1796                                                                                         attr.forcedValue = value;
1797                                                                                 }
1798
1799                                                                                 // Required values
1800                                                                                 if (prefix === '<')
1801                                                                                         attr.validValues = makeMap(value, '?');
1802                                                                         }
1803
1804                                                                         // Check for attribute patterns
1805                                                                         if (hasPatternsRegExp.test(attrName)) {
1806                                                                                 element.attributePatterns = element.attributePatterns || [];
1807                                                                                 attr.pattern = patternToRegExp(attrName);
1808                                                                                 element.attributePatterns.push(attr);
1809                                                                         } else {
1810                                                                                 // Add attribute to order list if it doesn't already exist
1811                                                                                 if (!attributes[attrName])
1812                                                                                         attributesOrder.push(attrName);
1813
1814                                                                                 attributes[attrName] = attr;
1815                                                                         }
1816                                                                 }
1817                                                         }
1818                                                 }
1819
1820                                                 // Global rule, store away these for later usage
1821                                                 if (!globalAttributes && elementName == '@') {
1822                                                         globalAttributes = attributes;
1823                                                         globalAttributesOrder = attributesOrder;
1824                                                 }
1825
1826                                                 // Handle substitute elements such as b/strong
1827                                                 if (outputName) {
1828                                                         element.outputName = elementName;
1829                                                         elements[outputName] = element;
1830                                                 }
1831
1832                                                 // Add pattern or exact element
1833                                                 if (hasPatternsRegExp.test(elementName)) {
1834                                                         element.pattern = patternToRegExp(elementName);
1835                                                         patternElements.push(element);
1836                                                 } else
1837                                                         elements[elementName] = element;
1838                                         }
1839                                 }
1840                         }
1841                 };
1842
1843                 function setValidElements(valid_elements) {
1844                         elements = {};
1845                         patternElements = [];
1846
1847                         addValidElements(valid_elements);
1848
1849                         each(transitional, function(element, name) {
1850                                 children[name] = element.children;
1851                         });
1852                 };
1853
1854                 // Adds custom non HTML elements to the schema
1855                 function addCustomElements(custom_elements) {
1856                         var customElementRegExp = /^(~)?(.+)$/;
1857
1858                         if (custom_elements) {
1859                                 each(split(custom_elements), function(rule) {
1860                                         var matches = customElementRegExp.exec(rule),
1861                                                 inline = matches[1] === '~',
1862                                                 cloneName = inline ? 'span' : 'div',
1863                                                 name = matches[2];
1864
1865                                         children[name] = children[cloneName];
1866                                         customElementsMap[name] = cloneName;
1867
1868                                         // If it's not marked as inline then add it to valid block elements
1869                                         if (!inline)
1870                                                 blockElementsMap[name] = {};
1871
1872                                         // Add custom elements at span/div positions
1873                                         each(children, function(element, child) {
1874                                                 if (element[cloneName])
1875                                                         element[name] = element[cloneName];
1876                                         });
1877                                 });
1878                         }
1879                 };
1880
1881                 // Adds valid children to the schema object
1882                 function addValidChildren(valid_children) {
1883                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
1884
1885                         if (valid_children) {
1886                                 each(split(valid_children), function(rule) {
1887                                         var matches = childRuleRegExp.exec(rule), parent, prefix;
1888
1889                                         if (matches) {
1890                                                 prefix = matches[1];
1891
1892                                                 // Add/remove items from default
1893                                                 if (prefix)
1894                                                         parent = children[matches[2]];
1895                                                 else
1896                                                         parent = children[matches[2]] = {'#comment' : {}};
1897
1898                                                 parent = children[matches[2]];
1899
1900                                                 each(split(matches[3], '|'), function(child) {
1901                                                         if (prefix === '-')
1902                                                                 delete parent[child];
1903                                                         else
1904                                                                 parent[child] = {};
1905                                                 });
1906                                         }
1907                                 });
1908                         }
1909                 };
1910
1911                 function getElementRule(name) {
1912                         var element = elements[name], i;
1913
1914                         // Exact match found
1915                         if (element)
1916                                 return element;
1917
1918                         // No exact match then try the patterns
1919                         i = patternElements.length;
1920                         while (i--) {
1921                                 element = patternElements[i];
1922
1923                                 if (element.pattern.test(name))
1924                                         return element;
1925                         }
1926                 };
1927
1928                 if (!settings.valid_elements) {
1929                         // No valid elements defined then clone the elements from the transitional spec
1930                         each(transitional, function(element, name) {
1931                                 elements[name] = {
1932                                         attributes : element.attributes,
1933                                         attributesOrder : element.attributesOrder
1934                                 };
1935
1936                                 children[name] = element.children;
1937                         });
1938
1939                         // Switch these
1940                         each(split('strong/b,em/i'), function(item) {
1941                                 item = split(item, '/');
1942                                 elements[item[1]].outputName = item[0];
1943                         });
1944
1945                         // Add default alt attribute for images
1946                         elements.img.attributesDefault = [{name: 'alt', value: ''}];
1947
1948                         // Remove these if they are empty by default
1949                         each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) {
1950                                 elements[name].removeEmpty = true;
1951                         });
1952
1953                         // Padd these by default
1954                         each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
1955                                 elements[name].paddEmpty = true;
1956                         });
1957                 } else
1958                         setValidElements(settings.valid_elements);
1959
1960                 addCustomElements(settings.custom_elements);
1961                 addValidChildren(settings.valid_children);
1962                 addValidElements(settings.extended_valid_elements);
1963
1964                 // Todo: Remove this when we fix list handling to be valid
1965                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');
1966
1967                 // If the user didn't allow span only allow internal spans
1968                 if (!getElementRule('span'))
1969                         addValidElements('span[!data-mce-type|*]');
1970
1971                 // Delete invalid elements
1972                 if (settings.invalid_elements) {
1973                         tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
1974                                 if (elements[item])
1975                                         delete elements[item];
1976                         });
1977                 }
1978
1979                 self.children = children;
1980
1981                 self.styles = validStyles;
1982
1983                 self.getBoolAttrs = function() {
1984                         return boolAttrMap;
1985                 };
1986
1987                 self.getBlockElements = function() {
1988                         return blockElementsMap;
1989                 };
1990
1991                 self.getShortEndedElements = function() {
1992                         return shortEndedElementsMap;
1993                 };
1994
1995                 self.getSelfClosingElements = function() {
1996                         return selfClosingElementsMap;
1997                 };
1998
1999                 self.getNonEmptyElements = function() {
2000                         return nonEmptyElementsMap;
2001                 };
2002
2003                 self.getWhiteSpaceElements = function() {
2004                         return whiteSpaceElementsMap;
2005                 };
2006
2007                 self.isValidChild = function(name, child) {
2008                         var parent = children[name];
2009
2010                         return !!(parent && parent[child]);
2011                 };
2012
2013                 self.getElementRule = getElementRule;
2014
2015                 self.getCustomElements = function() {
2016                         return customElementsMap;
2017                 };
2018
2019                 self.addValidElements = addValidElements;
2020
2021                 self.setValidElements = setValidElements;
2022
2023                 self.addCustomElements = addCustomElements;
2024
2025                 self.addValidChildren = addValidChildren;
2026         };
2027
2028         // Expose boolMap and blockElementMap as static properties for usage in DOMUtils
2029         tinymce.html.Schema.boolAttrMap = boolAttrMap;
2030         tinymce.html.Schema.blockElementsMap = blockElementsMap;
2031 })(tinymce);
2032
2033 (function(tinymce) {
2034         tinymce.html.SaxParser = function(settings, schema) {
2035                 var self = this, noop = function() {};
2036
2037                 settings = settings || {};
2038                 self.schema = schema = schema || new tinymce.html.Schema();
2039
2040                 if (settings.fix_self_closing !== false)
2041                         settings.fix_self_closing = true;
2042
2043                 // Add handler functions from settings and setup default handlers
2044                 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2045                         if (name)
2046                                 self[name] = settings[name] || noop;
2047                 });
2048
2049                 self.parse = function(html) {
2050                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2051                                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,
2052                                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2053                                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;
2054
2055                         function processEndTag(name) {
2056                                 var pos, i;
2057
2058                                 // Find position of parent of the same type
2059                                 pos = stack.length;
2060                                 while (pos--) {
2061                                         if (stack[pos].name === name)
2062                                                 break;                                          
2063                                 }
2064
2065                                 // Found parent
2066                                 if (pos >= 0) {
2067                                         // Close all the open elements
2068                                         for (i = stack.length - 1; i >= pos; i--) {
2069                                                 name = stack[i];
2070
2071                                                 if (name.valid)
2072                                                         self.end(name.name);
2073                                         }
2074
2075                                         // Remove the open elements from the stack
2076                                         stack.length = pos;
2077                                 }
2078                         };
2079
2080                         // Precompile RegExps and map objects
2081                         tokenRegExp = new RegExp('<(?:' +
2082                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment
2083                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2084                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2085                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2086                                 '(?:\\/([^>]+)>)|' + // End element
2087                                 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
2088                         ')', 'g');
2089
2090                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2091                         specialElements = {
2092                                 'script' : /<\/script[^>]*>/gi,
2093                                 'style' : /<\/style[^>]*>/gi,
2094                                 'noscript' : /<\/noscript[^>]*>/gi
2095                         };
2096
2097                         // Setup lookup tables for empty elements and boolean attributes
2098                         shortEndedElements = schema.getShortEndedElements();
2099                         selfClosing = schema.getSelfClosingElements();
2100                         fillAttrsMap = schema.getBoolAttrs();
2101                         validate = settings.validate;
2102                         removeInternalElements = settings.remove_internals;
2103                         fixSelfClosing = settings.fix_self_closing;
2104
2105                         while (matches = tokenRegExp.exec(html)) {
2106                                 // Text
2107                                 if (index < matches.index)
2108                                         self.text(decode(html.substr(index, matches.index - index)));
2109
2110                                 if (value = matches[6]) { // End element
2111                                         processEndTag(value.toLowerCase());
2112                                 } else if (value = matches[7]) { // Start element
2113                                         value = value.toLowerCase();
2114                                         isShortEnded = value in shortEndedElements;
2115
2116                                         // Is self closing tag for example an <li> after an open <li>
2117                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
2118                                                 processEndTag(value);
2119
2120                                         // Validate element
2121                                         if (!validate || (elementRule = schema.getElementRule(value))) {
2122                                                 isValidElement = true;
2123
2124                                                 // Grab attributes map and patters when validation is enabled
2125                                                 if (validate) {
2126                                                         validAttributesMap = elementRule.attributes;
2127                                                         validAttributePatterns = elementRule.attributePatterns;
2128                                                 }
2129
2130                                                 // Parse attributes
2131                                                 if (attribsValue = matches[8]) {
2132                                                         isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
2133
2134                                                         // If the element has internal attributes then remove it if we are told to do so
2135                                                         if (isInternalElement && removeInternalElements)
2136                                                                 isValidElement = false;
2137
2138                                                         attrList = [];
2139                                                         attrList.map = {};
2140
2141                                                         attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
2142                                                                 var attrRule, i;
2143
2144                                                                 name = name.toLowerCase();
2145                                                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2146
2147                                                                 // Validate name and value
2148                                                                 if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2149                                                                         attrRule = validAttributesMap[name];
2150
2151                                                                         // Find rule by pattern matching
2152                                                                         if (!attrRule && validAttributePatterns) {
2153                                                                                 i = validAttributePatterns.length;
2154                                                                                 while (i--) {
2155                                                                                         attrRule = validAttributePatterns[i];
2156                                                                                         if (attrRule.pattern.test(name))
2157                                                                                                 break;
2158                                                                                 }
2159
2160                                                                                 // No rule matched
2161                                                                                 if (i === -1)
2162                                                                                         attrRule = null;
2163                                                                         }
2164
2165                                                                         // No attribute rule found
2166                                                                         if (!attrRule)
2167                                                                                 return;
2168
2169                                                                         // Validate value
2170                                                                         if (attrRule.validValues && !(value in attrRule.validValues))
2171                                                                                 return;
2172                                                                 }
2173
2174                                                                 // Add attribute to list and map
2175                                                                 attrList.map[name] = value;
2176                                                                 attrList.push({
2177                                                                         name: name,
2178                                                                         value: value
2179                                                                 });
2180                                                         });
2181                                                 } else {
2182                                                         attrList = [];
2183                                                         attrList.map = {};
2184                                                 }
2185
2186                                                 // Process attributes if validation is enabled
2187                                                 if (validate && !isInternalElement) {
2188                                                         attributesRequired = elementRule.attributesRequired;
2189                                                         attributesDefault = elementRule.attributesDefault;
2190                                                         attributesForced = elementRule.attributesForced;
2191
2192                                                         // Handle forced attributes
2193                                                         if (attributesForced) {
2194                                                                 i = attributesForced.length;
2195                                                                 while (i--) {
2196                                                                         attr = attributesForced[i];
2197                                                                         name = attr.name;
2198                                                                         attrValue = attr.value;
2199
2200                                                                         if (attrValue === '{$uid}')
2201                                                                                 attrValue = 'mce_' + idCount++;
2202
2203                                                                         attrList.map[name] = attrValue;
2204                                                                         attrList.push({name: name, value: attrValue});
2205                                                                 }
2206                                                         }
2207
2208                                                         // Handle default attributes
2209                                                         if (attributesDefault) {
2210                                                                 i = attributesDefault.length;
2211                                                                 while (i--) {
2212                                                                         attr = attributesDefault[i];
2213                                                                         name = attr.name;
2214
2215                                                                         if (!(name in attrList.map)) {
2216                                                                                 attrValue = attr.value;
2217
2218                                                                                 if (attrValue === '{$uid}')
2219                                                                                         attrValue = 'mce_' + idCount++;
2220
2221                                                                                 attrList.map[name] = attrValue;
2222                                                                                 attrList.push({name: name, value: attrValue});
2223                                                                         }
2224                                                                 }
2225                                                         }
2226
2227                                                         // Handle required attributes
2228                                                         if (attributesRequired) {
2229                                                                 i = attributesRequired.length;
2230                                                                 while (i--) {
2231                                                                         if (attributesRequired[i] in attrList.map)
2232                                                                                 break;
2233                                                                 }
2234
2235                                                                 // None of the required attributes where found
2236                                                                 if (i === -1)
2237                                                                         isValidElement = false;
2238                                                         }
2239
2240                                                         // Invalidate element if it's marked as bogus
2241                                                         if (attrList.map['data-mce-bogus'])
2242                                                                 isValidElement = false;
2243                                                 }
2244
2245                                                 if (isValidElement)
2246                                                         self.start(value, attrList, isShortEnded);
2247                                         } else
2248                                                 isValidElement = false;
2249
2250                                         // Treat script, noscript and style a bit different since they may include code that looks like elements
2251                                         if (endRegExp = specialElements[value]) {
2252                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;
2253
2254                                                 if (matches = endRegExp.exec(html)) {
2255                                                         if (isValidElement)
2256                                                                 text = html.substr(index, matches.index - index);
2257
2258                                                         index = matches.index + matches[0].length;
2259                                                 } else {
2260                                                         text = html.substr(index);
2261                                                         index = html.length;
2262                                                 }
2263
2264                                                 if (isValidElement && text.length > 0)
2265                                                         self.text(text, true);
2266
2267                                                 if (isValidElement)
2268                                                         self.end(value);
2269
2270                                                 tokenRegExp.lastIndex = index;
2271                                                 continue;
2272                                         }
2273
2274                                         // Push value on to stack
2275                                         if (!isShortEnded) {
2276                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
2277                                                         stack.push({name: value, valid: isValidElement});
2278                                                 else if (isValidElement)
2279                                                         self.end(value);
2280                                         }
2281                                 } else if (value = matches[1]) { // Comment
2282                                         self.comment(value);
2283                                 } else if (value = matches[2]) { // CDATA
2284                                         self.cdata(value);
2285                                 } else if (value = matches[3]) { // DOCTYPE
2286                                         self.doctype(value);
2287                                 } else if (value = matches[4]) { // PI
2288                                         self.pi(value, matches[5]);
2289                                 }
2290
2291                                 index = matches.index + matches[0].length;
2292                         }
2293
2294                         // Text
2295                         if (index < html.length)
2296                                 self.text(decode(html.substr(index)));
2297
2298                         // Close any open elements
2299                         for (i = stack.length - 1; i >= 0; i--) {
2300                                 value = stack[i];
2301
2302                                 if (value.valid)
2303                                         self.end(value.name);
2304                         }
2305                 };
2306         }
2307 })(tinymce);
2308
2309 (function(tinymce) {
2310         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
2311                 '#text' : 3,
2312                 '#comment' : 8,
2313                 '#cdata' : 4,
2314                 '#pi' : 7,
2315                 '#doctype' : 10,
2316                 '#document-fragment' : 11
2317         };
2318
2319         // Walks the tree left/right
2320         function walk(node, root_node, prev) {
2321                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
2322
2323                 // Walk into nodes if it has a start
2324                 if (node[startName])
2325                         return node[startName];
2326
2327                 // Return the sibling if it has one
2328                 if (node !== root_node) {
2329                         sibling = node[siblingName];
2330
2331                         if (sibling)
2332                                 return sibling;
2333
2334                         // Walk up the parents to look for siblings
2335                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
2336                                 sibling = parent[siblingName];
2337
2338                                 if (sibling)
2339                                         return sibling;
2340                         }
2341                 }
2342         };
2343
2344         function Node(name, type) {
2345                 this.name = name;
2346                 this.type = type;
2347
2348                 if (type === 1) {
2349                         this.attributes = [];
2350                         this.attributes.map = {};
2351                 }
2352         }
2353
2354         tinymce.extend(Node.prototype, {
2355                 replace : function(node) {
2356                         var self = this;
2357
2358                         if (node.parent)
2359                                 node.remove();
2360
2361                         self.insert(node, self);
2362                         self.remove();
2363
2364                         return self;
2365                 },
2366
2367                 attr : function(name, value) {
2368                         var self = this, attrs, i, undef;
2369
2370                         if (typeof name !== "string") {
2371                                 for (i in name)
2372                                         self.attr(i, name[i]);
2373
2374                                 return self;
2375                         }
2376
2377                         if (attrs = self.attributes) {
2378                                 if (value !== undef) {
2379                                         // Remove attribute
2380                                         if (value === null) {
2381                                                 if (name in attrs.map) {
2382                                                         delete attrs.map[name];
2383
2384                                                         i = attrs.length;
2385                                                         while (i--) {
2386                                                                 if (attrs[i].name === name) {
2387                                                                         attrs = attrs.splice(i, 1);
2388                                                                         return self;
2389                                                                 }
2390                                                         }
2391                                                 }
2392
2393                                                 return self;
2394                                         }
2395
2396                                         // Set attribute
2397                                         if (name in attrs.map) {
2398                                                 // Set attribute
2399                                                 i = attrs.length;
2400                                                 while (i--) {
2401                                                         if (attrs[i].name === name) {
2402                                                                 attrs[i].value = value;
2403                                                                 break;
2404                                                         }
2405                                                 }
2406                                         } else
2407                                                 attrs.push({name: name, value: value});
2408
2409                                         attrs.map[name] = value;
2410
2411                                         return self;
2412                                 } else {
2413                                         return attrs.map[name];
2414                                 }
2415                         }
2416                 },
2417
2418                 clone : function() {
2419                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
2420
2421                         // Clone element attributes
2422                         if (selfAttrs = self.attributes) {
2423                                 cloneAttrs = [];
2424                                 cloneAttrs.map = {};
2425
2426                                 for (i = 0, l = selfAttrs.length; i < l; i++) {
2427                                         selfAttr = selfAttrs[i];
2428
2429                                         // Clone everything except id
2430                                         if (selfAttr.name !== 'id') {
2431                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
2432                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;
2433                                         }
2434                                 }
2435
2436                                 clone.attributes = cloneAttrs;
2437                         }
2438
2439                         clone.value = self.value;
2440                         clone.shortEnded = self.shortEnded;
2441
2442                         return clone;
2443                 },
2444
2445                 wrap : function(wrapper) {
2446                         var self = this;
2447
2448                         self.parent.insert(wrapper, self);
2449                         wrapper.append(self);
2450
2451                         return self;
2452                 },
2453
2454                 unwrap : function() {
2455                         var self = this, node, next;
2456
2457                         for (node = self.firstChild; node; ) {
2458                                 next = node.next;
2459                                 self.insert(node, self, true);
2460                                 node = next;
2461                         }
2462
2463                         self.remove();
2464                 },
2465
2466                 remove : function() {
2467                         var self = this, parent = self.parent, next = self.next, prev = self.prev;
2468
2469                         if (parent) {
2470                                 if (parent.firstChild === self) {
2471                                         parent.firstChild = next;
2472
2473                                         if (next)
2474                                                 next.prev = null;
2475                                 } else {
2476                                         prev.next = next;
2477                                 }
2478
2479                                 if (parent.lastChild === self) {
2480                                         parent.lastChild = prev;
2481
2482                                         if (prev)
2483                                                 prev.next = null;
2484                                 } else {
2485                                         next.prev = prev;
2486                                 }
2487
2488                                 self.parent = self.next = self.prev = null;
2489                         }
2490
2491                         return self;
2492                 },
2493
2494                 append : function(node) {
2495                         var self = this, last;
2496
2497                         if (node.parent)
2498                                 node.remove();
2499
2500                         last = self.lastChild;
2501                         if (last) {
2502                                 last.next = node;
2503                                 node.prev = last;
2504                                 self.lastChild = node;
2505                         } else
2506                                 self.lastChild = self.firstChild = node;
2507
2508                         node.parent = self;
2509
2510                         return node;
2511                 },
2512
2513                 insert : function(node, ref_node, before) {
2514                         var parent;
2515
2516                         if (node.parent)
2517                                 node.remove();
2518
2519                         parent = ref_node.parent || this;
2520
2521                         if (before) {
2522                                 if (ref_node === parent.firstChild)
2523                                         parent.firstChild = node;
2524                                 else
2525                                         ref_node.prev.next = node;
2526
2527                                 node.prev = ref_node.prev;
2528                                 node.next = ref_node;
2529                                 ref_node.prev = node;
2530                         } else {
2531                                 if (ref_node === parent.lastChild)
2532                                         parent.lastChild = node;
2533                                 else
2534                                         ref_node.next.prev = node;
2535
2536                                 node.next = ref_node.next;
2537                                 node.prev = ref_node;
2538                                 ref_node.next = node;
2539                         }
2540
2541                         node.parent = parent;
2542
2543                         return node;
2544                 },
2545
2546                 getAll : function(name) {
2547                         var self = this, node, collection = [];
2548
2549                         for (node = self.firstChild; node; node = walk(node, self)) {
2550                                 if (node.name === name)
2551                                         collection.push(node);
2552                         }
2553
2554                         return collection;
2555                 },
2556
2557                 empty : function() {
2558                         var self = this, nodes, i, node;
2559
2560                         // Remove all children
2561                         if (self.firstChild) {
2562                                 nodes = [];
2563
2564                                 // Collect the children
2565                                 for (node = self.firstChild; node; node = walk(node, self))
2566                                         nodes.push(node);
2567
2568                                 // Remove the children
2569                                 i = nodes.length;
2570                                 while (i--) {
2571                                         node = nodes[i];
2572                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
2573                                 }
2574                         }
2575
2576                         self.firstChild = self.lastChild = null;
2577
2578                         return self;
2579                 },
2580
2581                 isEmpty : function(elements) {
2582                         var self = this, node = self.firstChild, i, name;
2583
2584                         if (node) {
2585                                 do {
2586                                         if (node.type === 1) {
2587                                                 // Ignore bogus elements
2588                                                 if (node.attributes.map['data-mce-bogus'])
2589                                                         continue;
2590
2591                                                 // Keep empty elements like <img />
2592                                                 if (elements[node.name])
2593                                                         return false;
2594
2595                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>
2596                                                 i = node.attributes.length;
2597                                                 while (i--) {
2598                                                         name = node.attributes[i].name;
2599                                                         if (name === "name" || name.indexOf('data-') === 0)
2600                                                                 return false;
2601                                                 }
2602                                         }
2603
2604                                         // Keep non whitespace text nodes
2605                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
2606                                                 return false;
2607                                 } while (node = walk(node, self));
2608                         }
2609
2610                         return true;
2611                 },
2612
2613                 walk : function(prev) {
2614                         return walk(this, null, prev);
2615                 }
2616         });
2617
2618         tinymce.extend(Node, {
2619                 create : function(name, attrs) {
2620                         var node, attrName;
2621
2622                         // Create node
2623                         node = new Node(name, typeLookup[name] || 1);
2624
2625                         // Add attributes if needed
2626                         if (attrs) {
2627                                 for (attrName in attrs)
2628                                         node.attr(attrName, attrs[attrName]);
2629                         }
2630
2631                         return node;
2632                 }
2633         });
2634
2635         tinymce.html.Node = Node;
2636 })(tinymce);
2637
2638 (function(tinymce) {
2639         var Node = tinymce.html.Node;
2640
2641         tinymce.html.DomParser = function(settings, schema) {
2642                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
2643
2644                 settings = settings || {};
2645                 settings.validate = "validate" in settings ? settings.validate : true;
2646                 settings.root_name = settings.root_name || 'body';
2647                 self.schema = schema = schema || new tinymce.html.Schema();
2648
2649                 function fixInvalidChildren(nodes) {
2650                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
2651                                 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
2652
2653                         nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
2654                         nonEmptyElements = schema.getNonEmptyElements();
2655
2656                         for (ni = 0; ni < nodes.length; ni++) {
2657                                 node = nodes[ni];
2658
2659                                 // Already removed
2660                                 if (!node.parent)
2661                                         continue;
2662
2663                                 // Get list of all parent nodes until we find a valid parent to stick the child into
2664                                 parents = [node];
2665                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
2666                                         parents.push(parent);
2667
2668                                 // Found a suitable parent
2669                                 if (parent && parents.length > 1) {
2670                                         // Reverse the array since it makes looping easier
2671                                         parents.reverse();
2672
2673                                         // Clone the related parent and insert that after the moved node
2674                                         newParent = currentNode = self.filterNode(parents[0].clone());
2675
2676                                         // Start cloning and moving children on the left side of the target node
2677                                         for (i = 0; i < parents.length - 1; i++) {
2678                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {
2679                                                         tempNode = self.filterNode(parents[i].clone());
2680                                                         currentNode.append(tempNode);
2681                                                 } else
2682                                                         tempNode = currentNode;
2683
2684                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
2685                                                         nextNode = childNode.next;
2686                                                         tempNode.append(childNode);
2687                                                         childNode = nextNode;
2688                                                 }
2689
2690                                                 currentNode = tempNode;
2691                                         }
2692
2693                                         if (!newParent.isEmpty(nonEmptyElements)) {
2694                                                 parent.insert(newParent, parents[0], true);
2695                                                 parent.insert(node, newParent);
2696                                         } else {
2697                                                 parent.insert(node, parents[0], true);
2698                                         }
2699
2700                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
2701                                         parent = parents[0];
2702                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
2703                                                 parent.empty().remove();
2704                                         }
2705                                 } else if (node.parent) {
2706                                         // If it's an LI try to find a UL/OL for it or wrap it
2707                                         if (node.name === 'li') {
2708                                                 sibling = node.prev;
2709                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2710                                                         sibling.append(node);
2711                                                         continue;
2712                                                 }
2713
2714                                                 sibling = node.next;
2715                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
2716                                                         sibling.insert(node, sibling.firstChild, true);
2717                                                         continue;
2718                                                 }
2719
2720                                                 node.wrap(self.filterNode(new Node('ul', 1)));
2721                                                 continue;
2722                                         }
2723
2724                                         // Try wrapping the element in a DIV
2725                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
2726                                                 node.wrap(self.filterNode(new Node('div', 1)));
2727                                         } else {
2728                                                 // We failed wrapping it, then remove or unwrap it
2729                                                 if (node.name === 'style' || node.name === 'script')
2730                                                         node.empty().remove();
2731                                                 else
2732                                                         node.unwrap();
2733                                         }
2734                                 }
2735                         }
2736                 };
2737
2738                 self.filterNode = function(node) {
2739                         var i, name, list;
2740
2741                         // Run element filters
2742                         if (name in nodeFilters) {
2743                                 list = matchedNodes[name];
2744
2745                                 if (list)
2746                                         list.push(node);
2747                                 else
2748                                         matchedNodes[name] = [node];
2749                         }
2750
2751                         // Run attribute filters
2752                         i = attributeFilters.length;
2753                         while (i--) {
2754                                 name = attributeFilters[i].name;
2755
2756                                 if (name in node.attributes.map) {
2757                                         list = matchedAttributes[name];
2758
2759                                         if (list)
2760                                                 list.push(node);
2761                                         else
2762                                                 matchedAttributes[name] = [node];
2763                                 }
2764                         }
2765
2766                         return node;
2767                 };
2768
2769                 self.addNodeFilter = function(name, callback) {
2770                         tinymce.each(tinymce.explode(name), function(name) {
2771                                 var list = nodeFilters[name];
2772
2773                                 if (!list)
2774                                         nodeFilters[name] = list = [];
2775
2776                                 list.push(callback);
2777                         });
2778                 };
2779
2780                 self.addAttributeFilter = function(name, callback) {
2781                         tinymce.each(tinymce.explode(name), function(name) {
2782                                 var i;
2783
2784                                 for (i = 0; i < attributeFilters.length; i++) {
2785                                         if (attributeFilters[i].name === name) {
2786                                                 attributeFilters[i].callbacks.push(callback);
2787                                                 return;
2788                                         }
2789                                 }
2790
2791                                 attributeFilters.push({name: name, callbacks: [callback]});
2792                         });
2793                 };
2794
2795                 self.parse = function(html, args) {
2796                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
2797                                 blockElements, startWhiteSpaceRegExp, invalidChildren = [],
2798                                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
2799
2800                         args = args || {};
2801                         matchedNodes = {};
2802                         matchedAttributes = {};
2803                         blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
2804                         nonEmptyElements = schema.getNonEmptyElements();
2805                         children = schema.children;
2806                         validate = settings.validate;
2807                         rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
2808
2809                         whiteSpaceElements = schema.getWhiteSpaceElements();
2810                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;
2811                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;
2812                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;
2813
2814                         function addRootBlocks() {
2815                                 var node = rootNode.firstChild, next, rootBlockNode;
2816
2817                                 while (node) {
2818                                         next = node.next;
2819
2820                                         if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
2821                                                 if (!rootBlockNode) {
2822                                                         // Create a new root block element
2823                                                         rootBlockNode = createNode(rootBlockName, 1);
2824                                                         rootNode.insert(rootBlockNode, node);
2825                                                         rootBlockNode.append(node);
2826                                                 } else
2827                                                         rootBlockNode.append(node);
2828                                         } else {
2829                                                 rootBlockNode = null;
2830                                         }
2831
2832                                         node = next;
2833                                 };
2834                         };
2835
2836                         function createNode(name, type) {
2837                                 var node = new Node(name, type), list;
2838
2839                                 if (name in nodeFilters) {
2840                                         list = matchedNodes[name];
2841
2842                                         if (list)
2843                                                 list.push(node);
2844                                         else
2845                                                 matchedNodes[name] = [node];
2846                                 }
2847
2848                                 return node;
2849                         };
2850
2851                         function removeWhitespaceBefore(node) {
2852                                 var textNode, textVal, sibling;
2853
2854                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {
2855                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
2856
2857                                         if (textVal.length > 0) {
2858                                                 textNode.value = textVal;
2859                                                 textNode = textNode.prev;
2860                                         } else {
2861                                                 sibling = textNode.prev;
2862                                                 textNode.remove();
2863                                                 textNode = sibling;
2864                                         }
2865                                 }
2866                         };
2867
2868                         parser = new tinymce.html.SaxParser({
2869                                 validate : validate,
2870                                 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
2871
2872                                 cdata: function(text) {
2873                                         node.append(createNode('#cdata', 4)).value = text;
2874                                 },
2875
2876                                 text: function(text, raw) {
2877                                         var textNode;
2878
2879                                         // Trim all redundant whitespace on non white space elements
2880                                         if (!whiteSpaceElements[node.name]) {
2881                                                 text = text.replace(allWhiteSpaceRegExp, ' ');
2882
2883                                                 if (node.lastChild && blockElements[node.lastChild.name])
2884                                                         text = text.replace(startWhiteSpaceRegExp, '');
2885                                         }
2886
2887                                         // Do we need to create the node
2888                                         if (text.length !== 0) {
2889                                                 textNode = createNode('#text', 3);
2890                                                 textNode.raw = !!raw;
2891                                                 node.append(textNode).value = text;
2892                                         }
2893                                 },
2894
2895                                 comment: function(text) {
2896                                         node.append(createNode('#comment', 8)).value = text;
2897                                 },
2898
2899                                 pi: function(name, text) {
2900                                         node.append(createNode(name, 7)).value = text;
2901                                         removeWhitespaceBefore(node);
2902                                 },
2903
2904                                 doctype: function(text) {
2905                                         var newNode;
2906                 
2907                                         newNode = node.append(createNode('#doctype', 10));
2908                                         newNode.value = text;
2909                                         removeWhitespaceBefore(node);
2910                                 },
2911
2912                                 start: function(name, attrs, empty) {
2913                                         var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
2914
2915                                         elementRule = validate ? schema.getElementRule(name) : {};
2916                                         if (elementRule) {
2917                                                 newNode = createNode(elementRule.outputName || name, 1);
2918                                                 newNode.attributes = attrs;
2919                                                 newNode.shortEnded = empty;
2920
2921                                                 node.append(newNode);
2922
2923                                                 // Check if node is valid child of the parent node is the child is
2924                                                 // unknown we don't collect it since it's probably a custom element
2925                                                 parent = children[node.name];
2926                                                 if (parent && children[newNode.name] && !parent[newNode.name])
2927                                                         invalidChildren.push(newNode);
2928
2929                                                 attrFiltersLen = attributeFilters.length;
2930                                                 while (attrFiltersLen--) {
2931                                                         attrName = attributeFilters[attrFiltersLen].name;
2932
2933                                                         if (attrName in attrs.map) {
2934                                                                 list = matchedAttributes[attrName];
2935
2936                                                                 if (list)
2937                                                                         list.push(newNode);
2938                                                                 else
2939                                                                         matchedAttributes[attrName] = [newNode];
2940                                                         }
2941                                                 }
2942
2943                                                 // Trim whitespace before block
2944                                                 if (blockElements[name])
2945                                                         removeWhitespaceBefore(newNode);
2946
2947                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />
2948                                                 if (!empty)
2949                                                         node = newNode;
2950                                         }
2951                                 },
2952
2953                                 end: function(name) {
2954                                         var textNode, elementRule, text, sibling, tempNode;
2955
2956                                         elementRule = validate ? schema.getElementRule(name) : {};
2957                                         if (elementRule) {
2958                                                 if (blockElements[name]) {
2959                                                         if (!whiteSpaceElements[node.name]) {
2960                                                                 // Trim whitespace at beginning of block
2961                                                                 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
2962                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');
2963
2964                                                                         if (text.length > 0) {
2965                                                                                 textNode.value = text;
2966                                                                                 textNode = textNode.next;
2967                                                                         } else {
2968                                                                                 sibling = textNode.next;
2969                                                                                 textNode.remove();
2970                                                                                 textNode = sibling;
2971                                                                         }
2972                                                                 }
2973
2974                                                                 // Trim whitespace at end of block
2975                                                                 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
2976                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');
2977
2978                                                                         if (text.length > 0) {
2979                                                                                 textNode.value = text;
2980                                                                                 textNode = textNode.prev;
2981                                                                         } else {
2982                                                                                 sibling = textNode.prev;
2983                                                                                 textNode.remove();
2984                                                                                 textNode = sibling;
2985                                                                         }
2986                                                                 }
2987                                                         }
2988
2989                                                         // Trim start white space
2990                                                         textNode = node.prev;
2991                                                         if (textNode && textNode.type === 3) {
2992                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');
2993
2994                                                                 if (text.length > 0)
2995                                                                         textNode.value = text;
2996                                                                 else
2997                                                                         textNode.remove();
2998                                                         }
2999                                                 }
3000
3001                                                 // Handle empty nodes
3002                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {
3003                                                         if (node.isEmpty(nonEmptyElements)) {
3004                                                                 if (elementRule.paddEmpty)
3005                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';
3006                                                                 else {
3007                                                                         // Leave nodes that have a name like <a name="name">
3008                                                                         if (!node.attributes.map.name) {
3009                                                                                 tempNode = node.parent;
3010                                                                                 node.empty().remove();
3011                                                                                 node = tempNode;
3012                                                                                 return;
3013                                                                         }
3014                                                                 }
3015                                                         }
3016                                                 }
3017
3018                                                 node = node.parent;
3019                                         }
3020                                 }
3021                         }, schema);
3022
3023                         rootNode = node = new Node(args.context || settings.root_name, 11);
3024
3025                         parser.parse(html);
3026
3027                         // Fix invalid children or report invalid children in a contextual parsing
3028                         if (validate && invalidChildren.length) {
3029                                 if (!args.context)
3030                                         fixInvalidChildren(invalidChildren);
3031                                 else
3032                                         args.invalid = true;
3033                         }
3034
3035                         // Wrap nodes in the root into block elements if the root is body
3036                         if (rootBlockName && rootNode.name == 'body')
3037                                 addRootBlocks();
3038
3039                         // Run filters only when the contents is valid
3040                         if (!args.invalid) {
3041                                 // Run node filters
3042                                 for (name in matchedNodes) {
3043                                         list = nodeFilters[name];
3044                                         nodes = matchedNodes[name];
3045
3046                                         // Remove already removed children
3047                                         fi = nodes.length;
3048                                         while (fi--) {
3049                                                 if (!nodes[fi].parent)
3050                                                         nodes.splice(fi, 1);
3051                                         }
3052
3053                                         for (i = 0, l = list.length; i < l; i++)
3054                                                 list[i](nodes, name, args);
3055                                 }
3056
3057                                 // Run attribute filters
3058                                 for (i = 0, l = attributeFilters.length; i < l; i++) {
3059                                         list = attributeFilters[i];
3060
3061                                         if (list.name in matchedAttributes) {
3062                                                 nodes = matchedAttributes[list.name];
3063
3064                                                 // Remove already removed children
3065                                                 fi = nodes.length;
3066                                                 while (fi--) {
3067                                                         if (!nodes[fi].parent)
3068                                                                 nodes.splice(fi, 1);
3069                                                 }
3070
3071                                                 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
3072                                                         list.callbacks[fi](nodes, list.name, args);
3073                                         }
3074                                 }
3075                         }
3076
3077                         return rootNode;
3078                 };
3079
3080                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
3081                 // make it possible to place the caret inside empty blocks. This logic tries to remove
3082                 // these elements and keep br elements that where intended to be there intact
3083                 if (settings.remove_trailing_brs) {
3084                         self.addNodeFilter('br', function(nodes, name) {
3085                                 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
3086                                         nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
3087
3088                                 // Remove brs from body element as well
3089                                 blockElements.body = 1;
3090
3091                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
3092                                 for (i = 0; i < l; i++) {
3093                                         node = nodes[i];
3094                                         parent = node.parent;
3095
3096                                         if (blockElements[node.parent.name] && node === parent.lastChild) {
3097                                                 // Loop all nodes to the right of the current node and check for other BR elements
3098                                                 // excluding bookmarks since they are invisible
3099                                                 prev = node.prev;
3100                                                 while (prev) {
3101                                                         prevName = prev.name;
3102
3103                                                         // Ignore bookmarks
3104                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
3105                                                                 // Found a non BR element
3106                                                                 if (prevName !== "br")
3107                                                                         break;
3108         
3109                                                                 // Found another br it's a <br><br> structure then don't remove anything
3110                                                                 if (prevName === 'br') {
3111                                                                         node = null;
3112                                                                         break;
3113                                                                 }
3114                                                         }
3115
3116                                                         prev = prev.prev;
3117                                                 }
3118
3119                                                 if (node) {
3120                                                         node.remove();
3121
3122                                                         // Is the parent to be considered empty after we removed the BR
3123                                                         if (parent.isEmpty(nonEmptyElements)) {
3124                                                                 elementRule = schema.getElementRule(parent.name);
3125
3126                                                                 // Remove or padd the element depending on schema rule
3127                                                                 if (elementRule.removeEmpty)
3128                                                                         parent.remove();
3129                                                                 else if (elementRule.paddEmpty) 
3130                                                                         parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
3131                                                         }
3132                                                 }
3133                                         }
3134                                 }
3135                         });
3136                 }
3137         }
3138 })(tinymce);
3139
3140 tinymce.html.Writer = function(settings) {
3141         var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
3142
3143         settings = settings || {};
3144         indent = settings.indent;
3145         indentBefore = tinymce.makeMap(settings.indent_before || '');
3146         indentAfter = tinymce.makeMap(settings.indent_after || '');
3147         encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
3148         htmlOutput = settings.element_format == "html";
3149
3150         return {
3151                 start: function(name, attrs, empty) {
3152                         var i, l, attr, value;
3153
3154                         if (indent && indentBefore[name] && html.length > 0) {
3155                                 value = html[html.length - 1];
3156
3157                                 if (value.length > 0 && value !== '\n')
3158                                         html.push('\n');
3159                         }
3160
3161                         html.push('<', name);
3162
3163                         if (attrs) {
3164                                 for (i = 0, l = attrs.length; i < l; i++) {
3165                                         attr = attrs[i];
3166                                         html.push(' ', attr.name, '="', encode(attr.value, true), '"');
3167                                 }
3168                         }
3169
3170                         if (!empty || htmlOutput)
3171                                 html[html.length] = '>';
3172                         else
3173                                 html[html.length] = ' />';
3174
3175                         if (empty && indent && indentAfter[name] && html.length > 0) {
3176                                 value = html[html.length - 1];
3177
3178                                 if (value.length > 0 && value !== '\n')
3179                                         html.push('\n');
3180                         }
3181                 },
3182
3183                 end: function(name) {
3184                         var value;
3185
3186                         /*if (indent && indentBefore[name] && html.length > 0) {
3187                                 value = html[html.length - 1];
3188
3189                                 if (value.length > 0 && value !== '\n')
3190                                         html.push('\n');
3191                         }*/
3192
3193                         html.push('</', name, '>');
3194
3195                         if (indent && indentAfter[name] && html.length > 0) {
3196                                 value = html[html.length - 1];
3197
3198                                 if (value.length > 0 && value !== '\n')
3199                                         html.push('\n');
3200                         }
3201                 },
3202
3203                 text: function(text, raw) {
3204                         if (text.length > 0)
3205                                 html[html.length] = raw ? text : encode(text);
3206                 },
3207
3208                 cdata: function(text) {
3209                         html.push('<![CDATA[', text, ']]>');
3210                 },
3211
3212                 comment: function(text) {
3213                         html.push('<!--', text, '-->');
3214                 },
3215
3216                 pi: function(name, text) {
3217                         if (text)
3218                                 html.push('<?', name, ' ', text, '?>');
3219                         else
3220                                 html.push('<?', name, '?>');
3221
3222                         if (indent)
3223                                 html.push('\n');
3224                 },
3225
3226                 doctype: function(text) {
3227                         html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
3228                 },
3229
3230                 reset: function() {
3231                         html.length = 0;
3232                 },
3233
3234                 getContent: function() {
3235                         return html.join('').replace(/\n$/, '');
3236                 }
3237         };
3238 };
3239
3240 (function(tinymce) {
3241         tinymce.html.Serializer = function(settings, schema) {
3242                 var self = this, writer = new tinymce.html.Writer(settings);
3243
3244                 settings = settings || {};
3245                 settings.validate = "validate" in settings ? settings.validate : true;
3246
3247                 self.schema = schema = schema || new tinymce.html.Schema();
3248                 self.writer = writer;
3249
3250                 self.serialize = function(node) {
3251                         var handlers, validate;
3252
3253                         validate = settings.validate;
3254
3255                         handlers = {
3256                                 // #text
3257                                 3: function(node, raw) {
3258                                         writer.text(node.value, node.raw);
3259                                 },
3260
3261                                 // #comment
3262                                 8: function(node) {
3263                                         writer.comment(node.value);
3264                                 },
3265
3266                                 // Processing instruction
3267                                 7: function(node) {
3268                                         writer.pi(node.name, node.value);
3269                                 },
3270
3271                                 // Doctype
3272                                 10: function(node) {
3273                                         writer.doctype(node.value);
3274                                 },
3275
3276                                 // CDATA
3277                                 4: function(node) {
3278                                         writer.cdata(node.value);
3279                                 },
3280
3281                                 // Document fragment
3282                                 11: function(node) {
3283                                         if ((node = node.firstChild)) {
3284                                                 do {
3285                                                         walk(node);
3286                                                 } while (node = node.next);
3287                                         }
3288                                 }
3289                         };
3290
3291                         writer.reset();
3292
3293                         function walk(node) {
3294                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
3295
3296                                 if (!handler) {
3297                                         name = node.name;
3298                                         isEmpty = node.shortEnded;
3299                                         attrs = node.attributes;
3300
3301                                         // Sort attributes
3302                                         if (validate && attrs && attrs.length > 1) {
3303                                                 sortedAttrs = [];
3304                                                 sortedAttrs.map = {};
3305
3306                                                 elementRule = schema.getElementRule(node.name);
3307                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
3308                                                         attrName = elementRule.attributesOrder[i];
3309
3310                                                         if (attrName in attrs.map) {
3311                                                                 attrValue = attrs.map[attrName];
3312                                                                 sortedAttrs.map[attrName] = attrValue;
3313                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3314                                                         }
3315                                                 }
3316
3317                                                 for (i = 0, l = attrs.length; i < l; i++) {
3318                                                         attrName = attrs[i].name;
3319
3320                                                         if (!(attrName in sortedAttrs.map)) {
3321                                                                 attrValue = attrs.map[attrName];
3322                                                                 sortedAttrs.map[attrName] = attrValue;
3323                                                                 sortedAttrs.push({name: attrName, value: attrValue});
3324                                                         }
3325                                                 }
3326
3327                                                 attrs = sortedAttrs;
3328                                         }
3329
3330                                         writer.start(node.name, attrs, isEmpty);
3331
3332                                         if (!isEmpty) {
3333                                                 if ((node = node.firstChild)) {
3334                                                         do {
3335                                                                 walk(node);
3336                                                         } while (node = node.next);
3337                                                 }
3338
3339                                                 writer.end(name);
3340                                         }
3341                                 } else
3342                                         handler(node);
3343                         }
3344
3345                         // Serialize element and treat all non elements as fragments
3346                         if (node.type == 1 && !settings.inner)
3347                                 walk(node);
3348                         else
3349                                 handlers[11](node);
3350
3351                         return writer.getContent();
3352                 };
3353         }
3354 })(tinymce);
3355
3356 (function(tinymce) {
3357         // Shorten names
3358         var each = tinymce.each,
3359                 is = tinymce.is,
3360                 isWebKit = tinymce.isWebKit,
3361                 isIE = tinymce.isIE,
3362                 Entities = tinymce.html.Entities,
3363                 simpleSelectorRe = /^([a-z0-9],?)+$/i,
3364                 blockElementsMap = tinymce.html.Schema.blockElementsMap,
3365                 whiteSpaceRegExp = /^[ \t\r\n]*$/;
3366
3367         tinymce.create('tinymce.dom.DOMUtils', {
3368                 doc : null,
3369                 root : null,
3370                 files : null,
3371                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
3372                 props : {
3373                         "for" : "htmlFor",
3374                         "class" : "className",
3375                         className : "className",
3376                         checked : "checked",
3377                         disabled : "disabled",
3378                         maxlength : "maxLength",
3379                         readonly : "readOnly",
3380                         selected : "selected",
3381                         value : "value",
3382                         id : "id",
3383                         name : "name",
3384                         type : "type"
3385                 },
3386
3387                 DOMUtils : function(d, s) {
3388                         var t = this, globalStyle, name;
3389
3390                         t.doc = d;
3391                         t.win = window;
3392                         t.files = {};
3393                         t.cssFlicker = false;
3394                         t.counter = 0;
3395                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;
3396                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
3397                         t.hasOuterHTML = "outerHTML" in d.createElement("a");
3398
3399                         t.settings = s = tinymce.extend({
3400                                 keep_values : false,
3401                                 hex_colors : 1
3402                         }, s);
3403                         
3404                         t.schema = s.schema;
3405                         t.styles = new tinymce.html.Styles({
3406                                 url_converter : s.url_converter,
3407                                 url_converter_scope : s.url_converter_scope
3408                         }, s.schema);
3409
3410                         // Fix IE6SP2 flicker and check it failed for pre SP2
3411                         if (tinymce.isIE6) {
3412                                 try {
3413                                         d.execCommand('BackgroundImageCache', false, true);
3414                                 } catch (e) {
3415                                         t.cssFlicker = true;
3416                                 }
3417                         }
3418
3419                         if (isIE && s.schema) {
3420                                 // Add missing HTML 4/5 elements to IE
3421                                 ('abbr article aside audio canvas ' +
3422                                 'details figcaption figure footer ' +
3423                                 'header hgroup mark menu meter nav ' +
3424                                 'output progress section summary ' +
3425                                 'time video').replace(/\w+/g, function(name) {
3426                                         d.createElement(name);
3427                                 });
3428
3429                                 // Create all custom elements
3430                                 for (name in s.schema.getCustomElements()) {
3431                                         d.createElement(name);
3432                                 }
3433                         }
3434
3435                         tinymce.addUnload(t.destroy, t);
3436                 },
3437
3438                 getRoot : function() {
3439                         var t = this, s = t.settings;
3440
3441                         return (s && t.get(s.root_element)) || t.doc.body;
3442                 },
3443
3444                 getViewPort : function(w) {
3445                         var d, b;
3446
3447                         w = !w ? this.win : w;
3448                         d = w.document;
3449                         b = this.boxModel ? d.documentElement : d.body;
3450
3451                         // Returns viewport size excluding scrollbars
3452                         return {
3453                                 x : w.pageXOffset || b.scrollLeft,
3454                                 y : w.pageYOffset || b.scrollTop,
3455                                 w : w.innerWidth || b.clientWidth,
3456                                 h : w.innerHeight || b.clientHeight
3457                         };
3458                 },
3459
3460                 getRect : function(e) {
3461                         var p, t = this, sr;
3462
3463                         e = t.get(e);
3464                         p = t.getPos(e);
3465                         sr = t.getSize(e);
3466
3467                         return {
3468                                 x : p.x,
3469                                 y : p.y,
3470                                 w : sr.w,
3471                                 h : sr.h
3472                         };
3473                 },
3474
3475                 getSize : function(e) {
3476                         var t = this, w, h;
3477
3478                         e = t.get(e);
3479                         w = t.getStyle(e, 'width');
3480                         h = t.getStyle(e, 'height');
3481
3482                         // Non pixel value, then force offset/clientWidth
3483                         if (w.indexOf('px') === -1)
3484                                 w = 0;
3485
3486                         // Non pixel value, then force offset/clientWidth
3487                         if (h.indexOf('px') === -1)
3488                                 h = 0;
3489
3490                         return {
3491                                 w : parseInt(w) || e.offsetWidth || e.clientWidth,
3492                                 h : parseInt(h) || e.offsetHeight || e.clientHeight
3493                         };
3494                 },
3495
3496                 getParent : function(n, f, r) {
3497                         return this.getParents(n, f, r, false);
3498                 },
3499
3500                 getParents : function(n, f, r, c) {
3501                         var t = this, na, se = t.settings, o = [];
3502
3503                         n = t.get(n);
3504                         c = c === undefined;
3505
3506                         if (se.strict_root)
3507                                 r = r || t.getRoot();
3508
3509                         // Wrap node name as func
3510                         if (is(f, 'string')) {
3511                                 na = f;
3512
3513                                 if (f === '*') {
3514                                         f = function(n) {return n.nodeType == 1;};
3515                                 } else {
3516                                         f = function(n) {
3517                                                 return t.is(n, na);
3518                                         };
3519                                 }
3520                         }
3521
3522                         while (n) {
3523                                 if (n == r || !n.nodeType || n.nodeType === 9)
3524                                         break;
3525
3526                                 if (!f || f(n)) {
3527                                         if (c)
3528                                                 o.push(n);
3529                                         else
3530                                                 return n;
3531                                 }
3532
3533                                 n = n.parentNode;
3534                         }
3535
3536                         return c ? o : null;
3537                 },
3538
3539                 get : function(e) {
3540                         var n;
3541
3542                         if (e && this.doc && typeof(e) == 'string') {
3543                                 n = e;
3544                                 e = this.doc.getElementById(e);
3545
3546                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
3547                                 if (e && e.id !== n)
3548                                         return this.doc.getElementsByName(n)[1];
3549                         }
3550
3551                         return e;
3552                 },
3553
3554                 getNext : function(node, selector) {
3555                         return this._findSib(node, selector, 'nextSibling');
3556                 },
3557
3558                 getPrev : function(node, selector) {
3559                         return this._findSib(node, selector, 'previousSibling');
3560                 },
3561
3562
3563                 select : function(pa, s) {
3564                         var t = this;
3565
3566                         return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
3567                 },
3568
3569                 is : function(n, selector) {
3570                         var i;
3571
3572                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
3573                         if (n.length === undefined) {
3574                                 // Simple all selector
3575                                 if (selector === '*')
3576                                         return n.nodeType == 1;
3577
3578                                 // Simple selector just elements
3579                                 if (simpleSelectorRe.test(selector)) {
3580                                         selector = selector.toLowerCase().split(/,/);
3581                                         n = n.nodeName.toLowerCase();
3582
3583                                         for (i = selector.length - 1; i >= 0; i--) {
3584                                                 if (selector[i] == n)
3585                                                         return true;
3586                                         }
3587
3588                                         return false;
3589                                 }
3590                         }
3591
3592                         return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
3593                 },
3594
3595
3596                 add : function(p, n, a, h, c) {
3597                         var t = this;
3598
3599                         return this.run(p, function(p) {
3600                                 var e, k;
3601
3602                                 e = is(n, 'string') ? t.doc.createElement(n) : n;
3603                                 t.setAttribs(e, a);
3604
3605                                 if (h) {
3606                                         if (h.nodeType)
3607                                                 e.appendChild(h);
3608                                         else
3609                                                 t.setHTML(e, h);
3610                                 }
3611
3612                                 return !c ? p.appendChild(e) : e;
3613                         });
3614                 },
3615
3616                 create : function(n, a, h) {
3617                         return this.add(this.doc.createElement(n), n, a, h, 1);
3618                 },
3619
3620                 createHTML : function(n, a, h) {
3621                         var o = '', t = this, k;
3622
3623                         o += '<' + n;
3624
3625                         for (k in a) {
3626                                 if (a.hasOwnProperty(k))
3627                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';
3628                         }
3629
3630                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
3631                         if (typeof(h) != "undefined")
3632                                 return o + '>' + h + '</' + n + '>';
3633
3634                         return o + ' />';
3635                 },
3636
3637                 remove : function(node, keep_children) {
3638                         return this.run(node, function(node) {
3639                                 var child, parent = node.parentNode;
3640
3641                                 if (!parent)
3642                                         return null;
3643
3644                                 if (keep_children) {
3645                                         while (child = node.firstChild) {
3646                                                 // IE 8 will crash if you don't remove completely empty text nodes
3647                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
3648                                                         parent.insertBefore(child, node);
3649                                                 else
3650                                                         node.removeChild(child);
3651                                         }
3652                                 }
3653
3654                                 return parent.removeChild(node);
3655                         });
3656                 },
3657
3658                 setStyle : function(n, na, v) {
3659                         var t = this;
3660
3661                         return t.run(n, function(e) {
3662                                 var s, i;
3663
3664                                 s = e.style;
3665
3666                                 // Camelcase it, if needed
3667                                 na = na.replace(/-(\D)/g, function(a, b){
3668                                         return b.toUpperCase();
3669                                 });
3670
3671                                 // Default px suffix on these
3672                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
3673                                         v += 'px';
3674
3675                                 switch (na) {
3676                                         case 'opacity':
3677                                                 // IE specific opacity
3678                                                 if (isIE) {
3679                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
3680
3681                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)
3682                                                                 s.display = 'inline-block';
3683                                                 }
3684
3685                                                 // Fix for older browsers
3686                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
3687                                                 break;
3688
3689                                         case 'float':
3690                                                 isIE ? s.styleFloat = v : s.cssFloat = v;
3691                                                 break;
3692                                         
3693                                         default:
3694                                                 s[na] = v || '';
3695                                 }
3696
3697                                 // Force update of the style data
3698                                 if (t.settings.update_styles)
3699                                         t.setAttrib(e, 'data-mce-style');
3700                         });
3701                 },
3702
3703                 getStyle : function(n, na, c) {
3704                         n = this.get(n);
3705
3706                         if (!n)
3707                                 return;
3708
3709                         // Gecko
3710                         if (this.doc.defaultView && c) {
3711                                 // Remove camelcase
3712                                 na = na.replace(/[A-Z]/g, function(a){
3713                                         return '-' + a;
3714                                 });
3715
3716                                 try {
3717                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
3718                                 } catch (ex) {
3719                                         // Old safari might fail
3720                                         return null;
3721                                 }
3722                         }
3723
3724                         // Camelcase it, if needed
3725                         na = na.replace(/-(\D)/g, function(a, b){
3726                                 return b.toUpperCase();
3727                         });
3728
3729                         if (na == 'float')
3730                                 na = isIE ? 'styleFloat' : 'cssFloat';
3731
3732                         // IE & Opera
3733                         if (n.currentStyle && c)
3734                                 return n.currentStyle[na];
3735
3736                         return n.style ? n.style[na] : undefined;
3737                 },
3738
3739                 setStyles : function(e, o) {
3740                         var t = this, s = t.settings, ol;
3741
3742                         ol = s.update_styles;
3743                         s.update_styles = 0;
3744
3745                         each(o, function(v, n) {
3746                                 t.setStyle(e, n, v);
3747                         });
3748
3749                         // Update style info
3750                         s.update_styles = ol;
3751                         if (s.update_styles)
3752                                 t.setAttrib(e, s.cssText);
3753                 },
3754
3755                 removeAllAttribs: function(e) {
3756                         return this.run(e, function(e) {
3757                                 var i, attrs = e.attributes;
3758                                 for (i = attrs.length - 1; i >= 0; i--) {
3759                                         e.removeAttributeNode(attrs.item(i));
3760                                 }
3761                         });
3762                 },
3763
3764                 setAttrib : function(e, n, v) {
3765                         var t = this;
3766
3767                         // Whats the point
3768                         if (!e || !n)
3769                                 return;
3770
3771                         // Strict XML mode
3772                         if (t.settings.strict)
3773                                 n = n.toLowerCase();
3774
3775                         return this.run(e, function(e) {
3776                                 var s = t.settings;
3777
3778                                 switch (n) {
3779                                         case "style":
3780                                                 if (!is(v, 'string')) {
3781                                                         each(v, function(v, n) {
3782                                                                 t.setStyle(e, n, v);
3783                                                         });
3784
3785                                                         return;
3786                                                 }
3787
3788                                                 // No mce_style for elements with these since they might get resized by the user
3789                                                 if (s.keep_values) {
3790                                                         if (v && !t._isRes(v))
3791                                                                 e.setAttribute('data-mce-style', v, 2);
3792                                                         else
3793                                                                 e.removeAttribute('data-mce-style', 2);
3794                                                 }
3795
3796                                                 e.style.cssText = v;
3797                                                 break;
3798
3799                                         case "class":
3800                                                 e.className = v || ''; // Fix IE null bug
3801                                                 break;
3802
3803                                         case "src":
3804                                         case "href":
3805                                                 if (s.keep_values) {
3806                                                         if (s.url_converter)
3807                                                                 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
3808
3809                                                         t.setAttrib(e, 'data-mce-' + n, v, 2);
3810                                                 }
3811
3812                                                 break;
3813
3814                                         case "shape":
3815                                                 e.setAttribute('data-mce-style', v);
3816                                                 break;
3817                                 }
3818
3819                                 if (is(v) && v !== null && v.length !== 0)
3820                                         e.setAttribute(n, '' + v, 2);
3821                                 else
3822                                         e.removeAttribute(n, 2);
3823                         });
3824                 },
3825
3826                 setAttribs : function(e, o) {
3827                         var t = this;
3828
3829                         return this.run(e, function(e) {
3830                                 each(o, function(v, n) {
3831                                         t.setAttrib(e, n, v);
3832                                 });
3833                         });
3834                 },
3835
3836                 getAttrib : function(e, n, dv) {
3837                         var v, t = this, undef;
3838
3839                         e = t.get(e);
3840
3841                         if (!e || e.nodeType !== 1)
3842                                 return dv === undef ? false : dv;
3843
3844                         if (!is(dv))
3845                                 dv = '';
3846
3847                         // Try the mce variant for these
3848                         if (/^(src|href|style|coords|shape)$/.test(n)) {
3849                                 v = e.getAttribute("data-mce-" + n);
3850
3851                                 if (v)
3852                                         return v;
3853                         }
3854
3855                         if (isIE && t.props[n]) {
3856                                 v = e[t.props[n]];
3857                                 v = v && v.nodeValue ? v.nodeValue : v;
3858                         }
3859
3860                         if (!v)
3861                                 v = e.getAttribute(n, 2);
3862
3863                         // Check boolean attribs
3864                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
3865                                 if (e[t.props[n]] === true && v === '')
3866                                         return n;
3867
3868                                 return v ? n : '';
3869                         }
3870
3871                         // Inner input elements will override attributes on form elements
3872                         if (e.nodeName === "FORM" && e.getAttributeNode(n))
3873                                 return e.getAttributeNode(n).nodeValue;
3874
3875                         if (n === 'style') {
3876                                 v = v || e.style.cssText;
3877
3878                                 if (v) {
3879                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);
3880
3881                                         if (t.settings.keep_values && !t._isRes(v))
3882                                                 e.setAttribute('data-mce-style', v);
3883                                 }
3884                         }
3885
3886                         // Remove Apple and WebKit stuff
3887                         if (isWebKit && n === "class" && v)
3888                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
3889
3890                         // Handle IE issues
3891                         if (isIE) {
3892                                 switch (n) {
3893                                         case 'rowspan':
3894                                         case 'colspan':
3895                                                 // IE returns 1 as default value
3896                                                 if (v === 1)
3897                                                         v = '';
3898
3899                                                 break;
3900
3901                                         case 'size':
3902                                                 // IE returns +0 as default value for size
3903                                                 if (v === '+0' || v === 20 || v === 0)
3904                                                         v = '';
3905
3906                                                 break;
3907
3908                                         case 'width':
3909                                         case 'height':
3910                                         case 'vspace':
3911                                         case 'checked':
3912                                         case 'disabled':
3913                                         case 'readonly':
3914                                                 if (v === 0)
3915                                                         v = '';
3916
3917                                                 break;
3918
3919                                         case 'hspace':
3920                                                 // IE returns -1 as default value
3921                                                 if (v === -1)
3922                                                         v = '';
3923
3924                                                 break;
3925
3926                                         case 'maxlength':
3927                                         case 'tabindex':
3928                                                 // IE returns default value
3929                                                 if (v === 32768 || v === 2147483647 || v === '32768')
3930                                                         v = '';
3931
3932                                                 break;
3933
3934                                         case 'multiple':
3935                                         case 'compact':
3936                                         case 'noshade':
3937                                         case 'nowrap':
3938                                                 if (v === 65535)
3939                                                         return n;
3940
3941                                                 return dv;
3942
3943                                         case 'shape':
3944                                                 v = v.toLowerCase();
3945                                                 break;
3946
3947                                         default:
3948                                                 // IE has odd anonymous function for event attributes
3949                                                 if (n.indexOf('on') === 0 && v)
3950                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
3951                                 }
3952                         }
3953
3954                         return (v !== undef && v !== null && v !== '') ? '' + v : dv;
3955                 },
3956
3957                 getPos : function(n, ro) {
3958                         var t = this, x = 0, y = 0, e, d = t.doc, r;
3959
3960                         n = t.get(n);
3961                         ro = ro || d.body;
3962
3963                         if (n) {
3964                                 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
3965                                 if (n.getBoundingClientRect) {
3966                                         n = n.getBoundingClientRect();
3967                                         e = t.boxModel ? d.documentElement : d.body;
3968
3969                                         // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
3970                                         // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
3971                                         x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
3972                                         y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
3973
3974                                         return {x : x, y : y};
3975                                 }
3976
3977                                 r = n;
3978                                 while (r && r != ro && r.nodeType) {
3979                                         x += r.offsetLeft || 0;
3980                                         y += r.offsetTop || 0;
3981                                         r = r.offsetParent;
3982                                 }
3983
3984                                 r = n.parentNode;
3985                                 while (r && r != ro && r.nodeType) {
3986                                         x -= r.scrollLeft || 0;
3987                                         y -= r.scrollTop || 0;
3988                                         r = r.parentNode;
3989                                 }
3990                         }
3991
3992                         return {x : x, y : y};
3993                 },
3994
3995                 parseStyle : function(st) {
3996                         return this.styles.parse(st);
3997                 },
3998
3999                 serializeStyle : function(o, name) {
4000                         return this.styles.serialize(o, name);
4001                 },
4002
4003                 loadCSS : function(u) {
4004                         var t = this, d = t.doc, head;
4005
4006                         if (!u)
4007                                 u = '';
4008
4009                         head = t.select('head')[0];
4010
4011                         each(u.split(','), function(u) {
4012                                 var link;
4013
4014                                 if (t.files[u])
4015                                         return;
4016
4017                                 t.files[u] = true;
4018                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
4019
4020                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
4021                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
4022                                 // It's ugly but it seems to work fine.
4023                                 if (isIE && d.documentMode && d.recalc) {
4024                                         link.onload = function() {
4025                                                 if (d.recalc)
4026                                                         d.recalc();
4027
4028                                                 link.onload = null;
4029                                         };
4030                                 }
4031
4032                                 head.appendChild(link);
4033                         });
4034                 },
4035
4036                 addClass : function(e, c) {
4037                         return this.run(e, function(e) {
4038                                 var o;
4039
4040                                 if (!c)
4041                                         return 0;
4042
4043                                 if (this.hasClass(e, c))
4044                                         return e.className;
4045
4046                                 o = this.removeClass(e, c);
4047
4048                                 return e.className = (o != '' ? (o + ' ') : '') + c;
4049                         });
4050                 },
4051
4052                 removeClass : function(e, c) {
4053                         var t = this, re;
4054
4055                         return t.run(e, function(e) {
4056                                 var v;
4057
4058                                 if (t.hasClass(e, c)) {
4059                                         if (!re)
4060                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
4061
4062                                         v = e.className.replace(re, ' ');
4063                                         v = tinymce.trim(v != ' ' ? v : '');
4064
4065                                         e.className = v;
4066
4067                                         // Empty class attr
4068                                         if (!v) {
4069                                                 e.removeAttribute('class');
4070                                                 e.removeAttribute('className');
4071                                         }
4072
4073                                         return v;
4074                                 }
4075
4076                                 return e.className;
4077                         });
4078                 },
4079
4080                 hasClass : function(n, c) {
4081                         n = this.get(n);
4082
4083                         if (!n || !c)
4084                                 return false;
4085
4086                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
4087                 },
4088
4089                 show : function(e) {
4090                         return this.setStyle(e, 'display', 'block');
4091                 },
4092
4093                 hide : function(e) {
4094                         return this.setStyle(e, 'display', 'none');
4095                 },
4096
4097                 isHidden : function(e) {
4098                         e = this.get(e);
4099
4100                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
4101                 },
4102
4103                 uniqueId : function(p) {
4104                         return (!p ? 'mce_' : p) + (this.counter++);
4105                 },
4106
4107                 setHTML : function(element, html) {
4108                         var self = this;
4109
4110                         return self.run(element, function(element) {
4111                                 if (isIE) {
4112                                         // Remove all child nodes, IE keeps empty text nodes in DOM
4113                                         while (element.firstChild)
4114                                                 element.removeChild(element.firstChild);
4115
4116                                         try {
4117                                                 // IE will remove comments from the beginning
4118                                                 // unless you padd the contents with something
4119                                                 element.innerHTML = '<br />' + html;
4120                                                 element.removeChild(element.firstChild);
4121                                         } catch (ex) {
4122                                                 // 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
4123                                                 // This seems to fix this problem
4124
4125                                                 // Create new div with HTML contents and a BR infront to keep comments
4126                                                 element = self.create('div');
4127                                                 element.innerHTML = '<br />' + html;
4128
4129                                                 // Add all children from div to target
4130                                                 each (element.childNodes, function(node, i) {
4131                                                         // Skip br element
4132                                                         if (i)
4133                                                                 element.appendChild(node);
4134                                                 });
4135                                         }
4136                                 } else
4137                                         element.innerHTML = html;
4138
4139                                 return html;
4140                         });
4141                 },
4142
4143                 getOuterHTML : function(elm) {
4144                         var doc, self = this;
4145
4146                         elm = self.get(elm);
4147
4148                         if (!elm)
4149                                 return null;
4150
4151                         if (elm.nodeType === 1 && self.hasOuterHTML)
4152                                 return elm.outerHTML;
4153
4154                         doc = (elm.ownerDocument || self.doc).createElement("body");
4155                         doc.appendChild(elm.cloneNode(true));
4156
4157                         return doc.innerHTML;
4158                 },
4159
4160                 setOuterHTML : function(e, h, d) {
4161                         var t = this;
4162
4163                         function setHTML(e, h, d) {
4164                                 var n, tp;
4165
4166                                 tp = d.createElement("body");
4167                                 tp.innerHTML = h;
4168
4169                                 n = tp.lastChild;
4170                                 while (n) {
4171                                         t.insertAfter(n.cloneNode(true), e);
4172                                         n = n.previousSibling;
4173                                 }
4174
4175                                 t.remove(e);
4176                         };
4177
4178                         return this.run(e, function(e) {
4179                                 e = t.get(e);
4180
4181                                 // Only set HTML on elements
4182                                 if (e.nodeType == 1) {
4183                                         d = d || e.ownerDocument || t.doc;
4184
4185                                         if (isIE) {
4186                                                 try {
4187                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error
4188                                                         if (isIE && e.nodeType == 1)
4189                                                                 e.outerHTML = h;
4190                                                         else
4191                                                                 setHTML(e, h, d);
4192                                                 } catch (ex) {
4193                                                         // Fix for unknown runtime error
4194                                                         setHTML(e, h, d);
4195                                                 }
4196                                         } else
4197                                                 setHTML(e, h, d);
4198                                 }
4199                         });
4200                 },
4201
4202                 decode : Entities.decode,
4203
4204                 encode : Entities.encodeAllRaw,
4205
4206                 insertAfter : function(node, reference_node) {
4207                         reference_node = this.get(reference_node);
4208
4209                         return this.run(node, function(node) {
4210                                 var parent, nextSibling;
4211
4212                                 parent = reference_node.parentNode;
4213                                 nextSibling = reference_node.nextSibling;
4214
4215                                 if (nextSibling)
4216                                         parent.insertBefore(node, nextSibling);
4217                                 else
4218                                         parent.appendChild(node);
4219
4220                                 return node;
4221                         });
4222                 },
4223
4224                 isBlock : function(node) {
4225                         var type = node.nodeType;
4226
4227                         // If it's a node then check the type and use the nodeName
4228                         if (type)
4229                                 return !!(type === 1 && blockElementsMap[node.nodeName]);
4230
4231                         return !!blockElementsMap[node];
4232                 },
4233
4234                 replace : function(n, o, k) {
4235                         var t = this;
4236
4237                         if (is(o, 'array'))
4238                                 n = n.cloneNode(true);
4239
4240                         return t.run(o, function(o) {
4241                                 if (k) {
4242                                         each(tinymce.grep(o.childNodes), function(c) {
4243                                                 n.appendChild(c);
4244                                         });
4245                                 }
4246
4247                                 return o.parentNode.replaceChild(n, o);
4248                         });
4249                 },
4250
4251                 rename : function(elm, name) {
4252                         var t = this, newElm;
4253
4254                         if (elm.nodeName != name.toUpperCase()) {
4255                                 // Rename block element
4256                                 newElm = t.create(name);
4257
4258                                 // Copy attribs to new block
4259                                 each(t.getAttribs(elm), function(attr_node) {
4260                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
4261                                 });
4262
4263                                 // Replace block
4264                                 t.replace(newElm, elm, 1);
4265                         }
4266
4267                         return newElm || elm;
4268                 },
4269
4270                 findCommonAncestor : function(a, b) {
4271                         var ps = a, pe;
4272
4273                         while (ps) {
4274                                 pe = b;
4275
4276                                 while (pe && ps != pe)
4277                                         pe = pe.parentNode;
4278
4279                                 if (ps == pe)
4280                                         break;
4281
4282                                 ps = ps.parentNode;
4283                         }
4284
4285                         if (!ps && a.ownerDocument)
4286                                 return a.ownerDocument.documentElement;
4287
4288                         return ps;
4289                 },
4290
4291                 toHex : function(s) {
4292                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
4293
4294                         function hex(s) {
4295                                 s = parseInt(s).toString(16);
4296
4297                                 return s.length > 1 ? s : '0' + s; // 0 -> 00
4298                         };
4299
4300                         if (c) {
4301                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
4302
4303                                 return s;
4304                         }
4305
4306                         return s;
4307                 },
4308
4309                 getClasses : function() {
4310                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
4311
4312                         if (t.classes)
4313                                 return t.classes;
4314
4315                         function addClasses(s) {
4316                                 // IE style imports
4317                                 each(s.imports, function(r) {
4318                                         addClasses(r);
4319                                 });
4320
4321                                 each(s.cssRules || s.rules, function(r) {
4322                                         // Real type or fake it on IE
4323                                         switch (r.type || 1) {
4324                                                 // Rule
4325                                                 case 1:
4326                                                         if (r.selectorText) {
4327                                                                 each(r.selectorText.split(','), function(v) {
4328                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");
4329
4330                                                                         // Is internal or it doesn't contain a class
4331                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
4332                                                                                 return;
4333
4334                                                                         // Remove everything but class name
4335                                                                         ov = v;
4336                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
4337
4338                                                                         // Filter classes
4339                                                                         if (f && !(v = f(v, ov)))
4340                                                                                 return;
4341
4342                                                                         if (!lo[v]) {
4343                                                                                 cl.push({'class' : v});
4344                                                                                 lo[v] = 1;
4345                                                                         }
4346                                                                 });
4347                                                         }
4348                                                         break;
4349
4350                                                 // Import
4351                                                 case 3:
4352                                                         addClasses(r.styleSheet);
4353                                                         break;
4354                                         }
4355                                 });
4356                         };
4357
4358                         try {
4359                                 each(t.doc.styleSheets, addClasses);
4360                         } catch (ex) {
4361                                 // Ignore
4362                         }
4363
4364                         if (cl.length > 0)
4365                                 t.classes = cl;
4366
4367                         return cl;
4368                 },
4369
4370                 run : function(e, f, s) {
4371                         var t = this, o;
4372
4373                         if (t.doc && typeof(e) === 'string')
4374                                 e = t.get(e);
4375
4376                         if (!e)
4377                                 return false;
4378
4379                         s = s || this;
4380                         if (!e.nodeType && (e.length || e.length === 0)) {
4381                                 o = [];
4382
4383                                 each(e, function(e, i) {
4384                                         if (e) {
4385                                                 if (typeof(e) == 'string')
4386                                                         e = t.doc.getElementById(e);
4387
4388                                                 o.push(f.call(s, e, i));
4389                                         }
4390                                 });
4391
4392                                 return o;
4393                         }
4394
4395                         return f.call(s, e);
4396                 },
4397
4398                 getAttribs : function(n) {
4399                         var o;
4400
4401                         n = this.get(n);
4402
4403                         if (!n)
4404                                 return [];
4405
4406                         if (isIE) {
4407                                 o = [];
4408
4409                                 // Object will throw exception in IE
4410                                 if (n.nodeName == 'OBJECT')
4411                                         return n.attributes;
4412
4413                                 // IE doesn't keep the selected attribute if you clone option elements
4414                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
4415                                         o.push({specified : 1, nodeName : 'selected'});
4416
4417                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
4418                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
4419                                         o.push({specified : 1, nodeName : a});
4420                                 });
4421
4422                                 return o;
4423                         }
4424
4425                         return n.attributes;
4426                 },
4427
4428                 isEmpty : function(node, elements) {
4429                         var self = this, i, attributes, type, walker, name;
4430
4431                         node = node.firstChild;
4432                         if (node) {
4433                                 walker = new tinymce.dom.TreeWalker(node);
4434                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
4435
4436                                 do {
4437                                         type = node.nodeType;
4438
4439                                         if (type === 1) {
4440                                                 // Ignore bogus elements
4441                                                 if (node.getAttribute('data-mce-bogus'))
4442                                                         continue;
4443
4444                                                 // Keep empty elements like <img />
4445                                                 if (elements && elements[node.nodeName.toLowerCase()])
4446                                                         return false;
4447
4448                                                 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
4449                                                 attributes = self.getAttribs(node);
4450                                                 i = node.attributes.length;
4451                                                 while (i--) {
4452                                                         name = node.attributes[i].nodeName;
4453                                                         if (name === "name" || name === 'data-mce-bookmark')
4454                                                                 return false;
4455                                                 }
4456                                         }
4457
4458                                         // Keep non whitespace text nodes
4459                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
4460                                                 return false;
4461                                 } while (node = walker.next());
4462                         }
4463
4464                         return true;
4465                 },
4466
4467                 destroy : function(s) {
4468                         var t = this;
4469
4470                         if (t.events)
4471                                 t.events.destroy();
4472
4473                         t.win = t.doc = t.root = t.events = null;
4474
4475                         // Manual destroy then remove unload handler
4476                         if (!s)
4477                                 tinymce.removeUnload(t.destroy);
4478                 },
4479
4480                 createRng : function() {
4481                         var d = this.doc;
4482
4483                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
4484                 },
4485
4486                 nodeIndex : function(node, normalized) {
4487                         var idx = 0, lastNodeType, lastNode, nodeType;
4488
4489                         if (node) {
4490                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
4491                                         nodeType = node.nodeType;
4492
4493                                         // Normalize text nodes
4494                                         if (normalized && nodeType == 3) {
4495                                                 if (nodeType == lastNodeType || !node.nodeValue.length)
4496                                                         continue;
4497                                         }
4498                                         idx++;
4499                                         lastNodeType = nodeType;
4500                                 }
4501                         }
4502
4503                         return idx;
4504                 },
4505
4506                 split : function(pe, e, re) {
4507                         var t = this, r = t.createRng(), bef, aft, pa;
4508
4509                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
4510                         // but we don't want that in our code since it serves no purpose for the end user
4511                         // For example if this is chopped:
4512                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>
4513                         // would produce:
4514                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
4515                         // this function will then trim of empty edges and produce:
4516                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>
4517                         function trim(node) {
4518                                 var i, children = node.childNodes, type = node.nodeType;
4519
4520                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
4521                                         return;
4522
4523                                 for (i = children.length - 1; i >= 0; i--)
4524                                         trim(children[i]);
4525
4526                                 if (type != 9) {
4527                                         // Keep non whitespace text nodes
4528                                         if (type == 3 && node.nodeValue.length > 0) {
4529                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
4530                                                 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
4531                                                         return;
4532                                         } else if (type == 1) {
4533                                                 // If the only child is a bookmark then move it up
4534                                                 children = node.childNodes;
4535                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
4536                                                         node.parentNode.insertBefore(children[0], node);
4537
4538                                                 // Keep non empty elements or img, hr etc
4539                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
4540                                                         return;
4541                                         }
4542
4543                                         t.remove(node);
4544                                 }
4545
4546                                 return node;
4547                         };
4548
4549                         if (pe && e) {
4550                                 // Get before chunk
4551                                 r.setStart(pe.parentNode, t.nodeIndex(pe));
4552                                 r.setEnd(e.parentNode, t.nodeIndex(e));
4553                                 bef = r.extractContents();
4554
4555                                 // Get after chunk
4556                                 r = t.createRng();
4557                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
4558                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
4559                                 aft = r.extractContents();
4560
4561                                 // Insert before chunk
4562                                 pa = pe.parentNode;
4563                                 pa.insertBefore(trim(bef), pe);
4564
4565                                 // Insert middle chunk
4566                                 if (re)
4567                                         pa.replaceChild(re, e);
4568                                 else
4569                                         pa.insertBefore(e, pe);
4570
4571                                 // Insert after chunk
4572                                 pa.insertBefore(trim(aft), pe);
4573                                 t.remove(pe);
4574
4575                                 return re || e;
4576                         }
4577                 },
4578
4579                 bind : function(target, name, func, scope) {
4580                         var t = this;
4581
4582                         if (!t.events)
4583                                 t.events = new tinymce.dom.EventUtils();
4584
4585                         return t.events.add(target, name, func, scope || this);
4586                 },
4587
4588                 unbind : function(target, name, func) {
4589                         var t = this;
4590
4591                         if (!t.events)
4592                                 t.events = new tinymce.dom.EventUtils();
4593
4594                         return t.events.remove(target, name, func);
4595                 },
4596
4597
4598                 _findSib : function(node, selector, name) {
4599                         var t = this, f = selector;
4600
4601                         if (node) {
4602                                 // If expression make a function of it using is
4603                                 if (is(f, 'string')) {
4604                                         f = function(node) {
4605                                                 return t.is(node, selector);
4606                                         };
4607                                 }
4608
4609                                 // Loop all siblings
4610                                 for (node = node[name]; node; node = node[name]) {
4611                                         if (f(node))
4612                                                 return node;
4613                                 }
4614                         }
4615
4616                         return null;
4617                 },
4618
4619                 _isRes : function(c) {
4620                         // Is live resizble element
4621                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
4622                 }
4623
4624                 /*
4625                 walk : function(n, f, s) {
4626                         var d = this.doc, w;
4627
4628                         if (d.createTreeWalker) {
4629                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
4630
4631                                 while ((n = w.nextNode()) != null)
4632                                         f.call(s || this, n);
4633                         } else
4634                                 tinymce.walk(n, f, 'childNodes', s);
4635                 }
4636                 */
4637
4638                 /*
4639                 toRGB : function(s) {
4640                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
4641
4642                         if (c) {
4643                                 // #FFF -> #FFFFFF
4644                                 if (!is(c[3]))
4645                                         c[3] = c[2] = c[1];
4646
4647                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
4648                         }
4649
4650                         return s;
4651                 }
4652                 */
4653         });
4654
4655         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
4656 })(tinymce);
4657
4658 (function(ns) {
4659         // Range constructor
4660         function Range(dom) {
4661                 var t = this,
4662                         doc = dom.doc,
4663                         EXTRACT = 0,
4664                         CLONE = 1,
4665                         DELETE = 2,
4666                         TRUE = true,
4667                         FALSE = false,
4668                         START_OFFSET = 'startOffset',
4669                         START_CONTAINER = 'startContainer',
4670                         END_CONTAINER = 'endContainer',
4671                         END_OFFSET = 'endOffset',
4672                         extend = tinymce.extend,
4673                         nodeIndex = dom.nodeIndex;
4674
4675                 extend(t, {
4676                         // Inital states
4677                         startContainer : doc,
4678                         startOffset : 0,
4679                         endContainer : doc,
4680                         endOffset : 0,
4681                         collapsed : TRUE,
4682                         commonAncestorContainer : doc,
4683
4684                         // Range constants
4685                         START_TO_START : 0,
4686                         START_TO_END : 1,
4687                         END_TO_END : 2,
4688                         END_TO_START : 3,
4689
4690                         // Public methods
4691                         setStart : setStart,
4692                         setEnd : setEnd,
4693                         setStartBefore : setStartBefore,
4694                         setStartAfter : setStartAfter,
4695                         setEndBefore : setEndBefore,
4696                         setEndAfter : setEndAfter,
4697                         collapse : collapse,
4698                         selectNode : selectNode,
4699                         selectNodeContents : selectNodeContents,
4700                         compareBoundaryPoints : compareBoundaryPoints,
4701                         deleteContents : deleteContents,
4702                         extractContents : extractContents,
4703                         cloneContents : cloneContents,
4704                         insertNode : insertNode,
4705                         surroundContents : surroundContents,
4706                         cloneRange : cloneRange
4707                 });
4708
4709                 function setStart(n, o) {
4710                         _setEndPoint(TRUE, n, o);
4711                 };
4712
4713                 function setEnd(n, o) {
4714                         _setEndPoint(FALSE, n, o);
4715                 };
4716
4717                 function setStartBefore(n) {
4718                         setStart(n.parentNode, nodeIndex(n));
4719                 };
4720
4721                 function setStartAfter(n) {
4722                         setStart(n.parentNode, nodeIndex(n) + 1);
4723                 };
4724
4725                 function setEndBefore(n) {
4726                         setEnd(n.parentNode, nodeIndex(n));
4727                 };
4728
4729                 function setEndAfter(n) {
4730                         setEnd(n.parentNode, nodeIndex(n) + 1);
4731                 };
4732
4733                 function collapse(ts) {
4734                         if (ts) {
4735                                 t[END_CONTAINER] = t[START_CONTAINER];
4736                                 t[END_OFFSET] = t[START_OFFSET];
4737                         } else {
4738                                 t[START_CONTAINER] = t[END_CONTAINER];
4739                                 t[START_OFFSET] = t[END_OFFSET];
4740                         }
4741
4742                         t.collapsed = TRUE;
4743                 };
4744
4745                 function selectNode(n) {
4746                         setStartBefore(n);
4747                         setEndAfter(n);
4748                 };
4749
4750                 function selectNodeContents(n) {
4751                         setStart(n, 0);
4752                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
4753                 };
4754
4755                 function compareBoundaryPoints(h, r) {
4756                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
4757                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
4758
4759                         // Check START_TO_START
4760                         if (h === 0)
4761                                 return _compareBoundaryPoints(sc, so, rsc, rso);
4762         
4763                         // Check START_TO_END
4764                         if (h === 1)
4765                                 return _compareBoundaryPoints(ec, eo, rsc, rso);
4766         
4767                         // Check END_TO_END
4768                         if (h === 2)
4769                                 return _compareBoundaryPoints(ec, eo, rec, reo);
4770         
4771                         // Check END_TO_START
4772                         if (h === 3) 
4773                                 return _compareBoundaryPoints(sc, so, rec, reo);
4774                 };
4775
4776                 function deleteContents() {
4777                         _traverse(DELETE);
4778                 };
4779
4780                 function extractContents() {
4781                         return _traverse(EXTRACT);
4782                 };
4783
4784                 function cloneContents() {
4785                         return _traverse(CLONE);
4786                 };
4787
4788                 function insertNode(n) {
4789                         var startContainer = this[START_CONTAINER],
4790                                 startOffset = this[START_OFFSET], nn, o;
4791
4792                         // Node is TEXT_NODE or CDATA
4793                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
4794                                 if (!startOffset) {
4795                                         // At the start of text
4796                                         startContainer.parentNode.insertBefore(n, startContainer);
4797                                 } else if (startOffset >= startContainer.nodeValue.length) {
4798                                         // At the end of text
4799                                         dom.insertAfter(n, startContainer);
4800                                 } else {
4801                                         // Middle, need to split
4802                                         nn = startContainer.splitText(startOffset);
4803                                         startContainer.parentNode.insertBefore(n, nn);
4804                                 }
4805                         } else {
4806                                 // Insert element node
4807                                 if (startContainer.childNodes.length > 0)
4808                                         o = startContainer.childNodes[startOffset];
4809
4810                                 if (o)
4811                                         startContainer.insertBefore(n, o);
4812                                 else
4813                                         startContainer.appendChild(n);
4814                         }
4815                 };
4816
4817                 function surroundContents(n) {
4818                         var f = t.extractContents();
4819
4820                         t.insertNode(n);
4821                         n.appendChild(f);
4822                         t.selectNode(n);
4823                 };
4824
4825                 function cloneRange() {
4826                         return extend(new Range(dom), {
4827                                 startContainer : t[START_CONTAINER],
4828                                 startOffset : t[START_OFFSET],
4829                                 endContainer : t[END_CONTAINER],
4830                                 endOffset : t[END_OFFSET],
4831                                 collapsed : t.collapsed,
4832                                 commonAncestorContainer : t.commonAncestorContainer
4833                         });
4834                 };
4835
4836                 // Private methods
4837
4838                 function _getSelectedNode(container, offset) {
4839                         var child;
4840
4841                         if (container.nodeType == 3 /* TEXT_NODE */)
4842                                 return container;
4843
4844                         if (offset < 0)
4845                                 return container;
4846
4847                         child = container.firstChild;
4848                         while (child && offset > 0) {
4849                                 --offset;
4850                                 child = child.nextSibling;
4851                         }
4852
4853                         if (child)
4854                                 return child;
4855
4856                         return container;
4857                 };
4858
4859                 function _isCollapsed() {
4860                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
4861                 };
4862
4863                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
4864                         var c, offsetC, n, cmnRoot, childA, childB;
4865                         
4866                         // In the first case the boundary-points have the same container. A is before B
4867                         // if its offset is less than the offset of B, A is equal to B if its offset is
4868                         // equal to the offset of B, and A is after B if its offset is greater than the
4869                         // offset of B.
4870                         if (containerA == containerB) {
4871                                 if (offsetA == offsetB)
4872                                         return 0; // equal
4873
4874                                 if (offsetA < offsetB)
4875                                         return -1; // before
4876
4877                                 return 1; // after
4878                         }
4879
4880                         // In the second case a child node C of the container of A is an ancestor
4881                         // container of B. In this case, A is before B if the offset of A is less than or
4882                         // equal to the index of the child node C and A is after B otherwise.
4883                         c = containerB;
4884                         while (c && c.parentNode != containerA)
4885                                 c = c.parentNode;
4886
4887                         if (c) {
4888                                 offsetC = 0;
4889                                 n = containerA.firstChild;
4890
4891                                 while (n != c && offsetC < offsetA) {
4892                                         offsetC++;
4893                                         n = n.nextSibling;
4894                                 }
4895
4896                                 if (offsetA <= offsetC)
4897                                         return -1; // before
4898
4899                                 return 1; // after
4900                         }
4901
4902                         // In the third case a child node C of the container of B is an ancestor container
4903                         // of A. In this case, A is before B if the index of the child node C is less than
4904                         // the offset of B and A is after B otherwise.
4905                         c = containerA;
4906                         while (c && c.parentNode != containerB) {
4907                                 c = c.parentNode;
4908                         }
4909
4910                         if (c) {
4911                                 offsetC = 0;
4912                                 n = containerB.firstChild;
4913
4914                                 while (n != c && offsetC < offsetB) {
4915                                         offsetC++;
4916                                         n = n.nextSibling;
4917                                 }
4918
4919                                 if (offsetC < offsetB)
4920                                         return -1; // before
4921
4922                                 return 1; // after
4923                         }
4924
4925                         // In the fourth case, none of three other cases hold: the containers of A and B
4926                         // are siblings or descendants of sibling nodes. In this case, A is before B if
4927                         // the container of A is before the container of B in a pre-order traversal of the
4928                         // Ranges' context tree and A is after B otherwise.
4929                         cmnRoot = dom.findCommonAncestor(containerA, containerB);
4930                         childA = containerA;
4931
4932                         while (childA && childA.parentNode != cmnRoot)
4933                                 childA = childA.parentNode;
4934
4935                         if (!childA)
4936                                 childA = cmnRoot;
4937
4938                         childB = containerB;
4939                         while (childB && childB.parentNode != cmnRoot)
4940                                 childB = childB.parentNode;
4941
4942                         if (!childB)
4943                                 childB = cmnRoot;
4944
4945                         if (childA == childB)
4946                                 return 0; // equal
4947
4948                         n = cmnRoot.firstChild;
4949                         while (n) {
4950                                 if (n == childA)
4951                                         return -1; // before
4952
4953                                 if (n == childB)
4954                                         return 1; // after
4955
4956                                 n = n.nextSibling;
4957                         }
4958                 };
4959
4960                 function _setEndPoint(st, n, o) {
4961                         var ec, sc;
4962
4963                         if (st) {
4964                                 t[START_CONTAINER] = n;
4965                                 t[START_OFFSET] = o;
4966                         } else {
4967                                 t[END_CONTAINER] = n;
4968                                 t[END_OFFSET] = o;
4969                         }
4970
4971                         // If one boundary-point of a Range is set to have a root container
4972                         // other than the current one for the Range, the Range is collapsed to
4973                         // the new position. This enforces the restriction that both boundary-
4974                         // points of a Range must have the same root container.
4975                         ec = t[END_CONTAINER];
4976                         while (ec.parentNode)
4977                                 ec = ec.parentNode;
4978
4979                         sc = t[START_CONTAINER];
4980                         while (sc.parentNode)
4981                                 sc = sc.parentNode;
4982
4983                         if (sc == ec) {
4984                                 // The start position of a Range is guaranteed to never be after the
4985                                 // end position. To enforce this restriction, if the start is set to
4986                                 // be at a position after the end, the Range is collapsed to that
4987                                 // position.
4988                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
4989                                         t.collapse(st);
4990                         } else
4991                                 t.collapse(st);
4992
4993                         t.collapsed = _isCollapsed();
4994                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
4995                 };
4996
4997                 function _traverse(how) {
4998                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
4999
5000                         if (t[START_CONTAINER] == t[END_CONTAINER])
5001                                 return _traverseSameContainer(how);
5002
5003                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
5004                                 if (p == t[START_CONTAINER])
5005                                         return _traverseCommonStartContainer(c, how);
5006
5007                                 ++endContainerDepth;
5008                         }
5009
5010                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
5011                                 if (p == t[END_CONTAINER])
5012                                         return _traverseCommonEndContainer(c, how);
5013
5014                                 ++startContainerDepth;
5015                         }
5016
5017                         depthDiff = startContainerDepth - endContainerDepth;
5018
5019                         startNode = t[START_CONTAINER];
5020                         while (depthDiff > 0) {
5021                                 startNode = startNode.parentNode;
5022                                 depthDiff--;
5023                         }
5024
5025                         endNode = t[END_CONTAINER];
5026                         while (depthDiff < 0) {
5027                                 endNode = endNode.parentNode;
5028                                 depthDiff++;
5029                         }
5030
5031                         // ascend the ancestor hierarchy until we have a common parent.
5032                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
5033                                 startNode = sp;
5034                                 endNode = ep;
5035                         }
5036
5037                         return _traverseCommonAncestors(startNode, endNode, how);
5038                 };
5039
5040                  function _traverseSameContainer(how) {
5041                         var frag, s, sub, n, cnt, sibling, xferNode;
5042
5043                         if (how != DELETE)
5044                                 frag = doc.createDocumentFragment();
5045
5046                         // If selection is empty, just return the fragment
5047                         if (t[START_OFFSET] == t[END_OFFSET])
5048                                 return frag;
5049
5050                         // Text node needs special case handling
5051                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
5052                                 // get the substring
5053                                 s = t[START_CONTAINER].nodeValue;
5054                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
5055
5056                                 // set the original text node to its new value
5057                                 if (how != CLONE) {
5058                                         t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
5059
5060                                         // Nothing is partially selected, so collapse to start point
5061                                         t.collapse(TRUE);
5062                                 }
5063
5064                                 if (how == DELETE)
5065                                         return;
5066
5067                                 frag.appendChild(doc.createTextNode(sub));
5068                                 return frag;
5069                         }
5070
5071                         // Copy nodes between the start/end offsets.
5072                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
5073                         cnt = t[END_OFFSET] - t[START_OFFSET];
5074
5075                         while (cnt > 0) {
5076                                 sibling = n.nextSibling;
5077                                 xferNode = _traverseFullySelected(n, how);
5078
5079                                 if (frag)
5080                                         frag.appendChild( xferNode );
5081
5082                                 --cnt;
5083                                 n = sibling;
5084                         }
5085
5086                         // Nothing is partially selected, so collapse to start point
5087                         if (how != CLONE)
5088                                 t.collapse(TRUE);
5089
5090                         return frag;
5091                 };
5092
5093                 function _traverseCommonStartContainer(endAncestor, how) {
5094                         var frag, n, endIdx, cnt, sibling, xferNode;
5095
5096                         if (how != DELETE)
5097                                 frag = doc.createDocumentFragment();
5098
5099                         n = _traverseRightBoundary(endAncestor, how);
5100
5101                         if (frag)
5102                                 frag.appendChild(n);
5103
5104                         endIdx = nodeIndex(endAncestor);
5105                         cnt = endIdx - t[START_OFFSET];
5106
5107                         if (cnt <= 0) {
5108                                 // Collapse to just before the endAncestor, which
5109                                 // is partially selected.
5110                                 if (how != CLONE) {
5111                                         t.setEndBefore(endAncestor);
5112                                         t.collapse(FALSE);
5113                                 }
5114
5115                                 return frag;
5116                         }
5117
5118                         n = endAncestor.previousSibling;
5119                         while (cnt > 0) {
5120                                 sibling = n.previousSibling;
5121                                 xferNode = _traverseFullySelected(n, how);
5122
5123                                 if (frag)
5124                                         frag.insertBefore(xferNode, frag.firstChild);
5125
5126                                 --cnt;
5127                                 n = sibling;
5128                         }
5129
5130                         // Collapse to just before the endAncestor, which
5131                         // is partially selected.
5132                         if (how != CLONE) {
5133                                 t.setEndBefore(endAncestor);
5134                                 t.collapse(FALSE);
5135                         }
5136
5137                         return frag;
5138                 };
5139
5140                 function _traverseCommonEndContainer(startAncestor, how) {
5141                         var frag, startIdx, n, cnt, sibling, xferNode;
5142
5143                         if (how != DELETE)
5144                                 frag = doc.createDocumentFragment();
5145
5146                         n = _traverseLeftBoundary(startAncestor, how);
5147                         if (frag)
5148                                 frag.appendChild(n);
5149
5150                         startIdx = nodeIndex(startAncestor);
5151                         ++startIdx; // Because we already traversed it
5152
5153                         cnt = t[END_OFFSET] - startIdx;
5154                         n = startAncestor.nextSibling;
5155                         while (cnt > 0) {
5156                                 sibling = n.nextSibling;
5157                                 xferNode = _traverseFullySelected(n, how);
5158
5159                                 if (frag)
5160                                         frag.appendChild(xferNode);
5161
5162                                 --cnt;
5163                                 n = sibling;
5164                         }
5165
5166                         if (how != CLONE) {
5167                                 t.setStartAfter(startAncestor);
5168                                 t.collapse(TRUE);
5169                         }
5170
5171                         return frag;
5172                 };
5173
5174                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {
5175                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
5176
5177                         if (how != DELETE)
5178                                 frag = doc.createDocumentFragment();
5179
5180                         n = _traverseLeftBoundary(startAncestor, how);
5181                         if (frag)
5182                                 frag.appendChild(n);
5183
5184                         commonParent = startAncestor.parentNode;
5185                         startOffset = nodeIndex(startAncestor);
5186                         endOffset = nodeIndex(endAncestor);
5187                         ++startOffset;
5188
5189                         cnt = endOffset - startOffset;
5190                         sibling = startAncestor.nextSibling;
5191
5192                         while (cnt > 0) {
5193                                 nextSibling = sibling.nextSibling;
5194                                 n = _traverseFullySelected(sibling, how);
5195
5196                                 if (frag)
5197                                         frag.appendChild(n);
5198
5199                                 sibling = nextSibling;
5200                                 --cnt;
5201                         }
5202
5203                         n = _traverseRightBoundary(endAncestor, how);
5204
5205                         if (frag)
5206                                 frag.appendChild(n);
5207
5208                         if (how != CLONE) {
5209                                 t.setStartAfter(startAncestor);
5210                                 t.collapse(TRUE);
5211                         }
5212
5213                         return frag;
5214                 };
5215
5216                 function _traverseRightBoundary(root, how) {
5217                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
5218
5219                         if (next == root)
5220                                 return _traverseNode(next, isFullySelected, FALSE, how);
5221
5222                         parent = next.parentNode;
5223                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);
5224
5225                         while (parent) {
5226                                 while (next) {
5227                                         prevSibling = next.previousSibling;
5228                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
5229
5230                                         if (how != DELETE)
5231                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
5232
5233                                         isFullySelected = TRUE;
5234                                         next = prevSibling;
5235                                 }
5236
5237                                 if (parent == root)
5238                                         return clonedParent;
5239
5240                                 next = parent.previousSibling;
5241                                 parent = parent.parentNode;
5242
5243                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
5244
5245                                 if (how != DELETE)
5246                                         clonedGrandParent.appendChild(clonedParent);
5247
5248                                 clonedParent = clonedGrandParent;
5249                         }
5250                 };
5251
5252                 function _traverseLeftBoundary(root, how) {
5253                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
5254
5255                         if (next == root)
5256                                 return _traverseNode(next, isFullySelected, TRUE, how);
5257
5258                         parent = next.parentNode;
5259                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);
5260
5261                         while (parent) {
5262                                 while (next) {
5263                                         nextSibling = next.nextSibling;
5264                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
5265
5266                                         if (how != DELETE)
5267                                                 clonedParent.appendChild(clonedChild);
5268
5269                                         isFullySelected = TRUE;
5270                                         next = nextSibling;
5271                                 }
5272
5273                                 if (parent == root)
5274                                         return clonedParent;
5275
5276                                 next = parent.nextSibling;
5277                                 parent = parent.parentNode;
5278
5279                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
5280
5281                                 if (how != DELETE)
5282                                         clonedGrandParent.appendChild(clonedParent);
5283
5284                                 clonedParent = clonedGrandParent;
5285                         }
5286                 };
5287
5288                 function _traverseNode(n, isFullySelected, isLeft, how) {
5289                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;
5290
5291                         if (isFullySelected)
5292                                 return _traverseFullySelected(n, how);
5293
5294                         if (n.nodeType == 3 /* TEXT_NODE */) {
5295                                 txtValue = n.nodeValue;
5296
5297                                 if (isLeft) {
5298                                         offset = t[START_OFFSET];
5299                                         newNodeValue = txtValue.substring(offset);
5300                                         oldNodeValue = txtValue.substring(0, offset);
5301                                 } else {
5302                                         offset = t[END_OFFSET];
5303                                         newNodeValue = txtValue.substring(0, offset);
5304                                         oldNodeValue = txtValue.substring(offset);
5305                                 }
5306
5307                                 if (how != CLONE)
5308                                         n.nodeValue = oldNodeValue;
5309
5310                                 if (how == DELETE)
5311                                         return;
5312
5313                                 newNode = n.cloneNode(FALSE);
5314                                 newNode.nodeValue = newNodeValue;
5315
5316                                 return newNode;
5317                         }
5318
5319                         if (how == DELETE)
5320                                 return;
5321
5322                         return n.cloneNode(FALSE);
5323                 };
5324
5325                 function _traverseFullySelected(n, how) {
5326                         if (how != DELETE)
5327                                 return how == CLONE ? n.cloneNode(TRUE) : n;
5328
5329                         n.parentNode.removeChild(n);
5330                 };
5331         };
5332
5333         ns.Range = Range;
5334 })(tinymce.dom);
5335
5336 (function() {
5337         function Selection(selection) {
5338                 var self = this, dom = selection.dom, TRUE = true, FALSE = false;
5339
5340                 function getPosition(rng, start) {
5341                         var checkRng, startIndex = 0, endIndex, inside,
5342                                 children, child, offset, index, position = -1, parent;
5343
5344                         // Setup test range, collapse it and get the parent
5345                         checkRng = rng.duplicate();
5346                         checkRng.collapse(start);
5347                         parent = checkRng.parentElement();
5348
5349                         // Check if the selection is within the right document
5350                         if (parent.ownerDocument !== selection.dom.doc)
5351                                 return;
5352
5353                         // IE will report non editable elements as it's parent so look for an editable one
5354                         while (parent.contentEditable === "false") {
5355                                 parent = parent.parentNode;
5356                         }
5357
5358                         // If parent doesn't have any children then return that we are inside the element
5359                         if (!parent.hasChildNodes()) {
5360                                 return {node : parent, inside : 1};
5361                         }
5362
5363                         // Setup node list and endIndex
5364                         children = parent.children;
5365                         endIndex = children.length - 1;
5366
5367                         // Perform a binary search for the position
5368                         while (startIndex <= endIndex) {
5369                                 index = Math.floor((startIndex + endIndex) / 2);
5370
5371                                 // Move selection to node and compare the ranges
5372                                 child = children[index];
5373                                 checkRng.moveToElementText(child);
5374                                 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
5375
5376                                 // Before/after or an exact match
5377                                 if (position > 0) {
5378                                         endIndex = index - 1;
5379                                 } else if (position < 0) {
5380                                         startIndex = index + 1;
5381                                 } else {
5382                                         return {node : child};
5383                                 }
5384                         }
5385
5386                         // Check if child position is before or we didn't find a position
5387                         if (position < 0) {
5388                                 // No element child was found use the parent element and the offset inside that
5389                                 if (!child) {
5390                                         checkRng.moveToElementText(parent);
5391                                         checkRng.collapse(true);
5392                                         child = parent;
5393                                         inside = true;
5394                                 } else
5395                                         checkRng.collapse(false);
5396
5397                                 checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng);
5398
5399                                 // Fix for edge case: <div style="width: 100px; height:100px;"><table>..</table>ab|c</div>
5400                                 if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) {
5401                                         checkRng = rng.duplicate();
5402                                         checkRng.collapse(start);
5403
5404                                         offset = -1;
5405                                         while (parent == checkRng.parentElement()) {
5406                                                 if (checkRng.move('character', -1) == 0)
5407                                                         break;
5408
5409                                                 offset++;
5410                                         }
5411                                 }
5412
5413                                 offset = offset || checkRng.text.replace('\r\n', ' ').length;
5414                         } else {
5415                                 // Child position is after the selection endpoint
5416                                 checkRng.collapse(true);
5417                                 checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng);
5418
5419                                 // Get the length of the text to find where the endpoint is relative to it's container
5420                                 offset = checkRng.text.replace('\r\n', ' ').length;
5421                         }
5422
5423                         return {node : child, position : position, offset : offset, inside : inside};
5424                 };
5425
5426                 // Returns a W3C DOM compatible range object by using the IE Range API
5427                 function getRange() {
5428                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
5429
5430                         // If selection is outside the current document just return an empty range
5431                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
5432                         if (element.ownerDocument != dom.doc)
5433                                 return domRange;
5434
5435                         collapsed = selection.isCollapsed();
5436
5437                         // Handle control selection
5438                         if (ieRange.item) {
5439                                 domRange.setStart(element.parentNode, dom.nodeIndex(element));
5440                                 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
5441
5442                                 return domRange;
5443                         }
5444
5445                         function findEndPoint(start) {
5446                                 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
5447
5448                                 container = endPoint.node;
5449                                 offset = endPoint.offset;
5450
5451                                 if (endPoint.inside && !container.hasChildNodes()) {
5452                                         domRange[start ? 'setStart' : 'setEnd'](container, 0);
5453                                         return;
5454                                 }
5455
5456                                 if (offset === undef) {
5457                                         domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
5458                                         return;
5459                                 }
5460
5461                                 if (endPoint.position < 0) {
5462                                         sibling = endPoint.inside ? container.firstChild : container.nextSibling;
5463
5464                                         if (!sibling) {
5465                                                 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
5466                                                 return;
5467                                         }
5468
5469                                         if (!offset) {
5470                                                 if (sibling.nodeType == 3)
5471                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
5472                                                 else
5473                                                         domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
5474
5475                                                 return;
5476                                         }
5477
5478                                         // Find the text node and offset
5479                                         while (sibling) {
5480                                                 nodeValue = sibling.nodeValue;
5481                                                 textNodeOffset += nodeValue.length;
5482
5483                                                 // We are at or passed the position we where looking for
5484                                                 if (textNodeOffset >= offset) {
5485                                                         container = sibling;
5486                                                         textNodeOffset -= offset;
5487                                                         textNodeOffset = nodeValue.length - textNodeOffset;
5488                                                         break;
5489                                                 }
5490
5491                                                 sibling = sibling.nextSibling;
5492                                         }
5493                                 } else {
5494                                         // Find the text node and offset
5495                                         sibling = container.previousSibling;
5496
5497                                         if (!sibling)
5498                                                 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
5499
5500                                         // If there isn't any text to loop then use the first position
5501                                         if (!offset) {
5502                                                 if (container.nodeType == 3)
5503                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
5504                                                 else
5505                                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
5506
5507                                                 return;
5508                                         }
5509
5510                                         while (sibling) {
5511                                                 textNodeOffset += sibling.nodeValue.length;
5512
5513                                                 // We are at or passed the position we where looking for
5514                                                 if (textNodeOffset >= offset) {
5515                                                         container = sibling;
5516                                                         textNodeOffset -= offset;
5517                                                         break;
5518                                                 }
5519
5520                                                 sibling = sibling.previousSibling;
5521                                         }
5522                                 }
5523
5524                                 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
5525                         };
5526
5527                         try {
5528                                 // Find start point
5529                                 findEndPoint(true);
5530
5531                                 // Find end point if needed
5532                                 if (!collapsed)
5533                                         findEndPoint();
5534                         } catch (ex) {
5535                                 // IE has a nasty bug where text nodes might throw "invalid argument" when you
5536                                 // access the nodeValue or other properties of text nodes. This seems to happend when
5537                                 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
5538                                 if (ex.number == -2147024809) {
5539                                         // Get the current selection
5540                                         bookmark = self.getBookmark(2);
5541
5542                                         // Get start element
5543                                         tmpRange = ieRange.duplicate();
5544                                         tmpRange.collapse(true);
5545                                         element = tmpRange.parentElement();
5546
5547                                         // Get end element
5548                                         if (!collapsed) {
5549                                                 tmpRange = ieRange.duplicate();
5550                                                 tmpRange.collapse(false);
5551                                                 element2 = tmpRange.parentElement();
5552                                                 element2.innerHTML = element2.innerHTML;
5553                                         }
5554
5555                                         // Remove the broken elements
5556                                         element.innerHTML = element.innerHTML;
5557
5558                                         // Restore the selection
5559                                         self.moveToBookmark(bookmark);
5560
5561                                         // Since the range has moved we need to re-get it
5562                                         ieRange = selection.getRng();
5563
5564                                         // Find start point
5565                                         findEndPoint(true);
5566
5567                                         // Find end point if needed
5568                                         if (!collapsed)
5569                                                 findEndPoint();
5570                                 } else
5571                                         throw ex; // Throw other errors
5572                         }
5573
5574                         return domRange;
5575                 };
5576
5577                 this.getBookmark = function(type) {
5578                         var rng = selection.getRng(), start, end, bookmark = {};
5579
5580                         function getIndexes(node) {
5581                                 var node, parent, root, children, i, indexes = [];
5582
5583                                 parent = node.parentNode;
5584                                 root = dom.getRoot().parentNode;
5585
5586                                 while (parent != root) {
5587                                         children = parent.children;
5588
5589                                         i = children.length;
5590                                         while (i--) {
5591                                                 if (node === children[i]) {
5592                                                         indexes.push(i);
5593                                                         break;
5594                                                 }
5595                                         }
5596
5597                                         node = parent;
5598                                         parent = parent.parentNode;
5599                                 }
5600
5601                                 return indexes;
5602                         };
5603
5604                         function getBookmarkEndPoint(start) {
5605                                 var position;
5606
5607                                 position = getPosition(rng, start);
5608                                 if (position) {
5609                                         return {
5610                                                 position : position.position,
5611                                                 offset : position.offset,
5612                                                 indexes : getIndexes(position.node),
5613                                                 inside : position.inside
5614                                         };
5615                                 }
5616                         };
5617
5618                         // Non ubstructive bookmark
5619                         if (type === 2) {
5620                                 // Handle text selection
5621                                 if (!rng.item) {
5622                                         bookmark.start = getBookmarkEndPoint(true);
5623
5624                                         if (!selection.isCollapsed())
5625                                                 bookmark.end = getBookmarkEndPoint();
5626                                 } else
5627                                         bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
5628                         }
5629
5630                         return bookmark;
5631                 };
5632
5633                 this.moveToBookmark = function(bookmark) {
5634                         var rng, body = dom.doc.body;
5635
5636                         function resolveIndexes(indexes) {
5637                                 var node, i, idx, children;
5638
5639                                 node = dom.getRoot();
5640                                 for (i = indexes.length - 1; i >= 0; i--) {
5641                                         children = node.children;
5642                                         idx = indexes[i];
5643
5644                                         if (idx <= children.length - 1) {
5645                                                 node = children[idx];
5646                                         }
5647                                 }
5648
5649                                 return node;
5650                         };
5651                         
5652                         function setBookmarkEndPoint(start) {
5653                                 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
5654
5655                                 if (endPoint) {
5656                                         moveLeft = endPoint.position > 0;
5657
5658                                         moveRng = body.createTextRange();
5659                                         moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
5660
5661                                         offset = endPoint.offset;
5662                                         if (offset !== undef) {
5663                                                 moveRng.collapse(endPoint.inside || moveLeft);
5664                                                 moveRng.moveStart('character', moveLeft ? -offset : offset);
5665                                         } else
5666                                                 moveRng.collapse(start);
5667
5668                                         rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
5669
5670                                         if (start)
5671                                                 rng.collapse(true);
5672                                 }
5673                         };
5674
5675                         if (bookmark.start) {
5676                                 if (bookmark.start.ctrl) {
5677                                         rng = body.createControlRange();
5678                                         rng.addElement(resolveIndexes(bookmark.start.indexes));
5679                                         rng.select();
5680                                 } else {
5681                                         rng = body.createTextRange();
5682                                         setBookmarkEndPoint(true);
5683                                         setBookmarkEndPoint();
5684                                         rng.select();
5685                                 }
5686                         }
5687                 };
5688
5689                 this.addRange = function(rng) {
5690                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
5691
5692                         function setEndPoint(start) {
5693                                 var container, offset, marker, tmpRng, nodes;
5694
5695                                 marker = dom.create('a');
5696                                 container = start ? startContainer : endContainer;
5697                                 offset = start ? startOffset : endOffset;
5698                                 tmpRng = ieRng.duplicate();
5699
5700                                 if (container == doc || container == doc.documentElement) {
5701                                         container = body;
5702                                         offset = 0;
5703                                 }
5704
5705                                 if (container.nodeType == 3) {
5706                                         container.parentNode.insertBefore(marker, container);
5707                                         tmpRng.moveToElementText(marker);
5708                                         tmpRng.moveStart('character', offset);
5709                                         dom.remove(marker);
5710                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5711                                 } else {
5712                                         nodes = container.childNodes;
5713
5714                                         if (nodes.length) {
5715                                                 if (offset >= nodes.length) {
5716                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);
5717                                                 } else {
5718                                                         container.insertBefore(marker, nodes[offset]);
5719                                                 }
5720
5721                                                 tmpRng.moveToElementText(marker);
5722                                         } else {
5723                                                 // Empty node selection for example <div>|</div>
5724                                                 marker = doc.createTextNode('\uFEFF');
5725                                                 container.appendChild(marker);
5726                                                 tmpRng.moveToElementText(marker.parentNode);
5727                                                 tmpRng.collapse(TRUE);
5728                                         }
5729
5730                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
5731                                         dom.remove(marker);
5732                                 }
5733                         }
5734
5735                         // Setup some shorter versions
5736                         startContainer = rng.startContainer;
5737                         startOffset = rng.startOffset;
5738                         endContainer = rng.endContainer;
5739                         endOffset = rng.endOffset;
5740                         ieRng = body.createTextRange();
5741
5742                         // If single element selection then try making a control selection out of it
5743                         if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
5744                                 if (startOffset == endOffset - 1) {
5745                                         try {
5746                                                 ctrlRng = body.createControlRange();
5747                                                 ctrlRng.addElement(startContainer.childNodes[startOffset]);
5748                                                 ctrlRng.select();
5749                                                 return;
5750                                         } catch (ex) {
5751                                                 // Ignore
5752                                         }
5753                                 }
5754                         }
5755
5756                         // Set start/end point of selection
5757                         setEndPoint(true);
5758                         setEndPoint();
5759
5760                         // Select the new range and scroll it into view
5761                         ieRng.select();
5762                 };
5763
5764                 // Expose range method
5765                 this.getRangeAt = getRange;
5766         };
5767
5768         // Expose the selection object
5769         tinymce.dom.TridentSelection = Selection;
5770 })();
5771
5772
5773 /*
5774  * Sizzle CSS Selector Engine - v1.0
5775  *  Copyright 2009, The Dojo Foundation
5776  *  Released under the MIT, BSD, and GPL Licenses.
5777  *  More information: http://sizzlejs.com/
5778  */
5779 (function(){
5780
5781 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
5782         done = 0,
5783         toString = Object.prototype.toString,
5784         hasDuplicate = false,
5785         baseHasDuplicate = true;
5786
5787 // Here we check if the JavaScript engine is using some sort of
5788 // optimization where it does not always call our comparision
5789 // function. If that is the case, discard the hasDuplicate value.
5790 //   Thus far that includes Google Chrome.
5791 [0, 0].sort(function(){
5792         baseHasDuplicate = false;
5793         return 0;
5794 });
5795
5796 var Sizzle = function(selector, context, results, seed) {
5797         results = results || [];
5798         context = context || document;
5799
5800         var origContext = context;
5801
5802         if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
5803                 return [];
5804         }
5805         
5806         if ( !selector || typeof selector !== "string" ) {
5807                 return results;
5808         }
5809
5810         var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
5811                 soFar = selector, ret, cur, pop, i;
5812         
5813         // Reset the position of the chunker regexp (start from head)
5814         do {
5815                 chunker.exec("");
5816                 m = chunker.exec(soFar);
5817
5818                 if ( m ) {
5819                         soFar = m[3];
5820                 
5821                         parts.push( m[1] );
5822                 
5823                         if ( m[2] ) {
5824                                 extra = m[3];
5825                                 break;
5826                         }
5827                 }
5828         } while ( m );
5829
5830         if ( parts.length > 1 && origPOS.exec( selector ) ) {
5831                 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
5832                         set = posProcess( parts[0] + parts[1], context );
5833                 } else {
5834                         set = Expr.relative[ parts[0] ] ?
5835                                 [ context ] :
5836                                 Sizzle( parts.shift(), context );
5837
5838                         while ( parts.length ) {
5839                                 selector = parts.shift();
5840
5841                                 if ( Expr.relative[ selector ] ) {
5842                                         selector += parts.shift();
5843                                 }
5844                                 
5845                                 set = posProcess( selector, set );
5846                         }
5847                 }
5848         } else {
5849                 // Take a shortcut and set the context if the root selector is an ID
5850                 // (but not if it'll be faster if the inner selector is an ID)
5851                 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
5852                                 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
5853                         ret = Sizzle.find( parts.shift(), context, contextXML );
5854                         context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
5855                 }
5856
5857                 if ( context ) {
5858                         ret = seed ?
5859                                 { expr: parts.pop(), set: makeArray(seed) } :
5860                                 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
5861                         set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
5862
5863                         if ( parts.length > 0 ) {
5864                                 checkSet = makeArray(set);
5865                         } else {
5866                                 prune = false;
5867                         }
5868
5869                         while ( parts.length ) {
5870                                 cur = parts.pop();
5871                                 pop = cur;
5872
5873                                 if ( !Expr.relative[ cur ] ) {
5874                                         cur = "";
5875                                 } else {
5876                                         pop = parts.pop();
5877                                 }
5878
5879                                 if ( pop == null ) {
5880                                         pop = context;
5881                                 }
5882
5883                                 Expr.relative[ cur ]( checkSet, pop, contextXML );
5884                         }
5885                 } else {
5886                         checkSet = parts = [];
5887                 }
5888         }
5889
5890         if ( !checkSet ) {
5891                 checkSet = set;
5892         }
5893
5894         if ( !checkSet ) {
5895                 Sizzle.error( cur || selector );
5896         }
5897
5898         if ( toString.call(checkSet) === "[object Array]" ) {
5899                 if ( !prune ) {
5900                         results.push.apply( results, checkSet );
5901                 } else if ( context && context.nodeType === 1 ) {
5902                         for ( i = 0; checkSet[i] != null; i++ ) {
5903                                 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
5904                                         results.push( set[i] );
5905                                 }
5906                         }
5907                 } else {
5908                         for ( i = 0; checkSet[i] != null; i++ ) {
5909                                 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
5910                                         results.push( set[i] );
5911                                 }
5912                         }
5913                 }
5914         } else {
5915                 makeArray( checkSet, results );
5916         }
5917
5918         if ( extra ) {
5919                 Sizzle( extra, origContext, results, seed );
5920                 Sizzle.uniqueSort( results );
5921         }
5922
5923         return results;
5924 };
5925
5926 Sizzle.uniqueSort = function(results){
5927         if ( sortOrder ) {
5928                 hasDuplicate = baseHasDuplicate;
5929                 results.sort(sortOrder);
5930
5931                 if ( hasDuplicate ) {
5932                         for ( var i = 1; i < results.length; i++ ) {
5933                                 if ( results[i] === results[i-1] ) {
5934                                         results.splice(i--, 1);
5935                                 }
5936                         }
5937                 }
5938         }
5939
5940         return results;
5941 };
5942
5943 Sizzle.matches = function(expr, set){
5944         return Sizzle(expr, null, null, set);
5945 };
5946
5947 Sizzle.find = function(expr, context, isXML){
5948         var set;
5949
5950         if ( !expr ) {
5951                 return [];
5952         }
5953
5954         for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
5955                 var type = Expr.order[i], match;
5956                 
5957                 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
5958                         var left = match[1];
5959                         match.splice(1,1);
5960
5961                         if ( left.substr( left.length - 1 ) !== "\\" ) {
5962                                 match[1] = (match[1] || "").replace(/\\/g, "");
5963                                 set = Expr.find[ type ]( match, context, isXML );
5964                                 if ( set != null ) {
5965                                         expr = expr.replace( Expr.match[ type ], "" );
5966                                         break;
5967                                 }
5968                         }
5969                 }
5970         }
5971
5972         if ( !set ) {
5973                 set = context.getElementsByTagName("*");
5974         }
5975
5976         return {set: set, expr: expr};
5977 };
5978
5979 Sizzle.filter = function(expr, set, inplace, not){
5980         var old = expr, result = [], curLoop = set, match, anyFound,
5981                 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
5982
5983         while ( expr && set.length ) {
5984                 for ( var type in Expr.filter ) {
5985                         if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
5986                                 var filter = Expr.filter[ type ], found, item, left = match[1];
5987                                 anyFound = false;
5988
5989                                 match.splice(1,1);
5990
5991                                 if ( left.substr( left.length - 1 ) === "\\" ) {
5992                                         continue;
5993                                 }
5994
5995                                 if ( curLoop === result ) {
5996                                         result = [];
5997                                 }
5998
5999                                 if ( Expr.preFilter[ type ] ) {
6000                                         match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
6001
6002                                         if ( !match ) {
6003                                                 anyFound = found = true;
6004                                         } else if ( match === true ) {
6005                                                 continue;
6006                                         }
6007                                 }
6008
6009                                 if ( match ) {
6010                                         for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
6011                                                 if ( item ) {
6012                                                         found = filter( item, match, i, curLoop );
6013                                                         var pass = not ^ !!found;
6014
6015                                                         if ( inplace && found != null ) {
6016                                                                 if ( pass ) {
6017                                                                         anyFound = true;
6018                                                                 } else {
6019                                                                         curLoop[i] = false;
6020                                                                 }
6021                                                         } else if ( pass ) {
6022                                                                 result.push( item );
6023                                                                 anyFound = true;
6024                                                         }
6025                                                 }
6026                                         }
6027                                 }
6028
6029                                 if ( found !== undefined ) {
6030                                         if ( !inplace ) {
6031                                                 curLoop = result;
6032                                         }
6033
6034                                         expr = expr.replace( Expr.match[ type ], "" );
6035
6036                                         if ( !anyFound ) {
6037                                                 return [];
6038                                         }
6039
6040                                         break;
6041                                 }
6042                         }
6043                 }
6044
6045                 // Improper expression
6046                 if ( expr === old ) {
6047                         if ( anyFound == null ) {
6048                                 Sizzle.error( expr );
6049                         } else {
6050                                 break;
6051                         }
6052                 }
6053
6054                 old = expr;
6055         }
6056
6057         return curLoop;
6058 };
6059
6060 Sizzle.error = function( msg ) {
6061         throw "Syntax error, unrecognized expression: " + msg;
6062 };
6063
6064 var Expr = Sizzle.selectors = {
6065         order: [ "ID", "NAME", "TAG" ],
6066         match: {
6067                 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
6068                 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
6069                 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
6070                 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
6071                 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
6072                 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
6073                 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
6074                 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
6075         },
6076         leftMatch: {},
6077         attrMap: {
6078                 "class": "className",
6079                 "for": "htmlFor"
6080         },
6081         attrHandle: {
6082                 href: function(elem){
6083                         return elem.getAttribute("href");
6084                 }
6085         },
6086         relative: {
6087                 "+": function(checkSet, part){
6088                         var isPartStr = typeof part === "string",
6089                                 isTag = isPartStr && !/\W/.test(part),
6090                                 isPartStrNotTag = isPartStr && !isTag;
6091
6092                         if ( isTag ) {
6093                                 part = part.toLowerCase();
6094                         }
6095
6096                         for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
6097                                 if ( (elem = checkSet[i]) ) {
6098                                         while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
6099
6100                                         checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
6101                                                 elem || false :
6102                                                 elem === part;
6103                                 }
6104                         }
6105
6106                         if ( isPartStrNotTag ) {
6107                                 Sizzle.filter( part, checkSet, true );
6108                         }
6109                 },
6110                 ">": function(checkSet, part){
6111                         var isPartStr = typeof part === "string",
6112                                 elem, i = 0, l = checkSet.length;
6113
6114                         if ( isPartStr && !/\W/.test(part) ) {
6115                                 part = part.toLowerCase();
6116
6117                                 for ( ; i < l; i++ ) {
6118                                         elem = checkSet[i];
6119                                         if ( elem ) {
6120                                                 var parent = elem.parentNode;
6121                                                 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
6122                                         }
6123                                 }
6124                         } else {
6125                                 for ( ; i < l; i++ ) {
6126                                         elem = checkSet[i];
6127                                         if ( elem ) {
6128                                                 checkSet[i] = isPartStr ?
6129                                                         elem.parentNode :
6130                                                         elem.parentNode === part;
6131                                         }
6132                                 }
6133
6134                                 if ( isPartStr ) {
6135                                         Sizzle.filter( part, checkSet, true );
6136                                 }
6137                         }
6138                 },
6139                 "": function(checkSet, part, isXML){
6140                         var doneName = done++, checkFn = dirCheck, nodeCheck;
6141
6142                         if ( typeof part === "string" && !/\W/.test(part) ) {
6143                                 part = part.toLowerCase();
6144                                 nodeCheck = part;
6145                                 checkFn = dirNodeCheck;
6146                         }
6147
6148                         checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
6149                 },
6150                 "~": function(checkSet, part, isXML){
6151                         var doneName = done++, checkFn = dirCheck, nodeCheck;
6152
6153                         if ( typeof part === "string" && !/\W/.test(part) ) {
6154                                 part = part.toLowerCase();
6155                                 nodeCheck = part;
6156                                 checkFn = dirNodeCheck;
6157                         }
6158
6159                         checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
6160                 }
6161         },
6162         find: {
6163                 ID: function(match, context, isXML){
6164                         if ( typeof context.getElementById !== "undefined" && !isXML ) {
6165                                 var m = context.getElementById(match[1]);
6166                                 return m ? [m] : [];
6167                         }
6168                 },
6169                 NAME: function(match, context){
6170                         if ( typeof context.getElementsByName !== "undefined" ) {
6171                                 var ret = [], results = context.getElementsByName(match[1]);
6172
6173                                 for ( var i = 0, l = results.length; i < l; i++ ) {
6174                                         if ( results[i].getAttribute("name") === match[1] ) {
6175                                                 ret.push( results[i] );
6176                                         }
6177                                 }
6178
6179                                 return ret.length === 0 ? null : ret;
6180                         }
6181                 },
6182                 TAG: function(match, context){
6183                         return context.getElementsByTagName(match[1]);
6184                 }
6185         },
6186         preFilter: {
6187                 CLASS: function(match, curLoop, inplace, result, not, isXML){
6188                         match = " " + match[1].replace(/\\/g, "") + " ";
6189
6190                         if ( isXML ) {
6191                                 return match;
6192                         }
6193
6194                         for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
6195                                 if ( elem ) {
6196                                         if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
6197                                                 if ( !inplace ) {
6198                                                         result.push( elem );
6199                                                 }
6200                                         } else if ( inplace ) {
6201                                                 curLoop[i] = false;
6202                                         }
6203                                 }
6204                         }
6205
6206                         return false;
6207                 },
6208                 ID: function(match){
6209                         return match[1].replace(/\\/g, "");
6210                 },
6211                 TAG: function(match, curLoop){
6212                         return match[1].toLowerCase();
6213                 },
6214                 CHILD: function(match){
6215                         if ( match[1] === "nth" ) {
6216                                 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
6217                                 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
6218                                         match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
6219                                         !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
6220
6221                                 // calculate the numbers (first)n+(last) including if they are negative
6222                                 match[2] = (test[1] + (test[2] || 1)) - 0;
6223                                 match[3] = test[3] - 0;
6224                         }
6225
6226                         // TODO: Move to normal caching system
6227                         match[0] = done++;
6228
6229                         return match;
6230                 },
6231                 ATTR: function(match, curLoop, inplace, result, not, isXML){
6232                         var name = match[1].replace(/\\/g, "");
6233                         
6234                         if ( !isXML && Expr.attrMap[name] ) {
6235                                 match[1] = Expr.attrMap[name];
6236                         }
6237
6238                         if ( match[2] === "~=" ) {
6239                                 match[4] = " " + match[4] + " ";
6240                         }
6241
6242                         return match;
6243                 },
6244                 PSEUDO: function(match, curLoop, inplace, result, not){
6245                         if ( match[1] === "not" ) {
6246                                 // If we're dealing with a complex expression, or a simple one
6247                                 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
6248                                         match[3] = Sizzle(match[3], null, null, curLoop);
6249                                 } else {
6250                                         var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
6251                                         if ( !inplace ) {
6252                                                 result.push.apply( result, ret );
6253                                         }
6254                                         return false;
6255                                 }
6256                         } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
6257                                 return true;
6258                         }
6259                         
6260                         return match;
6261                 },
6262                 POS: function(match){
6263                         match.unshift( true );
6264                         return match;
6265                 }
6266         },
6267         filters: {
6268                 enabled: function(elem){
6269                         return elem.disabled === false && elem.type !== "hidden";
6270                 },
6271                 disabled: function(elem){
6272                         return elem.disabled === true;
6273                 },
6274                 checked: function(elem){
6275                         return elem.checked === true;
6276                 },
6277                 selected: function(elem){
6278                         // Accessing this property makes selected-by-default
6279                         // options in Safari work properly
6280                         elem.parentNode.selectedIndex;
6281                         return elem.selected === true;
6282                 },
6283                 parent: function(elem){
6284                         return !!elem.firstChild;
6285                 },
6286                 empty: function(elem){
6287                         return !elem.firstChild;
6288                 },
6289                 has: function(elem, i, match){
6290                         return !!Sizzle( match[3], elem ).length;
6291                 },
6292                 header: function(elem){
6293                         return (/h\d/i).test( elem.nodeName );
6294                 },
6295                 text: function(elem){
6296                         return "text" === elem.type;
6297                 },
6298                 radio: function(elem){
6299                         return "radio" === elem.type;
6300                 },
6301                 checkbox: function(elem){
6302                         return "checkbox" === elem.type;
6303                 },
6304                 file: function(elem){
6305                         return "file" === elem.type;
6306                 },
6307                 password: function(elem){
6308                         return "password" === elem.type;
6309                 },
6310                 submit: function(elem){
6311                         return "submit" === elem.type;
6312                 },
6313                 image: function(elem){
6314                         return "image" === elem.type;
6315                 },
6316                 reset: function(elem){
6317                         return "reset" === elem.type;
6318                 },
6319                 button: function(elem){
6320                         return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
6321                 },
6322                 input: function(elem){
6323                         return (/input|select|textarea|button/i).test(elem.nodeName);
6324                 }
6325         },
6326         setFilters: {
6327                 first: function(elem, i){
6328                         return i === 0;
6329                 },
6330                 last: function(elem, i, match, array){
6331                         return i === array.length - 1;
6332                 },
6333                 even: function(elem, i){
6334                         return i % 2 === 0;
6335                 },
6336                 odd: function(elem, i){
6337                         return i % 2 === 1;
6338                 },
6339                 lt: function(elem, i, match){
6340                         return i < match[3] - 0;
6341                 },
6342                 gt: function(elem, i, match){
6343                         return i > match[3] - 0;
6344                 },
6345                 nth: function(elem, i, match){
6346                         return match[3] - 0 === i;
6347                 },
6348                 eq: function(elem, i, match){
6349                         return match[3] - 0 === i;
6350                 }
6351         },
6352         filter: {
6353                 PSEUDO: function(elem, match, i, array){
6354                         var name = match[1], filter = Expr.filters[ name ];
6355
6356                         if ( filter ) {
6357                                 return filter( elem, i, match, array );
6358                         } else if ( name === "contains" ) {
6359                                 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
6360                         } else if ( name === "not" ) {
6361                                 var not = match[3];
6362
6363                                 for ( var j = 0, l = not.length; j < l; j++ ) {
6364                                         if ( not[j] === elem ) {
6365                                                 return false;
6366                                         }
6367                                 }
6368
6369                                 return true;
6370                         } else {
6371                                 Sizzle.error( "Syntax error, unrecognized expression: " + name );
6372                         }
6373                 },
6374                 CHILD: function(elem, match){
6375                         var type = match[1], node = elem;
6376                         switch (type) {
6377                                 case 'only':
6378                                 case 'first':
6379                                         while ( (node = node.previousSibling) )  {
6380                                                 if ( node.nodeType === 1 ) { 
6381                                                         return false; 
6382                                                 }
6383                                         }
6384                                         if ( type === "first" ) { 
6385                                                 return true; 
6386                                         }
6387                                         node = elem;
6388                                 case 'last':
6389                                         while ( (node = node.nextSibling) )      {
6390                                                 if ( node.nodeType === 1 ) { 
6391                                                         return false; 
6392                                                 }
6393                                         }
6394                                         return true;
6395                                 case 'nth':
6396                                         var first = match[2], last = match[3];
6397
6398                                         if ( first === 1 && last === 0 ) {
6399                                                 return true;
6400                                         }
6401                                         
6402                                         var doneName = match[0],
6403                                                 parent = elem.parentNode;
6404         
6405                                         if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
6406                                                 var count = 0;
6407                                                 for ( node = parent.firstChild; node; node = node.nextSibling ) {
6408                                                         if ( node.nodeType === 1 ) {
6409                                                                 node.nodeIndex = ++count;
6410                                                         }
6411                                                 } 
6412                                                 parent.sizcache = doneName;
6413                                         }
6414                                         
6415                                         var diff = elem.nodeIndex - last;
6416                                         if ( first === 0 ) {
6417                                                 return diff === 0;
6418                                         } else {
6419                                                 return ( diff % first === 0 && diff / first >= 0 );
6420                                         }
6421                         }
6422                 },
6423                 ID: function(elem, match){
6424                         return elem.nodeType === 1 && elem.getAttribute("id") === match;
6425                 },
6426                 TAG: function(elem, match){
6427                         return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
6428                 },
6429                 CLASS: function(elem, match){
6430                         return (" " + (elem.className || elem.getAttribute("class")) + " ")
6431                                 .indexOf( match ) > -1;
6432                 },
6433                 ATTR: function(elem, match){
6434                         var name = match[1],
6435                                 result = Expr.attrHandle[ name ] ?
6436                                         Expr.attrHandle[ name ]( elem ) :
6437                                         elem[ name ] != null ?
6438                                                 elem[ name ] :
6439                                                 elem.getAttribute( name ),
6440                                 value = result + "",
6441                                 type = match[2],
6442                                 check = match[4];
6443
6444                         return result == null ?
6445                                 type === "!=" :
6446                                 type === "=" ?
6447                                 value === check :
6448                                 type === "*=" ?
6449                                 value.indexOf(check) >= 0 :
6450                                 type === "~=" ?
6451                                 (" " + value + " ").indexOf(check) >= 0 :
6452                                 !check ?
6453                                 value && result !== false :
6454                                 type === "!=" ?
6455                                 value !== check :
6456                                 type === "^=" ?
6457                                 value.indexOf(check) === 0 :
6458                                 type === "$=" ?
6459                                 value.substr(value.length - check.length) === check :
6460                                 type === "|=" ?
6461                                 value === check || value.substr(0, check.length + 1) === check + "-" :
6462                                 false;
6463                 },
6464                 POS: function(elem, match, i, array){
6465                         var name = match[2], filter = Expr.setFilters[ name ];
6466
6467                         if ( filter ) {
6468                                 return filter( elem, i, match, array );
6469                         }
6470                 }
6471         }
6472 };
6473
6474 var origPOS = Expr.match.POS,
6475         fescape = function(all, num){
6476                 return "\\" + (num - 0 + 1);
6477         };
6478
6479 for ( var type in Expr.match ) {
6480         Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
6481         Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
6482 }
6483
6484 var makeArray = function(array, results) {
6485         array = Array.prototype.slice.call( array, 0 );
6486
6487         if ( results ) {
6488                 results.push.apply( results, array );
6489                 return results;
6490         }
6491         
6492         return array;
6493 };
6494
6495 // Perform a simple check to determine if the browser is capable of
6496 // converting a NodeList to an array using builtin methods.
6497 // Also verifies that the returned array holds DOM nodes
6498 // (which is not the case in the Blackberry browser)
6499 try {
6500         Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
6501
6502 // Provide a fallback method if it does not work
6503 } catch(e){
6504         makeArray = function(array, results) {
6505                 var ret = results || [], i = 0;
6506
6507                 if ( toString.call(array) === "[object Array]" ) {
6508                         Array.prototype.push.apply( ret, array );
6509                 } else {
6510                         if ( typeof array.length === "number" ) {
6511                                 for ( var l = array.length; i < l; i++ ) {
6512                                         ret.push( array[i] );
6513                                 }
6514                         } else {
6515                                 for ( ; array[i]; i++ ) {
6516                                         ret.push( array[i] );
6517                                 }
6518                         }
6519                 }
6520
6521                 return ret;
6522         };
6523 }
6524
6525 var sortOrder;
6526
6527 if ( document.documentElement.compareDocumentPosition ) {
6528         sortOrder = function( a, b ) {
6529                 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
6530                         if ( a == b ) {
6531                                 hasDuplicate = true;
6532                         }
6533                         return a.compareDocumentPosition ? -1 : 1;
6534                 }
6535
6536                 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
6537                 if ( ret === 0 ) {
6538                         hasDuplicate = true;
6539                 }
6540                 return ret;
6541         };
6542 } else if ( "sourceIndex" in document.documentElement ) {
6543         sortOrder = function( a, b ) {
6544                 if ( !a.sourceIndex || !b.sourceIndex ) {
6545                         if ( a == b ) {
6546                                 hasDuplicate = true;
6547                         }
6548                         return a.sourceIndex ? -1 : 1;
6549                 }
6550
6551                 var ret = a.sourceIndex - b.sourceIndex;
6552                 if ( ret === 0 ) {
6553                         hasDuplicate = true;
6554                 }
6555                 return ret;
6556         };
6557 } else if ( document.createRange ) {
6558         sortOrder = function( a, b ) {
6559                 if ( !a.ownerDocument || !b.ownerDocument ) {
6560                         if ( a == b ) {
6561                                 hasDuplicate = true;
6562                         }
6563                         return a.ownerDocument ? -1 : 1;
6564                 }
6565
6566                 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
6567                 aRange.setStart(a, 0);
6568                 aRange.setEnd(a, 0);
6569                 bRange.setStart(b, 0);
6570                 bRange.setEnd(b, 0);
6571                 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
6572                 if ( ret === 0 ) {
6573                         hasDuplicate = true;
6574                 }
6575                 return ret;
6576         };
6577 }
6578
6579 // Utility function for retreiving the text value of an array of DOM nodes
6580 Sizzle.getText = function( elems ) {
6581         var ret = "", elem;
6582
6583         for ( var i = 0; elems[i]; i++ ) {
6584                 elem = elems[i];
6585
6586                 // Get the text from text nodes and CDATA nodes
6587                 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
6588                         ret += elem.nodeValue;
6589
6590                 // Traverse everything else, except comment nodes
6591                 } else if ( elem.nodeType !== 8 ) {
6592                         ret += Sizzle.getText( elem.childNodes );
6593                 }
6594         }
6595
6596         return ret;
6597 };
6598
6599 // Check to see if the browser returns elements by name when
6600 // querying by getElementById (and provide a workaround)
6601 (function(){
6602         // We're going to inject a fake input element with a specified name
6603         var form = document.createElement("div"),
6604                 id = "script" + (new Date()).getTime();
6605         form.innerHTML = "<a name='" + id + "'/>";
6606
6607         // Inject it into the root element, check its status, and remove it quickly
6608         var root = document.documentElement;
6609         root.insertBefore( form, root.firstChild );
6610
6611         // The workaround has to do additional checks after a getElementById
6612         // Which slows things down for other browsers (hence the branching)
6613         if ( document.getElementById( id ) ) {
6614                 Expr.find.ID = function(match, context, isXML){
6615                         if ( typeof context.getElementById !== "undefined" && !isXML ) {
6616                                 var m = context.getElementById(match[1]);
6617                                 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
6618                         }
6619                 };
6620
6621                 Expr.filter.ID = function(elem, match){
6622                         var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
6623                         return elem.nodeType === 1 && node && node.nodeValue === match;
6624                 };
6625         }
6626
6627         root.removeChild( form );
6628         root = form = null; // release memory in IE
6629 })();
6630
6631 (function(){
6632         // Check to see if the browser returns only elements
6633         // when doing getElementsByTagName("*")
6634
6635         // Create a fake element
6636         var div = document.createElement("div");
6637         div.appendChild( document.createComment("") );
6638
6639         // Make sure no comments are found
6640         if ( div.getElementsByTagName("*").length > 0 ) {
6641                 Expr.find.TAG = function(match, context){
6642                         var results = context.getElementsByTagName(match[1]);
6643
6644                         // Filter out possible comments
6645                         if ( match[1] === "*" ) {
6646                                 var tmp = [];
6647
6648                                 for ( var i = 0; results[i]; i++ ) {
6649                                         if ( results[i].nodeType === 1 ) {
6650                                                 tmp.push( results[i] );
6651                                         }
6652                                 }
6653
6654                                 results = tmp;
6655                         }
6656
6657                         return results;
6658                 };
6659         }
6660
6661         // Check to see if an attribute returns normalized href attributes
6662         div.innerHTML = "<a href='#'></a>";
6663         if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
6664                         div.firstChild.getAttribute("href") !== "#" ) {
6665                 Expr.attrHandle.href = function(elem){
6666                         return elem.getAttribute("href", 2);
6667                 };
6668         }
6669
6670         div = null; // release memory in IE
6671 })();
6672
6673 if ( document.querySelectorAll ) {
6674         (function(){
6675                 var oldSizzle = Sizzle, div = document.createElement("div");
6676                 div.innerHTML = "<p class='TEST'></p>";
6677
6678                 // Safari can't handle uppercase or unicode characters when
6679                 // in quirks mode.
6680                 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
6681                         return;
6682                 }
6683         
6684                 Sizzle = function(query, context, extra, seed){
6685                         context = context || document;
6686
6687                         // Only use querySelectorAll on non-XML documents
6688                         // (ID selectors don't work in non-HTML documents)
6689                         if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
6690                                 try {
6691                                         return makeArray( context.querySelectorAll(query), extra );
6692                                 } catch(e){}
6693                         }
6694                 
6695                         return oldSizzle(query, context, extra, seed);
6696                 };
6697
6698                 for ( var prop in oldSizzle ) {
6699                         Sizzle[ prop ] = oldSizzle[ prop ];
6700                 }
6701
6702                 div = null; // release memory in IE
6703         })();
6704 }
6705
6706 (function(){
6707         var div = document.createElement("div");
6708
6709         div.innerHTML = "<div class='test e'></div><div class='test'></div>";
6710
6711         // Opera can't find a second classname (in 9.6)
6712         // Also, make sure that getElementsByClassName actually exists
6713         if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
6714                 return;
6715         }
6716
6717         // Safari caches class attributes, doesn't catch changes (in 3.2)
6718         div.lastChild.className = "e";
6719
6720         if ( div.getElementsByClassName("e").length === 1 ) {
6721                 return;
6722         }
6723         
6724         Expr.order.splice(1, 0, "CLASS");
6725         Expr.find.CLASS = function(match, context, isXML) {
6726                 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
6727                         return context.getElementsByClassName(match[1]);
6728                 }
6729         };
6730
6731         div = null; // release memory in IE
6732 })();
6733
6734 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
6735         for ( var i = 0, l = checkSet.length; i < l; i++ ) {
6736                 var elem = checkSet[i];
6737                 if ( elem ) {
6738                         elem = elem[dir];
6739                         var match = false;
6740
6741                         while ( elem ) {
6742                                 if ( elem.sizcache === doneName ) {
6743                                         match = checkSet[elem.sizset];
6744                                         break;
6745                                 }
6746
6747                                 if ( elem.nodeType === 1 && !isXML ){
6748                                         elem.sizcache = doneName;
6749                                         elem.sizset = i;
6750                                 }
6751
6752                                 if ( elem.nodeName.toLowerCase() === cur ) {
6753                                         match = elem;
6754                                         break;
6755                                 }
6756
6757                                 elem = elem[dir];
6758                         }
6759
6760                         checkSet[i] = match;
6761                 }
6762         }
6763 }
6764
6765 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
6766         for ( var i = 0, l = checkSet.length; i < l; i++ ) {
6767                 var elem = checkSet[i];
6768                 if ( elem ) {
6769                         elem = elem[dir];
6770                         var match = false;
6771
6772                         while ( elem ) {
6773                                 if ( elem.sizcache === doneName ) {
6774                                         match = checkSet[elem.sizset];
6775                                         break;
6776                                 }
6777
6778                                 if ( elem.nodeType === 1 ) {
6779                                         if ( !isXML ) {
6780                                                 elem.sizcache = doneName;
6781                                                 elem.sizset = i;
6782                                         }
6783                                         if ( typeof cur !== "string" ) {
6784                                                 if ( elem === cur ) {
6785                                                         match = true;
6786                                                         break;
6787                                                 }
6788
6789                                         } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
6790                                                 match = elem;
6791                                                 break;
6792                                         }
6793                                 }
6794
6795                                 elem = elem[dir];
6796                         }
6797
6798                         checkSet[i] = match;
6799                 }
6800         }
6801 }
6802
6803 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
6804         return !!(a.compareDocumentPosition(b) & 16);
6805 } : function(a, b){
6806         return a !== b && (a.contains ? a.contains(b) : true);
6807 };
6808
6809 Sizzle.isXML = function(elem){
6810         // documentElement is verified for cases where it doesn't yet exist
6811         // (such as loading iframes in IE - #4833) 
6812         var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
6813         return documentElement ? documentElement.nodeName !== "HTML" : false;
6814 };
6815
6816 var posProcess = function(selector, context){
6817         var tmpSet = [], later = "", match,
6818                 root = context.nodeType ? [context] : context;
6819
6820         // Position selectors must be done after the filter
6821         // And so must :not(positional) so we move all PSEUDOs to the end
6822         while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
6823                 later += match[0];
6824                 selector = selector.replace( Expr.match.PSEUDO, "" );
6825         }
6826
6827         selector = Expr.relative[selector] ? selector + "*" : selector;
6828
6829         for ( var i = 0, l = root.length; i < l; i++ ) {
6830                 Sizzle( selector, root[i], tmpSet );
6831         }
6832
6833         return Sizzle.filter( later, tmpSet );
6834 };
6835
6836 // EXPOSE
6837
6838 window.tinymce.dom.Sizzle = Sizzle;
6839
6840 })();
6841
6842
6843 (function(tinymce) {
6844         // Shorten names
6845         var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
6846
6847         tinymce.create('tinymce.dom.EventUtils', {
6848                 EventUtils : function() {
6849                         this.inits = [];
6850                         this.events = [];
6851                 },
6852
6853                 add : function(o, n, f, s) {
6854                         var cb, t = this, el = t.events, r;
6855
6856                         if (n instanceof Array) {
6857                                 r = [];
6858
6859                                 each(n, function(n) {
6860                                         r.push(t.add(o, n, f, s));
6861                                 });
6862
6863                                 return r;
6864                         }
6865
6866                         // Handle array
6867                         if (o && o.hasOwnProperty && o instanceof Array) {
6868                                 r = [];
6869
6870                                 each(o, function(o) {
6871                                         o = DOM.get(o);
6872                                         r.push(t.add(o, n, f, s));
6873                                 });
6874
6875                                 return r;
6876                         }
6877
6878                         o = DOM.get(o);
6879
6880                         if (!o)
6881                                 return;
6882
6883                         // Setup event callback
6884                         cb = function(e) {
6885                                 // Is all events disabled
6886                                 if (t.disabled)
6887                                         return;
6888
6889                                 e = e || window.event;
6890
6891                                 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid
6892                                 if (e && isIE) {
6893                                         if (!e.target)
6894                                                 e.target = e.srcElement;
6895
6896                                         // Patch in preventDefault, stopPropagation methods for W3C compatibility
6897                                         tinymce.extend(e, t._stoppers);
6898                                 }
6899
6900                                 if (!s)
6901                                         return f(e);
6902
6903                                 return f.call(s, e);
6904                         };
6905
6906                         if (n == 'unload') {
6907                                 tinymce.unloads.unshift({func : cb});
6908                                 return cb;
6909                         }
6910
6911                         if (n == 'init') {
6912                                 if (t.domLoaded)
6913                                         cb();
6914                                 else
6915                                         t.inits.push(cb);
6916
6917                                 return cb;
6918                         }
6919
6920                         // Store away listener reference
6921                         el.push({
6922                                 obj : o,
6923                                 name : n,
6924                                 func : f,
6925                                 cfunc : cb,
6926                                 scope : s
6927                         });
6928
6929                         t._add(o, n, cb);
6930
6931                         return f;
6932                 },
6933
6934                 remove : function(o, n, f) {
6935                         var t = this, a = t.events, s = false, r;
6936
6937                         // Handle array
6938                         if (o && o.hasOwnProperty && o instanceof Array) {
6939                                 r = [];
6940
6941                                 each(o, function(o) {
6942                                         o = DOM.get(o);
6943                                         r.push(t.remove(o, n, f));
6944                                 });
6945
6946                                 return r;
6947                         }
6948
6949                         o = DOM.get(o);
6950
6951                         each(a, function(e, i) {
6952                                 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
6953                                         a.splice(i, 1);
6954                                         t._remove(o, n, e.cfunc);
6955                                         s = true;
6956                                         return false;
6957                                 }
6958                         });
6959
6960                         return s;
6961                 },
6962
6963                 clear : function(o) {
6964                         var t = this, a = t.events, i, e;
6965
6966                         if (o) {
6967                                 o = DOM.get(o);
6968
6969                                 for (i = a.length - 1; i >= 0; i--) {
6970                                         e = a[i];
6971
6972                                         if (e.obj === o) {
6973                                                 t._remove(e.obj, e.name, e.cfunc);
6974                                                 e.obj = e.cfunc = null;
6975                                                 a.splice(i, 1);
6976                                         }
6977                                 }
6978                         }
6979                 },
6980
6981                 cancel : function(e) {
6982                         if (!e)
6983                                 return false;
6984
6985                         this.stop(e);
6986
6987                         return this.prevent(e);
6988                 },
6989
6990                 stop : function(e) {
6991                         if (e.stopPropagation)
6992                                 e.stopPropagation();
6993                         else
6994                                 e.cancelBubble = true;
6995
6996                         return false;
6997                 },
6998
6999                 prevent : function(e) {
7000                         if (e.preventDefault)
7001                                 e.preventDefault();
7002                         else
7003                                 e.returnValue = false;
7004
7005                         return false;
7006                 },
7007
7008                 destroy : function() {
7009                         var t = this;
7010
7011                         each(t.events, function(e, i) {
7012                                 t._remove(e.obj, e.name, e.cfunc);
7013                                 e.obj = e.cfunc = null;
7014                         });
7015
7016                         t.events = [];
7017                         t = null;
7018                 },
7019
7020                 _add : function(o, n, f) {
7021                         if (o.attachEvent)
7022                                 o.attachEvent('on' + n, f);
7023                         else if (o.addEventListener)
7024                                 o.addEventListener(n, f, false);
7025                         else
7026                                 o['on' + n] = f;
7027                 },
7028
7029                 _remove : function(o, n, f) {
7030                         if (o) {
7031                                 try {
7032                                         if (o.detachEvent)
7033                                                 o.detachEvent('on' + n, f);
7034                                         else if (o.removeEventListener)
7035                                                 o.removeEventListener(n, f, false);
7036                                         else
7037                                                 o['on' + n] = null;
7038                                 } catch (ex) {
7039                                         // Might fail with permission denined on IE so we just ignore that
7040                                 }
7041                         }
7042                 },
7043
7044                 _pageInit : function(win) {
7045                         var t = this;
7046
7047                         // Keep it from running more than once
7048                         if (t.domLoaded)
7049                                 return;
7050
7051                         t.domLoaded = true;
7052
7053                         each(t.inits, function(c) {
7054                                 c();
7055                         });
7056
7057                         t.inits = [];
7058                 },
7059
7060                 _wait : function(win) {
7061                         var t = this, doc = win.document;
7062
7063                         // No need since the document is already loaded
7064                         if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
7065                                 t.domLoaded = 1;
7066                                 return;
7067                         }
7068
7069                         // Use IE method
7070                         if (doc.attachEvent) {
7071                                 doc.attachEvent("onreadystatechange", function() {
7072                                         if (doc.readyState === "complete") {
7073                                                 doc.detachEvent("onreadystatechange", arguments.callee);
7074                                                 t._pageInit(win);
7075                                         }
7076                                 });
7077
7078                                 if (doc.documentElement.doScroll && win == win.top) {
7079                                         (function() {
7080                                                 if (t.domLoaded)
7081                                                         return;
7082
7083                                                 try {
7084                                                         // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
7085                                                         // http://javascript.nwbox.com/IEContentLoaded/
7086                                                         doc.documentElement.doScroll("left");
7087                                                 } catch (ex) {
7088                                                         setTimeout(arguments.callee, 0);
7089                                                         return;
7090                                                 }
7091
7092                                                 t._pageInit(win);
7093                                         })();
7094                                 }
7095                         } else if (doc.addEventListener) {
7096                                 t._add(win, 'DOMContentLoaded', function() {
7097                                         t._pageInit(win);
7098                                 });
7099                         }
7100
7101                         t._add(win, 'load', function() {
7102                                 t._pageInit(win);
7103                         });
7104                 },
7105
7106                 _stoppers : {
7107                         preventDefault : function() {
7108                                 this.returnValue = false;
7109                         },
7110
7111                         stopPropagation : function() {
7112                                 this.cancelBubble = true;
7113                         }
7114                 }
7115         });
7116
7117         Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
7118
7119         // Dispatch DOM content loaded event for IE and Safari
7120         Event._wait(window);
7121
7122         tinymce.addUnload(function() {
7123                 Event.destroy();
7124         });
7125 })(tinymce);
7126
7127 (function(tinymce) {
7128         tinymce.dom.Element = function(id, settings) {
7129                 var t = this, dom, el;
7130
7131                 t.settings = settings = settings || {};
7132                 t.id = id;
7133                 t.dom = dom = settings.dom || tinymce.DOM;
7134
7135                 // Only IE leaks DOM references, this is a lot faster
7136                 if (!tinymce.isIE)
7137                         el = dom.get(t.id);
7138
7139                 tinymce.each(
7140                                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
7141                                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
7142                                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
7143                                 'isHidden,setHTML,get').split(/,/)
7144                         , function(k) {
7145                                 t[k] = function() {
7146                                         var a = [id], i;
7147
7148                                         for (i = 0; i < arguments.length; i++)
7149                                                 a.push(arguments[i]);
7150
7151                                         a = dom[k].apply(dom, a);
7152                                         t.update(k);
7153
7154                                         return a;
7155                                 };
7156                 });
7157
7158                 tinymce.extend(t, {
7159                         on : function(n, f, s) {
7160                                 return tinymce.dom.Event.add(t.id, n, f, s);
7161                         },
7162
7163                         getXY : function() {
7164                                 return {
7165                                         x : parseInt(t.getStyle('left')),
7166                                         y : parseInt(t.getStyle('top'))
7167                                 };
7168                         },
7169
7170                         getSize : function() {
7171                                 var n = dom.get(t.id);
7172
7173                                 return {
7174                                         w : parseInt(t.getStyle('width') || n.clientWidth),
7175                                         h : parseInt(t.getStyle('height') || n.clientHeight)
7176                                 };
7177                         },
7178
7179                         moveTo : function(x, y) {
7180                                 t.setStyles({left : x, top : y});
7181                         },
7182
7183                         moveBy : function(x, y) {
7184                                 var p = t.getXY();
7185
7186                                 t.moveTo(p.x + x, p.y + y);
7187                         },
7188
7189                         resizeTo : function(w, h) {
7190                                 t.setStyles({width : w, height : h});
7191                         },
7192
7193                         resizeBy : function(w, h) {
7194                                 var s = t.getSize();
7195
7196                                 t.resizeTo(s.w + w, s.h + h);
7197                         },
7198
7199                         update : function(k) {
7200                                 var b;
7201
7202                                 if (tinymce.isIE6 && settings.blocker) {
7203                                         k = k || '';
7204
7205                                         // Ignore getters
7206                                         if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
7207                                                 return;
7208
7209                                         // Remove blocker on remove
7210                                         if (k == 'remove') {
7211                                                 dom.remove(t.blocker);
7212                                                 return;
7213                                         }
7214
7215                                         if (!t.blocker) {
7216                                                 t.blocker = dom.uniqueId();
7217                                                 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
7218                                                 dom.setStyle(b, 'opacity', 0);
7219                                         } else
7220                                                 b = dom.get(t.blocker);
7221
7222                                         dom.setStyles(b, {
7223                                                 left : t.getStyle('left', 1),
7224                                                 top : t.getStyle('top', 1),
7225                                                 width : t.getStyle('width', 1),
7226                                                 height : t.getStyle('height', 1),
7227                                                 display : t.getStyle('display', 1),
7228                                                 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
7229                                         });
7230                                 }
7231                         }
7232                 });
7233         };
7234 })(tinymce);
7235
7236 (function(tinymce) {
7237         function trimNl(s) {
7238                 return s.replace(/[\n\r]+/g, '');
7239         };
7240
7241         // Shorten names
7242         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
7243
7244         tinymce.create('tinymce.dom.Selection', {
7245                 Selection : function(dom, win, serializer) {
7246                         var t = this;
7247
7248                         t.dom = dom;
7249                         t.win = win;
7250                         t.serializer = serializer;
7251
7252                         // Add events
7253                         each([
7254                                 'onBeforeSetContent',
7255
7256                                 'onBeforeGetContent',
7257
7258                                 'onSetContent',
7259
7260                                 'onGetContent'
7261                         ], function(e) {
7262                                 t[e] = new tinymce.util.Dispatcher(t);
7263                         });
7264
7265                         // No W3C Range support
7266                         if (!t.win.getSelection)
7267                                 t.tridentSel = new tinymce.dom.TridentSelection(t);
7268
7269                         if (tinymce.isIE && dom.boxModel)
7270                                 this._fixIESelection();
7271
7272                         // Prevent leaks
7273                         tinymce.addUnload(t.destroy, t);
7274                 },
7275
7276                 setCursorLocation: function(node, offset) {
7277                         var t = this; var r = t.dom.createRng();
7278                         r.setStart(node, offset);
7279                         r.setEnd(node, offset);
7280                         t.setRng(r);
7281                         t.collapse(false);
7282                 },
7283                 getContent : function(s) {
7284                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
7285
7286                         s = s || {};
7287                         wb = wa = '';
7288                         s.get = true;
7289                         s.format = s.format || 'html';
7290                         s.forced_root_block = '';
7291                         t.onBeforeGetContent.dispatch(t, s);
7292
7293                         if (s.format == 'text')
7294                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
7295
7296                         if (r.cloneContents) {
7297                                 n = r.cloneContents();
7298
7299                                 if (n)
7300                                         e.appendChild(n);
7301                         } else if (is(r.item) || is(r.htmlText))
7302                                 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
7303                         else
7304                                 e.innerHTML = r.toString();
7305
7306                         // Keep whitespace before and after
7307                         if (/^\s/.test(e.innerHTML))
7308                                 wb = ' ';
7309
7310                         if (/\s+$/.test(e.innerHTML))
7311                                 wa = ' ';
7312
7313                         s.getInner = true;
7314
7315                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
7316                         t.onGetContent.dispatch(t, s);
7317
7318                         return s.content;
7319                 },
7320
7321                 setContent : function(content, args) {
7322                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
7323
7324                         args = args || {format : 'html'};
7325                         args.set = true;
7326                         content = args.content = content;
7327
7328                         // Dispatch before set content event
7329                         if (!args.no_events)
7330                                 self.onBeforeSetContent.dispatch(self, args);
7331
7332                         content = args.content;
7333
7334                         if (rng.insertNode) {
7335                                 // Make caret marker since insertNode places the caret in the beginning of text after insert
7336                                 content += '<span id="__caret">_</span>';
7337
7338                                 // Delete and insert new node
7339                                 if (rng.startContainer == doc && rng.endContainer == doc) {
7340                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
7341                                         doc.body.innerHTML = content;
7342                                 } else {
7343                                         rng.deleteContents();
7344
7345                                         if (doc.body.childNodes.length == 0) {
7346                                                 doc.body.innerHTML = content;
7347                                         } else {
7348                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges
7349                                                 if (rng.createContextualFragment) {
7350                                                         rng.insertNode(rng.createContextualFragment(content));
7351                                                 } else {
7352                                                         // Fake createContextualFragment call in IE 9
7353                                                         frag = doc.createDocumentFragment();
7354                                                         temp = doc.createElement('div');
7355
7356                                                         frag.appendChild(temp);
7357                                                         temp.outerHTML = content;
7358
7359                                                         rng.insertNode(frag);
7360                                                 }
7361                                         }
7362                                 }
7363
7364                                 // Move to caret marker
7365                                 caretNode = self.dom.get('__caret');
7366
7367                                 // Make sure we wrap it compleatly, Opera fails with a simple select call
7368                                 rng = doc.createRange();
7369                                 rng.setStartBefore(caretNode);
7370                                 rng.setEndBefore(caretNode);
7371                                 self.setRng(rng);
7372
7373                                 // Remove the caret position
7374                                 self.dom.remove('__caret');
7375
7376                                 try {
7377                                         self.setRng(rng);
7378                                 } catch (ex) {
7379                                         // Might fail on Opera for some odd reason
7380                                 }
7381                         } else {
7382                                 if (rng.item) {
7383                                         // Delete content and get caret text selection
7384                                         doc.execCommand('Delete', false, null);
7385                                         rng = self.getRng();
7386                                 }
7387
7388                                 rng.pasteHTML(content);
7389                         }
7390
7391                         // Dispatch set content event
7392                         if (!args.no_events)
7393                                 self.onSetContent.dispatch(self, args);
7394                 },
7395
7396                 getStart : function() {
7397                         var rng = this.getRng(), startElement, parentElement, checkRng, node;
7398
7399                         if (rng.duplicate || rng.item) {
7400                                 // Control selection, return first item
7401                                 if (rng.item)
7402                                         return rng.item(0);
7403
7404                                 // Get start element
7405                                 checkRng = rng.duplicate();
7406                                 checkRng.collapse(1);
7407                                 startElement = checkRng.parentElement();
7408
7409                                 // Check if range parent is inside the start element, then return the inner parent element
7410                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
7411                                 parentElement = node = rng.parentElement();
7412                                 while (node = node.parentNode) {
7413                                         if (node == startElement) {
7414                                                 startElement = parentElement;
7415                                                 break;
7416                                         }
7417                                 }
7418
7419                                 return startElement;
7420                         } else {
7421                                 startElement = rng.startContainer;
7422
7423                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())
7424                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
7425
7426                                 if (startElement && startElement.nodeType == 3)
7427                                         return startElement.parentNode;
7428
7429                                 return startElement;
7430                         }
7431                 },
7432
7433                 getEnd : function() {
7434                         var t = this, r = t.getRng(), e, eo;
7435
7436                         if (r.duplicate || r.item) {
7437                                 if (r.item)
7438                                         return r.item(0);
7439
7440                                 r = r.duplicate();
7441                                 r.collapse(0);
7442                                 e = r.parentElement();
7443
7444                                 if (e && e.nodeName == 'BODY')
7445                                         return e.lastChild || e;
7446
7447                                 return e;
7448                         } else {
7449                                 e = r.endContainer;
7450                                 eo = r.endOffset;
7451
7452                                 if (e.nodeType == 1 && e.hasChildNodes())
7453                                         e = e.childNodes[eo > 0 ? eo - 1 : eo];
7454
7455                                 if (e && e.nodeType == 3)
7456                                         return e.parentNode;
7457
7458                                 return e;
7459                         }
7460                 },
7461
7462                 getBookmark : function(type, normalized) {
7463                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
7464
7465                         function findIndex(name, element) {
7466                                 var index = 0;
7467
7468                                 each(dom.select(name), function(node, i) {
7469                                         if (node == element)
7470                                                 index = i;
7471                                 });
7472
7473                                 return index;
7474                         };
7475
7476                         if (type == 2) {
7477                                 function getLocation() {
7478                                         var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
7479
7480                                         function getPoint(rng, start) {
7481                                                 var container = rng[start ? 'startContainer' : 'endContainer'],
7482                                                         offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
7483
7484                                                 if (container.nodeType == 3) {
7485                                                         if (normalized) {
7486                                                                 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
7487                                                                         offset += node.nodeValue.length;
7488                                                         }
7489
7490                                                         point.push(offset);
7491                                                 } else {
7492                                                         childNodes = container.childNodes;
7493
7494                                                         if (offset >= childNodes.length && childNodes.length) {
7495                                                                 after = 1;
7496                                                                 offset = Math.max(0, childNodes.length - 1);
7497                                                         }
7498
7499                                                         point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
7500                                                 }
7501
7502                                                 for (; container && container != root; container = container.parentNode)
7503                                                         point.push(t.dom.nodeIndex(container, normalized));
7504
7505                                                 return point;
7506                                         };
7507
7508                                         bookmark.start = getPoint(rng, true);
7509
7510                                         if (!t.isCollapsed())
7511                                                 bookmark.end = getPoint(rng);
7512
7513                                         return bookmark;
7514                                 };
7515
7516                                 if (t.tridentSel)
7517                                         return t.tridentSel.getBookmark(type);
7518
7519                                 return getLocation();
7520                         }
7521
7522                         // Handle simple range
7523                         if (type)
7524                                 return {rng : t.getRng()};
7525
7526                         rng = t.getRng();
7527                         id = dom.uniqueId();
7528                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();
7529                         styles = 'overflow:hidden;line-height:0px';
7530
7531                         // Explorer method
7532                         if (rng.duplicate || rng.item) {
7533                                 // Text selection
7534                                 if (!rng.item) {
7535                                         rng2 = rng.duplicate();
7536
7537                                         try {
7538                                                 // Insert start marker
7539                                                 rng.collapse();
7540                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
7541
7542                                                 // Insert end marker
7543                                                 if (!collapsed) {
7544                                                         rng2.collapse(false);
7545
7546                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
7547                                                         rng.moveToElementText(rng2.parentElement());
7548                                                         if (rng.compareEndPoints('StartToEnd', rng2) == 0)
7549                                                                 rng2.move('character', -1);
7550
7551                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
7552                                                 }
7553                                         } catch (ex) {
7554                                                 // IE might throw unspecified error so lets ignore it
7555                                                 return null;
7556                                         }
7557                                 } else {
7558                                         // Control selection
7559                                         element = rng.item(0);
7560                                         name = element.nodeName;
7561
7562                                         return {name : name, index : findIndex(name, element)};
7563                                 }
7564                         } else {
7565                                 element = t.getNode();
7566                                 name = element.nodeName;
7567                                 if (name == 'IMG')
7568                                         return {name : name, index : findIndex(name, element)};
7569
7570                                 // W3C method
7571                                 rng2 = rng.cloneRange();
7572
7573                                 // Insert end marker
7574                                 if (!collapsed) {
7575                                         rng2.collapse(false);
7576                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
7577                                 }
7578
7579                                 rng.collapse(true);
7580                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
7581                         }
7582
7583                         t.moveToBookmark({id : id, keep : 1});
7584
7585                         return {id : id};
7586                 },
7587
7588                 moveToBookmark : function(bookmark) {
7589                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
7590
7591                         if (bookmark) {
7592                                 if (bookmark.start) {
7593                                         rng = dom.createRng();
7594                                         root = dom.getRoot();
7595
7596                                         function setEndPoint(start) {
7597                                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
7598
7599                                                 if (point) {
7600                                                         offset = point[0];
7601
7602                                                         // Find container node
7603                                                         for (node = root, i = point.length - 1; i >= 1; i--) {
7604                                                                 children = node.childNodes;
7605
7606                                                                 if (point[i] > children.length - 1)
7607                                                                         return;
7608
7609                                                                 node = children[point[i]];
7610                                                         }
7611
7612                                                         // Move text offset to best suitable location
7613                                                         if (node.nodeType === 3)
7614                                                                 offset = Math.min(point[0], node.nodeValue.length);
7615
7616                                                         // Move element offset to best suitable location
7617                                                         if (node.nodeType === 1)
7618                                                                 offset = Math.min(point[0], node.childNodes.length);
7619
7620                                                         // Set offset within container node
7621                                                         if (start)
7622                                                                 rng.setStart(node, offset);
7623                                                         else
7624                                                                 rng.setEnd(node, offset);
7625                                                 }
7626
7627                                                 return true;
7628                                         };
7629
7630                                         if (t.tridentSel)
7631                                                 return t.tridentSel.moveToBookmark(bookmark);
7632
7633                                         if (setEndPoint(true) && setEndPoint()) {
7634                                                 t.setRng(rng);
7635                                         }
7636                                 } else if (bookmark.id) {
7637                                         function restoreEndPoint(suffix) {
7638                                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
7639
7640                                                 if (marker) {
7641                                                         node = marker.parentNode;
7642
7643                                                         if (suffix == 'start') {
7644                                                                 if (!keep) {
7645                                                                         idx = dom.nodeIndex(marker);
7646                                                                 } else {
7647                                                                         node = marker.firstChild;
7648                                                                         idx = 1;
7649                                                                 }
7650
7651                                                                 startContainer = endContainer = node;
7652                                                                 startOffset = endOffset = idx;
7653                                                         } else {
7654                                                                 if (!keep) {
7655                                                                         idx = dom.nodeIndex(marker);
7656                                                                 } else {
7657                                                                         node = marker.firstChild;
7658                                                                         idx = 1;
7659                                                                 }
7660
7661                                                                 endContainer = node;
7662                                                                 endOffset = idx;
7663                                                         }
7664
7665                                                         if (!keep) {
7666                                                                 prev = marker.previousSibling;
7667                                                                 next = marker.nextSibling;
7668
7669                                                                 // Remove all marker text nodes
7670                                                                 each(tinymce.grep(marker.childNodes), function(node) {
7671                                                                         if (node.nodeType == 3)
7672                                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
7673                                                                 });
7674
7675                                                                 // Remove marker but keep children if for example contents where inserted into the marker
7676                                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
7677                                                                 while (marker = dom.get(bookmark.id + '_' + suffix))
7678                                                                         dom.remove(marker, 1);
7679
7680                                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
7681                                                                 // 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
7682                                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
7683                                                                         idx = prev.nodeValue.length;
7684                                                                         prev.appendData(next.nodeValue);
7685                                                                         dom.remove(next);
7686
7687                                                                         if (suffix == 'start') {
7688                                                                                 startContainer = endContainer = prev;
7689                                                                                 startOffset = endOffset = idx;
7690                                                                         } else {
7691                                                                                 endContainer = prev;
7692                                                                                 endOffset = idx;
7693                                                                         }
7694                                                                 }
7695                                                         }
7696                                                 }
7697                                         };
7698
7699                                         function addBogus(node) {
7700                                                 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
7701                                                 if (dom.isBlock(node) && !node.innerHTML)
7702                                                         node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
7703
7704                                                 return node;
7705                                         };
7706
7707                                         // Restore start/end points
7708                                         restoreEndPoint('start');
7709                                         restoreEndPoint('end');
7710
7711                                         if (startContainer) {
7712                                                 rng = dom.createRng();
7713                                                 rng.setStart(addBogus(startContainer), startOffset);
7714                                                 rng.setEnd(addBogus(endContainer), endOffset);
7715                                                 t.setRng(rng);
7716                                         }
7717                                 } else if (bookmark.name) {
7718                                         t.select(dom.select(bookmark.name)[bookmark.index]);
7719                                 } else if (bookmark.rng)
7720                                         t.setRng(bookmark.rng);
7721                         }
7722                 },
7723
7724                 select : function(node, content) {
7725                         var t = this, dom = t.dom, rng = dom.createRng(), idx;
7726
7727                         if (node) {
7728                                 idx = dom.nodeIndex(node);
7729                                 rng.setStart(node.parentNode, idx);
7730                                 rng.setEnd(node.parentNode, idx + 1);
7731
7732                                 // Find first/last text node or BR element
7733                                 if (content) {
7734                                         function setPoint(node, start) {
7735                                                 var walker = new tinymce.dom.TreeWalker(node, node);
7736
7737                                                 do {
7738                                                         // Text node
7739                                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
7740                                                                 if (start)
7741                                                                         rng.setStart(node, 0);
7742                                                                 else
7743                                                                         rng.setEnd(node, node.nodeValue.length);
7744
7745                                                                 return;
7746                                                         }
7747
7748                                                         // BR element
7749                                                         if (node.nodeName == 'BR') {
7750                                                                 if (start)
7751                                                                         rng.setStartBefore(node);
7752                                                                 else
7753                                                                         rng.setEndBefore(node);
7754
7755                                                                 return;
7756                                                         }
7757                                                 } while (node = (start ? walker.next() : walker.prev()));
7758                                         };
7759
7760                                         setPoint(node, 1);
7761                                         setPoint(node);
7762                                 }
7763
7764                                 t.setRng(rng);
7765                         }
7766
7767                         return node;
7768                 },
7769
7770                 isCollapsed : function() {
7771                         var t = this, r = t.getRng(), s = t.getSel();
7772
7773                         if (!r || r.item)
7774                                 return false;
7775
7776                         if (r.compareEndPoints)
7777                                 return r.compareEndPoints('StartToEnd', r) === 0;
7778
7779                         return !s || r.collapsed;
7780                 },
7781
7782                 collapse : function(to_start) {
7783                         var self = this, rng = self.getRng(), node;
7784
7785                         // Control range on IE
7786                         if (rng.item) {
7787                                 node = rng.item(0);
7788                                 rng = self.win.document.body.createTextRange();
7789                                 rng.moveToElementText(node);
7790                         }
7791
7792                         rng.collapse(!!to_start);
7793                         self.setRng(rng);
7794                 },
7795
7796                 getSel : function() {
7797                         var t = this, w = this.win;
7798
7799                         return w.getSelection ? w.getSelection() : w.document.selection;
7800                 },
7801
7802                 getRng : function(w3c) {
7803                         var t = this, s, r, elm, doc = t.win.document;
7804
7805                         // Found tridentSel object then we need to use that one
7806                         if (w3c && t.tridentSel)
7807                                 return t.tridentSel.getRangeAt(0);
7808
7809                         try {
7810                                 if (s = t.getSel())
7811                                         r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
7812                         } catch (ex) {
7813                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
7814                         }
7815
7816                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
7817                         if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
7818                                 elm = doc.selection.createRange().item(0);
7819                                 r = doc.createRange();
7820                                 r.setStartBefore(elm);
7821                                 r.setEndAfter(elm);
7822                         }
7823
7824                         // No range found then create an empty one
7825                         // This can occur when the editor is placed in a hidden container element on Gecko
7826                         // Or on IE when there was an exception
7827                         if (!r)
7828                                 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
7829
7830                         if (t.selectedRange && t.explicitRange) {
7831                                 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
7832                                         // Safari, Opera and Chrome only ever select text which causes the range to change.
7833                                         // This lets us use the originally set range if the selection hasn't been changed by the user.
7834                                         r = t.explicitRange;
7835                                 } else {
7836                                         t.selectedRange = null;
7837                                         t.explicitRange = null;
7838                                 }
7839                         }
7840
7841                         return r;
7842                 },
7843
7844                 setRng : function(r) {
7845                         var s, t = this;
7846                         
7847                         if (!t.tridentSel) {
7848                                 s = t.getSel();
7849
7850                                 if (s) {
7851                                         t.explicitRange = r;
7852
7853                                         try {
7854                                                 s.removeAllRanges();
7855                                         } catch (ex) {
7856                                                 // IE9 might throw errors here don't know why
7857                                         }
7858
7859                                         s.addRange(r);
7860                                         t.selectedRange = s.getRangeAt(0);
7861                                 }
7862                         } else {
7863                                 // Is W3C Range
7864                                 if (r.cloneRange) {
7865                                         t.tridentSel.addRange(r);
7866                                         return;
7867                                 }
7868
7869                                 // Is IE specific range
7870                                 try {
7871                                         r.select();
7872                                 } catch (ex) {
7873                                         // Needed for some odd IE bug #1843306
7874                                 }
7875                         }
7876                 },
7877
7878                 setNode : function(n) {
7879                         var t = this;
7880
7881                         t.setContent(t.dom.getOuterHTML(n));
7882
7883                         return n;
7884                 },
7885
7886                 getNode : function() {
7887                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
7888
7889                         // Range maybe lost after the editor is made visible again
7890                         if (!rng)
7891                                 return t.dom.getRoot();
7892
7893                         if (rng.setStart) {
7894                                 elm = rng.commonAncestorContainer;
7895
7896                                 // Handle selection a image or other control like element such as anchors
7897                                 if (!rng.collapsed) {
7898                                         if (rng.startContainer == rng.endContainer) {
7899                                                 if (rng.endOffset - rng.startOffset < 2) {
7900                                                         if (rng.startContainer.hasChildNodes())
7901                                                                 elm = rng.startContainer.childNodes[rng.startOffset];
7902                                                 }
7903                                         }
7904
7905                                         // If the anchor node is a element instead of a text node then return this element
7906                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) 
7907                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];
7908
7909                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
7910                                         // This happens when you double click an underlined word in FireFox.
7911                                         if (start.nodeType === 3 && end.nodeType === 3) {
7912                                                 function skipEmptyTextNodes(n, forwards) {
7913                                                         var orig = n;
7914                                                         while (n && n.nodeType === 3 && n.length === 0) {
7915                                                                 n = forwards ? n.nextSibling : n.previousSibling;
7916                                                         }
7917                                                         return n || orig;
7918                                                 }
7919                                                 if (start.length === rng.startOffset) {
7920                                                         start = skipEmptyTextNodes(start.nextSibling, true);
7921                                                 } else {
7922                                                         start = start.parentNode;
7923                                                 }
7924                                                 if (rng.endOffset === 0) {
7925                                                         end = skipEmptyTextNodes(end.previousSibling, false);
7926                                                 } else {
7927                                                         end = end.parentNode;
7928                                                 }
7929
7930                                                 if (start && start === end)
7931                                                         return start;
7932                                         }
7933                                 }
7934
7935                                 if (elm && elm.nodeType == 3)
7936                                         return elm.parentNode;
7937
7938                                 return elm;
7939                         }
7940
7941                         return rng.item ? rng.item(0) : rng.parentElement();
7942                 },
7943
7944                 getSelectedBlocks : function(st, en) {
7945                         var t = this, dom = t.dom, sb, eb, n, bl = [];
7946
7947                         sb = dom.getParent(st || t.getStart(), dom.isBlock);
7948                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);
7949
7950                         if (sb)
7951                                 bl.push(sb);
7952
7953                         if (sb && eb && sb != eb) {
7954                                 n = sb;
7955
7956                                 while ((n = n.nextSibling) && n != eb) {
7957                                         if (dom.isBlock(n))
7958                                                 bl.push(n);
7959                                 }
7960                         }
7961
7962                         if (eb && sb != eb)
7963                                 bl.push(eb);
7964
7965                         return bl;
7966                 },
7967
7968                 normalize : function() {
7969                         var self = this, rng, normalized;
7970
7971                         // Normalize only on non IE browsers for now
7972                         if (tinymce.isIE)
7973                                 return;
7974
7975                         function normalizeEndPoint(start) {
7976                                 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node;
7977
7978                                 container = rng[(start ? 'start' : 'end') + 'Container'];
7979                                 offset = rng[(start ? 'start' : 'end') + 'Offset'];
7980
7981                                 // If the container is a document move it to the body element
7982                                 if (container.nodeType === 9) {
7983                                         container = container.body;
7984                                         offset = 0;
7985                                 }
7986
7987                                 // If the container is body try move it into the closest text node or position
7988                                 // TODO: Add more logic here to handle element selection cases
7989                                 if (container === body) {
7990                                         // Resolve the index
7991                                         if (container.hasChildNodes()) {
7992                                                 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
7993                                                 offset = 0;
7994
7995                                                 // Walk the DOM to find a text node to place the caret at or a BR
7996                                                 node = container;
7997                                                 walker = new tinymce.dom.TreeWalker(container, body);
7998                                                 do {
7999                                                         // Found a text node use that position
8000                                                         if (node.nodeType === 3) {
8001                                                                 offset = start ? 0 : node.nodeValue.length - 1;
8002                                                                 container = node;
8003                                                                 break;
8004                                                         }
8005
8006                                                         // Found a BR element that we can place the caret before
8007                                                         if (node.nodeName === 'BR') {
8008                                                                 offset = dom.nodeIndex(node);
8009                                                                 container = node.parentNode;
8010                                                                 break;
8011                                                         }
8012                                                 } while (node = (start ? walker.next() : walker.prev()));
8013
8014                                                 normalized = true;
8015                                         }
8016                                 }
8017
8018                                 // Set endpoint if it was normalized
8019                                 if (normalized)
8020                                         rng['set' + (start ? 'Start' : 'End')](container, offset);
8021                         };
8022
8023                         rng = self.getRng();
8024
8025                         // Normalize the end points
8026                         normalizeEndPoint(true);
8027                         
8028                         if (rng.collapsed)
8029                                 normalizeEndPoint();
8030
8031                         // Set the selection if it was normalized
8032                         if (normalized) {
8033                                 //console.log(self.dom.dumpRng(rng));
8034                                 self.setRng(rng);
8035                         }
8036                 },
8037
8038                 destroy : function(s) {
8039                         var t = this;
8040
8041                         t.win = null;
8042
8043                         // Manual destroy then remove unload handler
8044                         if (!s)
8045                                 tinymce.removeUnload(t.destroy);
8046                 },
8047
8048                 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
8049                 _fixIESelection : function() {
8050                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
8051
8052                         // Make HTML element unselectable since we are going to handle selection by hand
8053                         doc.documentElement.unselectable = true;
8054
8055                         // Return range from point or null if it failed
8056                         function rngFromPoint(x, y) {
8057                                 var rng = body.createTextRange();
8058
8059                                 try {
8060                                         rng.moveToPoint(x, y);
8061                                 } catch (ex) {
8062                                         // IE sometimes throws and exception, so lets just ignore it
8063                                         rng = null;
8064                                 }
8065
8066                                 return rng;
8067                         };
8068
8069                         // Fires while the selection is changing
8070                         function selectionChange(e) {
8071                                 var pointRng;
8072
8073                                 // Check if the button is down or not
8074                                 if (e.button) {
8075                                         // Create range from mouse position
8076                                         pointRng = rngFromPoint(e.x, e.y);
8077
8078                                         if (pointRng) {
8079                                                 // Check if pointRange is before/after selection then change the endPoint
8080                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
8081                                                         pointRng.setEndPoint('StartToStart', startRng);
8082                                                 else
8083                                                         pointRng.setEndPoint('EndToEnd', startRng);
8084
8085                                                 pointRng.select();
8086                                         }
8087                                 } else
8088                                         endSelection();
8089                         }
8090
8091                         // Removes listeners
8092                         function endSelection() {
8093                                 var rng = doc.selection.createRange();
8094
8095                                 // If the range is collapsed then use the last start range
8096                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
8097                                         startRng.select();
8098
8099                                 dom.unbind(doc, 'mouseup', endSelection);
8100                                 dom.unbind(doc, 'mousemove', selectionChange);
8101                                 startRng = started = 0;
8102                         };
8103
8104                         // Detect when user selects outside BODY
8105                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
8106                                 if (e.target.nodeName === 'HTML') {
8107                                         if (started)
8108                                                 endSelection();
8109
8110                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
8111                                         htmlElm = doc.documentElement;
8112                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)
8113                                                 return;
8114
8115                                         started = 1;
8116                                         // Setup start position
8117                                         startRng = rngFromPoint(e.x, e.y);
8118                                         if (startRng) {
8119                                                 // Listen for selection change events
8120                                                 dom.bind(doc, 'mouseup', endSelection);
8121                                                 dom.bind(doc, 'mousemove', selectionChange);
8122
8123                                                 dom.win.focus();
8124                                                 startRng.select();
8125                                         }
8126                                 }
8127                         });
8128                 }
8129         });
8130 })(tinymce);
8131
8132 (function(tinymce) {
8133         tinymce.dom.Serializer = function(settings, dom, schema) {
8134                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
8135
8136                 // Support the old apply_source_formatting option
8137                 if (!settings.apply_source_formatting)
8138                         settings.indent = false;
8139
8140                 settings.remove_trailing_brs = true;
8141
8142                 // Default DOM and Schema if they are undefined
8143                 dom = dom || tinymce.DOM;
8144                 schema = schema || new tinymce.html.Schema(settings);
8145                 settings.entity_encoding = settings.entity_encoding || 'named';
8146
8147                 onPreProcess = new tinymce.util.Dispatcher(self);
8148
8149                 onPostProcess = new tinymce.util.Dispatcher(self);
8150
8151                 htmlParser = new tinymce.html.DomParser(settings, schema);
8152
8153                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
8154                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
8155                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
8156
8157                         while (i--) {
8158                                 node = nodes[i];
8159
8160                                 value = node.attributes.map[internalName];
8161                                 if (value !== undef) {
8162                                         // Set external name to internal value and remove internal
8163                                         node.attr(name, value.length > 0 ? value : null);
8164                                         node.attr(internalName, null);
8165                                 } else {
8166                                         // No internal attribute found then convert the value we have in the DOM
8167                                         value = node.attributes.map[name];
8168
8169                                         if (name === "style")
8170                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);
8171                                         else if (urlConverter)
8172                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);
8173
8174                                         node.attr(name, value.length > 0 ? value : null);
8175                                 }
8176                         }
8177                 });
8178
8179                 // Remove internal classes mceItem<..>
8180                 htmlParser.addAttributeFilter('class', function(nodes, name) {
8181                         var i = nodes.length, node, value;
8182
8183                         while (i--) {
8184                                 node = nodes[i];
8185                                 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
8186                                 node.attr('class', value.length > 0 ? value : null);
8187                         }
8188                 });
8189
8190                 // Remove bookmark elements
8191                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
8192                         var i = nodes.length, node;
8193
8194                         while (i--) {
8195                                 node = nodes[i];
8196
8197                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
8198                                         node.remove();
8199                         }
8200                 });
8201
8202                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles
8203                 htmlParser.addNodeFilter('script,style', function(nodes, name) {
8204                         var i = nodes.length, node, value;
8205
8206                         function trim(value) {
8207                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
8208                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')
8209                                                 .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
8210                                                 .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
8211                         };
8212
8213                         while (i--) {
8214                                 node = nodes[i];
8215                                 value = node.firstChild ? node.firstChild.value : '';
8216
8217                                 if (name === "script") {
8218                                         // Remove mce- prefix from script elements
8219                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
8220
8221                                         if (value.length > 0)
8222                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
8223                                 } else {
8224                                         if (value.length > 0)
8225                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
8226                                 }
8227                         }
8228                 });
8229
8230                 // Convert comments to cdata and handle protected comments
8231                 htmlParser.addNodeFilter('#comment', function(nodes, name) {
8232                         var i = nodes.length, node;
8233
8234                         while (i--) {
8235                                 node = nodes[i];
8236
8237                                 if (node.value.indexOf('[CDATA[') === 0) {
8238                                         node.name = '#cdata';
8239                                         node.type = 4;
8240                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
8241                                 } else if (node.value.indexOf('mce:protected ') === 0) {
8242                                         node.name = "#text";
8243                                         node.type = 3;
8244                                         node.raw = true;
8245                                         node.value = unescape(node.value).substr(14);
8246                                 }
8247                         }
8248                 });
8249
8250                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
8251                         var i = nodes.length, node;
8252
8253                         while (i--) {
8254                                 node = nodes[i];
8255                                 if (node.type === 7)
8256                                         node.remove();
8257                                 else if (node.type === 1) {
8258                                         if (name === "input" && !("type" in node.attributes.map))
8259                                                 node.attr('type', 'text');
8260                                 }
8261                         }
8262                 });
8263
8264                 // Fix list elements, TODO: Replace this later
8265                 if (settings.fix_list_elements) {
8266                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
8267                                 var i = nodes.length, node, parentNode;
8268
8269                                 while (i--) {
8270                                         node = nodes[i];
8271                                         parentNode = node.parent;
8272
8273                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {
8274                                                 if (node.prev && node.prev.name === 'li') {
8275                                                         node.prev.append(node);
8276                                                 }
8277                                         }
8278                                 }
8279                         });
8280                 }
8281
8282                 // Remove internal data attributes
8283                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
8284                         var i = nodes.length;
8285
8286                         while (i--) {
8287                                 nodes[i].attr(name, null);
8288                         }
8289                 });
8290
8291                 // Return public methods
8292                 return {
8293                         schema : schema,
8294
8295                         addNodeFilter : htmlParser.addNodeFilter,
8296
8297                         addAttributeFilter : htmlParser.addAttributeFilter,
8298
8299                         onPreProcess : onPreProcess,
8300
8301                         onPostProcess : onPostProcess,
8302
8303                         serialize : function(node, args) {
8304                                 var impl, doc, oldDoc, htmlSerializer, content;
8305
8306                                 // Explorer won't clone contents of script and style and the
8307                                 // selected index of select elements are cleared on a clone operation.
8308                                 if (isIE && dom.select('script,style,select').length > 0) {
8309                                         content = node.innerHTML;
8310                                         node = node.cloneNode(false);
8311                                         dom.setHTML(node, content);
8312                                 } else
8313                                         node = node.cloneNode(true);
8314
8315                                 // Nodes needs to be attached to something in WebKit/Opera
8316                                 // Older builds of Opera crashes if you attach the node to an document created dynamically
8317                                 // and since we can't feature detect a crash we need to sniff the acutal build number
8318                                 // This fix will make DOM ranges and make Sizzle happy!
8319                                 impl = node.ownerDocument.implementation;
8320                                 if (impl.createHTMLDocument) {
8321                                         // Create an empty HTML document
8322                                         doc = impl.createHTMLDocument("");
8323
8324                                         // Add the element or it's children if it's a body element to the new document
8325                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
8326                                                 doc.body.appendChild(doc.importNode(node, true));
8327                                         });
8328
8329                                         // Grab first child or body element for serialization
8330                                         if (node.nodeName != 'BODY')
8331                                                 node = doc.body.firstChild;
8332                                         else
8333                                                 node = doc.body;
8334
8335                                         // set the new document in DOMUtils so createElement etc works
8336                                         oldDoc = dom.doc;
8337                                         dom.doc = doc;
8338                                 }
8339
8340                                 args = args || {};
8341                                 args.format = args.format || 'html';
8342
8343                                 // Pre process
8344                                 if (!args.no_events) {
8345                                         args.node = node;
8346                                         onPreProcess.dispatch(self, args);
8347                                 }
8348
8349                                 // Setup serializer
8350                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);
8351
8352                                 // Parse and serialize HTML
8353                                 args.content = htmlSerializer.serialize(
8354                                         htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
8355                                 );
8356
8357                                 // Replace all BOM characters for now until we can find a better solution
8358                                 if (!args.cleanup)
8359                                         args.content = args.content.replace(/\uFEFF/g, '');
8360
8361                                 // Post process
8362                                 if (!args.no_events)
8363                                         onPostProcess.dispatch(self, args);
8364
8365                                 // Restore the old document if it was changed
8366                                 if (oldDoc)
8367                                         dom.doc = oldDoc;
8368
8369                                 args.node = null;
8370
8371                                 return args.content;
8372                         },
8373
8374                         addRules : function(rules) {
8375                                 schema.addValidElements(rules);
8376                         },
8377
8378                         setRules : function(rules) {
8379                                 schema.setValidElements(rules);
8380                         }
8381                 };
8382         };
8383 })(tinymce);
8384 (function(tinymce) {
8385         tinymce.dom.ScriptLoader = function(settings) {
8386                 var QUEUED = 0,
8387                         LOADING = 1,
8388                         LOADED = 2,
8389                         states = {},
8390                         queue = [],
8391                         scriptLoadedCallbacks = {},
8392                         queueLoadedCallbacks = [],
8393                         loading = 0,
8394                         undefined;
8395
8396                 function loadScript(url, callback) {
8397                         var t = this, dom = tinymce.DOM, elm, uri, loc, id;
8398
8399                         // Execute callback when script is loaded
8400                         function done() {
8401                                 dom.remove(id);
8402
8403                                 if (elm)
8404                                         elm.onreadystatechange = elm.onload = elm = null;
8405
8406                                 callback();
8407                         };
8408                         
8409                         function error() {
8410                                 // Report the error so it's easier for people to spot loading errors
8411                                 if (typeof(console) !== "undefined" && console.log)
8412                                         console.log("Failed to load: " + url);
8413
8414                                 // We can't mark it as done if there is a load error since
8415                                 // A) We don't want to produce 404 errors on the server and
8416                                 // B) the onerror event won't fire on all browsers.
8417                                 // done();
8418                         };
8419
8420                         id = dom.uniqueId();
8421
8422                         if (tinymce.isIE6) {
8423                                 uri = new tinymce.util.URI(url);
8424                                 loc = location;
8425
8426                                 // If script is from same domain and we
8427                                 // use IE 6 then use XHR since it's more reliable
8428                                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
8429                                         tinymce.util.XHR.send({
8430                                                 url : tinymce._addVer(uri.getURI()),
8431                                                 success : function(content) {
8432                                                         // Create new temp script element
8433                                                         var script = dom.create('script', {
8434                                                                 type : 'text/javascript'
8435                                                         });
8436
8437                                                         // Evaluate script in global scope
8438                                                         script.text = content;
8439                                                         document.getElementsByTagName('head')[0].appendChild(script);
8440                                                         dom.remove(script);
8441
8442                                                         done();
8443                                                 },
8444                                                 
8445                                                 error : error
8446                                         });
8447
8448                                         return;
8449                                 }
8450                         }
8451
8452                         // Create new script element
8453                         elm = dom.create('script', {
8454                                 id : id,
8455                                 type : 'text/javascript',
8456                                 src : tinymce._addVer(url)
8457                         });
8458
8459                         // Add onload listener for non IE browsers since IE9
8460                         // fires onload event before the script is parsed and executed
8461                         if (!tinymce.isIE)
8462                                 elm.onload = done;
8463
8464                         // Add onerror event will get fired on some browsers but not all of them
8465                         elm.onerror = error;
8466
8467                         // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
8468                         if (!tinymce.isOpera) {
8469                                 elm.onreadystatechange = function() {
8470                                         var state = elm.readyState;
8471
8472                                         // Loaded state is passed on IE 6 however there
8473                                         // are known issues with this method but we can't use
8474                                         // XHR in a cross domain loading
8475                                         if (state == 'complete' || state == 'loaded')
8476                                                 done();
8477                                 };
8478                         }
8479
8480                         // Most browsers support this feature so we report errors
8481                         // for those at least to help users track their missing plugins etc
8482                         // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
8483                         /*elm.onerror = function() {
8484                                 alert('Failed to load: ' + url);
8485                         };*/
8486
8487                         // Add script to document
8488                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
8489                 };
8490
8491                 this.isDone = function(url) {
8492                         return states[url] == LOADED;
8493                 };
8494
8495                 this.markDone = function(url) {
8496                         states[url] = LOADED;
8497                 };
8498
8499                 this.add = this.load = function(url, callback, scope) {
8500                         var item, state = states[url];
8501
8502                         // Add url to load queue
8503                         if (state == undefined) {
8504                                 queue.push(url);
8505                                 states[url] = QUEUED;
8506                         }
8507
8508                         if (callback) {
8509                                 // Store away callback for later execution
8510                                 if (!scriptLoadedCallbacks[url])
8511                                         scriptLoadedCallbacks[url] = [];
8512
8513                                 scriptLoadedCallbacks[url].push({
8514                                         func : callback,
8515                                         scope : scope || this
8516                                 });
8517                         }
8518                 };
8519
8520                 this.loadQueue = function(callback, scope) {
8521                         this.loadScripts(queue, callback, scope);
8522                 };
8523
8524                 this.loadScripts = function(scripts, callback, scope) {
8525                         var loadScripts;
8526
8527                         function execScriptLoadedCallbacks(url) {
8528                                 // Execute URL callback functions
8529                                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {
8530                                         callback.func.call(callback.scope);
8531                                 });
8532
8533                                 scriptLoadedCallbacks[url] = undefined;
8534                         };
8535
8536                         queueLoadedCallbacks.push({
8537                                 func : callback,
8538                                 scope : scope || this
8539                         });
8540
8541                         loadScripts = function() {
8542                                 var loadingScripts = tinymce.grep(scripts);
8543
8544                                 // Current scripts has been handled
8545                                 scripts.length = 0;
8546
8547                                 // Load scripts that needs to be loaded
8548                                 tinymce.each(loadingScripts, function(url) {
8549                                         // Script is already loaded then execute script callbacks directly
8550                                         if (states[url] == LOADED) {
8551                                                 execScriptLoadedCallbacks(url);
8552                                                 return;
8553                                         }
8554
8555                                         // Is script not loading then start loading it
8556                                         if (states[url] != LOADING) {
8557                                                 states[url] = LOADING;
8558                                                 loading++;
8559
8560                                                 loadScript(url, function() {
8561                                                         states[url] = LOADED;
8562                                                         loading--;
8563
8564                                                         execScriptLoadedCallbacks(url);
8565
8566                                                         // Load more scripts if they where added by the recently loaded script
8567                                                         loadScripts();
8568                                                 });
8569                                         }
8570                                 });
8571
8572                                 // No scripts are currently loading then execute all pending queue loaded callbacks
8573                                 if (!loading) {
8574                                         tinymce.each(queueLoadedCallbacks, function(callback) {
8575                                                 callback.func.call(callback.scope);
8576                                         });
8577
8578                                         queueLoadedCallbacks.length = 0;
8579                                 }
8580                         };
8581
8582                         loadScripts();
8583                 };
8584         };
8585
8586         // Global script loader
8587         tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
8588 })(tinymce);
8589
8590 tinymce.dom.TreeWalker = function(start_node, root_node) {
8591         var node = start_node;
8592
8593         function findSibling(node, start_name, sibling_name, shallow) {
8594                 var sibling, parent;
8595
8596                 if (node) {
8597                         // Walk into nodes if it has a start
8598                         if (!shallow && node[start_name])
8599                                 return node[start_name];
8600
8601                         // Return the sibling if it has one
8602                         if (node != root_node) {
8603                                 sibling = node[sibling_name];
8604                                 if (sibling)
8605                                         return sibling;
8606
8607                                 // Walk up the parents to look for siblings
8608                                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
8609                                         sibling = parent[sibling_name];
8610                                         if (sibling)
8611                                                 return sibling;
8612                                 }
8613                         }
8614                 }
8615         };
8616
8617         this.current = function() {
8618                 return node;
8619         };
8620
8621         this.next = function(shallow) {
8622                 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
8623         };
8624
8625         this.prev = function(shallow) {
8626                 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
8627         };
8628 };
8629
8630 (function(tinymce) {
8631         tinymce.dom.RangeUtils = function(dom) {
8632                 var INVISIBLE_CHAR = '\uFEFF';
8633
8634                 this.walk = function(rng, callback) {
8635                         var startContainer = rng.startContainer,
8636                                 startOffset = rng.startOffset,
8637                                 endContainer = rng.endContainer,
8638                                 endOffset = rng.endOffset,
8639                                 ancestor, startPoint,
8640                                 endPoint, node, parent, siblings, nodes;
8641
8642                         // Handle table cell selection the table plugin enables
8643                         // you to fake select table cells and perform formatting actions on them
8644                         nodes = dom.select('td.mceSelected,th.mceSelected');
8645                         if (nodes.length > 0) {
8646                                 tinymce.each(nodes, function(node) {
8647                                         callback([node]);
8648                                 });
8649
8650                                 return;
8651                         }
8652
8653                         function collectSiblings(node, name, end_node) {
8654                                 var siblings = [];
8655
8656                                 for (; node && node != end_node; node = node[name])
8657                                         siblings.push(node);
8658
8659                                 return siblings;
8660                         };
8661
8662                         function findEndPoint(node, root) {
8663                                 do {
8664                                         if (node.parentNode == root)
8665                                                 return node;
8666
8667                                         node = node.parentNode;
8668                                 } while(node);
8669                         };
8670
8671                         function walkBoundary(start_node, end_node, next) {
8672                                 var siblingName = next ? 'nextSibling' : 'previousSibling';
8673
8674                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
8675                                         parent = node.parentNode;
8676                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
8677
8678                                         if (siblings.length) {
8679                                                 if (!next)
8680                                                         siblings.reverse();
8681
8682                                                 callback(siblings);
8683                                         }
8684                                 }
8685                         };
8686
8687                         // If index based start position then resolve it
8688                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
8689                                 startContainer = startContainer.childNodes[startOffset];
8690
8691                         // If index based end position then resolve it
8692                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
8693                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
8694
8695                         // Find common ancestor and end points
8696                         ancestor = dom.findCommonAncestor(startContainer, endContainer);
8697
8698                         // Same container
8699                         if (startContainer == endContainer)
8700                                 return callback([startContainer]);
8701
8702                         // Process left side
8703                         for (node = startContainer; node; node = node.parentNode) {
8704                                 if (node == endContainer)
8705                                         return walkBoundary(startContainer, ancestor, true);
8706
8707                                 if (node == ancestor)
8708                                         break;
8709                         }
8710
8711                         // Process right side
8712                         for (node = endContainer; node; node = node.parentNode) {
8713                                 if (node == startContainer)
8714                                         return walkBoundary(endContainer, ancestor);
8715
8716                                 if (node == ancestor)
8717                                         break;
8718                         }
8719
8720                         // Find start/end point
8721                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;
8722                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;
8723
8724                         // Walk left leaf
8725                         walkBoundary(startContainer, startPoint, true);
8726
8727                         // Walk the middle from start to end point
8728                         siblings = collectSiblings(
8729                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,
8730                                 'nextSibling',
8731                                 endPoint == endContainer ? endPoint.nextSibling : endPoint
8732                         );
8733
8734                         if (siblings.length)
8735                                 callback(siblings);
8736
8737                         // Walk right leaf
8738                         walkBoundary(endContainer, endPoint);
8739                 };
8740
8741                 /*              this.split = function(rng) {
8742                         var startContainer = rng.startContainer,
8743                                 startOffset = rng.startOffset,
8744                                 endContainer = rng.endContainer,
8745                                 endOffset = rng.endOffset;
8746
8747                         function splitText(node, offset) {
8748                                 if (offset == node.nodeValue.length)
8749                                         node.appendData(INVISIBLE_CHAR);
8750
8751                                 node = node.splitText(offset);
8752
8753                                 if (node.nodeValue === INVISIBLE_CHAR)
8754                                         node.nodeValue = '';
8755
8756                                 return node;
8757                         };
8758
8759                         // Handle single text node
8760                         if (startContainer == endContainer) {
8761                                 if (startContainer.nodeType == 3) {
8762                                         if (startOffset != 0)
8763                                                 startContainer = endContainer = splitText(startContainer, startOffset);
8764
8765                                         if (endOffset - startOffset != startContainer.nodeValue.length)
8766                                                 splitText(startContainer, endOffset - startOffset);
8767                                 }
8768                         } else {
8769                                 // Split startContainer text node if needed
8770                                 if (startContainer.nodeType == 3 && startOffset != 0) {
8771                                         startContainer = splitText(startContainer, startOffset);
8772                                         startOffset = 0;
8773                                 }
8774
8775                                 // Split endContainer text node if needed
8776                                 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
8777                                         endContainer = splitText(endContainer, endOffset).previousSibling;
8778                                         endOffset = endContainer.nodeValue.length;
8779                                 }
8780                         }
8781
8782                         return {
8783                                 startContainer : startContainer,
8784                                 startOffset : startOffset,
8785                                 endContainer : endContainer,
8786                                 endOffset : endOffset
8787                         };
8788                 };
8789 */
8790         };
8791
8792         tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
8793                 if (rng1 && rng2) {
8794                         // Compare native IE ranges
8795                         if (rng1.item || rng1.duplicate) {
8796                                 // Both are control ranges and the selected element matches
8797                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
8798                                         return true;
8799
8800                                 // Both are text ranges and the range matches
8801                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
8802                                         return true;
8803                         } else {
8804                                 // Compare w3c ranges
8805                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
8806                         }
8807                 }
8808
8809                 return false;
8810         };
8811 })(tinymce);
8812
8813 (function(tinymce) {
8814         var Event = tinymce.dom.Event, each = tinymce.each;
8815
8816         tinymce.create('tinymce.ui.KeyboardNavigation', {
8817                 KeyboardNavigation: function(settings, dom) {
8818                         var t = this, root = settings.root, items = settings.items,
8819                                         enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
8820                                         excludeFromTabOrder = settings.excludeFromTabOrder,
8821                                         itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
8822
8823                         dom = dom || tinymce.DOM;
8824
8825                         itemFocussed = function(evt) {
8826                                 focussedId = evt.target.id;
8827                         };
8828                         
8829                         itemBlurred = function(evt) {
8830                                 dom.setAttrib(evt.target.id, 'tabindex', '-1');
8831                         };
8832                         
8833                         rootFocussed = function(evt) {
8834                                 var item = dom.get(focussedId);
8835                                 dom.setAttrib(item, 'tabindex', '0');
8836                                 item.focus();
8837                         };
8838                         
8839                         t.focus = function() {
8840                                 dom.get(focussedId).focus();
8841                         };
8842
8843                         t.destroy = function() {
8844                                 each(items, function(item) {
8845                                         dom.unbind(dom.get(item.id), 'focus', itemFocussed);
8846                                         dom.unbind(dom.get(item.id), 'blur', itemBlurred);
8847                                 });
8848
8849                                 dom.unbind(dom.get(root), 'focus', rootFocussed);
8850                                 dom.unbind(dom.get(root), 'keydown', rootKeydown);
8851
8852                                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
8853                                 t.destroy = function() {};
8854                         };
8855                         
8856                         t.moveFocus = function(dir, evt) {
8857                                 var idx = -1, controls = t.controls, newFocus;
8858
8859                                 if (!focussedId)
8860                                         return;
8861
8862                                 each(items, function(item, index) {
8863                                         if (item.id === focussedId) {
8864                                                 idx = index;
8865                                                 return false;
8866                                         }
8867                                 });
8868
8869                                 idx += dir;
8870                                 if (idx < 0) {
8871                                         idx = items.length - 1;
8872                                 } else if (idx >= items.length) {
8873                                         idx = 0;
8874                                 }
8875                                 
8876                                 newFocus = items[idx];
8877                                 dom.setAttrib(focussedId, 'tabindex', '-1');
8878                                 dom.setAttrib(newFocus.id, 'tabindex', '0');
8879                                 dom.get(newFocus.id).focus();
8880
8881                                 if (settings.actOnFocus) {
8882                                         settings.onAction(newFocus.id);
8883                                 }
8884
8885                                 if (evt)
8886                                         Event.cancel(evt);
8887                         };
8888                         
8889                         rootKeydown = function(evt) {
8890                                 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;
8891                                 
8892                                 switch (evt.keyCode) {
8893                                         case DOM_VK_LEFT:
8894                                                 if (enableLeftRight) t.moveFocus(-1);
8895                                                 break;
8896         
8897                                         case DOM_VK_RIGHT:
8898                                                 if (enableLeftRight) t.moveFocus(1);
8899                                                 break;
8900         
8901                                         case DOM_VK_UP:
8902                                                 if (enableUpDown) t.moveFocus(-1);
8903                                                 break;
8904
8905                                         case DOM_VK_DOWN:
8906                                                 if (enableUpDown) t.moveFocus(1);
8907                                                 break;
8908
8909                                         case DOM_VK_ESCAPE:
8910                                                 if (settings.onCancel) {
8911                                                         settings.onCancel();
8912                                                         Event.cancel(evt);
8913                                                 }
8914                                                 break;
8915
8916                                         case DOM_VK_ENTER:
8917                                         case DOM_VK_RETURN:
8918                                         case DOM_VK_SPACE:
8919                                                 if (settings.onAction) {
8920                                                         settings.onAction(focussedId);
8921                                                         Event.cancel(evt);
8922                                                 }
8923                                                 break;
8924                                 }
8925                         };
8926
8927                         // Set up state and listeners for each item.
8928                         each(items, function(item, idx) {
8929                                 var tabindex;
8930
8931                                 if (!item.id) {
8932                                         item.id = dom.uniqueId('_mce_item_');
8933                                 }
8934
8935                                 if (excludeFromTabOrder) {
8936                                         dom.bind(item.id, 'blur', itemBlurred);
8937                                         tabindex = '-1';
8938                                 } else {
8939                                         tabindex = (idx === 0 ? '0' : '-1');
8940                                 }
8941
8942                                 dom.setAttrib(item.id, 'tabindex', tabindex);
8943                                 dom.bind(dom.get(item.id), 'focus', itemFocussed);
8944                         });
8945                         
8946                         // Setup initial state for root element.
8947                         if (items[0]){
8948                                 focussedId = items[0].id;
8949                         }
8950
8951                         dom.setAttrib(root, 'tabindex', '-1');
8952                         
8953                         // Setup listeners for root element.
8954                         dom.bind(dom.get(root), 'focus', rootFocussed);
8955                         dom.bind(dom.get(root), 'keydown', rootKeydown);
8956                 }
8957         });
8958 })(tinymce);
8959 (function(tinymce) {
8960         // Shorten class names
8961         var DOM = tinymce.DOM, is = tinymce.is;
8962
8963         tinymce.create('tinymce.ui.Control', {
8964                 Control : function(id, s, editor) {
8965                         this.id = id;
8966                         this.settings = s = s || {};
8967                         this.rendered = false;
8968                         this.onRender = new tinymce.util.Dispatcher(this);
8969                         this.classPrefix = '';
8970                         this.scope = s.scope || this;
8971                         this.disabled = 0;
8972                         this.active = 0;
8973                         this.editor = editor;
8974                 },
8975                 
8976                 setAriaProperty : function(property, value) {
8977                         var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
8978                         if (element) {
8979                                 DOM.setAttrib(element, 'aria-' + property, !!value);
8980                         }
8981                 },
8982                 
8983                 focus : function() {
8984                         DOM.get(this.id).focus();
8985                 },
8986
8987                 setDisabled : function(s) {
8988                         if (s != this.disabled) {
8989                                 this.setAriaProperty('disabled', s);
8990
8991                                 this.setState('Disabled', s);
8992                                 this.setState('Enabled', !s);
8993                                 this.disabled = s;
8994                         }
8995                 },
8996
8997                 isDisabled : function() {
8998                         return this.disabled;
8999                 },
9000
9001                 setActive : function(s) {
9002                         if (s != this.active) {
9003                                 this.setState('Active', s);
9004                                 this.active = s;
9005                                 this.setAriaProperty('pressed', s);
9006                         }
9007                 },
9008
9009                 isActive : function() {
9010                         return this.active;
9011                 },
9012
9013                 setState : function(c, s) {
9014                         var n = DOM.get(this.id);
9015
9016                         c = this.classPrefix + c;
9017
9018                         if (s)
9019                                 DOM.addClass(n, c);
9020                         else
9021                                 DOM.removeClass(n, c);
9022                 },
9023
9024                 isRendered : function() {
9025                         return this.rendered;
9026                 },
9027
9028                 renderHTML : function() {
9029                 },
9030
9031                 renderTo : function(n) {
9032                         DOM.setHTML(n, this.renderHTML());
9033                 },
9034
9035                 postRender : function() {
9036                         var t = this, b;
9037
9038                         // Set pending states
9039                         if (is(t.disabled)) {
9040                                 b = t.disabled;
9041                                 t.disabled = -1;
9042                                 t.setDisabled(b);
9043                         }
9044
9045                         if (is(t.active)) {
9046                                 b = t.active;
9047                                 t.active = -1;
9048                                 t.setActive(b);
9049                         }
9050                 },
9051
9052                 remove : function() {
9053                         DOM.remove(this.id);
9054                         this.destroy();
9055                 },
9056
9057                 destroy : function() {
9058                         tinymce.dom.Event.clear(this.id);
9059                 }
9060         });
9061 })(tinymce);
9062 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
9063         Container : function(id, s, editor) {
9064                 this.parent(id, s, editor);
9065
9066                 this.controls = [];
9067
9068                 this.lookup = {};
9069         },
9070
9071         add : function(c) {
9072                 this.lookup[c.id] = c;
9073                 this.controls.push(c);
9074
9075                 return c;
9076         },
9077
9078         get : function(n) {
9079                 return this.lookup[n];
9080         }
9081 });
9082
9083
9084 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9085         Separator : function(id, s) {
9086                 this.parent(id, s);
9087                 this.classPrefix = 'mceSeparator';
9088                 this.setDisabled(true);
9089         },
9090
9091         renderHTML : function() {
9092                 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
9093         }
9094 });
9095
9096 (function(tinymce) {
9097         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9098
9099         tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
9100                 MenuItem : function(id, s) {
9101                         this.parent(id, s);
9102                         this.classPrefix = 'mceMenuItem';
9103                 },
9104
9105                 setSelected : function(s) {
9106                         this.setState('Selected', s);
9107                         this.setAriaProperty('checked', !!s);
9108                         this.selected = s;
9109                 },
9110
9111                 isSelected : function() {
9112                         return this.selected;
9113                 },
9114
9115                 postRender : function() {
9116                         var t = this;
9117                         
9118                         t.parent();
9119
9120                         // Set pending state
9121                         if (is(t.selected))
9122                                 t.setSelected(t.selected);
9123                 }
9124         });
9125 })(tinymce);
9126
9127 (function(tinymce) {
9128         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9129
9130         tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
9131                 Menu : function(id, s) {
9132                         var t = this;
9133
9134                         t.parent(id, s);
9135                         t.items = {};
9136                         t.collapsed = false;
9137                         t.menuCount = 0;
9138                         t.onAddItem = new tinymce.util.Dispatcher(this);
9139                 },
9140
9141                 expand : function(d) {
9142                         var t = this;
9143
9144                         if (d) {
9145                                 walk(t, function(o) {
9146                                         if (o.expand)
9147                                                 o.expand();
9148                                 }, 'items', t);
9149                         }
9150
9151                         t.collapsed = false;
9152                 },
9153
9154                 collapse : function(d) {
9155                         var t = this;
9156
9157                         if (d) {
9158                                 walk(t, function(o) {
9159                                         if (o.collapse)
9160                                                 o.collapse();
9161                                 }, 'items', t);
9162                         }
9163
9164                         t.collapsed = true;
9165                 },
9166
9167                 isCollapsed : function() {
9168                         return this.collapsed;
9169                 },
9170
9171                 add : function(o) {
9172                         if (!o.settings)
9173                                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
9174
9175                         this.onAddItem.dispatch(this, o);
9176
9177                         return this.items[o.id] = o;
9178                 },
9179
9180                 addSeparator : function() {
9181                         return this.add({separator : true});
9182                 },
9183
9184                 addMenu : function(o) {
9185                         if (!o.collapse)
9186                                 o = this.createMenu(o);
9187
9188                         this.menuCount++;
9189
9190                         return this.add(o);
9191                 },
9192
9193                 hasMenus : function() {
9194                         return this.menuCount !== 0;
9195                 },
9196
9197                 remove : function(o) {
9198                         delete this.items[o.id];
9199                 },
9200
9201                 removeAll : function() {
9202                         var t = this;
9203
9204                         walk(t, function(o) {
9205                                 if (o.removeAll)
9206                                         o.removeAll();
9207                                 else
9208                                         o.remove();
9209
9210                                 o.destroy();
9211                         }, 'items', t);
9212
9213                         t.items = {};
9214                 },
9215
9216                 createMenu : function(o) {
9217                         var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
9218
9219                         m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
9220
9221                         return m;
9222                 }
9223         });
9224 })(tinymce);
9225 (function(tinymce) {
9226         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
9227
9228         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
9229                 DropMenu : function(id, s) {
9230                         s = s || {};
9231                         s.container = s.container || DOM.doc.body;
9232                         s.offset_x = s.offset_x || 0;
9233                         s.offset_y = s.offset_y || 0;
9234                         s.vp_offset_x = s.vp_offset_x || 0;
9235                         s.vp_offset_y = s.vp_offset_y || 0;
9236
9237                         if (is(s.icons) && !s.icons)
9238                                 s['class'] += ' mceNoIcons';
9239
9240                         this.parent(id, s);
9241                         this.onShowMenu = new tinymce.util.Dispatcher(this);
9242                         this.onHideMenu = new tinymce.util.Dispatcher(this);
9243                         this.classPrefix = 'mceMenu';
9244                 },
9245
9246                 createMenu : function(s) {
9247                         var t = this, cs = t.settings, m;
9248
9249                         s.container = s.container || cs.container;
9250                         s.parent = t;
9251                         s.constrain = s.constrain || cs.constrain;
9252                         s['class'] = s['class'] || cs['class'];
9253                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
9254                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
9255                         s.keyboard_focus = cs.keyboard_focus;
9256                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
9257
9258                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
9259
9260                         return m;
9261                 },
9262                 
9263                 focus : function() {
9264                         var t = this;
9265                         if (t.keyboardNav) {
9266                                 t.keyboardNav.focus();
9267                         }
9268                 },
9269
9270                 update : function() {
9271                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
9272
9273                         tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
9274                         th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
9275
9276                         if (!DOM.boxModel)
9277                                 t.element.setStyles({width : tw + 2, height : th + 2});
9278                         else
9279                                 t.element.setStyles({width : tw, height : th});
9280
9281                         if (s.max_width)
9282                                 DOM.setStyle(co, 'width', tw);
9283
9284                         if (s.max_height) {
9285                                 DOM.setStyle(co, 'height', th);
9286
9287                                 if (tb.clientHeight < s.max_height)
9288                                         DOM.setStyle(co, 'overflow', 'hidden');
9289                         }
9290                 },
9291
9292                 showMenu : function(x, y, px) {
9293                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
9294
9295                         t.collapse(1);
9296
9297                         if (t.isMenuVisible)
9298                                 return;
9299
9300                         if (!t.rendered) {
9301                                 co = DOM.add(t.settings.container, t.renderNode());
9302
9303                                 each(t.items, function(o) {
9304                                         o.postRender();
9305                                 });
9306
9307                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
9308                         } else
9309                                 co = DOM.get('menu_' + t.id);
9310
9311                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
9312                         if (!tinymce.isOpera)
9313                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
9314
9315                         DOM.show(co);
9316                         t.update();
9317
9318                         x += s.offset_x || 0;
9319                         y += s.offset_y || 0;
9320                         vp.w -= 4;
9321                         vp.h -= 4;
9322
9323                         // Move inside viewport if not submenu
9324                         if (s.constrain) {
9325                                 w = co.clientWidth - ot;
9326                                 h = co.clientHeight - ot;
9327                                 mx = vp.x + vp.w;
9328                                 my = vp.y + vp.h;
9329
9330                                 if ((x + s.vp_offset_x + w) > mx)
9331                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
9332
9333                                 if ((y + s.vp_offset_y + h) > my)
9334                                         y = Math.max(0, (my - s.vp_offset_y) - h);
9335                         }
9336
9337                         DOM.setStyles(co, {left : x , top : y});
9338                         t.element.update();
9339
9340                         t.isMenuVisible = 1;
9341                         t.mouseClickFunc = Event.add(co, 'click', function(e) {
9342                                 var m;
9343
9344                                 e = e.target;
9345
9346                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
9347                                         m = t.items[e.id];
9348
9349                                         if (m.isDisabled())
9350                                                 return;
9351
9352                                         dm = t;
9353
9354                                         while (dm) {
9355                                                 if (dm.hideMenu)
9356                                                         dm.hideMenu();
9357
9358                                                 dm = dm.settings.parent;
9359                                         }
9360
9361                                         if (m.settings.onclick)
9362                                                 m.settings.onclick(e);
9363
9364                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
9365                                 }
9366                         });
9367
9368                         if (t.hasMenus()) {
9369                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
9370                                         var m, r, mi;
9371
9372                                         e = e.target;
9373                                         if (e && (e = DOM.getParent(e, 'tr'))) {
9374                                                 m = t.items[e.id];
9375
9376                                                 if (t.lastMenu)
9377                                                         t.lastMenu.collapse(1);
9378
9379                                                 if (m.isDisabled())
9380                                                         return;
9381
9382                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {
9383                                                         //p = DOM.getPos(s.container);
9384                                                         r = DOM.getRect(e);
9385                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
9386                                                         t.lastMenu = m;
9387                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
9388                                                 }
9389                                         }
9390                                 });
9391                         }
9392                         
9393                         Event.add(co, 'keydown', t._keyHandler, t);
9394
9395                         t.onShowMenu.dispatch(t);
9396
9397                         if (s.keyboard_focus) { 
9398                                 t._setupKeyboardNav(); 
9399                         }
9400                 },
9401
9402                 hideMenu : function(c) {
9403                         var t = this, co = DOM.get('menu_' + t.id), e;
9404
9405                         if (!t.isMenuVisible)
9406                                 return;
9407
9408                         if (t.keyboardNav) t.keyboardNav.destroy();
9409                         Event.remove(co, 'mouseover', t.mouseOverFunc);
9410                         Event.remove(co, 'click', t.mouseClickFunc);
9411                         Event.remove(co, 'keydown', t._keyHandler);
9412                         DOM.hide(co);
9413                         t.isMenuVisible = 0;
9414
9415                         if (!c)
9416                                 t.collapse(1);
9417
9418                         if (t.element)
9419                                 t.element.hide();
9420
9421                         if (e = DOM.get(t.id))
9422                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
9423
9424                         t.onHideMenu.dispatch(t);
9425                 },
9426
9427                 add : function(o) {
9428                         var t = this, co;
9429
9430                         o = t.parent(o);
9431
9432                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))
9433                                 t._add(DOM.select('tbody', co)[0], o);
9434
9435                         return o;
9436                 },
9437
9438                 collapse : function(d) {
9439                         this.parent(d);
9440                         this.hideMenu(1);
9441                 },
9442
9443                 remove : function(o) {
9444                         DOM.remove(o.id);
9445                         this.destroy();
9446
9447                         return this.parent(o);
9448                 },
9449
9450                 destroy : function() {
9451                         var t = this, co = DOM.get('menu_' + t.id);
9452
9453                         if (t.keyboardNav) t.keyboardNav.destroy();
9454                         Event.remove(co, 'mouseover', t.mouseOverFunc);
9455                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
9456                         Event.remove(co, 'click', t.mouseClickFunc);
9457                         Event.remove(co, 'keydown', t._keyHandler);
9458
9459                         if (t.element)
9460                                 t.element.remove();
9461
9462                         DOM.remove(co);
9463                 },
9464
9465                 renderNode : function() {
9466                         var t = this, s = t.settings, n, tb, co, w;
9467
9468                         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'});
9469                         if (t.settings.parent) {
9470                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
9471                         }
9472                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
9473                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
9474
9475                         if (s.menu_line)
9476                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
9477
9478 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
9479                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
9480                         tb = DOM.add(n, 'tbody');
9481
9482                         each(t.items, function(o) {
9483                                 t._add(tb, o);
9484                         });
9485
9486                         t.rendered = true;
9487
9488                         return w;
9489                 },
9490
9491                 // Internal functions
9492                 _setupKeyboardNav : function(){
9493                         var contextMenu, menuItems, t=this; 
9494                         contextMenu = DOM.select('#menu_' + t.id)[0];
9495                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
9496                         menuItems.splice(0,0,contextMenu);
9497                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({
9498                                 root: 'menu_' + t.id,
9499                                 items: menuItems,
9500                                 onCancel: function() {
9501                                         t.hideMenu();
9502                                 },
9503                                 enableUpDown: true
9504                         });
9505                         contextMenu.focus();
9506                 },
9507
9508                 _keyHandler : function(evt) {
9509                         var t = this, e;
9510                         switch (evt.keyCode) {
9511                                 case 37: // Left
9512                                         if (t.settings.parent) {
9513                                                 t.hideMenu();
9514                                                 t.settings.parent.focus();
9515                                                 Event.cancel(evt);
9516                                         }
9517                                         break;
9518                                 case 39: // Right
9519                                         if (t.mouseOverFunc)
9520                                                 t.mouseOverFunc(evt);
9521                                         break;
9522                         }
9523                 },
9524
9525                 _add : function(tb, o) {
9526                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
9527
9528                         if (s.separator) {
9529                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
9530                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
9531
9532                                 if (n = ro.previousSibling)
9533                                         DOM.addClass(n, 'mceLast');
9534
9535                                 return;
9536                         }
9537
9538                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
9539                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
9540                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
9541
9542                         if (s.parent) {
9543                                 DOM.setAttrib(a, 'aria-haspopup', 'true');
9544                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
9545                         }
9546
9547                         DOM.addClass(it, s['class']);
9548 //                      n = DOM.add(n, 'span', {'class' : 'item'});
9549
9550                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
9551
9552                         if (s.icon_src)
9553                                 DOM.add(ic, 'img', {src : s.icon_src});
9554
9555                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
9556
9557                         if (o.settings.style)
9558                                 DOM.setAttrib(n, 'style', o.settings.style);
9559
9560                         if (tb.childNodes.length == 1)
9561                                 DOM.addClass(ro, 'mceFirst');
9562
9563                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
9564                                 DOM.addClass(ro, 'mceFirst');
9565
9566                         if (o.collapse)
9567                                 DOM.addClass(ro, cp + 'ItemSub');
9568
9569                         if (n = ro.previousSibling)
9570                                 DOM.removeClass(n, 'mceLast');
9571
9572                         DOM.addClass(ro, 'mceLast');
9573                 }
9574         });
9575 })(tinymce);
9576 (function(tinymce) {
9577         var DOM = tinymce.DOM;
9578
9579         tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
9580                 Button : function(id, s, ed) {
9581                         this.parent(id, s, ed);
9582                         this.classPrefix = 'mceButton';
9583                 },
9584
9585                 renderHTML : function() {
9586                         var cp = this.classPrefix, s = this.settings, h, l;
9587
9588                         l = DOM.encode(s.label || '');
9589                         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) + '">';
9590                         if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
9591                                 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
9592                         else
9593                                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
9594
9595                         h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
9596                         h += '</a>';
9597                         return h;
9598                 },
9599
9600                 postRender : function() {
9601                         var t = this, s = t.settings;
9602
9603                         tinymce.dom.Event.add(t.id, 'click', function(e) {
9604                                 if (!t.isDisabled())
9605                                         return s.onclick.call(s.scope, e);
9606                         });
9607                 }
9608         });
9609 })(tinymce);
9610
9611 (function(tinymce) {
9612         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
9613
9614         tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
9615                 ListBox : function(id, s, ed) {
9616                         var t = this;
9617
9618                         t.parent(id, s, ed);
9619
9620                         t.items = [];
9621
9622                         t.onChange = new Dispatcher(t);
9623
9624                         t.onPostRender = new Dispatcher(t);
9625
9626                         t.onAdd = new Dispatcher(t);
9627
9628                         t.onRenderMenu = new tinymce.util.Dispatcher(this);
9629
9630                         t.classPrefix = 'mceListBox';
9631                 },
9632
9633                 select : function(va) {
9634                         var t = this, fv, f;
9635
9636                         if (va == undefined)
9637                                 return t.selectByIndex(-1);
9638
9639                         // Is string or number make function selector
9640                         if (va && va.call)
9641                                 f = va;
9642                         else {
9643                                 f = function(v) {
9644                                         return v == va;
9645                                 };
9646                         }
9647
9648                         // Do we need to do something?
9649                         if (va != t.selectedValue) {
9650                                 // Find item
9651                                 each(t.items, function(o, i) {
9652                                         if (f(o.value)) {
9653                                                 fv = 1;
9654                                                 t.selectByIndex(i);
9655                                                 return false;
9656                                         }
9657                                 });
9658
9659                                 if (!fv)
9660                                         t.selectByIndex(-1);
9661                         }
9662                 },
9663
9664                 selectByIndex : function(idx) {
9665                         var t = this, e, o;
9666
9667                         if (idx != t.selectedIndex) {
9668                                 e = DOM.get(t.id + '_text');
9669                                 o = t.items[idx];
9670
9671                                 if (o) {
9672                                         t.selectedValue = o.value;
9673                                         t.selectedIndex = idx;
9674                                         DOM.setHTML(e, DOM.encode(o.title));
9675                                         DOM.removeClass(e, 'mceTitle');
9676                                         DOM.setAttrib(t.id, 'aria-valuenow', o.title);
9677                                 } else {
9678                                         DOM.setHTML(e, DOM.encode(t.settings.title));
9679                                         DOM.addClass(e, 'mceTitle');
9680                                         t.selectedValue = t.selectedIndex = null;
9681                                         DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
9682                                 }
9683                                 e = 0;
9684                         }
9685                 },
9686
9687                 add : function(n, v, o) {
9688                         var t = this;
9689
9690                         o = o || {};
9691                         o = tinymce.extend(o, {
9692                                 title : n,
9693                                 value : v
9694                         });
9695
9696                         t.items.push(o);
9697                         t.onAdd.dispatch(t, o);
9698                 },
9699
9700                 getLength : function() {
9701                         return this.items.length;
9702                 },
9703
9704                 renderHTML : function() {
9705                         var h = '', t = this, s = t.settings, cp = t.classPrefix;
9706
9707                         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>';
9708                         h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
9709                         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>';
9710                         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>';
9711                         h += '</tr></tbody></table></span>';
9712
9713                         return h;
9714                 },
9715
9716                 showMenu : function() {
9717                         var t = this, p2, e = DOM.get(this.id), m;
9718
9719                         if (t.isDisabled() || t.items.length == 0)
9720                                 return;
9721
9722                         if (t.menu && t.menu.isMenuVisible)
9723                                 return t.hideMenu();
9724
9725                         if (!t.isMenuRendered) {
9726                                 t.renderMenu();
9727                                 t.isMenuRendered = true;
9728                         }
9729
9730                         p2 = DOM.getPos(e);
9731
9732                         m = t.menu;
9733                         m.settings.offset_x = p2.x;
9734                         m.settings.offset_y = p2.y;
9735                         m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
9736
9737                         // Select in menu
9738                         if (t.oldID)
9739                                 m.items[t.oldID].setSelected(0);
9740
9741                         each(t.items, function(o) {
9742                                 if (o.value === t.selectedValue) {
9743                                         m.items[o.id].setSelected(1);
9744                                         t.oldID = o.id;
9745                                 }
9746                         });
9747
9748                         m.showMenu(0, e.clientHeight);
9749
9750                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9751                         DOM.addClass(t.id, t.classPrefix + 'Selected');
9752
9753                         //DOM.get(t.id + '_text').focus();
9754                 },
9755
9756                 hideMenu : function(e) {
9757                         var t = this;
9758
9759                         if (t.menu && t.menu.isMenuVisible) {
9760                                 DOM.removeClass(t.id, t.classPrefix + 'Selected');
9761
9762                                 // Prevent double toogles by canceling the mouse click event to the button
9763                                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
9764                                         return;
9765
9766                                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {
9767                                         DOM.removeClass(t.id, t.classPrefix + 'Selected');
9768                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9769                                         t.menu.hideMenu();
9770                                 }
9771                         }
9772                 },
9773
9774                 renderMenu : function() {
9775                         var t = this, m;
9776
9777                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9778                                 menu_line : 1,
9779                                 'class' : t.classPrefix + 'Menu mceNoIcons',
9780                                 max_width : 150,
9781                                 max_height : 150
9782                         });
9783
9784                         m.onHideMenu.add(function() {
9785                                 t.hideMenu();
9786                                 t.focus();
9787                         });
9788
9789                         m.add({
9790                                 title : t.settings.title,
9791                                 'class' : 'mceMenuItemTitle',
9792                                 onclick : function() {
9793                                         if (t.settings.onselect('') !== false)
9794                                                 t.select(''); // Must be runned after
9795                                 }
9796                         });
9797
9798                         each(t.items, function(o) {
9799                                 // No value then treat it as a title
9800                                 if (o.value === undefined) {
9801                                         m.add({
9802                                                 title : o.title,
9803                                                 'class' : 'mceMenuItemTitle',
9804                                                 onclick : function() {
9805                                                         if (t.settings.onselect('') !== false)
9806                                                                 t.select(''); // Must be runned after
9807                                                 }
9808                                         });
9809                                 } else {
9810                                         o.id = DOM.uniqueId();
9811                                         o.onclick = function() {
9812                                                 if (t.settings.onselect(o.value) !== false)
9813                                                         t.select(o.value); // Must be runned after
9814                                         };
9815
9816                                         m.add(o);
9817                                 }
9818                         });
9819
9820                         t.onRenderMenu.dispatch(t, m);
9821                         t.menu = m;
9822                 },
9823
9824                 postRender : function() {
9825                         var t = this, cp = t.classPrefix;
9826
9827                         Event.add(t.id, 'click', t.showMenu, t);
9828                         Event.add(t.id, 'keydown', function(evt) {
9829                                 if (evt.keyCode == 32) { // Space
9830                                         t.showMenu(evt);
9831                                         Event.cancel(evt);
9832                                 }
9833                         });
9834                         Event.add(t.id, 'focus', function() {
9835                                 if (!t._focused) {
9836                                         t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
9837                                                 if (e.keyCode == 40) {
9838                                                         t.showMenu();
9839                                                         Event.cancel(e);
9840                                                 }
9841                                         });
9842                                         t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
9843                                                 var v;
9844                                                 if (e.keyCode == 13) {
9845                                                         // Fake select on enter
9846                                                         v = t.selectedValue;
9847                                                         t.selectedValue = null; // Needs to be null to fake change
9848                                                         Event.cancel(e);
9849                                                         t.settings.onselect(v);
9850                                                 }
9851                                         });
9852                                 }
9853
9854                                 t._focused = 1;
9855                         });
9856                         Event.add(t.id, 'blur', function() {
9857                                 Event.remove(t.id, 'keydown', t.keyDownHandler);
9858                                 Event.remove(t.id, 'keypress', t.keyPressHandler);
9859                                 t._focused = 0;
9860                         });
9861
9862                         // Old IE doesn't have hover on all elements
9863                         if (tinymce.isIE6 || !DOM.boxModel) {
9864                                 Event.add(t.id, 'mouseover', function() {
9865                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
9866                                                 DOM.addClass(t.id, cp + 'Hover');
9867                                 });
9868
9869                                 Event.add(t.id, 'mouseout', function() {
9870                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))
9871                                                 DOM.removeClass(t.id, cp + 'Hover');
9872                                 });
9873                         }
9874
9875                         t.onPostRender.dispatch(t, DOM.get(t.id));
9876                 },
9877
9878                 destroy : function() {
9879                         this.parent();
9880
9881                         Event.clear(this.id + '_text');
9882                         Event.clear(this.id + '_open');
9883                 }
9884         });
9885 })(tinymce);
9886 (function(tinymce) {
9887         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
9888
9889         tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
9890                 NativeListBox : function(id, s) {
9891                         this.parent(id, s);
9892                         this.classPrefix = 'mceNativeListBox';
9893                 },
9894
9895                 setDisabled : function(s) {
9896                         DOM.get(this.id).disabled = s;
9897                         this.setAriaProperty('disabled', s);
9898                 },
9899
9900                 isDisabled : function() {
9901                         return DOM.get(this.id).disabled;
9902                 },
9903
9904                 select : function(va) {
9905                         var t = this, fv, f;
9906
9907                         if (va == undefined)
9908                                 return t.selectByIndex(-1);
9909
9910                         // Is string or number make function selector
9911                         if (va && va.call)
9912                                 f = va;
9913                         else {
9914                                 f = function(v) {
9915                                         return v == va;
9916                                 };
9917                         }
9918
9919                         // Do we need to do something?
9920                         if (va != t.selectedValue) {
9921                                 // Find item
9922                                 each(t.items, function(o, i) {
9923                                         if (f(o.value)) {
9924                                                 fv = 1;
9925                                                 t.selectByIndex(i);
9926                                                 return false;
9927                                         }
9928                                 });
9929
9930                                 if (!fv)
9931                                         t.selectByIndex(-1);
9932                         }
9933                 },
9934
9935                 selectByIndex : function(idx) {
9936                         DOM.get(this.id).selectedIndex = idx + 1;
9937                         this.selectedValue = this.items[idx] ? this.items[idx].value : null;
9938                 },
9939
9940                 add : function(n, v, a) {
9941                         var o, t = this;
9942
9943                         a = a || {};
9944                         a.value = v;
9945
9946                         if (t.isRendered())
9947                                 DOM.add(DOM.get(this.id), 'option', a, n);
9948
9949                         o = {
9950                                 title : n,
9951                                 value : v,
9952                                 attribs : a
9953                         };
9954
9955                         t.items.push(o);
9956                         t.onAdd.dispatch(t, o);
9957                 },
9958
9959                 getLength : function() {
9960                         return this.items.length;
9961                 },
9962
9963                 renderHTML : function() {
9964                         var h, t = this;
9965
9966                         h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
9967
9968                         each(t.items, function(it) {
9969                                 h += DOM.createHTML('option', {value : it.value}, it.title);
9970                         });
9971
9972                         h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
9973                         h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
9974                         return h;
9975                 },
9976
9977                 postRender : function() {
9978                         var t = this, ch, changeListenerAdded = true;
9979
9980                         t.rendered = true;
9981
9982                         function onChange(e) {
9983                                 var v = t.items[e.target.selectedIndex - 1];
9984
9985                                 if (v && (v = v.value)) {
9986                                         t.onChange.dispatch(t, v);
9987
9988                                         if (t.settings.onselect)
9989                                                 t.settings.onselect(v);
9990                                 }
9991                         };
9992
9993                         Event.add(t.id, 'change', onChange);
9994
9995                         // Accessibility keyhandler
9996                         Event.add(t.id, 'keydown', function(e) {
9997                                 var bf;
9998
9999                                 Event.remove(t.id, 'change', ch);
10000                                 changeListenerAdded = false;
10001
10002                                 bf = Event.add(t.id, 'blur', function() {
10003                                         if (changeListenerAdded) return;
10004                                         changeListenerAdded = true;
10005                                         Event.add(t.id, 'change', onChange);
10006                                         Event.remove(t.id, 'blur', bf);
10007                                 });
10008
10009                                 if (e.keyCode == 13 || e.keyCode == 32) {
10010                                         onChange(e);
10011                                         return Event.cancel(e);
10012                                 }
10013                         });
10014
10015                         t.onPostRender.dispatch(t, DOM.get(t.id));
10016                 }
10017         });
10018 })(tinymce);
10019 (function(tinymce) {
10020         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10021
10022         tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
10023                 MenuButton : function(id, s, ed) {
10024                         this.parent(id, s, ed);
10025
10026                         this.onRenderMenu = new tinymce.util.Dispatcher(this);
10027
10028                         s.menu_container = s.menu_container || DOM.doc.body;
10029                 },
10030
10031                 showMenu : function() {
10032                         var t = this, p1, p2, e = DOM.get(t.id), m;
10033
10034                         if (t.isDisabled())
10035                                 return;
10036
10037                         if (!t.isMenuRendered) {
10038                                 t.renderMenu();
10039                                 t.isMenuRendered = true;
10040                         }
10041
10042                         if (t.isMenuVisible)
10043                                 return t.hideMenu();
10044
10045                         p1 = DOM.getPos(t.settings.menu_container);
10046                         p2 = DOM.getPos(e);
10047
10048                         m = t.menu;
10049                         m.settings.offset_x = p2.x;
10050                         m.settings.offset_y = p2.y;
10051                         m.settings.vp_offset_x = p2.x;
10052                         m.settings.vp_offset_y = p2.y;
10053                         m.settings.keyboard_focus = t._focused;
10054                         m.showMenu(0, e.clientHeight);
10055
10056                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10057                         t.setState('Selected', 1);
10058
10059                         t.isMenuVisible = 1;
10060                 },
10061
10062                 renderMenu : function() {
10063                         var t = this, m;
10064
10065                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
10066                                 menu_line : 1,
10067                                 'class' : this.classPrefix + 'Menu',
10068                                 icons : t.settings.icons
10069                         });
10070
10071                         m.onHideMenu.add(function() {
10072                                 t.hideMenu();
10073                                 t.focus();
10074                         });
10075
10076                         t.onRenderMenu.dispatch(t, m);
10077                         t.menu = m;
10078                 },
10079
10080                 hideMenu : function(e) {
10081                         var t = this;
10082
10083                         // Prevent double toogles by canceling the mouse click event to the button
10084                         if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
10085                                 return;
10086
10087                         if (!e || !DOM.getParent(e.target, '.mceMenu')) {
10088                                 t.setState('Selected', 0);
10089                                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10090                                 if (t.menu)
10091                                         t.menu.hideMenu();
10092                         }
10093
10094                         t.isMenuVisible = 0;
10095                 },
10096
10097                 postRender : function() {
10098                         var t = this, s = t.settings;
10099
10100                         Event.add(t.id, 'click', function() {
10101                                 if (!t.isDisabled()) {
10102                                         if (s.onclick)
10103                                                 s.onclick(t.value);
10104
10105                                         t.showMenu();
10106                                 }
10107                         });
10108                 }
10109         });
10110 })(tinymce);
10111
10112 (function(tinymce) {
10113         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10114
10115         tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
10116                 SplitButton : function(id, s, ed) {
10117                         this.parent(id, s, ed);
10118                         this.classPrefix = 'mceSplitButton';
10119                 },
10120
10121                 renderHTML : function() {
10122                         var h, t = this, s = t.settings, h1;
10123
10124                         h = '<tbody><tr>';
10125
10126                         if (s.image)
10127                                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
10128                         else
10129                                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
10130
10131                         h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
10132                         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>';
10133         
10134                         h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
10135                         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>';
10136
10137                         h += '</tr></tbody>';
10138                         h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0',  'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
10139                         return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
10140                 },
10141
10142                 postRender : function() {
10143                         var t = this, s = t.settings, activate;
10144
10145                         if (s.onclick) {
10146                                 activate = function(evt) {
10147                                         if (!t.isDisabled()) {
10148                                                 s.onclick(t.value);
10149                                                 Event.cancel(evt);
10150                                         }
10151                                 };
10152                                 Event.add(t.id + '_action', 'click', activate);
10153                                 Event.add(t.id, ['click', 'keydown'], function(evt) {
10154                                         var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
10155                                         if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
10156                                                 activate();
10157                                                 Event.cancel(evt);
10158                                         } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
10159                                                 t.showMenu();
10160                                                 Event.cancel(evt);
10161                                         }
10162                                 });
10163                         }
10164
10165                         Event.add(t.id + '_open', 'click', function (evt) {
10166                                 t.showMenu();
10167                                 Event.cancel(evt);
10168                         });
10169                         Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
10170                         Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
10171
10172                         // Old IE doesn't have hover on all elements
10173                         if (tinymce.isIE6 || !DOM.boxModel) {
10174                                 Event.add(t.id, 'mouseover', function() {
10175                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
10176                                                 DOM.addClass(t.id, 'mceSplitButtonHover');
10177                                 });
10178
10179                                 Event.add(t.id, 'mouseout', function() {
10180                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
10181                                                 DOM.removeClass(t.id, 'mceSplitButtonHover');
10182                                 });
10183                         }
10184                 },
10185
10186                 destroy : function() {
10187                         this.parent();
10188
10189                         Event.clear(this.id + '_action');
10190                         Event.clear(this.id + '_open');
10191                         Event.clear(this.id);
10192                 }
10193         });
10194 })(tinymce);
10195
10196 (function(tinymce) {
10197         var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
10198
10199         tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
10200                 ColorSplitButton : function(id, s, ed) {
10201                         var t = this;
10202
10203                         t.parent(id, s, ed);
10204
10205                         t.settings = s = tinymce.extend({
10206                                 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',
10207                                 grid_width : 8,
10208                                 default_color : '#888888'
10209                         }, t.settings);
10210
10211                         t.onShowMenu = new tinymce.util.Dispatcher(t);
10212
10213                         t.onHideMenu = new tinymce.util.Dispatcher(t);
10214
10215                         t.value = s.default_color;
10216                 },
10217
10218                 showMenu : function() {
10219                         var t = this, r, p, e, p2;
10220
10221                         if (t.isDisabled())
10222                                 return;
10223
10224                         if (!t.isMenuRendered) {
10225                                 t.renderMenu();
10226                                 t.isMenuRendered = true;
10227                         }
10228
10229                         if (t.isMenuVisible)
10230                                 return t.hideMenu();
10231
10232                         e = DOM.get(t.id);
10233                         DOM.show(t.id + '_menu');
10234                         DOM.addClass(e, 'mceSplitButtonSelected');
10235                         p2 = DOM.getPos(e);
10236                         DOM.setStyles(t.id + '_menu', {
10237                                 left : p2.x,
10238                                 top : p2.y + e.clientHeight,
10239                                 zIndex : 200000
10240                         });
10241                         e = 0;
10242
10243                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10244                         t.onShowMenu.dispatch(t);
10245
10246                         if (t._focused) {
10247                                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
10248                                         if (e.keyCode == 27)
10249                                                 t.hideMenu();
10250                                 });
10251
10252                                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
10253                         }
10254
10255                         t.isMenuVisible = 1;
10256                 },
10257
10258                 hideMenu : function(e) {
10259                         var t = this;
10260
10261                         if (t.isMenuVisible) {
10262                                 // Prevent double toogles by canceling the mouse click event to the button
10263                                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
10264                                         return;
10265
10266                                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
10267                                         DOM.removeClass(t.id, 'mceSplitButtonSelected');
10268                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10269                                         Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
10270                                         DOM.hide(t.id + '_menu');
10271                                 }
10272
10273                                 t.isMenuVisible = 0;
10274                         }
10275                 },
10276
10277                 renderMenu : function() {
10278                         var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
10279
10280                         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;'});
10281                         m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
10282                         DOM.add(m, 'span', {'class' : 'mceMenuLine'});
10283
10284                         n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
10285                         tb = DOM.add(n, 'tbody');
10286
10287                         // Generate color grid
10288                         i = 0;
10289                         each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
10290                                 c = c.replace(/^#/, '');
10291
10292                                 if (!i--) {
10293                                         tr = DOM.add(tb, 'tr');
10294                                         i = s.grid_width - 1;
10295                                 }
10296
10297                                 n = DOM.add(tr, 'td');
10298                                 n = DOM.add(n, 'a', {
10299                                         role : 'option',
10300                                         href : 'javascript:;',
10301                                         style : {
10302                                                 backgroundColor : '#' + c
10303                                         },
10304                                         'title': t.editor.getLang('colors.' + c, c),
10305                                         'data-mce-color' : '#' + c
10306                                 });
10307
10308                                 if (t.editor.forcedHighContrastMode) {
10309                                         n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
10310                                         if (n.getContext && (context = n.getContext("2d"))) {
10311                                                 context.fillStyle = '#' + c;
10312                                                 context.fillRect(0, 0, 16, 16);
10313                                         } else {
10314                                                 // No point leaving a canvas element around if it's not supported for drawing on anyway.
10315                                                 DOM.remove(n);
10316                                         }
10317                                 }
10318                         });
10319
10320                         if (s.more_colors_func) {
10321                                 n = DOM.add(tb, 'tr');
10322                                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
10323                                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
10324
10325                                 Event.add(n, 'click', function(e) {
10326                                         s.more_colors_func.call(s.more_colors_scope || this);
10327                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem
10328                                 });
10329                         }
10330
10331                         DOM.addClass(m, 'mceColorSplitMenu');
10332                         
10333                         new tinymce.ui.KeyboardNavigation({
10334                                 root: t.id + '_menu',
10335                                 items: DOM.select('a', t.id + '_menu'),
10336                                 onCancel: function() {
10337                                         t.hideMenu();
10338                                         t.focus();
10339                                 }
10340                         });
10341
10342                         // Prevent IE from scrolling and hindering click to occur #4019
10343                         Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
10344
10345                         Event.add(t.id + '_menu', 'click', function(e) {
10346                                 var c;
10347
10348                                 e = DOM.getParent(e.target, 'a', tb);
10349
10350                                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
10351                                         t.setColor(c);
10352
10353                                 return Event.cancel(e); // Prevent IE auto save warning
10354                         });
10355
10356                         return w;
10357                 },
10358
10359                 setColor : function(c) {
10360                         this.displayColor(c);
10361                         this.hideMenu();
10362                         this.settings.onselect(c);
10363                 },
10364                 
10365                 displayColor : function(c) {
10366                         var t = this;
10367
10368                         DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
10369
10370                         t.value = c;
10371                 },
10372
10373                 postRender : function() {
10374                         var t = this, id = t.id;
10375
10376                         t.parent();
10377                         DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
10378                         DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
10379                 },
10380
10381                 destroy : function() {
10382                         this.parent();
10383
10384                         Event.clear(this.id + '_menu');
10385                         Event.clear(this.id + '_more');
10386                         DOM.remove(this.id + '_menu');
10387                 }
10388         });
10389 })(tinymce);
10390
10391 (function(tinymce) {
10392 // Shorten class names
10393 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
10394 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
10395         renderHTML : function() {
10396                 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
10397
10398                 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
10399                 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
10400                 h.push("<span role='application'>");
10401                 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
10402                 each(controls, function(toolbar) {
10403                         h.push(toolbar.renderHTML());
10404                 });
10405                 h.push("</span>");
10406                 h.push('</div>');
10407
10408                 return h.join('');
10409         },
10410         
10411         focus : function() {
10412                 this.keyNav.focus();
10413         },
10414         
10415         postRender : function() {
10416                 var t = this, items = [];
10417
10418                 each(t.controls, function(toolbar) {
10419                         each (toolbar.controls, function(control) {
10420                                 if (control.id) {
10421                                         items.push(control);
10422                                 }
10423                         });
10424                 });
10425
10426                 t.keyNav = new tinymce.ui.KeyboardNavigation({
10427                         root: t.id,
10428                         items: items,
10429                         onCancel: function() {
10430                                 t.editor.focus();
10431                         },
10432                         excludeFromTabOrder: !t.settings.tab_focus_toolbar
10433                 });
10434         },
10435         
10436         destroy : function() {
10437                 var self = this;
10438
10439                 self.parent();
10440                 self.keyNav.destroy();
10441                 Event.clear(self.id);
10442         }
10443 });
10444 })(tinymce);
10445
10446 (function(tinymce) {
10447 // Shorten class names
10448 var dom = tinymce.DOM, each = tinymce.each;
10449 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
10450         renderHTML : function() {
10451                 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
10452
10453                 cl = t.controls;
10454                 for (i=0; i<cl.length; i++) {
10455                         // Get current control, prev control, next control and if the control is a list box or not
10456                         co = cl[i];
10457                         pr = cl[i - 1];
10458                         nx = cl[i + 1];
10459
10460                         // Add toolbar start
10461                         if (i === 0) {
10462                                 c = 'mceToolbarStart';
10463
10464                                 if (co.Button)
10465                                         c += ' mceToolbarStartButton';
10466                                 else if (co.SplitButton)
10467                                         c += ' mceToolbarStartSplitButton';
10468                                 else if (co.ListBox)
10469                                         c += ' mceToolbarStartListBox';
10470
10471                                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
10472                         }
10473
10474                         // Add toolbar end before list box and after the previous button
10475                         // This is to fix the o2k7 editor skins
10476                         if (pr && co.ListBox) {
10477                                 if (pr.Button || pr.SplitButton)
10478                                         h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
10479                         }
10480
10481                         // Render control HTML
10482
10483                         // IE 8 quick fix, needed to propertly generate a hit area for anchors
10484                         if (dom.stdMode)
10485                                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';
10486                         else
10487                                 h += '<td>' + co.renderHTML() + '</td>';
10488
10489                         // Add toolbar start after list box and before the next button
10490                         // This is to fix the o2k7 editor skins
10491                         if (nx && co.ListBox) {
10492                                 if (nx.Button || nx.SplitButton)
10493                                         h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
10494                         }
10495                 }
10496
10497                 c = 'mceToolbarEnd';
10498
10499                 if (co.Button)
10500                         c += ' mceToolbarEndButton';
10501                 else if (co.SplitButton)
10502                         c += ' mceToolbarEndSplitButton';
10503                 else if (co.ListBox)
10504                         c += ' mceToolbarEndListBox';
10505
10506                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
10507
10508                 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>');
10509         }
10510 });
10511 })(tinymce);
10512
10513 (function(tinymce) {
10514         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
10515
10516         tinymce.create('tinymce.AddOnManager', {
10517                 AddOnManager : function() {
10518                         var self = this;
10519
10520                         self.items = [];
10521                         self.urls = {};
10522                         self.lookup = {};
10523                         self.onAdd = new Dispatcher(self);
10524                 },
10525
10526                 get : function(n) {
10527                         if (this.lookup[n]) {
10528                                 return this.lookup[n].instance;
10529                         } else {
10530                                 return undefined;
10531                         }
10532                 },
10533
10534                 dependencies : function(n) {
10535                         var result;
10536                         if (this.lookup[n]) {
10537                                 result = this.lookup[n].dependencies;
10538                         }
10539                         return result || [];
10540                 },
10541
10542                 requireLangPack : function(n) {
10543                         var s = tinymce.settings;
10544
10545                         if (s && s.language && s.language_load !== false)
10546                                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
10547                 },
10548
10549                 add : function(id, o, dependencies) {
10550                         this.items.push(o);
10551                         this.lookup[id] = {instance:o, dependencies:dependencies};
10552                         this.onAdd.dispatch(this, id, o);
10553
10554                         return o;
10555                 },
10556                 createUrl: function(baseUrl, dep) {
10557                         if (typeof dep === "object") {
10558                                 return dep
10559                         } else {
10560                                 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
10561                         }
10562                 },
10563
10564                 addComponents: function(pluginName, scripts) {
10565                         var pluginUrl = this.urls[pluginName];
10566                         tinymce.each(scripts, function(script){
10567                                 tinymce.ScriptLoader.add(pluginUrl+"/"+script); 
10568                         });
10569                 },
10570
10571                 load : function(n, u, cb, s) {
10572                         var t = this, url = u;
10573
10574                         function loadDependencies() {
10575                                 var dependencies = t.dependencies(n);
10576                                 tinymce.each(dependencies, function(dep) {
10577                                         var newUrl = t.createUrl(u, dep);
10578                                         t.load(newUrl.resource, newUrl, undefined, undefined);
10579                                 });
10580                                 if (cb) {
10581                                         if (s) {
10582                                                 cb.call(s);
10583                                         } else {
10584                                                 cb.call(tinymce.ScriptLoader);
10585                                         }
10586                                 }
10587                         }
10588
10589                         if (t.urls[n])
10590                                 return;
10591                         if (typeof u === "object")
10592                                 url = u.prefix + u.resource + u.suffix;
10593
10594                         if (url.indexOf('/') != 0 && url.indexOf('://') == -1)
10595                                 url = tinymce.baseURL + '/' + url;
10596
10597                         t.urls[n] = url.substring(0, url.lastIndexOf('/'));
10598
10599                         if (t.lookup[n]) {
10600                                 loadDependencies();
10601                         } else {
10602                                 tinymce.ScriptLoader.add(url, loadDependencies, s);
10603                         }
10604                 }
10605         });
10606
10607         // Create plugin and theme managers
10608         tinymce.PluginManager = new tinymce.AddOnManager();
10609         tinymce.ThemeManager = new tinymce.AddOnManager();
10610 }(tinymce));
10611
10612 (function(tinymce) {
10613         // Shorten names
10614         var each = tinymce.each, extend = tinymce.extend,
10615                 DOM = tinymce.DOM, Event = tinymce.dom.Event,
10616                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10617                 explode = tinymce.explode,
10618                 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
10619
10620         // Setup some URLs where the editor API is located and where the document is
10621         tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
10622         if (!/[\/\\]$/.test(tinymce.documentBaseURL))
10623                 tinymce.documentBaseURL += '/';
10624
10625         tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
10626
10627         tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
10628
10629         // Add before unload listener
10630         // This was required since IE was leaking memory if you added and removed beforeunload listeners
10631         // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
10632         tinymce.onBeforeUnload = new Dispatcher(tinymce);
10633
10634         // Must be on window or IE will leak if the editor is placed in frame or iframe
10635         Event.add(window, 'beforeunload', function(e) {
10636                 tinymce.onBeforeUnload.dispatch(tinymce, e);
10637         });
10638
10639         tinymce.onAddEditor = new Dispatcher(tinymce);
10640
10641         tinymce.onRemoveEditor = new Dispatcher(tinymce);
10642
10643         tinymce.EditorManager = extend(tinymce, {
10644                 editors : [],
10645
10646                 i18n : {},
10647
10648                 activeEditor : null,
10649
10650                 init : function(s) {
10651                         var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
10652
10653                         function execCallback(se, n, s) {
10654                                 var f = se[n];
10655
10656                                 if (!f)
10657                                         return;
10658
10659                                 if (tinymce.is(f, 'string')) {
10660                                         s = f.replace(/\.\w+$/, '');
10661                                         s = s ? tinymce.resolve(s) : 0;
10662                                         f = tinymce.resolve(f);
10663                                 }
10664
10665                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
10666                         };
10667
10668                         s = extend({
10669                                 theme : "simple",
10670                                 language : "en"
10671                         }, s);
10672
10673                         t.settings = s;
10674
10675                         // Legacy call
10676                         Event.add(document, 'init', function() {
10677                                 var l, co;
10678
10679                                 execCallback(s, 'onpageload');
10680
10681                                 switch (s.mode) {
10682                                         case "exact":
10683                                                 l = s.elements || '';
10684
10685                                                 if(l.length > 0) {
10686                                                         each(explode(l), function(v) {
10687                                                                 if (DOM.get(v)) {
10688                                                                         ed = new tinymce.Editor(v, s);
10689                                                                         el.push(ed);
10690                                                                         ed.render(1);
10691                                                                 } else {
10692                                                                         each(document.forms, function(f) {
10693                                                                                 each(f.elements, function(e) {
10694                                                                                         if (e.name === v) {
10695                                                                                                 v = 'mce_editor_' + instanceCounter++;
10696                                                                                                 DOM.setAttrib(e, 'id', v);
10697
10698                                                                                                 ed = new tinymce.Editor(v, s);
10699                                                                                                 el.push(ed);
10700                                                                                                 ed.render(1);
10701                                                                                         }
10702                                                                                 });
10703                                                                         });
10704                                                                 }
10705                                                         });
10706                                                 }
10707                                                 break;
10708
10709                                         case "textareas":
10710                                         case "specific_textareas":
10711                                                 function hasClass(n, c) {
10712                                                         return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
10713                                                 };
10714
10715                                                 each(DOM.select('textarea'), function(v) {
10716                                                         if (s.editor_deselector && hasClass(v, s.editor_deselector))
10717                                                                 return;
10718
10719                                                         if (!s.editor_selector || hasClass(v, s.editor_selector)) {
10720                                                                 // Can we use the name
10721                                                                 e = DOM.get(v.name);
10722                                                                 if (!v.id && !e)
10723                                                                         v.id = v.name;
10724
10725                                                                 // Generate unique name if missing or already exists
10726                                                                 if (!v.id || t.get(v.id))
10727                                                                         v.id = DOM.uniqueId();
10728
10729                                                                 ed = new tinymce.Editor(v.id, s);
10730                                                                 el.push(ed);
10731                                                                 ed.render(1);
10732                                                         }
10733                                                 });
10734                                                 break;
10735                                 }
10736
10737                                 // Call onInit when all editors are initialized
10738                                 if (s.oninit) {
10739                                         l = co = 0;
10740
10741                                         each(el, function(ed) {
10742                                                 co++;
10743
10744                                                 if (!ed.initialized) {
10745                                                         // Wait for it
10746                                                         ed.onInit.add(function() {
10747                                                                 l++;
10748
10749                                                                 // All done
10750                                                                 if (l == co)
10751                                                                         execCallback(s, 'oninit');
10752                                                         });
10753                                                 } else
10754                                                         l++;
10755
10756                                                 // All done
10757                                                 if (l == co)
10758                                                         execCallback(s, 'oninit');                                      
10759                                         });
10760                                 }
10761                         });
10762                 },
10763
10764                 get : function(id) {
10765                         if (id === undefined)
10766                                 return this.editors;
10767
10768                         return this.editors[id];
10769                 },
10770
10771                 getInstanceById : function(id) {
10772                         return this.get(id);
10773                 },
10774
10775                 add : function(editor) {
10776                         var self = this, editors = self.editors;
10777
10778                         // Add named and index editor instance
10779                         editors[editor.id] = editor;
10780                         editors.push(editor);
10781
10782                         self._setActive(editor);
10783                         self.onAddEditor.dispatch(self, editor);
10784
10785
10786                         return editor;
10787                 },
10788
10789                 remove : function(editor) {
10790                         var t = this, i, editors = t.editors;
10791
10792                         // Not in the collection
10793                         if (!editors[editor.id])
10794                                 return null;
10795
10796                         delete editors[editor.id];
10797
10798                         for (i = 0; i < editors.length; i++) {
10799                                 if (editors[i] == editor) {
10800                                         editors.splice(i, 1);
10801                                         break;
10802                                 }
10803                         }
10804
10805                         // Select another editor since the active one was removed
10806                         if (t.activeEditor == editor)
10807                                 t._setActive(editors[0]);
10808
10809                         editor.destroy();
10810                         t.onRemoveEditor.dispatch(t, editor);
10811
10812                         return editor;
10813                 },
10814
10815                 execCommand : function(c, u, v) {
10816                         var t = this, ed = t.get(v), w;
10817
10818                         // Manager commands
10819                         switch (c) {
10820                                 case "mceFocus":
10821                                         ed.focus();
10822                                         return true;
10823
10824                                 case "mceAddEditor":
10825                                 case "mceAddControl":
10826                                         if (!t.get(v))
10827                                                 new tinymce.Editor(v, t.settings).render();
10828
10829                                         return true;
10830
10831                                 case "mceAddFrameControl":
10832                                         w = v.window;
10833
10834                                         // Add tinyMCE global instance and tinymce namespace to specified window
10835                                         w.tinyMCE = tinyMCE;
10836                                         w.tinymce = tinymce;
10837
10838                                         tinymce.DOM.doc = w.document;
10839                                         tinymce.DOM.win = w;
10840
10841                                         ed = new tinymce.Editor(v.element_id, v);
10842                                         ed.render();
10843
10844                                         // Fix IE memory leaks
10845                                         if (tinymce.isIE) {
10846                                                 function clr() {
10847                                                         ed.destroy();
10848                                                         w.detachEvent('onunload', clr);
10849                                                         w = w.tinyMCE = w.tinymce = null; // IE leak
10850                                                 };
10851
10852                                                 w.attachEvent('onunload', clr);
10853                                         }
10854
10855                                         v.page_window = null;
10856
10857                                         return true;
10858
10859                                 case "mceRemoveEditor":
10860                                 case "mceRemoveControl":
10861                                         if (ed)
10862                                                 ed.remove();
10863
10864                                         return true;
10865
10866                                 case 'mceToggleEditor':
10867                                         if (!ed) {
10868                                                 t.execCommand('mceAddControl', 0, v);
10869                                                 return true;
10870                                         }
10871
10872                                         if (ed.isHidden())
10873                                                 ed.show();
10874                                         else
10875                                                 ed.hide();
10876
10877                                         return true;
10878                         }
10879
10880                         // Run command on active editor
10881                         if (t.activeEditor)
10882                                 return t.activeEditor.execCommand(c, u, v);
10883
10884                         return false;
10885                 },
10886
10887                 execInstanceCommand : function(id, c, u, v) {
10888                         var ed = this.get(id);
10889
10890                         if (ed)
10891                                 return ed.execCommand(c, u, v);
10892
10893                         return false;
10894                 },
10895
10896                 triggerSave : function() {
10897                         each(this.editors, function(e) {
10898                                 e.save();
10899                         });
10900                 },
10901
10902                 addI18n : function(p, o) {
10903                         var lo, i18n = this.i18n;
10904
10905                         if (!tinymce.is(p, 'string')) {
10906                                 each(p, function(o, lc) {
10907                                         each(o, function(o, g) {
10908                                                 each(o, function(o, k) {
10909                                                         if (g === 'common')
10910                                                                 i18n[lc + '.' + k] = o;
10911                                                         else
10912                                                                 i18n[lc + '.' + g + '.' + k] = o;
10913                                                 });
10914                                         });
10915                                 });
10916                         } else {
10917                                 each(o, function(o, k) {
10918                                         i18n[p + '.' + k] = o;
10919                                 });
10920                         }
10921                 },
10922
10923                 // Private methods
10924
10925                 _setActive : function(editor) {
10926                         this.selectedInstance = this.activeEditor = editor;
10927                 }
10928         });
10929 })(tinymce);
10930
10931 (function(tinymce) {
10932         // Shorten these names
10933         var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
10934                 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
10935                 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
10936                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10937                 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
10938
10939         tinymce.create('tinymce.Editor', {
10940                 Editor : function(id, s) {
10941                         var t = this;
10942
10943                         t.id = t.editorId = id;
10944
10945                         t.execCommands = {};
10946                         t.queryStateCommands = {};
10947                         t.queryValueCommands = {};
10948
10949                         t.isNotDirty = false;
10950
10951                         t.plugins = {};
10952
10953                         // Add events to the editor
10954                         each([
10955                                 'onPreInit',
10956
10957                                 'onBeforeRenderUI',
10958
10959                                 'onPostRender',
10960
10961                                 'onInit',
10962
10963                                 'onRemove',
10964
10965                                 'onActivate',
10966
10967                                 'onDeactivate',
10968
10969                                 'onClick',
10970
10971                                 'onEvent',
10972
10973                                 'onMouseUp',
10974
10975                                 'onMouseDown',
10976
10977                                 'onDblClick',
10978
10979                                 'onKeyDown',
10980
10981                                 'onKeyUp',
10982
10983                                 'onKeyPress',
10984
10985                                 'onContextMenu',
10986
10987                                 'onSubmit',
10988
10989                                 'onReset',
10990
10991                                 'onPaste',
10992
10993                                 'onPreProcess',
10994
10995                                 'onPostProcess',
10996
10997                                 'onBeforeSetContent',
10998
10999                                 'onBeforeGetContent',
11000
11001                                 'onSetContent',
11002
11003                                 'onGetContent',
11004
11005                                 'onLoadContent',
11006
11007                                 'onSaveContent',
11008
11009                                 'onNodeChange',
11010
11011                                 'onChange',
11012
11013                                 'onBeforeExecCommand',
11014
11015                                 'onExecCommand',
11016
11017                                 'onUndo',
11018
11019                                 'onRedo',
11020
11021                                 'onVisualAid',
11022
11023                                 'onSetProgressState'
11024                         ], function(e) {
11025                                 t[e] = new Dispatcher(t);
11026                         });
11027
11028                         t.settings = s = extend({
11029                                 id : id,
11030                                 language : 'en',
11031                                 docs_language : 'en',
11032                                 theme : 'simple',
11033                                 skin : 'default',
11034                                 delta_width : 0,
11035                                 delta_height : 0,
11036                                 popup_css : '',
11037                                 plugins : '',
11038                                 document_base_url : tinymce.documentBaseURL,
11039                                 add_form_submit_trigger : 1,
11040                                 submit_patch : 1,
11041                                 add_unload_trigger : 1,
11042                                 convert_urls : 1,
11043                                 relative_urls : 1,
11044                                 remove_script_host : 1,
11045                                 table_inline_editing : 0,
11046                                 object_resizing : 1,
11047                                 cleanup : 1,
11048                                 accessibility_focus : 1,
11049                                 custom_shortcuts : 1,
11050                                 custom_undo_redo_keyboard_shortcuts : 1,
11051                                 custom_undo_redo_restore_selection : 1,
11052                                 custom_undo_redo : 1,
11053                                 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
11054                                 visual_table_class : 'mceItemTable',
11055                                 visual : 1,
11056                                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
11057                                 apply_source_formatting : 1,
11058                                 directionality : 'ltr',
11059                                 forced_root_block : 'p',
11060                                 hidden_input : 1,
11061                                 padd_empty_editor : 1,
11062                                 render_ui : 1,
11063                                 init_theme : 1,
11064                                 force_p_newlines : 1,
11065                                 indentation : '30px',
11066                                 keep_styles : 1,
11067                                 fix_table_elements : 1,
11068                                 inline_styles : 1,
11069                                 convert_fonts_to_spans : true,
11070                                 indent : 'simple',
11071                                 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
11072                                 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
11073                                 validate : true,
11074                                 entity_encoding : 'named',
11075                                 url_converter : t.convertURL,
11076                                 url_converter_scope : t,
11077                                 ie7_compat : true
11078                         }, s);
11079
11080                         t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
11081                                 base_uri : tinyMCE.baseURI
11082                         });
11083
11084                         t.baseURI = tinymce.baseURI;
11085
11086                         t.contentCSS = [];
11087
11088                         // Call setup
11089                         t.execCallback('setup', t);
11090                 },
11091
11092                 render : function(nst) {
11093                         var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
11094
11095                         // Page is not loaded yet, wait for it
11096                         if (!Event.domLoaded) {
11097                                 Event.add(document, 'init', function() {
11098                                         t.render();
11099                                 });
11100                                 return;
11101                         }
11102
11103                         tinyMCE.settings = s;
11104
11105                         // Element not found, then skip initialization
11106                         if (!t.getElement())
11107                                 return;
11108
11109                         // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
11110                         // here since the browser says it has contentEditable support but there is no visible
11111                         // caret We will remove this check ones Apple implements full contentEditable support
11112                         if (tinymce.isIDevice && !tinymce.isIOS5)
11113                                 return;
11114
11115                         // Add hidden input for non input elements inside form elements
11116                         if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
11117                                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
11118
11119                         if (tinymce.WindowManager)
11120                                 t.windowManager = new tinymce.WindowManager(t);
11121
11122                         if (s.encoding == 'xml') {
11123                                 t.onGetContent.add(function(ed, o) {
11124                                         if (o.save)
11125                                                 o.content = DOM.encode(o.content);
11126                                 });
11127                         }
11128
11129                         if (s.add_form_submit_trigger) {
11130                                 t.onSubmit.addToTop(function() {
11131                                         if (t.initialized) {
11132                                                 t.save();
11133                                                 t.isNotDirty = 1;
11134                                         }
11135                                 });
11136                         }
11137
11138                         if (s.add_unload_trigger) {
11139                                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
11140                                         if (t.initialized && !t.destroyed && !t.isHidden())
11141                                                 t.save({format : 'raw', no_events : true});
11142                                 });
11143                         }
11144
11145                         tinymce.addUnload(t.destroy, t);
11146
11147                         if (s.submit_patch) {
11148                                 t.onBeforeRenderUI.add(function() {
11149                                         var n = t.getElement().form;
11150
11151                                         if (!n)
11152                                                 return;
11153
11154                                         // Already patched
11155                                         if (n._mceOldSubmit)
11156                                                 return;
11157
11158                                         // Check page uses id="submit" or name="submit" for it's submit button
11159                                         if (!n.submit.nodeType && !n.submit.length) {
11160                                                 t.formElement = n;
11161                                                 n._mceOldSubmit = n.submit;
11162                                                 n.submit = function() {
11163                                                         // Save all instances
11164                                                         tinymce.triggerSave();
11165                                                         t.isNotDirty = 1;
11166
11167                                                         return t.formElement._mceOldSubmit(t.formElement);
11168                                                 };
11169                                         }
11170
11171                                         n = null;
11172                                 });
11173                         }
11174
11175                         // Load scripts
11176                         function loadScripts() {
11177                                 if (s.language && s.language_load !== false)
11178                                         sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
11179
11180                                 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
11181                                         ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
11182
11183                                 each(explode(s.plugins), function(p) {
11184                                         if (p &&!PluginManager.urls[p]) {
11185                                                 if (p.charAt(0) == '-') {
11186                                                         p = p.substr(1, p.length);
11187                                                         var dependencies = PluginManager.dependencies(p);
11188                                                         each(dependencies, function(dep) {
11189                                                                 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
11190                                                                 var dep = PluginManager.createUrl(defaultSettings, dep);
11191                                                                 PluginManager.load(dep.resource, dep);
11192                                                                 
11193                                                         });
11194                                                 } else {
11195                                                         // Skip safari plugin, since it is removed as of 3.3b1
11196                                                         if (p == 'safari') {
11197                                                                 return;
11198                                                         }
11199                                                         PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
11200                                                 }
11201                                         }
11202                                 });
11203
11204                                 // Init when que is loaded
11205                                 sl.loadQueue(function() {
11206                                         if (!t.removed)
11207                                                 t.init();
11208                                 });
11209                         };
11210
11211                         loadScripts();
11212                 },
11213
11214                 init : function() {
11215                         var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
11216
11217                         tinymce.add(t);
11218
11219                         s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
11220
11221                         if (s.theme) {
11222                                 s.theme = s.theme.replace(/-/, '');
11223                                 o = ThemeManager.get(s.theme);
11224                                 t.theme = new o();
11225
11226                                 if (t.theme.init && s.init_theme)
11227                                         t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
11228                         }
11229                         function initPlugin(p) {
11230                                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
11231                                 if (c && tinymce.inArray(initializedPlugins,p) === -1) {
11232                                         each(PluginManager.dependencies(p), function(dep){
11233                                                 initPlugin(dep);
11234                                         });
11235                                         po = new c(t, u);
11236
11237                                         t.plugins[p] = po;
11238
11239                                         if (po.init) {
11240                                                 po.init(t, u);
11241                                                 initializedPlugins.push(p);
11242                                         }
11243                                 }
11244                         }
11245                         
11246                         // Create all plugins
11247                         each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
11248
11249                         // Setup popup CSS path(s)
11250                         if (s.popup_css !== false) {
11251                                 if (s.popup_css)
11252                                         s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
11253                                 else
11254                                         s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
11255                         }
11256
11257                         if (s.popup_css_add)
11258                                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
11259
11260                         t.controlManager = new tinymce.ControlManager(t);
11261
11262                         if (s.custom_undo_redo) {
11263                                 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
11264                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
11265                                                 t.undoManager.beforeChange();
11266                                 });
11267
11268                                 t.onExecCommand.add(function(ed, cmd, ui, val, a) {
11269                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
11270                                                 t.undoManager.add();
11271                                 });
11272                         }
11273
11274                         t.onExecCommand.add(function(ed, c) {
11275                                 // Don't refresh the select lists until caret move
11276                                 if (!/^(FontName|FontSize)$/.test(c))
11277                                         t.nodeChanged();
11278                         });
11279
11280                         // Remove ghost selections on images and tables in Gecko
11281                         if (isGecko) {
11282                                 function repaint(a, o) {
11283                                         if (!o || !o.initial)
11284                                                 t.execCommand('mceRepaint');
11285                                 };
11286
11287                                 t.onUndo.add(repaint);
11288                                 t.onRedo.add(repaint);
11289                                 t.onSetContent.add(repaint);
11290                         }
11291
11292                         // Enables users to override the control factory
11293                         t.onBeforeRenderUI.dispatch(t, t.controlManager);
11294
11295                         // Measure box
11296                         if (s.render_ui) {
11297                                 w = s.width || e.style.width || e.offsetWidth;
11298                                 h = s.height || e.style.height || e.offsetHeight;
11299                                 t.orgDisplay = e.style.display;
11300                                 re = /^[0-9\.]+(|px)$/i;
11301
11302                                 if (re.test('' + w))
11303                                         w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
11304
11305                                 if (re.test('' + h))
11306                                         h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
11307
11308                                 // Render UI
11309                                 o = t.theme.renderUI({
11310                                         targetNode : e,
11311                                         width : w,
11312                                         height : h,
11313                                         deltaWidth : s.delta_width,
11314                                         deltaHeight : s.delta_height
11315                                 });
11316
11317                                 t.editorContainer = o.editorContainer;
11318                         }
11319
11320
11321                         // User specified a document.domain value
11322                         if (document.domain && location.hostname != document.domain)
11323                                 tinymce.relaxedDomain = document.domain;
11324
11325                         // Resize editor
11326                         DOM.setStyles(o.sizeContainer || o.editorContainer, {
11327                                 width : w,
11328                                 height : h
11329                         });
11330
11331                         // Load specified content CSS last
11332                         if (s.content_css) {
11333                                 tinymce.each(explode(s.content_css), function(u) {
11334                                         t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
11335                                 });
11336                         }
11337
11338                         h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
11339                         if (h < 100)
11340                                 h = 100;
11341
11342                         t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
11343
11344                         // We only need to override paths if we have to
11345                         // IE has a bug where it remove site absolute urls to relative ones if this is specified
11346                         if (s.document_base_url != tinymce.documentBaseURL)
11347                                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
11348
11349                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
11350                         if (s.ie7_compat)
11351                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
11352                         else
11353                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
11354
11355                         t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
11356
11357                         // Firefox 2 doesn't load stylesheets correctly this way
11358                         if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {
11359                                 for (i = 0; i < t.contentCSS.length; i++)
11360                                         t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
11361
11362                                 t.contentCSS = [];
11363                         }
11364
11365                         bi = s.body_id || 'tinymce';
11366                         if (bi.indexOf('=') != -1) {
11367                                 bi = t.getParam('body_id', '', 'hash');
11368                                 bi = bi[t.id] || bi;
11369                         }
11370
11371                         bc = s.body_class || '';
11372                         if (bc.indexOf('=') != -1) {
11373                                 bc = t.getParam('body_class', '', 'hash');
11374                                 bc = bc[t.id] || '';
11375                         }
11376
11377                         t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
11378
11379                         // Domain relaxing enabled, then set document domain
11380                         if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
11381                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button
11382                                 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();})()';                         
11383                         }
11384
11385                         // Create iframe
11386                         // TODO: ACC add the appropriate description on this.
11387                         n = DOM.add(o.iframeContainer, 'iframe', { 
11388                                 id : t.id + "_ifr",
11389                                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
11390                                 frameBorder : '0',
11391                                 allowTransparency : "true",
11392                                 title : s.aria_label,
11393                                 style : {
11394                                         width : '100%',
11395                                         height : h
11396                                 }
11397                         });
11398
11399                         t.contentAreaContainer = o.iframeContainer;
11400                         DOM.get(o.editorContainer).style.display = t.orgDisplay;
11401                         DOM.get(t.id).style.display = 'none';
11402                         DOM.setAttrib(t.id, 'aria-hidden', true);
11403
11404                         if (!tinymce.relaxedDomain || !u)
11405                                 t.setupIframe();
11406
11407                         e = n = o = null; // Cleanup
11408                 },
11409
11410                 setupIframe : function(filled) {
11411                         var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
11412
11413                         // Setup iframe body
11414                         if ((!isIE || !tinymce.relaxedDomain) && !filled) {
11415                                 // We need to wait for the load event on Gecko
11416                                 if (isGecko && !s.readonly) {
11417                                         t.getWin().addEventListener("DOMContentLoaded", function() {
11418                                                 window.setTimeout(function() {
11419                                                         var b = t.getBody(), undef;
11420
11421                                                         // Editable element needs to have some contents or backspace/delete won't work properly for some odd reason on FF 3.6 or older
11422                                                         b.innerHTML = '<br>';
11423
11424                                                         // Check if Gecko supports contentEditable mode FF2 doesn't
11425                                                         if (b.contentEditable !== undef) {
11426                                                                 // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
11427                                                                 b.contentEditable = false;
11428                                                                 b.contentEditable = true;
11429
11430                                                                 // Caret doesn't get rendered when you mousedown on the HTML element on FF 3.x
11431                                                                 t.onMouseDown.add(function(ed, e) {
11432                                                                         if (e.target.nodeName === "HTML") {
11433                                                                                 // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus
11434                                                                                 b.contentEditable = false;
11435                                                                                 b.contentEditable = true;
11436
11437                                                                                 d.designMode = 'on'; // Render the caret
11438
11439                                                                                 // Remove design mode again after a while so it has some time to execute
11440                                                                                 window.setTimeout(function() {
11441                                                                                         d.designMode = 'off';
11442                                                                                         t.getBody().focus();
11443                                                                                 }, 1);
11444                                                                         }
11445                                                                 });
11446                                                         } else
11447                                                                 d.designMode = 'on';
11448
11449                                                         // Call setup frame once the contentEditable/designMode has been initialized
11450                                                         // since the caret won't be rendered some times otherwise.
11451                                                         t.setupIframe(true);
11452                                                 }, 1);
11453                                         }, false);
11454                                 }
11455
11456                                 d.open();
11457                                 d.write(t.iframeHTML);
11458                                 d.close();
11459
11460                                 if (tinymce.relaxedDomain)
11461                                         d.domain = tinymce.relaxedDomain;
11462
11463                                 // Wait for iframe onload event on Gecko
11464                                 if (isGecko && !s.readonly)
11465                                         return;
11466                         }
11467
11468                         // It will not steal focus while setting contentEditable
11469                         b = t.getBody();
11470                         b.disabled = true;
11471
11472                         if (!isGecko && !s.readonly)
11473                                 b.contentEditable = true;
11474
11475                         b.disabled = false;
11476
11477                         t.schema = new tinymce.html.Schema(s);
11478
11479                         t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
11480                                 keep_values : true,
11481                                 url_converter : t.convertURL,
11482                                 url_converter_scope : t,
11483                                 hex_colors : s.force_hex_style_colors,
11484                                 class_filter : s.class_filter,
11485                                 update_styles : 1,
11486                                 fix_ie_paragraphs : 1,
11487                                 schema : t.schema
11488                         });
11489
11490                         t.parser = new tinymce.html.DomParser(s, t.schema);
11491
11492                         // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
11493                         if (!t.settings.allow_html_in_named_anchor) {
11494                                 t.parser.addAttributeFilter('name', function(nodes, name) {
11495                                         var i = nodes.length, sibling, prevSibling, parent, node;
11496         
11497                                         while (i--) {
11498                                                 node = nodes[i];
11499                                                 if (node.name === 'a' && node.firstChild) {
11500                                                         parent = node.parent;
11501         
11502                                                         // Move children after current node
11503                                                         sibling = node.lastChild;
11504                                                         do {
11505                                                                 prevSibling = sibling.prev;
11506                                                                 parent.insert(sibling, node);
11507                                                                 sibling = prevSibling;
11508                                                         } while (sibling);
11509                                                 }
11510                                         }
11511                                 });
11512                         }
11513
11514                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style
11515                         t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
11516                                 var i = nodes.length, node, dom = t.dom, value, internalName;
11517
11518                                 while (i--) {
11519                                         node = nodes[i];
11520                                         value = node.attr(name);
11521                                         internalName = 'data-mce-' + name;
11522
11523                                         // Add internal attribute if we need to we don't on a refresh of the document
11524                                         if (!node.attributes.map[internalName]) {       
11525                                                 if (name === "style")
11526                                                         node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
11527                                                 else
11528                                                         node.attr(internalName, t.convertURL(value, name, node.name));
11529                                         }
11530                                 }
11531                         });
11532
11533                         // Keep scripts from executing
11534                         t.parser.addNodeFilter('script', function(nodes, name) {
11535                                 var i = nodes.length;
11536
11537                                 while (i--)
11538                                         nodes[i].attr('type', 'mce-text/javascript');
11539                         });
11540
11541                         t.parser.addNodeFilter('#cdata', function(nodes, name) {
11542                                 var i = nodes.length, node;
11543
11544                                 while (i--) {
11545                                         node = nodes[i];
11546                                         node.type = 8;
11547                                         node.name = '#comment';
11548                                         node.value = '[CDATA[' + node.value + ']]';
11549                                 }
11550                         });
11551
11552                         t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
11553                                 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
11554
11555                                 while (i--) {
11556                                         node = nodes[i];
11557
11558                                         if (node.isEmpty(nonEmptyElements))
11559                                                 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
11560                                 }
11561                         });
11562
11563                         t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
11564
11565                         t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
11566
11567                         t.formatter = new tinymce.Formatter(this);
11568
11569                         // Register default formats
11570                         t.formatter.register({
11571                                 alignleft : [
11572                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
11573                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
11574                                 ],
11575
11576                                 aligncenter : [
11577                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
11578                                         {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
11579                                         {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
11580                                 ],
11581
11582                                 alignright : [
11583                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
11584                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
11585                                 ],
11586
11587                                 alignfull : [
11588                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
11589                                 ],
11590
11591                                 bold : [
11592                                         {inline : 'strong', remove : 'all'},
11593                                         {inline : 'span', styles : {fontWeight : 'bold'}},
11594                                         {inline : 'b', remove : 'all'}
11595                                 ],
11596
11597                                 italic : [
11598                                         {inline : 'em', remove : 'all'},
11599                                         {inline : 'span', styles : {fontStyle : 'italic'}},
11600                                         {inline : 'i', remove : 'all'}
11601                                 ],
11602
11603                                 underline : [
11604                                         {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
11605                                         {inline : 'u', remove : 'all'}
11606                                 ],
11607
11608                                 strikethrough : [
11609                                         {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
11610                                         {inline : 'strike', remove : 'all'}
11611                                 ],
11612
11613                                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
11614                                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
11615                                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},
11616                                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},
11617                                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
11618                                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
11619                                 subscript : {inline : 'sub'},
11620                                 superscript : {inline : 'sup'},
11621
11622                                 removeformat : [
11623                                         {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
11624                                         {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
11625                                         {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
11626                                 ]
11627                         });
11628
11629                         // Register default block formats
11630                         each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
11631                                 t.formatter.register(name, {block : name, remove : 'all'});
11632                         });
11633
11634                         // Register user defined formats
11635                         t.formatter.register(t.settings.formats);
11636
11637                         t.undoManager = new tinymce.UndoManager(t);
11638
11639                         // Pass through
11640                         t.undoManager.onAdd.add(function(um, l) {
11641                                 if (um.hasUndo())
11642                                         return t.onChange.dispatch(t, l, um);
11643                         });
11644
11645                         t.undoManager.onUndo.add(function(um, l) {
11646                                 return t.onUndo.dispatch(t, l, um);
11647                         });
11648
11649                         t.undoManager.onRedo.add(function(um, l) {
11650                                 return t.onRedo.dispatch(t, l, um);
11651                         });
11652
11653                         t.forceBlocks = new tinymce.ForceBlocks(t, {
11654                                 forced_root_block : s.forced_root_block
11655                         });
11656
11657                         t.editorCommands = new tinymce.EditorCommands(t);
11658
11659                         // Pass through
11660                         t.serializer.onPreProcess.add(function(se, o) {
11661                                 return t.onPreProcess.dispatch(t, o, se);
11662                         });
11663
11664                         t.serializer.onPostProcess.add(function(se, o) {
11665                                 return t.onPostProcess.dispatch(t, o, se);
11666                         });
11667
11668                         t.onPreInit.dispatch(t);
11669
11670                         if (!s.gecko_spellcheck)
11671                                 t.getBody().spellcheck = 0;
11672
11673                         if (!s.readonly)
11674                                 t._addEvents();
11675
11676                         t.controlManager.onPostRender.dispatch(t, t.controlManager);
11677                         t.onPostRender.dispatch(t);
11678
11679                         t.quirks = new tinymce.util.Quirks(this);
11680
11681                         if (s.directionality)
11682                                 t.getBody().dir = s.directionality;
11683
11684                         if (s.nowrap)
11685                                 t.getBody().style.whiteSpace = "nowrap";
11686
11687                         if (s.handle_node_change_callback) {
11688                                 t.onNodeChange.add(function(ed, cm, n) {
11689                                         t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
11690                                 });
11691                         }
11692
11693                         if (s.save_callback) {
11694                                 t.onSaveContent.add(function(ed, o) {
11695                                         var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
11696
11697                                         if (h)
11698                                                 o.content = h;
11699                                 });
11700                         }
11701
11702                         if (s.onchange_callback) {
11703                                 t.onChange.add(function(ed, l) {
11704                                         t.execCallback('onchange_callback', t, l);
11705                                 });
11706                         }
11707
11708                         if (s.protect) {
11709                                 t.onBeforeSetContent.add(function(ed, o) {
11710                                         if (s.protect) {
11711                                                 each(s.protect, function(pattern) {
11712                                                         o.content = o.content.replace(pattern, function(str) {
11713                                                                 return '<!--mce:protected ' + escape(str) + '-->';
11714                                                         });
11715                                                 });
11716                                         }
11717                                 });
11718                         }
11719
11720                         if (s.convert_newlines_to_brs) {
11721                                 t.onBeforeSetContent.add(function(ed, o) {
11722                                         if (o.initial)
11723                                                 o.content = o.content.replace(/\r?\n/g, '<br />');
11724                                 });
11725                         }
11726
11727                         if (s.preformatted) {
11728                                 t.onPostProcess.add(function(ed, o) {
11729                                         o.content = o.content.replace(/^\s*<pre.*?>/, '');
11730                                         o.content = o.content.replace(/<\/pre>\s*$/, '');
11731
11732                                         if (o.set)
11733                                                 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
11734                                 });
11735                         }
11736
11737                         if (s.verify_css_classes) {
11738                                 t.serializer.attribValueFilter = function(n, v) {
11739                                         var s, cl;
11740
11741                                         if (n == 'class') {
11742                                                 // Build regexp for classes
11743                                                 if (!t.classesRE) {
11744                                                         cl = t.dom.getClasses();
11745
11746                                                         if (cl.length > 0) {
11747                                                                 s = '';
11748
11749                                                                 each (cl, function(o) {
11750                                                                         s += (s ? '|' : '') + o['class'];
11751                                                                 });
11752
11753                                                                 t.classesRE = new RegExp('(' + s + ')', 'gi');
11754                                                         }
11755                                                 }
11756
11757                                                 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
11758                                         }
11759
11760                                         return v;
11761                                 };
11762                         }
11763
11764                         if (s.cleanup_callback) {
11765                                 t.onBeforeSetContent.add(function(ed, o) {
11766                                         o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11767                                 });
11768
11769                                 t.onPreProcess.add(function(ed, o) {
11770                                         if (o.set)
11771                                                 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
11772
11773                                         if (o.get)
11774                                                 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
11775                                 });
11776
11777                                 t.onPostProcess.add(function(ed, o) {
11778                                         if (o.set)
11779                                                 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11780
11781                                         if (o.get)                                              
11782                                                 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
11783                                 });
11784                         }
11785
11786                         if (s.save_callback) {
11787                                 t.onGetContent.add(function(ed, o) {
11788                                         if (o.save)
11789                                                 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
11790                                 });
11791                         }
11792
11793                         if (s.handle_event_callback) {
11794                                 t.onEvent.add(function(ed, e, o) {
11795                                         if (t.execCallback('handle_event_callback', e, ed, o) === false)
11796                                                 Event.cancel(e);
11797                                 });
11798                         }
11799
11800                         // Add visual aids when new contents is added
11801                         t.onSetContent.add(function() {
11802                                 t.addVisual(t.getBody());
11803                         });
11804
11805                         // Remove empty contents
11806                         if (s.padd_empty_editor) {
11807                                 t.onPostProcess.add(function(ed, o) {
11808                                         o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
11809                                 });
11810                         }
11811
11812                         if (isGecko) {
11813                                 // Fix gecko link bug, when a link is placed at the end of block elements there is
11814                                 // no way to move the caret behind the link. This fix adds a bogus br element after the link
11815                                 function fixLinks(ed, o) {
11816                                         each(ed.dom.select('a'), function(n) {
11817                                                 var pn = n.parentNode;
11818
11819                                                 if (ed.dom.isBlock(pn) && pn.lastChild === n)
11820                                                         ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
11821                                         });
11822                                 };
11823
11824                                 t.onExecCommand.add(function(ed, cmd) {
11825                                         if (cmd === 'CreateLink')
11826                                                 fixLinks(ed);
11827                                 });
11828
11829                                 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
11830                         }
11831
11832                         t.load({initial : true, format : 'html'});
11833                         t.startContent = t.getContent({format : 'raw'});
11834                         t.undoManager.add();
11835                         t.initialized = true;
11836
11837                         t.onInit.dispatch(t);
11838                         t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
11839                         t.execCallback('init_instance_callback', t);
11840                         t.focus(true);
11841                         t.nodeChanged({initial : 1});
11842
11843                         // Load specified content CSS last
11844                         each(t.contentCSS, function(u) {
11845                                 t.dom.loadCSS(u);
11846                         });
11847
11848                         // Handle auto focus
11849                         if (s.auto_focus) {
11850                                 setTimeout(function () {
11851                                         var ed = tinymce.get(s.auto_focus);
11852
11853                                         ed.selection.select(ed.getBody(), 1);
11854                                         ed.selection.collapse(1);
11855                                         ed.getBody().focus();
11856                                         ed.getWin().focus();
11857                                 }, 100);
11858                         }
11859
11860                         e = null;
11861                 },
11862
11863
11864                 focus : function(sf) {
11865                         var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
11866
11867                         if (!sf) {
11868                                 // Get selected control element
11869                                 ieRng = selection.getRng();
11870                                 if (ieRng.item) {
11871                                         controlElm = ieRng.item(0);
11872                                 }
11873
11874                                 selection.normalize();
11875
11876                                 // Is not content editable
11877                                 if (!ce)
11878                                         t.getWin().focus();
11879
11880                                 // Focus the body as well since it's contentEditable
11881                                 if (tinymce.isGecko) {
11882                                         t.getBody().focus();
11883                                 }
11884
11885                                 // Restore selected control element
11886                                 // This is needed when for example an image is selected within a
11887                                 // layer a call to focus will then remove the control selection
11888                                 if (controlElm && controlElm.ownerDocument == doc) {
11889                                         ieRng = doc.body.createControlRange();
11890                                         ieRng.addElement(controlElm);
11891                                         ieRng.select();
11892                                 }
11893
11894                         }
11895
11896                         if (tinymce.activeEditor != t) {
11897                                 if ((oed = tinymce.activeEditor) != null)
11898                                         oed.onDeactivate.dispatch(oed, t);
11899
11900                                 t.onActivate.dispatch(t, oed);
11901                         }
11902
11903                         tinymce._setActive(t);
11904                 },
11905
11906                 execCallback : function(n) {
11907                         var t = this, f = t.settings[n], s;
11908
11909                         if (!f)
11910                                 return;
11911
11912                         // Look through lookup
11913                         if (t.callbackLookup && (s = t.callbackLookup[n])) {
11914                                 f = s.func;
11915                                 s = s.scope;
11916                         }
11917
11918                         if (is(f, 'string')) {
11919                                 s = f.replace(/\.\w+$/, '');
11920                                 s = s ? tinymce.resolve(s) : 0;
11921                                 f = tinymce.resolve(f);
11922                                 t.callbackLookup = t.callbackLookup || {};
11923                                 t.callbackLookup[n] = {func : f, scope : s};
11924                         }
11925
11926                         return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
11927                 },
11928
11929                 translate : function(s) {
11930                         var c = this.settings.language || 'en', i18n = tinymce.i18n;
11931
11932                         if (!s)
11933                                 return '';
11934
11935                         return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
11936                                 return i18n[c + '.' + b] || '{#' + b + '}';
11937                         });
11938                 },
11939
11940                 getLang : function(n, dv) {
11941                         return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
11942                 },
11943
11944                 getParam : function(n, dv, ty) {
11945                         var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
11946
11947                         if (ty === 'hash') {
11948                                 o = {};
11949
11950                                 if (is(v, 'string')) {
11951                                         each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
11952                                                 v = v.split('=');
11953
11954                                                 if (v.length > 1)
11955                                                         o[tr(v[0])] = tr(v[1]);
11956                                                 else
11957                                                         o[tr(v[0])] = tr(v);
11958                                         });
11959                                 } else
11960                                         o = v;
11961
11962                                 return o;
11963                         }
11964
11965                         return v;
11966                 },
11967
11968                 nodeChanged : function(o) {
11969                         var t = this, s = t.selection, n = s.getStart() || t.getBody();
11970
11971                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
11972                         if (t.initialized) {
11973                                 o = o || {};
11974                                 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
11975
11976                                 // Get parents and add them to object
11977                                 o.parents = [];
11978                                 t.dom.getParent(n, function(node) {
11979                                         if (node.nodeName == 'BODY')
11980                                                 return true;
11981
11982                                         o.parents.push(node);
11983                                 });
11984
11985                                 t.onNodeChange.dispatch(
11986                                         t,
11987                                         o ? o.controlManager || t.controlManager : t.controlManager,
11988                                         n,
11989                                         s.isCollapsed(),
11990                                         o
11991                                 );
11992                         }
11993                 },
11994
11995                 addButton : function(n, s) {
11996                         var t = this;
11997
11998                         t.buttons = t.buttons || {};
11999                         t.buttons[n] = s;
12000                 },
12001
12002                 addCommand : function(name, callback, scope) {
12003                         this.execCommands[name] = {func : callback, scope : scope || this};
12004                 },
12005
12006                 addQueryStateHandler : function(name, callback, scope) {
12007                         this.queryStateCommands[name] = {func : callback, scope : scope || this};
12008                 },
12009
12010                 addQueryValueHandler : function(name, callback, scope) {
12011                         this.queryValueCommands[name] = {func : callback, scope : scope || this};
12012                 },
12013
12014                 addShortcut : function(pa, desc, cmd_func, sc) {
12015                         var t = this, c;
12016
12017                         if (!t.settings.custom_shortcuts)
12018                                 return false;
12019
12020                         t.shortcuts = t.shortcuts || {};
12021
12022                         if (is(cmd_func, 'string')) {
12023                                 c = cmd_func;
12024
12025                                 cmd_func = function() {
12026                                         t.execCommand(c, false, null);
12027                                 };
12028                         }
12029
12030                         if (is(cmd_func, 'object')) {
12031                                 c = cmd_func;
12032
12033                                 cmd_func = function() {
12034                                         t.execCommand(c[0], c[1], c[2]);
12035                                 };
12036                         }
12037
12038                         each(explode(pa), function(pa) {
12039                                 var o = {
12040                                         func : cmd_func,
12041                                         scope : sc || this,
12042                                         desc : desc,
12043                                         alt : false,
12044                                         ctrl : false,
12045                                         shift : false
12046                                 };
12047
12048                                 each(explode(pa, '+'), function(v) {
12049                                         switch (v) {
12050                                                 case 'alt':
12051                                                 case 'ctrl':
12052                                                 case 'shift':
12053                                                         o[v] = true;
12054                                                         break;
12055
12056                                                 default:
12057                                                         o.charCode = v.charCodeAt(0);
12058                                                         o.keyCode = v.toUpperCase().charCodeAt(0);
12059                                         }
12060                                 });
12061
12062                                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
12063                         });
12064
12065                         return true;
12066                 },
12067
12068                 execCommand : function(cmd, ui, val, a) {
12069                         var t = this, s = 0, o, st;
12070
12071                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
12072                                 t.focus();
12073
12074                         o = {};
12075                         t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
12076                         if (o.terminate)
12077                                 return false;
12078
12079                         // Command callback
12080                         if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
12081                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
12082                                 return true;
12083                         }
12084
12085                         // Registred commands
12086                         if (o = t.execCommands[cmd]) {
12087                                 st = o.func.call(o.scope, ui, val);
12088
12089                                 // Fall through on true
12090                                 if (st !== true) {
12091                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
12092                                         return st;
12093                                 }
12094                         }
12095
12096                         // Plugin commands
12097                         each(t.plugins, function(p) {
12098                                 if (p.execCommand && p.execCommand(cmd, ui, val)) {
12099                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
12100                                         s = 1;
12101                                         return false;
12102                                 }
12103                         });
12104
12105                         if (s)
12106                                 return true;
12107
12108                         // Theme commands
12109                         if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
12110                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
12111                                 return true;
12112                         }
12113
12114                         // Editor commands
12115                         if (t.editorCommands.execCommand(cmd, ui, val)) {
12116                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);
12117                                 return true;
12118                         }
12119
12120                         // Browser commands
12121                         t.getDoc().execCommand(cmd, ui, val);
12122                         t.onExecCommand.dispatch(t, cmd, ui, val, a);
12123                 },
12124
12125                 queryCommandState : function(cmd) {
12126                         var t = this, o, s;
12127
12128                         // Is hidden then return undefined
12129                         if (t._isHidden())
12130                                 return;
12131
12132                         // Registred commands
12133                         if (o = t.queryStateCommands[cmd]) {
12134                                 s = o.func.call(o.scope);
12135
12136                                 // Fall though on true
12137                                 if (s !== true)
12138                                         return s;
12139                         }
12140
12141                         // Registred commands
12142                         o = t.editorCommands.queryCommandState(cmd);
12143                         if (o !== -1)
12144                                 return o;
12145
12146                         // Browser commands
12147                         try {
12148                                 return this.getDoc().queryCommandState(cmd);
12149                         } catch (ex) {
12150                                 // Fails sometimes see bug: 1896577
12151                         }
12152                 },
12153
12154                 queryCommandValue : function(c) {
12155                         var t = this, o, s;
12156
12157                         // Is hidden then return undefined
12158                         if (t._isHidden())
12159                                 return;
12160
12161                         // Registred commands
12162                         if (o = t.queryValueCommands[c]) {
12163                                 s = o.func.call(o.scope);
12164
12165                                 // Fall though on true
12166                                 if (s !== true)
12167                                         return s;
12168                         }
12169
12170                         // Registred commands
12171                         o = t.editorCommands.queryCommandValue(c);
12172                         if (is(o))
12173                                 return o;
12174
12175                         // Browser commands
12176                         try {
12177                                 return this.getDoc().queryCommandValue(c);
12178                         } catch (ex) {
12179                                 // Fails sometimes see bug: 1896577
12180                         }
12181                 },
12182
12183                 show : function() {
12184                         var t = this;
12185
12186                         DOM.show(t.getContainer());
12187                         DOM.hide(t.id);
12188                         t.load();
12189                 },
12190
12191                 hide : function() {
12192                         var t = this, d = t.getDoc();
12193
12194                         // Fixed bug where IE has a blinking cursor left from the editor
12195                         if (isIE && d)
12196                                 d.execCommand('SelectAll');
12197
12198                         // We must save before we hide so Safari doesn't crash
12199                         t.save();
12200                         DOM.hide(t.getContainer());
12201                         DOM.setStyle(t.id, 'display', t.orgDisplay);
12202                 },
12203
12204                 isHidden : function() {
12205                         return !DOM.isHidden(this.id);
12206                 },
12207
12208                 setProgressState : function(b, ti, o) {
12209                         this.onSetProgressState.dispatch(this, b, ti, o);
12210
12211                         return b;
12212                 },
12213
12214                 load : function(o) {
12215                         var t = this, e = t.getElement(), h;
12216
12217                         if (e) {
12218                                 o = o || {};
12219                                 o.load = true;
12220
12221                                 // Double encode existing entities in the value
12222                                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
12223                                 o.element = e;
12224
12225                                 if (!o.no_events)
12226                                         t.onLoadContent.dispatch(t, o);
12227
12228                                 o.element = e = null;
12229
12230                                 return h;
12231                         }
12232                 },
12233
12234                 save : function(o) {
12235                         var t = this, e = t.getElement(), h, f;
12236
12237                         if (!e || !t.initialized)
12238                                 return;
12239
12240                         o = o || {};
12241                         o.save = true;
12242
12243                         // Add undo level will trigger onchange event
12244                         if (!o.no_events) {
12245                                 t.undoManager.typing = false;
12246                                 t.undoManager.add();
12247                         }
12248
12249                         o.element = e;
12250                         h = o.content = t.getContent(o);
12251
12252                         if (!o.no_events)
12253                                 t.onSaveContent.dispatch(t, o);
12254
12255                         h = o.content;
12256
12257                         if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
12258                                 e.innerHTML = h;
12259
12260                                 // Update hidden form element
12261                                 if (f = DOM.getParent(t.id, 'form')) {
12262                                         each(f.elements, function(e) {
12263                                                 if (e.name == t.id) {
12264                                                         e.value = h;
12265                                                         return false;
12266                                                 }
12267                                         });
12268                                 }
12269                         } else
12270                                 e.value = h;
12271
12272                         o.element = e = null;
12273
12274                         return h;
12275                 },
12276
12277                 setContent : function(content, args) {
12278                         var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
12279
12280                         // Setup args object
12281                         args = args || {};
12282                         args.format = args.format || 'html';
12283                         args.set = true;
12284                         args.content = content;
12285
12286                         // Do preprocessing
12287                         if (!args.no_events)
12288                                 self.onBeforeSetContent.dispatch(self, args);
12289
12290                         content = args.content;
12291
12292                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
12293                         // It will also be impossible to place the caret in the editor unless there is a BR element present
12294                         if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
12295                                 forcedRootBlockName = self.settings.forced_root_block;
12296                                 if (forcedRootBlockName)
12297                                         content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
12298                                 else
12299                                         content = '<br data-mce-bogus="1">';
12300
12301                                 body.innerHTML = content;
12302                                 self.selection.select(body, true);
12303                                 self.selection.collapse(true);
12304                                 return;
12305                         }
12306
12307                         // Parse and serialize the html
12308                         if (args.format !== 'raw') {
12309                                 content = new tinymce.html.Serializer({}, self.schema).serialize(
12310                                         self.parser.parse(content)
12311                                 );
12312                         }
12313
12314                         // Set the new cleaned contents to the editor
12315                         args.content = tinymce.trim(content);
12316                         self.dom.setHTML(body, args.content);
12317
12318                         // Do post processing
12319                         if (!args.no_events)
12320                                 self.onSetContent.dispatch(self, args);
12321
12322                         self.selection.normalize();
12323
12324                         return args.content;
12325                 },
12326
12327                 getContent : function(args) {
12328                         var self = this, content;
12329
12330                         // Setup args object
12331                         args = args || {};
12332                         args.format = args.format || 'html';
12333                         args.get = true;
12334
12335                         // Do preprocessing
12336                         if (!args.no_events)
12337                                 self.onBeforeGetContent.dispatch(self, args);
12338
12339                         // Get raw contents or by default the cleaned contents
12340                         if (args.format == 'raw')
12341                                 content = self.getBody().innerHTML;
12342                         else
12343                                 content = self.serializer.serialize(self.getBody(), args);
12344
12345                         args.content = tinymce.trim(content);
12346
12347                         // Do post processing
12348                         if (!args.no_events)
12349                                 self.onGetContent.dispatch(self, args);
12350
12351                         return args.content;
12352                 },
12353
12354                 isDirty : function() {
12355                         var self = this;
12356
12357                         return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
12358                 },
12359
12360                 getContainer : function() {
12361                         var t = this;
12362
12363                         if (!t.container)
12364                                 t.container = DOM.get(t.editorContainer || t.id + '_parent');
12365
12366                         return t.container;
12367                 },
12368
12369                 getContentAreaContainer : function() {
12370                         return this.contentAreaContainer;
12371                 },
12372
12373                 getElement : function() {
12374                         return DOM.get(this.settings.content_element || this.id);
12375                 },
12376
12377                 getWin : function() {
12378                         var t = this, e;
12379
12380                         if (!t.contentWindow) {
12381                                 e = DOM.get(t.id + "_ifr");
12382
12383                                 if (e)
12384                                         t.contentWindow = e.contentWindow;
12385                         }
12386
12387                         return t.contentWindow;
12388                 },
12389
12390                 getDoc : function() {
12391                         var t = this, w;
12392
12393                         if (!t.contentDocument) {
12394                                 w = t.getWin();
12395
12396                                 if (w)
12397                                         t.contentDocument = w.document;
12398                         }
12399
12400                         return t.contentDocument;
12401                 },
12402
12403                 getBody : function() {
12404                         return this.bodyElement || this.getDoc().body;
12405                 },
12406
12407                 convertURL : function(u, n, e) {
12408                         var t = this, s = t.settings;
12409
12410                         // Use callback instead
12411                         if (s.urlconverter_callback)
12412                                 return t.execCallback('urlconverter_callback', u, e, true, n);
12413
12414                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
12415                         if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
12416                                 return u;
12417
12418                         // Convert to relative
12419                         if (s.relative_urls)
12420                                 return t.documentBaseURI.toRelative(u);
12421
12422                         // Convert to absolute
12423                         u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
12424
12425                         return u;
12426                 },
12427
12428                 addVisual : function(e) {
12429                         var t = this, s = t.settings;
12430
12431                         e = e || t.getBody();
12432
12433                         if (!is(t.hasVisual))
12434                                 t.hasVisual = s.visual;
12435
12436                         each(t.dom.select('table,a', e), function(e) {
12437                                 var v;
12438
12439                                 switch (e.nodeName) {
12440                                         case 'TABLE':
12441                                                 v = t.dom.getAttrib(e, 'border');
12442
12443                                                 if (!v || v == '0') {
12444                                                         if (t.hasVisual)
12445                                                                 t.dom.addClass(e, s.visual_table_class);
12446                                                         else
12447                                                                 t.dom.removeClass(e, s.visual_table_class);
12448                                                 }
12449
12450                                                 return;
12451
12452                                         case 'A':
12453                                                 v = t.dom.getAttrib(e, 'name');
12454
12455                                                 if (v) {
12456                                                         if (t.hasVisual)
12457                                                                 t.dom.addClass(e, 'mceItemAnchor');
12458                                                         else
12459                                                                 t.dom.removeClass(e, 'mceItemAnchor');
12460                                                 }
12461
12462                                                 return;
12463                                 }
12464                         });
12465
12466                         t.onVisualAid.dispatch(t, e, t.hasVisual);
12467                 },
12468
12469                 remove : function() {
12470                         var t = this, e = t.getContainer();
12471
12472                         t.removed = 1; // Cancels post remove event execution
12473                         t.hide();
12474
12475                         t.execCallback('remove_instance_callback', t);
12476                         t.onRemove.dispatch(t);
12477
12478                         // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
12479                         t.onExecCommand.listeners = [];
12480
12481                         tinymce.remove(t);
12482                         DOM.remove(e);
12483                 },
12484
12485                 destroy : function(s) {
12486                         var t = this;
12487
12488                         // One time is enough
12489                         if (t.destroyed)
12490                                 return;
12491
12492                         if (!s) {
12493                                 tinymce.removeUnload(t.destroy);
12494                                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);
12495
12496                                 // Manual destroy
12497                                 if (t.theme && t.theme.destroy)
12498                                         t.theme.destroy();
12499
12500                                 // Destroy controls, selection and dom
12501                                 t.controlManager.destroy();
12502                                 t.selection.destroy();
12503                                 t.dom.destroy();
12504
12505                                 // Remove all events
12506
12507                                 // Don't clear the window or document if content editable
12508                                 // is enabled since other instances might still be present
12509                                 if (!t.settings.content_editable) {
12510                                         Event.clear(t.getWin());
12511                                         Event.clear(t.getDoc());
12512                                 }
12513
12514                                 Event.clear(t.getBody());
12515                                 Event.clear(t.formElement);
12516                         }
12517
12518                         if (t.formElement) {
12519                                 t.formElement.submit = t.formElement._mceOldSubmit;
12520                                 t.formElement._mceOldSubmit = null;
12521                         }
12522
12523                         t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
12524
12525                         if (t.selection)
12526                                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
12527
12528                         t.destroyed = 1;
12529                 },
12530
12531                 // Internal functions
12532
12533                 _addEvents : function() {
12534                         // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
12535                         var t = this, i, s = t.settings, dom = t.dom, lo = {
12536                                 mouseup : 'onMouseUp',
12537                                 mousedown : 'onMouseDown',
12538                                 click : 'onClick',
12539                                 keyup : 'onKeyUp',
12540                                 keydown : 'onKeyDown',
12541                                 keypress : 'onKeyPress',
12542                                 submit : 'onSubmit',
12543                                 reset : 'onReset',
12544                                 contextmenu : 'onContextMenu',
12545                                 dblclick : 'onDblClick',
12546                                 paste : 'onPaste' // Doesn't work in all browsers yet
12547                         };
12548
12549                         function eventHandler(e, o) {
12550                                 var ty = e.type;
12551
12552                                 // Don't fire events when it's removed
12553                                 if (t.removed)
12554                                         return;
12555
12556                                 // Generic event handler
12557                                 if (t.onEvent.dispatch(t, e, o) !== false) {
12558                                         // Specific event handler
12559                                         t[lo[e.fakeType || e.type]].dispatch(t, e, o);
12560                                 }
12561                         };
12562
12563                         // Add DOM events
12564                         each(lo, function(v, k) {
12565                                 switch (k) {
12566                                         case 'contextmenu':
12567                                                 dom.bind(t.getDoc(), k, eventHandler);
12568                                                 break;
12569
12570                                         case 'paste':
12571                                                 dom.bind(t.getBody(), k, function(e) {
12572                                                         eventHandler(e);
12573                                                 });
12574                                                 break;
12575
12576                                         case 'submit':
12577                                         case 'reset':
12578                                                 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
12579                                                 break;
12580
12581                                         default:
12582                                                 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
12583                                 }
12584                         });
12585
12586                         dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
12587                                 t.focus(true);
12588                         });
12589
12590
12591                         // Fixes bug where a specified document_base_uri could result in broken images
12592                         // This will also fix drag drop of images in Gecko
12593                         if (tinymce.isGecko) {
12594                                 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
12595                                         var v;
12596
12597                                         e = e.target;
12598
12599                                         if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
12600                                                 e.src = t.documentBaseURI.toAbsolute(v);
12601                                 });
12602                         }
12603
12604                         // Set various midas options in Gecko
12605                         if (isGecko) {
12606                                 function setOpts() {
12607                                         var t = this, d = t.getDoc(), s = t.settings;
12608
12609                                         if (isGecko && !s.readonly) {
12610                                                 if (t._isHidden()) {
12611                                                         try {
12612                                                                 if (!s.content_editable) {
12613                                                                         d.body.contentEditable = false;
12614                                                                         d.body.contentEditable = true;
12615                                                                 }
12616                                                         } catch (ex) {
12617                                                                 // Fails if it's hidden
12618                                                         }
12619                                                 }
12620
12621                                                 try {
12622                                                         // Try new Gecko method
12623                                                         d.execCommand("styleWithCSS", 0, false);
12624                                                 } catch (ex) {
12625                                                         // Use old method
12626                                                         if (!t._isHidden())
12627                                                                 try {d.execCommand("useCSS", 0, true);} catch (ex) {}
12628                                                 }
12629
12630                                                 if (!s.table_inline_editing)
12631                                                         try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
12632
12633                                                 if (!s.object_resizing)
12634                                                         try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
12635                                         }
12636                                 };
12637
12638                                 t.onBeforeExecCommand.add(setOpts);
12639                                 t.onMouseDown.add(setOpts);
12640                         }
12641
12642                         t.onClick.add(function(ed, e) {
12643                                 e = e.target;
12644
12645                                 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
12646                                 // WebKit can't even do simple things like selecting an image
12647                                 // Needs tobe the setBaseAndExtend or it will fail to select floated images
12648                                 if (tinymce.isWebKit && e.nodeName == 'IMG')
12649                                         t.selection.getSel().setBaseAndExtent(e, 0, e, 1);
12650
12651                                 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))
12652                                         t.selection.select(e);
12653
12654                                 t.nodeChanged();
12655                         });
12656
12657                         // Add node change handlers
12658                         t.onMouseUp.add(t.nodeChanged);
12659                         //t.onClick.add(t.nodeChanged);
12660                         t.onKeyUp.add(function(ed, e) {
12661                                 var c = e.keyCode;
12662
12663                                 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)
12664                                         t.nodeChanged();
12665                         });
12666
12667
12668                         // Add block quote deletion handler
12669                         t.onKeyDown.add(function(ed, e) {
12670                                 // Was the BACKSPACE key pressed?
12671                                 if (e.keyCode != 8)
12672                                         return;
12673
12674                                 var n = ed.selection.getRng().startContainer;
12675                                 var offset = ed.selection.getRng().startOffset;
12676
12677                                 while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
12678                                         n = n.parentNode;
12679                                         
12680                                 // Is the cursor at the beginning of a blockquote?
12681                                 if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
12682                                         // Remove the blockquote
12683                                         ed.formatter.toggle('blockquote', null, n.parentNode);
12684
12685                                         // Move the caret to the beginning of n
12686                                         var rng = ed.selection.getRng();
12687                                         rng.setStart(n, 0);
12688                                         rng.setEnd(n, 0);
12689                                         ed.selection.setRng(rng);
12690                                         ed.selection.collapse(false);
12691                                 }
12692                         });
12693  
12694
12695
12696                         // Add reset handler
12697                         t.onReset.add(function() {
12698                                 t.setContent(t.startContent, {format : 'raw'});
12699                         });
12700
12701                         // Add shortcuts
12702                         if (s.custom_shortcuts) {
12703                                 if (s.custom_undo_redo_keyboard_shortcuts) {
12704                                         t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
12705                                         t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
12706                                 }
12707
12708                                 // Add default shortcuts for gecko
12709                                 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
12710                                 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
12711                                 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
12712
12713                                 // BlockFormat shortcuts keys
12714                                 for (i=1; i<=6; i++)
12715                                         t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
12716
12717                                 t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
12718                                 t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
12719                                 t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
12720
12721                                 function find(e) {
12722                                         var v = null;
12723
12724                                         if (!e.altKey && !e.ctrlKey && !e.metaKey)
12725                                                 return v;
12726
12727                                         each(t.shortcuts, function(o) {
12728                                                 if (tinymce.isMac && o.ctrl != e.metaKey)
12729                                                         return;
12730                                                 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
12731                                                         return;
12732
12733                                                 if (o.alt != e.altKey)
12734                                                         return;
12735
12736                                                 if (o.shift != e.shiftKey)
12737                                                         return;
12738
12739                                                 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
12740                                                         v = o;
12741                                                         return false;
12742                                                 }
12743                                         });
12744
12745                                         return v;
12746                                 };
12747
12748                                 t.onKeyUp.add(function(ed, e) {
12749                                         var o = find(e);
12750
12751                                         if (o)
12752                                                 return Event.cancel(e);
12753                                 });
12754
12755                                 t.onKeyPress.add(function(ed, e) {
12756                                         var o = find(e);
12757
12758                                         if (o)
12759                                                 return Event.cancel(e);
12760                                 });
12761
12762                                 t.onKeyDown.add(function(ed, e) {
12763                                         var o = find(e);
12764
12765                                         if (o) {
12766                                                 o.func.call(o.scope);
12767                                                 return Event.cancel(e);
12768                                         }
12769                                 });
12770                         }
12771
12772                         if (tinymce.isIE) {
12773                                 // Fix so resize will only update the width and height attributes not the styles of an image
12774                                 // It will also block mceItemNoResize items
12775                                 dom.bind(t.getDoc(), 'controlselect', function(e) {
12776                                         var re = t.resizeInfo, cb;
12777
12778                                         e = e.target;
12779
12780                                         // Don't do this action for non image elements
12781                                         if (e.nodeName !== 'IMG')
12782                                                 return;
12783
12784                                         if (re)
12785                                                 dom.unbind(re.node, re.ev, re.cb);
12786
12787                                         if (!dom.hasClass(e, 'mceItemNoResize')) {
12788                                                 ev = 'resizeend';
12789                                                 cb = dom.bind(e, ev, function(e) {
12790                                                         var v;
12791
12792                                                         e = e.target;
12793
12794                                                         if (v = dom.getStyle(e, 'width')) {
12795                                                                 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
12796                                                                 dom.setStyle(e, 'width', '');
12797                                                         }
12798
12799                                                         if (v = dom.getStyle(e, 'height')) {
12800                                                                 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
12801                                                                 dom.setStyle(e, 'height', '');
12802                                                         }
12803                                                 });
12804                                         } else {
12805                                                 ev = 'resizestart';
12806                                                 cb = dom.bind(e, 'resizestart', Event.cancel, Event);
12807                                         }
12808
12809                                         re = t.resizeInfo = {
12810                                                 node : e,
12811                                                 ev : ev,
12812                                                 cb : cb
12813                                         };
12814                                 });
12815                         }
12816
12817                         if (tinymce.isOpera) {
12818                                 t.onClick.add(function(ed, e) {
12819                                         Event.prevent(e);
12820                                 });
12821                         }
12822
12823                         // Add custom undo/redo handlers
12824                         if (s.custom_undo_redo) {
12825                                 function addUndo() {
12826                                         t.undoManager.typing = false;
12827                                         t.undoManager.add();
12828                                 };
12829
12830                                 dom.bind(t.getDoc(), 'focusout', function(e) {
12831                                         if (!t.removed && t.undoManager.typing)
12832                                                 addUndo();
12833                                 });
12834
12835                                 // Add undo level when contents is drag/dropped within the editor
12836                                 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
12837                                         addUndo();
12838                                 });
12839
12840                                 t.onKeyUp.add(function(ed, e) {
12841                                         var keyCode = e.keyCode;
12842
12843                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)
12844                                                 addUndo();
12845                                 });
12846
12847                                 t.onKeyDown.add(function(ed, e) {
12848                                         var keyCode = e.keyCode, sel;
12849
12850                                         if (keyCode == 8) {
12851                                                 sel = t.getDoc().selection;
12852
12853                                                 // Fix IE control + backspace browser bug
12854                                                 if (sel && sel.createRange && sel.createRange().item) {
12855                                                         t.undoManager.beforeChange();
12856                                                         ed.dom.remove(sel.createRange().item(0));
12857                                                         addUndo();
12858
12859                                                         return Event.cancel(e);
12860                                                 }
12861                                         }
12862
12863                                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
12864                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
12865                                                 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior
12866                                                 // Todo: Remove this once we normalize enter behavior on IE
12867                                                 if (tinymce.isIE && keyCode == 13)
12868                                                         t.undoManager.beforeChange();
12869
12870                                                 if (t.undoManager.typing)
12871                                                         addUndo();
12872
12873                                                 return;
12874                                         }
12875
12876                                         // If key isn't shift,ctrl,alt,capslock,metakey
12877                                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
12878                                                 t.undoManager.beforeChange();
12879                                                 t.undoManager.typing = true;
12880                                                 t.undoManager.add();
12881                                         }
12882                                 });
12883
12884                                 t.onMouseDown.add(function() {
12885                                         if (t.undoManager.typing)
12886                                                 addUndo();
12887                                 });
12888                         }
12889
12890                         // Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5
12891                         // It only fires the nodeChange event every 50ms since it would other wise update the UI when you type and it hogs the CPU
12892                         if (tinymce.isWebKit) {
12893                                 dom.bind(t.getDoc(), 'selectionchange', function() {
12894                                         if (t.selectionTimer) {
12895                                                 window.clearTimeout(t.selectionTimer);
12896                                                 t.selectionTimer = 0;
12897                                         }
12898
12899                                         t.selectionTimer = window.setTimeout(function() {
12900                                                 t.nodeChanged();
12901                                         }, 50);
12902                                 });
12903                         }
12904
12905                         // Bug fix for FireFox keeping styles from end of selection instead of start.
12906                         if (tinymce.isGecko) {
12907                                 function getAttributeApplyFunction() {
12908                                         var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
12909
12910                                         return function() {
12911                                                 var target = t.selection.getStart();
12912
12913                                                 if (target !== t.getBody()) {
12914                                                         t.dom.removeAllAttribs(target);
12915
12916                                                         each(template, function(attr) {
12917                                                                 target.setAttributeNode(attr.cloneNode(true));
12918                                                         });
12919                                                 }
12920                                         };
12921                                 }
12922
12923                                 function isSelectionAcrossElements() {
12924                                         var s = t.selection;
12925
12926                                         return !s.isCollapsed() && s.getStart() != s.getEnd();
12927                                 }
12928
12929                                 t.onKeyPress.add(function(ed, e) {
12930                                         var applyAttributes;
12931
12932                                         if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
12933                                                 applyAttributes = getAttributeApplyFunction();
12934                                                 t.getDoc().execCommand('delete', false, null);
12935                                                 applyAttributes();
12936
12937                                                 return Event.cancel(e);
12938                                         }
12939                                 });
12940
12941                                 t.dom.bind(t.getDoc(), 'cut', function(e) {
12942                                         var applyAttributes;
12943
12944                                         if (isSelectionAcrossElements()) {
12945                                                 applyAttributes = getAttributeApplyFunction();
12946                                                 t.onKeyUp.addToTop(Event.cancel, Event);
12947
12948                                                 setTimeout(function() {
12949                                                         applyAttributes();
12950                                                         t.onKeyUp.remove(Event.cancel, Event);
12951                                                 }, 0);
12952                                         }
12953                                 });
12954                         }
12955                 },
12956
12957                 _isHidden : function() {
12958                         var s;
12959
12960                         if (!isGecko)
12961                                 return 0;
12962
12963                         // Weird, wheres that cursor selection?
12964                         s = this.selection.getSel();
12965                         return (!s || !s.rangeCount || s.rangeCount == 0);
12966                 }
12967         });
12968 })(tinymce);
12969
12970 (function(tinymce) {
12971         // Added for compression purposes
12972         var each = tinymce.each, undefined, TRUE = true, FALSE = false;
12973
12974         tinymce.EditorCommands = function(editor) {
12975                 var dom = editor.dom,
12976                         selection = editor.selection,
12977                         commands = {state: {}, exec : {}, value : {}},
12978                         settings = editor.settings,
12979                         bookmark;
12980
12981                 function execCommand(command, ui, value) {
12982                         var func;
12983
12984                         command = command.toLowerCase();
12985                         if (func = commands.exec[command]) {
12986                                 func(command, ui, value);
12987                                 return TRUE;
12988                         }
12989
12990                         return FALSE;
12991                 };
12992
12993                 function queryCommandState(command) {
12994                         var func;
12995
12996                         command = command.toLowerCase();
12997                         if (func = commands.state[command])
12998                                 return func(command);
12999
13000                         return -1;
13001                 };
13002
13003                 function queryCommandValue(command) {
13004                         var func;
13005
13006                         command = command.toLowerCase();
13007                         if (func = commands.value[command])
13008                                 return func(command);
13009
13010                         return FALSE;
13011                 };
13012
13013                 function addCommands(command_list, type) {
13014                         type = type || 'exec';
13015
13016                         each(command_list, function(callback, command) {
13017                                 each(command.toLowerCase().split(','), function(command) {
13018                                         commands[type][command] = callback;
13019                                 });
13020                         });
13021                 };
13022
13023                 // Expose public methods
13024                 tinymce.extend(this, {
13025                         execCommand : execCommand,
13026                         queryCommandState : queryCommandState,
13027                         queryCommandValue : queryCommandValue,
13028                         addCommands : addCommands
13029                 });
13030
13031                 // Private methods
13032
13033                 function execNativeCommand(command, ui, value) {
13034                         if (ui === undefined)
13035                                 ui = FALSE;
13036
13037                         if (value === undefined)
13038                                 value = null;
13039
13040                         return editor.getDoc().execCommand(command, ui, value);
13041                 };
13042
13043                 function isFormatMatch(name) {
13044                         return editor.formatter.match(name);
13045                 };
13046
13047                 function toggleFormat(name, value) {
13048                         editor.formatter.toggle(name, value ? {value : value} : undefined);
13049                 };
13050
13051                 function storeSelection(type) {
13052                         bookmark = selection.getBookmark(type);
13053                 };
13054
13055                 function restoreSelection() {
13056                         selection.moveToBookmark(bookmark);
13057                 };
13058
13059                 // Add execCommand overrides
13060                 addCommands({
13061                         // Ignore these, added for compatibility
13062                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},
13063
13064                         // Add undo manager logic
13065                         'mceEndUndoLevel,mceAddUndoLevel' : function() {
13066                                 editor.undoManager.add();
13067                         },
13068
13069                         'Cut,Copy,Paste' : function(command) {
13070                                 var doc = editor.getDoc(), failed;
13071
13072                                 // Try executing the native command
13073                                 try {
13074                                         execNativeCommand(command);
13075                                 } catch (ex) {
13076                                         // Command failed
13077                                         failed = TRUE;
13078                                 }
13079
13080                                 // Present alert message about clipboard access not being available
13081                                 if (failed || !doc.queryCommandSupported(command)) {
13082                                         if (tinymce.isGecko) {
13083                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
13084                                                         if (state)
13085                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
13086                                                 });
13087                                         } else
13088                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));
13089                                 }
13090                         },
13091
13092                         // Override unlink command
13093                         unlink : function(command) {
13094                                 if (selection.isCollapsed())
13095                                         selection.select(selection.getNode());
13096
13097                                 execNativeCommand(command);
13098                                 selection.collapse(FALSE);
13099                         },
13100
13101                         // Override justify commands to use the text formatter engine
13102                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13103                                 var align = command.substring(7);
13104
13105                                 // Remove all other alignments first
13106                                 each('left,center,right,full'.split(','), function(name) {
13107                                         if (align != name)
13108                                                 editor.formatter.remove('align' + name);
13109                                 });
13110
13111                                 toggleFormat('align' + align);
13112                                 execCommand('mceRepaint');
13113                         },
13114
13115                         // Override list commands to fix WebKit bug
13116                         'InsertUnorderedList,InsertOrderedList' : function(command) {
13117                                 var listElm, listParent;
13118
13119                                 execNativeCommand(command);
13120
13121                                 // WebKit produces lists within block elements so we need to split them
13122                                 // we will replace the native list creation logic to custom logic later on
13123                                 // TODO: Remove this when the list creation logic is removed
13124                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');
13125                                 if (listElm) {
13126                                         listParent = listElm.parentNode;
13127
13128                                         // If list is within a text block then split that block
13129                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
13130                                                 storeSelection();
13131                                                 dom.split(listParent, listElm);
13132                                                 restoreSelection();
13133                                         }
13134                                 }
13135                         },
13136
13137                         // Override commands to use the text formatter engine
13138                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13139                                 toggleFormat(command);
13140                         },
13141
13142                         // Override commands to use the text formatter engine
13143                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
13144                                 toggleFormat(command, value);
13145                         },
13146
13147                         FontSize : function(command, ui, value) {
13148                                 var fontClasses, fontSizes;
13149
13150                                 // Convert font size 1-7 to styles
13151                                 if (value >= 1 && value <= 7) {
13152                                         fontSizes = tinymce.explode(settings.font_size_style_values);
13153                                         fontClasses = tinymce.explode(settings.font_size_classes);
13154
13155                                         if (fontClasses)
13156                                                 value = fontClasses[value - 1] || value;
13157                                         else
13158                                                 value = fontSizes[value - 1] || value;
13159                                 }
13160
13161                                 toggleFormat(command, value);
13162                         },
13163
13164                         RemoveFormat : function(command) {
13165                                 editor.formatter.remove(command);
13166                         },
13167
13168                         mceBlockQuote : function(command) {
13169                                 toggleFormat('blockquote');
13170                         },
13171
13172                         FormatBlock : function(command, ui, value) {
13173                                 return toggleFormat(value || 'p');
13174                         },
13175
13176                         mceCleanup : function() {
13177                                 var bookmark = selection.getBookmark();
13178
13179                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
13180
13181                                 selection.moveToBookmark(bookmark);
13182                         },
13183
13184                         mceRemoveNode : function(command, ui, value) {
13185                                 var node = value || selection.getNode();
13186
13187                                 // Make sure that the body node isn't removed
13188                                 if (node != editor.getBody()) {
13189                                         storeSelection();
13190                                         editor.dom.remove(node, TRUE);
13191                                         restoreSelection();
13192                                 }
13193                         },
13194
13195                         mceSelectNodeDepth : function(command, ui, value) {
13196                                 var counter = 0;
13197
13198                                 dom.getParent(selection.getNode(), function(node) {
13199                                         if (node.nodeType == 1 && counter++ == value) {
13200                                                 selection.select(node);
13201                                                 return FALSE;
13202                                         }
13203                                 }, editor.getBody());
13204                         },
13205
13206                         mceSelectNode : function(command, ui, value) {
13207                                 selection.select(value);
13208                         },
13209
13210                         mceInsertContent : function(command, ui, value) {
13211                                 var parser, serializer, parentNode, rootNode, fragment, args,
13212                                         marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
13213
13214                                 // Setup parser and serializer
13215                                 parser = editor.parser;
13216                                 serializer = new tinymce.html.Serializer({}, editor.schema);
13217                                 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
13218
13219                                 // Run beforeSetContent handlers on the HTML to be inserted
13220                                 args = {content: value, format: 'html'};
13221                                 selection.onBeforeSetContent.dispatch(selection, args);
13222                                 value = args.content;
13223
13224                                 // Add caret at end of contents if it's missing
13225                                 if (value.indexOf('{$caret}') == -1)
13226                                         value += '{$caret}';
13227
13228                                 // Replace the caret marker with a span bookmark element
13229                                 value = value.replace(/\{\$caret\}/, bookmarkHtml);
13230
13231                                 // Insert node maker where we will insert the new HTML and get it's parent
13232                                 if (!selection.isCollapsed())
13233                                         editor.getDoc().execCommand('Delete', false, null);
13234
13235                                 parentNode = selection.getNode();
13236
13237                                 // Parse the fragment within the context of the parent node
13238                                 args = {context : parentNode.nodeName.toLowerCase()};
13239                                 fragment = parser.parse(value, args);
13240
13241                                 // Move the caret to a more suitable location
13242                                 node = fragment.lastChild;
13243                                 if (node.attr('id') == 'mce_marker') {
13244                                         marker = node;
13245
13246                                         for (node = node.prev; node; node = node.walk(true)) {
13247                                                 if (node.type == 3 || !dom.isBlock(node.name)) {
13248                                                         node.parent.insert(marker, node, node.name === 'br');
13249                                                         break;
13250                                                 }
13251                                         }
13252                                 }
13253
13254                                 // If parser says valid we can insert the contents into that parent
13255                                 if (!args.invalid) {
13256                                         value = serializer.serialize(fragment);
13257
13258                                         // Check if parent is empty or only has one BR element then set the innerHTML of that parent
13259                                         node = parentNode.firstChild;
13260                                         node2 = parentNode.lastChild;
13261                                         if (!node || (node === node2 && node.nodeName === 'BR'))
13262                                                 dom.setHTML(parentNode, value);
13263                                         else
13264                                                 selection.setContent(value);
13265                                 } else {
13266                                         // If the fragment was invalid within that context then we need
13267                                         // to parse and process the parent it's inserted into
13268
13269                                         // Insert bookmark node and get the parent
13270                                         selection.setContent(bookmarkHtml);
13271                                         parentNode = editor.selection.getNode();
13272                                         rootNode = editor.getBody();
13273
13274                                         // Opera will return the document node when selection is in root
13275                                         if (parentNode.nodeType == 9)
13276                                                 parentNode = node = rootNode;
13277                                         else
13278                                                 node = parentNode;
13279
13280                                         // Find the ancestor just before the root element
13281                                         while (node !== rootNode) {
13282                                                 parentNode = node;
13283                                                 node = node.parentNode;
13284                                         }
13285
13286                                         // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
13287                                         value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
13288                                         value = serializer.serialize(
13289                                                 parser.parse(
13290                                                         // Need to replace by using a function since $ in the contents would otherwise be a problem
13291                                                         value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
13292                                                                 return serializer.serialize(fragment);
13293                                                         })
13294                                                 )
13295                                         );
13296
13297                                         // Set the inner/outer HTML depending on if we are in the root or not
13298                                         if (parentNode == rootNode)
13299                                                 dom.setHTML(rootNode, value);
13300                                         else
13301                                                 dom.setOuterHTML(parentNode, value);
13302                                 }
13303
13304                                 marker = dom.get('mce_marker');
13305
13306                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
13307                                 nodeRect = dom.getRect(marker);
13308                                 viewPortRect = dom.getViewPort(editor.getWin());
13309
13310                                 // Check if node is out side the viewport if it is then scroll to it
13311                                 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
13312                                         (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
13313                                         viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
13314                                         viewportBodyElement.scrollLeft = nodeRect.x;
13315                                         viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
13316                                 }
13317
13318                                 // Move selection before marker and remove it
13319                                 rng = dom.createRng();
13320
13321                                 // If previous sibling is a text node set the selection to the end of that node
13322                                 node = marker.previousSibling;
13323                                 if (node && node.nodeType == 3) {
13324                                         rng.setStart(node, node.nodeValue.length);
13325                                 } else {
13326                                         // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
13327                                         rng.setStartBefore(marker);
13328                                         rng.setEndBefore(marker);
13329                                 }
13330
13331                                 // Remove the marker node and set the new range
13332                                 dom.remove(marker);
13333                                 selection.setRng(rng);
13334
13335                                 // Dispatch after event and add any visual elements needed
13336                                 selection.onSetContent.dispatch(selection, args);
13337                                 editor.addVisual();
13338                         },
13339
13340                         mceInsertRawHTML : function(command, ui, value) {
13341                                 selection.setContent('tiny_mce_marker');
13342                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
13343                         },
13344
13345                         mceSetContent : function(command, ui, value) {
13346                                 editor.setContent(value);
13347                         },
13348
13349                         'Indent,Outdent' : function(command) {
13350                                 var intentValue, indentUnit, value;
13351
13352                                 // Setup indent level
13353                                 intentValue = settings.indentation;
13354                                 indentUnit = /[a-z%]+$/i.exec(intentValue);
13355                                 intentValue = parseInt(intentValue);
13356
13357                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
13358                                         each(selection.getSelectedBlocks(), function(element) {
13359                                                 if (command == 'outdent') {
13360                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
13361                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
13362                                                 } else
13363                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
13364                                         });
13365                                 } else
13366                                         execNativeCommand(command);
13367                         },
13368
13369                         mceRepaint : function() {
13370                                 var bookmark;
13371
13372                                 if (tinymce.isGecko) {
13373                                         try {
13374                                                 storeSelection(TRUE);
13375
13376                                                 if (selection.getSel())
13377                                                         selection.getSel().selectAllChildren(editor.getBody());
13378
13379                                                 selection.collapse(TRUE);
13380                                                 restoreSelection();
13381                                         } catch (ex) {
13382                                                 // Ignore
13383                                         }
13384                                 }
13385                         },
13386
13387                         mceToggleFormat : function(command, ui, value) {
13388                                 editor.formatter.toggle(value);
13389                         },
13390
13391                         InsertHorizontalRule : function() {
13392                                 editor.execCommand('mceInsertContent', false, '<hr />');
13393                         },
13394
13395                         mceToggleVisualAid : function() {
13396                                 editor.hasVisual = !editor.hasVisual;
13397                                 editor.addVisual();
13398                         },
13399
13400                         mceReplaceContent : function(command, ui, value) {
13401                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
13402                         },
13403
13404                         mceInsertLink : function(command, ui, value) {
13405                                 var link = dom.getParent(selection.getNode(), 'a'), img, style, cls;
13406
13407                                 if (tinymce.is(value, 'string'))
13408                                         value = {href : value};
13409
13410                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
13411                                 value.href = value.href.replace(' ', '%20');
13412
13413                                 if (!link) {
13414                                         // WebKit can't create links on floated images for some odd reason
13415                                         // So, just remove styles and restore it later
13416                                         if (tinymce.isWebKit) {
13417                                                 img = dom.getParent(selection.getNode(), 'img');
13418
13419                                                 if (img) {
13420                                                         style = img.style.cssText;
13421                                                         cls = img.className;
13422                                                         img.style.cssText = null;
13423                                                         img.className = null;
13424                                                 }
13425                                         }
13426
13427                                         execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');
13428
13429                                         // Restore styles
13430                                         if (style)
13431                                                 img.style.cssText = style;
13432                                         if (cls)
13433                                                 img.className = cls;
13434
13435                                         each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {
13436                                                 dom.setAttribs(link, value);
13437                                         });
13438                                 } else {
13439                                         if (value.href)
13440                                                 dom.setAttribs(link, value);
13441                                         else
13442                                                 editor.dom.remove(link, TRUE);
13443                                 }
13444                         },
13445                         
13446                         selectAll : function() {
13447                                 var root = dom.getRoot(), rng = dom.createRng();
13448
13449                                 rng.setStart(root, 0);
13450                                 rng.setEnd(root, root.childNodes.length);
13451
13452                                 editor.selection.setRng(rng);
13453                         }
13454                 });
13455
13456                 // Add queryCommandState overrides
13457                 addCommands({
13458                         // Override justify commands
13459                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13460                                 return isFormatMatch('align' + command.substring(7));
13461                         },
13462
13463                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13464                                 return isFormatMatch(command);
13465                         },
13466
13467                         mceBlockQuote : function() {
13468                                 return isFormatMatch('blockquote');
13469                         },
13470
13471                         Outdent : function() {
13472                                 var node;
13473
13474                                 if (settings.inline_styles) {
13475                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
13476                                                 return TRUE;
13477
13478                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
13479                                                 return TRUE;
13480                                 }
13481
13482                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
13483                         },
13484
13485                         'InsertUnorderedList,InsertOrderedList' : function(command) {
13486                                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
13487                         }
13488                 }, 'state');
13489
13490                 // Add queryCommandValue overrides
13491                 addCommands({
13492                         'FontSize,FontName' : function(command) {
13493                                 var value = 0, parent;
13494
13495                                 if (parent = dom.getParent(selection.getNode(), 'span')) {
13496                                         if (command == 'fontsize')
13497                                                 value = parent.style.fontSize;
13498                                         else
13499                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
13500                                 }
13501
13502                                 return value;
13503                         }
13504                 }, 'value');
13505
13506                 // Add undo manager logic
13507                 if (settings.custom_undo_redo) {
13508                         addCommands({
13509                                 Undo : function() {
13510                                         editor.undoManager.undo();
13511                                 },
13512
13513                                 Redo : function() {
13514                                         editor.undoManager.redo();
13515                                 }
13516                         });
13517                 }
13518         };
13519 })(tinymce);
13520
13521 (function(tinymce) {
13522         var Dispatcher = tinymce.util.Dispatcher;
13523
13524         tinymce.UndoManager = function(editor) {
13525                 var self, index = 0, data = [], beforeBookmark;
13526
13527                 function getContent() {
13528                         return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
13529                 };
13530
13531                 return self = {
13532                         typing : false,
13533
13534                         onAdd : new Dispatcher(self),
13535
13536                         onUndo : new Dispatcher(self),
13537
13538                         onRedo : new Dispatcher(self),
13539
13540                         beforeChange : function() {
13541                                 beforeBookmark = editor.selection.getBookmark(2, true);
13542                         },
13543
13544                         add : function(level) {
13545                                 var i, settings = editor.settings, lastLevel;
13546
13547                                 level = level || {};
13548                                 level.content = getContent();
13549
13550                                 // Add undo level if needed
13551                                 lastLevel = data[index];
13552                                 if (lastLevel && lastLevel.content == level.content)
13553                                         return null;
13554
13555                                 // Set before bookmark on previous level
13556                                 if (data[index])
13557                                         data[index].beforeBookmark = beforeBookmark;
13558
13559                                 // Time to compress
13560                                 if (settings.custom_undo_redo_levels) {
13561                                         if (data.length > settings.custom_undo_redo_levels) {
13562                                                 for (i = 0; i < data.length - 1; i++)
13563                                                         data[i] = data[i + 1];
13564
13565                                                 data.length--;
13566                                                 index = data.length;
13567                                         }
13568                                 }
13569
13570                                 // Get a non intrusive normalized bookmark
13571                                 level.bookmark = editor.selection.getBookmark(2, true);
13572
13573                                 // Crop array if needed
13574                                 if (index < data.length - 1)
13575                                         data.length = index + 1;
13576
13577                                 data.push(level);
13578                                 index = data.length - 1;
13579
13580                                 self.onAdd.dispatch(self, level);
13581                                 editor.isNotDirty = 0;
13582
13583                                 return level;
13584                         },
13585
13586                         undo : function() {
13587                                 var level, i;
13588
13589                                 if (self.typing) {
13590                                         self.add();
13591                                         self.typing = false;
13592                                 }
13593
13594                                 if (index > 0) {
13595                                         level = data[--index];
13596
13597                                         editor.setContent(level.content, {format : 'raw'});
13598                                         editor.selection.moveToBookmark(level.beforeBookmark);
13599
13600                                         self.onUndo.dispatch(self, level);
13601                                 }
13602
13603                                 return level;
13604                         },
13605
13606                         redo : function() {
13607                                 var level;
13608
13609                                 if (index < data.length - 1) {
13610                                         level = data[++index];
13611
13612                                         editor.setContent(level.content, {format : 'raw'});
13613                                         editor.selection.moveToBookmark(level.bookmark);
13614
13615                                         self.onRedo.dispatch(self, level);
13616                                 }
13617
13618                                 return level;
13619                         },
13620
13621                         clear : function() {
13622                                 data = [];
13623                                 index = 0;
13624                                 self.typing = false;
13625                         },
13626
13627                         hasUndo : function() {
13628                                 return index > 0 || this.typing;
13629                         },
13630
13631                         hasRedo : function() {
13632                                 return index < data.length - 1 && !this.typing;
13633                         }
13634                 };
13635         };
13636 })(tinymce);
13637
13638 (function(tinymce) {
13639         // Shorten names
13640         var Event = tinymce.dom.Event,
13641                 isIE = tinymce.isIE,
13642                 isGecko = tinymce.isGecko,
13643                 isOpera = tinymce.isOpera,
13644                 each = tinymce.each,
13645                 extend = tinymce.extend,
13646                 TRUE = true,
13647                 FALSE = false;
13648
13649         function cloneFormats(node) {
13650                 var clone, temp, inner;
13651
13652                 do {
13653                         if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
13654                                 if (clone) {
13655                                         temp = node.cloneNode(false);
13656                                         temp.appendChild(clone);
13657                                         clone = temp;
13658                                 } else {
13659                                         clone = inner = node.cloneNode(false);
13660                                 }
13661
13662                                 clone.removeAttribute('id');
13663                         }
13664                 } while (node = node.parentNode);
13665
13666                 if (clone)
13667                         return {wrapper : clone, inner : inner};
13668         };
13669
13670         // Checks if the selection/caret is at the end of the specified block element
13671         function isAtEnd(rng, par) {
13672                 var rng2 = par.ownerDocument.createRange();
13673
13674                 rng2.setStart(rng.endContainer, rng.endOffset);
13675                 rng2.setEndAfter(par);
13676
13677                 // 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
13678                 return rng2.cloneContents().textContent.length == 0;
13679         };
13680
13681         function splitList(selection, dom, li) {
13682                 var listBlock, block;
13683
13684                 if (dom.isEmpty(li)) {
13685                         listBlock = dom.getParent(li, 'ul,ol');
13686
13687                         if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
13688                                 dom.split(listBlock, li);
13689                                 block = dom.create('p', 0, '<br data-mce-bogus="1" />');
13690                                 dom.replace(block, li);
13691                                 selection.select(block, 1);
13692                         }
13693
13694                         return FALSE;
13695                 }
13696
13697                 return TRUE;
13698         };
13699
13700         tinymce.create('tinymce.ForceBlocks', {
13701                 ForceBlocks : function(ed) {
13702                         var t = this, s = ed.settings, elm;
13703
13704                         t.editor = ed;
13705                         t.dom = ed.dom;
13706                         elm = (s.forced_root_block || 'p').toLowerCase();
13707                         s.element = elm.toUpperCase();
13708
13709                         ed.onPreInit.add(t.setup, t);
13710                 },
13711
13712                 setup : function() {
13713                         var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();
13714
13715                         // Force root blocks
13716                         if (s.forced_root_block) {
13717                                 function addRootBlocks() {
13718                                         var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;
13719
13720                                         if (!node || node.nodeType !== 1)
13721                                                 return;
13722
13723                                         // Check if node is wrapped in block
13724                                         while (node != rootNode) {
13725                                                 if (blockElements[node.nodeName])
13726                                                         return;
13727
13728                                                 node = node.parentNode;
13729                                         }
13730
13731                                         // Get current selection
13732                                         rng = selection.getRng();
13733                                         if (rng.setStart) {
13734                                                 startContainer = rng.startContainer;
13735                                                 startOffset = rng.startOffset;
13736                                                 endContainer = rng.endContainer;
13737                                                 endOffset = rng.endOffset;
13738                                         } else {
13739                                                 // Force control range into text range
13740                                                 if (rng.item) {
13741                                                         rng = ed.getDoc().body.createTextRange();
13742                                                         rng.moveToElementText(rng.item(0));
13743                                                 }
13744
13745                                                 tmpRng = rng.duplicate();
13746                                                 tmpRng.collapse(true);
13747                                                 startOffset = tmpRng.move('character', offset) * -1;
13748
13749                                                 if (!tmpRng.collapsed) {
13750                                                         tmpRng = rng.duplicate();
13751                                                         tmpRng.collapse(false);
13752                                                         endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
13753                                                 }
13754                                         }
13755
13756                                         // Wrap non block elements and text nodes
13757                                         for (node = rootNode.firstChild; node; node) {
13758                                                 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
13759                                                         if (!rootBlockNode) {
13760                                                                 rootBlockNode = dom.create(s.forced_root_block);
13761                                                                 node.parentNode.insertBefore(rootBlockNode, node);
13762                                                         }
13763
13764                                                         tempNode = node;
13765                                                         node = node.nextSibling;
13766                                                         rootBlockNode.appendChild(tempNode);
13767                                                 } else {
13768                                                         rootBlockNode = null;
13769                                                         node = node.nextSibling;
13770                                                 }
13771                                         }
13772
13773                                         if (rng.setStart) {
13774                                                 rng.setStart(startContainer, startOffset);
13775                                                 rng.setEnd(endContainer, endOffset);
13776                                                 selection.setRng(rng);
13777                                         } else {
13778                                                 try {
13779                                                         rng = ed.getDoc().body.createTextRange();
13780                                                         rng.moveToElementText(rootNode);
13781                                                         rng.collapse(true);
13782                                                         rng.moveStart('character', startOffset);
13783
13784                                                         if (endOffset > 0)
13785                                                                 rng.moveEnd('character', endOffset);
13786
13787                                                         rng.select();
13788                                                 } catch (ex) {
13789                                                         // Ignore
13790                                                 }
13791                                         }
13792
13793                                         ed.nodeChanged();
13794                                 };
13795
13796                                 ed.onKeyUp.add(addRootBlocks);
13797                                 ed.onClick.add(addRootBlocks);
13798                         }
13799
13800                         if (s.force_br_newlines) {
13801                                 // Force IE to produce BRs on enter
13802                                 if (isIE) {
13803                                         ed.onKeyPress.add(function(ed, e) {
13804                                                 var n;
13805
13806                                                 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
13807                                                         selection.setContent('<br id="__" /> ', {format : 'raw'});
13808                                                         n = dom.get('__');
13809                                                         n.removeAttribute('id');
13810                                                         selection.select(n);
13811                                                         selection.collapse();
13812                                                         return Event.cancel(e);
13813                                                 }
13814                                         });
13815                                 }
13816                         }
13817
13818                         if (s.force_p_newlines) {
13819                                 if (!isIE) {
13820                                         ed.onKeyPress.add(function(ed, e) {
13821                                                 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
13822                                                         Event.cancel(e);
13823                                         });
13824                                 } else {
13825                                         // Ungly hack to for IE to preserve the formatting when you press
13826                                         // enter at the end of a block element with formatted contents
13827                                         // This logic overrides the browsers default logic with
13828                                         // custom logic that enables us to control the output
13829                                         tinymce.addUnload(function() {
13830                                                 t._previousFormats = 0; // Fix IE leak
13831                                         });
13832
13833                                         ed.onKeyPress.add(function(ed, e) {
13834                                                 t._previousFormats = 0;
13835
13836                                                 // Clone the current formats, this will later be applied to the new block contents
13837                                                 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
13838                                                         t._previousFormats = cloneFormats(ed.selection.getStart());
13839                                         });
13840
13841                                         ed.onKeyUp.add(function(ed, e) {
13842                                                 // Let IE break the element and the wrap the new caret location in the previous formats
13843                                                 if (e.keyCode == 13 && !e.shiftKey) {
13844                                                         var parent = ed.selection.getStart(), fmt = t._previousFormats;
13845
13846                                                         // Parent is an empty block
13847                                                         if (!parent.hasChildNodes() && fmt) {
13848                                                                 parent = dom.getParent(parent, dom.isBlock);
13849
13850                                                                 if (parent && parent.nodeName != 'LI') {
13851                                                                         parent.innerHTML = '';
13852
13853                                                                         if (t._previousFormats) {
13854                                                                                 parent.appendChild(fmt.wrapper);
13855                                                                                 fmt.inner.innerHTML = '\uFEFF';
13856                                                                         } else
13857                                                                                 parent.innerHTML = '\uFEFF';
13858
13859                                                                         selection.select(parent, 1);
13860                                                                         selection.collapse(true);
13861                                                                         ed.getDoc().execCommand('Delete', false, null);
13862                                                                         t._previousFormats = 0;
13863                                                                 }
13864                                                         }
13865                                                 }
13866                                         });
13867                                 }
13868
13869                                 if (isGecko) {
13870                                         ed.onKeyDown.add(function(ed, e) {
13871                                                 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
13872                                                         t.backspaceDelete(e, e.keyCode == 8);
13873                                         });
13874                                 }
13875                         }
13876
13877                         // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
13878                         if (tinymce.isWebKit) {
13879                                 function insertBr(ed) {
13880                                         var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
13881
13882                                         // Insert BR element
13883                                         rng.insertNode(br = dom.create('br'));
13884
13885                                         // Place caret after BR
13886                                         rng.setStartAfter(br);
13887                                         rng.setEndAfter(br);
13888                                         selection.setRng(rng);
13889
13890                                         // Could not place caret after BR then insert an nbsp entity and move the caret
13891                                         if (selection.getSel().focusNode == br.previousSibling) {
13892                                                 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
13893                                                 selection.collapse(TRUE);
13894                                         }
13895
13896                                         // Create a temporary DIV after the BR and get the position as it
13897                                         // seems like getPos() returns 0 for text nodes and BR elements.
13898                                         dom.insertAfter(div, br);
13899                                         divYPos = dom.getPos(div).y;
13900                                         dom.remove(div);
13901
13902                                         // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
13903                                         if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
13904                                                 ed.getWin().scrollTo(0, divYPos);
13905                                 };
13906
13907                                 ed.onKeyPress.add(function(ed, e) {
13908                                         if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
13909                                                 insertBr(ed);
13910                                                 Event.cancel(e);
13911                                         }
13912                                 });
13913                         }
13914
13915                         // IE specific fixes
13916                         if (isIE) {
13917                                 // Replaces IE:s auto generated paragraphs with the specified element name
13918                                 if (s.element != 'P') {
13919                                         ed.onKeyPress.add(function(ed, e) {
13920                                                 t.lastElm = selection.getNode().nodeName;
13921                                         });
13922
13923                                         ed.onKeyUp.add(function(ed, e) {
13924                                                 var bl, n = selection.getNode(), b = ed.getBody();
13925
13926                                                 if (b.childNodes.length === 1 && n.nodeName == 'P') {
13927                                                         n = dom.rename(n, s.element);
13928                                                         selection.select(n);
13929                                                         selection.collapse();
13930                                                         ed.nodeChanged();
13931                                                 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
13932                                                         bl = dom.getParent(n, 'p');
13933
13934                                                         if (bl) {
13935                                                                 dom.rename(bl, s.element);
13936                                                                 ed.nodeChanged();
13937                                                         }
13938                                                 }
13939                                         });
13940                                 }
13941                         }
13942                 },
13943
13944                 getParentBlock : function(n) {
13945                         var d = this.dom;
13946
13947                         return d.getParent(n, d.isBlock);
13948                 },
13949
13950                 insertPara : function(e) {
13951                         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;
13952                         var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
13953
13954                         ed.undoManager.beforeChange();
13955
13956                         // If root blocks are forced then use Operas default behavior since it's really good
13957 // Removed due to bug: #1853816
13958 //                      if (se.forced_root_block && isOpera)
13959 //                              return TRUE;
13960
13961                         // Setup before range
13962                         rb = d.createRange();
13963
13964                         // If is before the first block element and in body, then move it into first block element
13965                         rb.setStart(s.anchorNode, s.anchorOffset);
13966                         rb.collapse(TRUE);
13967
13968                         // Setup after range
13969                         ra = d.createRange();
13970
13971                         // If is before the first block element and in body, then move it into first block element
13972                         ra.setStart(s.focusNode, s.focusOffset);
13973                         ra.collapse(TRUE);
13974
13975                         // Setup start/end points
13976                         dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
13977                         sn = dir ? s.anchorNode : s.focusNode;
13978                         so = dir ? s.anchorOffset : s.focusOffset;
13979                         en = dir ? s.focusNode : s.anchorNode;
13980                         eo = dir ? s.focusOffset : s.anchorOffset;
13981
13982                         // If selection is in empty table cell
13983                         if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
13984                                 if (sn.firstChild.nodeName == 'BR')
13985                                         dom.remove(sn.firstChild); // Remove BR
13986
13987                                 // Create two new block elements
13988                                 if (sn.childNodes.length == 0) {
13989                                         ed.dom.add(sn, se.element, null, '<br />');
13990                                         aft = ed.dom.add(sn, se.element, null, '<br />');
13991                                 } else {
13992                                         n = sn.innerHTML;
13993                                         sn.innerHTML = '';
13994                                         ed.dom.add(sn, se.element, null, n);
13995                                         aft = ed.dom.add(sn, se.element, null, '<br />');
13996                                 }
13997
13998                                 // Move caret into the last one
13999                                 r = d.createRange();
14000                                 r.selectNodeContents(aft);
14001                                 r.collapse(1);
14002                                 ed.selection.setRng(r);
14003
14004                                 return FALSE;
14005                         }
14006
14007                         // If the caret is in an invalid location in FF we need to move it into the first block
14008                         if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
14009                                 sn = en = sn.firstChild;
14010                                 so = eo = 0;
14011                                 rb = d.createRange();
14012                                 rb.setStart(sn, 0);
14013                                 ra = d.createRange();
14014                                 ra.setStart(en, 0);
14015                         }
14016
14017                         // Never use body as start or end node
14018                         sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
14019                         sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
14020                         en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
14021                         en = en.nodeName == "BODY" ? en.firstChild : en;
14022
14023                         // Get start and end blocks
14024                         sb = t.getParentBlock(sn);
14025                         eb = t.getParentBlock(en);
14026                         bn = sb ? sb.nodeName : se.element; // Get block name to create
14027
14028                         // Return inside list use default browser behavior
14029                         if (n = t.dom.getParent(sb, 'li,pre')) {
14030                                 if (n.nodeName == 'LI')
14031                                         return splitList(ed.selection, t.dom, n);
14032
14033                                 return TRUE;
14034                         }
14035
14036                         // If caption or absolute layers then always generate new blocks within
14037                         if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
14038                                 bn = se.element;
14039                                 sb = null;
14040                         }
14041
14042                         // If caption or absolute layers then always generate new blocks within
14043                         if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
14044                                 bn = se.element;
14045                                 eb = null;
14046                         }
14047
14048                         // Use P instead
14049                         if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
14050                                 bn = se.element;
14051                                 sb = eb = null;
14052                         }
14053
14054                         // Setup new before and after blocks
14055                         bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
14056                         aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
14057
14058                         // Remove id from after clone
14059                         aft.removeAttribute('id');
14060
14061                         // Is header and cursor is at the end, then force paragraph under
14062                         if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
14063                                 aft = ed.dom.create(se.element);
14064
14065                         // Find start chop node
14066                         n = sc = sn;
14067                         do {
14068                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
14069                                         break;
14070
14071                                 sc = n;
14072                         } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
14073
14074                         // Find end chop node
14075                         n = ec = en;
14076                         do {
14077                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
14078                                         break;
14079
14080                                 ec = n;
14081                         } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
14082
14083                         // Place first chop part into before block element
14084                         if (sc.nodeName == bn)
14085                                 rb.setStart(sc, 0);
14086                         else
14087                                 rb.setStartBefore(sc);
14088
14089                         rb.setEnd(sn, so);
14090                         bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
14091
14092                         // Place secnd chop part within new block element
14093                         try {
14094                                 ra.setEndAfter(ec);
14095                         } catch(ex) {
14096                                 //console.debug(s.focusNode, s.focusOffset);
14097                         }
14098
14099                         ra.setStart(en, eo);
14100                         aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
14101
14102                         // Create range around everything
14103                         r = d.createRange();
14104                         if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
14105                                 r.setStartBefore(sc.parentNode);
14106                         } else {
14107                                 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
14108                                         r.setStartBefore(rb.startContainer);
14109                                 else
14110                                         r.setStart(rb.startContainer, rb.startOffset);
14111                         }
14112
14113                         if (!ec.nextSibling && ec.parentNode.nodeName == bn)
14114                                 r.setEndAfter(ec.parentNode);
14115                         else
14116                                 r.setEnd(ra.endContainer, ra.endOffset);
14117
14118                         // Delete and replace it with new block elements
14119                         r.deleteContents();
14120
14121                         if (isOpera)
14122                                 ed.getWin().scrollTo(0, vp.y);
14123
14124                         // Never wrap blocks in blocks
14125                         if (bef.firstChild && bef.firstChild.nodeName == bn)
14126                                 bef.innerHTML = bef.firstChild.innerHTML;
14127
14128                         if (aft.firstChild && aft.firstChild.nodeName == bn)
14129                                 aft.innerHTML = aft.firstChild.innerHTML;
14130
14131                         function appendStyles(e, en) {
14132                                 var nl = [], nn, n, i;
14133
14134                                 e.innerHTML = '';
14135
14136                                 // Make clones of style elements
14137                                 if (se.keep_styles) {
14138                                         n = en;
14139                                         do {
14140                                                 // We only want style specific elements
14141                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
14142                                                         nn = n.cloneNode(FALSE);
14143                                                         dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
14144                                                         nl.push(nn);
14145                                                 }
14146                                         } while (n = n.parentNode);
14147                                 }
14148
14149                                 // Append style elements to aft
14150                                 if (nl.length > 0) {
14151                                         for (i = nl.length - 1, nn = e; i >= 0; i--)
14152                                                 nn = nn.appendChild(nl[i]);
14153
14154                                         // Padd most inner style element
14155                                         nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
14156                                         return nl[0]; // Move caret to most inner element
14157                                 } else
14158                                         e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
14159                         };
14160                                 
14161                         // Padd empty blocks
14162                         if (dom.isEmpty(bef))
14163                                 appendStyles(bef, sn);
14164
14165                         // Fill empty afterblook with current style
14166                         if (dom.isEmpty(aft))
14167                                 car = appendStyles(aft, en);
14168
14169                         // Opera needs this one backwards for older versions
14170                         if (isOpera && parseFloat(opera.version()) < 9.5) {
14171                                 r.insertNode(bef);
14172                                 r.insertNode(aft);
14173                         } else {
14174                                 r.insertNode(aft);
14175                                 r.insertNode(bef);
14176                         }
14177
14178                         // Normalize
14179                         aft.normalize();
14180                         bef.normalize();
14181
14182                         // Move cursor and scroll into view
14183                         ed.selection.select(aft, true);
14184                         ed.selection.collapse(true);
14185
14186                         // 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
14187                         y = ed.dom.getPos(aft).y;
14188                         //ch = aft.clientHeight;
14189
14190                         // Is element within viewport
14191                         if (y < vp.y || y + 25 > vp.y + vp.h) {
14192                                 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
14193
14194                                 /*console.debug(
14195                                         'Element: y=' + y + ', h=' + ch + ', ' +
14196                                         'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
14197                                 );*/
14198                         }
14199
14200                         ed.undoManager.add();
14201
14202                         return FALSE;
14203                 },
14204
14205                 backspaceDelete : function(e, bs) {
14206                         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;
14207
14208                         // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
14209                         if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
14210                                 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
14211
14212                                 // Walk the dom backwards until we find a text node
14213                                 for (n = sc.lastChild; n; n = walker.prev()) {
14214                                         if (n.nodeType == 3) {
14215                                                 r.setStart(n, n.nodeValue.length);
14216                                                 r.collapse(true);
14217                                                 se.setRng(r);
14218                                                 return;
14219                                         }
14220                                 }
14221                         }
14222
14223                         // The caret sometimes gets stuck in Gecko if you delete empty paragraphs
14224                         // This workaround removes the element by hand and moves the caret to the previous element
14225                         if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
14226                                 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
14227                                         // Find previous block element
14228                                         n = sc;
14229                                         while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
14230
14231                                         if (n) {
14232                                                 if (sc != b.firstChild) {
14233                                                         // Find last text node
14234                                                         w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
14235                                                         while (tn = w.nextNode())
14236                                                                 n = tn;
14237
14238                                                         // Place caret at the end of last text node
14239                                                         r = ed.getDoc().createRange();
14240                                                         r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
14241                                                         r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
14242                                                         se.setRng(r);
14243
14244                                                         // Remove the target container
14245                                                         ed.dom.remove(sc);
14246                                                 }
14247
14248                                                 return Event.cancel(e);
14249                                         }
14250                                 }
14251                         }
14252                 }
14253         });
14254 })(tinymce);
14255
14256 (function(tinymce) {
14257         // Shorten names
14258         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
14259
14260         tinymce.create('tinymce.ControlManager', {
14261                 ControlManager : function(ed, s) {
14262                         var t = this, i;
14263
14264                         s = s || {};
14265                         t.editor = ed;
14266                         t.controls = {};
14267                         t.onAdd = new tinymce.util.Dispatcher(t);
14268                         t.onPostRender = new tinymce.util.Dispatcher(t);
14269                         t.prefix = s.prefix || ed.id + '_';
14270                         t._cls = {};
14271
14272                         t.onPostRender.add(function() {
14273                                 each(t.controls, function(c) {
14274                                         c.postRender();
14275                                 });
14276                         });
14277                 },
14278
14279                 get : function(id) {
14280                         return this.controls[this.prefix + id] || this.controls[id];
14281                 },
14282
14283                 setActive : function(id, s) {
14284                         var c = null;
14285
14286                         if (c = this.get(id))
14287                                 c.setActive(s);
14288
14289                         return c;
14290                 },
14291
14292                 setDisabled : function(id, s) {
14293                         var c = null;
14294
14295                         if (c = this.get(id))
14296                                 c.setDisabled(s);
14297
14298                         return c;
14299                 },
14300
14301                 add : function(c) {
14302                         var t = this;
14303
14304                         if (c) {
14305                                 t.controls[c.id] = c;
14306                                 t.onAdd.dispatch(c, t);
14307                         }
14308
14309                         return c;
14310                 },
14311
14312                 createControl : function(n) {
14313                         var c, t = this, ed = t.editor;
14314
14315                         each(ed.plugins, function(p) {
14316                                 if (p.createControl) {
14317                                         c = p.createControl(n, t);
14318
14319                                         if (c)
14320                                                 return false;
14321                                 }
14322                         });
14323
14324                         switch (n) {
14325                                 case "|":
14326                                 case "separator":
14327                                         return t.createSeparator();
14328                         }
14329
14330                         if (!c && ed.buttons && (c = ed.buttons[n]))
14331                                 return t.createButton(n, c);
14332
14333                         return t.add(c);
14334                 },
14335
14336                 createDropMenu : function(id, s, cc) {
14337                         var t = this, ed = t.editor, c, bm, v, cls;
14338
14339                         s = extend({
14340                                 'class' : 'mceDropDown',
14341                                 constrain : ed.settings.constrain_menus
14342                         }, s);
14343
14344                         s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
14345                         if (v = ed.getParam('skin_variant'))
14346                                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
14347
14348                         id = t.prefix + id;
14349                         cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
14350                         c = t.controls[id] = new cls(id, s);
14351                         c.onAddItem.add(function(c, o) {
14352                                 var s = o.settings;
14353
14354                                 s.title = ed.getLang(s.title, s.title);
14355
14356                                 if (!s.onclick) {
14357                                         s.onclick = function(v) {
14358                                                 if (s.cmd)
14359                                                         ed.execCommand(s.cmd, s.ui || false, s.value);
14360                                         };
14361                                 }
14362                         });
14363
14364                         ed.onRemove.add(function() {
14365                                 c.destroy();
14366                         });
14367
14368                         // Fix for bug #1897785, #1898007
14369                         if (tinymce.isIE) {
14370                                 c.onShowMenu.add(function() {
14371                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
14372                                         ed.focus();
14373
14374                                         bm = ed.selection.getBookmark(1);
14375                                 });
14376
14377                                 c.onHideMenu.add(function() {
14378                                         if (bm) {
14379                                                 ed.selection.moveToBookmark(bm);
14380                                                 bm = 0;
14381                                         }
14382                                 });
14383                         }
14384
14385                         return t.add(c);
14386                 },
14387
14388                 createListBox : function(id, s, cc) {
14389                         var t = this, ed = t.editor, cmd, c, cls;
14390
14391                         if (t.get(id))
14392                                 return null;
14393
14394                         s.title = ed.translate(s.title);
14395                         s.scope = s.scope || ed;
14396
14397                         if (!s.onselect) {
14398                                 s.onselect = function(v) {
14399                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14400                                 };
14401                         }
14402
14403                         s = extend({
14404                                 title : s.title,
14405                                 'class' : 'mce_' + id,
14406                                 scope : s.scope,
14407                                 control_manager : t
14408                         }, s);
14409
14410                         id = t.prefix + id;
14411
14412                         if (ed.settings.use_native_selects)
14413                                 c = new tinymce.ui.NativeListBox(id, s);
14414                         else {
14415                                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;
14416                                 c = new cls(id, s, ed);
14417                         }
14418
14419                         t.controls[id] = c;
14420
14421                         // Fix focus problem in Safari
14422                         if (tinymce.isWebKit) {
14423                                 c.onPostRender.add(function(c, n) {
14424                                         // Store bookmark on mousedown
14425                                         Event.add(n, 'mousedown', function() {
14426                                                 ed.bookmark = ed.selection.getBookmark(1);
14427                                         });
14428
14429                                         // Restore on focus, since it might be lost
14430                                         Event.add(n, 'focus', function() {
14431                                                 ed.selection.moveToBookmark(ed.bookmark);
14432                                                 ed.bookmark = null;
14433                                         });
14434                                 });
14435                         }
14436
14437                         if (c.hideMenu)
14438                                 ed.onMouseDown.add(c.hideMenu, c);
14439
14440                         return t.add(c);
14441                 },
14442
14443                 createButton : function(id, s, cc) {
14444                         var t = this, ed = t.editor, o, c, cls;
14445
14446                         if (t.get(id))
14447                                 return null;
14448
14449                         s.title = ed.translate(s.title);
14450                         s.label = ed.translate(s.label);
14451                         s.scope = s.scope || ed;
14452
14453                         if (!s.onclick && !s.menu_button) {
14454                                 s.onclick = function() {
14455                                         ed.execCommand(s.cmd, s.ui || false, s.value);
14456                                 };
14457                         }
14458
14459                         s = extend({
14460                                 title : s.title,
14461                                 'class' : 'mce_' + id,
14462                                 unavailable_prefix : ed.getLang('unavailable', ''),
14463                                 scope : s.scope,
14464                                 control_manager : t
14465                         }, s);
14466
14467                         id = t.prefix + id;
14468
14469                         if (s.menu_button) {
14470                                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
14471                                 c = new cls(id, s, ed);
14472                                 ed.onMouseDown.add(c.hideMenu, c);
14473                         } else {
14474                                 cls = t._cls.button || tinymce.ui.Button;
14475                                 c = new cls(id, s, ed);
14476                         }
14477
14478                         return t.add(c);
14479                 },
14480
14481                 createMenuButton : function(id, s, cc) {
14482                         s = s || {};
14483                         s.menu_button = 1;
14484
14485                         return this.createButton(id, s, cc);
14486                 },
14487
14488                 createSplitButton : function(id, s, cc) {
14489                         var t = this, ed = t.editor, cmd, c, cls;
14490
14491                         if (t.get(id))
14492                                 return null;
14493
14494                         s.title = ed.translate(s.title);
14495                         s.scope = s.scope || ed;
14496
14497                         if (!s.onclick) {
14498                                 s.onclick = function(v) {
14499                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14500                                 };
14501                         }
14502
14503                         if (!s.onselect) {
14504                                 s.onselect = function(v) {
14505                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14506                                 };
14507                         }
14508
14509                         s = extend({
14510                                 title : s.title,
14511                                 'class' : 'mce_' + id,
14512                                 scope : s.scope,
14513                                 control_manager : t
14514                         }, s);
14515
14516                         id = t.prefix + id;
14517                         cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
14518                         c = t.add(new cls(id, s, ed));
14519                         ed.onMouseDown.add(c.hideMenu, c);
14520
14521                         return c;
14522                 },
14523
14524                 createColorSplitButton : function(id, s, cc) {
14525                         var t = this, ed = t.editor, cmd, c, cls, bm;
14526
14527                         if (t.get(id))
14528                                 return null;
14529
14530                         s.title = ed.translate(s.title);
14531                         s.scope = s.scope || ed;
14532
14533                         if (!s.onclick) {
14534                                 s.onclick = function(v) {
14535                                         if (tinymce.isIE)
14536                                                 bm = ed.selection.getBookmark(1);
14537
14538                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14539                                 };
14540                         }
14541
14542                         if (!s.onselect) {
14543                                 s.onselect = function(v) {
14544                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);
14545                                 };
14546                         }
14547
14548                         s = extend({
14549                                 title : s.title,
14550                                 'class' : 'mce_' + id,
14551                                 'menu_class' : ed.getParam('skin') + 'Skin',
14552                                 scope : s.scope,
14553                                 more_colors_title : ed.getLang('more_colors')
14554                         }, s);
14555
14556                         id = t.prefix + id;
14557                         cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
14558                         c = new cls(id, s, ed);
14559                         ed.onMouseDown.add(c.hideMenu, c);
14560
14561                         // Remove the menu element when the editor is removed
14562                         ed.onRemove.add(function() {
14563                                 c.destroy();
14564                         });
14565
14566                         // Fix for bug #1897785, #1898007
14567                         if (tinymce.isIE) {
14568                                 c.onShowMenu.add(function() {
14569                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location
14570                                         ed.focus();
14571                                         bm = ed.selection.getBookmark(1);
14572                                 });
14573
14574                                 c.onHideMenu.add(function() {
14575                                         if (bm) {
14576                                                 ed.selection.moveToBookmark(bm);
14577                                                 bm = 0;
14578                                         }
14579                                 });
14580                         }
14581
14582                         return t.add(c);
14583                 },
14584
14585                 createToolbar : function(id, s, cc) {
14586                         var c, t = this, cls;
14587
14588                         id = t.prefix + id;
14589                         cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
14590                         c = new cls(id, s, t.editor);
14591
14592                         if (t.get(id))
14593                                 return null;
14594
14595                         return t.add(c);
14596                 },
14597                 
14598                 createToolbarGroup : function(id, s, cc) {
14599                         var c, t = this, cls;
14600                         id = t.prefix + id;
14601                         cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
14602                         c = new cls(id, s, t.editor);
14603                         
14604                         if (t.get(id))
14605                                 return null;
14606                         
14607                         return t.add(c);
14608                 },
14609
14610                 createSeparator : function(cc) {
14611                         var cls = cc || this._cls.separator || tinymce.ui.Separator;
14612
14613                         return new cls();
14614                 },
14615
14616                 setControlType : function(n, c) {
14617                         return this._cls[n.toLowerCase()] = c;
14618                 },
14619         
14620                 destroy : function() {
14621                         each(this.controls, function(c) {
14622                                 c.destroy();
14623                         });
14624
14625                         this.controls = null;
14626                 }
14627         });
14628 })(tinymce);
14629
14630 (function(tinymce) {
14631         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
14632
14633         tinymce.create('tinymce.WindowManager', {
14634                 WindowManager : function(ed) {
14635                         var t = this;
14636
14637                         t.editor = ed;
14638                         t.onOpen = new Dispatcher(t);
14639                         t.onClose = new Dispatcher(t);
14640                         t.params = {};
14641                         t.features = {};
14642                 },
14643
14644                 open : function(s, p) {
14645                         var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
14646
14647                         // Default some options
14648                         s = s || {};
14649                         p = p || {};
14650                         sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
14651                         sh = isOpera ? vp.h : screen.height;
14652                         s.name = s.name || 'mc_' + new Date().getTime();
14653                         s.width = parseInt(s.width || 320);
14654                         s.height = parseInt(s.height || 240);
14655                         s.resizable = true;
14656                         s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
14657                         s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
14658                         p.inline = false;
14659                         p.mce_width = s.width;
14660                         p.mce_height = s.height;
14661                         p.mce_auto_focus = s.auto_focus;
14662
14663                         if (mo) {
14664                                 if (isIE) {
14665                                         s.center = true;
14666                                         s.help = false;
14667                                         s.dialogWidth = s.width + 'px';
14668                                         s.dialogHeight = s.height + 'px';
14669                                         s.scroll = s.scrollbars || false;
14670                                 }
14671                         }
14672
14673                         // Build features string
14674                         each(s, function(v, k) {
14675                                 if (tinymce.is(v, 'boolean'))
14676                                         v = v ? 'yes' : 'no';
14677
14678                                 if (!/^(name|url)$/.test(k)) {
14679                                         if (isIE && mo)
14680                                                 f += (f ? ';' : '') + k + ':' + v;
14681                                         else
14682                                                 f += (f ? ',' : '') + k + '=' + v;
14683                                 }
14684                         });
14685
14686                         t.features = s;
14687                         t.params = p;
14688                         t.onOpen.dispatch(t, s, p);
14689
14690                         u = s.url || s.file;
14691                         u = tinymce._addVer(u);
14692
14693                         try {
14694                                 if (isIE && mo) {
14695                                         w = 1;
14696                                         window.showModalDialog(u, window, f);
14697                                 } else
14698                                         w = window.open(u, s.name, f);
14699                         } catch (ex) {
14700                                 // Ignore
14701                         }
14702
14703                         if (!w)
14704                                 alert(t.editor.getLang('popup_blocked'));
14705                 },
14706
14707                 close : function(w) {
14708                         w.close();
14709                         this.onClose.dispatch(this);
14710                 },
14711
14712                 createInstance : function(cl, a, b, c, d, e) {
14713                         var f = tinymce.resolve(cl);
14714
14715                         return new f(a, b, c, d, e);
14716                 },
14717
14718                 confirm : function(t, cb, s, w) {
14719                         w = w || window;
14720
14721                         cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
14722                 },
14723
14724                 alert : function(tx, cb, s, w) {
14725                         var t = this;
14726
14727                         w = w || window;
14728                         w.alert(t._decode(t.editor.getLang(tx, tx)));
14729
14730                         if (cb)
14731                                 cb.call(s || t);
14732                 },
14733
14734                 resizeBy : function(dw, dh, win) {
14735                         win.resizeBy(dw, dh);
14736                 },
14737
14738                 // Internal functions
14739
14740                 _decode : function(s) {
14741                         return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
14742                 }
14743         });
14744 }(tinymce));
14745 (function(tinymce) {
14746         tinymce.Formatter = function(ed) {
14747                 var formats = {},
14748                         each = tinymce.each,
14749                         dom = ed.dom,
14750                         selection = ed.selection,
14751                         TreeWalker = tinymce.dom.TreeWalker,
14752                         rangeUtils = new tinymce.dom.RangeUtils(dom),
14753                         isValid = ed.schema.isValidChild,
14754                         isBlock = dom.isBlock,
14755                         forcedRootBlock = ed.settings.forced_root_block,
14756                         nodeIndex = dom.nodeIndex,
14757                         INVISIBLE_CHAR = '\uFEFF',
14758                         MCE_ATTR_RE = /^(src|href|style)$/,
14759                         FALSE = false,
14760                         TRUE = true,
14761                         undefined,
14762                         pendingFormats = {apply : [], remove : []};
14763
14764                 function isArray(obj) {
14765                         return obj instanceof Array;
14766                 };
14767
14768                 function getParents(node, selector) {
14769                         return dom.getParents(node, selector, dom.getRoot());
14770                 };
14771
14772                 function isCaretNode(node) {
14773                         return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
14774                 };
14775
14776                 // Public functions
14777
14778                 function get(name) {
14779                         return name ? formats[name] : formats;
14780                 };
14781
14782                 function register(name, format) {
14783                         if (name) {
14784                                 if (typeof(name) !== 'string') {
14785                                         each(name, function(format, name) {
14786                                                 register(name, format);
14787                                         });
14788                                 } else {
14789                                         // Force format into array and add it to internal collection
14790                                         format = format.length ? format : [format];
14791
14792                                         each(format, function(format) {
14793                                                 // Set deep to false by default on selector formats this to avoid removing
14794                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs
14795                                                 if (format.deep === undefined)
14796                                                         format.deep = !format.selector;
14797
14798                                                 // Default to true
14799                                                 if (format.split === undefined)
14800                                                         format.split = !format.selector || format.inline;
14801
14802                                                 // Default to true
14803                                                 if (format.remove === undefined && format.selector && !format.inline)
14804                                                         format.remove = 'none';
14805
14806                                                 // Mark format as a mixed format inline + block level
14807                                                 if (format.selector && format.inline) {
14808                                                         format.mixed = true;
14809                                                         format.block_expand = true;
14810                                                 }
14811
14812                                                 // Split classes if needed
14813                                                 if (typeof(format.classes) === 'string')
14814                                                         format.classes = format.classes.split(/\s+/);
14815                                         });
14816
14817                                         formats[name] = format;
14818                                 }
14819                         }
14820                 };
14821
14822                 var getTextDecoration = function(node) {
14823                         var decoration;
14824
14825                         ed.dom.getParent(node, function(n) {
14826                                 decoration = ed.dom.getStyle(n, 'text-decoration');
14827                                 return decoration && decoration !== 'none';
14828                         });
14829
14830                         return decoration;
14831                 };
14832
14833                 var processUnderlineAndColor = function(node) {
14834                         var textDecoration;
14835                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
14836                                 textDecoration = getTextDecoration(node.parentNode);
14837                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {
14838                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);
14839                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
14840                                         ed.dom.setStyle(node, 'text-decoration', null);
14841                                 }
14842                         }
14843                 };
14844
14845                 function apply(name, vars, node) {
14846                         var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
14847
14848                         function moveStart(rng) {
14849                                 var container = rng.startContainer,
14850                                         offset = rng.startOffset,
14851                                         walker, node;
14852
14853                                 // Move startContainer/startOffset in to a suitable node
14854                                 if (container.nodeType == 1 || container.nodeValue === "") {
14855                                         container = container.nodeType == 1 ? container.childNodes[offset] : container;
14856
14857                                         // Might fail if the offset is behind the last element in it's container
14858                                         if (container) {
14859                                                 walker = new TreeWalker(container, container.parentNode);
14860                                                 for (node = walker.current(); node; node = walker.next()) {
14861                                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14862                                                                 rng.setStart(node, 0);
14863                                                                 break;
14864                                                         }
14865                                                 }
14866                                         }
14867                                 }
14868
14869                                 return rng;
14870                         };
14871
14872                         function setElementFormat(elm, fmt) {
14873                                 fmt = fmt || format;
14874
14875                                 if (elm) {
14876                                         each(fmt.styles, function(value, name) {
14877                                                 dom.setStyle(elm, name, replaceVars(value, vars));
14878                                         });
14879
14880                                         each(fmt.attributes, function(value, name) {
14881                                                 dom.setAttrib(elm, name, replaceVars(value, vars));
14882                                         });
14883
14884                                         each(fmt.classes, function(value) {
14885                                                 value = replaceVars(value, vars);
14886
14887                                                 if (!dom.hasClass(elm, value))
14888                                                         dom.addClass(elm, value);
14889                                         });
14890                                 }
14891                         };
14892                         function adjustSelectionToVisibleSelection() {
14893
14894                                 function findSelectionEnd(start, end) {
14895                                         var walker = new TreeWalker(end);
14896                                         for (node = walker.current(); node; node = walker.prev()) {
14897                                                 if (node.childNodes.length > 1 || node == start) {
14898                                                         return node;
14899                                                 }
14900                                         }
14901                                 }
14902
14903                                 // Adjust selection so that a end container with a end offset of zero is not included in the selection
14904                                 // as this isn't visible to the user.
14905                                 var rng = ed.selection.getRng();
14906                                 var start = rng.startContainer;
14907                                 var end = rng.endContainer;
14908                                 if (start != end && rng.endOffset == 0) {
14909                                         var newEnd = findSelectionEnd(start, end);
14910                                         var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
14911                                         rng.setEnd(newEnd, endOffset);
14912                                 }
14913                                 return rng;
14914                         }
14915                         
14916                         function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
14917                                 var nodes =[], listIndex =-1, list, startIndex = -1, endIndex = -1, currentWrapElm;
14918                                 
14919                                 // find the index of the first child list.
14920                                 each(node.childNodes, function(n, index) {
14921                                         if (n.nodeName==="UL"||n.nodeName==="OL") {listIndex = index; list=n; return false; }
14922                                 });
14923                                 
14924                                 // get the index of the bookmarks
14925                                 each(node.childNodes, function(n, index) {
14926                                         if (n.nodeName==="SPAN" &&dom.getAttrib(n, "data-mce-type")=="bookmark" && n.id==bookmark.id+"_start") {startIndex=index}
14927                                         if (n.nodeName==="SPAN" &&dom.getAttrib(n, "data-mce-type")=="bookmark" && n.id==bookmark.id+"_end") {endIndex=index}
14928                                 });
14929                                 
14930                                 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
14931                                 if (listIndex<=0 || (startIndex<listIndex&&endIndex>listIndex)) {
14932                                         each(tinymce.grep(node.childNodes), process);
14933                                         return 0;
14934                                 } else {
14935                                         currentWrapElm = wrapElm.cloneNode(FALSE);
14936                                         
14937                                         // create a list of the nodes on the same side of the list as the selection
14938                                         each(tinymce.grep(node.childNodes), function(n, index) {
14939                                                 if ((startIndex<listIndex && index <listIndex) || (startIndex>listIndex && index >listIndex)) {
14940                                                         nodes.push(n); 
14941                                                         n.parentNode.removeChild(n); 
14942                                                 }
14943                                         });
14944                                         
14945                                         // insert the wrapping element either before or after the list.
14946                                         if (startIndex<listIndex) {
14947                                                 node.insertBefore(currentWrapElm, list);
14948                                         } else if (startIndex>listIndex) {
14949                                                 node.insertBefore(currentWrapElm, list.nextSibling);
14950                                         }
14951                                         
14952                                         // add the new nodes to the list.
14953                                         newWrappers.push(currentWrapElm);
14954                                         each(nodes, function(node){currentWrapElm.appendChild(node)});
14955                                         return currentWrapElm;
14956                                 }
14957                         };
14958                         
14959                         function applyRngStyle(rng, bookmark) {
14960                                 var newWrappers = [], wrapName, wrapElm;
14961
14962                                 // Setup wrapper element
14963                                 wrapName = format.inline || format.block;
14964                                 wrapElm = dom.create(wrapName);
14965                                 setElementFormat(wrapElm);
14966
14967                                 rangeUtils.walk(rng, function(nodes) {
14968                                         var currentWrapElm;
14969
14970                                         function process(node) {
14971                                                 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
14972
14973                                                 // Stop wrapping on br elements
14974                                                 if (isEq(nodeName, 'br')) {
14975                                                         currentWrapElm = 0;
14976
14977                                                         // Remove any br elements when we wrap things
14978                                                         if (format.block)
14979                                                                 dom.remove(node);
14980
14981                                                         return;
14982                                                 }
14983
14984                                                 // If node is wrapper type
14985                                                 if (format.wrapper && matchNode(node, name, vars)) {
14986                                                         currentWrapElm = 0;
14987                                                         return;
14988                                                 }
14989
14990                                                 // Can we rename the block
14991                                                 if (format.block && !format.wrapper && isTextBlock(nodeName)) {
14992                                                         node = dom.rename(node, wrapName);
14993                                                         setElementFormat(node);
14994                                                         newWrappers.push(node);
14995                                                         currentWrapElm = 0;
14996                                                         return;
14997                                                 }
14998
14999                                                 // Handle selector patterns
15000                                                 if (format.selector) {
15001                                                         // Look for matching formats
15002                                                         each(formatList, function(format) {
15003                                                                 // Check collapsed state if it exists
15004                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {
15005                                                                         return;
15006                                                                 }
15007
15008                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {
15009                                                                         setElementFormat(node, format);
15010                                                                         found = true;
15011                                                                 }
15012                                                         });
15013
15014                                                         // Continue processing if a selector match wasn't found and a inline element is defined
15015                                                         if (!format.inline || found) {
15016                                                                 currentWrapElm = 0;
15017                                                                 return;
15018                                                         }
15019                                                 }
15020
15021                                                 // Is it valid to wrap this item
15022                                                 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
15023                                                                 !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
15024                                                         // Start wrapping
15025                                                         if (!currentWrapElm) {
15026                                                                 // Wrap the node
15027                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
15028                                                                 node.parentNode.insertBefore(currentWrapElm, node);
15029                                                                 newWrappers.push(currentWrapElm);
15030                                                         }
15031
15032                                                         currentWrapElm.appendChild(node);
15033                                                 } else if (nodeName == 'li' && bookmark) {
15034                                                         // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
15035                                                         currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
15036                                                 } else {
15037                                                         // Start a new wrapper for possible children
15038                                                         currentWrapElm = 0;
15039
15040                                                         each(tinymce.grep(node.childNodes), process);
15041
15042                                                         // End the last wrapper
15043                                                         currentWrapElm = 0;
15044                                                 }
15045                                         };
15046
15047                                         // Process siblings from range
15048                                         each(nodes, process);
15049                                 });
15050
15051                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link
15052                                 if (format.wrap_links === false) {
15053                                         each(newWrappers, function(node) {
15054                                                 function process(node) {
15055                                                         var i, currentWrapElm, children;
15056
15057                                                         if (node.nodeName === 'A') {
15058                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);
15059                                                                 newWrappers.push(currentWrapElm);
15060
15061                                                                 children = tinymce.grep(node.childNodes);
15062                                                                 for (i = 0; i < children.length; i++)
15063                                                                         currentWrapElm.appendChild(children[i]);
15064
15065                                                                 node.appendChild(currentWrapElm);
15066                                                         }
15067
15068                                                         each(tinymce.grep(node.childNodes), process);
15069                                                 };
15070
15071                                                 process(node);
15072                                         });
15073                                 }
15074
15075                                 // Cleanup
15076                                 each(newWrappers, function(node) {
15077                                         var childCount;
15078
15079                                         function getChildCount(node) {
15080                                                 var count = 0;
15081
15082                                                 each(node.childNodes, function(node) {
15083                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
15084                                                                 count++;
15085                                                 });
15086
15087                                                 return count;
15088                                         };
15089
15090                                         function mergeStyles(node) {
15091                                                 var child, clone;
15092
15093                                                 each(node.childNodes, function(node) {
15094                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
15095                                                                 child = node;
15096                                                                 return FALSE; // break loop
15097                                                         }
15098                                                 });
15099
15100                                                 // If child was found and of the same type as the current node
15101                                                 if (child && matchName(child, format)) {
15102                                                         clone = child.cloneNode(FALSE);
15103                                                         setElementFormat(clone);
15104
15105                                                         dom.replace(clone, node, TRUE);
15106                                                         dom.remove(child, 1);
15107                                                 }
15108
15109                                                 return clone || node;
15110                                         };
15111
15112                                         childCount = getChildCount(node);
15113
15114                                         // Remove empty nodes but only if there is multiple wrappers and they are not block
15115                                         // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
15116                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
15117                                                 dom.remove(node, 1);
15118                                                 return;
15119                                         }
15120
15121                                         if (format.inline || format.wrapper) {
15122                                                 // Merges the current node with it's children of similar type to reduce the number of elements
15123                                                 if (!format.exact && childCount === 1)
15124                                                         node = mergeStyles(node);
15125
15126                                                 // Remove/merge children
15127                                                 each(formatList, function(format) {
15128                                                         // Merge all children of similar type will move styles from child to parent
15129                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
15130                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
15131                                                         each(dom.select(format.inline, node), function(child) {
15132                                                                 var parent;
15133
15134                                                                 // When wrap_links is set to false we don't want
15135                                                                 // to remove the format on children within links
15136                                                                 if (format.wrap_links === false) {
15137                                                                         parent = child.parentNode;
15138
15139                                                                         do {
15140                                                                                 if (parent.nodeName === 'A')
15141                                                                                         return;
15142                                                                         } while (parent = parent.parentNode);
15143                                                                 }
15144
15145                                                                 removeFormat(format, vars, child, format.exact ? child : null);
15146                                                         });
15147                                                 });
15148
15149                                                 // Remove child if direct parent is of same type
15150                                                 if (matchNode(node.parentNode, name, vars)) {
15151                                                         dom.remove(node, 1);
15152                                                         node = 0;
15153                                                         return TRUE;
15154                                                 }
15155
15156                                                 // Look for parent with similar style format
15157                                                 if (format.merge_with_parents) {
15158                                                         dom.getParent(node.parentNode, function(parent) {
15159                                                                 if (matchNode(parent, name, vars)) {
15160                                                                         dom.remove(node, 1);
15161                                                                         node = 0;
15162                                                                         return TRUE;
15163                                                                 }
15164                                                         });
15165                                                 }
15166
15167                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
15168                                                 if (node) {
15169                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
15170                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
15171                                                 }
15172                                         }
15173                                 });
15174                         };
15175
15176                         if (format) {
15177                                 if (node) {
15178                                         rng = dom.createRng();
15179
15180                                         rng.setStartBefore(node);
15181                                         rng.setEndAfter(node);
15182
15183                                         applyRngStyle(expandRng(rng, formatList));
15184                                 } else {
15185                                         if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
15186                                                 // Obtain selection node before selection is unselected by applyRngStyle()
15187                                                 var curSelNode = ed.selection.getNode();
15188
15189                                                 // Apply formatting to selection
15190                                                 ed.selection.setRng(adjustSelectionToVisibleSelection());
15191                                                 bookmark = selection.getBookmark();
15192                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
15193
15194                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.
15195                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {
15196                                                         tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
15197                                                         processUnderlineAndColor(curSelNode);
15198                                                 }
15199
15200                                                 selection.moveToBookmark(bookmark);
15201                                                 selection.setRng(moveStart(selection.getRng(TRUE)));
15202                                                 ed.nodeChanged();
15203                                         } else
15204                                                 performCaretAction('apply', name, vars);
15205                                 }
15206                         }
15207                 };
15208
15209                 function remove(name, vars, node) {
15210                         var formatList = get(name), format = formatList[0], bookmark, i, rng;
15211                         function moveStart(rng) {
15212                                 var container = rng.startContainer,
15213                                         offset = rng.startOffset,
15214                                         walker, node, nodes, tmpNode;
15215
15216                                 // Convert text node into index if possible
15217                                 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
15218                                         container = container.parentNode;
15219                                         offset = nodeIndex(container) + 1;
15220                                 }
15221
15222                                 // Move startContainer/startOffset in to a suitable node
15223                                 if (container.nodeType == 1) {
15224                                         nodes = container.childNodes;
15225                                         container = nodes[Math.min(offset, nodes.length - 1)];
15226                                         walker = new TreeWalker(container);
15227
15228                                         // If offset is at end of the parent node walk to the next one
15229                                         if (offset > nodes.length - 1)
15230                                                 walker.next();
15231
15232                                         for (node = walker.current(); node; node = walker.next()) {
15233                                                 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
15234                                                         // IE has a "neat" feature where it moves the start node into the closest element
15235                                                         // we can avoid this by inserting an element before it and then remove it after we set the selection
15236                                                         tmpNode = dom.create('a', null, INVISIBLE_CHAR);
15237                                                         node.parentNode.insertBefore(tmpNode, node);
15238
15239                                                         // Set selection and remove tmpNode
15240                                                         rng.setStart(node, 0);
15241                                                         selection.setRng(rng);
15242                                                         dom.remove(tmpNode);
15243
15244                                                         return;
15245                                                 }
15246                                         }
15247                                 }
15248                         };
15249
15250                         // Merges the styles for each node
15251                         function process(node) {
15252                                 var children, i, l;
15253
15254                                 // Grab the children first since the nodelist might be changed
15255                                 children = tinymce.grep(node.childNodes);
15256
15257                                 // Process current node
15258                                 for (i = 0, l = formatList.length; i < l; i++) {
15259                                         if (removeFormat(formatList[i], vars, node, node))
15260                                                 break;
15261                                 }
15262
15263                                 // Process the children
15264                                 if (format.deep) {
15265                                         for (i = 0, l = children.length; i < l; i++)
15266                                                 process(children[i]);
15267                                 }
15268                         };
15269
15270                         function findFormatRoot(container) {
15271                                 var formatRoot;
15272
15273                                 // Find format root
15274                                 each(getParents(container.parentNode).reverse(), function(parent) {
15275                                         var format;
15276
15277                                         // Find format root element
15278                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
15279                                                 // Is the node matching the format we are looking for
15280                                                 format = matchNode(parent, name, vars);
15281                                                 if (format && format.split !== false)
15282                                                         formatRoot = parent;
15283                                         }
15284                                 });
15285
15286                                 return formatRoot;
15287                         };
15288
15289                         function wrapAndSplit(format_root, container, target, split) {
15290                                 var parent, clone, lastClone, firstClone, i, formatRootParent;
15291
15292                                 // Format root found then clone formats and split it
15293                                 if (format_root) {
15294                                         formatRootParent = format_root.parentNode;
15295
15296                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
15297                                                 clone = parent.cloneNode(FALSE);
15298
15299                                                 for (i = 0; i < formatList.length; i++) {
15300                                                         if (removeFormat(formatList[i], vars, clone, clone)) {
15301                                                                 clone = 0;
15302                                                                 break;
15303                                                         }
15304                                                 }
15305
15306                                                 // Build wrapper node
15307                                                 if (clone) {
15308                                                         if (lastClone)
15309                                                                 clone.appendChild(lastClone);
15310
15311                                                         if (!firstClone)
15312                                                                 firstClone = clone;
15313
15314                                                         lastClone = clone;
15315                                                 }
15316                                         }
15317
15318                                         // Never split block elements if the format is mixed
15319                                         if (split && (!format.mixed || !isBlock(format_root)))
15320                                                 container = dom.split(format_root, container);
15321
15322                                         // Wrap container in cloned formats
15323                                         if (lastClone) {
15324                                                 target.parentNode.insertBefore(lastClone, target);
15325                                                 firstClone.appendChild(target);
15326                                         }
15327                                 }
15328
15329                                 return container;
15330                         };
15331
15332                         function splitToFormatRoot(container) {
15333                                 return wrapAndSplit(findFormatRoot(container), container, container, true);
15334                         };
15335
15336                         function unwrap(start) {
15337                                 var node = dom.get(start ? '_start' : '_end'),
15338                                         out = node[start ? 'firstChild' : 'lastChild'];
15339
15340                                 // If the end is placed within the start the result will be removed
15341                                 // So this checks if the out node is a bookmark node if it is it
15342                                 // checks for another more suitable node
15343                                 if (isBookmarkNode(out))
15344                                         out = out[start ? 'firstChild' : 'lastChild'];
15345
15346                                 dom.remove(node, true);
15347
15348                                 return out;
15349                         };
15350
15351                         function removeRngStyle(rng) {
15352                                 var startContainer, endContainer;
15353
15354                                 rng = expandRng(rng, formatList, TRUE);
15355
15356                                 if (format.split) {
15357                                         startContainer = getContainer(rng, TRUE);
15358                                         endContainer = getContainer(rng);
15359
15360                                         if (startContainer != endContainer) {
15361                                                 // Wrap start/end nodes in span element since these might be cloned/moved
15362                                                 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
15363                                                 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
15364
15365                                                 // Split start/end
15366                                                 splitToFormatRoot(startContainer);
15367                                                 splitToFormatRoot(endContainer);
15368
15369                                                 // Unwrap start/end to get real elements again
15370                                                 startContainer = unwrap(TRUE);
15371                                                 endContainer = unwrap();
15372                                         } else
15373                                                 startContainer = endContainer = splitToFormatRoot(startContainer);
15374
15375                                         // Update range positions since they might have changed after the split operations
15376                                         rng.startContainer = startContainer.parentNode;
15377                                         rng.startOffset = nodeIndex(startContainer);
15378                                         rng.endContainer = endContainer.parentNode;
15379                                         rng.endOffset = nodeIndex(endContainer) + 1;
15380                                 }
15381
15382                                 // Remove items between start/end
15383                                 rangeUtils.walk(rng, function(nodes) {
15384                                         each(nodes, function(node) {
15385                                                 process(node);
15386
15387                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
15388                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
15389                                                         removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
15390                                                 }
15391                                         });
15392                                 });
15393                         };
15394
15395                         // Handle node
15396                         if (node) {
15397                                 rng = dom.createRng();
15398                                 rng.setStartBefore(node);
15399                                 rng.setEndAfter(node);
15400                                 removeRngStyle(rng);
15401                                 return;
15402                         }
15403
15404                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
15405                                 bookmark = selection.getBookmark();
15406                                 removeRngStyle(selection.getRng(TRUE));
15407                                 selection.moveToBookmark(bookmark);
15408
15409                                 // 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
15410                                 if (match(name, vars, selection.getStart())) {
15411                                         moveStart(selection.getRng(true));
15412                                 }
15413
15414                                 ed.nodeChanged();
15415                         } else
15416                                 performCaretAction('remove', name, vars);
15417                 };
15418
15419                 function toggle(name, vars, node) {
15420                         var fmt = get(name);
15421
15422                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
15423                                 remove(name, vars, node);
15424                         else
15425                                 apply(name, vars, node);
15426                 };
15427
15428                 function matchNode(node, name, vars, similar) {
15429                         var formatList = get(name), format, i, classes;
15430
15431                         function matchItems(node, format, item_name) {
15432                                 var key, value, items = format[item_name], i;
15433
15434                                 // Check all items
15435                                 if (items) {
15436                                         // Non indexed object
15437                                         if (items.length === undefined) {
15438                                                 for (key in items) {
15439                                                         if (items.hasOwnProperty(key)) {
15440                                                                 if (item_name === 'attributes')
15441                                                                         value = dom.getAttrib(node, key);
15442                                                                 else
15443                                                                         value = getStyle(node, key);
15444
15445                                                                 if (similar && !value && !format.exact)
15446                                                                         return;
15447
15448                                                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
15449                                                                         return;
15450                                                         }
15451                                                 }
15452                                         } else {
15453                                                 // Only one match needed for indexed arrays
15454                                                 for (i = 0; i < items.length; i++) {
15455                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
15456                                                                 return format;
15457                                                 }
15458                                         }
15459                                 }
15460
15461                                 return format;
15462                         };
15463
15464                         if (formatList && node) {
15465                                 // Check each format in list
15466                                 for (i = 0; i < formatList.length; i++) {
15467                                         format = formatList[i];
15468
15469                                         // Name name, attributes, styles and classes
15470                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
15471                                                 // Match classes
15472                                                 if (classes = format.classes) {
15473                                                         for (i = 0; i < classes.length; i++) {
15474                                                                 if (!dom.hasClass(node, classes[i]))
15475                                                                         return;
15476                                                         }
15477                                                 }
15478
15479                                                 return format;
15480                                         }
15481                                 }
15482                         }
15483                 };
15484
15485                 function match(name, vars, node) {
15486                         var startNode, i;
15487
15488                         function matchParents(node) {
15489                                 // Find first node with similar format settings
15490                                 node = dom.getParent(node, function(node) {
15491                                         return !!matchNode(node, name, vars, true);
15492                                 });
15493
15494                                 // Do an exact check on the similar format element
15495                                 return matchNode(node, name, vars);
15496                         };
15497
15498                         // Check specified node
15499                         if (node)
15500                                 return matchParents(node);
15501
15502                         // Check pending formats
15503                         if (selection.isCollapsed()) {
15504                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
15505                                         if (pendingFormats.apply[i].name == name)
15506                                                 return true;
15507                                 }
15508
15509                                 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
15510                                         if (pendingFormats.remove[i].name == name)
15511                                                 return false;
15512                                 }
15513
15514                                 return matchParents(selection.getNode());
15515                         }
15516
15517                         // Check selected node
15518                         node = selection.getNode();
15519                         if (matchParents(node))
15520                                 return TRUE;
15521
15522                         // Check start node if it's different
15523                         startNode = selection.getStart();
15524                         if (startNode != node) {
15525                                 if (matchParents(startNode))
15526                                         return TRUE;
15527                         }
15528
15529                         return FALSE;
15530                 };
15531
15532                 function matchAll(names, vars) {
15533                         var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
15534
15535                         // If the selection is collapsed then check pending formats
15536                         if (selection.isCollapsed()) {
15537                                 for (ni = 0; ni < names.length; ni++) {
15538                                         // If the name is to be removed, then stop it from being added
15539                                         for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
15540                                                 name = names[ni];
15541
15542                                                 if (pendingFormats.remove[i].name == name) {
15543                                                         checkedMap[name] = true;
15544                                                         break;
15545                                                 }
15546                                         }
15547                                 }
15548
15549                                 // If the format is to be applied
15550                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
15551                                         for (ni = 0; ni < names.length; ni++) {
15552                                                 name = names[ni];
15553
15554                                                 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
15555                                                         checkedMap[name] = true;
15556                                                         matchedFormatNames.push(name);
15557                                                 }
15558                                         }
15559                                 }
15560                         }
15561
15562                         // Check start of selection for formats
15563                         startElement = selection.getStart();
15564                         dom.getParent(startElement, function(node) {
15565                                 var i, name;
15566
15567                                 for (i = 0; i < names.length; i++) {
15568                                         name = names[i];
15569
15570                                         if (!checkedMap[name] && matchNode(node, name, vars)) {
15571                                                 checkedMap[name] = true;
15572                                                 matchedFormatNames.push(name);
15573                                         }
15574                                 }
15575                         });
15576
15577                         return matchedFormatNames;
15578                 };
15579
15580                 function canApply(name) {
15581                         var formatList = get(name), startNode, parents, i, x, selector;
15582
15583                         if (formatList) {
15584                                 startNode = selection.getStart();
15585                                 parents = getParents(startNode);
15586
15587                                 for (x = formatList.length - 1; x >= 0; x--) {
15588                                         selector = formatList[x].selector;
15589
15590                                         // Format is not selector based, then always return TRUE
15591                                         if (!selector)
15592                                                 return TRUE;
15593
15594                                         for (i = parents.length - 1; i >= 0; i--) {
15595                                                 if (dom.is(parents[i], selector))
15596                                                         return TRUE;
15597                                         }
15598                                 }
15599                         }
15600
15601                         return FALSE;
15602                 };
15603
15604                 // Expose to public
15605                 tinymce.extend(this, {
15606                         get : get,
15607                         register : register,
15608                         apply : apply,
15609                         remove : remove,
15610                         toggle : toggle,
15611                         match : match,
15612                         matchAll : matchAll,
15613                         matchNode : matchNode,
15614                         canApply : canApply
15615                 });
15616
15617                 // Private functions
15618
15619                 function matchName(node, format) {
15620                         // Check for inline match
15621                         if (isEq(node, format.inline))
15622                                 return TRUE;
15623
15624                         // Check for block match
15625                         if (isEq(node, format.block))
15626                                 return TRUE;
15627
15628                         // Check for selector match
15629                         if (format.selector)
15630                                 return dom.is(node, format.selector);
15631                 };
15632
15633                 function isEq(str1, str2) {
15634                         str1 = str1 || '';
15635                         str2 = str2 || '';
15636
15637                         str1 = '' + (str1.nodeName || str1);
15638                         str2 = '' + (str2.nodeName || str2);
15639
15640                         return str1.toLowerCase() == str2.toLowerCase();
15641                 };
15642
15643                 function getStyle(node, name) {
15644                         var styleVal = dom.getStyle(node, name);
15645
15646                         // Force the format to hex
15647                         if (name == 'color' || name == 'backgroundColor')
15648                                 styleVal = dom.toHex(styleVal);
15649
15650                         // Opera will return bold as 700
15651                         if (name == 'fontWeight' && styleVal == 700)
15652                                 styleVal = 'bold';
15653
15654                         return '' + styleVal;
15655                 };
15656
15657                 function replaceVars(value, vars) {
15658                         if (typeof(value) != "string")
15659                                 value = value(vars);
15660                         else if (vars) {
15661                                 value = value.replace(/%(\w+)/g, function(str, name) {
15662                                         return vars[name] || str;
15663                                 });
15664                         }
15665
15666                         return value;
15667                 };
15668
15669                 function isWhiteSpaceNode(node) {
15670                         return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
15671                 };
15672
15673                 function wrap(node, name, attrs) {
15674                         var wrapper = dom.create(name, attrs);
15675
15676                         node.parentNode.insertBefore(wrapper, node);
15677                         wrapper.appendChild(node);
15678
15679                         return wrapper;
15680                 };
15681
15682                 function expandRng(rng, format, remove) {
15683                         var startContainer = rng.startContainer,
15684                                 startOffset = rng.startOffset,
15685                                 endContainer = rng.endContainer,
15686                                 endOffset = rng.endOffset, sibling, lastIdx, leaf;
15687
15688                         // This function walks up the tree if there is no siblings before/after the node
15689                         function findParentContainer(container, child_name, sibling_name, root) {
15690                                 var parent, child;
15691
15692                                 root = root || dom.getRoot();
15693
15694                                 for (;;) {
15695                                         // Check if we can move up are we at root level or body level
15696                                         parent = container.parentNode;
15697
15698                                         // Stop expanding on block elements or root depending on format
15699                                         if (parent == root || (!format[0].block_expand && isBlock(parent)))
15700                                                 return container;
15701
15702                                         for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
15703                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
15704                                                         return container;
15705
15706                                                 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
15707                                                         return container;
15708                                         }
15709
15710                                         container = container.parentNode;
15711                                 }
15712
15713                                 return container;
15714                         };
15715
15716                         // This function walks down the tree to find the leaf at the selection.
15717                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15718                         function findLeaf(node, offset) {
15719                                 if (offset === undefined)
15720                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15721                                 while (node && node.hasChildNodes()) {
15722                                         node = node.childNodes[offset];
15723                                         if (node)
15724                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15725                                 }
15726                                 return { node: node, offset: offset };
15727                         }
15728
15729                         // If index based start position then resolve it
15730                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15731                                 lastIdx = startContainer.childNodes.length - 1;
15732                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15733
15734                                 if (startContainer.nodeType == 3)
15735                                         startOffset = 0;
15736                         }
15737
15738                         // If index based end position then resolve it
15739                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15740                                 lastIdx = endContainer.childNodes.length - 1;
15741                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15742
15743                                 if (endContainer.nodeType == 3)
15744                                         endOffset = endContainer.nodeValue.length;
15745                         }
15746
15747                         // Exclude bookmark nodes if possible
15748                         if (isBookmarkNode(startContainer.parentNode))
15749                                 startContainer = startContainer.parentNode;
15750
15751                         if (isBookmarkNode(startContainer))
15752                                 startContainer = startContainer.nextSibling || startContainer;
15753
15754                         if (isBookmarkNode(endContainer.parentNode)) {
15755                                 endOffset = dom.nodeIndex(endContainer);
15756                                 endContainer = endContainer.parentNode;
15757                         }
15758
15759                         if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
15760                                 endContainer = endContainer.previousSibling;
15761                                 endOffset = endContainer.length;
15762                         }
15763
15764                         if (format[0].inline) {
15765                                 // Avoid applying formatting to a trailing space.
15766                                 leaf = findLeaf(endContainer, endOffset);
15767                                 if (leaf.node) {
15768                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
15769                                                 leaf = findLeaf(leaf.node.previousSibling);
15770
15771                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
15772                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
15773
15774                                                 if (leaf.offset > 1) {
15775                                                         endContainer = leaf.node;
15776                                                         endContainer.splitText(leaf.offset - 1);
15777                                                 } else if (leaf.node.previousSibling) {
15778                                                         endContainer = leaf.node.previousSibling;
15779                                                 }
15780                                         }
15781                                 }
15782                         }
15783                         
15784                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers
15785                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
15786                         // This will reduce the number of wrapper elements that needs to be created
15787                         // Move start point up the tree
15788                         if (format[0].inline || format[0].block_expand) {
15789                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15790                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15791                         }
15792
15793                         // Expand start/end container to matching selector
15794                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
15795                                 function findSelectorEndPoint(container, sibling_name) {
15796                                         var parents, i, y, curFormat;
15797
15798                                         if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
15799                                                 container = container[sibling_name];
15800
15801                                         parents = getParents(container);
15802                                         for (i = 0; i < parents.length; i++) {
15803                                                 for (y = 0; y < format.length; y++) {
15804                                                         curFormat = format[y];
15805
15806                                                         // If collapsed state is set then skip formats that doesn't match that
15807                                                         if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
15808                                                                 continue;
15809
15810                                                         if (dom.is(parents[i], curFormat.selector))
15811                                                                 return parents[i];
15812                                                 }
15813                                         }
15814
15815                                         return container;
15816                                 };
15817
15818                                 // Find new startContainer/endContainer if there is better one
15819                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
15820                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
15821                         }
15822
15823                         // Expand start/end container to matching block element or text node
15824                         if (format[0].block || format[0].selector) {
15825                                 function findBlockEndPoint(container, sibling_name, sibling_name2) {
15826                                         var node;
15827
15828                                         // Expand to block of similar type
15829                                         if (!format[0].wrapper)
15830                                                 node = dom.getParent(container, format[0].block);
15831
15832                                         // Expand to first wrappable block element or any block element
15833                                         if (!node)
15834                                                 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
15835
15836                                         // Exclude inner lists from wrapping
15837                                         if (node && format[0].wrapper)
15838                                                 node = getParents(node, 'ul,ol').reverse()[0] || node;
15839
15840                                         // Didn't find a block element look for first/last wrappable element
15841                                         if (!node) {
15842                                                 node = container;
15843
15844                                                 while (node[sibling_name] && !isBlock(node[sibling_name])) {
15845                                                         node = node[sibling_name];
15846
15847                                                         // Break on BR but include it will be removed later on
15848                                                         // we can't remove it now since we need to check if it can be wrapped
15849                                                         if (isEq(node, 'br'))
15850                                                                 break;
15851                                                 }
15852                                         }
15853
15854                                         return node || container;
15855                                 };
15856
15857                                 // Find new startContainer/endContainer if there is better one
15858                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');
15859                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');
15860
15861                                 // Non block element then try to expand up the leaf
15862                                 if (format[0].block) {
15863                                         if (!isBlock(startContainer))
15864                                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15865
15866                                         if (!isBlock(endContainer))
15867                                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15868                                 }
15869                         }
15870
15871                         // Setup index for startContainer
15872                         if (startContainer.nodeType == 1) {
15873                                 startOffset = nodeIndex(startContainer);
15874                                 startContainer = startContainer.parentNode;
15875                         }
15876
15877                         // Setup index for endContainer
15878                         if (endContainer.nodeType == 1) {
15879                                 endOffset = nodeIndex(endContainer) + 1;
15880                                 endContainer = endContainer.parentNode;
15881                         }
15882
15883                         // Return new range like object
15884                         return {
15885                                 startContainer : startContainer,
15886                                 startOffset : startOffset,
15887                                 endContainer : endContainer,
15888                                 endOffset : endOffset
15889                         };
15890                 }
15891
15892                 function removeFormat(format, vars, node, compare_node) {
15893                         var i, attrs, stylesModified;
15894
15895                         // Check if node matches format
15896                         if (!matchName(node, format))
15897                                 return FALSE;
15898
15899                         // Should we compare with format attribs and styles
15900                         if (format.remove != 'all') {
15901                                 // Remove styles
15902                                 each(format.styles, function(value, name) {
15903                                         value = replaceVars(value, vars);
15904
15905                                         // Indexed array
15906                                         if (typeof(name) === 'number') {
15907                                                 name = value;
15908                                                 compare_node = 0;
15909                                         }
15910
15911                                         if (!compare_node || isEq(getStyle(compare_node, name), value))
15912                                                 dom.setStyle(node, name, '');
15913
15914                                         stylesModified = 1;
15915                                 });
15916
15917                                 // Remove style attribute if it's empty
15918                                 if (stylesModified && dom.getAttrib(node, 'style') == '') {
15919                                         node.removeAttribute('style');
15920                                         node.removeAttribute('data-mce-style');
15921                                 }
15922
15923                                 // Remove attributes
15924                                 each(format.attributes, function(value, name) {
15925                                         var valueOut;
15926
15927                                         value = replaceVars(value, vars);
15928
15929                                         // Indexed array
15930                                         if (typeof(name) === 'number') {
15931                                                 name = value;
15932                                                 compare_node = 0;
15933                                         }
15934
15935                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
15936                                                 // Keep internal classes
15937                                                 if (name == 'class') {
15938                                                         value = dom.getAttrib(node, name);
15939                                                         if (value) {
15940                                                                 // Build new class value where everything is removed except the internal prefixed classes
15941                                                                 valueOut = '';
15942                                                                 each(value.split(/\s+/), function(cls) {
15943                                                                         if (/mce\w+/.test(cls))
15944                                                                                 valueOut += (valueOut ? ' ' : '') + cls;
15945                                                                 });
15946
15947                                                                 // We got some internal classes left
15948                                                                 if (valueOut) {
15949                                                                         dom.setAttrib(node, name, valueOut);
15950                                                                         return;
15951                                                                 }
15952                                                         }
15953                                                 }
15954
15955                                                 // IE6 has a bug where the attribute doesn't get removed correctly
15956                                                 if (name == "class")
15957                                                         node.removeAttribute('className');
15958
15959                                                 // Remove mce prefixed attributes
15960                                                 if (MCE_ATTR_RE.test(name))
15961                                                         node.removeAttribute('data-mce-' + name);
15962
15963                                                 node.removeAttribute(name);
15964                                         }
15965                                 });
15966
15967                                 // Remove classes
15968                                 each(format.classes, function(value) {
15969                                         value = replaceVars(value, vars);
15970
15971                                         if (!compare_node || dom.hasClass(compare_node, value))
15972                                                 dom.removeClass(node, value);
15973                                 });
15974
15975                                 // Check for non internal attributes
15976                                 attrs = dom.getAttribs(node);
15977                                 for (i = 0; i < attrs.length; i++) {
15978                                         if (attrs[i].nodeName.indexOf('_') !== 0)
15979                                                 return FALSE;
15980                                 }
15981                         }
15982
15983                         // Remove the inline child if it's empty for example <b> or <span>
15984                         if (format.remove != 'none') {
15985                                 removeNode(node, format);
15986                                 return TRUE;
15987                         }
15988                 };
15989
15990                 function removeNode(node, format) {
15991                         var parentNode = node.parentNode, rootBlockElm;
15992
15993                         if (format.block) {
15994                                 if (!forcedRootBlock) {
15995                                         function find(node, next, inc) {
15996                                                 node = getNonWhiteSpaceSibling(node, next, inc);
15997
15998                                                 return !node || (node.nodeName == 'BR' || isBlock(node));
15999                                         };
16000
16001                                         // Append BR elements if needed before we remove the block
16002                                         if (isBlock(node) && !isBlock(parentNode)) {
16003                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
16004                                                         node.insertBefore(dom.create('br'), node.firstChild);
16005
16006                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
16007                                                         node.appendChild(dom.create('br'));
16008                                         }
16009                                 } else {
16010                                         // Wrap the block in a forcedRootBlock if we are at the root of document
16011                                         if (parentNode == dom.getRoot()) {
16012                                                 if (!format.list_block || !isEq(node, format.list_block)) {
16013                                                         each(tinymce.grep(node.childNodes), function(node) {
16014                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
16015                                                                         if (!rootBlockElm)
16016                                                                                 rootBlockElm = wrap(node, forcedRootBlock);
16017                                                                         else
16018                                                                                 rootBlockElm.appendChild(node);
16019                                                                 } else
16020                                                                         rootBlockElm = 0;
16021                                                         });
16022                                                 }
16023                                         }
16024                                 }
16025                         }
16026
16027                         // Never remove nodes that isn't the specified inline element if a selector is specified too
16028                         if (format.selector && format.inline && !isEq(format.inline, node))
16029                                 return;
16030
16031                         dom.remove(node, 1);
16032                 };
16033
16034                 function getNonWhiteSpaceSibling(node, next, inc) {
16035                         if (node) {
16036                                 next = next ? 'nextSibling' : 'previousSibling';
16037
16038                                 for (node = inc ? node : node[next]; node; node = node[next]) {
16039                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node))
16040                                                 return node;
16041                                 }
16042                         }
16043                 };
16044
16045                 function isBookmarkNode(node) {
16046                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
16047                 };
16048
16049                 function mergeSiblings(prev, next) {
16050                         var marker, sibling, tmpSibling;
16051
16052                         function compareElements(node1, node2) {
16053                                 // Not the same name
16054                                 if (node1.nodeName != node2.nodeName)
16055                                         return FALSE;
16056
16057                                 function getAttribs(node) {
16058                                         var attribs = {};
16059
16060                                         each(dom.getAttribs(node), function(attr) {
16061                                                 var name = attr.nodeName.toLowerCase();
16062
16063                                                 // Don't compare internal attributes or style
16064                                                 if (name.indexOf('_') !== 0 && name !== 'style')
16065                                                         attribs[name] = dom.getAttrib(node, name);
16066                                         });
16067
16068                                         return attribs;
16069                                 };
16070
16071                                 function compareObjects(obj1, obj2) {
16072                                         var value, name;
16073
16074                                         for (name in obj1) {
16075                                                 // Obj1 has item obj2 doesn't have
16076                                                 if (obj1.hasOwnProperty(name)) {
16077                                                         value = obj2[name];
16078
16079                                                         // Obj2 doesn't have obj1 item
16080                                                         if (value === undefined)
16081                                                                 return FALSE;
16082
16083                                                         // Obj2 item has a different value
16084                                                         if (obj1[name] != value)
16085                                                                 return FALSE;
16086
16087                                                         // Delete similar value
16088                                                         delete obj2[name];
16089                                                 }
16090                                         }
16091
16092                                         // Check if obj 2 has something obj 1 doesn't have
16093                                         for (name in obj2) {
16094                                                 // Obj2 has item obj1 doesn't have
16095                                                 if (obj2.hasOwnProperty(name))
16096                                                         return FALSE;
16097                                         }
16098
16099                                         return TRUE;
16100                                 };
16101
16102                                 // Attribs are not the same
16103                                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))
16104                                         return FALSE;
16105
16106                                 // Styles are not the same
16107                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
16108                                         return FALSE;
16109
16110                                 return TRUE;
16111                         };
16112
16113                         // Check if next/prev exists and that they are elements
16114                         if (prev && next) {
16115                                 function findElementSibling(node, sibling_name) {
16116                                         for (sibling = node; sibling; sibling = sibling[sibling_name]) {
16117                                                 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
16118                                                         return node;
16119
16120                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
16121                                                         return sibling;
16122                                         }
16123
16124                                         return node;
16125                                 };
16126
16127                                 // If previous sibling is empty then jump over it
16128                                 prev = findElementSibling(prev, 'previousSibling');
16129                                 next = findElementSibling(next, 'nextSibling');
16130
16131                                 // Compare next and previous nodes
16132                                 if (compareElements(prev, next)) {
16133                                         // Append nodes between
16134                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {
16135                                                 tmpSibling = sibling;
16136                                                 sibling = sibling.nextSibling;
16137                                                 prev.appendChild(tmpSibling);
16138                                         }
16139
16140                                         // Remove next node
16141                                         dom.remove(next);
16142
16143                                         // Move children into prev node
16144                                         each(tinymce.grep(next.childNodes), function(node) {
16145                                                 prev.appendChild(node);
16146                                         });
16147
16148                                         return prev;
16149                                 }
16150                         }
16151
16152                         return next;
16153                 };
16154
16155                 function isTextBlock(name) {
16156                         return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
16157                 };
16158
16159                 function getContainer(rng, start) {
16160                         var container, offset, lastIdx;
16161
16162                         container = rng[start ? 'startContainer' : 'endContainer'];
16163                         offset = rng[start ? 'startOffset' : 'endOffset'];
16164
16165                         if (container.nodeType == 1) {
16166                                 lastIdx = container.childNodes.length - 1;
16167
16168                                 if (!start && offset)
16169                                         offset--;
16170
16171                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];
16172                         }
16173
16174                         return container;
16175                 };
16176
16177                 function performCaretAction(type, name, vars) {
16178                         var i, currentPendingFormats = pendingFormats[type],
16179                                 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
16180
16181                         function hasPending() {
16182                                 return pendingFormats.apply.length || pendingFormats.remove.length;
16183                         };
16184
16185                         function resetPending() {
16186                                 pendingFormats.apply = [];
16187                                 pendingFormats.remove = [];
16188                         };
16189
16190                         function perform(caret_node) {
16191                                 // Apply pending formats
16192                                 each(pendingFormats.apply.reverse(), function(item) {
16193                                         apply(item.name, item.vars, caret_node);
16194
16195                                         // Colored nodes should be underlined so that the color of the underline matches the text color.
16196                                         if (item.name === 'forecolor' && item.vars.value)
16197                                                 processUnderlineAndColor(caret_node.parentNode);
16198                                 });
16199
16200                                 // Remove pending formats
16201                                 each(pendingFormats.remove.reverse(), function(item) {
16202                                         remove(item.name, item.vars, caret_node);
16203                                 });
16204
16205                                 dom.remove(caret_node, 1);
16206                                 resetPending();
16207                         };
16208
16209                         // Check if it already exists then ignore it
16210                         for (i = currentPendingFormats.length - 1; i >= 0; i--) {
16211                                 if (currentPendingFormats[i].name == name)
16212                                         return;
16213                         }
16214
16215                         currentPendingFormats.push({name : name, vars : vars});
16216
16217                         // Check if it's in the other type, then remove it
16218                         for (i = otherPendingFormats.length - 1; i >= 0; i--) {
16219                                 if (otherPendingFormats[i].name == name)
16220                                         otherPendingFormats.splice(i, 1);
16221                         }
16222
16223                         // Pending apply or remove formats
16224                         if (hasPending()) {
16225                                 ed.getDoc().execCommand('FontName', false, 'mceinline');
16226                                 pendingFormats.lastRng = selection.getRng();
16227
16228                                 // IE will convert the current word
16229                                 each(dom.select('font,span'), function(node) {
16230                                         var bookmark;
16231
16232                                         if (isCaretNode(node)) {
16233                                                 bookmark = selection.getBookmark();
16234                                                 perform(node);
16235                                                 selection.moveToBookmark(bookmark);
16236                                                 ed.nodeChanged();
16237                                         }
16238                                 });
16239
16240                                 // Only register listeners once if we need to
16241                                 if (!pendingFormats.isListening && hasPending()) {
16242                                         pendingFormats.isListening = true;
16243                                         function performPendingFormat(node, textNode) {
16244                                                 var rng = dom.createRng();
16245                                                 perform(node);
16246
16247                                                 rng.setStart(textNode, textNode.nodeValue.length);
16248                                                 rng.setEnd(textNode, textNode.nodeValue.length);
16249                                                 selection.setRng(rng);
16250                                                 ed.nodeChanged();
16251                                         }
16252                                         var enterKeyPressed = false;
16253
16254                                         each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
16255                                                 ed[event].addToTop(function(ed, e) {
16256                                                         if (e.keyCode==13 && !e.shiftKey) {
16257                                                                 enterKeyPressed = true;
16258                                                                 return;
16259                                                         }
16260                                                         // Do we have pending formats and is the selection moved has moved
16261                                                         if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
16262                                                                 var foundCaret = false;
16263                                                                 each(dom.select('font,span'), function(node) {
16264                                                                         var textNode, rng;
16265
16266                                                                         // Look for marker
16267                                                                         if (isCaretNode(node)) {
16268                                                                                 foundCaret = true;
16269                                                                                 textNode = node.firstChild;
16270
16271                                                                                 // Find the first text node within node
16272                                                                                 while (textNode && textNode.nodeType != 3)
16273                                                                                         textNode = textNode.firstChild;
16274
16275                                                                                 if (textNode) 
16276                                                                                         performPendingFormat(node, textNode);
16277                                                                                 else
16278                                                                                         dom.remove(node);
16279                                                                         }
16280                                                                 });
16281                                                                 
16282                                                                 // no caret - so we are 
16283                                                                 if (enterKeyPressed && !foundCaret) {
16284                                                                         var node = selection.getNode();
16285                                                                         var textNode = node;
16286
16287                                                                         // Find the first text node within node
16288                                                                         while (textNode && textNode.nodeType != 3)
16289                                                                                 textNode = textNode.firstChild;
16290                                                                         if (textNode) {
16291                                                                                 node=textNode.parentNode;
16292                                                                                 while (!isBlock(node)){
16293                                                                                         node=node.parentNode;
16294                                                                                 }
16295                                                                                 performPendingFormat(node, textNode);
16296                                                                         }
16297                                                                 }
16298
16299                                                                 // Always unbind and clear pending styles on keyup
16300                                                                 if (e.type == 'keyup' || e.type == 'mouseup') {
16301                                                                         resetPending();
16302                                                                         enterKeyPressed=false;
16303                                                                 }
16304                                                         }
16305                                                 });
16306                                         });
16307                                 }
16308                         }
16309                 };
16310         };
16311 })(tinymce);
16312
16313 tinymce.onAddEditor.add(function(tinymce, ed) {
16314         var filters, fontSizes, dom, settings = ed.settings;
16315
16316         if (settings.inline_styles) {
16317                 fontSizes = tinymce.explode(settings.font_size_style_values);
16318
16319                 function replaceWithSpan(node, styles) {
16320                         tinymce.each(styles, function(value, name) {
16321                                 if (value)
16322                                         dom.setStyle(node, name, value);
16323                         });
16324
16325                         dom.rename(node, 'span');
16326                 };
16327
16328                 filters = {
16329                         font : function(dom, node) {
16330                                 replaceWithSpan(node, {
16331                                         backgroundColor : node.style.backgroundColor,
16332                                         color : node.color,
16333                                         fontFamily : node.face,
16334                                         fontSize : fontSizes[parseInt(node.size) - 1]
16335                                 });
16336                         },
16337
16338                         u : function(dom, node) {
16339                                 replaceWithSpan(node, {
16340                                         textDecoration : 'underline'
16341                                 });
16342                         },
16343
16344                         strike : function(dom, node) {
16345                                 replaceWithSpan(node, {
16346                                         textDecoration : 'line-through'
16347                                 });
16348                         }
16349                 };
16350
16351                 function convert(editor, params) {
16352                         dom = editor.dom;
16353
16354                         if (settings.convert_fonts_to_spans) {
16355                                 tinymce.each(dom.select('font,u,strike', params.node), function(node) {
16356                                         filters[node.nodeName.toLowerCase()](ed.dom, node);
16357                                 });
16358                         }
16359                 };
16360
16361                 ed.onPreProcess.add(convert);
16362                 ed.onSetContent.add(convert);
16363
16364                 ed.onInit.add(function() {
16365                         ed.selection.onSetContent.add(convert);
16366                 });
16367         }
16368 });
16369